From c8e8d86ddce78254509061625111fcd3f34c3d2f Mon Sep 17 00:00:00 2001 From: _Mads <75088349+TFSMads@users.noreply.github.com> Date: Thu, 9 Jun 2022 16:32:12 +0200 Subject: [PATCH 001/619] Changed drop effect from check against Material.AIR to Material#isAir (#4795) --- src/main/java/ch/njol/skript/effects/EffDrop.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/effects/EffDrop.java b/src/main/java/ch/njol/skript/effects/EffDrop.java index f28f74eac7d..846bf7fbaee 100644 --- a/src/main/java/ch/njol/skript/effects/EffDrop.java +++ b/src/main/java/ch/njol/skript/effects/EffDrop.java @@ -85,7 +85,7 @@ public void execute(Event e) { if (o instanceof ItemStack) o = new ItemType((ItemStack) o); for (ItemStack is : ((ItemType) o).getItem().getAll()) { - if (is.getType() != Material.AIR && is.getAmount() > 0) { + if (!is.getType().isAir() && is.getAmount() > 0) { if (useVelocity) { lastSpawned = l.getWorld().dropItemNaturally(itemDropLoc, is); } else { From 31e27552b32b500f6a8e5c96931bde9583ff084d Mon Sep 17 00:00:00 2001 From: _Mads <75088349+TFSMads@users.noreply.github.com> Date: Fri, 10 Jun 2022 20:20:19 +0200 Subject: [PATCH 002/619] EffDrop change Material#isAir since the method is only available in versions 1.15 and later (#4798) --- .../java/ch/njol/skript/effects/EffDrop.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/effects/EffDrop.java b/src/main/java/ch/njol/skript/effects/EffDrop.java index 846bf7fbaee..ee0e99f645f 100644 --- a/src/main/java/ch/njol/skript/effects/EffDrop.java +++ b/src/main/java/ch/njol/skript/effects/EffDrop.java @@ -85,7 +85,7 @@ public void execute(Event e) { if (o instanceof ItemStack) o = new ItemType((ItemStack) o); for (ItemStack is : ((ItemType) o).getItem().getAll()) { - if (!is.getType().isAir() && is.getAmount() > 0) { + if (!isAir(is.getType()) && is.getAmount() > 0) { if (useVelocity) { lastSpawned = l.getWorld().dropItemNaturally(itemDropLoc, is); } else { @@ -101,6 +101,21 @@ public void execute(Event e) { } } + // Only 1.15 and versions after have Material#isAir method + private static final boolean IS_AIR_EXISTS = Skript.methodExists(Material.class, "isAir"); + // Version 1.14 have multiple air types but no Material#isAir method + private static final boolean OTHER_AIR_EXISTS = Skript.isRunningMinecraft(1, 14); + + private static boolean isAir(Material type) { + if (IS_AIR_EXISTS) { + return type.isAir(); + } else if (OTHER_AIR_EXISTS) { + return type == Material.AIR || type == Material.CAVE_AIR || type == Material.VOID_AIR; + } + // All versions prior to 1.14 only have 1 air type + return type == Material.AIR; + } + @Override public String toString(@Nullable Event e, boolean debug) { return "drop " + drops.toString(e, debug) + " " + locations.toString(e, debug); From 0c74e07bdd9ae12cce9c6f1de716608bc72b05b3 Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Mon, 13 Jun 2022 19:39:31 +0200 Subject: [PATCH 003/619] Remove old tests (#4767) * Make tests error when non-Exception Throwable during runtime * 1.18 test updated to 1.18.2 Removed tests 1.9, 1.10, 1.11 and 1.12 --- build.gradle | 2 +- src/main/java/ch/njol/skript/Skript.java | 10 +++++++++- .../java/ch/njol/skript/lang/TriggerItem.java | 4 ++++ .../skript/environments/extra/paper-1.11.2.json | 17 ----------------- .../{paper-1.10.2.json => paper-1.15.2.json} | 4 ++-- .../skript/environments/main/paper-1.12.2.json | 17 ----------------- .../{extra => main}/paper-1.17.1.json | 0 .../{paper-1.18.1.json => paper-1.18.2.json} | 4 ++-- .../skript/environments/main/paper-1.9.4.json | 17 ----------------- 9 files changed, 18 insertions(+), 57 deletions(-) delete mode 100644 src/test/skript/environments/extra/paper-1.11.2.json rename src/test/skript/environments/extra/{paper-1.10.2.json => paper-1.15.2.json} (85%) delete mode 100644 src/test/skript/environments/main/paper-1.12.2.json rename src/test/skript/environments/{extra => main}/paper-1.17.1.json (100%) rename src/test/skript/environments/main/{paper-1.18.1.json => paper-1.18.2.json} (85%) delete mode 100644 src/test/skript/environments/main/paper-1.9.4.json diff --git a/build.gradle b/build.gradle index f1b900fa694..df8348f0b65 100644 --- a/build.gradle +++ b/build.gradle @@ -189,7 +189,7 @@ void createTestTask(String name, String environments, boolean devMode) { } // Register different Skript testing tasks -createTestTask('quickTest', 'src/test/skript/environments/main/paper-1.18.1.json', false) +createTestTask('quickTest', 'src/test/skript/environments/main/paper-1.18.2.json', false) createTestTask('skriptTest', 'src/test/skript/environments/main', false) createTestTask('skriptTestFull', 'src/test/skript/environments/', false) createTestTask('skriptTestDev', 'src/test/skript/environments/main/' + (project.property('testEnv') == null diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index a477904ed6b..499b9cd5cb4 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -1575,7 +1575,15 @@ public static EmptyStacktraceException exception(final @Nullable Throwable cause * Set to true when an exception is thrown. */ private static boolean errored = false; - + + /** + * Mark that an exception has occurred at some point during runtime. + * Only used for Skript's testing system. + */ + public static void markErrored() { + errored = true; + } + /** * Used if something happens that shouldn't happen * diff --git a/src/main/java/ch/njol/skript/lang/TriggerItem.java b/src/main/java/ch/njol/skript/lang/TriggerItem.java index a3a45289936..d862f024c8b 100644 --- a/src/main/java/ch/njol/skript/lang/TriggerItem.java +++ b/src/main/java/ch/njol/skript/lang/TriggerItem.java @@ -98,6 +98,10 @@ public static boolean walk(final TriggerItem start, final Event e) { } catch (final Exception ex) { if (ex.getStackTrace().length != 0) // empty exceptions have already been printed Skript.exception(ex, i); + } catch (Throwable throwable) { + // not all Throwables are Exceptions, but we usually don't want to catch them (without rethrowing) + Skript.markErrored(); + throw throwable; } return false; } diff --git a/src/test/skript/environments/extra/paper-1.11.2.json b/src/test/skript/environments/extra/paper-1.11.2.json deleted file mode 100644 index f28206f18b4..00000000000 --- a/src/test/skript/environments/extra/paper-1.11.2.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "paper-1.11.2", - "resources": [ - {"source": "server.properties.generic", "target": "server.properties"} - ], - "paperDownloads": [ - { - "version": "1.11.2", - "target": "paperclip.jar" - } - ], - "skriptTarget": "plugins/Skript.jar", - "commandLine": [ - "-Dcom.mojang.eula.agree=true", - "-jar", "paperclip.jar" - ] -} diff --git a/src/test/skript/environments/extra/paper-1.10.2.json b/src/test/skript/environments/extra/paper-1.15.2.json similarity index 85% rename from src/test/skript/environments/extra/paper-1.10.2.json rename to src/test/skript/environments/extra/paper-1.15.2.json index e36de63845b..b7075f12c6a 100644 --- a/src/test/skript/environments/extra/paper-1.10.2.json +++ b/src/test/skript/environments/extra/paper-1.15.2.json @@ -1,11 +1,11 @@ { - "name": "paper-1.10.2", + "name": "paper-1.15.2", "resources": [ {"source": "server.properties.generic", "target": "server.properties"} ], "paperDownloads": [ { - "version": "1.10.2", + "version": "1.15.2", "target": "paperclip.jar" } ], diff --git a/src/test/skript/environments/main/paper-1.12.2.json b/src/test/skript/environments/main/paper-1.12.2.json deleted file mode 100644 index 62634e51897..00000000000 --- a/src/test/skript/environments/main/paper-1.12.2.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "paper-1.12.2", - "resources": [ - {"source": "server.properties.generic", "target": "server.properties"} - ], - "paperDownloads": [ - { - "version": "1.12.2", - "target": "paperclip.jar" - } - ], - "skriptTarget": "plugins/Skript.jar", - "commandLine": [ - "-Dcom.mojang.eula.agree=true", - "-jar", "paperclip.jar" - ] -} diff --git a/src/test/skript/environments/extra/paper-1.17.1.json b/src/test/skript/environments/main/paper-1.17.1.json similarity index 100% rename from src/test/skript/environments/extra/paper-1.17.1.json rename to src/test/skript/environments/main/paper-1.17.1.json diff --git a/src/test/skript/environments/main/paper-1.18.1.json b/src/test/skript/environments/main/paper-1.18.2.json similarity index 85% rename from src/test/skript/environments/main/paper-1.18.1.json rename to src/test/skript/environments/main/paper-1.18.2.json index 6963e8cb35c..406bdd99c06 100644 --- a/src/test/skript/environments/main/paper-1.18.1.json +++ b/src/test/skript/environments/main/paper-1.18.2.json @@ -1,11 +1,11 @@ { - "name": "paper-1.18.1", + "name": "paper-1.18.2", "resources": [ {"source": "server.properties.generic", "target": "server.properties"} ], "paperDownloads": [ { - "version": "1.18.1", + "version": "1.18.2", "target": "paperclip.jar" } ], diff --git a/src/test/skript/environments/main/paper-1.9.4.json b/src/test/skript/environments/main/paper-1.9.4.json deleted file mode 100644 index 5df0e4ee803..00000000000 --- a/src/test/skript/environments/main/paper-1.9.4.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "paper-1.9.4", - "resources": [ - {"source": "server.properties.generic", "target": "server.properties"} - ], - "paperDownloads": [ - { - "version": "1.9.4", - "target": "paperclip.jar" - } - ], - "skriptTarget": "plugins/Skript.jar", - "commandLine": [ - "-Dcom.mojang.eula.agree=true", - "-jar", "paperclip.jar" - ] -} From 7ece3f70f40b2aa6f19d8ced55f85a268f2fc1db Mon Sep 17 00:00:00 2001 From: APickledWalrus <apickledwalrus@gmail.com> Date: Mon, 20 Jun 2022 13:00:31 -0400 Subject: [PATCH 004/619] Fix Locale-related String conversion issues (#4822) --- build.gradle | 2 +- src/main/java/ch/njol/skript/ScriptLoader.java | 8 ++++---- .../ch/njol/skript/SkriptCommandTabCompleter.java | 2 +- src/main/java/ch/njol/skript/aliases/Aliases.java | 5 +++-- .../java/ch/njol/skript/aliases/AliasesParser.java | 3 ++- .../ch/njol/skript/classes/data/SkriptClasses.java | 4 ++-- .../java/ch/njol/skript/command/CommandHelp.java | 3 ++- src/main/java/ch/njol/skript/command/Commands.java | 7 ++++--- .../java/ch/njol/skript/command/ScriptCommand.java | 5 +++-- src/main/java/ch/njol/skript/doc/HTMLGenerator.java | 2 +- src/main/java/ch/njol/skript/effects/EffLog.java | 3 ++- .../ch/njol/skript/expressions/ExprCommandInfo.java | 3 ++- src/main/java/ch/njol/skript/lang/SkriptParser.java | 3 ++- .../java/ch/njol/skript/lang/UnparsedLiteral.java | 4 ++-- .../ch/njol/skript/lang/function/Parameter.java | 4 +++- .../java/ch/njol/skript/localization/Language.java | 2 +- .../njol/skript/patterns/LiteralPatternElement.java | 4 +++- .../java/ch/njol/skript/patterns/SkriptPattern.java | 3 ++- .../java/ch/njol/skript/registrations/Classes.java | 3 ++- .../java/ch/njol/skript/util/EnchantmentType.java | 5 +++-- .../java/ch/njol/skript/util/PotionDataUtils.java | 2 +- .../java/ch/njol/skript/util/PotionEffectUtils.java | 5 +++-- src/main/java/ch/njol/skript/util/SkriptColor.java | 3 ++- .../java/ch/njol/skript/util/StructureType.java | 3 ++- src/main/java/ch/njol/skript/util/Timespan.java | 9 +++++---- src/main/java/ch/njol/skript/util/Utils.java | 13 +++++++------ .../java/ch/njol/skript/util/chat/ChatMessages.java | 3 ++- .../ch/njol/skript/util/chat/MessageComponent.java | 2 +- src/main/java/ch/njol/util/Kleenean.java | 4 +++- 29 files changed, 71 insertions(+), 48 deletions(-) diff --git a/build.gradle b/build.gradle index df8348f0b65..10d0b072cb5 100644 --- a/build.gradle +++ b/build.gradle @@ -142,7 +142,7 @@ tasks.create('testNaming') { // Regression tests for (def file : project.file('src/test/skript/tests/regressions').listFiles()) { def name = file.getName() - if (name.toLowerCase() != name) { + if (name.toLowerCase(Locale.ENGLISH) != name) { throw new InvalidUserDataException('invalid test name: ' + name) } } diff --git a/src/main/java/ch/njol/skript/ScriptLoader.java b/src/main/java/ch/njol/skript/ScriptLoader.java index a0a987b96f7..f5a2842cebf 100644 --- a/src/main/java/ch/njol/skript/ScriptLoader.java +++ b/src/main/java/ch/njol/skript/ScriptLoader.java @@ -701,7 +701,7 @@ private static ScriptInfo loadScript(@Nullable Config config) { if (!SkriptParser.validateLine(event)) continue; - if (event.toLowerCase().startsWith("command ")) { + if (event.toLowerCase(Locale.ENGLISH).startsWith("command ")) { getParser().setCurrentEvent("command", CommandEvent.class); @@ -715,7 +715,7 @@ private static ScriptInfo loadScript(@Nullable Config config) { getParser().deleteCurrentEvent(); continue; - } else if (event.toLowerCase().startsWith("function ")) { + } else if (event.toLowerCase(Locale.ENGLISH).startsWith("function ")) { getParser().setCurrentEvent("function", FunctionEvent.class); @@ -964,7 +964,7 @@ public static Config loadStructure(Config config) { if (!SkriptParser.validateLine(event)) continue; - if (event.toLowerCase().startsWith("function ")) { + if (event.toLowerCase(Locale.ENGLISH).startsWith("function ")) { getParser().setCurrentEvent("function", FunctionEvent.class); @@ -1163,7 +1163,7 @@ static Trigger loadTrigger(SectionNode node) { assert false : node; return null; } - if (event.toLowerCase().startsWith("on ")) + if (event.toLowerCase(Locale.ENGLISH).startsWith("on ")) event = "" + event.substring("on ".length()); NonNullPair<SkriptEventInfo<?>, SkriptEvent> parsedEvent = diff --git a/src/main/java/ch/njol/skript/SkriptCommandTabCompleter.java b/src/main/java/ch/njol/skript/SkriptCommandTabCompleter.java index ae3050db616..10cc7649a6f 100644 --- a/src/main/java/ch/njol/skript/SkriptCommandTabCompleter.java +++ b/src/main/java/ch/njol/skript/SkriptCommandTabCompleter.java @@ -56,7 +56,7 @@ public List<String> onTabComplete(CommandSender sender, Command command, String // Live update, this will get all old and new (even not loaded) scripts Files.walk(scripts.toPath()) .map(Path::toFile) - .filter(f -> (!f.isDirectory() && f.getName().toLowerCase().endsWith(".sk")) || + .filter(f -> (!f.isDirectory() && StringUtils.endsWithIgnoreCase(f.getName(), ".sk")) || f.isDirectory()) // filter folders and skript files only .filter(f -> { // Filtration for enable, disable and reload if (args[0].equalsIgnoreCase("enable")) diff --git a/src/main/java/ch/njol/skript/aliases/Aliases.java b/src/main/java/ch/njol/skript/aliases/Aliases.java index 87c01211ad2..d812ce14aa3 100644 --- a/src/main/java/ch/njol/skript/aliases/Aliases.java +++ b/src/main/java/ch/njol/skript/aliases/Aliases.java @@ -28,6 +28,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -269,7 +270,7 @@ public static ItemType parseItemType(String s) { t.setAmount(1); } - String lc = s.toLowerCase(); + String lc = s.toLowerCase(Locale.ENGLISH); String of = Language.getSpaced("enchantments.of").toLowerCase(); int c = -1; outer: while ((c = lc.indexOf(of, c + 1)) != -1) { @@ -335,7 +336,7 @@ private static ItemType parseType(final String s, final ItemType t, final boolea @Nullable private static ItemType getAlias(final String s) { ItemType i; - String lc = "" + s.toLowerCase(); + String lc = "" + s.toLowerCase(Locale.ENGLISH); final Matcher m = p_any.matcher(lc); if (m.matches()) { lc = "" + m.group(m.groupCount()); diff --git a/src/main/java/ch/njol/skript/aliases/AliasesParser.java b/src/main/java/ch/njol/skript/aliases/AliasesParser.java index 4d2ba8fcfed..2faf25dd891 100644 --- a/src/main/java/ch/njol/skript/aliases/AliasesParser.java +++ b/src/main/java/ch/njol/skript/aliases/AliasesParser.java @@ -23,6 +23,7 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.function.Function; @@ -682,7 +683,7 @@ protected void loadSingleAlias(Map<String, Variation> variations, String item) { Skript.warning(m_empty_alias.toString()); } else { // Intern id to save some memory - id = id.toLowerCase().intern(); + id = id.toLowerCase(Locale.ENGLISH).intern(); assert id != null; try { // Create singular and plural forms of the alias 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 f7062ec2e57..fd7eb69bfa3 100644 --- a/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java @@ -174,7 +174,7 @@ public String toString(final WeatherType o, final int flags) { @Override public String toVariableNameString(final WeatherType o) { - return "" + o.name().toLowerCase(); + return "" + o.name().toLowerCase(Locale.ENGLISH); } }) @@ -702,7 +702,7 @@ public String toString(final StructureType o, final int flags) { @Override public String toVariableNameString(final StructureType o) { - return "" + o.name().toLowerCase(); + return "" + o.name().toLowerCase(Locale.ENGLISH); } }).serializer(new EnumSerializer<>(StructureType.class))); diff --git a/src/main/java/ch/njol/skript/command/CommandHelp.java b/src/main/java/ch/njol/skript/command/CommandHelp.java index dd619557c19..8fcf6f0a3b4 100644 --- a/src/main/java/ch/njol/skript/command/CommandHelp.java +++ b/src/main/java/ch/njol/skript/command/CommandHelp.java @@ -22,6 +22,7 @@ import static org.bukkit.ChatColor.RESET; import java.util.LinkedHashMap; +import java.util.Locale; import java.util.Map.Entry; import org.bukkit.command.CommandSender; @@ -120,7 +121,7 @@ private boolean test(final CommandSender sender, final String[] args, final int showHelp(sender); return false; } - final Object help = arguments.get(args[index].toLowerCase()); + final Object help = arguments.get(args[index].toLowerCase(Locale.ENGLISH)); if (help == null && wildcardArg == null) { showHelp(sender, m_invalid_argument.toString(argsColor + args[index])); return false; diff --git a/src/main/java/ch/njol/skript/command/Commands.java b/src/main/java/ch/njol/skript/command/Commands.java index b1ae02be998..44b14978a80 100644 --- a/src/main/java/ch/njol/skript/command/Commands.java +++ b/src/main/java/ch/njol/skript/command/Commands.java @@ -25,6 +25,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; @@ -241,7 +242,7 @@ public Boolean call() throws Exception { */ static boolean handleCommand(final CommandSender sender, final String command) { final String[] cmd = command.split("\\s+", 2); - cmd[0] = cmd[0].toLowerCase(); + cmd[0] = cmd[0].toLowerCase(Locale.ENGLISH); if (cmd[0].endsWith("?")) { final ScriptCommand c = commands.get(cmd[0].substring(0, cmd[0].length() - 1)); if (c != null) { @@ -346,7 +347,7 @@ public static ScriptCommand loadCommand(final SectionNode node, final boolean al final boolean a = m.matches(); assert a; - final String command = "" + m.group(1).toLowerCase(); + final String command = "" + m.group(1).toLowerCase(Locale.ENGLISH); final ScriptCommand existingCommand = commands.get(command); if (alsoRegister && existingCommand != null && existingCommand.getLabel().equals(command)) { final File f = existingCommand.getScript(); @@ -530,7 +531,7 @@ public static void registerCommand(final ScriptCommand command) { } commands.put(command.getLabel(), command); for (final String alias : command.getActiveAliases()) { - commands.put(alias.toLowerCase(), command); + commands.put(alias.toLowerCase(Locale.ENGLISH), command); } command.registerHelp(); } diff --git a/src/main/java/ch/njol/skript/command/ScriptCommand.java b/src/main/java/ch/njol/skript/command/ScriptCommand.java index 007cbec8871..791472d0b78 100644 --- a/src/main/java/ch/njol/skript/command/ScriptCommand.java +++ b/src/main/java/ch/njol/skript/command/ScriptCommand.java @@ -27,6 +27,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Set; @@ -131,7 +132,7 @@ public ScriptCommand(final File script, final String name, final String pattern, @Nullable VariableString cooldownStorage, final int executableBy, final List<TriggerItem> items) { Validate.notNull(name, pattern, arguments, description, usage, aliases, items); this.name = name; - label = "" + name.toLowerCase(); + label = "" + name.toLowerCase(Locale.ENGLISH); this.permission = permission; if (permissionMessage == null) { VariableString defaultMsg = VariableString.newInstance(Language.get("commands.no permission message")); @@ -321,7 +322,7 @@ public void register(final SimpleCommandMap commandMap, final Map<String, Comman aliases.remove(label); final Iterator<String> as = activeAliases.iterator(); while (as.hasNext()) { - final String lowerAlias = as.next().toLowerCase(); + final String lowerAlias = as.next().toLowerCase(Locale.ENGLISH); if (knownCommands.containsKey(lowerAlias) && (aliases == null || !aliases.contains(lowerAlias))) { as.remove(); continue; diff --git a/src/main/java/ch/njol/skript/doc/HTMLGenerator.java b/src/main/java/ch/njol/skript/doc/HTMLGenerator.java index 69f42460397..73dceca1944 100644 --- a/src/main/java/ch/njol/skript/doc/HTMLGenerator.java +++ b/src/main/java/ch/njol/skript/doc/HTMLGenerator.java @@ -201,7 +201,7 @@ public void generate() { if (filesInside.isDirectory()) continue; - if (!filesInside.getName().toLowerCase().endsWith(".png")) { // Copy images + if (!filesInside.getName().toLowerCase(Locale.ENGLISH).endsWith(".png")) { // Copy images writeFile(new File(fileTo + "/" + filesInside.getName()), readFile(filesInside)); } diff --git a/src/main/java/ch/njol/skript/effects/EffLog.java b/src/main/java/ch/njol/skript/effects/EffLog.java index 038c62c5607..944372d2de7 100644 --- a/src/main/java/ch/njol/skript/effects/EffLog.java +++ b/src/main/java/ch/njol/skript/effects/EffLog.java @@ -24,6 +24,7 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.HashMap; +import java.util.Locale; import java.util.logging.Level; import org.bukkit.event.Event; @@ -90,7 +91,7 @@ protected void execute(final Event e) { for (final String message : messages.getArray(e)) { if (files != null) { for (String s : files.getArray(e)) { - s = s.toLowerCase(); + s = s.toLowerCase(Locale.ENGLISH); if (!s.endsWith(".log")) s += ".log"; if (s.equals("server.log")) { diff --git a/src/main/java/ch/njol/skript/expressions/ExprCommandInfo.java b/src/main/java/ch/njol/skript/expressions/ExprCommandInfo.java index f41ee12938d..1403494945d 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprCommandInfo.java +++ b/src/main/java/ch/njol/skript/expressions/ExprCommandInfo.java @@ -19,6 +19,7 @@ package ch.njol.skript.expressions; import java.util.ArrayList; +import java.util.Locale; import java.util.Objects; import org.bukkit.command.Command; @@ -153,6 +154,6 @@ public Class<? extends String> getReturnType() { @Override public String toString(@Nullable Event e, boolean debug) { - return "the " + type.name().toLowerCase().replace("_", " ") + " of command " + commandName.toString(e, debug); + return "the " + type.name().toLowerCase(Locale.ENGLISH).replace("_", " ") + " of command " + commandName.toString(e, debug); } } diff --git a/src/main/java/ch/njol/skript/lang/SkriptParser.java b/src/main/java/ch/njol/skript/lang/SkriptParser.java index 8411deb7a6f..2f72bf326ed 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptParser.java +++ b/src/main/java/ch/njol/skript/lang/SkriptParser.java @@ -62,6 +62,7 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.regex.MatchResult; import java.util.regex.Matcher; @@ -996,7 +997,7 @@ public static NonNullPair<SkriptEventInfo<?>, SkriptEvent> parseEvent(String eve String priorityString = split[split.length - 1]; try { - priority = EventPriority.valueOf(priorityString.toUpperCase()); + priority = EventPriority.valueOf(priorityString.toUpperCase(Locale.ENGLISH)); } catch (IllegalArgumentException e) { // Priority doesn't exist log.printErrors("The priority " + priorityString + " doesn't exist"); return null; diff --git a/src/main/java/ch/njol/skript/lang/UnparsedLiteral.java b/src/main/java/ch/njol/skript/lang/UnparsedLiteral.java index 2b30b60e25d..3bfbaf26b25 100644 --- a/src/main/java/ch/njol/skript/lang/UnparsedLiteral.java +++ b/src/main/java/ch/njol/skript/lang/UnparsedLiteral.java @@ -201,12 +201,12 @@ public <R> Literal<? extends R> getConvertedExpression(final ParseContext contex // if (t != null) { // if (!m.group().matches("\\s*,\\s*")) { // if (isAndSet) { -// if (and != m.group().toLowerCase().contains("and")) { +// if (and != m.group().toLowerCase(Locale.ENGLISH).contains("and")) { // Skript.warning("list has multiple 'and' or 'or', will default to 'and'"); // and = true; // } // } else { -// and = m.group().toLowerCase().contains("and"); +// and = m.group().toLowerCase(Locale.ENGLISH).contains("and"); // isAndSet = true; // } // } diff --git a/src/main/java/ch/njol/skript/lang/function/Parameter.java b/src/main/java/ch/njol/skript/lang/function/Parameter.java index 7776f6b6707..0182d71523a 100644 --- a/src/main/java/ch/njol/skript/lang/function/Parameter.java +++ b/src/main/java/ch/njol/skript/lang/function/Parameter.java @@ -30,6 +30,8 @@ import ch.njol.skript.util.Utils; import org.eclipse.jdt.annotation.Nullable; +import java.util.Locale; + public final class Parameter<T> { /** @@ -58,7 +60,7 @@ public final class Parameter<T> { @SuppressWarnings("null") public Parameter(String name, ClassInfo<T> type, boolean single, @Nullable Expression<? extends T> def) { - this.name = name != null ? name.toLowerCase() : null; + this.name = name != null ? name.toLowerCase(Locale.ENGLISH) : null; this.type = type; this.def = def; this.single = single; diff --git a/src/main/java/ch/njol/skript/localization/Language.java b/src/main/java/ch/njol/skript/localization/Language.java index 1725ceebefb..74e095a4c77 100644 --- a/src/main/java/ch/njol/skript/localization/Language.java +++ b/src/main/java/ch/njol/skript/localization/Language.java @@ -217,7 +217,7 @@ public static void loadDefault(SkriptAddon addon) { } public static boolean load(String name) { - name = "" + name.toLowerCase(); + name = "" + name.toLowerCase(Locale.ENGLISH); localizedLanguage = new HashMap<>(); boolean exists = load(Skript.getAddonInstance(), name); diff --git a/src/main/java/ch/njol/skript/patterns/LiteralPatternElement.java b/src/main/java/ch/njol/skript/patterns/LiteralPatternElement.java index b656ed1359d..113a915c2b3 100644 --- a/src/main/java/ch/njol/skript/patterns/LiteralPatternElement.java +++ b/src/main/java/ch/njol/skript/patterns/LiteralPatternElement.java @@ -20,6 +20,8 @@ import org.jetbrains.annotations.Nullable; +import java.util.Locale; + /** * A {@link PatternElement} that contains a literal string to be matched, for example {@code hello world}. * This element does not handle spaces as would be expected. @@ -29,7 +31,7 @@ public class LiteralPatternElement extends PatternElement { private final char[] literal; public LiteralPatternElement(String literal) { - this.literal = literal.toLowerCase().toCharArray(); + this.literal = literal.toLowerCase(Locale.ENGLISH).toCharArray(); } public boolean isEmpty() { diff --git a/src/main/java/ch/njol/skript/patterns/SkriptPattern.java b/src/main/java/ch/njol/skript/patterns/SkriptPattern.java index 81e1b82135b..5a3d65a8083 100644 --- a/src/main/java/ch/njol/skript/patterns/SkriptPattern.java +++ b/src/main/java/ch/njol/skript/patterns/SkriptPattern.java @@ -25,6 +25,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Locale; public class SkriptPattern { @@ -42,7 +43,7 @@ public SkriptPattern(PatternElement first, int expressionAmount) { @Nullable public MatchResult match(String expr, int flags, ParseContext parseContext) { // Matching shortcut - String lowerExpr = expr.toLowerCase(); + String lowerExpr = expr.toLowerCase(Locale.ENGLISH); for (String keyword : keywords) if (!lowerExpr.contains(keyword)) return null; diff --git a/src/main/java/ch/njol/skript/registrations/Classes.java b/src/main/java/ch/njol/skript/registrations/Classes.java index 37ac750ffe7..8ae8eef2a6d 100644 --- a/src/main/java/ch/njol/skript/registrations/Classes.java +++ b/src/main/java/ch/njol/skript/registrations/Classes.java @@ -33,6 +33,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.Set; import java.util.regex.Pattern; @@ -334,7 +335,7 @@ public static Class<?> getClass(final String codeName) { @Nullable public static ClassInfo<?> getClassInfoFromUserInput(String name) { checkAllowClassInfoInteraction(); - name = "" + name.toLowerCase(); + name = "" + name.toLowerCase(Locale.ENGLISH); for (final ClassInfo<?> ci : getClassInfos()) { final Pattern[] uip = ci.getUserInputPatterns(); if (uip == null) diff --git a/src/main/java/ch/njol/skript/util/EnchantmentType.java b/src/main/java/ch/njol/skript/util/EnchantmentType.java index 21a536c00ae..1ef13893fc9 100644 --- a/src/main/java/ch/njol/skript/util/EnchantmentType.java +++ b/src/main/java/ch/njol/skript/util/EnchantmentType.java @@ -20,6 +20,7 @@ import java.util.Collection; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import java.util.regex.Pattern; @@ -118,7 +119,7 @@ public static String toString(final Enchantment e, final int flags) { NAMES.put(e, names[0]); for (String name : names) - PATTERNS.put(name.toLowerCase(), e); + PATTERNS.put(name.toLowerCase(Locale.ENGLISH), e); } }); } @@ -152,7 +153,7 @@ public static EnchantmentType parse(final String s) { @Nullable public static Enchantment parseEnchantment(final String s) { - return PATTERNS.get(s.toLowerCase()); + return PATTERNS.get(s.toLowerCase(Locale.ENGLISH)); } @SuppressWarnings("null") diff --git a/src/main/java/ch/njol/skript/util/PotionDataUtils.java b/src/main/java/ch/njol/skript/util/PotionDataUtils.java index ff7eeff7e28..e7369d03f02 100644 --- a/src/main/java/ch/njol/skript/util/PotionDataUtils.java +++ b/src/main/java/ch/njol/skript/util/PotionDataUtils.java @@ -90,7 +90,7 @@ public enum PotionDataUtils { PotionDataUtils(String potionType, boolean extended, boolean upgraded, int duration, int amplifier) { try { - this.potionType = PotionType.valueOf(potionType.toUpperCase(Locale.ROOT)); + this.potionType = PotionType.valueOf(potionType.toUpperCase(Locale.ENGLISH)); this.name = potionType; } catch (IllegalArgumentException ignore) { this.potionType = null; diff --git a/src/main/java/ch/njol/skript/util/PotionEffectUtils.java b/src/main/java/ch/njol/skript/util/PotionEffectUtils.java index 67793749a01..2e6c9037863 100644 --- a/src/main/java/ch/njol/skript/util/PotionEffectUtils.java +++ b/src/main/java/ch/njol/skript/util/PotionEffectUtils.java @@ -22,6 +22,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import org.bukkit.entity.LivingEntity; @@ -75,7 +76,7 @@ public void onLanguageChange() { final String[] ls = Language.getList("potions." + t.getName()); names[t.getId()] = ls[0]; for (final String l : ls) { - types.put(l.toLowerCase(), t); + types.put(l.toLowerCase(Locale.ENGLISH), t); } } } @@ -84,7 +85,7 @@ public void onLanguageChange() { @Nullable public static PotionEffectType parseType(final String s) { - return types.get(s.toLowerCase()); + return types.get(s.toLowerCase(Locale.ENGLISH)); } // This is a stupid bandaid to fix comparison issues when converting potion datas diff --git a/src/main/java/ch/njol/skript/util/SkriptColor.java b/src/main/java/ch/njol/skript/util/SkriptColor.java index 7f3935fa3f5..424112a6694 100644 --- a/src/main/java/ch/njol/skript/util/SkriptColor.java +++ b/src/main/java/ch/njol/skript/util/SkriptColor.java @@ -23,6 +23,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; @@ -76,7 +77,7 @@ public enum SkriptColor implements Color { String node = LANGUAGE_NODE + "." + color.name(); color.setAdjective(new Adjective(node + ".adjective")); for (String name : Language.getList(node + ".names")) - names.put(name.toLowerCase(), color); + names.put(name.toLowerCase(Locale.ENGLISH), color); } }); } diff --git a/src/main/java/ch/njol/skript/util/StructureType.java b/src/main/java/ch/njol/skript/util/StructureType.java index 27568b0204b..cf06272a6be 100644 --- a/src/main/java/ch/njol/skript/util/StructureType.java +++ b/src/main/java/ch/njol/skript/util/StructureType.java @@ -19,6 +19,7 @@ package ch.njol.skript.util; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.regex.Pattern; @@ -115,7 +116,7 @@ public static StructureType fromName(String s) { parseMap.put(Pattern.compile(pattern, Pattern.CASE_INSENSITIVE), t); } } - s = "" + s.toLowerCase(); + s = "" + s.toLowerCase(Locale.ENGLISH); for (final Entry<Pattern, StructureType> e : parseMap.entrySet()) { if (e.getKey().matcher(s).matches()) return e.getValue(); diff --git a/src/main/java/ch/njol/skript/util/Timespan.java b/src/main/java/ch/njol/skript/util/Timespan.java index a68eae84ed7..5d2a7a0e5a9 100644 --- a/src/main/java/ch/njol/skript/util/Timespan.java +++ b/src/main/java/ch/njol/skript/util/Timespan.java @@ -19,6 +19,7 @@ package ch.njol.skript.util; import java.util.HashMap; +import java.util.Locale; import org.eclipse.jdt.annotation.Nullable; @@ -53,8 +54,8 @@ public class Timespan implements YggdrasilSerializable, Comparable<Timespan> { / @Override public void onLanguageChange() { for (int i = 0; i < names.length; i++) { - parseValues.put(names[i].getSingular().toLowerCase(), times[i]); - parseValues.put(names[i].getPlural().toLowerCase(), times[i]); + parseValues.put(names[i].getSingular().toLowerCase(Locale.ENGLISH), times[i]); + parseValues.put(names[i].getPlural().toLowerCase(Locale.ENGLISH), times[i]); } } }); @@ -76,7 +77,7 @@ public static Timespan parse(final String s) { t += times[offset + i] * Utils.parseLong("" + ss[i]); } } else { // <number> minutes/seconds/.. etc - final String[] subs = s.toLowerCase().split("\\s+"); + final String[] subs = s.toLowerCase(Locale.ENGLISH).split("\\s+"); for (int i = 0; i < subs.length; i++) { String sub = subs[i]; @@ -117,7 +118,7 @@ public static Timespan parse(final String s) { if (sub.endsWith(",")) sub = sub.substring(0, sub.length() - 1); - final Long d = parseValues.get(sub.toLowerCase()); + final Long d = parseValues.get(sub.toLowerCase(Locale.ENGLISH)); if (d == null) 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 3110b02cf30..076ba17aabd 100644 --- a/src/main/java/ch/njol/skript/util/Utils.java +++ b/src/main/java/ch/njol/skript/util/Utils.java @@ -43,6 +43,7 @@ import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Random; import java.util.concurrent.CompletableFuture; @@ -229,8 +230,8 @@ public static NonNullPair<String, Boolean> getEnglishPlural(final String s) { 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())) - return new NonNullPair<>(s.substring(0, s.length() - p[1].length()) + p[0].toUpperCase(), 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); } return new NonNullPair<>(s, Boolean.FALSE); } @@ -486,9 +487,9 @@ public void onLanguageChange() { chat.clear(); for (final ChatColor style : styles) { for (final String s : Language.getList("chat styles." + style.name())) { - chat.put(s.toLowerCase(), style.toString()); + chat.put(s.toLowerCase(Locale.ENGLISH), style.toString()); if (english) - englishChat.put(s.toLowerCase(), style.toString()); + englishChat.put(s.toLowerCase(Locale.ENGLISH), style.toString()); } } } @@ -521,7 +522,7 @@ public String run(final Matcher m) { SkriptColor color = SkriptColor.fromName("" + m.group(1)); if (color != null) return color.getFormattedChat(); - final String tag = m.group(1).toLowerCase(); + final String tag = m.group(1).toLowerCase(Locale.ENGLISH); final String f = chat.get(tag); if (f != null) return f; @@ -559,7 +560,7 @@ public String run(final Matcher m) { SkriptColor color = SkriptColor.fromName("" + m.group(1)); if (color != null) return color.getFormattedChat(); - final String tag = m.group(1).toLowerCase(); + final String tag = m.group(1).toLowerCase(Locale.ENGLISH); final String f = englishChat.get(tag); if (f != null) return f; diff --git a/src/main/java/ch/njol/skript/util/chat/ChatMessages.java b/src/main/java/ch/njol/skript/util/chat/ChatMessages.java index d48e2a71226..88b90c62766 100644 --- a/src/main/java/ch/njol/skript/util/chat/ChatMessages.java +++ b/src/main/java/ch/njol/skript/util/chat/ChatMessages.java @@ -23,6 +23,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -227,7 +228,7 @@ else if (c2 == '>') } else { name = tag; } - name = name.toLowerCase(); // Tags are case-insensitive + name = name.toLowerCase(Locale.ENGLISH); // Tags are case-insensitive boolean tryHex = Utils.HEX_SUPPORTED && name.startsWith("#"); ChatColor chatColor = null; diff --git a/src/main/java/ch/njol/skript/util/chat/MessageComponent.java b/src/main/java/ch/njol/skript/util/chat/MessageComponent.java index b47b8f0f04f..a6b750cba6b 100644 --- a/src/main/java/ch/njol/skript/util/chat/MessageComponent.java +++ b/src/main/java/ch/njol/skript/util/chat/MessageComponent.java @@ -138,7 +138,7 @@ public static enum Action { @SuppressWarnings("null") Action() { - spigotName = this.name().toUpperCase(); + spigotName = this.name().toUpperCase(Locale.ENGLISH); } } diff --git a/src/main/java/ch/njol/util/Kleenean.java b/src/main/java/ch/njol/util/Kleenean.java index 6062c29592c..6939a301e1c 100644 --- a/src/main/java/ch/njol/util/Kleenean.java +++ b/src/main/java/ch/njol/util/Kleenean.java @@ -18,6 +18,8 @@ */ package ch.njol.util; +import java.util.Locale; + /** * A three-valued logic type (true, unknown, false), named after Stephen Cole Kleene. * @@ -39,7 +41,7 @@ public enum Kleenean { @Override public final String toString() { - return "" + name().toLowerCase(); + return "" + name().toLowerCase(Locale.ENGLISH); } public final Kleenean is(final Kleenean other) { From 404695857dc5a9f6eca4c45c62b165cf340e7a54 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Mon, 20 Jun 2022 11:06:08 -0600 Subject: [PATCH 005/619] Allow multiple players in the food level expression (#4807) --- src/main/java/ch/njol/skript/expressions/ExprFoodLevel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprFoodLevel.java b/src/main/java/ch/njol/skript/expressions/ExprFoodLevel.java index d092137590c..6944819e926 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprFoodLevel.java +++ b/src/main/java/ch/njol/skript/expressions/ExprFoodLevel.java @@ -49,7 +49,7 @@ public class ExprFoodLevel extends PropertyExpression<Player, Number> { static { - Skript.registerExpression(ExprFoodLevel.class, Number.class, ExpressionType.PROPERTY, "[the] (food|hunger)[[ ](level|met(er|re)|bar)] [of %player%]", "%player%'[s] (food|hunger)[[ ](level|met(er|re)|bar)]"); + Skript.registerExpression(ExprFoodLevel.class, Number.class, ExpressionType.PROPERTY, "[the] (food|hunger)[[ ](level|met(er|re)|bar)] [of %players%]", "%players%'[s] (food|hunger)[[ ](level|met(er|re)|bar)]"); } @SuppressWarnings({"unchecked", "null"}) From e1c1735ff509250a7a2f8eee9c22100c884eb1d1 Mon Sep 17 00:00:00 2001 From: APickledWalrus <apickledwalrus@gmail.com> Date: Mon, 20 Jun 2022 13:20:05 -0400 Subject: [PATCH 006/619] Update Paper API Endpoints (#4763) --- build.gradle | 2 +- src/main/java/ch/njol/skript/tests/platform/Environment.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 10d0b072cb5..c156245de70 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ allprojects { mavenCentral() maven { url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' } maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } - maven { url 'https://papermc.io/repo/repository/maven-public/' } + maven { url 'https://repo.papermc.io/repository/maven-public/' } maven { url 'https://ci.emc.gs/nexus/content/groups/aikar/' } } } diff --git a/src/main/java/ch/njol/skript/tests/platform/Environment.java b/src/main/java/ch/njol/skript/tests/platform/Environment.java index 1c23e01ea5e..aeacfb11333 100644 --- a/src/main/java/ch/njol/skript/tests/platform/Environment.java +++ b/src/main/java/ch/njol/skript/tests/platform/Environment.java @@ -109,7 +109,7 @@ private void generateSource() throws IOException { if (source != null) return; - String stringUrl = "https://papermc.io/api/v2/projects/paper/versions/" + version; + String stringUrl = "https://api.papermc.io/v2/projects/paper/versions/" + version; URL url = new URL(stringUrl); JsonObject jsonObject; try (InputStream is = url.openStream()) { @@ -130,7 +130,7 @@ private void generateSource() throws IOException { if (latestBuild == -1) throw new IllegalStateException("No builds for this version"); - source = "https://papermc.io/api/v2/projects/paper/versions/" + version + "/builds/" + latestBuild + source = "https://api.papermc.io/v2/projects/paper/versions/" + version + "/builds/" + latestBuild + "/downloads/paper-" + version + "-" + latestBuild + ".jar"; } } From 185cc43301b9e3495e0ada77e453db990c137c69 Mon Sep 17 00:00:00 2001 From: Shane Bee <shanebolenback@me.com> Date: Sat, 25 Jun 2022 06:56:39 -0700 Subject: [PATCH 007/619] SimpleEntityData - cleanup (#4801) - Add a couple shortcut methods for registering new entities - Remove pre 1.13 version checks --- .../njol/skript/entity/SimpleEntityData.java | 259 ++++++++---------- 1 file changed, 114 insertions(+), 145 deletions(-) diff --git a/src/main/java/ch/njol/skript/entity/SimpleEntityData.java b/src/main/java/ch/njol/skript/entity/SimpleEntityData.java index e5f15afe568..5a744d5fb34 100644 --- a/src/main/java/ch/njol/skript/entity/SimpleEntityData.java +++ b/src/main/java/ch/njol/skript/entity/SimpleEntityData.java @@ -30,7 +30,6 @@ import org.bukkit.entity.Arrow; import org.bukkit.entity.Bat; import org.bukkit.entity.Blaze; -import org.bukkit.entity.Boat; import org.bukkit.entity.CaveSpider; import org.bukkit.entity.ChestedHorse; import org.bukkit.entity.Chicken; @@ -174,167 +173,137 @@ public boolean equals(final @Nullable Object obj) { } private final static List<SimpleEntityDataInfo> types = new ArrayList<>(); - static { - types.add(new SimpleEntityDataInfo("arrow", Arrow.class)); - if (Skript.classExists("org.bukkit.entity.SpectralArrow")) - types.add(new SimpleEntityDataInfo("spectral arrow", SpectralArrow.class)); - if (Skript.classExists("org.bukkit.entity.TippedArrow")) - types.add(new SimpleEntityDataInfo("tipped arrow", TippedArrow.class)); - if (!Skript.methodExists(Boat.class, "getWoodType")) // Only for 1.9 and lower. See BoatData instead - types.add(new SimpleEntityDataInfo("boat", Boat.class)); - types.add(new SimpleEntityDataInfo("blaze", Blaze.class)); - types.add(new SimpleEntityDataInfo("chicken", Chicken.class)); - types.add(new SimpleEntityDataInfo("mooshroom", MushroomCow.class)); - types.add(new SimpleEntityDataInfo("cow", Cow.class)); - types.add(new SimpleEntityDataInfo("cave spider", CaveSpider.class)); - if (Skript.classExists("org.bukkit.entity.DragonFireball")) - types.add(new SimpleEntityDataInfo("dragon fireball", DragonFireball.class)); - types.add(new SimpleEntityDataInfo("egg", Egg.class)); - types.add(new SimpleEntityDataInfo("ender crystal", EnderCrystal.class)); - types.add(new SimpleEntityDataInfo("ender dragon", EnderDragon.class)); - types.add(new SimpleEntityDataInfo("ender pearl", EnderPearl.class)); - types.add(new SimpleEntityDataInfo("small fireball", SmallFireball.class)); - types.add(new SimpleEntityDataInfo("large fireball", LargeFireball.class)); - types.add(new SimpleEntityDataInfo("fireball", Fireball.class)); - types.add(new SimpleEntityDataInfo("fish hook", FishHook.class)); - types.add(new SimpleEntityDataInfo("ghast", Ghast.class)); - types.add(new SimpleEntityDataInfo("giant", Giant.class)); - types.add(new SimpleEntityDataInfo("iron golem", IronGolem.class)); - types.add(new SimpleEntityDataInfo("magma cube", MagmaCube.class)); - types.add(new SimpleEntityDataInfo("slime", Slime.class)); - types.add(new SimpleEntityDataInfo("painting", Painting.class)); - types.add(new SimpleEntityDataInfo("zombie pigman", PigZombie.class)); - types.add(new SimpleEntityDataInfo("silverfish", Silverfish.class)); - types.add(new SimpleEntityDataInfo("snowball", Snowball.class)); - types.add(new SimpleEntityDataInfo("snow golem", Snowman.class)); - types.add(new SimpleEntityDataInfo("spider", Spider.class)); - types.add(new SimpleEntityDataInfo("bottle of enchanting", ThrownExpBottle.class)); - types.add(new SimpleEntityDataInfo("tnt", TNTPrimed.class)); - types.add(new SimpleEntityDataInfo("leash hitch", LeashHitch.class)); - - if (Skript.classExists("org.bukkit.entity.ItemFrame")) { - types.add(new SimpleEntityDataInfo("item frame", ItemFrame.class)); - types.add(new SimpleEntityDataInfo("bat", Bat.class)); - types.add(new SimpleEntityDataInfo("witch", Witch.class)); - types.add(new SimpleEntityDataInfo("wither", Wither.class)); - types.add(new SimpleEntityDataInfo("wither skull", WitherSkull.class)); - } - if (Skript.classExists("org.bukkit.entity.Firework")) - types.add(new SimpleEntityDataInfo("firework", Firework.class)); - if(Skript.classExists("org.bukkit.entity.ArmorStand")){ - types.add(new SimpleEntityDataInfo("endermite", Endermite.class)); - types.add(new SimpleEntityDataInfo("armor stand", ArmorStand.class)); - } - if (Skript.classExists("org.bukkit.entity.Shulker")) { - types.add(new SimpleEntityDataInfo("shulker", Shulker.class)); - types.add(new SimpleEntityDataInfo("shulker bullet", ShulkerBullet.class)); - } - if (Skript.classExists("org.bukkit.entity.PolarBear")) { - types.add(new SimpleEntityDataInfo("polar bear", PolarBear.class)); - } - if (Skript.classExists("org.bukkit.entity.AreaEffectCloud")) { - types.add(new SimpleEntityDataInfo("area effect cloud", AreaEffectCloud.class)); - } - if (Skript.isRunningMinecraft(1, 11)) { // More subtypes, more supertypes - changes needed - types.add(new SimpleEntityDataInfo("wither skeleton", WitherSkeleton.class)); - types.add(new SimpleEntityDataInfo("stray", Stray.class)); - types.add(new SimpleEntityDataInfo("husk", Husk.class)); - types.add(new SimpleEntityDataInfo("skeleton", Skeleton.class, true)); - // Guardians - types.add(new SimpleEntityDataInfo("elder guardian", ElderGuardian.class)); - types.add(new SimpleEntityDataInfo("normal guardian", Guardian.class)); - types.add(new SimpleEntityDataInfo("guardian", Guardian.class, true)); - - // Horses - types.add(new SimpleEntityDataInfo("donkey", Donkey.class)); - types.add(new SimpleEntityDataInfo("mule", Mule.class)); - types.add(new SimpleEntityDataInfo("llama", Llama.class)); - types.add(new SimpleEntityDataInfo("undead horse", ZombieHorse.class)); - types.add(new SimpleEntityDataInfo("skeleton horse", SkeletonHorse.class)); - types.add(new SimpleEntityDataInfo("horse", Horse.class)); - - // New 1.11 horse supertypes - types.add(new SimpleEntityDataInfo("chested horse", ChestedHorse.class, true)); - types.add(new SimpleEntityDataInfo("any horse", AbstractHorse.class, true)); - - types.add(new SimpleEntityDataInfo("llama spit", LlamaSpit.class)); + private static void addSimpleEntity(String codeName, Class<? extends Entity> entityclass) { + types.add(new SimpleEntityDataInfo(codeName, entityclass)); + } - // 1.11 hostile mobs - types.add(new SimpleEntityDataInfo("evoker", Evoker.class)); - types.add(new SimpleEntityDataInfo("evoker fangs", EvokerFangs.class)); - types.add(new SimpleEntityDataInfo("vex", Vex.class)); - types.add(new SimpleEntityDataInfo("vindicator", Vindicator.class)); - } + private static void addSuperEntity(String codeName, Class<? extends Entity> entityclass) { + types.add(new SimpleEntityDataInfo(codeName, entityclass, true)); + } + static { + // Simple Entities + addSimpleEntity("arrow", Arrow.class); + addSimpleEntity("spectral arrow", SpectralArrow.class); + addSimpleEntity("tipped arrow", TippedArrow.class); + addSimpleEntity("blaze", Blaze.class); + addSimpleEntity("chicken", Chicken.class); + addSimpleEntity("mooshroom", MushroomCow.class); + addSimpleEntity("cow", Cow.class); + addSimpleEntity("cave spider", CaveSpider.class); + addSimpleEntity("dragon fireball", DragonFireball.class); + addSimpleEntity("egg", Egg.class); + addSimpleEntity("ender crystal", EnderCrystal.class); + addSimpleEntity("ender dragon", EnderDragon.class); + addSimpleEntity("ender pearl", EnderPearl.class); + addSimpleEntity("small fireball", SmallFireball.class); + addSimpleEntity("large fireball", LargeFireball.class); + addSimpleEntity("fireball", Fireball.class); + addSimpleEntity("fish hook", FishHook.class); + addSimpleEntity("ghast", Ghast.class); + addSimpleEntity("giant", Giant.class); + addSimpleEntity("iron golem", IronGolem.class); + addSimpleEntity("magma cube", MagmaCube.class); + addSimpleEntity("slime", Slime.class); + addSimpleEntity("painting", Painting.class); + addSimpleEntity("zombie pigman", PigZombie.class); + addSimpleEntity("silverfish", Silverfish.class); + addSimpleEntity("snowball", Snowball.class); + addSimpleEntity("snow golem", Snowman.class); + addSimpleEntity("spider", Spider.class); + addSimpleEntity("bottle of enchanting", ThrownExpBottle.class); + addSimpleEntity("tnt", TNTPrimed.class); + addSimpleEntity("leash hitch", LeashHitch.class); + addSimpleEntity("item frame", ItemFrame.class); + addSimpleEntity("bat", Bat.class); + addSimpleEntity("witch", Witch.class); + addSimpleEntity("wither", Wither.class); + addSimpleEntity("wither skull", WitherSkull.class); + addSimpleEntity("firework", Firework.class); + addSimpleEntity("endermite", Endermite.class); + addSimpleEntity("armor stand", ArmorStand.class); + addSimpleEntity("shulker", Shulker.class); + addSimpleEntity("shulker bullet", ShulkerBullet.class); + addSimpleEntity("polar bear", PolarBear.class); + addSimpleEntity("area effect cloud", AreaEffectCloud.class); + addSimpleEntity("wither skeleton", WitherSkeleton.class); + addSimpleEntity("stray", Stray.class); + addSimpleEntity("husk", Husk.class); + addSuperEntity("skeleton", Skeleton.class); + addSimpleEntity("llama spit", LlamaSpit.class); + addSimpleEntity("evoker", Evoker.class); + addSimpleEntity("evoker fangs", EvokerFangs.class); + addSimpleEntity("vex", Vex.class); + addSimpleEntity("vindicator", Vindicator.class); + addSimpleEntity("elder guardian", ElderGuardian.class); + addSimpleEntity("normal guardian", Guardian.class); + addSimpleEntity("donkey", Donkey.class); + addSimpleEntity("mule", Mule.class); + addSimpleEntity("llama", Llama.class); + addSimpleEntity("undead horse", ZombieHorse.class); + addSimpleEntity("skeleton horse", SkeletonHorse.class); + addSimpleEntity("horse", Horse.class); + addSimpleEntity("dolphin", Dolphin.class); + addSimpleEntity("phantom", Phantom.class); + addSimpleEntity("drowned", Drowned.class); + addSimpleEntity("turtle", Turtle.class); + addSimpleEntity("cod", Cod.class); + addSimpleEntity("puffer fish", PufferFish.class); + addSimpleEntity("salmon", Salmon.class); + addSimpleEntity("tropical fish", TropicalFish.class); + addSimpleEntity("trident", Trident.class); if (Skript.classExists("org.bukkit.entity.Illusioner")) // Added in 1.12 - types.add(new SimpleEntityDataInfo("illusioner", Illusioner.class)); - - if (Skript.isRunningMinecraft(1, 13)) { // More subtypes, more supertypes - changes needed - types.add(new SimpleEntityDataInfo("dolphin", Dolphin.class)); - types.add(new SimpleEntityDataInfo("phantom", Phantom.class)); - types.add(new SimpleEntityDataInfo("drowned", Drowned.class)); - types.add(new SimpleEntityDataInfo("turtle", Turtle.class)); - types.add(new SimpleEntityDataInfo("cod", Cod.class)); - types.add(new SimpleEntityDataInfo("puffer fish", PufferFish.class)); - types.add(new SimpleEntityDataInfo("salmon", Salmon.class)); - types.add(new SimpleEntityDataInfo("tropical fish", TropicalFish.class)); - types.add(new SimpleEntityDataInfo("trident", Trident.class)); - } - + addSimpleEntity("illusioner", Illusioner.class); + if (Skript.isRunningMinecraft(1, 14)) { - types.add(new SimpleEntityDataInfo("pillager", Pillager.class)); - types.add(new SimpleEntityDataInfo("ravager", Ravager.class)); - types.add(new SimpleEntityDataInfo("wandering trader", WanderingTrader.class)); + addSimpleEntity("pillager", Pillager.class); + addSimpleEntity("ravager", Ravager.class); + addSimpleEntity("wandering trader", WanderingTrader.class); } - + if (Skript.isRunningMinecraft(1, 16)) { - types.add(new SimpleEntityDataInfo("piglin", Piglin.class)); - types.add(new SimpleEntityDataInfo("hoglin", Hoglin.class)); - types.add(new SimpleEntityDataInfo("zoglin", Zoglin.class)); - types.add(new SimpleEntityDataInfo("strider", Strider.class)); + addSimpleEntity("piglin", Piglin.class); + addSimpleEntity("hoglin", Hoglin.class); + addSimpleEntity("zoglin", Zoglin.class); + addSimpleEntity("strider", Strider.class); } - + if (Skript.classExists("org.bukkit.entity.PiglinBrute")) // Added in 1.16.2 - types.add(new SimpleEntityDataInfo("piglin brute", PiglinBrute.class)); + addSimpleEntity("piglin brute", PiglinBrute.class); if (Skript.isRunningMinecraft(1, 17)) { - types.add(new SimpleEntityDataInfo("glow squid", GlowSquid.class)); - types.add(new SimpleEntityDataInfo("marker", Marker.class)); - types.add(new SimpleEntityDataInfo("glow item frame", GlowItemFrame.class)); + addSimpleEntity("glow squid", GlowSquid.class); + addSimpleEntity("marker", Marker.class); + addSimpleEntity("glow item frame", GlowItemFrame.class); } // Register zombie after Husk and Drowned to make sure both work - types.add(new SimpleEntityDataInfo("zombie", Zombie.class)); + addSimpleEntity("zombie", Zombie.class); // Register squid after glow squid to make sure both work - types.add(new SimpleEntityDataInfo("squid", Squid.class)); - - // TODO !Update with every version [entities] - - // supertypes - types.add(new SimpleEntityDataInfo("human", HumanEntity.class, true)); - types.add(new SimpleEntityDataInfo("damageable", Damageable.class, true)); - types.add(new SimpleEntityDataInfo("monster", Monster.class, true)); - if (Skript.classExists("org.bukkit.entity.Mob")) { // Introduced by Spigot 1.13 - types.add(new SimpleEntityDataInfo("mob", Mob.class, true)); - } - types.add(new SimpleEntityDataInfo("creature", Creature.class, true)); - types.add(new SimpleEntityDataInfo("animal", Animals.class, true)); - types.add(new SimpleEntityDataInfo("golem", Golem.class, true)); - types.add(new SimpleEntityDataInfo("projectile", Projectile.class, true)); - types.add(new SimpleEntityDataInfo("living entity", LivingEntity.class, true)); - types.add(new SimpleEntityDataInfo("entity", Entity.class, true)); - types.add(new SimpleEntityDataInfo("water mob" , WaterMob.class, true)); - types.add(new SimpleEntityDataInfo("fish" , Fish.class, true)); - - types.add(new SimpleEntityDataInfo("any fireball", Fireball.class, true)); + addSimpleEntity("squid", Squid.class); - if (Skript.classExists("org.bukkit.entity.Illager")) { // Introduced in Spigot 1.12 - types.add(new SimpleEntityDataInfo("illager", Illager.class, true)); - types.add(new SimpleEntityDataInfo("spellcaster", Spellcaster.class, true)); - } + // SuperTypes + addSuperEntity("human", HumanEntity.class); + addSuperEntity("damageable", Damageable.class); + addSuperEntity("monster", Monster.class); + addSuperEntity("mob", Mob.class); + addSuperEntity("creature", Creature.class); + addSuperEntity("animal", Animals.class); + addSuperEntity("golem", Golem.class); + addSuperEntity("projectile", Projectile.class); + addSuperEntity("living entity", LivingEntity.class); + addSuperEntity("entity", Entity.class); + addSuperEntity("chested horse", ChestedHorse.class); + addSuperEntity("any horse", AbstractHorse.class); + addSuperEntity("guardian", Guardian.class); + addSuperEntity("water mob" , WaterMob.class); + addSuperEntity("fish" , Fish.class); + addSuperEntity("any fireball", Fireball.class); + addSuperEntity("illager", Illager.class); + addSuperEntity("spellcaster", Spellcaster.class); if (Skript.classExists("org.bukkit.entity.Raider")) // Introduced in Spigot 1.14 - types.add(new SimpleEntityDataInfo("raider", Raider.class, true)); + addSuperEntity("raider", Raider.class); } static { From a850de2c4b9be29e1e70b9a5909a9e30bf24a4fc Mon Sep 17 00:00:00 2001 From: Shane Bee <shanebolenback@me.com> Date: Tue, 28 Jun 2022 15:40:32 -0700 Subject: [PATCH 008/619] Update Minecraft 1.19 stuff (#4834) * Update Minecraft 1.19 stuff - new entities - new enchantment - update some classes due to changes in Paper 1.19 --- build.gradle | 2 +- .../classes/data/DefaultComparators.java | 4 + .../ch/njol/skript/entity/BoatChestData.java | 143 ++++++++++++++++++ .../java/ch/njol/skript/entity/FrogData.java | 102 +++++++++++++ .../njol/skript/entity/SimpleEntityData.java | 9 ++ .../skript/hooks/biomes/BiomeMapUtil.java | 10 +- .../ch/njol/skript/util/BlockStateBlock.java | 16 ++ .../njol/skript/util/DelayedChangeBlock.java | 16 ++ src/main/resources/lang/default.lang | 73 ++++++++- 9 files changed, 368 insertions(+), 7 deletions(-) create mode 100644 src/main/java/ch/njol/skript/entity/BoatChestData.java create mode 100644 src/main/java/ch/njol/skript/entity/FrogData.java diff --git a/build.gradle b/build.gradle index c156245de70..24b3f68bdab 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ allprojects { dependencies { shadow group: 'io.papermc', name: 'paperlib', version: '1.0.6' - implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.17.1-R0.1-SNAPSHOT' + implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.19-R0.1-SNAPSHOT' implementation group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.600' implementation group: 'com.google.code.findbugs', name: 'findbugs', version: '3.0.1' implementation group: 'com.sk89q.worldguard', name: 'worldguard-legacy', version: '7.0.0-SNAPSHOT' diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java b/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java index 8f44e4edb35..e8c353c3976 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java @@ -25,6 +25,7 @@ import ch.njol.skript.aliases.ItemType; import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.classes.Comparator; +import ch.njol.skript.entity.BoatChestData; import ch.njol.skript.entity.BoatData; import ch.njol.skript.entity.EntityData; import ch.njol.skript.entity.RabbitData; @@ -50,6 +51,7 @@ import org.bukkit.block.data.BlockData; import org.bukkit.command.CommandSender; import org.bukkit.enchantments.EnchantmentOffer; +import org.bukkit.entity.ChestBoat; import org.bukkit.entity.Entity; import org.bukkit.entity.FallingBlock; import org.bukkit.entity.Item; @@ -294,6 +296,8 @@ public Relation compare(EntityData e, ItemType i) { // return Relation.get(i.isOfType(Material.SKULL_ITEM.getId(), (short) 1)); if (e instanceof BoatData) return Relation.get(((BoatData)e).isOfItemType(i)); + if (e instanceof BoatChestData) + return Relation.get(((BoatChestData) e).isOfItemType(i)); if (e instanceof RabbitData) return Relation.get(i.isOfType(Material.RABBIT)); for (ItemData data : i.getTypes()) { diff --git a/src/main/java/ch/njol/skript/entity/BoatChestData.java b/src/main/java/ch/njol/skript/entity/BoatChestData.java new file mode 100644 index 00000000000..bb8d8506e23 --- /dev/null +++ b/src/main/java/ch/njol/skript/entity/BoatChestData.java @@ -0,0 +1,143 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.entity; + +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.Aliases; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptParser; +import org.bukkit.TreeSpecies; +import org.bukkit.entity.ChestBoat; +import org.bukkit.inventory.ItemStack; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.Random; + +public class BoatChestData extends EntityData<ChestBoat> { + + private static ItemType oakBoat = null; + private static ItemType spruceBoat = null; + private static ItemType birchBoat = null; + private static ItemType jungleBoat = null; + private static ItemType acaciaBoat = null; + private static ItemType darkOakBoat = null; + + static { + if (Skript.classExists("org.bukkit.entity.ChestBoat")) { + oakBoat = Aliases.javaItemType("oak chest boat"); + spruceBoat = Aliases.javaItemType("spruce chest boat"); + birchBoat = Aliases.javaItemType("birch chest boat"); + jungleBoat = Aliases.javaItemType("jungle chest boat"); + acaciaBoat = Aliases.javaItemType("acacia chest boat"); + darkOakBoat = Aliases.javaItemType("dark oak chest boat"); + EntityData.register(BoatChestData.class, "chest boat", ChestBoat.class, 0, + "chest boat", "any chest boat", "oak chest boat", "spruce chest boat", "birch chest boat", + "jungle chest boat", "acacia chest boat", "dark oak chest boat"); + } + } + + public BoatChestData() { + this(0); + } + + public BoatChestData(@Nullable TreeSpecies type) { + this(type != null ? type.ordinal() + 2 : 1); + } + + private BoatChestData(int type) { + matchedPattern = type; + } + + @Override + protected boolean init(Literal<?>[] exprs, int matchedPattern, SkriptParser.ParseResult parseResult) { + return true; + } + + @Override + protected boolean init(@Nullable Class<? extends ChestBoat> c, @Nullable ChestBoat e) { + if (e != null) + matchedPattern = 2 + e.getWoodType().ordinal(); + return true; + } + + @Override + public void set(ChestBoat entity) { + if (matchedPattern == 1) // If the type is 'any boat'. + matchedPattern += new Random().nextInt(TreeSpecies.values().length); // It will spawn a random boat type in case is 'any boat'. + if (matchedPattern > 1) // 0 and 1 are excluded + entity.setWoodType(TreeSpecies.values()[matchedPattern - 2]); // Removes 2 to fix the index. + } + + @Override + protected boolean match(ChestBoat entity) { + return matchedPattern <= 1 || entity.getWoodType().ordinal() == matchedPattern - 2; + } + + @Override + public Class<? extends ChestBoat> getType() { + return ChestBoat.class; + } + + @Override + public EntityData getSuperType() { + return new BoatChestData(matchedPattern); + } + + @Override + protected int hashCode_i() { + return matchedPattern <= 1 ? 0 : matchedPattern; + } + + @Override + protected boolean equals_i(EntityData<?> obj) { + if (obj instanceof BoatData) + return matchedPattern == ((BoatData) obj).matchedPattern; + return false; + } + + @Override + public boolean isSupertypeOf(EntityData<?> e) { + if (e instanceof BoatData) + return matchedPattern <= 1 || matchedPattern == ((BoatData) e).matchedPattern; + return false; + } + + public boolean isOfItemType(ItemType itemType) { + if (itemType.getRandom() == null) + return false; + int ordinal = -1; + + ItemStack stack = itemType.getRandom(); + if (oakBoat.isOfType(stack)) + ordinal = 0; + else if (spruceBoat.isOfType(stack)) + ordinal = TreeSpecies.REDWOOD.ordinal(); + else if (birchBoat.isOfType(stack)) + ordinal = TreeSpecies.BIRCH.ordinal(); + else if (jungleBoat.isOfType(stack)) + ordinal = TreeSpecies.JUNGLE.ordinal(); + else if (acaciaBoat.isOfType(stack)) + ordinal = TreeSpecies.ACACIA.ordinal(); + else if (darkOakBoat.isOfType(stack)) + ordinal = TreeSpecies.DARK_OAK.ordinal(); + return hashCode_i() == ordinal + 2 || (matchedPattern + ordinal == 0) || ordinal == 0; + } + +} diff --git a/src/main/java/ch/njol/skript/entity/FrogData.java b/src/main/java/ch/njol/skript/entity/FrogData.java new file mode 100644 index 00000000000..116c781b326 --- /dev/null +++ b/src/main/java/ch/njol/skript/entity/FrogData.java @@ -0,0 +1,102 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.entity; + +import ch.njol.skript.Skript; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptParser; +import org.bukkit.entity.Frog; +import org.bukkit.entity.Frog.Variant; +import org.eclipse.jdt.annotation.Nullable; + +public class FrogData extends EntityData<Frog> { + + static { + if (Skript.classExists("org.bukkit.entity.Frog")) { + EntityData.register(FrogData.class, "frog", Frog.class, 0, + "frog", "temperate frog", "warm frog", "cold frog"); + } + } + + @Nullable + private Variant variant = null; + + public FrogData() { + } + + public FrogData(@Nullable Variant variant) { + this.variant = variant; + matchedPattern = variant != null ? variant.ordinal() + 1 : 0; + } + + @Override + protected boolean init(Literal<?>[] exprs, int matchedPattern, SkriptParser.ParseResult parseResult) { + if (matchedPattern > 0) + variant = Variant.values()[matchedPattern - 1]; + return true; + } + + @Override + protected boolean init(@Nullable Class<? extends Frog> c, @Nullable Frog frog) { + if (frog != null) + variant = frog.getVariant(); + return true; + } + + @Override + public void set(Frog entity) { + if (variant != null) + entity.setVariant(variant); + } + + @Override + protected boolean match(Frog entity) { + return variant == null || variant == entity.getVariant(); + } + + @Override + public Class<? extends Frog> getType() { + return Frog.class; + } + + @Override + public EntityData getSuperType() { + return new FrogData(variant); + } + + @Override + protected int hashCode_i() { + return variant != null ? variant.hashCode() : 0; + } + + @Override + protected boolean equals_i(EntityData<?> data) { + if (!(data instanceof FrogData)) + return false; + return variant == ((FrogData) data).variant; + } + + @Override + public boolean isSupertypeOf(EntityData<?> data) { + if (!(data instanceof FrogData)) + return false; + return variant == null || variant == ((FrogData) data).variant; + } + +} diff --git a/src/main/java/ch/njol/skript/entity/SimpleEntityData.java b/src/main/java/ch/njol/skript/entity/SimpleEntityData.java index 5a744d5fb34..819035c397e 100644 --- a/src/main/java/ch/njol/skript/entity/SimpleEntityData.java +++ b/src/main/java/ch/njol/skript/entity/SimpleEntityData.java @@ -24,6 +24,7 @@ import java.util.List; import org.bukkit.entity.AbstractHorse; +import org.bukkit.entity.Allay; import org.bukkit.entity.Animals; import org.bukkit.entity.AreaEffectCloud; import org.bukkit.entity.ArmorStand; @@ -107,6 +108,7 @@ import org.bukkit.entity.Stray; import org.bukkit.entity.Strider; import org.bukkit.entity.TNTPrimed; +import org.bukkit.entity.Tadpole; import org.bukkit.entity.ThrownExpBottle; import org.bukkit.entity.TippedArrow; import org.bukkit.entity.Trident; @@ -115,6 +117,7 @@ import org.bukkit.entity.Vex; import org.bukkit.entity.Vindicator; import org.bukkit.entity.WanderingTrader; +import org.bukkit.entity.Warden; import org.bukkit.entity.WaterMob; import org.bukkit.entity.Witch; import org.bukkit.entity.Wither; @@ -277,6 +280,12 @@ private static void addSuperEntity(String codeName, Class<? extends Entity> enti addSimpleEntity("marker", Marker.class); addSimpleEntity("glow item frame", GlowItemFrame.class); } + + if (Skript.isRunningMinecraft(1, 19)) { + addSimpleEntity("allay", Allay.class); + addSimpleEntity("tadpole", Tadpole.class); + addSimpleEntity("warden", Warden.class); + } // Register zombie after Husk and Drowned to make sure both work addSimpleEntity("zombie", Zombie.class); diff --git a/src/main/java/ch/njol/skript/hooks/biomes/BiomeMapUtil.java b/src/main/java/ch/njol/skript/hooks/biomes/BiomeMapUtil.java index a2905d2c86e..53d77759539 100644 --- a/src/main/java/ch/njol/skript/hooks/biomes/BiomeMapUtil.java +++ b/src/main/java/ch/njol/skript/hooks/biomes/BiomeMapUtil.java @@ -44,18 +44,18 @@ public enum To19Mapping { MUSHROOM_FIELDS("MUSHROOM_ISLAND"), MUSHROOM_FIELD_SHORE("MUSHROOM_ISLAND_SHORE"), BEACH("BEACHES"), - DESERT_HILLS(Biome.DESERT_HILLS), + DESERT_HILLS("DESERT_HILLS"), WOODED_HILLS("FOREST_HILLS"), - TAIGA_HILLS(Biome.TAIGA_HILLS), + TAIGA_HILLS("TAIGA_HILLS"), MOUNTAIN_EDGE("SMALLER_EXTREME_HILLS"), JUNGLE(Biome.JUNGLE), - JUNGLE_HILLS(Biome.JUNGLE_HILLS), - JUNGLE_EDGE(Biome.JUNGLE_EDGE), + JUNGLE_HILLS("JUNGLE_HILLS"), + JUNGLE_EDGE("JUNGLE_EDGE"), DEEP_OCEAN(Biome.DEEP_OCEAN), STONE_SHORE("STONE_BEACH"), SNOWY_BEACH("COLD_BEACH"), BIRCH_FOREST(Biome.BIRCH_FOREST), - BIRCH_FOREST_HILLS(Biome.BIRCH_FOREST_HILLS), + BIRCH_FOREST_HILLS("BIRCH_FOREST_HILLS"), DARK_FOREST("ROOFED_FOREST"), SNOWY_TAIGA("TAIGA_COLD"), SNOWY_TAIGA_HILLS("TAIGA_COLD_HILLS"), diff --git a/src/main/java/ch/njol/skript/util/BlockStateBlock.java b/src/main/java/ch/njol/skript/util/BlockStateBlock.java index ef43315df4c..09304441e5a 100644 --- a/src/main/java/ch/njol/skript/util/BlockStateBlock.java +++ b/src/main/java/ch/njol/skript/util/BlockStateBlock.java @@ -27,6 +27,7 @@ import org.bukkit.FluidCollisionMode; import org.bukkit.Location; import org.bukkit.Material; +import org.bukkit.SoundGroup; import org.bukkit.World; import org.bukkit.block.Biome; import org.bukkit.block.Block; @@ -207,6 +208,11 @@ public Biome getBiome() { return state.getBlock().getBiome(); } + @Override + public @NotNull Biome getComputedBiome() { + return state.getBlock().getComputedBiome(); + } + @Override public void setBiome(Biome bio) { state.getBlock().setBiome(bio); @@ -480,6 +486,11 @@ public BlockSoundGroup getSoundGroup() { return state.getBlock().getSoundGroup(); } + @Override + public @NotNull SoundGroup getBlockSoundGroup() { + return state.getBlock().getBlockSoundGroup(); + } + @Override public String getTranslationKey() { return state.getBlock().getTranslationKey(); @@ -510,6 +521,11 @@ public boolean isValidTool(@NotNull ItemStack itemStack) { return state.getBlock().getCollisionShape(); } + @Override + public boolean canPlace(@NotNull BlockData data) { + return state.getBlock().canPlace(data); + } + @Override public float getBreakSpeed(@NotNull Player player) { return state.getBlock().getBreakSpeed(player); diff --git a/src/main/java/ch/njol/skript/util/DelayedChangeBlock.java b/src/main/java/ch/njol/skript/util/DelayedChangeBlock.java index d3629e9213f..3d9671f0aa6 100644 --- a/src/main/java/ch/njol/skript/util/DelayedChangeBlock.java +++ b/src/main/java/ch/njol/skript/util/DelayedChangeBlock.java @@ -27,6 +27,7 @@ import org.bukkit.FluidCollisionMode; import org.bukkit.Location; import org.bukkit.Material; +import org.bukkit.SoundGroup; import org.bukkit.World; import org.bukkit.block.Biome; import org.bukkit.block.Block; @@ -207,6 +208,11 @@ public Biome getBiome() { return b.getBiome(); } + @Override + public @NotNull Biome getComputedBiome() { + return b.getComputedBiome(); + } + @Override public void setBiome(Biome bio) { b.setBiome(bio); @@ -444,6 +450,11 @@ public BlockSoundGroup getSoundGroup() { return b.getSoundGroup(); } + @Override + public @NotNull SoundGroup getBlockSoundGroup() { + return b.getBlockSoundGroup(); + } + @Override public String getTranslationKey() { return b.getTranslationKey(); @@ -475,6 +486,11 @@ public VoxelShape getCollisionShape() { return b.getCollisionShape(); } + @Override + public boolean canPlace(@NotNull BlockData data) { + return b.canPlace(data); + } + @Override public float getBreakSpeed(@NotNull Player player) { return b.getBreakSpeed(player); diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 8ffe537e750..57c7eed5572 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -93,6 +93,8 @@ enchantments: quick_charge: Quick Charge # new 1.16 Enchantment soul_speed: Soul Speed + # new 1.19 Enchantment + swift_sneak: Swift Sneak # -- Potion Effects -- potions: @@ -133,6 +135,9 @@ potions: bad_omen: bad omen hero_of_the_village: hero of the village + # 1.19 potion effect types + darkness: darkness + # -- Weather -- weather: clear: @@ -253,6 +258,9 @@ biomes: windswept_gravelly_hills: windswept gravelly hills, gravelly mountains windswept_savanna: windswept savanna, shattered savanna wooded_badlands: wooded badlands, wooded mesa, mesa forest, badlands forest + # 1.19 biomes + mangrove_swamp: mangrove swamp + deep_dark: deep dark # -- Tree Types -- tree types: @@ -1147,7 +1155,52 @@ entities: glow item frame: name: glow item frame¦s pattern: glow item[ ]frame(|1¦s) - + # 1.19 Entities + chest boat: + name: chest boat¦s + pattern: chest boat(|1¦s) + any chest boat: + name: chest boat¦s + pattern: any chest boat(|1¦s) + oak chest boat: + name: oak chest boat¦s + pattern: oak chest boat(|1¦s) + spruce chest boat: + name: spruce chest boat¦s + pattern: spruce chest boat(|1¦s) + birch chest boat: + name: birch chest boat¦s + pattern: birch chest boat(|1¦s) + jungle chest boat: + name: jungle chest boat¦s + pattern: jungle chest boat(|1¦s) + acacia chest boat: + name: acacia chest boat¦s + pattern: acacia chest boat(|1¦s) + dark oak chest boat: + name: dark oak chest boat¦s + pattern: dark oak chest boat(|1¦s) + frog: + name: frog¦s + pattern: <age> frog(|1¦s)|(4¦)frog (kid(|1¦s)|child(|1¦ren)) + temperate frog: + name: temperate frog¦s + pattern: <age> temperate frog(|1¦s)|(4¦)temperate frog (kid(|1¦s)|child(|1¦ren)) + warm frog: + name: warm frog¦s + pattern: <age> warm frog(|1¦s)|(4¦)warm frog (kid(|1¦s)|child(|1¦ren)) + cold frog: + name: cold frog¦s + pattern: <age> cold frog(|1¦s)|(4¦)cold frog (kid(|1¦s)|child(|1¦ren)) + allay: + name: allay¦s + pattern: allay(|1¦s) + tadpole: + name: tadpole¦s + pattern: tadpole(|1¦s) + warden: + name: warden¦s + pattern: warden(|1¦s) # -- Heal Reasons -- heal reasons: @@ -1206,6 +1259,7 @@ damage causes: freeze: freeze dryout: dryout custom: unknown, custom, plugin, a plugin + sonic_boom: sonic boom # -- Teleport Causes -- teleport causes: @@ -1613,6 +1667,23 @@ visual effects: name: scrape @- pattern: scrape + # 1.19 particles + sonic_boom: + name: sonic boom @- + pattern: sonic boom + sculk_soul: + name: sculk soul @- + pattern: sculk soul + sculk_charge: + name: sculk charge @- + pattern: sculk charge + sculk_charge_pop: + name: sculk charge pop @- + pattern: sculk charge pop + shriek: + name: shriek @- + pattern: shriek + # -- Inventory Actions -- inventory actions: From e453fa3cfc36872d0d9d9d140b29dca340311e11 Mon Sep 17 00:00:00 2001 From: kiip1 <25848425+kiip1@users.noreply.github.com> Date: Wed, 29 Jun 2022 18:34:29 +0200 Subject: [PATCH 009/619] Fix visual effects default range (#4843) --- src/main/java/ch/njol/skript/effects/EffVisualEffect.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffVisualEffect.java b/src/main/java/ch/njol/skript/effects/EffVisualEffect.java index c02d6b9f164..d919df3bed2 100644 --- a/src/main/java/ch/njol/skript/effects/EffVisualEffect.java +++ b/src/main/java/ch/njol/skript/effects/EffVisualEffect.java @@ -47,8 +47,8 @@ public class EffVisualEffect extends Effect { static { Skript.registerEffect(EffVisualEffect.class, - "(play|show) %visualeffects% (on|%directions%) %entities/locations% [(to %-players%|in (radius|range) of %number%)]", - "(play|show) %number% %visualeffects% (on|%directions%) %locations% [(to %-players%|in (radius|range) of %number%)]"); + "(play|show) %visualeffects% (on|%directions%) %entities/locations% [(to %-players%|in (radius|range) of %-number%)]", + "(play|show) %number% %visualeffects% (on|%directions%) %locations% [(to %-players%|in (radius|range) of %-number%)]"); } @SuppressWarnings("NotNullFieldNotInitialized") From 73cee044e2d081a0aaf8a1575eb3bd5c0a868878 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Wed, 29 Jun 2022 09:31:36 -0600 Subject: [PATCH 010/619] Fix server startup issue 1.19 (#4849) (cherry picked from commit 97f1d3edf4daaf566d6eedcbc1d9366d5a616182) --- src/main/java/ch/njol/skript/Skript.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 499b9cd5cb4..9567f4dc9ee 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -1090,7 +1090,8 @@ public void onPluginDisable(PluginDisableEvent event) { try { // Spigot removed the mapping for this method in 1.18, so its back to obfuscated method - String isRunningMethod = Skript.isRunningMinecraft(1, 18) ? "v" : "isRunning"; + // 1.19 mapping is u and 1.18 is v + String isRunningMethod = Skript.isRunningMinecraft(1, 19) ? "u" : Skript.isRunningMinecraft(1, 18) ? "v" :"isRunning"; IS_RUNNING = MC_SERVER.getClass().getMethod(isRunningMethod); } catch (NoSuchMethodException e) { throw new RuntimeException(e); From 24c062eae2fd5f0d6e91745266f85e856f513a83 Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Thu, 30 Jun 2022 05:44:42 +0200 Subject: [PATCH 011/619] Add Expression#getOptionalSingle (#4740) * Add Expression#getSingleOrDefault --- .../java/ch/njol/skript/lang/Expression.java | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/main/java/ch/njol/skript/lang/Expression.java b/src/main/java/ch/njol/skript/lang/Expression.java index df4aa775573..c0b58f166b0 100644 --- a/src/main/java/ch/njol/skript/lang/Expression.java +++ b/src/main/java/ch/njol/skript/lang/Expression.java @@ -18,15 +18,6 @@ */ package ch.njol.skript.lang; -import java.util.Iterator; -import java.util.Spliterators; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import org.bukkit.event.Event; -import org.bukkit.inventory.ItemStack; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer; import ch.njol.skript.classes.Changer.ChangeMode; @@ -39,6 +30,15 @@ import ch.njol.skript.registrations.Classes; import ch.njol.skript.util.slot.Slot; import ch.njol.util.Checker; +import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.Iterator; +import java.util.Optional; +import java.util.Spliterators; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; /** * Represents an expression. Expressions are used within conditions, effects and other expressions. @@ -63,7 +63,20 @@ public interface Expression<T> extends SyntaxElement, Debuggable { * @throws UnsupportedOperationException (optional) if this was called on a non-single expression */ @Nullable - public T getSingle(final Event e); + T getSingle(Event e); + + /** + * Get an optional of the single value of this expression. + * <p> + * Do not use this in conditions, use {@link #check(Event, Checker, boolean)} instead. + * + * @param e the event + * @return an {@link Optional} containing the {@link #getSingle(Event) single value} of this expression for this event. + * @see #getSingle(Event) + */ + default Optional<T> getOptionalSingle(Event e) { + return Optional.ofNullable(getSingle(e)); + } /** * Get all the values of this expression. The returned array is empty if this expression doesn't have any values for the given event. From f6c6ba5f48c7f59706f7516b21940329e804c09f Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Wed, 29 Jun 2022 22:01:04 -0600 Subject: [PATCH 012/619] Fix spelling mistake in Classes (#4223) * Fix spelling mistake --- src/main/java/ch/njol/skript/registrations/Classes.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/njol/skript/registrations/Classes.java b/src/main/java/ch/njol/skript/registrations/Classes.java index 8ae8eef2a6d..46a80c13c60 100644 --- a/src/main/java/ch/njol/skript/registrations/Classes.java +++ b/src/main/java/ch/njol/skript/registrations/Classes.java @@ -281,8 +281,8 @@ public static ClassInfo<?> getClassInfoNoError(final @Nullable String codeName) * <p> * This method can be called even while Skript is loading. * - * @param c The exact class to get the class info for - * @return The class info for the given class of null if no info was found. + * @param c The exact class to get the class info for. + * @return The class info for the given class or null if no info was found. */ @SuppressWarnings("unchecked") @Nullable From a45b189ca5bdc601a2dc6e99c74ea429f8c666a4 Mon Sep 17 00:00:00 2001 From: APickledWalrus <apickledwalrus@gmail.com> Date: Thu, 30 Jun 2022 00:13:09 -0400 Subject: [PATCH 013/619] Update bStats (#4631) * Update bStats --- build.gradle | 4 + src/main/java/ch/njol/skript/Metrics.java | 1036 --------------------- src/main/java/ch/njol/skript/Skript.java | 175 ++-- 3 files changed, 57 insertions(+), 1158 deletions(-) delete mode 100644 src/main/java/ch/njol/skript/Metrics.java diff --git a/build.gradle b/build.gradle index 24b3f68bdab..e53a57e2f55 100644 --- a/build.gradle +++ b/build.gradle @@ -27,6 +27,7 @@ allprojects { dependencies { shadow group: 'io.papermc', name: 'paperlib', version: '1.0.6' + shadow group: 'org.bstats', name: 'bstats-bukkit', version: '3.0.0' implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.19-R0.1-SNAPSHOT' implementation group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.600' implementation group: 'com.google.code.findbugs', name: 'findbugs', version: '3.0.1' @@ -77,8 +78,11 @@ tasks.withType(ShadowJar) { ] dependencies { include(dependency('io.papermc:paperlib')) + include(dependency('org.bstats:bstats-bukkit')) + include(dependency('org.bstats:bstats-base')) } relocate 'io.papermc.lib', 'ch.njol.skript.paperlib' + relocate 'org.bstats', 'ch.njol.skript.bstats' manifest { attributes( 'Name': 'ch/njol/skript', diff --git a/src/main/java/ch/njol/skript/Metrics.java b/src/main/java/ch/njol/skript/Metrics.java deleted file mode 100644 index 002a6e40d1d..00000000000 --- a/src/main/java/ch/njol/skript/Metrics.java +++ /dev/null @@ -1,1036 +0,0 @@ -package ch.njol.skript; - -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.net.URL; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Timer; -import java.util.TimerTask; -import java.util.UUID; -import java.util.logging.Level; -import java.util.zip.GZIPOutputStream; - -import javax.net.ssl.HttpsURLConnection; - -import org.bukkit.Bukkit; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.plugin.ServicePriority; -import org.bukkit.plugin.java.JavaPlugin; -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; - -/** - * bStats collects some data for plugin authors. - * - * Check out https://bStats.org/ to learn more about bStats! - * - * Slightly modified for usage in Skript, still under original license though. - */ -public class Metrics { - - // The version of this bStats class - public static final int B_STATS_VERSION = 1; - - // The url to which the data is sent - private static final String URL = "https://bStats.org/submitData/bukkit"; - - // Should failed requests be logged? - private static boolean logFailedRequests; - - // The uuid of the server - private static String serverUUID; - - // The plugin - private final JavaPlugin plugin; - - // A list with all custom charts - private final List<CustomChart> charts = new ArrayList<>(); - - /** - * Class constructor. - * - * @param plugin The plugin which stats should be submitted. - */ - public Metrics(JavaPlugin plugin) { - if (plugin == null) { - throw new IllegalArgumentException("Plugin cannot be null!"); - } - this.plugin = plugin; - - // Get the config file - File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); - File configFile = new File(bStatsFolder, "config.yml"); - YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); - - // Check if the config file exists - if (!config.isSet("serverUuid")) { - - // Add default values - config.addDefault("enabled", true); - // Every server gets it's unique random id. - config.addDefault("serverUuid", UUID.randomUUID().toString()); - // Should failed request be logged? - config.addDefault("logFailedRequests", false); - - // Inform the server owners about bStats - config.options().header( - "bStats collects some data for plugin authors like how many servers are using their plugins.\n" + - "To honor their work, you should not disable it.\n" + - "This has nearly no effect on the server performance!\n" + - "Check out https://bStats.org/ to learn more :)" - ).copyDefaults(true); - try { - config.save(configFile); - } catch (IOException ignored) { } - } - - // Load the data - serverUUID = config.getString("serverUuid"); - logFailedRequests = config.getBoolean("logFailedRequests", false); - if (config.getBoolean("enabled", true)) { - boolean found = false; - // Search for all other bStats Metrics classes to see if we are the first one - for (Class<?> service : Bukkit.getServicesManager().getKnownServices()) { - try { - service.getField("B_STATS_VERSION"); // Our identifier :) - found = true; // We aren't the first - break; - } catch (NoSuchFieldException ignored) { } - } - // Register our service - Bukkit.getServicesManager().register(Metrics.class, this, plugin, ServicePriority.Normal); - if (!found) { - // We are the first! - startSubmitting(); - } - } - } - - /** - * Adds a custom chart. - * - * @param chart The chart to add. - */ - public void addCustomChart(CustomChart chart) { - if (chart == null) { - throw new IllegalArgumentException("Chart cannot be null!"); - } - charts.add(chart); - } - - /** - * Starts the Scheduler which submits our data every 30 minutes. - */ - private void startSubmitting() { - final Timer timer = new Timer(true); // We use a timer cause the Bukkit scheduler is affected by server lags - timer.scheduleAtFixedRate(new TimerTask() { - @SuppressWarnings("synthetic-access") - @Override - public void run() { - if (!plugin.isEnabled()) { // Plugin was disabled - timer.cancel(); - return; - } - // Nevertheless we want our code to run in the Bukkit main thread, so we have to use the Bukkit scheduler - // Don't be afraid! The connection to the bStats server is still async, only the stats collection is sync ;) - Bukkit.getScheduler().runTask(plugin, new Runnable() { - @Override - public void run() { - submitData(); - } - }); - } - }, 1000*60*5, 1000*60*30); - // Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough time to start - // WARNING: Changing the frequency has no effect but your plugin WILL be blocked/deleted! - // WARNING: Just don't do it! - } - - /** - * Gets the plugin specific data. - * This method is called using Reflection. - * - * @return The plugin specific data. - */ - @SuppressWarnings("unchecked") - public JSONObject getPluginData() { - JSONObject data = new JSONObject(); - - String pluginName = plugin.getDescription().getName(); - String pluginVersion = plugin.getDescription().getVersion(); - - data.put("pluginName", pluginName); // Append the name of the plugin - data.put("pluginVersion", pluginVersion); // Append the version of the plugin - JSONArray customCharts = new JSONArray(); - for (CustomChart customChart : charts) { - // Add the data of the custom charts - JSONObject chart = customChart.getRequestJsonObject(); - if (chart == null) { // If the chart is null, we skip it - continue; - } - customCharts.add(chart); - } - data.put("customCharts", customCharts); - - return data; - } - - /** - * Gets the server specific data. - * - * @return The server specific data. - */ - @SuppressWarnings("unchecked") - private static JSONObject getServerData() { - // Minecraft specific data - int playerAmount = Bukkit.getOnlinePlayers().size(); - int onlineMode = Bukkit.getOnlineMode() ? 1 : 0; - String bukkitVersion = org.bukkit.Bukkit.getVersion(); - bukkitVersion = bukkitVersion.substring(bukkitVersion.indexOf("MC: ") + 4, bukkitVersion.length() - 1); - - // OS/Java specific data - String javaVersion = System.getProperty("java.version"); - String osName = System.getProperty("os.name"); - String osArch = System.getProperty("os.arch"); - String osVersion = System.getProperty("os.version"); - int coreCount = Runtime.getRuntime().availableProcessors(); - - JSONObject data = new JSONObject(); - - data.put("serverUUID", serverUUID); - - data.put("playerAmount", playerAmount); - data.put("onlineMode", onlineMode); - data.put("bukkitVersion", bukkitVersion); - - data.put("javaVersion", javaVersion); - data.put("osName", osName); - data.put("osArch", osArch); - data.put("osVersion", osVersion); - data.put("coreCount", coreCount); - - return data; - } - - /** - * Collects the data and sends it afterwards. - */ - @SuppressWarnings("unchecked") - private void submitData() { - final JSONObject data = getServerData(); - - JSONArray pluginData = new JSONArray(); - // Search for all other bStats Metrics classes to get their plugin data - for (Class<?> service : Bukkit.getServicesManager().getKnownServices()) { - try { - service.getField("B_STATS_VERSION"); // Our identifier :) - } catch (NoSuchFieldException ignored) { - continue; // Continue "searching" - } - // Found one! - try { - pluginData.add(service.getMethod("getPluginData").invoke(Bukkit.getServicesManager().load(service))); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) { } - } - - data.put("plugins", pluginData); - - // Create a new thread for the connection to the bStats server - new Thread(new Runnable() { - @SuppressWarnings("synthetic-access") - @Override - public void run() { - try { - // Send the data - sendData(data); - } catch (Exception e) { - // Something went wrong! :( - if (logFailedRequests) { - plugin.getLogger().log(Level.WARNING, "Could not submit plugin stats of " + plugin.getName(), e); - } - } - } - }).start(); - } - - /** - * Sends the data to the bStats server. - * - * @param data The data to send. - * @throws Exception If the request failed. - */ - private static void sendData(JSONObject data) throws Exception { - if (data == null) { - throw new IllegalArgumentException("Data cannot be null!"); - } - if (Bukkit.isPrimaryThread()) { - throw new IllegalAccessException("This method must not be called from the main thread!"); - } - HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection(); - - // Compress the data to save bandwidth - byte[] compressedData = compress(data.toString()); - - // Add headers - connection.setRequestMethod("POST"); - connection.addRequestProperty("Accept", "application/json"); - connection.addRequestProperty("Connection", "close"); - connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip our request - connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); - connection.setRequestProperty("Content-Type", "application/json"); // We send our data in JSON format - connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION); - - // Send data - connection.setDoOutput(true); - DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream()); - outputStream.write(compressedData); - outputStream.flush(); - outputStream.close(); - - connection.getInputStream().close(); // We don't care about the response - Just send our data :) - } - - /** - * Gzips the given String. - * - * @param str The string to gzip. - * @return The gzipped String. - * @throws IOException If the compression failed. - */ - private static byte[] compress(final String str) throws IOException { - if (str == null) { - return null; - } - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - GZIPOutputStream gzip = new GZIPOutputStream(outputStream); - gzip.write(str.getBytes("UTF-8")); - gzip.close(); - return outputStream.toByteArray(); - } - - /** - * Represents a custom chart. - */ - public static abstract class CustomChart { - - // The id of the chart - protected final String chartId; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - */ - public CustomChart(String chartId) { - if (chartId == null || chartId.isEmpty()) { - throw new IllegalArgumentException("ChartId cannot be null or empty!"); - } - this.chartId = chartId; - } - - @SuppressWarnings({"unchecked", "synthetic-access"}) - protected JSONObject getRequestJsonObject() { - JSONObject chart = new JSONObject(); - chart.put("chartId", chartId); - try { - JSONObject data = getChartData(); - if (data == null) { - // If the data is null we don't send the chart. - return null; - } - chart.put("data", data); - } catch (Throwable t) { - if (logFailedRequests) { - Bukkit.getLogger().log(Level.WARNING, "Failed to get data for custom chart with id " + chartId, t); - } - return null; - } - return chart; - } - - protected abstract JSONObject getChartData(); - - } - - /** - * Represents a custom simple pie. - */ - public static abstract class SimplePie extends CustomChart { - - /** - * Class constructor. - * - * @param chartId The id of the chart. - */ - public SimplePie(String chartId) { - super(chartId); - } - - /** - * Gets the value of the pie. - * - * @return The value of the pie. - */ - public abstract String getValue(); - - @SuppressWarnings("unchecked") - @Override - protected JSONObject getChartData() { - JSONObject data = new JSONObject(); - String value = getValue(); - if (value == null || value.isEmpty()) { - // Null = skip the chart - return null; - } - data.put("value", value); - return data; - } - } - - /** - * Represents a custom advanced pie. - */ - public static abstract class AdvancedPie extends CustomChart { - - /** - * Class constructor. - * - * @param chartId The id of the chart. - */ - public AdvancedPie(String chartId) { - super(chartId); - } - - /** - * Gets the values of the pie. - * - * @param valueMap Just an empty map. The only reason it exists is to make your life easier. - * You don't have to create a map yourself! - * @return The values of the pie. - */ - public abstract HashMap<String, Integer> getValues(HashMap<String, Integer> valueMap); - - @SuppressWarnings("unchecked") - @Override - protected JSONObject getChartData() { - JSONObject data = new JSONObject(); - JSONObject values = new JSONObject(); - HashMap<String, Integer> map = getValues(new HashMap<String, Integer>()); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry<String, Integer> entry : map.entrySet()) { - if (entry.getValue() == 0) { - continue; // Skip this invalid - } - allSkipped = false; - values.put(entry.getKey(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - data.put("values", values); - return data; - } - } - - /** - * Represents a custom single line chart. - */ - public static abstract class SingleLineChart extends CustomChart { - - /** - * Class constructor. - * - * @param chartId The id of the chart. - */ - public SingleLineChart(String chartId) { - super(chartId); - } - - /** - * Gets the value of the chart. - * - * @return The value of the chart. - */ - public abstract int getValue(); - - @SuppressWarnings("unchecked") - @Override - protected JSONObject getChartData() { - JSONObject data = new JSONObject(); - int value = getValue(); - if (value == 0) { - // Null = skip the chart - return null; - } - data.put("value", value); - return data; - } - - } - - /** - * Represents a custom multi line chart. - */ - public static abstract class MultiLineChart extends CustomChart { - - /** - * Class constructor. - * - * @param chartId The id of the chart. - */ - public MultiLineChart(String chartId) { - super(chartId); - } - - /** - * Gets the values of the chart. - * - * @param valueMap Just an empty map. The only reason it exists is to make your life easier. - * You don't have to create a map yourself! - * @return The values of the chart. - */ - public abstract HashMap<String, Integer> getValues(HashMap<String, Integer> valueMap); - - @SuppressWarnings("unchecked") - @Override - protected JSONObject getChartData() { - JSONObject data = new JSONObject(); - JSONObject values = new JSONObject(); - HashMap<String, Integer> map = getValues(new HashMap<String, Integer>()); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry<String, Integer> entry : map.entrySet()) { - if (entry.getValue() == 0) { - continue; // Skip this invalid - } - allSkipped = false; - values.put(entry.getKey(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - data.put("values", values); - return data; - } - - } - - /** - * Represents a custom simple bar chart. - */ - public static abstract class SimpleBarChart extends CustomChart { - - /** - * Class constructor. - * - * @param chartId The id of the chart. - */ - public SimpleBarChart(String chartId) { - super(chartId); - } - - /** - * Gets the value of the chart. - * - * @param valueMap Just an empty map. The only reason it exists is to make your life easier. - * You don't have to create a map yourself! - * @return The value of the chart. - */ - public abstract HashMap<String, Integer> getValues(HashMap<String, Integer> valueMap); - - @SuppressWarnings("unchecked") - @Override - protected JSONObject getChartData() { - JSONObject data = new JSONObject(); - JSONObject values = new JSONObject(); - HashMap<String, Integer> map = getValues(new HashMap<String, Integer>()); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - for (Map.Entry<String, Integer> entry : map.entrySet()) { - JSONArray categoryValues = new JSONArray(); - categoryValues.add(entry.getValue()); - values.put(entry.getKey(), categoryValues); - } - data.put("values", values); - return data; - } - - } - - /** - * Represents a custom advanced bar chart. - */ - public static abstract class AdvancedBarChart extends CustomChart { - - /** - * Class constructor. - * - * @param chartId The id of the chart. - */ - public AdvancedBarChart(String chartId) { - super(chartId); - } - - /** - * Gets the value of the chart. - * - * @param valueMap Just an empty map. The only reason it exists is to make your life easier. - * You don't have to create a map yourself! - * @return The value of the chart. - */ - public abstract HashMap<String, int[]> getValues(HashMap<String, int[]> valueMap); - - @SuppressWarnings("unchecked") - @Override - protected JSONObject getChartData() { - JSONObject data = new JSONObject(); - JSONObject values = new JSONObject(); - HashMap<String, int[]> map = getValues(new HashMap<String, int[]>()); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry<String, int[]> entry : map.entrySet()) { - if (entry.getValue().length == 0) { - continue; // Skip this invalid - } - allSkipped = false; - JSONArray categoryValues = new JSONArray(); - for (int categoryValue : entry.getValue()) { - categoryValues.add(categoryValue); - } - values.put(entry.getKey(), categoryValues); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - data.put("values", values); - return data; - } - - } - - /** - * Represents a custom simple map chart. - */ - public static abstract class SimpleMapChart extends CustomChart { - - /** - * Class constructor. - * - * @param chartId The id of the chart. - */ - public SimpleMapChart(String chartId) { - super(chartId); - } - - /** - * Gets the value of the chart. - * - * @return The value of the chart. - */ - public abstract Country getValue(); - - @SuppressWarnings("unchecked") - @Override - protected JSONObject getChartData() { - JSONObject data = new JSONObject(); - Country value = getValue(); - - if (value == null) { - // Null = skip the chart - return null; - } - data.put("value", value.getCountryIsoTag()); - return data; - } - - } - - /** - * Represents a custom advanced map chart. - */ - public static abstract class AdvancedMapChart extends CustomChart { - - /** - * Class constructor. - * - * @param chartId The id of the chart. - */ - public AdvancedMapChart(String chartId) { - super(chartId); - } - - /** - * Gets the value of the chart. - * - * @param valueMap Just an empty map. The only reason it exists is to make your life easier. - * You don't have to create a map yourself! - * @return The value of the chart. - */ - public abstract HashMap<Country, Integer> getValues(HashMap<Country, Integer> valueMap); - - @SuppressWarnings("unchecked") - @Override - protected JSONObject getChartData() { - JSONObject data = new JSONObject(); - JSONObject values = new JSONObject(); - HashMap<Country, Integer> map = getValues(new HashMap<Country, Integer>()); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry<Country, Integer> entry : map.entrySet()) { - if (entry.getValue() == 0) { - continue; // Skip this invalid - } - allSkipped = false; - values.put(entry.getKey().getCountryIsoTag(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - data.put("values", values); - return data; - } - - } - - /** - * A enum which is used for custom maps. - */ - public enum Country { - - /** - * bStats will use the country of the server. - */ - AUTO_DETECT("AUTO", "Auto Detected"), - - ANDORRA("AD", "Andorra"), - UNITED_ARAB_EMIRATES("AE", "United Arab Emirates"), - AFGHANISTAN("AF", "Afghanistan"), - ANTIGUA_AND_BARBUDA("AG", "Antigua and Barbuda"), - ANGUILLA("AI", "Anguilla"), - ALBANIA("AL", "Albania"), - ARMENIA("AM", "Armenia"), - NETHERLANDS_ANTILLES("AN", "Netherlands Antilles"), - ANGOLA("AO", "Angola"), - ANTARCTICA("AQ", "Antarctica"), - ARGENTINA("AR", "Argentina"), - AMERICAN_SAMOA("AS", "American Samoa"), - AUSTRIA("AT", "Austria"), - AUSTRALIA("AU", "Australia"), - ARUBA("AW", "Aruba"), - ALAND_ISLANDS("AX", "Åland Islands"), - AZERBAIJAN("AZ", "Azerbaijan"), - BOSNIA_AND_HERZEGOVINA("BA", "Bosnia and Herzegovina"), - BARBADOS("BB", "Barbados"), - BANGLADESH("BD", "Bangladesh"), - BELGIUM("BE", "Belgium"), - BURKINA_FASO("BF", "Burkina Faso"), - BULGARIA("BG", "Bulgaria"), - BAHRAIN("BH", "Bahrain"), - BURUNDI("BI", "Burundi"), - BENIN("BJ", "Benin"), - SAINT_BARTHELEMY("BL", "Saint Barthélemy"), - BERMUDA("BM", "Bermuda"), - BRUNEI("BN", "Brunei"), - BOLIVIA("BO", "Bolivia"), - BONAIRE_SINT_EUSTATIUS_AND_SABA("BQ", "Bonaire, Sint Eustatius and Saba"), - BRAZIL("BR", "Brazil"), - BAHAMAS("BS", "Bahamas"), - BHUTAN("BT", "Bhutan"), - BOUVET_ISLAND("BV", "Bouvet Island"), - BOTSWANA("BW", "Botswana"), - BELARUS("BY", "Belarus"), - BELIZE("BZ", "Belize"), - CANADA("CA", "Canada"), - COCOS_ISLANDS("CC", "Cocos Islands"), - THE_DEMOCRATIC_REPUBLIC_OF_CONGO("CD", "The Democratic Republic Of Congo"), - CENTRAL_AFRICAN_REPUBLIC("CF", "Central African Republic"), - CONGO("CG", "Congo"), - SWITZERLAND("CH", "Switzerland"), - COTE_D_IVOIRE("CI", "Côte d'Ivoire"), - COOK_ISLANDS("CK", "Cook Islands"), - CHILE("CL", "Chile"), - CAMEROON("CM", "Cameroon"), - CHINA("CN", "China"), - COLOMBIA("CO", "Colombia"), - COSTA_RICA("CR", "Costa Rica"), - CUBA("CU", "Cuba"), - CAPE_VERDE("CV", "Cape Verde"), - CURACAO("CW", "Curaçao"), - CHRISTMAS_ISLAND("CX", "Christmas Island"), - CYPRUS("CY", "Cyprus"), - CZECH_REPUBLIC("CZ", "Czech Republic"), - GERMANY("DE", "Germany"), - DJIBOUTI("DJ", "Djibouti"), - DENMARK("DK", "Denmark"), - DOMINICA("DM", "Dominica"), - DOMINICAN_REPUBLIC("DO", "Dominican Republic"), - ALGERIA("DZ", "Algeria"), - ECUADOR("EC", "Ecuador"), - ESTONIA("EE", "Estonia"), - EGYPT("EG", "Egypt"), - WESTERN_SAHARA("EH", "Western Sahara"), - ERITREA("ER", "Eritrea"), - SPAIN("ES", "Spain"), - ETHIOPIA("ET", "Ethiopia"), - FINLAND("FI", "Finland"), - FIJI("FJ", "Fiji"), - FALKLAND_ISLANDS("FK", "Falkland Islands"), - MICRONESIA("FM", "Micronesia"), - FAROE_ISLANDS("FO", "Faroe Islands"), - FRANCE("FR", "France"), - GABON("GA", "Gabon"), - UNITED_KINGDOM("GB", "United Kingdom"), - GRENADA("GD", "Grenada"), - GEORGIA("GE", "Georgia"), - FRENCH_GUIANA("GF", "French Guiana"), - GUERNSEY("GG", "Guernsey"), - GHANA("GH", "Ghana"), - GIBRALTAR("GI", "Gibraltar"), - GREENLAND("GL", "Greenland"), - GAMBIA("GM", "Gambia"), - GUINEA("GN", "Guinea"), - GUADELOUPE("GP", "Guadeloupe"), - EQUATORIAL_GUINEA("GQ", "Equatorial Guinea"), - GREECE("GR", "Greece"), - SOUTH_GEORGIA_AND_THE_SOUTH_SANDWICH_ISLANDS("GS", "South Georgia And The South Sandwich Islands"), - GUATEMALA("GT", "Guatemala"), - GUAM("GU", "Guam"), - GUINEA_BISSAU("GW", "Guinea-Bissau"), - GUYANA("GY", "Guyana"), - HONG_KONG("HK", "Hong Kong"), - HEARD_ISLAND_AND_MCDONALD_ISLANDS("HM", "Heard Island And McDonald Islands"), - HONDURAS("HN", "Honduras"), - CROATIA("HR", "Croatia"), - HAITI("HT", "Haiti"), - HUNGARY("HU", "Hungary"), - INDONESIA("ID", "Indonesia"), - IRELAND("IE", "Ireland"), - ISRAEL("IL", "Israel"), - ISLE_OF_MAN("IM", "Isle Of Man"), - INDIA("IN", "India"), - BRITISH_INDIAN_OCEAN_TERRITORY("IO", "British Indian Ocean Territory"), - IRAQ("IQ", "Iraq"), - IRAN("IR", "Iran"), - ICELAND("IS", "Iceland"), - ITALY("IT", "Italy"), - JERSEY("JE", "Jersey"), - JAMAICA("JM", "Jamaica"), - JORDAN("JO", "Jordan"), - JAPAN("JP", "Japan"), - KENYA("KE", "Kenya"), - KYRGYZSTAN("KG", "Kyrgyzstan"), - CAMBODIA("KH", "Cambodia"), - KIRIBATI("KI", "Kiribati"), - COMOROS("KM", "Comoros"), - SAINT_KITTS_AND_NEVIS("KN", "Saint Kitts And Nevis"), - NORTH_KOREA("KP", "North Korea"), - SOUTH_KOREA("KR", "South Korea"), - KUWAIT("KW", "Kuwait"), - CAYMAN_ISLANDS("KY", "Cayman Islands"), - KAZAKHSTAN("KZ", "Kazakhstan"), - LAOS("LA", "Laos"), - LEBANON("LB", "Lebanon"), - SAINT_LUCIA("LC", "Saint Lucia"), - LIECHTENSTEIN("LI", "Liechtenstein"), - SRI_LANKA("LK", "Sri Lanka"), - LIBERIA("LR", "Liberia"), - LESOTHO("LS", "Lesotho"), - LITHUANIA("LT", "Lithuania"), - LUXEMBOURG("LU", "Luxembourg"), - LATVIA("LV", "Latvia"), - LIBYA("LY", "Libya"), - MOROCCO("MA", "Morocco"), - MONACO("MC", "Monaco"), - MOLDOVA("MD", "Moldova"), - MONTENEGRO("ME", "Montenegro"), - SAINT_MARTIN("MF", "Saint Martin"), - MADAGASCAR("MG", "Madagascar"), - MARSHALL_ISLANDS("MH", "Marshall Islands"), - MACEDONIA("MK", "Macedonia"), - MALI("ML", "Mali"), - MYANMAR("MM", "Myanmar"), - MONGOLIA("MN", "Mongolia"), - MACAO("MO", "Macao"), - NORTHERN_MARIANA_ISLANDS("MP", "Northern Mariana Islands"), - MARTINIQUE("MQ", "Martinique"), - MAURITANIA("MR", "Mauritania"), - MONTSERRAT("MS", "Montserrat"), - MALTA("MT", "Malta"), - MAURITIUS("MU", "Mauritius"), - MALDIVES("MV", "Maldives"), - MALAWI("MW", "Malawi"), - MEXICO("MX", "Mexico"), - MALAYSIA("MY", "Malaysia"), - MOZAMBIQUE("MZ", "Mozambique"), - NAMIBIA("NA", "Namibia"), - NEW_CALEDONIA("NC", "New Caledonia"), - NIGER("NE", "Niger"), - NORFOLK_ISLAND("NF", "Norfolk Island"), - NIGERIA("NG", "Nigeria"), - NICARAGUA("NI", "Nicaragua"), - NETHERLANDS("NL", "Netherlands"), - NORWAY("NO", "Norway"), - NEPAL("NP", "Nepal"), - NAURU("NR", "Nauru"), - NIUE("NU", "Niue"), - NEW_ZEALAND("NZ", "New Zealand"), - OMAN("OM", "Oman"), - PANAMA("PA", "Panama"), - PERU("PE", "Peru"), - FRENCH_POLYNESIA("PF", "French Polynesia"), - PAPUA_NEW_GUINEA("PG", "Papua New Guinea"), - PHILIPPINES("PH", "Philippines"), - PAKISTAN("PK", "Pakistan"), - POLAND("PL", "Poland"), - SAINT_PIERRE_AND_MIQUELON("PM", "Saint Pierre And Miquelon"), - PITCAIRN("PN", "Pitcairn"), - PUERTO_RICO("PR", "Puerto Rico"), - PALESTINE("PS", "Palestine"), - PORTUGAL("PT", "Portugal"), - PALAU("PW", "Palau"), - PARAGUAY("PY", "Paraguay"), - QATAR("QA", "Qatar"), - REUNION("RE", "Reunion"), - ROMANIA("RO", "Romania"), - SERBIA("RS", "Serbia"), - RUSSIA("RU", "Russia"), - RWANDA("RW", "Rwanda"), - SAUDI_ARABIA("SA", "Saudi Arabia"), - SOLOMON_ISLANDS("SB", "Solomon Islands"), - SEYCHELLES("SC", "Seychelles"), - SUDAN("SD", "Sudan"), - SWEDEN("SE", "Sweden"), - SINGAPORE("SG", "Singapore"), - SAINT_HELENA("SH", "Saint Helena"), - SLOVENIA("SI", "Slovenia"), - SVALBARD_AND_JAN_MAYEN("SJ", "Svalbard And Jan Mayen"), - SLOVAKIA("SK", "Slovakia"), - SIERRA_LEONE("SL", "Sierra Leone"), - SAN_MARINO("SM", "San Marino"), - SENEGAL("SN", "Senegal"), - SOMALIA("SO", "Somalia"), - SURINAME("SR", "Suriname"), - SOUTH_SUDAN("SS", "South Sudan"), - SAO_TOME_AND_PRINCIPE("ST", "Sao Tome And Principe"), - EL_SALVADOR("SV", "El Salvador"), - SINT_MAARTEN_DUTCH_PART("SX", "Sint Maarten (Dutch part)"), - SYRIA("SY", "Syria"), - SWAZILAND("SZ", "Swaziland"), - TURKS_AND_CAICOS_ISLANDS("TC", "Turks And Caicos Islands"), - CHAD("TD", "Chad"), - FRENCH_SOUTHERN_TERRITORIES("TF", "French Southern Territories"), - TOGO("TG", "Togo"), - THAILAND("TH", "Thailand"), - TAJIKISTAN("TJ", "Tajikistan"), - TOKELAU("TK", "Tokelau"), - TIMOR_LESTE("TL", "Timor-Leste"), - TURKMENISTAN("TM", "Turkmenistan"), - TUNISIA("TN", "Tunisia"), - TONGA("TO", "Tonga"), - TURKEY("TR", "Turkey"), - TRINIDAD_AND_TOBAGO("TT", "Trinidad and Tobago"), - TUVALU("TV", "Tuvalu"), - TAIWAN("TW", "Taiwan"), - TANZANIA("TZ", "Tanzania"), - UKRAINE("UA", "Ukraine"), - UGANDA("UG", "Uganda"), - UNITED_STATES_MINOR_OUTLYING_ISLANDS("UM", "United States Minor Outlying Islands"), - UNITED_STATES("US", "United States"), - URUGUAY("UY", "Uruguay"), - UZBEKISTAN("UZ", "Uzbekistan"), - VATICAN("VA", "Vatican"), - SAINT_VINCENT_AND_THE_GRENADINES("VC", "Saint Vincent And The Grenadines"), - VENEZUELA("VE", "Venezuela"), - BRITISH_VIRGIN_ISLANDS("VG", "British Virgin Islands"), - U_S__VIRGIN_ISLANDS("VI", "U.S. Virgin Islands"), - VIETNAM("VN", "Vietnam"), - VANUATU("VU", "Vanuatu"), - WALLIS_AND_FUTUNA("WF", "Wallis And Futuna"), - SAMOA("WS", "Samoa"), - YEMEN("YE", "Yemen"), - MAYOTTE("YT", "Mayotte"), - SOUTH_AFRICA("ZA", "South Africa"), - ZAMBIA("ZM", "Zambia"), - ZIMBABWE("ZW", "Zimbabwe"); - - private String isoTag; - private String name; - - Country(String isoTag, String name) { - this.isoTag = isoTag; - this.name = name; - } - - /** - * Gets the name of the country. - * - * @return The name of the country. - */ - public String getCountryName() { - return name; - } - - /** - * Gets the iso tag of the country. - * - * @return The iso tag of the country. - */ - public String getCountryIsoTag() { - return isoTag; - } - - /** - * Gets a country by it's iso tag. - * - * @param isoTag The iso tag of the county. - * @return The country with the given iso tag or <code>null</code> if unknown. - */ - public static Country byIsoTag(String isoTag) { - for (Country country : Country.values()) { - if (country.getCountryIsoTag().equals(isoTag)) { - return country; - } - } - return null; - } - - /** - * Gets a country by a locale. - * - * @param locale The locale. - * @return The country from the giben locale or <code>null</code> if unknown country or - * if the locale does not contain a country. - */ - public static Country byLocale(Locale locale) { - return byIsoTag(locale.getCountry()); - } - - } - -} \ No newline at end of file diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 9567f4dc9ee..43862998eee 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -90,6 +90,8 @@ import ch.njol.util.coll.iterator.CheckedIterator; import ch.njol.util.coll.iterator.EnumerationIterable; 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; @@ -656,128 +658,57 @@ protected void afterErrors() { final long vld = System.currentTimeMillis() - vls; if (logNormal()) info("Loaded " + Variables.numVariables() + " variables in " + ((vld / 100) / 10.) + " seconds"); - - Metrics metrics = new Metrics(Skript.this); - - metrics.addCustomChart(new Metrics.SimplePie("pluginLanguage") { - - @Override - public String getValue() { - return Language.getName(); - } - }); - metrics.addCustomChart(new Metrics.SimplePie("effectCommands") { - - @Override - public String getValue() { - return "" + SkriptConfig.enableEffectCommands.value(); - } - }); - metrics.addCustomChart(new Metrics.SimplePie("uuidsWithPlayers") { - - @Override - public String getValue() { - return "" + SkriptConfig.usePlayerUUIDsInVariableNames.value(); - } - }); - metrics.addCustomChart(new Metrics.SimplePie("playerVariableFix") { - - @Override - public String getValue() { - return "" + SkriptConfig.enablePlayerVariableFix.value(); - } - }); - metrics.addCustomChart(new Metrics.SimplePie("logVerbosity") { - - @Override - public String getValue() { - return "" + SkriptConfig.verbosity.value().name().toLowerCase(Locale.ENGLISH).replace('_', ' '); - } - }); - metrics.addCustomChart(new Metrics.SimplePie("pluginPriority") { - - @Override - public String getValue() { - return "" + SkriptConfig.defaultEventPriority.value().name().toLowerCase(Locale.ENGLISH).replace('_', ' '); - } - }); - metrics.addCustomChart(new Metrics.SimplePie("logPlayerCommands") { - - @Override - public String getValue() { - return "" + SkriptConfig.logPlayerCommands.value(); - } - }); - metrics.addCustomChart(new Metrics.SimplePie("maxTargetDistance") { - - @Override - public String getValue() { - return "" + SkriptConfig.maxTargetBlockDistance.value(); - } - }); - metrics.addCustomChart(new Metrics.SimplePie("softApiExceptions") { - - @Override - public String getValue() { - return "" + SkriptConfig.apiSoftExceptions.value(); - } - }); - metrics.addCustomChart(new Metrics.SimplePie("timingsStatus") { - - @Override - public String getValue() { - if (!Skript.classExists("co.aikar.timings.Timings")) - return "unsupported"; - else - return "" + SkriptConfig.enableTimings.value(); - } - }); - metrics.addCustomChart(new Metrics.SimplePie("parseLinks") { - - @Override - public String getValue() { - return "" + ChatMessages.linkParseMode.name().toLowerCase(Locale.ENGLISH); - } - }); - metrics.addCustomChart(new Metrics.SimplePie("colorResetCodes") { - - @Override - public String getValue() { - return "" + SkriptConfig.colorResetCodes.value(); - } - }); - metrics.addCustomChart(new Metrics.SimplePie("functionsWithNulls") { - - @Override - public String getValue() { - return "" + SkriptConfig.executeFunctionsWithMissingParams.value(); - } - }); - metrics.addCustomChart(new Metrics.SimplePie("buildFlavor") { - - @Override - public String getValue() { - if (updater != null) { - return updater.getCurrentRelease().flavor; - } - return "unknown"; - } - }); - metrics.addCustomChart(new Metrics.SimplePie("updateCheckerEnabled") { - - @Override - public String getValue() { - return "" + SkriptConfig.checkForNewVersion.value(); - } - }); - metrics.addCustomChart(new Metrics.SimplePie("releaseChannel") { - - @Override - public String getValue() { - return "" + SkriptConfig.releaseChannel.value(); - } - }); - + + // 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", () -> + SkriptConfig.logPlayerCommands.value().toString() + )); + 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; /* From 503e4fa26bc05928f930dab3b337e94ccc9f5336 Mon Sep 17 00:00:00 2001 From: hotpocket184 <80357042+hotpocket184@users.noreply.github.com> Date: Thu, 30 Jun 2022 00:47:37 -0400 Subject: [PATCH 014/619] Fix for PrepareItemCraftEvent (#4852) Set event-slot index to 0 --- .../java/ch/njol/skript/classes/data/BukkitEventValues.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a68b2914d0f..4f1be7d518a 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -1053,7 +1053,7 @@ public Player get(BlockFertilizeEvent event) { @Override @Nullable public Slot get(final PrepareItemCraftEvent e) { - return new InventorySlot(e.getInventory(), 9); + return new InventorySlot(e.getInventory(), 0); } }, 0); EventValues.registerEventValue(PrepareItemCraftEvent.class, Player.class, new Getter<Player, PrepareItemCraftEvent>() { From 1c8a75d2e5b7dc55b4832fb2551c540d4dabce9b Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Wed, 29 Jun 2022 22:51:32 -0600 Subject: [PATCH 015/619] Improve visualeffect syntax (#4851) Clean up the visual effect pattern to avoid some potential errors and making the syntax more user friendly --- src/main/resources/lang/default.lang | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 57c7eed5572..4cca7dab598 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -1282,7 +1282,7 @@ game modes: # -- Visual Effects -- visual effects: - area_expression: [with [(1¦offset of %-number%, %-number%(,| and) %-number%)][(2¦[and ]speed %-number%)]] + area_expression: [[with] [(1¦offset (of|by) %-number%, %-number%(,| and) %-number%)][(2¦[(and|at) ]speed %-number%)]] effect: ender_signal: name: ender signal @an From 537429659da541f46d8c6ff34a28f851cc57f42f Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <alali_ayham@yahoo.com> Date: Thu, 30 Jun 2022 07:56:22 +0300 Subject: [PATCH 016/619] =?UTF-8?q?=E2=9A=92=EF=B8=8F=20Update=20config.sk?= =?UTF-8?q?=20examples=20to=20use=20`::`=20instead=20of=20`.`=20(#4837)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update config.sk examples to use :: instead of . --- src/main/resources/config.sk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/config.sk b/src/main/resources/config.sk index 0cd857048c8..41e3e681b5e 100644 --- a/src/main/resources/config.sk +++ b/src/main/resources/config.sk @@ -85,8 +85,8 @@ player variable fix: true # It will check if the player is online and then get the valid(new) player object and update the variable object to that one. use player UUIDs in variable names: false -# Whether to use a player's UUID instead of their name in variables, e.g. {home.%player%} will look like -# {home.e5240337-a4a2-39dd-8ed9-e5ce729a8522} instead of {home.njol}. +# Whether to use a player's UUID instead of their name in variables, e.g. {home::%player%} will look like +# {home::e5240337-a4a2-39dd-8ed9-e5ce729a8522} instead of {home::njol}. # Please note that if this setting is changed old variables WILL NOT be renamed automatically. From 386c4ef1a21b755010ccc591f47aaac9cc5db8b1 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Wed, 29 Jun 2022 23:04:02 -0600 Subject: [PATCH 017/619] Make both sound syntaxes similar (#4808) Mirror the `(to|for)` from the other sound syntax to make both them support it. --- src/main/java/ch/njol/skript/effects/EffPlaySound.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffPlaySound.java b/src/main/java/ch/njol/skript/effects/EffPlaySound.java index 4b09011c4c4..8375dafb18d 100644 --- a/src/main/java/ch/njol/skript/effects/EffPlaySound.java +++ b/src/main/java/ch/njol/skript/effects/EffPlaySound.java @@ -61,13 +61,13 @@ public class EffPlaySound extends Effect { if (SOUND_CATEGORIES_EXIST) { Skript.registerEffect(EffPlaySound.class, "play sound[s] %strings% [(in|from) %-soundcategory%] " + - "[(at|with) volume %-number%] [(and|at|with) pitch %-number%] at %locations% [for %-players%]", + "[(at|with) volume %-number%] [(and|at|with) pitch %-number%] at %locations% [(to|for) %-players%]", "play sound[s] %strings% [(in|from) %-soundcategory%] " + "[(at|with) volume %-number%] [(and|at|with) pitch %-number%] [(to|for) %players%] [(at|from) %-locations%]"); } else { Skript.registerEffect(EffPlaySound.class, "play sound[s] %strings% [(at|with) volume %-number%] " + - "[(and|at|with) pitch %-number%] at %locations% [for %-players%]", + "[(and|at|with) pitch %-number%] at %locations% [(to|for) %-players%]", "play sound[s] %strings% [(at|with) volume %-number%] " + "[(and|at|with) pitch %-number%] [(to|for) %players%] [(at|from) %-locations%]"); } From 6e0cf2bc18a14f1afbfefa5b398375b0df5948f1 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Wed, 29 Jun 2022 23:31:52 -0600 Subject: [PATCH 018/619] Avoid negative level experience (#4805) Negative numbers when setting the player level causes a console error. --- src/main/java/ch/njol/skript/expressions/ExprLevel.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/ch/njol/skript/expressions/ExprLevel.java b/src/main/java/ch/njol/skript/expressions/ExprLevel.java index 0a06eed0014..89f14ff718f 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprLevel.java +++ b/src/main/java/ch/njol/skript/expressions/ExprLevel.java @@ -121,6 +121,8 @@ public void change(final Event e, final @Nullable Object[] delta, final ChangeMo assert false; continue; } + if (level < 0) + continue; if (getTime() > 0 && e instanceof PlayerDeathEvent && ((PlayerDeathEvent) e).getEntity() == p && !Delay.isDelayed(e)) { ((PlayerDeathEvent) e).setNewLevel(level); } else { From f090e98e127d8c748089a28c5e75e37212560d24 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <alali_ayham@yahoo.com> Date: Thu, 30 Jun 2022 09:23:15 +0300 Subject: [PATCH 019/619] =?UTF-8?q?=F0=9F=9A=80=20Add=20age=20of=20block?= =?UTF-8?q?=20expression=20(#4594)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🚀 Add age of block expression --- .../njol/skript/expressions/ExprBlockAge.java | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprBlockAge.java diff --git a/src/main/java/ch/njol/skript/expressions/ExprBlockAge.java b/src/main/java/ch/njol/skript/expressions/ExprBlockAge.java new file mode 100644 index 00000000000..749c1324229 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprBlockAge.java @@ -0,0 +1,106 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.block.Block; +import org.bukkit.block.data.Ageable; +import org.bukkit.block.data.BlockData; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Block Age") +@Description({"Returns the age or max age of a block. 'Age' represents the different growth stages that a crop-like block can go through.", + "A value of 0 indicates that the crop was freshly planted, whilst a value equal to 'maximum age' indicates that the crop is ripe and ready to be harvested."}) +@Examples("set age of targeted block to max age of targeted block") +@RequiredPlugins("Minecraft 1.13+") +@Since("INSERT VERSION") +public class ExprBlockAge extends SimplePropertyExpression<Block, Integer> { + + static { + if (Skript.classExists("org.bukkit.block.data.Ageable")) + register(ExprBlockAge.class, Integer.class, "[:max[imum]] age", "block"); + } + + private boolean isMax = false; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + isMax = parseResult.hasTag("max"); + setExpr((Expression<Block>) exprs[0]); + return true; + } + + @Override + @Nullable + public Integer convert(Block block) { + BlockData bd = block.getBlockData(); + if (bd instanceof Ageable) { + if (isMax) + return ((Ageable) bd).getMaximumAge(); + else + return ((Ageable) bd).getAge(); + } + return null; + } + + @Override + @Nullable + public Class<?>[] acceptChange(ChangeMode mode) { + return !isMax && (mode == ChangeMode.SET || mode == ChangeMode.RESET) ? CollectionUtils.array(Number.class) : null; + } + + @Override + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + if (mode == ChangeMode.SET && (delta == null || delta[0] == null)) + return; + + int value = mode == ChangeMode.RESET ? 0 : ((Number) delta[0]).intValue(); + for (Block block : getExpr().getArray(event)) { + BlockData bd = block.getBlockData(); + if (bd instanceof Ageable) { + ((Ageable) bd).setAge(value); + } + block.setBlockData(bd); + } + } + + @Override + public Class<? extends Integer> getReturnType() { + return Integer.class; + } + + @Override + protected String getPropertyName() { + return (isMax ? "max " : "") + "age"; + } + +} From c6bfd66fc3ba5c59a370d2c16cac7ce9a3cf4e91 Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Thu, 30 Jun 2022 08:34:10 +0200 Subject: [PATCH 020/619] Parse more UnparsedLiterals, but only if they're actually meaningful (#4776) * Parse more UnparsedLiterals, but only if they're actually meaningful * Fix visual effects not being visible further than 1 block away if optional range was not set. * Fix server startup issue 1.19 (#4849) --- .../ch/njol/skript/lang/SkriptParser.java | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/main/java/ch/njol/skript/lang/SkriptParser.java b/src/main/java/ch/njol/skript/lang/SkriptParser.java index 2f72bf326ed..36343e5c139 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptParser.java +++ b/src/main/java/ch/njol/skript/lang/SkriptParser.java @@ -369,15 +369,20 @@ private final <T> Expression<? extends T> parseSingleExpr(final boolean allowUnp log.printError(); return null; } - if (allowUnparsedLiteral && types[0] == Object.class) { + if (types[0] == Object.class) { + // Do check if a literal with this name actually exists before returning an UnparsedLiteral + if (!allowUnparsedLiteral || Classes.parseSimple(expr, Object.class, context) == null) { + log.printError(); + return null; + } log.clear(); - LogEntry e = log.getError(); + final LogEntry e = log.getError(); return (Literal<? extends T>) new UnparsedLiteral(expr, e != null && (error == null || e.quality > error.quality) ? e : error); } for (final Class<? extends T> c : types) { log.clear(); assert c != null; - T t = Classes.parse(expr, c, context); + final T t = Classes.parse(expr, c, context); if (t != null) { log.printLog(); return new SimpleLiteral<>(t, false); @@ -563,14 +568,20 @@ else if (!hasSingular && hasPlural) log.printError(); return null; } - if (allowUnparsedLiteral && vi.classes[0].getC() == Object.class) { + if (vi.classes[0].getC() == Object.class) { + // Do check if a literal with this name actually exists before returning an UnparsedLiteral + if (!allowUnparsedLiteral || Classes.parseSimple(expr, Object.class, context) == null) { + log.printError(); + return null; + } log.clear(); - LogEntry e = log.getError(); + final LogEntry e = log.getError(); return new UnparsedLiteral(expr, e != null && (error == null || e.quality > error.quality) ? e : error); } - for (ClassInfo<?> ci : vi.classes) { + for (final ClassInfo<?> ci : vi.classes) { log.clear(); - Object t = Classes.parse(expr, ci.getC(), context); + assert ci.getC() != null; + final Object t = Classes.parse(expr, ci.getC(), context); if (t != null) { log.printLog(); return new SimpleLiteral<>(t, false, new UnparsedLiteral(expr)); @@ -613,7 +624,7 @@ public final <T> Expression<? extends T> parseExpression(final Class<? extends T final boolean isObject = types.length == 1 && types[0] == Object.class; final ParseLogHandler log = SkriptLogger.startParseLogHandler(); try { - final Expression<? extends T> r = parseSingleExpr(false, null, types); + final Expression<? extends T> r = parseSingleExpr(true, null, types); if (r != null) { log.printLog(); return r; @@ -739,7 +750,7 @@ public final Expression<?> parseExpression(final ExprInfo vi) { final ParseLogHandler log = SkriptLogger.startParseLogHandler(); try { // Attempt to parse a single expression - final Expression<?> r = parseSingleExpr(false, null, vi); + final Expression<?> r = parseSingleExpr(true, null, vi); if (r != null) { log.printLog(); return r; From 71b6784788555cc3ad385202eaef8261aa0b37d2 Mon Sep 17 00:00:00 2001 From: Kiip <25848425+kiip1@users.noreply.github.com> Date: Thu, 30 Jun 2022 16:25:10 +0200 Subject: [PATCH 021/619] Allow for continue in while loops (#4839) * Allow for continue in while loops --- .../ch/njol/skript/effects/EffContinue.java | 22 +++++++++++++------ .../tests/syntaxes/effects/EffContinue.sk | 11 ++++++++++ 2 files changed, 26 insertions(+), 7 deletions(-) create mode 100644 src/test/skript/tests/syntaxes/effects/EffContinue.sk diff --git a/src/main/java/ch/njol/skript/effects/EffContinue.java b/src/main/java/ch/njol/skript/effects/EffContinue.java index d3c814a909d..74a36d2c72d 100644 --- a/src/main/java/ch/njol/skript/effects/EffContinue.java +++ b/src/main/java/ch/njol/skript/effects/EffContinue.java @@ -27,12 +27,16 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.TriggerItem; +import ch.njol.skript.lang.TriggerSection; +import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.skript.sections.SecLoop; +import ch.njol.skript.sections.SecWhile; import ch.njol.util.Kleenean; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; import java.util.List; +import java.util.stream.Collectors; @Name("Continue") @Description("Skips the value currently being looped, moving on to the next value if it exists.") @@ -40,7 +44,7 @@ "\tif loop-value does not have permission \"moderator\":", "\t\tcontinue # filter out non moderators", "\tbroadcast \"%loop-player% is a moderator!\" # Only moderators get broadcast"}) -@Since("2.2-dev37") +@Since("2.2-dev37, INSERT VERSION (while loops)") public class EffContinue extends Effect { static { @@ -48,16 +52,20 @@ public class EffContinue extends Effect { } @SuppressWarnings("NotNullFieldNotInitialized") - private SecLoop loop; + private TriggerSection section; @Override public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - List<SecLoop> loops = getParser().getCurrentSections(SecLoop.class); - if (loops.isEmpty()) { - Skript.error("Continue may only be used in loops"); + List<TriggerSection> currentSections = ParserInstance.get().getCurrentSections().stream() + .filter(s -> s instanceof SecLoop || s instanceof SecWhile) + .collect(Collectors.toList()); + + if (currentSections.isEmpty()) { + Skript.error("Continue may only be used in while or loops"); return false; } - loop = loops.get(loops.size() - 1); + + section = currentSections.get(currentSections.size() - 1); return true; } @@ -69,7 +77,7 @@ protected void execute(Event e) { @Nullable @Override protected TriggerItem walk(Event e) { - return loop; + return section; } @Override diff --git a/src/test/skript/tests/syntaxes/effects/EffContinue.sk b/src/test/skript/tests/syntaxes/effects/EffContinue.sk new file mode 100644 index 00000000000..2a4685a8877 --- /dev/null +++ b/src/test/skript/tests/syntaxes/effects/EffContinue.sk @@ -0,0 +1,11 @@ +test "continue effect": + loop 10 times: + if loop-value is equal to 5: + continue + assert loop-value is not 5 with "continue in loop failed" + set {_i} to 0 + while {_i} is smaller than 10: + increase {_i} by 1 + if {_i} is equal to 5: + continue + assert {_i} is not 5 with "continue in while failed" From d35ada0ed9d109c8114f35dbacc6d80f347cf057 Mon Sep 17 00:00:00 2001 From: oskarkk <16339637+oskarkk@users.noreply.github.com> Date: Fri, 1 Jul 2022 10:16:58 +0200 Subject: [PATCH 022/619] Fix problems with EffSendTitle (#4362) * Fix errors when times in EffSendTitle are null * Fix "send subtitle" and add some clarification to doc * Improve code formatting, logic and variable names in EffSendTitle --- .../ch/njol/skript/effects/EffSendTitle.java | 52 +++++++++++++------ 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffSendTitle.java b/src/main/java/ch/njol/skript/effects/EffSendTitle.java index fb2e0465eae..d6746ad36db 100644 --- a/src/main/java/ch/njol/skript/effects/EffSendTitle.java +++ b/src/main/java/ch/njol/skript/effects/EffSendTitle.java @@ -34,13 +34,22 @@ import ch.njol.util.Kleenean; @Name("Title - Send") -@Description({"Sends a title/subtitle to the given player(s) with optional fadein/stay/fadeout times for Minecraft versions 1.11 and above.", - "Note: if no input is given for the title/subtitle or the times," + - "it will keep the ones from the last title sent, use the <a href='effects.html#EffResetTitle'>reset title</a> effect to restore the default values."}) -@Examples({"send title \"Competition Started\" with subtitle \"Have fun, Stay safe!\" to player for 5 seconds", - "send title \"Hi %player%\" to player", "send title \"Loot Drop\" with subtitle \"starts in 3 minutes\" to all players", - "send title \"Hello %player%!\" with subtitle \"Welcome to our server\" to player for 5 seconds with fadein 1 second and fade out 1 second", - "send subtitle \"Party!\" to all players"}) +@Description({ + "Sends a title/subtitle to the given player(s) with optional fadein/stay/fadeout times for Minecraft versions 1.11 and above. ", + "", + "If you're sending only the subtitle, it will be shown only if there's a title displayed at the moment, otherwise it will " + + "be sent with the next title. To show only the subtitle, use: <code>send title \" \" with subtitle \"yourtexthere\" to player</code>.", + "", + "Note: if no input is given for the times, it will keep the ones from the last title sent, " + + "use the <a href='effects.html#EffResetTitle'>reset title</a> effect to restore the default values." +}) +@Examples({ + "send title \"Competition Started\" with subtitle \"Have fun, Stay safe!\" to player for 5 seconds", + "send title \"Hi %player%\" to player", + "send title \"Loot Drop\" with subtitle \"starts in 3 minutes\" to all players", + "send title \"Hello %player%!\" with subtitle \"Welcome to our server\" to player for 5 seconds with fadein 1 second and fade out 1 second", + "send subtitle \"Party!\" to all players" +}) @Since("2.3") public class EffSendTitle extends Effect { @@ -55,7 +64,6 @@ public class EffSendTitle extends Effect { Skript.registerEffect(EffSendTitle.class, "send title %string% [with subtitle %-string%] [to %players%]", "send subtitle %string% [to %players%]"); - } @Nullable @@ -84,19 +92,33 @@ public boolean init(final Expression<?>[] exprs, final int matchedPattern, final @SuppressWarnings("null") @Override protected void execute(final Event e) { - String title = this.title != null ? this.title.getSingle(e) : "", - sub = subtitle != null ? subtitle.getSingle(e) : null; + String title = this.title != null ? this.title.getSingle(e) : null; + String subtitle = this.subtitle != null ? this.subtitle.getSingle(e) : null; if (TIME_SUPPORTED) { - int in = fadeIn != null ? (int) fadeIn.getSingle(e).getTicks_i() : -1, - stay = this.stay != null ? (int) this.stay.getSingle(e).getTicks_i() : -1, - out = fadeOut != null ? (int) fadeOut.getSingle(e).getTicks_i() : -1; + int fadeIn, stay, fadeOut; + fadeIn = stay = fadeOut = -1; + + if (this.fadeIn != null) { + Timespan t = this.fadeIn.getSingle(e); + fadeIn = t != null ? (int) t.getTicks_i() : -1; + } + + if (this.stay != null) { + Timespan t = this.stay.getSingle(e); + stay = t != null ? (int) t.getTicks_i() : -1; + } + + if (this.fadeOut != null) { + Timespan t = this.fadeOut.getSingle(e); + fadeOut = t != null ? (int) t.getTicks_i() : -1; + } for (Player p : recipients.getArray(e)) - p.sendTitle(title, sub, in, stay, out); + p.sendTitle(title, subtitle, fadeIn, stay, fadeOut); } else { for (Player p : recipients.getArray(e)) - p.sendTitle(title, sub); + p.sendTitle(title, subtitle); } } From 4672477b407e421340431ca7a7330176c59e9134 Mon Sep 17 00:00:00 2001 From: Shane Bee <shanebolenback@me.com> Date: Fri, 1 Jul 2022 08:12:57 -0700 Subject: [PATCH 023/619] FallingBlockData - fix an issue with missing consumer (#4844) * FallingBlockData - fix an issue with missing consumer - twas a bug I found in SkBee - also cleaned up some deprecated code * FallingBlockData - update assertion * FallingBlockData - remove air check - Both minecraft and craftbukkit allow this, so we shouldn't stop it --- .../ch/njol/skript/entity/FallingBlockData.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/ch/njol/skript/entity/FallingBlockData.java b/src/main/java/ch/njol/skript/entity/FallingBlockData.java index 821e0f90772..66e2c9378f6 100644 --- a/src/main/java/ch/njol/skript/entity/FallingBlockData.java +++ b/src/main/java/ch/njol/skript/entity/FallingBlockData.java @@ -24,7 +24,6 @@ import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.entity.FallingBlock; -import org.bukkit.inventory.ItemStack; import org.bukkit.util.Consumer; import org.eclipse.jdt.annotation.Nullable; @@ -112,18 +111,22 @@ protected boolean match(final FallingBlock entity) { return true; } - @SuppressWarnings("deprecation") @Override @Nullable public FallingBlock spawn(Location loc, @Nullable Consumer<FallingBlock> consumer) { ItemType t = CollectionUtils.getRandom(types); assert t != null; - ItemStack i = t.getRandom(); - if (i == null || i.getType() == Material.AIR || !i.getType().isBlock()) { - assert false : i; + Material material = t.getMaterial(); + + if (!material.isBlock()) { + assert false : t; return null; } - return loc.getWorld().spawnFallingBlock(loc, i.getType(), (byte) i.getDurability()); + FallingBlock fallingBlock = loc.getWorld().spawnFallingBlock(loc, material.createBlockData()); + if (consumer != null) + consumer.accept(fallingBlock); + + return fallingBlock; } @Override From 5e8a5d892d214dddfc878604fd24d56634f45633 Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Sat, 2 Jul 2022 10:59:18 +0200 Subject: [PATCH 024/619] Temporarily change composter particle pattern (#4857) Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> --- src/main/resources/lang/default.lang | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 4cca7dab598..5871800c0e6 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -1528,7 +1528,7 @@ visual effects: pattern: campfire signal smoke composter: name: composter @- - pattern: composter + pattern: composter particle flash: name: flash @- pattern: flash From 21020c2d67cf0fa1efe96ecfa75b43e8eb044487 Mon Sep 17 00:00:00 2001 From: _Mads <75088349+TFSMads@users.noreply.github.com> Date: Tue, 5 Jul 2022 22:08:33 +0200 Subject: [PATCH 025/619] Changed argumentPattern regex in Commands class (#4796) --- src/main/java/ch/njol/skript/command/Commands.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/command/Commands.java b/src/main/java/ch/njol/skript/command/Commands.java index 44b14978a80..7d1935202dd 100644 --- a/src/main/java/ch/njol/skript/command/Commands.java +++ b/src/main/java/ch/njol/skript/command/Commands.java @@ -312,7 +312,7 @@ static boolean handleEffectCommand(final CommandSender sender, String command) { @SuppressWarnings("null") private final static Pattern commandPattern = Pattern.compile("(?i)^command /?(\\S+)\\s*(\\s+(.+))?$"), - argumentPattern = Pattern.compile("<\\s*(?:(.+?)\\s*:\\s*)?(.+?)\\s*(?:=\\s*(" + SkriptParser.wildcard + "))?\\s*>"); + argumentPattern = Pattern.compile("<\\s*(?:([^>]+?)\\s*:\\s*)?(.+?)\\s*(?:=\\s*(" + SkriptParser.wildcard + "))?\\s*>"); @Nullable public static ScriptCommand loadCommand(final SectionNode node) { From 688b617e2fa506b3f4ab001d5bd19d05fb0659c4 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <alali_ayham@yahoo.com> Date: Tue, 5 Jul 2022 23:25:23 +0300 Subject: [PATCH 026/619] =?UTF-8?q?=F0=9F=9A=80=20Fix=20ExprEntities=20ite?= =?UTF-8?q?rate=20method=20in=20chunks=20(#4608)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../njol/skript/expressions/ExprEntities.java | 35 ++++--------------- .../syntaxes/expressions/ExprEntities.sk | 5 +++ 2 files changed, 12 insertions(+), 28 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprEntities.java b/src/main/java/ch/njol/skript/expressions/ExprEntities.java index 7d07d0761b8..e65d6f257a2 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprEntities.java +++ b/src/main/java/ch/njol/skript/expressions/ExprEntities.java @@ -20,6 +20,7 @@ import java.lang.reflect.Array; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; @@ -53,7 +54,7 @@ @Name("Entities") @Description("All entities in all worlds, in a specific world, in a chunk or in a radius around a certain location, " + - "e.g. 'all players', 'all creepers in the player's world', or 'players in radius 100 of the player'.") + "e.g. <code>all players</code>, <code>all creepers in the player's world</code>, or <code>players in radius 100 of the player</code>.") @Examples({"kill all creepers in the player's world", "send \"Psst!\" to all players within 100 meters of the player", "give a diamond to all ops", @@ -167,6 +168,9 @@ public Iterator<? extends Entity> iterator(Event e) { return null; double d = n.doubleValue(); + if (l.getWorld() == null) // safety + return null; + Collection<Entity> es = l.getWorld().getNearbyEntities(l, d, d, d); double radiusSquared = d * d * Skript.EPSILON_MULT; EntityData<?>[] ts = types.getAll(e); @@ -180,35 +184,10 @@ public Iterator<? extends Entity> iterator(Event e) { return false; }); } else { - if (worlds == null && returnType == Player.class) + if (chunks == null || returnType == Player.class) return super.iterator(e); - return new NonNullIterator<Entity>() { - private World[] ws = worlds == null ? Bukkit.getWorlds().toArray(new World[0]) : worlds.getArray(e); - private EntityData<?>[] ts = types.getAll(e); - private int w = -1; - @Nullable - private Iterator<? extends Entity> curIter = null; - - @Override - @Nullable - protected Entity getNext() { - while (true) { - while (curIter == null || !curIter.hasNext()) { - w++; - if (w == ws.length) - return null; - curIter = ws[w].getEntitiesByClass(returnType).iterator(); - } - while (curIter.hasNext()) { - Entity current = curIter.next(); - for (EntityData<?> t : ts) { - if (t.isInstance(current)) - return current; - } - } - } - }}; + return Arrays.stream(EntityData.getAll(types.getArray(e), returnType, chunks.getArray(e))).iterator(); } } diff --git a/src/test/skript/tests/syntaxes/expressions/ExprEntities.sk b/src/test/skript/tests/syntaxes/expressions/ExprEntities.sk index e98cad46aa3..707bc3d593a 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprEntities.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprEntities.sk @@ -2,5 +2,10 @@ test "entities in chunk": spawn 10 sheep at spawn of world "world" wait 1 tick assert size of all entities in chunk at spawn of world "world" >= 10 with "Size of all entities in spawn chunk is not > 10: %size of all entities in chunk at spawn of world "world"%" + + loop all entities in chunk at spawn of world "world": + add loop-entity to {_e::*} + assert size of {_e::*} >= 10 with "Size of all entities in spawn chunk is not > 10 (iterating): %size of {_e::*}%" + delete all entities in chunk at spawn of world "world" assert size of all entities in chunk at spawn of world "world" = 0 with "Size of all entities in spawn chunk != 0: %size of all entities in chunk at spawn of world "world"%" From d4be6901f9908f1dddc00e1a8fd3adc7206944e6 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <alali_ayham@yahoo.com> Date: Tue, 5 Jul 2022 23:29:13 +0300 Subject: [PATCH 027/619] =?UTF-8?q?=E2=9A=92=EF=B8=8F=20Update=20docs=20li?= =?UTF-8?q?nk,=20add=20tutorials=20link,=20fix=20equals=20for=20some=20arg?= =?UTF-8?q?s=20(#4838)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/ch/njol/skript/SkriptCommand.java | 7 ++++--- src/main/resources/lang/english.lang | 3 ++- src/main/resources/lang/german.lang | 3 ++- src/main/resources/lang/korean.lang | 3 ++- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/java/ch/njol/skript/SkriptCommand.java b/src/main/java/ch/njol/skript/SkriptCommand.java index c063a448580..d4f04d724ef 100644 --- a/src/main/java/ch/njol/skript/SkriptCommand.java +++ b/src/main/java/ch/njol/skript/SkriptCommand.java @@ -184,7 +184,7 @@ public boolean onCommand(@Nullable CommandSender sender, @Nullable Command comma } } } else if (args[0].equalsIgnoreCase("enable")) { - if (args[1].equals("all")) { + if (args[1].equalsIgnoreCase("all")) { try { info(sender, "enable.all.enabling"); File[] files = toggleScripts(new File(Skript.getInstance().getDataFolder(), Skript.SCRIPTSFOLDER), true).toArray(new File[0]); @@ -264,7 +264,7 @@ public boolean onCommand(@Nullable CommandSender sender, @Nullable Command comma } } } else if (args[0].equalsIgnoreCase("disable")) { - if (args[1].equals("all")) { + if (args[1].equalsIgnoreCase("all")) { ScriptLoader.disableScripts(); try { toggleScripts(new File(Skript.getInstance().getDataFolder(), Skript.SCRIPTSFOLDER), false); @@ -318,7 +318,7 @@ public boolean onCommand(@Nullable CommandSender sender, @Nullable Command comma Skript.info(sender, "" + SkriptUpdater.m_internal_error); return true; } - if (args[1].equals("check")) { + if (args[1].equalsIgnoreCase("check")) { updater.updateCheck(sender); } else if (args[1].equalsIgnoreCase("changes")) { updater.changesCheck(sender); @@ -328,6 +328,7 @@ public boolean onCommand(@Nullable CommandSender sender, @Nullable Command comma } else if (args[0].equalsIgnoreCase("info")) { info(sender, "info.aliases"); info(sender, "info.documentation"); + info(sender, "info.tutorials"); info(sender, "info.server", Bukkit.getVersion()); info(sender, "info.version", Skript.getVersion()); info(sender, "info.addons", Skript.getAddons().isEmpty() ? "None" : ""); diff --git a/src/main/resources/lang/english.lang b/src/main/resources/lang/english.lang index feadaf100c8..6673775e7ba 100644 --- a/src/main/resources/lang/english.lang +++ b/src/main/resources/lang/english.lang @@ -111,7 +111,8 @@ skript command: next page: <grey>page %s of %s. Type <gold>/skript update changes %s<gray> for the next page (hint: use the up arrow key) info: aliases: Skript's aliases can be found here: <aqua>https://github.com/SkriptLang/skript-aliases - documentation: Skript's documentation can be found here: <aqua>https://skriptlang.github.io/Skript + documentation: Skript's documentation can be found here: <aqua>https://docs.skriptlang.org/ + tutorials: Skript's tutorials can be found here: <aqua>https://docs.skriptlang.org/tutorials version: Skript Version: <aqua>%s server: Server Version: <aqua>%s addons: Installed Skript Addons: <aqua>%s diff --git a/src/main/resources/lang/german.lang b/src/main/resources/lang/german.lang index a4c554e324c..89a14b0110a 100644 --- a/src/main/resources/lang/german.lang +++ b/src/main/resources/lang/german.lang @@ -111,7 +111,8 @@ skript command: next page: <gray>Seite %s von %s. Tippe <gold>/skript update changes %s<gray> für die nächste Seite (Tipp: benutze die Pfeil-Hoch-Taste) info: aliases: Die Aliase von Skript finden Sie hier: <aqua>https://github.com/SkriptLang/skript-aliases - documentation: Die Dokumentation von Skript finden Sie hier: <aqua>https://skriptlang.github.io/Skript + documentation: Die Dokumentation von Skript finden Sie hier: <aqua>https://docs.skriptlang.org/ + tutorials: Die Tutorials von Skript finden Sie hier: <aqua>https://docs.skriptlang.org/tutorials version: Skript Version: <aqua>%s server: Server Version: <aqua>%s addons: Installierte Skript Addons: <aqua>%s diff --git a/src/main/resources/lang/korean.lang b/src/main/resources/lang/korean.lang index 2efacd41572..0d40df4d485 100644 --- a/src/main/resources/lang/korean.lang +++ b/src/main/resources/lang/korean.lang @@ -111,7 +111,8 @@ skript command: next page: <grey>%s의 %s페이지입니다. 다음 페이지를 보려면 <gold>/skript update changes %s <gray>를 입력하십시오 (위쪽 화살표 키를 사용해보십시오) info: aliases: Skript의 별명은 여기에서 찾을 수 있습니다: <aqua>https://github.com/SkriptLang/skript-aliases - documentation: Skript의 문서는 여기에서 찾을 수 있습니다: <aqua>https://skriptlang.github.io/Skript + documentation: Skript의 문서는 여기에서 찾을 수 있습니다: <aqua>https://docs.skriptlang.org/ + tutorials: Skript의 튜토리얼은 여기에서 찾을 수 있습니다: <aqua>https://docs.skriptlang.org/tutorials version: Skript 버전: <aqua>%s server: 서버 버전: <aqua>%s addons: 설치된 Skript 애드온: <aqua>%s From db3214d13f9a57cd4e6904d7f33bf08302b442c4 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <alali_ayham@yahoo.com> Date: Tue, 5 Jul 2022 23:43:12 +0300 Subject: [PATCH 028/619] =?UTF-8?q?=F0=9F=9A=80=20Add=20missing=20event=20?= =?UTF-8?q?values=20for=20crafting=20events=20+=20fix=20`PrepareItemCraftE?= =?UTF-8?q?vent`=20(#4409)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../classes/data/BukkitEventValues.java | 73 +++++++++++-------- .../java/ch/njol/skript/events/EvtItem.java | 12 +-- 2 files changed, 48 insertions(+), 37 deletions(-) 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 4f1be7d518a..1a9fc9c5363 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -18,8 +18,24 @@ */ package ch.njol.skript.classes.data; -import java.util.List; - +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.Aliases; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.command.CommandEvent; +import ch.njol.skript.events.EvtMoveOn; +import ch.njol.skript.events.bukkit.ScriptEvent; +import ch.njol.skript.events.bukkit.SkriptStartEvent; +import ch.njol.skript.events.bukkit.SkriptStopEvent; +import ch.njol.skript.registrations.EventValues; +import ch.njol.skript.util.BlockStateBlock; +import ch.njol.skript.util.BlockUtils; +import ch.njol.skript.util.DelayedChangeBlock; +import ch.njol.skript.util.Direction; +import ch.njol.skript.util.Getter; +import ch.njol.skript.util.slot.InventorySlot; +import ch.njol.skript.util.slot.Slot; +import com.destroystokyo.paper.event.entity.ProjectileCollideEvent; +import com.destroystokyo.paper.event.player.PlayerArmorChangeEvent; import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.FireworkEffect; @@ -126,25 +142,7 @@ import org.bukkit.potion.PotionEffectType; import org.eclipse.jdt.annotation.Nullable; -import com.destroystokyo.paper.event.entity.ProjectileCollideEvent; -import com.destroystokyo.paper.event.player.PlayerArmorChangeEvent; - -import ch.njol.skript.Skript; -import ch.njol.skript.aliases.Aliases; -import ch.njol.skript.aliases.ItemType; -import ch.njol.skript.command.CommandEvent; -import ch.njol.skript.events.EvtMoveOn; -import ch.njol.skript.events.bukkit.ScriptEvent; -import ch.njol.skript.events.bukkit.SkriptStartEvent; -import ch.njol.skript.events.bukkit.SkriptStopEvent; -import ch.njol.skript.registrations.EventValues; -import ch.njol.skript.util.BlockStateBlock; -import ch.njol.skript.util.BlockUtils; -import ch.njol.skript.util.DelayedChangeBlock; -import ch.njol.skript.util.Direction; -import ch.njol.skript.util.Getter; -import ch.njol.skript.util.slot.InventorySlot; -import ch.njol.skript.util.slot.Slot; +import java.util.List; /** * @author Peter Güttinger @@ -156,7 +154,8 @@ public BukkitEventValues() {} private static final boolean offHandSupport = Skript.isRunningMinecraft(1, 9); private static final boolean NAMESPACE_SUPPORT = Skript.classExists("org.bukkit.NamespacedKey"); - + private static final ItemStack AIR_IS = new ItemStack(Material.AIR); + static { // === WorldEvents === @@ -1040,22 +1039,26 @@ public Player get(BlockFertilizeEvent event) { } }, 0); } - // CraftItemEvent REMIND maybe re-add this when Skript parser is reworked? -// EventValues.registerEventValue(CraftItemEvent.class, ItemStack.class, new Getter<ItemStack, CraftItemEvent>() { -// @Override -// @Nullable -// public ItemStack get(final CraftItemEvent e) { -// return e.getRecipe().getResult(); -// } -// }, 0); // PrepareItemCraftEvent EventValues.registerEventValue(PrepareItemCraftEvent.class, Slot.class, new Getter<Slot, PrepareItemCraftEvent>() { @Override - @Nullable public Slot get(final PrepareItemCraftEvent e) { return new InventorySlot(e.getInventory(), 0); } }, 0); + EventValues.registerEventValue(PrepareItemCraftEvent.class, ItemStack.class, new Getter<ItemStack, PrepareItemCraftEvent>() { + @Override + public ItemStack get(PrepareItemCraftEvent e) { + ItemStack item = e.getInventory().getResult(); + return item != null ? item : AIR_IS; + } + }, 0); + EventValues.registerEventValue(PrepareItemCraftEvent.class, Inventory.class, new Getter<Inventory, PrepareItemCraftEvent>() { + @Override + public Inventory get(PrepareItemCraftEvent e) { + return e.getInventory(); + } + }, 0); EventValues.registerEventValue(PrepareItemCraftEvent.class, Player.class, new Getter<Player, PrepareItemCraftEvent>() { @Override @Nullable @@ -1092,6 +1095,14 @@ public String get(PrepareItemCraftEvent e) { } }, 0); } + // CraftItemEvent + EventValues.registerEventValue(CraftItemEvent.class, ItemStack.class, new Getter<ItemStack, CraftItemEvent>() { + @Override + @Nullable + public ItemStack get(CraftItemEvent e) { + return e.getRecipe().getResult(); + } + }, 0); //InventoryOpenEvent EventValues.registerEventValue(InventoryOpenEvent.class, Player.class, new Getter<Player, InventoryOpenEvent>() { @Override diff --git a/src/main/java/ch/njol/skript/events/EvtItem.java b/src/main/java/ch/njol/skript/events/EvtItem.java index 39e6b603316..5e82cfb297c 100644 --- a/src/main/java/ch/njol/skript/events/EvtItem.java +++ b/src/main/java/ch/njol/skript/events/EvtItem.java @@ -64,17 +64,17 @@ public class EvtItem extends SkriptEvent { .description("Called when a player drops an item from their inventory.") .examples("on drop:") .since("<i>unknown</i> (before 2.1)"); - // TODO limit to InventoryAction.PICKUP_* and similar (e.g. COLLECT_TO_CURSOR) - Skript.registerEvent("Craft", EvtItem.class, CraftItemEvent.class, "[player] craft[ing] [[of] %itemtypes%]") - .description("Called when a player crafts an item.") - .examples("on craft:") - .since("<i>unknown</i> (before 2.1)"); - if (hasPrepareCraftEvent) { + if (hasPrepareCraftEvent) { // Must be loaded before CraftItemEvent Skript.registerEvent("Prepare Craft", EvtItem.class, PrepareItemCraftEvent.class, "[player] (preparing|beginning) craft[ing] [[of] %itemtypes%]") .description("Called just before displaying crafting result to player. Note that setting the result item might or might not work due to Bukkit bugs.") .examples("on preparing craft of torch:") .since("2.2-Fixes-V10"); } + // TODO limit to InventoryAction.PICKUP_* and similar (e.g. COLLECT_TO_CURSOR) + Skript.registerEvent("Craft", EvtItem.class, CraftItemEvent.class, "[player] craft[ing] [[of] %itemtypes%]") + .description("Called when a player crafts an item.") + .examples("on craft:") + .since("<i>unknown</i> (before 2.1)"); if (hasEntityPickupItemEvent) { Skript.registerEvent("Pick Up", EvtItem.class, CollectionUtils.array(PlayerPickupItemEvent.class, EntityPickupItemEvent.class), "[(player|1¦entity)] (pick[ ]up|picking up) [[of] %itemtypes%]") From 416d6e0e16a66294bbab24391d17a7538ff9a6ff Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <alali_ayham@yahoo.com> Date: Thu, 7 Jul 2022 18:12:45 +0300 Subject: [PATCH 029/619] =?UTF-8?q?=F0=9F=9A=80=20Update=20Aliases=20to=20?= =?UTF-8?q?1.19=20(#4877)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- skript-aliases | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skript-aliases b/skript-aliases index adaf5cbd9ba..34420cb77d9 160000 --- a/skript-aliases +++ b/skript-aliases @@ -1 +1 @@ -Subproject commit adaf5cbd9baab8dbcd34a1ab75923912d6941a7e +Subproject commit 34420cb77d90abc76ddd98c2a7207c84ef4614a9 From 15455c4e7c034935e8364d6ee3c2df633f532e47 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <alali_ayham@yahoo.com> Date: Thu, 7 Jul 2022 18:49:45 +0300 Subject: [PATCH 030/619] =?UTF-8?q?=F0=9F=9B=A0=20Fix=20ExprHanging=20CCE?= =?UTF-8?q?=20(#4874)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/ch/njol/skript/expressions/ExprHanging.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/ch/njol/skript/expressions/ExprHanging.java b/src/main/java/ch/njol/skript/expressions/ExprHanging.java index 68c217a1885..d4d66461f94 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprHanging.java +++ b/src/main/java/ch/njol/skript/expressions/ExprHanging.java @@ -69,7 +69,11 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable public Entity[] get(Event e) { + if (!(e instanceof HangingEvent)) + return null; + Entity entity = null; + if (!isRemover) entity = ((HangingEvent) e).getEntity(); else if (e instanceof HangingBreakByEntityEvent) From b6de45898385cf73058459f28bf237ff52e7284b Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <alali_ayham@yahoo.com> Date: Fri, 8 Jul 2022 19:19:49 +0300 Subject: [PATCH 031/619] =?UTF-8?q?=E2=9A=92=EF=B8=8F=20Fix=201.19=20alias?= =?UTF-8?q?es=20(#4881)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- skript-aliases | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skript-aliases b/skript-aliases index 34420cb77d9..bd436508546 160000 --- a/skript-aliases +++ b/skript-aliases @@ -1 +1 @@ -Subproject commit 34420cb77d90abc76ddd98c2a7207c84ef4614a9 +Subproject commit bd4365085465b08d1ba5c97a3d65cf9c724bc22a From a90e109e35a339d30c2402e9cee8c7a7666ce16e Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Fri, 8 Jul 2022 18:25:06 +0200 Subject: [PATCH 032/619] Add 1.19 spawn reasons to default.lang (#4879) --- src/main/resources/lang/default.lang | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 5871800c0e6..2ef13897172 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -1775,6 +1775,7 @@ spawn reasons: infection: infection, infected jockey: jockey lightning: lightning + metamorphosis: metamorphosis mount: mount natural: natural nether_portal: nether portal @@ -1789,6 +1790,7 @@ spawn reasons: slime_split: slime split spawner: mob spawner, creature spawner, spawner spawner_egg: spawn egg + spell: spell trap: trap village_defense: village defense, golem defense, iron golem defense village_invasion: village invasion, village invading From b825095e6f6b57d638e450e58ede430ca061b277 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <alali_ayham@yahoo.com> Date: Mon, 11 Jul 2022 00:32:20 +0300 Subject: [PATCH 033/619] =?UTF-8?q?=F0=9F=9A=80=20Update=201.19=20Aliases?= =?UTF-8?q?=20to=20master=20branch=20(#4894)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- skript-aliases | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skript-aliases b/skript-aliases index bd436508546..e99be898254 160000 --- a/skript-aliases +++ b/skript-aliases @@ -1 +1 @@ -Subproject commit bd4365085465b08d1ba5c97a3d65cf9c724bc22a +Subproject commit e99be898254965b0207992f66a3289ab6744106e From 5351339903cd7858b9db4257f6318cdfe881e926 Mon Sep 17 00:00:00 2001 From: Olyno <olyno.dev@gmail.com> Date: Mon, 11 Jul 2022 11:42:22 +0200 Subject: [PATCH 034/619] Fix empty player parsing (#3802) --- .../skript/classes/data/BukkitClasses.java | 79 ++++++++++--------- 1 file changed, 41 insertions(+), 38 deletions(-) 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 c3efa0b54c3..349b342851c 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -18,35 +18,16 @@ */ package ch.njol.skript.classes.data; -import ch.njol.skript.Skript; -import ch.njol.skript.SkriptConfig; -import ch.njol.skript.aliases.Aliases; -import ch.njol.skript.aliases.ItemType; -import ch.njol.skript.bukkitutil.EnchantmentUtils; -import ch.njol.skript.bukkitutil.ItemUtils; -import ch.njol.skript.classes.ClassInfo; -import ch.njol.skript.classes.ConfigurationSerializer; -import ch.njol.skript.classes.EnumSerializer; -import ch.njol.skript.classes.Parser; -import ch.njol.skript.classes.Serializer; -import ch.njol.skript.entity.EntityData; -import ch.njol.skript.expressions.ExprDamageCause; -import ch.njol.skript.expressions.base.EventValueExpression; -import ch.njol.skript.lang.ParseContext; -import ch.njol.skript.lang.util.SimpleLiteral; -import ch.njol.skript.localization.Language; -import ch.njol.skript.localization.Message; -import ch.njol.skript.registrations.Classes; -import ch.njol.skript.util.BiomeUtils; -import ch.njol.skript.util.BlockUtils; -import ch.njol.skript.util.DamageCauseUtils; -import ch.njol.skript.util.EnchantmentType; -import ch.njol.skript.util.EnumUtils; -import ch.njol.skript.util.InventoryActions; -import ch.njol.skript.util.PotionEffectUtils; -import ch.njol.skript.util.StringMode; -import ch.njol.util.StringUtils; -import ch.njol.yggdrasil.Fields; +import java.io.StreamCorruptedException; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Map.Entry; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.Difficulty; @@ -92,15 +73,35 @@ import org.bukkit.util.Vector; import org.eclipse.jdt.annotation.Nullable; -import java.io.StreamCorruptedException; -import java.util.Arrays; -import java.util.List; -import java.util.Locale; -import java.util.Map.Entry; -import java.util.UUID; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; +import ch.njol.skript.Skript; +import ch.njol.skript.SkriptConfig; +import ch.njol.skript.aliases.Aliases; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.bukkitutil.EnchantmentUtils; +import ch.njol.skript.bukkitutil.ItemUtils; +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.classes.ConfigurationSerializer; +import ch.njol.skript.classes.EnumSerializer; +import ch.njol.skript.classes.Parser; +import ch.njol.skript.classes.Serializer; +import ch.njol.skript.entity.EntityData; +import ch.njol.skript.expressions.ExprDamageCause; +import ch.njol.skript.expressions.base.EventValueExpression; +import ch.njol.skript.lang.ParseContext; +import ch.njol.skript.lang.util.SimpleLiteral; +import ch.njol.skript.localization.Language; +import ch.njol.skript.localization.Message; +import ch.njol.skript.registrations.Classes; +import ch.njol.skript.util.BiomeUtils; +import ch.njol.skript.util.BlockUtils; +import ch.njol.skript.util.DamageCauseUtils; +import ch.njol.skript.util.EnchantmentType; +import ch.njol.skript.util.EnumUtils; +import ch.njol.skript.util.InventoryActions; +import ch.njol.skript.util.PotionEffectUtils; +import ch.njol.skript.util.StringMode; +import ch.njol.util.StringUtils; +import ch.njol.yggdrasil.Fields; /** * @author Peter Güttinger @@ -733,6 +734,8 @@ public String toVariableNameString(InventoryType o) { @Nullable public Player parse(String s, ParseContext context) { if (context == ParseContext.COMMAND) { + if (s.isEmpty()) + return null; if (UUID_PATTERN.matcher(s).matches()) return Bukkit.getPlayer(UUID.fromString(s)); List<Player> ps = Bukkit.matchPlayer(s); From b59b2ddf0f40a4562d05c8de1ff59e8a776a7f2c Mon Sep 17 00:00:00 2001 From: APickledWalrus <apickledwalrus@gmail.com> Date: Mon, 11 Jul 2022 05:52:51 -0400 Subject: [PATCH 035/619] Add gender support to EnumUtils (#4510) --- .../ch/njol/skript/localization/Noun.java | 3 +- .../java/ch/njol/skript/util/EnumUtils.java | 91 ++++++++++++------- 2 files changed, 57 insertions(+), 37 deletions(-) diff --git a/src/main/java/ch/njol/skript/localization/Noun.java b/src/main/java/ch/njol/skript/localization/Noun.java index c40fa6e99f9..1f1c8fbfa65 100644 --- a/src/main/java/ch/njol/skript/localization/Noun.java +++ b/src/main/java/ch/njol/skript/localization/Noun.java @@ -290,8 +290,7 @@ public static String getGenderID(int gender) { } /** - * Strips the gender identifier from given string and returns the used - * gender. Used for aliases. + * Strips the gender identifier from given string and returns the used gender. * * @param s String. * @param key Key to report in case of error. diff --git a/src/main/java/ch/njol/skript/util/EnumUtils.java b/src/main/java/ch/njol/skript/util/EnumUtils.java index f527a953f3a..41e86ad7b66 100644 --- a/src/main/java/ch/njol/skript/util/EnumUtils.java +++ b/src/main/java/ch/njol/skript/util/EnumUtils.java @@ -18,80 +18,101 @@ */ package ch.njol.skript.util; -import java.util.HashMap; - -import org.eclipse.jdt.annotation.Nullable; - +import ch.njol.skript.Skript; import ch.njol.skript.localization.Language; -import ch.njol.skript.localization.LanguageChangeListener; +import ch.njol.skript.localization.Noun; +import ch.njol.util.NonNullPair; import ch.njol.util.StringUtils; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.HashMap; +import java.util.Locale; -/** - * @author Peter Güttinger - */ public final class EnumUtils<E extends Enum<E>> { private final Class<E> c; private final String languageNode; - + private String[] names; private final HashMap<String, E> parseMap = new HashMap<>(); - public EnumUtils(final Class<E> c, final String languageNode) { + public EnumUtils(Class<E> c, String languageNode) { assert c != null && c.isEnum() : c; assert languageNode != null && !languageNode.isEmpty() && !languageNode.endsWith(".") : languageNode; this.c = c; this.languageNode = languageNode; - + names = new String[c.getEnumConstants().length]; - Language.addListener(new LanguageChangeListener() { - @Override - public void onLanguageChange() { - validate(true); - } - }); + Language.addListener(() -> validate(true)); } /** * Updates the names if the language has changed or the enum was modified (using reflection). */ - final void validate(final boolean force) { + void validate(boolean force) { boolean update = force; - - final int newL = c.getEnumConstants().length; - if (newL > names.length) { - names = new String[newL]; + + E[] constants = c.getEnumConstants(); + + if (constants.length != names.length) { // Simple check + names = new String[constants.length]; update = true; + } else { // Deeper check + for (E constant : constants) { + if (!parseMap.containsValue(constant)) { // A new value was added to the enum + update = true; + break; + } + } } - + if (update) { parseMap.clear(); - for (final E e : c.getEnumConstants()) { - final String[] ls = Language.getList(languageNode + "." + e.name()); - names[e.ordinal()] = ls[0]; - for (final String l : ls) - parseMap.put(l.toLowerCase(), e); + for (E e : constants) { + String key = languageNode + "." + e.name(); + int ordinal = e.ordinal(); + + String[] values = Language.getList(key); + for (String option : values) { + option = option.toLowerCase(Locale.ENGLISH); + if (values.length == 1 && option.equals(key.toLowerCase(Locale.ENGLISH))) { + Skript.warning("Missing lang enum constant for '" + key + "'"); + continue; + } + + NonNullPair<String, Integer> strippedOption = Noun.stripGender(option, key); + String first = strippedOption.getFirst(); + Integer second = strippedOption.getSecond(); + + if (names[ordinal] == null) { // Add to name array if needed + names[ordinal] = first; + } + + parseMap.put(first, e); + if (second != -1) { // There is a gender present + parseMap.put(Noun.getArticleWithSpace(second, Language.F_INDEFINITE_ARTICLE) + first, e); + } + } } } } @Nullable - public final E parse(final String s) { + public E parse(String s) { validate(false); - return parseMap.get(s.toLowerCase()); + return parseMap.get(s.toLowerCase(Locale.ENGLISH)); } - - @SuppressWarnings("null") - public final String toString(final E e, final int flags) { + + public String toString(E e, int flags) { validate(false); return names[e.ordinal()]; } - public final String getAllNames() { + public String getAllNames() { validate(false); - return StringUtils.join(names, ", "); + return StringUtils.join(parseMap.keySet(), ", "); } } From aedce0d9b51f67b8da193651d722f2ffbde72c84 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <alali_ayham@yahoo.com> Date: Mon, 11 Jul 2022 13:11:53 +0300 Subject: [PATCH 036/619] Fix parser formatting in debug mode (#4620) --- src/main/java/ch/njol/skript/log/LogEntry.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/njol/skript/log/LogEntry.java b/src/main/java/ch/njol/skript/log/LogEntry.java index aec362311de..65e977db0ca 100644 --- a/src/main/java/ch/njol/skript/log/LogEntry.java +++ b/src/main/java/ch/njol/skript/log/LogEntry.java @@ -41,7 +41,6 @@ public class LogEntry { @Nullable public final Node node; - @Nullable private final String from; private final boolean tracked; @@ -160,13 +159,17 @@ public String toFormattedString() { details = OTHER_DETAILS; } + String from = this.from; + if (!from.isEmpty()) + from = "§7 " + from + "\n"; + // Replace configured messages chat styles without user variables String lineInfoMsg = replaceNewline(Utils.replaceEnglishChatStyles(lineInfo.getValue() == null ? lineInfo.key : lineInfo.getValue())); String detailsMsg = replaceNewline(Utils.replaceEnglishChatStyles(details.getValue() == null ? details.key : details.getValue())); String lineDetailsMsg = replaceNewline(Utils.replaceEnglishChatStyles(LINE_DETAILS.getValue() == null ? LINE_DETAILS.key : LINE_DETAILS.getValue())); return - String.format(lineInfoMsg, String.valueOf(node.getLine()), c.getFileName()) + + String.format(lineInfoMsg, String.valueOf(node.getLine()), c.getFileName()) + // String.valueOf is to convert the line number (int) to a String String.format(detailsMsg, message.replaceAll("§", "&")) + from + String.format(lineDetailsMsg, node.save().trim().replaceAll("§", "&")); } From ff5df8bdb4c4ca51914fabde3ac5f0869676b2d6 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <alali_ayham@yahoo.com> Date: Mon, 11 Jul 2022 13:29:12 +0300 Subject: [PATCH 037/619] Remove CondWeather and add it to default comparators (#4641) --- .../classes/data/DefaultComparators.java | 14 +++ .../njol/skript/conditions/CondWeather.java | 86 ------------------- 2 files changed, 14 insertions(+), 86 deletions(-) delete mode 100644 src/main/java/ch/njol/skript/conditions/CondWeather.java diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java b/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java index e8c353c3976..b3f2898ffd0 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java @@ -34,6 +34,7 @@ import ch.njol.skript.util.Date; import ch.njol.skript.util.EnchantmentType; import ch.njol.skript.util.Experience; +import ch.njol.skript.util.WeatherType; import ch.njol.skript.util.GameruleValue; import ch.njol.skript.util.StructureType; import ch.njol.skript.util.Time; @@ -609,6 +610,19 @@ public boolean supportsOrdering() { return false; } }); + + // World - WeatherType + Comparators.registerComparator(World.class, WeatherType.class, new Comparator<World, WeatherType>() { + @Override + public Relation compare(World world, WeatherType weatherType) { + return Relation.get(WeatherType.fromWorld(world) == weatherType); + } + + @Override + public boolean supportsOrdering() { + return false; + } + }); } } diff --git a/src/main/java/ch/njol/skript/conditions/CondWeather.java b/src/main/java/ch/njol/skript/conditions/CondWeather.java deleted file mode 100644 index a4b7a02f8d0..00000000000 --- a/src/main/java/ch/njol/skript/conditions/CondWeather.java +++ /dev/null @@ -1,86 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.conditions; - -import org.bukkit.World; -import org.bukkit.event.Event; -import org.bukkit.event.weather.WeatherEvent; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.Skript; -import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Examples; -import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.Since; -import ch.njol.skript.effects.Delay; -import ch.njol.skript.lang.Condition; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.util.WeatherType; -import ch.njol.util.Kleenean; - -/** - * @author Peter Güttinger - */ -@Name("Weather") -@Description({"Checks whether the weather in a world is of a specific type.", - "<i>I welcome any ideas how to write this condition differently.</i>"}) -@Examples({"is thundering", - "is raining in \"world\" or \"world2\""}) -@Since("1.0") -public class CondWeather extends Condition { - - static { - // TODO a better alternative syntax, without the 'is' at the beginning - Skript.registerCondition(CondWeather.class, "is %weathertypes% [in %worlds%]"); - } - - @SuppressWarnings("null") - private Expression<WeatherType> weathers; - @SuppressWarnings("null") - private Expression<World> worlds; - - @SuppressWarnings({"unchecked", "null"}) - @Override - public boolean init(final Expression<?>[] vars, final int matchedPattern, final Kleenean isDelayed, final ParseResult parser) { - weathers = (Expression<WeatherType>) vars[0]; - worlds = (Expression<World>) vars[1]; - return true; - } - - @Override - public boolean check(final Event e) { - return worlds.check(e, w -> { - final WeatherType weatherType; - if (e instanceof WeatherEvent && w.equals(((WeatherEvent) e).getWorld()) && !Delay.isDelayed(e)) { - weatherType = WeatherType.fromEvent((WeatherEvent) e); - } else { - weatherType = WeatherType.fromWorld(w); - } - return weathers.check(e, - expectedType -> expectedType == weatherType); - }, isNegated()); - } - - @Override - public String toString(final @Nullable Event e, final boolean debug) { - return "is " + weathers.toString(e, debug) + " in " + worlds.toString(e, debug); - } - -} From ceaa32d7ed6595d723c8bfe7949ed0de2c2553c0 Mon Sep 17 00:00:00 2001 From: Jay <72931234+Ankoki@users.noreply.github.com> Date: Mon, 11 Jul 2022 11:39:07 +0100 Subject: [PATCH 038/619] Add [by %entitydata%] to on damage event (#4642) --- .../java/ch/njol/skript/events/EvtDamage.java | 53 +++++++++++++------ 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/src/main/java/ch/njol/skript/events/EvtDamage.java b/src/main/java/ch/njol/skript/events/EvtDamage.java index 42567abbcd0..c24a6db1982 100644 --- a/src/main/java/ch/njol/skript/events/EvtDamage.java +++ b/src/main/java/ch/njol/skript/events/EvtDamage.java @@ -38,35 +38,55 @@ */ @SuppressWarnings("unchecked") public class EvtDamage extends SkriptEvent { + static { - Skript.registerEvent("Damage", EvtDamage.class, EntityDamageEvent.class, "damag(e|ing) [of %entitydata%]") + Skript.registerEvent("Damage", EvtDamage.class, EntityDamageEvent.class, "damag(e|ing) [of %entitydata%] [by %entitydata%]") .description("Called when an entity receives damage, e.g. by an attack from another entity, lava, fire, drowning, fall, suffocation, etc.") - .examples("on damage:", "on damage of a player:") - .since("1.0"); + .examples("on damage:", "on damage of a player:", "on damage of player by zombie:") + .since("1.0, INSERT VERSION (by entity)"); } @Nullable - private Literal<EntityData<?>> types; + private Literal<EntityData<?>> ofTypes, byTypes; @Override - public boolean init(final Literal<?>[] args, final int matchedPattern, final ParseResult parser) { - types = (Literal<EntityData<?>>) args[0]; + public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parser) { + ofTypes = (Literal<EntityData<?>>) args[0]; + byTypes = (Literal<EntityData<?>>) args[1]; return true; } @Override - public boolean check(final Event evt) { - final EntityDamageEvent e = (EntityDamageEvent) evt; - if (!checkType(e.getEntity())) + public boolean check(Event evt) { + EntityDamageEvent e = (EntityDamageEvent) evt; + if (evt instanceof EntityDamageByEntityEvent) { + EntityDamageByEntityEvent event = (EntityDamageByEntityEvent) evt; + if (!checkDamager(event.getDamager())) + return false; + } else if (byTypes != null) { + return false; + } + if (!checkDamaged(e.getEntity())) return false; if (e instanceof EntityDamageByEntityEvent && ((EntityDamageByEntityEvent) e).getDamager() instanceof EnderDragon && ((EntityDamageByEntityEvent) e).getEntity() instanceof EnderDragon) return false; return checkDamage(e); } + + private boolean checkDamager(Entity e) { + if (byTypes != null) { + for (EntityData<?> d : byTypes.getAll()) { + if (d.isInstance(e)) + return true; + } + return false; + } + return true; + } - private boolean checkType(final Entity e) { - if (types != null) { - for (final EntityData<?> d : types.getAll()) { + private boolean checkDamaged(Entity e) { + if (ofTypes != null) { + for (EntityData<?> d : ofTypes.getAll()) { if (d.isInstance(e)) return true; } @@ -76,16 +96,17 @@ private boolean checkType(final Entity e) { } @Override - public String toString(final @Nullable Event e, final boolean debug) { - return "damage" + (types != null ? " of " + types.toString(e, debug) : ""); + public String toString(@Nullable Event e, boolean debug) { + return "damage" + (ofTypes != null ? " of " + ofTypes.toString(e, debug) : "") + + (byTypes != null ? " by " + byTypes.toString(e, debug) : ""); } // private final static WeakHashMap<LivingEntity, Integer> lastDamages = new WeakHashMap<LivingEntity, Integer>(); - private static boolean checkDamage(final EntityDamageEvent e) { + private static boolean checkDamage(EntityDamageEvent e) { if (!(e.getEntity() instanceof LivingEntity)) return true; - final LivingEntity en = (LivingEntity) e.getEntity(); + LivingEntity en = (LivingEntity) e.getEntity(); if (HealthUtils.getHealth(en) <= 0) return false; // if (en.getNoDamageTicks() <= en.getMaximumNoDamageTicks() / 2) { From f42e414647852383f0686f8c84e015dc8c6266be Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Wed, 13 Jul 2022 00:41:41 +0200 Subject: [PATCH 039/619] Update version on master branch (#4901) --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index cab44eaf09f..5f667188e10 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ groupid=ch.njol name=skript -version=2.6.2 +version=2.6.3 jarName=Skript.jar testEnv=paper-1.16.5 From b34527d944237678e2e33c0cc7a6bdabc2362f4f Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <alali_ayham@yahoo.com> Date: Wed, 13 Jul 2022 05:24:49 +0300 Subject: [PATCH 040/619] =?UTF-8?q?=F0=9F=9B=A0=20Support=20multiple=20pla?= =?UTF-8?q?yers=20in=20`CondPlayedBefore`=20(#4833)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Support multiple players --- .../skript/conditions/CondPlayedBefore.java | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/main/java/ch/njol/skript/conditions/CondPlayedBefore.java b/src/main/java/ch/njol/skript/conditions/CondPlayedBefore.java index 9b529ef3c5f..e35d748b7f2 100644 --- a/src/main/java/ch/njol/skript/conditions/CondPlayedBefore.java +++ b/src/main/java/ch/njol/skript/conditions/CondPlayedBefore.java @@ -18,10 +18,6 @@ */ package ch.njol.skript.conditions; -import org.bukkit.OfflinePlayer; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; @@ -31,44 +27,48 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; +import org.bukkit.OfflinePlayer; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; -/** - * @author Peter Güttinger - */ @Name("Has Played Before") -@Description("Checks whether a player has played on this server before. You can also use <a href='events.html#first_join'>on first join</a> if you want to make triggers for new players.") -@Examples({"player has played on this server before", - "player hasn't played before"}) -@Since("1.4") +@Description("Checks whether a player has played on this server before. You can also use " + + "<a href='events.html#first_join'>on first join</a> if you want to make triggers for new players.") +@Examples({ + "player has played on this server before", + "player hasn't played before" +}) +@Since("1.4, INSERT VERSION (multiple players)") public class CondPlayedBefore extends Condition { static { Skript.registerCondition(CondPlayedBefore.class, - "%offlineplayer% [(has|did)] [already] play[ed] [on (this|the) server] (before|already)", - "%offlineplayer% (has not|hasn't|did not|didn't) [(already|yet)] play[ed] [on (this|the) server] (before|already|yet)"); + "%offlineplayers% [(has|have|did)] [already] play[ed] [on (this|the) server] (before|already)", + "%offlineplayers% (has not|hasn't|have not|haven't|did not|didn't) [(already|yet)] play[ed] [on (this|the) server] (before|already|yet)"); } @SuppressWarnings("null") - private Expression<OfflinePlayer> player; + private Expression<OfflinePlayer> players; - @SuppressWarnings({"unchecked", "null"}) @Override - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { - player = (Expression<OfflinePlayer>) exprs[0]; + @SuppressWarnings({"unchecked", "null"}) + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + players = (Expression<OfflinePlayer>) exprs[0]; setNegated(matchedPattern == 1); return true; } @Override - public boolean check(final Event e) { - return player.check(e, + public boolean check(Event e) { + return players.check(e, OfflinePlayer::hasPlayedBefore, isNegated()); } @Override - public String toString(final @Nullable Event e, final boolean debug) { - return player.toString(e, debug) + (isNegated() ? " hasn't" : " has") + " played on this server before"; + public String toString(@Nullable Event e, boolean debug) { + return players.toString(e, debug) + (isNegated() ? (players.isSingle() ? " hasn't" : " haven't") : (players.isSingle() ? " has" : " have")) + + " played on this server before"; } } From 3fda0b8554921698113ac620811642eb32189e91 Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Wed, 13 Jul 2022 04:46:25 +0200 Subject: [PATCH 041/619] Disable watchdog in tests (#4900) Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> --- src/main/java/ch/njol/skript/tests/platform/Environment.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/ch/njol/skript/tests/platform/Environment.java b/src/main/java/ch/njol/skript/tests/platform/Environment.java index aeacfb11333..f0fa4c3aaec 100644 --- a/src/main/java/ch/njol/skript/tests/platform/Environment.java +++ b/src/main/java/ch/njol/skript/tests/platform/Environment.java @@ -229,6 +229,7 @@ public TestResults runTests(Path runnerRoot, Path testsRoot, boolean devMode, St args.add("-Dskript.testing.dir=" + testsRoot); args.add("-Dskript.testing.devMode=" + devMode); args.add("-Dskript.testing.results=test_results.json"); + args.add("-Ddisable.watchdog=true"); args.addAll(Arrays.asList(jvmArgs)); args.addAll(Arrays.asList(commandLine)); From e6c7bd014c746a9a461c6a16791b1c8717af6f8f Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Wed, 13 Jul 2022 09:59:00 +0200 Subject: [PATCH 042/619] Make random vector expression components signed (#3821) --- .../ch/njol/skript/expressions/ExprVectorRandom.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorRandom.java b/src/main/java/ch/njol/skript/expressions/ExprVectorRandom.java index 00ec72acbc5..6834b1d4992 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorRandom.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorRandom.java @@ -18,6 +18,8 @@ */ package ch.njol.skript.expressions; +import java.util.Random; + import org.bukkit.event.Event; import org.bukkit.util.Vector; import org.eclipse.jdt.annotation.Nullable; @@ -40,9 +42,11 @@ @Name("Vectors - Random Vector") @Description("Creates a random vector.") @Examples({"set {_v} to a random vector"}) -@Since("2.2-dev28") +@Since("2.2-dev28, INSERT VERSION (signed components)") public class ExprVectorRandom extends SimpleExpression<Vector> { + private static final Random random = new Random(); + static { Skript.registerExpression(ExprVectorRandom.class, Vector.class, ExpressionType.SIMPLE, "[a] random vector"); } @@ -54,7 +58,7 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override protected Vector[] get(Event e) { - return CollectionUtils.array(Vector.getRandom()); + return CollectionUtils.array(new Vector(randomSignedDouble(), randomSignedDouble(), randomSignedDouble())); } @Override @@ -71,5 +75,9 @@ public Class<? extends Vector> getReturnType() { public String toString(@Nullable Event e, boolean debug) { return "random vector"; } + + private static double randomSignedDouble() { + return random.nextDouble() * (random.nextBoolean() ? 1 : -1); + } } From e215b9d98ee5ed4dafb8a62fc9f8f6067fe5bcce Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Wed, 13 Jul 2022 13:44:21 +0200 Subject: [PATCH 043/619] Fix test servers crashing part 2 (#4906) --- src/main/java/ch/njol/skript/Skript.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 43862998eee..6d9af1b5dd4 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -648,7 +648,11 @@ protected void afterErrors() { Skript.exception(e, "Failed to write test results."); } info("Testing done, shutting down the server."); - Bukkit.getServer().shutdown(); + // Delay server shutdown to stop the server from crashing because the current tick takes a long time due to all the tests + Bukkit.getScheduler().runTaskLater(Skript.this, () -> { + Bukkit.getServer().shutdown(); + }, 5); + } return; From f9abb0c5fac1b5cad3be9a07e622b3d0da980906 Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Wed, 13 Jul 2022 14:02:38 +0200 Subject: [PATCH 044/619] Fix lingering potion spawning on 1.13 (#4904) --- .../njol/skript/entity/ThrownPotionData.java | 42 ++++++++++++------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/src/main/java/ch/njol/skript/entity/ThrownPotionData.java b/src/main/java/ch/njol/skript/entity/ThrownPotionData.java index 8af3f226519..450fef3258e 100644 --- a/src/main/java/ch/njol/skript/entity/ThrownPotionData.java +++ b/src/main/java/ch/njol/skript/entity/ThrownPotionData.java @@ -21,6 +21,7 @@ import java.util.Arrays; import org.bukkit.Location; +import org.bukkit.entity.LingeringPotion; import org.bukkit.entity.ThrownPotion; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; @@ -49,6 +50,11 @@ public class ThrownPotionData extends EntityData<ThrownPotion> { private static final Adjective m_adjective = new Adjective("entities.thrown potion.adjective"); private static final boolean RUNNING_LEGACY = !Skript.isRunningMinecraft(1, 13); + private static final boolean LINGERING_POTION_ENTITY_USED = !Skript.isRunningMinecraft(1, 14); + // LingeringPotion class deprecated and marked for removal + @SuppressWarnings("removal") + private static final Class<? extends ThrownPotion> LINGERING_POTION_ENTITY_CLASS = + LINGERING_POTION_ENTITY_USED ? LingeringPotion.class : ThrownPotion.class; private static final ItemType POTION = Aliases.javaItemType("potion"); private static final ItemType SPLASH_POTION = Aliases.javaItemType("splash potion"); private static final ItemType LINGER_POTION = Aliases.javaItemType("lingering potion"); @@ -57,7 +63,7 @@ public class ThrownPotionData extends EntityData<ThrownPotion> { private ItemType[] types; @Override - protected boolean init(final Literal<?>[] exprs, final int matchedPattern, final ParseResult parseResult) { + protected boolean init(Literal<?>[] exprs, int matchedPattern, ParseResult parseResult) { if (exprs.length > 0 && exprs[0] != null) { return (types = Converters.convert((ItemType[]) exprs[0].getAll(), ItemType.class, t -> { // If the itemtype is a potion, lets make it a splash potion (required by Bukkit) @@ -79,18 +85,18 @@ protected boolean init(final Literal<?>[] exprs, final int matchedPattern, final } @Override - protected boolean init(final @Nullable Class<? extends ThrownPotion> c, final @Nullable ThrownPotion e) { + protected boolean init(@Nullable Class<? extends ThrownPotion> c, @Nullable ThrownPotion e) { if (e != null) { - final ItemStack i = e.getItem(); + ItemStack i = e.getItem(); types = new ItemType[] {new ItemType(i)}; } return true; } @Override - protected boolean match(final ThrownPotion entity) { + protected boolean match(ThrownPotion entity) { if (types != null) { - for (final ItemType t : types) { + for (ItemType t : types) { if (t.isOfType(entity.getItem())) return true; } @@ -99,6 +105,7 @@ protected boolean match(final ThrownPotion entity) { return true; } + @SuppressWarnings({"unchecked", "rawtypes"}) @Override public @Nullable ThrownPotion spawn(Location loc, @Nullable Consumer<ThrownPotion> consumer) { ItemType t = CollectionUtils.getRandom(types); @@ -107,24 +114,27 @@ protected boolean match(final ThrownPotion entity) { if (i == null) return null; + Class<ThrownPotion> thrownPotionClass = (Class) (LINGER_POTION.isOfType(i) ? LINGERING_POTION_ENTITY_CLASS : ThrownPotion.class); ThrownPotion potion; if (consumer != null) - potion = loc.getWorld().spawn(loc, ThrownPotion.class, consumer); + potion = loc.getWorld().spawn(loc, thrownPotionClass, consumer); else - potion = loc.getWorld().spawn(loc, ThrownPotion.class); + potion = loc.getWorld().spawn(loc, thrownPotionClass); potion.setItem(i); return potion; } @Override - public void set(final ThrownPotion entity) { + public void set(ThrownPotion entity) { if (types != null) { - final ItemType t = CollectionUtils.getRandom(types); + ItemType t = CollectionUtils.getRandom(types); assert t != null; ItemStack i = t.getRandom(); if (i == null) return; // Missing item, can't make thrown potion of it + if (LINGERING_POTION_ENTITY_USED && (LINGERING_POTION_ENTITY_CLASS.isInstance(entity) != LINGER_POTION.isOfType(i))) + return; entity.setItem(i); } assert false; @@ -141,10 +151,10 @@ public EntityData getSuperType() { } @Override - public boolean isSupertypeOf(final EntityData<?> e) { + public boolean isSupertypeOf(EntityData<?> e) { if (!(e instanceof ThrownPotionData)) return false; - final ThrownPotionData d = (ThrownPotionData) e; + ThrownPotionData d = (ThrownPotionData) e; if (types != null) { return d.types != null && ItemType.isSubset(types, d.types); } @@ -152,11 +162,11 @@ public boolean isSupertypeOf(final EntityData<?> e) { } @Override - public String toString(final int flags) { - final ItemType[] types = this.types; + public String toString(int flags) { + ItemType[] types = this.types; if (types == null) return super.toString(flags); - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); b.append(Noun.getArticleWithSpace(types[0].getTypes().get(0).getGender(), flags)); b.append(m_adjective.toString(types[0].getTypes().get(0).getGender(), flags)); b.append(" "); @@ -167,7 +177,7 @@ public String toString(final int flags) { // return ItemType.serialize(types); @Override @Deprecated - protected boolean deserialize(final String s) { + protected boolean deserialize(String s) { throw new UnsupportedOperationException("old serialization is no longer supported"); // if (s.isEmpty()) // return true; @@ -176,7 +186,7 @@ protected boolean deserialize(final String s) { } @Override - protected boolean equals_i(final EntityData<?> obj) { + protected boolean equals_i(EntityData<?> obj) { if (!(obj instanceof ThrownPotionData)) return false; return Arrays.equals(types, ((ThrownPotionData) obj).types); From 3dbf414e0a16fae3c9ae59d15f0243f2534bf38f Mon Sep 17 00:00:00 2001 From: APickledWalrus <apickledwalrus@gmail.com> Date: Wed, 13 Jul 2022 13:00:08 -0400 Subject: [PATCH 045/619] Remove WorldGuard 6 support (#4764) --- build.gradle | 4 - settings.gradle | 1 - skript-worldguard6/.gitignore | 3 - skript-worldguard6/build.gradle | 16 - .../module/worldguard6/WorldGuard6Hook.java | 285 ------------------ .../module/worldguard6/package-info.java | 28 -- .../skript/hooks/regions/WorldGuardHook.java | 12 +- 7 files changed, 1 insertion(+), 348 deletions(-) delete mode 100644 skript-worldguard6/.gitignore delete mode 100644 skript-worldguard6/build.gradle delete mode 100644 skript-worldguard6/src/main/java/ch/njol/skript/module/worldguard6/WorldGuard6Hook.java delete mode 100644 skript-worldguard6/src/main/java/ch/njol/skript/module/worldguard6/package-info.java diff --git a/build.gradle b/build.gradle index e53a57e2f55..186abe84042 100644 --- a/build.gradle +++ b/build.gradle @@ -93,7 +93,6 @@ tasks.withType(ShadowJar) { from 'skript-aliases', { into 'aliases-english' // Change this if we get aliases in other languages } - from 'skript-worldguard6/build/classes/java/main' } processResources { @@ -233,7 +232,6 @@ task githubRelease(type: ShadowJar) { 'Sealed': 'true' ) } - from 'skript-worldguard6/build/classes/java/main' } task spigotResources(type: ProcessResources) { @@ -269,7 +267,6 @@ task spigotRelease(type: ShadowJar) { 'Sealed': 'true' ) } - from 'skript-worldguard6/build/classes/java/main' } task nightlyResources(type: ProcessResources) { @@ -301,5 +298,4 @@ task nightlyRelease(type: ShadowJar) { 'Sealed': 'true' ) } - from 'skript-worldguard6/build/classes/java/main' } diff --git a/settings.gradle b/settings.gradle index 7966204f946..051b410bf2e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1 @@ rootProject.name = 'Skript' -include 'skript-worldguard6' diff --git a/skript-worldguard6/.gitignore b/skript-worldguard6/.gitignore deleted file mode 100644 index 430b1ae3894..00000000000 --- a/skript-worldguard6/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -.classpath -.project -.settings diff --git a/skript-worldguard6/build.gradle b/skript-worldguard6/build.gradle deleted file mode 100644 index 299928bc4f3..00000000000 --- a/skript-worldguard6/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply plugin: 'java' - -tasks.withType(JavaCompile).configureEach { - options.compilerArgs += ["-source", "1.8", "-target", "1.8"] -} - -dependencies { - implementation rootProject - implementation 'org.eclipse.jdt:org.eclipse.jdt.annotation:2.2.600' - implementation 'com.destroystokyo.paper:paper-api:1.13.2-R0.1-SNAPSHOT' - implementation 'com.sk89q:worldguard:6.1.1-SNAPSHOT' -} - -jar { - archiveName 'Skript-worldguard6.jar' -} diff --git a/skript-worldguard6/src/main/java/ch/njol/skript/module/worldguard6/WorldGuard6Hook.java b/skript-worldguard6/src/main/java/ch/njol/skript/module/worldguard6/WorldGuard6Hook.java deleted file mode 100644 index 69bd2779eec..00000000000 --- a/skript-worldguard6/src/main/java/ch/njol/skript/module/worldguard6/WorldGuard6Hook.java +++ /dev/null @@ -1,285 +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 <http://www.gnu.org/licenses/>. - * - * - * Copyright 2011-2017 Peter Güttinger and contributors - */ -package ch.njol.skript.module.worldguard6; - -import java.io.IOException; -import java.io.NotSerializableException; -import java.io.StreamCorruptedException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.UUID; - -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.OfflinePlayer; -import org.bukkit.World; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import org.bukkit.util.Vector; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.Skript; -import ch.njol.skript.hooks.regions.RegionsPlugin; -import ch.njol.skript.hooks.regions.classes.Region; -import ch.njol.skript.util.AABB; -import ch.njol.yggdrasil.Fields; -import ch.njol.yggdrasil.YggdrasilID; - -import com.sk89q.worldedit.BlockVector; -import com.sk89q.worldguard.bukkit.WorldGuardPlugin; -import com.sk89q.worldguard.domains.DefaultDomain; -import com.sk89q.worldguard.protection.ApplicableRegionSet; -import com.sk89q.worldguard.protection.managers.RegionManager; -import com.sk89q.worldguard.protection.regions.ProtectedRegion; - -/** - * Old WorldGuard hook, which works with WorldGuard 6 only. - */ -public class WorldGuard6Hook extends RegionsPlugin<WorldGuardPlugin> { - - public WorldGuard6Hook() throws IOException {} - - boolean supportsUUIDs; - - @Override - protected boolean init() { - supportsUUIDs = Skript.methodExists(DefaultDomain.class, "getUniqueIds"); - - // Manually load syntaxes for regions, because we're in module package - try { - Skript.getAddonInstance().loadClasses("ch.njol.skript.hooks.regions"); - } catch (IOException e) { - Skript.exception(e); - return false; - } - return super.init(); - } - - @Override - public String getName() { - return "WorldGuard"; - } - - @Override - public boolean canBuild_i(final Player p, final Location l) { - return plugin.canBuild(p, l); - } - - @YggdrasilID("WorldGuardRegion") - public final class WorldGuardRegion extends Region { - - final World world; - private transient ProtectedRegion region; - - @SuppressWarnings({"null", "unused"}) - private WorldGuardRegion() { - world = null; - } - - public WorldGuardRegion(final World w, final ProtectedRegion r) { - world = w; - region = r; - } - - @Override - public boolean contains(final Location l) { - return l.getWorld().equals(world) && region.contains(l.getBlockX(), l.getBlockY(), l.getBlockZ()); - } - - @SuppressWarnings("deprecation") - @Override - public boolean isMember(final OfflinePlayer p) { - if (supportsUUIDs) - return region.isMember(plugin.wrapOfflinePlayer(p)); - else - return region.isMember(p.getName()); - } - - @SuppressWarnings("deprecation") - @Override - public Collection<OfflinePlayer> getMembers() { - if (supportsUUIDs) { - final Collection<UUID> ids = region.getMembers().getUniqueIds(); - final Collection<OfflinePlayer> r = new ArrayList<>(ids.size()); - for (final UUID id : ids) - r.add(Bukkit.getOfflinePlayer(id)); - return r; - } else { - final Collection<String> ps = region.getMembers().getPlayers(); - final Collection<OfflinePlayer> r = new ArrayList<>(ps.size()); - for (final String p : ps) - r.add(Bukkit.getOfflinePlayer(p)); - return r; - } - } - - @SuppressWarnings("deprecation") - @Override - public boolean isOwner(final OfflinePlayer p) { - if (supportsUUIDs) - return region.isOwner(plugin.wrapOfflinePlayer(p)); - else - return region.isOwner(p.getName()); - } - - @SuppressWarnings("deprecation") - @Override - public Collection<OfflinePlayer> getOwners() { - if (supportsUUIDs) { - final Collection<UUID> ids = region.getOwners().getUniqueIds(); - final Collection<OfflinePlayer> r = new ArrayList<>(ids.size()); - for (final UUID id : ids) - r.add(Bukkit.getOfflinePlayer(id)); - return r; - } else { - final Collection<String> ps = region.getOwners().getPlayers(); - final Collection<OfflinePlayer> r = new ArrayList<>(ps.size()); - for (final String p : ps) - r.add(Bukkit.getOfflinePlayer(p)); - return r; - } - } - - @Override - public Iterator<Block> getBlocks() { - final BlockVector min = region.getMinimumPoint(), max = region.getMaximumPoint(); - return new AABB(world, new Vector(min.getBlockX(), min.getBlockY(), min.getBlockZ()), new Vector(max.getBlockX(), max.getBlockY(), max.getBlockZ())).iterator(); -// final Iterator<BlockVector2D> iter = region.getPoints().iterator(); -// if (!iter.hasNext()) -// return EmptyIterator.get(); -// return new Iterator<Block>() { -// @SuppressWarnings("null") -// BlockVector2D current = iter.next(); -// int height = 0; -// final int maxHeight = world.getMaxHeight(); -// -// @SuppressWarnings("null") -// @Override -// public boolean hasNext() { -// if (height >= maxHeight && iter.hasNext()) { -// height = 0; -// current = iter.next(); -// } -// return height < maxHeight; -// } -// -// @SuppressWarnings("null") -// @Override -// public Block next() { -// if (!hasNext()) -// throw new NoSuchElementException(); -// return world.getBlockAt(current.getBlockX(), height++, current.getBlockZ()); -// } -// -// @Override -// public void remove() { -// throw new UnsupportedOperationException(); -// } -// }; - } - - @Override - public Fields serialize() throws NotSerializableException { - final Fields f = new Fields(this); - f.putObject("region", region.getId()); - return f; - } - - @Override - public void deserialize(final Fields fields) throws StreamCorruptedException, NotSerializableException { - final String r = fields.getAndRemoveObject("region", String.class); - fields.setFields(this); - final ProtectedRegion region = plugin.getRegionManager(world).getRegion(r); - if (region == null) - throw new StreamCorruptedException("Invalid region " + r + " in world " + world); - this.region = region; - } - - @Override - public String toString() { - return region.getId() + " in world " + world.getName(); - } - - @Override - public RegionsPlugin<?> getPlugin() { - return WorldGuard6Hook.this; - } - - @Override - public boolean equals(final @Nullable Object o) { - if (o == this) - return true; - if (o == null) - return false; - if (!(o instanceof WorldGuardRegion)) - return false; - return world.equals(((WorldGuardRegion) o).world) && region.equals(((WorldGuardRegion) o).region); - } - - @Override - public int hashCode() { - return world.hashCode() * 31 + region.hashCode(); - } - - } - - @SuppressWarnings("null") - @Override - public Collection<? extends Region> getRegionsAt_i(@Nullable final Location l) { - final ArrayList<Region> r = new ArrayList<>(); - - if (l == null) // Working around possible cause of issue #280 - return Collections.emptyList(); - if (l.getWorld() == null) - return Collections.emptyList(); - RegionManager manager = plugin.getRegionManager(l.getWorld()); - if (manager == null) - return r; - ApplicableRegionSet applicable = manager.getApplicableRegions(l); - if (applicable == null) - return r; - final Iterator<ProtectedRegion> i = applicable.iterator(); - while (i.hasNext()) - r.add(new WorldGuardRegion(l.getWorld(), i.next())); - return r; - } - - @Override - @Nullable - public Region getRegion_i(final World world, final String name) { - final ProtectedRegion r = plugin.getRegionManager(world).getRegion(name); - if (r != null) - return new WorldGuardRegion(world, r); - return null; - } - - @Override - public boolean hasMultipleOwners_i() { - return true; - } - - @Override - protected Class<? extends Region> getRegionClass() { - return WorldGuardRegion.class; - } - -} diff --git a/skript-worldguard6/src/main/java/ch/njol/skript/module/worldguard6/package-info.java b/skript-worldguard6/src/main/java/ch/njol/skript/module/worldguard6/package-info.java deleted file mode 100644 index a263ae32728..00000000000 --- a/skript-worldguard6/src/main/java/ch/njol/skript/module/worldguard6/package-info.java +++ /dev/null @@ -1,28 +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 <http://www.gnu.org/licenses/>. - * - * - * Copyright 2011-2017 Peter Güttinger and contributors - */ -/** - * @author Peter Güttinger - */ -@NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) -package ch.njol.skript.module.worldguard6; - -import org.eclipse.jdt.annotation.DefaultLocation; -import org.eclipse.jdt.annotation.NonNullByDefault; - diff --git a/src/main/java/ch/njol/skript/hooks/regions/WorldGuardHook.java b/src/main/java/ch/njol/skript/hooks/regions/WorldGuardHook.java index e283d81e903..601b6523643 100644 --- a/src/main/java/ch/njol/skript/hooks/regions/WorldGuardHook.java +++ b/src/main/java/ch/njol/skript/hooks/regions/WorldGuardHook.java @@ -60,17 +60,7 @@ public WorldGuardHook() throws IOException {} @Override protected boolean init() { - if (!Skript.classExists("com.sk89q.worldguard.WorldGuard")) { // Assume WorldGuard 6 - try { - Class<?> oldHook = Class.forName("ch.njol.skript.module.worldguard6.WorldGuard6Hook", true, getClass().getClassLoader()); - oldHook.getDeclaredConstructor().newInstance(); - return true; - } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | IllegalArgumentException - | InvocationTargetException | NoSuchMethodException | SecurityException e) { - Skript.error("An error occurred while trying to enable support for WorldGuard 6. WorldGuard region support has been disabled!"); - } - return false; - } else if (!Skript.classExists("com.sk89q.worldedit.math.BlockVector3")) { + if (!Skript.classExists("com.sk89q.worldedit.math.BlockVector3")) { Skript.error("WorldEdit you're using is not compatible with Skript. Disabling WorldGuard support!"); return false; } From bf80f174eb56e40d13d0e9151be57042674bc8c2 Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Fri, 15 Jul 2022 14:59:57 +0200 Subject: [PATCH 046/619] Update maven publish workflow and JitPack Java version (#4907) --- .github/workflows/repo.yml | 2 +- jitpack.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/repo.yml b/.github/workflows/repo.yml index 03f95e462a1..7ce1880c651 100644 --- a/.github/workflows/repo.yml +++ b/.github/workflows/repo.yml @@ -2,7 +2,7 @@ name: Publish to maven repo on: release: - types: [created] + types: [published] jobs: publish: diff --git a/jitpack.yml b/jitpack.yml index 7d4c6bd1b6a..39620071fa1 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -1,5 +1,5 @@ before_install: - source "$HOME/.sdkman/bin/sdkman-init.sh" - sdk update - - sdk install java 16.0.1-open - - sdk use java 16.0.1-open + - sdk install java 17.0.2-open + - sdk use java 17.0.2-open From dc9cb99496e337de70f097a018c066a5ce7f277e Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Fri, 15 Jul 2022 07:09:08 -0600 Subject: [PATCH 047/619] Remove travis.yml (#4870) --- .travis.yml | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index fc042026d39..00000000000 --- a/.travis.yml +++ /dev/null @@ -1,4 +0,0 @@ -language: java -cache: - directories: - - $HOME/.gradle From 0e91c2e81332f41bd8cf14cdb2014e537846b95b Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Fri, 15 Jul 2022 15:27:39 +0200 Subject: [PATCH 048/619] Fix and improve some errors and exceptions (#4243) --- src/main/java/ch/njol/skript/Skript.java | 21 +++++-- .../ch/njol/skript/effects/EffChange.java | 3 +- .../ch/njol/skript/expressions/ExprDrops.java | 1 - .../ch/njol/skript/lang/SkriptParser.java | 22 +++---- .../skript/variables/FlatFileStorage.java | 27 ++++---- .../ch/njol/skript/variables/Variables.java | 62 ++++++++++--------- 6 files changed, 77 insertions(+), 59 deletions(-) diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 6d9af1b5dd4..d1cea1d8ff9 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -117,6 +117,7 @@ import java.lang.Thread.UncaughtExceptionHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -529,24 +530,26 @@ public void run() { // Load hooks from Skript jar try { try (JarFile jar = new JarFile(getFile())) { - for (final JarEntry e : new EnumerationIterable<>(jar.entries())) { + for (JarEntry e : new EnumerationIterable<>(jar.entries())) { if (e.getName().startsWith("ch/njol/skript/hooks/") && e.getName().endsWith("Hook.class") && StringUtils.count("" + e.getName(), '/') <= 5) { final String c = e.getName().replace('/', '.').substring(0, e.getName().length() - ".class".length()); try { - final Class<?> hook = Class.forName(c, true, getClassLoader()); - if (hook != null && Hook.class.isAssignableFrom(hook) && !hook.isInterface() && Hook.class != hook && isHookEnabled((Class<? extends Hook<?>>) hook)) { + Class<?> hook = Class.forName(c, true, getClassLoader()); + if (Hook.class.isAssignableFrom(hook) && !Modifier.isAbstract(hook.getModifiers()) && isHookEnabled((Class<? extends Hook<?>>) hook)) { hook.getDeclaredConstructor().setAccessible(true); hook.getDeclaredConstructor().newInstance(); } - } catch (final ClassNotFoundException ex) { + } catch (ClassNotFoundException ex) { Skript.exception(ex, "Cannot load class " + c); - } catch (final ExceptionInInitializerError err) { + } catch (ExceptionInInitializerError err) { Skript.exception(err.getCause(), "Class " + c + " generated an exception while loading"); + } catch (Exception ex) { + Skript.exception(ex, "Exception initializing hook: " + c); } } } } - } catch (final Exception e) { + } catch (IOException e) { error("Error while loading plugin hooks" + (e.getLocalizedMessage() == null ? "" : ": " + e.getLocalizedMessage())); Skript.exception(e); } @@ -1529,6 +1532,12 @@ public static void markErrored() { */ public static EmptyStacktraceException exception(@Nullable Throwable cause, final @Nullable Thread thread, final @Nullable TriggerItem item, final String... info) { errored = true; + + // Don't send full exception message again, when caught exception (likely) comes from this method + if (cause instanceof EmptyStacktraceException) { + return new EmptyStacktraceException(); + } + // First error: gather plugin package information if (!checkedPlugins) { for (Plugin plugin : Bukkit.getPluginManager().getPlugins()) { diff --git a/src/main/java/ch/njol/skript/effects/EffChange.java b/src/main/java/ch/njol/skript/effects/EffChange.java index 13a299446d2..bb79b1b6eb8 100644 --- a/src/main/java/ch/njol/skript/effects/EffChange.java +++ b/src/main/java/ch/njol/skript/effects/EffChange.java @@ -216,10 +216,11 @@ public boolean init(final Expression<?>[] exprs, final int matchedPattern, final return false; } log.clear(); + log.stop(); final Class<?>[] r = new Class[rs.length]; for (int i = 0; i < rs.length; i++) r[i] = rs[i].isArray() ? rs[i].getComponentType() : rs[i]; - if (rs.length == 1 && rs[0] == Object.class) + if (r.length == 1 && r[0] == Object.class) Skript.error("Can't understand this expression: " + changer, ErrorQuality.NOT_AN_EXPRESSION); else if (mode == ChangeMode.SET) Skript.error(what + " can't be set to " + changer + " because the latter is " + SkriptParser.notOfType(r), ErrorQuality.SEMANTIC_ERROR); diff --git a/src/main/java/ch/njol/skript/expressions/ExprDrops.java b/src/main/java/ch/njol/skript/expressions/ExprDrops.java index 8a81ce69239..75eb58d074a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprDrops.java +++ b/src/main/java/ch/njol/skript/expressions/ExprDrops.java @@ -94,7 +94,6 @@ public Class<?>[] acceptChange(ChangeMode mode) { case DELETE: case RESET: default: - assert false; return null; } } diff --git a/src/main/java/ch/njol/skript/lang/SkriptParser.java b/src/main/java/ch/njol/skript/lang/SkriptParser.java index 36343e5c139..c8efa14e459 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptParser.java +++ b/src/main/java/ch/njol/skript/lang/SkriptParser.java @@ -209,15 +209,15 @@ public static <T extends SyntaxElement> T parseStatic(String expr, final Iterato } @Nullable - private final <T extends SyntaxElement> T parse(final Iterator<? extends SyntaxElementInfo<? extends T>> source) { - final ParseLogHandler log = SkriptLogger.startParseLogHandler(); + private <T extends SyntaxElement> T parse(Iterator<? extends SyntaxElementInfo<? extends T>> source) { + ParseLogHandler log = SkriptLogger.startParseLogHandler(); try { while (source.hasNext()) { - final SyntaxElementInfo<? extends T> info = source.next(); + SyntaxElementInfo<? extends T> info = source.next(); patternsLoop: for (int i = 0; i < info.patterns.length; i++) { log.clear(); try { - final String pattern = info.patterns[i]; + String pattern = info.patterns[i]; assert pattern != null; ParseResult res; try { @@ -228,12 +228,12 @@ private final <T extends SyntaxElement> T parse(final Iterator<? extends SyntaxE if (res != null) { int x = -1; for (int j = 0; (x = nextUnescaped(pattern, '%', x + 1)) != -1; j++) { - final int x2 = nextUnescaped(pattern, '%', x + 1); + int x2 = nextUnescaped(pattern, '%', x + 1); if (res.exprs[j] == null) { - final String name = pattern.substring(x + 1, x2); + String name = pattern.substring(x + 1, x2); if (!name.startsWith("-")) { - final ExprInfo vi = getExprInfo(name); - final DefaultExpression<?> expr = vi.classes[0].getDefaultExpression(); + ExprInfo vi = getExprInfo(name); + DefaultExpression<?> expr = vi.classes[0].getDefaultExpression(); if (expr == null) throw new SkriptAPIException("The class '" + vi.classes[0].getCodeName() + "' does not provide a default expression. Either allow null (with %-" + vi.classes[0].getCodeName() + "%) or make it mandatory [pattern: " + info.patterns[i] + "]"); if (!(expr instanceof Literal) && (vi.flagMask & PARSE_EXPRESSIONS) == 0) @@ -251,15 +251,13 @@ private final <T extends SyntaxElement> T parse(final Iterator<? extends SyntaxE } x = x2; } - final T t = info.c.newInstance(); + T t = info.c.newInstance(); if (t.init(res.exprs, i, getParser().getHasDelayBefore(), res)) { log.printLog(); return t; } } - } catch (final InstantiationException e) { - assert false; - } catch (final IllegalAccessException e) { + } catch (final InstantiationException | IllegalAccessException e) { assert false; } } diff --git a/src/main/java/ch/njol/skript/variables/FlatFileStorage.java b/src/main/java/ch/njol/skript/variables/FlatFileStorage.java index 534c8378f64..02de372d561 100644 --- a/src/main/java/ch/njol/skript/variables/FlatFileStorage.java +++ b/src/main/java/ch/njol/skript/variables/FlatFileStorage.java @@ -423,24 +423,29 @@ public final void saveVariables(final boolean finalSave) { * @param map */ @SuppressWarnings("unchecked") - private final void save(final PrintWriter pw, final String parent, final TreeMap<String, Object> map) { - outer: for (final Entry<String, Object> e : map.entrySet()) { - final Object val = e.getValue(); + private void save(PrintWriter pw, String parent, TreeMap<String, Object> map) { + outer: for (Entry<String, Object> e : map.entrySet()) { + Object val = e.getValue(); if (val == null) continue; if (val instanceof TreeMap) { save(pw, parent + e.getKey() + Variable.SEPARATOR, (TreeMap<String, Object>) val); } else { - final String name = (e.getKey() == null ? parent.substring(0, parent.length() - Variable.SEPARATOR.length()) : parent + e.getKey()); - for (final VariablesStorage s : Variables.storages) { - if (s.accept(name)) { - if (s == this) { - final SerializedVariable.Value value = Classes.serialize(val); - if (value != null) - writeCSV(pw, name, value.type, encode(value.data)); + String name = e.getKey() == null ? parent.substring(0, parent.length() - Variable.SEPARATOR.length()) : parent + e.getKey(); + + try { + for (VariablesStorage s : Variables.storages) { + if (s.accept(name)) { + if (s == this) { + SerializedVariable.Value value = Classes.serialize(val); + if (value != null) + writeCSV(pw, name, value.type, encode(value.data)); + } + continue outer; } - continue outer; } + } catch (Exception ex) { + Skript.exception(ex, "Error saving variable named " + name); } } } diff --git a/src/main/java/ch/njol/skript/variables/Variables.java b/src/main/java/ch/njol/skript/variables/Variables.java index 98067272dc1..09c728e9ab6 100644 --- a/src/main/java/ch/njol/skript/variables/Variables.java +++ b/src/main/java/ch/njol/skript/variables/Variables.java @@ -487,17 +487,21 @@ else if (loadConflicts == MAX_CONFLICT_WARNINGS + 1) } finally { variablesLock.writeLock().unlock(); } - - for (final VariablesStorage s : storages) { - if (s.accept(name)) { - if (s != source) { - final Value v = serialize(value); - s.save(name, v != null ? v.type : null, v != null ? v.data : null); - if (value != null) - source.save(name, null, null); + + try { + for (final VariablesStorage s : storages) { + if (s.accept(name)) { + if (s != source) { + final Value v = serialize(value); + s.save(name, v != null ? v.type : null, v != null ? v.data : null); + if (value != null) + source.save(name, null, null); + } + return true; } - return true; } + } catch (Exception e) { + Skript.exception(e, "Error saving variable named " + name); } return false; } @@ -537,40 +541,42 @@ private static int onStoragesLoaded() { } } - public static SerializedVariable serialize(final String name, final @Nullable Object value) { + public static SerializedVariable serialize(String name, @Nullable Object value) { assert Bukkit.isPrimaryThread(); - final SerializedVariable.Value var = serialize(value); + SerializedVariable.Value var; + try { + var = serialize(value); + } catch (Exception e) { + throw Skript.exception(e, "Error saving variable named " + name); + } return new SerializedVariable(name, var); } - public static SerializedVariable.@Nullable Value serialize(final @Nullable Object value) { + public static SerializedVariable.@Nullable Value serialize(@Nullable Object value) { assert Bukkit.isPrimaryThread(); return Classes.serialize(value); } - private static void saveVariableChange(final String name, final @Nullable Object value) { + private static void saveVariableChange(String name, @Nullable Object value) { saveQueue.add(serialize(name, value)); } - final static BlockingQueue<SerializedVariable> saveQueue = new LinkedBlockingQueue<>(); + static final BlockingQueue<SerializedVariable> saveQueue = new LinkedBlockingQueue<>(); static volatile boolean closed = false; - private final static Thread saveThread = Skript.newThread(new Runnable() { - @Override - public void run() { - while (!closed) { - try { - // Save one variable change - SerializedVariable v = saveQueue.take(); - for (VariablesStorage s : storages) { - if (s.accept(v.name)) { - s.save(v); - break; - } + private static final Thread saveThread = Skript.newThread(() -> { + while (!closed) { + try { + // Save one variable change + SerializedVariable v = saveQueue.take(); + for (VariablesStorage s : storages) { + if (s.accept(v.name)) { + s.save(v); + break; } - } catch (final InterruptedException e) {} - } + } + } catch (final InterruptedException ignored) {} } }, "Skript variable save thread"); From 215939ff1e99f2b4399888b8795e1f9bf796037f Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Fri, 15 Jul 2022 15:32:22 +0200 Subject: [PATCH 049/619] Make event values return item stacks instead of item types if possible (#4128) --- .../classes/data/BukkitEventValues.java | 123 +++++++++--------- 1 file changed, 60 insertions(+), 63 deletions(-) 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 1a9fc9c5363..54d1a69d818 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -350,11 +350,11 @@ public Block get(final BlockIgniteEvent e) { } }, 0); // BlockDispenseEvent - EventValues.registerEventValue(BlockDispenseEvent.class, ItemType.class, new Getter<ItemType, BlockDispenseEvent>() { + EventValues.registerEventValue(BlockDispenseEvent.class, ItemStack.class, new Getter<ItemStack, BlockDispenseEvent>() { @Override @Nullable - public ItemType get(final BlockDispenseEvent e) { - return new ItemType(e.getItem()); + public ItemStack get(final BlockDispenseEvent e) { + return e.getItem(); } }, 0); // BlockCanBuildEvent @@ -552,11 +552,11 @@ public PotionEffectType get(AreaEffectCloudApplyEvent e) { }, 0); } // ItemSpawnEvent - EventValues.registerEventValue(ItemSpawnEvent.class, ItemType.class, new Getter<ItemType, ItemSpawnEvent>() { + EventValues.registerEventValue(ItemSpawnEvent.class, ItemStack.class, new Getter<ItemStack, ItemSpawnEvent>() { @Override @Nullable - public ItemType get(final ItemSpawnEvent e) { - return new ItemType(e.getEntity().getItemStack()); + public ItemStack get(final ItemSpawnEvent e) { + return e.getEntity().getItemStack(); } }, 0); // LightningStrikeEvent @@ -648,11 +648,11 @@ public Item get(final PlayerDropItemEvent e) { return e.getItemDrop(); } }, 0); - EventValues.registerEventValue(PlayerDropItemEvent.class, ItemType.class, new Getter<ItemType, PlayerDropItemEvent>() { + EventValues.registerEventValue(PlayerDropItemEvent.class, ItemStack.class, new Getter<ItemStack, PlayerDropItemEvent>() { @Override @Nullable - public ItemType get(final PlayerDropItemEvent e) { - return new ItemType(e.getItemDrop().getItemStack()); + public ItemStack get(final PlayerDropItemEvent e) { + return e.getItemDrop().getItemStack(); } }, 0); // PlayerPickupItemEvent @@ -670,11 +670,11 @@ public Item get(final PlayerPickupItemEvent e) { return e.getItem(); } }, 0); - EventValues.registerEventValue(PlayerPickupItemEvent.class, ItemType.class, new Getter<ItemType, PlayerPickupItemEvent>() { + EventValues.registerEventValue(PlayerPickupItemEvent.class, ItemStack.class, new Getter<ItemStack, PlayerPickupItemEvent>() { @Override @Nullable - public ItemType get(final PlayerPickupItemEvent e) { - return new ItemType(e.getItem().getItemStack()); + public ItemStack get(final PlayerPickupItemEvent e) { + return e.getItem().getItemStack(); } }, 0); // EntityPickupItemEvent @@ -702,21 +702,21 @@ public ItemType get(final EntityPickupItemEvent e) { } // PlayerItemConsumeEvent if (Skript.supports("org.bukkit.event.player.PlayerItemConsumeEvent")) { - EventValues.registerEventValue(PlayerItemConsumeEvent.class, ItemType.class, new Getter<ItemType, PlayerItemConsumeEvent>() { + EventValues.registerEventValue(PlayerItemConsumeEvent.class, ItemStack.class, new Getter<ItemStack, PlayerItemConsumeEvent>() { @Override @Nullable - public ItemType get(final PlayerItemConsumeEvent e) { - return new ItemType(e.getItem()); + public ItemStack get(final PlayerItemConsumeEvent e) { + return e.getItem(); } }, 0); } // PlayerItemBreakEvent if (Skript.supports("org.bukkit.event.player.PlayerItemBreakEvent")) { - EventValues.registerEventValue(PlayerItemBreakEvent.class, ItemType.class, new Getter<ItemType, PlayerItemBreakEvent>() { + EventValues.registerEventValue(PlayerItemBreakEvent.class, ItemStack.class, new Getter<ItemStack, PlayerItemBreakEvent>() { @Override @Nullable - public ItemType get(final PlayerItemBreakEvent e) { - return new ItemType(e.getBrokenItem()); + public ItemStack get(final PlayerItemBreakEvent e) { + return e.getBrokenItem(); } }, 0); } @@ -728,30 +728,29 @@ public Entity get(final PlayerInteractEntityEvent e) { return e.getRightClicked(); } }, 0); - EventValues.registerEventValue(PlayerInteractEntityEvent.class, ItemType.class, new Getter<ItemType, PlayerInteractEntityEvent>() { + EventValues.registerEventValue(PlayerInteractEntityEvent.class, ItemStack.class, new Getter<ItemStack, PlayerInteractEntityEvent>() { @Override @Nullable - public ItemType get(final PlayerInteractEntityEvent e) { + public ItemStack get(final PlayerInteractEntityEvent e) { if (offHandSupport) { EquipmentSlot hand = e.getHand(); if (hand == EquipmentSlot.HAND) - return new ItemType(e.getPlayer().getInventory().getItemInMainHand()); + return e.getPlayer().getInventory().getItemInMainHand(); else if (hand == EquipmentSlot.OFF_HAND) - return new ItemType(e.getPlayer().getInventory().getItemInOffHand()); + return e.getPlayer().getInventory().getItemInOffHand(); else return null; } else { - return new ItemType(e.getPlayer().getItemInHand()); + return e.getPlayer().getItemInHand(); } } }, 0); // PlayerInteractEvent - EventValues.registerEventValue(PlayerInteractEvent.class, ItemType.class, new Getter<ItemType, PlayerInteractEvent>() { + EventValues.registerEventValue(PlayerInteractEvent.class, ItemStack.class, new Getter<ItemStack, PlayerInteractEvent>() { @Override @Nullable - public ItemType get(final PlayerInteractEvent e) { - ItemStack item = e.getItem(); - return item == null ? null : new ItemType(item); + public ItemStack get(final PlayerInteractEvent e) { + return e.getItem(); } }, 0); EventValues.registerEventValue(PlayerInteractEvent.class, Block.class, new Getter<Block, PlayerInteractEvent>() { @@ -785,10 +784,10 @@ public Block get(final PlayerMoveEvent e) { } }, 0); // PlayerItemDamageEvent - EventValues.registerEventValue(PlayerItemDamageEvent.class, ItemType.class, new Getter<ItemType, PlayerItemDamageEvent>() { + EventValues.registerEventValue(PlayerItemDamageEvent.class, ItemStack.class, new Getter<ItemStack, PlayerItemDamageEvent>() { @Override - public ItemType get(PlayerItemDamageEvent event) { - return new ItemType(event.getItem()); + public ItemStack get(PlayerItemDamageEvent event) { + return event.getItem(); } }, 0); //PlayerItemMendEvent @@ -800,11 +799,11 @@ public Player get(PlayerItemMendEvent e) { return e.getPlayer(); } }, 0); - EventValues.registerEventValue(PlayerItemMendEvent.class, ItemType.class, new Getter<ItemType, PlayerItemMendEvent>() { + EventValues.registerEventValue(PlayerItemMendEvent.class, ItemStack.class, new Getter<ItemStack, PlayerItemMendEvent>() { @Override @Nullable - public ItemType get(PlayerItemMendEvent e) { - return new ItemType(e.getItem()); + public ItemStack get(PlayerItemMendEvent e) { + return e.getItem(); } }, 0); EventValues.registerEventValue(PlayerItemMendEvent.class, Entity.class, new Getter<Entity, PlayerItemMendEvent>() { @@ -982,14 +981,13 @@ public World get(final InventoryClickEvent e) { return e.getWhoClicked().getWorld(); } }, 0); - EventValues.registerEventValue(InventoryClickEvent.class, ItemType.class, new Getter<ItemType, InventoryClickEvent>() { + EventValues.registerEventValue(InventoryClickEvent.class, ItemStack.class, new Getter<ItemStack, InventoryClickEvent>() { @Override @Nullable - public ItemType get(final InventoryClickEvent e) { + public ItemStack get(final InventoryClickEvent e) { if (e instanceof CraftItemEvent) - return new ItemType(((CraftItemEvent) e).getRecipe().getResult()); - ItemStack item = e.getCurrentItem(); - return item == null ? null : new ItemType(item); + return ((CraftItemEvent) e).getRecipe().getResult(); + return e.getCurrentItem(); } }, 0); EventValues.registerEventValue(InventoryClickEvent.class, Slot.class, new Getter<Slot, InventoryClickEvent>() { @@ -1148,11 +1146,11 @@ public Item get(InventoryPickupItemEvent event) { return event.getItem(); } }, 0); - EventValues.registerEventValue(InventoryPickupItemEvent.class, ItemType.class, new Getter<ItemType, InventoryPickupItemEvent>() { + EventValues.registerEventValue(InventoryPickupItemEvent.class, ItemStack.class, new Getter<ItemStack, InventoryPickupItemEvent>() { @Nullable @Override - public ItemType get(InventoryPickupItemEvent event) { - return new ItemType(event.getItem().getItemStack()); + public ItemStack get(InventoryPickupItemEvent event) { + return event.getItem().getItemStack(); } }, 0); //PortalCreateEvent @@ -1173,12 +1171,12 @@ public Entity get(final PortalCreateEvent e) { }, 0); } //PlayerEditBookEvent - EventValues.registerEventValue(PlayerEditBookEvent.class, ItemType.class, new Getter<ItemType, PlayerEditBookEvent>() { + EventValues.registerEventValue(PlayerEditBookEvent.class, ItemStack.class, new Getter<ItemStack, PlayerEditBookEvent>() { @Override - public ItemType get(PlayerEditBookEvent e) { + public ItemStack get(PlayerEditBookEvent e) { ItemStack book = new ItemStack(e.getPlayer().getItemInHand().getType()); book.setItemMeta(e.getNewBookMeta()); - return new ItemType(book); //TODO: Find better way to derive this event value + return book; // TODO: Find better way to derive this event value } }, 0); //ItemDespawnEvent @@ -1189,11 +1187,11 @@ public Item get(ItemDespawnEvent e) { return e.getEntity(); } }, 0); - EventValues.registerEventValue(ItemDespawnEvent.class, ItemType.class, new Getter<ItemType, ItemDespawnEvent>() { + EventValues.registerEventValue(ItemDespawnEvent.class, ItemStack.class, new Getter<ItemStack, ItemDespawnEvent>() { @Override @Nullable - public ItemType get(ItemDespawnEvent e) { - return new ItemType(e.getEntity().getItemStack()); + public ItemStack get(ItemDespawnEvent e) { + return e.getEntity().getItemStack(); } }, 0); //ItemMergeEvent @@ -1211,11 +1209,11 @@ public Item get(ItemMergeEvent e) { return e.getTarget(); } }, 1); - EventValues.registerEventValue(ItemMergeEvent.class, ItemType.class, new Getter<ItemType, ItemMergeEvent>() { + EventValues.registerEventValue(ItemMergeEvent.class, ItemStack.class, new Getter<ItemStack, ItemMergeEvent>() { @Override @Nullable - public ItemType get(ItemMergeEvent e) { - return new ItemType(e.getEntity().getItemStack()); + public ItemStack get(ItemMergeEvent e) { + return e.getEntity().getItemStack(); } }, 0); //PlayerTeleportEvent @@ -1271,21 +1269,20 @@ public FireworkEffect get(FireworkExplodeEvent e) { } //PlayerRiptideEvent if (Skript.classExists("org.bukkit.event.player.PlayerRiptideEvent")) { - EventValues.registerEventValue(PlayerRiptideEvent.class, ItemType.class, new Getter<ItemType, PlayerRiptideEvent>() { + EventValues.registerEventValue(PlayerRiptideEvent.class, ItemStack.class, new Getter<ItemStack, PlayerRiptideEvent>() { @Override - public ItemType get(PlayerRiptideEvent e) { - return new ItemType(e.getItem()); + public ItemStack get(PlayerRiptideEvent e) { + return e.getItem(); } }, 0); } //PlayerArmorChangeEvent if (Skript.classExists("com.destroystokyo.paper.event.player.PlayerArmorChangeEvent")) { - EventValues.registerEventValue(PlayerArmorChangeEvent.class, ItemType.class, new Getter<ItemType, PlayerArmorChangeEvent>() { + EventValues.registerEventValue(PlayerArmorChangeEvent.class, ItemStack.class, new Getter<ItemStack, PlayerArmorChangeEvent>() { @Override @Nullable - public ItemType get(PlayerArmorChangeEvent e) { - ItemStack stack = e.getNewItem(); - return stack == null ? null : new ItemType(stack); + public ItemStack get(PlayerArmorChangeEvent e) { + return e.getNewItem(); } }, 0); } @@ -1297,11 +1294,11 @@ public Player get(PrepareItemEnchantEvent e) { return e.getEnchanter(); } }, 0); - EventValues.registerEventValue(PrepareItemEnchantEvent.class, ItemType.class, new Getter<ItemType, PrepareItemEnchantEvent>() { + EventValues.registerEventValue(PrepareItemEnchantEvent.class, ItemStack.class, new Getter<ItemStack, PrepareItemEnchantEvent>() { @Override @Nullable - public ItemType get(PrepareItemEnchantEvent e) { - return new ItemType(e.getItem()); + public ItemStack get(PrepareItemEnchantEvent e) { + return e.getItem(); } }, 0); EventValues.registerEventValue(PrepareItemEnchantEvent.class, Block.class, new Getter<Block, PrepareItemEnchantEvent>() { @@ -1319,11 +1316,11 @@ public Player get(EnchantItemEvent e) { return e.getEnchanter(); } }, 0); - EventValues.registerEventValue(EnchantItemEvent.class, ItemType.class, new Getter<ItemType, EnchantItemEvent>() { + EventValues.registerEventValue(EnchantItemEvent.class, ItemStack.class, new Getter<ItemStack, EnchantItemEvent>() { @Override @Nullable - public ItemType get(EnchantItemEvent e) { - return new ItemType(e.getItem()); + public ItemStack get(EnchantItemEvent e) { + return e.getItem(); } }, 0); EventValues.registerEventValue(EnchantItemEvent.class, Block.class, new Getter<Block, EnchantItemEvent>() { From 5c8156396935ec1c94225701fab8f58bfd0cfa1a Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Fri, 15 Jul 2022 16:50:12 +0200 Subject: [PATCH 050/619] Remove legacy code (#4885) --- src/main/java/ch/njol/skript/Skript.java | 8 +- .../java/ch/njol/skript/aliases/ItemData.java | 55 +--- .../java/ch/njol/skript/aliases/ItemType.java | 2 +- .../njol/skript/aliases/MaterialRegistry.java | 24 +- .../njol/skript/bukkitutil/BiomeMappings.java | 57 ---- .../njol/skript/bukkitutil/BukkitUnsafe.java | 144 +-------- .../skript/bukkitutil/EnchantmentUtils.java | 64 +--- .../ch/njol/skript/bukkitutil/ItemUtils.java | 62 +--- .../njol/skript/bukkitutil/PlayerUtils.java | 92 ++---- .../njol/skript/bukkitutil/Workarounds.java | 55 ---- .../skript/bukkitutil/block/BlockCompat.java | 3 +- .../bukkitutil/block/MagicBlockCompat.java | 212 ------------ .../skript/classes/data/BukkitClasses.java | 304 +++++++++--------- .../classes/data/BukkitEventValues.java | 266 +++++++-------- .../skript/classes/data/DefaultChangers.java | 5 +- .../classes/data/DefaultComparators.java | 74 ++--- .../classes/data/DefaultConverters.java | 18 +- .../java/ch/njol/skript/command/Commands.java | 88 ++--- .../conditions/CondHasScoreboardTag.java | 3 +- .../ch/njol/skript/effects/EffToggle.java | 96 +----- .../ch/njol/skript/entity/EndermanData.java | 40 +-- .../ch/njol/skript/entity/EntityData.java | 4 +- .../java/ch/njol/skript/entity/FishData.java | 5 +- .../ch/njol/skript/entity/GuardianData.java | 98 ------ .../java/ch/njol/skript/entity/HorseData.java | 172 ---------- .../njol/skript/entity/SimpleEntityData.java | 13 +- .../ch/njol/skript/entity/SkeletonData.java | 159 --------- .../njol/skript/entity/ThrownPotionData.java | 4 +- .../njol/skript/entity/TropicalFishData.java | 12 +- .../ch/njol/skript/entity/VillagerData.java | 18 +- .../skript/entity/ZombieVillagerData.java | 7 +- .../java/ch/njol/skript/events/EvtBlock.java | 51 ++- .../ch/njol/skript/events/EvtBlockLegacy.java | 138 -------- .../java/ch/njol/skript/events/EvtClick.java | 13 +- .../skript/expressions/ExprDurability.java | 8 +- .../expressions/ExprEnchantmentExpCosts.java | 192 ----------- .../expressions/ExprEntityAttribute.java | 6 +- .../njol/skript/expressions/ExprFacing.java | 55 +--- .../skript/expressions/ExprGlidingState.java | 3 +- .../njol/skript/expressions/ExprGravity.java | 3 +- .../skript/expressions/ExprMaxHealth.java | 5 - .../expressions/ExprOnlinePlayersCount.java | 5 +- .../expressions/ExprScoreboardTags.java | 7 +- .../ch/njol/skript/expressions/ExprSpeed.java | 4 - .../skript/expressions/ExprTimePlayed.java | 13 +- .../ch/njol/skript/expressions/ExprUUID.java | 27 +- .../skript/expressions/ExprUnbreakable.java | 13 +- .../njol/skript/hooks/biomes/BiomeHook.java | 73 ----- .../skript/hooks/biomes/BiomeMapUtil.java | 119 ------- .../skript/hooks/biomes/package-info.java | 27 -- .../java/ch/njol/skript/lang/Variable.java | 4 +- .../java/ch/njol/skript/util/BiomeUtils.java | 31 +- .../ch/njol/skript/util/BlockStateBlock.java | 22 -- .../njol/skript/util/DelayedChangeBlock.java | 5 - .../java/ch/njol/skript/util/Direction.java | 17 +- .../java/ch/njol/skript/util/SkriptColor.java | 2 +- src/main/java/ch/njol/skript/util/Task.java | 9 +- .../skript/util/chat/BungeeConverter.java | 12 +- 58 files changed, 557 insertions(+), 2471 deletions(-) delete mode 100644 src/main/java/ch/njol/skript/bukkitutil/BiomeMappings.java delete mode 100644 src/main/java/ch/njol/skript/bukkitutil/Workarounds.java delete mode 100644 src/main/java/ch/njol/skript/bukkitutil/block/MagicBlockCompat.java delete mode 100644 src/main/java/ch/njol/skript/entity/GuardianData.java delete mode 100644 src/main/java/ch/njol/skript/entity/HorseData.java delete mode 100644 src/main/java/ch/njol/skript/entity/SkeletonData.java delete mode 100644 src/main/java/ch/njol/skript/events/EvtBlockLegacy.java delete mode 100644 src/main/java/ch/njol/skript/expressions/ExprEnchantmentExpCosts.java delete mode 100644 src/main/java/ch/njol/skript/hooks/biomes/BiomeHook.java delete mode 100644 src/main/java/ch/njol/skript/hooks/biomes/BiomeMapUtil.java delete mode 100644 src/main/java/ch/njol/skript/hooks/biomes/package-info.java diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index d1cea1d8ff9..8750b65d8c5 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -19,9 +19,7 @@ package ch.njol.skript; import ch.njol.skript.aliases.Aliases; -import ch.njol.skript.bukkitutil.BukkitUnsafe; import ch.njol.skript.bukkitutil.BurgerHelper; -import ch.njol.skript.bukkitutil.Workarounds; import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.classes.Comparator; import ch.njol.skript.classes.Converter; @@ -358,8 +356,6 @@ public void onEnable() { getAddonInstance(); - Workarounds.init(); - // Start the updater // Note: if config prohibits update checks, it will NOT do network connections try { @@ -468,9 +464,7 @@ public void onEnable() { assert updater != null; updater.updateCheck(console); } - - BukkitUnsafe.initialize(); // Needed for aliases - + try { Aliases.load(); // Loaded before anything that might use them } catch (StackOverflowError e) { diff --git a/src/main/java/ch/njol/skript/aliases/ItemData.java b/src/main/java/ch/njol/skript/aliases/ItemData.java index 29a226d843f..2d74e0015f9 100644 --- a/src/main/java/ch/njol/skript/aliases/ItemData.java +++ b/src/main/java/ch/njol/skript/aliases/ItemData.java @@ -81,9 +81,6 @@ public static class OldItemData { static final MaterialRegistry materialRegistry; - private static final boolean SPAWN_EGG_META_EXISTS = Skript.classExists("org.bukkit.inventory.meta.SpawnEggMeta"); - private static final boolean HAS_NEW_SKULL_META_METHODS = Skript.methodExists(SkullMeta.class, "getOwningPlayer"); - // Load or create material registry static { Gson gson = new GsonBuilder().registerTypeAdapterFactory(EnumTypeAdapter.factory).serializeNulls().create(); @@ -119,8 +116,11 @@ public static class OldItemData { /** * Before 1.13, data values ("block states") are applicable to items. + * + * @deprecated before 1.13 is no longer supported */ - public static final boolean itemDataValues = !Skript.isRunningMinecraft(1, 13); + @Deprecated + public static final boolean itemDataValues = false; /** * ItemStack, which is used for everything but serialization. @@ -341,16 +341,12 @@ public MatchQuality matchAlias(ItemData item) { return MatchQuality.DIFFERENT; } BlockValues values = blockValues; - if (!itemDataValues) { - // Items (held in inventories) don't have block values - // If this is an item, given item must not have them either - if (itemForm && item.blockValues != null && !item.blockValues.isDefault()) { - return MatchQuality.SAME_MATERIAL; - } - } else if (itemFlags != 0 && ItemUtils.getDamage(stack) != ItemUtils.getDamage(item.stack)) { - return MatchQuality.DIFFERENT; // On 1.12 and below, items may share a material but have a different data value (ex: white wool vs red wool) + // Items (held in inventories) don't have block values + // If this is an item, given item must not have them either + if (itemForm && item.blockValues != null && !item.blockValues.isDefault()) { + return MatchQuality.SAME_MATERIAL; } - + /* * Initially, expect exact match. Lower expectations as new differences * between items are discovered. @@ -460,35 +456,16 @@ private static MatchQuality compareItemMetas(ItemMeta first, ItemMeta second) { PotionData theirPotion = ((PotionMeta) second).getBasePotionData(); return !Objects.equals(ourPotion, theirPotion) ? MatchQuality.SAME_MATERIAL : quality; } - - // Only check spawn egg data on 1.12 and below. See issue #3167 - if (!MaterialRegistry.newMaterials && SPAWN_EGG_META_EXISTS && second instanceof SpawnEggMeta) { - if (!(first instanceof SpawnEggMeta)) { - return MatchQuality.DIFFERENT; // Second is a spawn egg, first is clearly not - } - // Compare spawn egg spawned type - EntityType ourSpawnedType = ((SpawnEggMeta) first).getSpawnedType(); - EntityType theirSpawnedType = ((SpawnEggMeta) second).getSpawnedType(); - return !Objects.equals(ourSpawnedType, theirSpawnedType) ? MatchQuality.SAME_MATERIAL : quality; - } - + // Skull owner if (second instanceof SkullMeta) { if (!(first instanceof SkullMeta)) { return MatchQuality.DIFFERENT; // Second is a skull, first is clearly not } // Compare skull owners - if (HAS_NEW_SKULL_META_METHODS) { - OfflinePlayer ourOwner = ((SkullMeta) first).getOwningPlayer(); - OfflinePlayer theirOwner = ((SkullMeta) second).getOwningPlayer(); - return !Objects.equals(ourOwner, theirOwner) ? MatchQuality.SAME_MATERIAL : quality; - } else { // Use old methods - @SuppressWarnings("deprecation") - String ourOwner = ((SkullMeta) first).getOwner(); - @SuppressWarnings("deprecation") - String theirOwner = ((SkullMeta) second).getOwner(); - return !Objects.equals(ourOwner, theirOwner) ? MatchQuality.SAME_MATERIAL : quality; - } + OfflinePlayer ourOwner = ((SkullMeta) first).getOwningPlayer(); + OfflinePlayer theirOwner = ((SkullMeta) second).getOwningPlayer(); + return !Objects.equals(ourOwner, theirOwner) ? MatchQuality.SAME_MATERIAL : quality; } return quality; @@ -649,10 +626,8 @@ public ItemData aliasCopy() { meta.setDisplayName(null); // Clear display name data.stack.setItemMeta(meta); } - if (!itemDataValues) { - ItemUtils.setDamage(data.stack, 0); // Set to undamaged - } - + ItemUtils.setDamage(data.stack, 0); // Set to undamaged + data.type = type; data.blockValues = blockValues; data.itemForm = itemForm; diff --git a/src/main/java/ch/njol/skript/aliases/ItemType.java b/src/main/java/ch/njol/skript/aliases/ItemType.java index 3d2e88519e4..99da8dac535 100644 --- a/src/main/java/ch/njol/skript/aliases/ItemType.java +++ b/src/main/java/ch/njol/skript/aliases/ItemType.java @@ -938,7 +938,7 @@ public boolean isSimilar(ItemType other) { if (myType.isPlain() != otherType.isPlain()) { minimumQuality = MatchQuality.EXACT; } else if ((otherType.isAlias() && !myType.isAlias()) - || (!ItemData.itemDataValues && myType.itemForm && otherType.blockValues != null && !otherType.blockValues.isDefault())) { + || (myType.itemForm && otherType.blockValues != null && !otherType.blockValues.isDefault())) { // First Check: Don't require an EXACT match if the other ItemData is an alias. They only need to share a material. // Second Check: Items (held in inventories) don't have block values, but the other item does (may be an item-block comparison) minimumQuality = MatchQuality.SAME_MATERIAL; diff --git a/src/main/java/ch/njol/skript/aliases/MaterialRegistry.java b/src/main/java/ch/njol/skript/aliases/MaterialRegistry.java index 345d16b36b8..2843b7eb2d6 100644 --- a/src/main/java/ch/njol/skript/aliases/MaterialRegistry.java +++ b/src/main/java/ch/njol/skript/aliases/MaterialRegistry.java @@ -30,9 +30,7 @@ * items as variables. */ public class MaterialRegistry { - - static final boolean newMaterials = Skript.isRunningMinecraft(1, 13); - + /** * Loads a material registry from an array of strings. New materials will * be added and legacy names updated when possible. @@ -45,21 +43,15 @@ public static MaterialRegistry load(String[] names) { // Use names we have to fill at least some mappings boolean[] processed = new boolean[materials.length]; - for (int i = 0; i < names.length; i++) { - String name = names[i]; - if (name == null) { + for (String name : names) { + if (name == null) continue; // This slot is intentionally empty + + Material mat = Material.getMaterial(name); + if (mat == null) { // Try getting legacy material instead + mat = Material.getMaterial(name, true); } - Material mat; - if (newMaterials) { - mat = Material.getMaterial(name); - if (mat == null) { // Try getting legacy material instead - mat = Material.getMaterial(name, true); - } - } else { // Pre-1.13, no getMaterial existed - mat = Material.valueOf(name); - } - + mappings.add(mat); if (mat != null) { processed[mat.ordinal()] = true; // This material exists diff --git a/src/main/java/ch/njol/skript/bukkitutil/BiomeMappings.java b/src/main/java/ch/njol/skript/bukkitutil/BiomeMappings.java deleted file mode 100644 index f78b962515b..00000000000 --- a/src/main/java/ch/njol/skript/bukkitutil/BiomeMappings.java +++ /dev/null @@ -1,57 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.bukkitutil; - -import org.bukkit.block.Biome; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.Skript; -import ch.njol.skript.hooks.biomes.BiomeHook; -import ch.njol.skript.hooks.biomes.BiomeMapUtil.To19Mapping; -import ch.njol.skript.util.EnumUtils; - -/** - * 1.13 to 1.9+ biome name mappings. - */ -public abstract class BiomeMappings { - - private final static EnumUtils<Biome> util = new EnumUtils<>(Biome.class, "biomes"); - - private final static boolean mapFor19 = !Skript.isRunningMinecraft(1, 13); - - public static @Nullable Biome parse(final String name) { - if (!mapFor19) return util.parse(name); - - To19Mapping mapping = BiomeHook.getUtil().parse(name); - if (mapping == null) return util.parse(name); // Should not happen - incomplete maps are a mess to work with for programmer - return mapping.getHandle(); - } - - public static String toString(final Biome biome, final int flags) { - if (!mapFor19) return util.toString(biome, flags); - To19Mapping mapping = To19Mapping.getMapping(biome); - if (mapping == null) return ""; - return BiomeHook.getUtil().toString(mapping, flags); - } - - public static String getAllNames() { - if (!mapFor19) return util.getAllNames(); - return BiomeHook.getUtil().getAllNames(); - } -} \ No newline at end of file diff --git a/src/main/java/ch/njol/skript/bukkitutil/BukkitUnsafe.java b/src/main/java/ch/njol/skript/bukkitutil/BukkitUnsafe.java index 9c7d7ed7ffd..05d188657f0 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/BukkitUnsafe.java +++ b/src/main/java/ch/njol/skript/bukkitutil/BukkitUnsafe.java @@ -56,144 +56,31 @@ public class BukkitUnsafe { */ @Nullable private static final UnsafeValues unsafe = Bukkit.getUnsafe(); - - /** - * 1.9 Spigot has some "fun" bugs. - */ - private static final boolean knownNullPtr = !Skript.isRunningMinecraft(1, 11); - + static { if (unsafe == null) throw new Error("UnsafeValues are not available."); } - - /** - * Before 1.13, Vanilla material names were translated using - * this + a lookup table. - */ - @Nullable - private static MethodHandle unsafeFromInternalNameMethod; - - private static final boolean newMaterials = Skript.isRunningMinecraft(1, 13); - - /** - * Vanilla material names to Bukkit materials. - */ - @Nullable - private static Map<String,Material> materialMap; - - /** - * If we have material map for this version, using it is preferred. - * Otherwise, it can be used as fallback. - */ - private static boolean preferMaterialMap = true; - - /** - * We only spit one exception (unless debugging) from UnsafeValues. Some - * users might not care, and find 1.12 material mappings accurate enough. - */ - private static boolean unsafeValuesErrored; - + /** * Maps pre 1.12 ids to materials for variable conversions. */ @Nullable private static Map<Integer,Material> idMappings; - - public static void initialize() { - if (!newMaterials) { - MethodHandle mh; - try { - mh = MethodHandles.lookup().findVirtual(UnsafeValues.class, - "getMaterialFromInternalName", MethodType.methodType(Material.class, String.class)); - } catch (NoSuchMethodException | IllegalAccessException e) { - mh = null; - } - unsafeFromInternalNameMethod = mh; - - try { - Version version = Skript.getMinecraftVersion(); - boolean mapExists = loadMaterialMap("materials/" + version.getMajor() + "." + version.getMinor() + ".json"); - if (!mapExists) { - loadMaterialMap("materials/1.9.json"); // 1.9 is oldest we have mappings for - preferMaterialMap = false; - Skript.warning("Material mappings for " + version + " are not available."); - Skript.warning("Depending on your server software, some aliases may not work."); - } - } catch (IOException e) { - Skript.exception(e, "Failed to load material mappings. Aliases may not work properly."); - } - } - } - + @Nullable public static Material getMaterialFromMinecraftId(String id) { - if (newMaterials) { - // On 1.13, Vanilla and Spigot names are same - if (id.length() > 9) - return Material.matchMaterial(id.substring(10)); // Strip 'minecraft:' out - else // Malformed material name - return null; - } else { - // If we have correct material map, prefer using it - if (preferMaterialMap) { - if (id.length() > 9) { - assert materialMap != null; - return materialMap.get(id.substring(10)); // Strip 'minecraft:' out - } - } - - // Otherwise, hacks - Material type = null; - try { - assert unsafeFromInternalNameMethod != null; - type = (Material) unsafeFromInternalNameMethod.invokeExact(unsafe, id); - } catch (Throwable e) { - // Only spit out an error once unless debugging - if (!unsafeValuesErrored || Skript.debug()) { - Skript.exception(e, "UnsafeValues failed to get material from Vanilla id"); - unsafeValuesErrored = true; - } - } - if (type == null || type == Material.AIR) { // If there is no item form, UnsafeValues won't work - // So we're going to rely on 1.12's material mappings - assert materialMap != null; - return materialMap.get(id); - } - return type; - } + // On 1.13, Vanilla and Spigot names are same + if (id.length() > 9) + return Material.matchMaterial(id.substring(10)); // Strip 'minecraft:' out + else // Malformed material name + return null; } - - private static boolean loadMaterialMap(String name) throws IOException { - try (InputStream is = Skript.getInstance().getResource(name)) { - if (is == null) { // No mappings for this Minecraft version - return false; - } - String data = new String(ByteStreams.toByteArray(is), StandardCharsets.UTF_8); - - Type type = new TypeToken<Map<String,Material>>(){}.getType(); - materialMap = new GsonBuilder(). - registerTypeAdapterFactory(EnumTypeAdapter.factory) - .create().fromJson(data, type); - } - - return true; - } - + public static void modifyItemStack(ItemStack stack, String arguments) { if (unsafe == null) throw new IllegalStateException("modifyItemStack could not be performed as UnsafeValues are not available."); - assert unsafe != null; - try { - unsafe.modifyItemStack(stack, arguments); - } catch (NullPointerException e) { - if (knownNullPtr) { // Probably known Spigot bug - // So we continue doing whatever we were doing and hope it works - Skript.warning("Item " + stack.getType() + arguments + " failed modifyItemStack. This is a bug on old Spigot versions."); - } else { // Not known null pointer, don't just swallow - throw e; - } - } + unsafe.modifyItemStack(stack, arguments); } private static void initIdMappings() { @@ -210,14 +97,9 @@ private static void initIdMappings() { // Process raw mappings Map<Integer, Material> parsed = new HashMap<>(rawMappings.size()); - if (newMaterials) { // Legacy material conversion API - for (Map.Entry<Integer, String> entry : rawMappings.entrySet()) { - parsed.put(entry.getKey(), Material.matchMaterial(entry.getValue(), true)); - } - } else { // Just enum API - for (Map.Entry<Integer, String> entry : rawMappings.entrySet()) { - parsed.put(entry.getKey(), Material.valueOf(entry.getValue())); - } + // Legacy material conversion API + for (Map.Entry<Integer, String> entry : rawMappings.entrySet()) { + parsed.put(entry.getKey(), Material.matchMaterial(entry.getValue(), true)); } idMappings = parsed; } catch (IOException e) { diff --git a/src/main/java/ch/njol/skript/bukkitutil/EnchantmentUtils.java b/src/main/java/ch/njol/skript/bukkitutil/EnchantmentUtils.java index 05cdad07f6a..fcebff78917 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/EnchantmentUtils.java +++ b/src/main/java/ch/njol/skript/bukkitutil/EnchantmentUtils.java @@ -27,67 +27,17 @@ import ch.njol.skript.Skript; /** - * Maps enchantments to their ids in Minecraft 1.12. + * Maps enchantments to their keys. */ public class EnchantmentUtils { - - private static final boolean KEY_METHOD_EXISTS = Skript.methodExists(Enchantment.class, "getKey"); - private static final BiMap<Enchantment, String> ENCHANTMENTS = HashBiMap.create(); - - static { - ENCHANTMENTS.put(Enchantment.PROTECTION_ENVIRONMENTAL, "protection"); - ENCHANTMENTS.put(Enchantment.PROTECTION_FIRE, "fire_protection"); - ENCHANTMENTS.put(Enchantment.PROTECTION_FALL, "feather_falling"); - ENCHANTMENTS.put(Enchantment.PROTECTION_EXPLOSIONS, "blast_protection"); - ENCHANTMENTS.put(Enchantment.PROTECTION_PROJECTILE, "projectile_protection"); - ENCHANTMENTS.put(Enchantment.OXYGEN, "respiration"); - ENCHANTMENTS.put(Enchantment.WATER_WORKER, "aqua_affinity"); - ENCHANTMENTS.put(Enchantment.THORNS, "thorns"); - ENCHANTMENTS.put(Enchantment.DEPTH_STRIDER, "depth_strider"); - ENCHANTMENTS.put(Enchantment.DAMAGE_ALL, "sharpness"); - ENCHANTMENTS.put(Enchantment.DAMAGE_UNDEAD, "smite"); - ENCHANTMENTS.put(Enchantment.DAMAGE_ARTHROPODS, "bane_of_arthropods"); - ENCHANTMENTS.put(Enchantment.KNOCKBACK, "knockback"); - ENCHANTMENTS.put(Enchantment.FIRE_ASPECT, "fire_aspect"); - ENCHANTMENTS.put(Enchantment.LOOT_BONUS_MOBS, "looting"); - ENCHANTMENTS.put(Enchantment.DIG_SPEED, "efficiency"); - ENCHANTMENTS.put(Enchantment.SILK_TOUCH, "silk_touch"); - ENCHANTMENTS.put(Enchantment.DURABILITY, "unbreaking"); - ENCHANTMENTS.put(Enchantment.LOOT_BONUS_BLOCKS, "fortune"); - ENCHANTMENTS.put(Enchantment.ARROW_DAMAGE, "power"); - ENCHANTMENTS.put(Enchantment.ARROW_KNOCKBACK, "punch"); - ENCHANTMENTS.put(Enchantment.ARROW_FIRE, "flame"); - ENCHANTMENTS.put(Enchantment.ARROW_INFINITE, "infinity"); - ENCHANTMENTS.put(Enchantment.LUCK, "luck_of_the_sea"); - ENCHANTMENTS.put(Enchantment.LURE, "lure"); - - if (Skript.isRunningMinecraft(1, 9)) { - ENCHANTMENTS.put(Enchantment.FROST_WALKER, "frost_walker"); - ENCHANTMENTS.put(Enchantment.MENDING, "mending"); - } - - if (Skript.isRunningMinecraft(1, 11)) { - ENCHANTMENTS.put(Enchantment.BINDING_CURSE, "binding_curse"); - ENCHANTMENTS.put(Enchantment.VANISHING_CURSE, "vanishing_curse"); - } - - if (Skript.isRunningMinecraft(1, 12)) { - ENCHANTMENTS.put(Enchantment.SWEEPING_EDGE, "sweeping_edge"); - } - } - - public static String getKey(Enchantment ench) { - if (KEY_METHOD_EXISTS) - return ench.getKey().getKey(); - String name = ENCHANTMENTS.get(ench); - assert name != null : "missing name for " + ench; - return name; + + public static String getKey(Enchantment enchantment) { + return enchantment.getKey().getKey(); } - + @Nullable public static Enchantment getByKey(String key) { - if (KEY_METHOD_EXISTS) - return Enchantment.getByKey(NamespacedKey.minecraft(key)); - return ENCHANTMENTS.inverse().get(key); + return Enchantment.getByKey(NamespacedKey.minecraft(key)); } + } diff --git a/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java b/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java index 68168525984..5e769682861 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java +++ b/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java @@ -30,26 +30,17 @@ * Miscellaneous static utility methods related to items. */ public class ItemUtils { - - private ItemUtils() {} // Not to be instanced - - private static final boolean damageMeta = Skript.classExists("org.bukkit.inventory.meta.Damageable"); - + /** * Gets damage/durability of an item, or 0 if it does not have damage. * @param stack Item. * @return Damage. */ - @SuppressWarnings("deprecation") public static int getDamage(ItemStack stack) { - if (damageMeta) { - ItemMeta meta = stack.getItemMeta(); - if (meta instanceof Damageable) - return ((Damageable) meta).getDamage(); - return 0; // Not damageable item - } else { - return stack.getDurability(); - } + ItemMeta meta = stack.getItemMeta(); + if (meta instanceof Damageable) + return ((Damageable) meta).getDamage(); + return 0; // Not damageable item } /** @@ -58,34 +49,14 @@ public static int getDamage(ItemStack stack) { * @param damage New damage. Note that on some Minecraft versions, * this might be truncated to short. */ - @SuppressWarnings("deprecation") public static void setDamage(ItemStack stack, int damage) { - if (damageMeta) { - ItemMeta meta = stack.getItemMeta(); - if (meta instanceof Damageable) { - ((Damageable) meta).setDamage(damage); - stack.setItemMeta(meta); - } - } else { - stack.setDurability((short) damage); - } - } - - @Nullable - private static final Material bedItem; - @Nullable - private static final Material bedBlock; - - static { - if (!damageMeta) { - bedItem = Material.valueOf("BED"); - bedBlock = Material.valueOf("BED_BLOCK"); - } else { - bedItem = null; - bedBlock = null; + ItemMeta meta = stack.getItemMeta(); + if (meta instanceof Damageable) { + ((Damageable) meta).setDamage(damage); + stack.setItemMeta(meta); } } - + /** * Gets a block material corresponding to given item material, which might * be the given material. If no block material is found, null is returned. @@ -94,12 +65,6 @@ public static void setDamage(ItemStack stack, int damage) { */ @Nullable public static Material asBlock(Material type) { - if (!damageMeta) { // Apply some hacks on 1.12 and older - if (type == bedItem) { // BED and BED_BLOCK mess, issue #1856 - return bedBlock; - } - } - if (type.isBlock()) { return type; } else { @@ -114,13 +79,6 @@ public static Material asBlock(Material type) { * @return Item version of material or null. */ public static Material asItem(Material type) { - if (!damageMeta) { - if (type == bedBlock) { - assert bedItem != null; - return bedItem; - } - } - // Assume (naively) that all types are valid items return type; } diff --git a/src/main/java/ch/njol/skript/bukkitutil/PlayerUtils.java b/src/main/java/ch/njol/skript/bukkitutil/PlayerUtils.java index 3cb54d35443..20c945ee4bb 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/PlayerUtils.java +++ b/src/main/java/ch/njol/skript/bukkitutil/PlayerUtils.java @@ -18,9 +18,6 @@ */ package ch.njol.skript.bukkitutil; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -38,71 +35,42 @@ import ch.njol.skript.util.Task; /** - * TODO check all updates and find out which ones are not required - * - * @author Peter Güttinger + * Contains utility methods related to players */ public abstract class PlayerUtils { - private PlayerUtils() {} - - final static Set<Player> inviUpdate = new HashSet<>(); - - public static void updateInventory(final @Nullable Player p) { - if (p != null) - inviUpdate.add(p); + + private static final Set<Player> inventoryUpdateList = Collections.synchronizedSet(new HashSet<>()); + + /** + * Updates the clients inventory within a tick, using {@link Player#updateInventory()}. + * Recommended over directly calling the update method, + * as multiple calls to this method within a short timespan will not send multiple updates to the client. + */ + public static void updateInventory(@Nullable Player player) { + if (player != null) + inventoryUpdateList.add(player); } - - // created when first used - final static Task task = new Task(Skript.getInstance(), 1, 1) { - @Override - public void run() { - try { - for (final Player p : inviUpdate) + + static { + new Task(Skript.getInstance(), 1, 1) { + @Override + public void run() { + for (Player p : inventoryUpdateList) p.updateInventory(); - } catch (final NullPointerException e) { // can happen on older CraftBukkit (Tekkit) builds - if (Skript.debug()) - e.printStackTrace(); + + inventoryUpdateList.clear(); } - inviUpdate.clear(); - } - }; - - private final static boolean hasCollecionGetOnlinePlayers = Skript.methodExists(Bukkit.class, "getOnlinePlayers", new Class[0], Collection.class); - @Nullable - private static Method getOnlinePlayers = null; - - @SuppressWarnings({"null", "unchecked"}) + }; + } + + /** + * @deprecated use {@link Bukkit#getOnlinePlayers()} instead + */ + @Deprecated public static Collection<? extends Player> getOnlinePlayers() { - if (hasCollecionGetOnlinePlayers) { - return ImmutableList.copyOf(Bukkit.getOnlinePlayers()); - } else { - if (getOnlinePlayers == null) { - try { - getOnlinePlayers = Bukkit.class.getDeclaredMethod("getOnlinePlayers"); - } catch (final NoSuchMethodException e) { - Skript.outdatedError(e); - } catch (final SecurityException e) { - Skript.exception(e); - } - } - try { - final Object o = getOnlinePlayers.invoke(null); - if (o instanceof Collection<?>) - return ImmutableList.copyOf((Collection<? extends Player>) o); - else - return Arrays.asList(((Player[]) o).clone()); - } catch (final IllegalAccessException e) { - Skript.outdatedError(e); - } catch (final IllegalArgumentException e) { - Skript.outdatedError(e); - } catch (final InvocationTargetException e) { - Skript.exception(e); - } - return Collections.emptyList(); - } + return ImmutableList.copyOf(Bukkit.getOnlinePlayers()); } - - + public static boolean canEat(Player p, Material food) { GameMode gm = p.getGameMode(); if (gm == GameMode.CREATIVE || gm == GameMode.SPECTATOR) @@ -123,7 +91,7 @@ public static boolean canEat(Player p, Material food) { } if (p.getFoodLevel() < 20 || special) return true; - + return false; } } diff --git a/src/main/java/ch/njol/skript/bukkitutil/Workarounds.java b/src/main/java/ch/njol/skript/bukkitutil/Workarounds.java deleted file mode 100644 index a2b8f9e7dbf..00000000000 --- a/src/main/java/ch/njol/skript/bukkitutil/Workarounds.java +++ /dev/null @@ -1,55 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.bukkitutil; - -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.event.Event.Result; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerInteractEvent; - -import ch.njol.skript.Skript; - -/** - * Workarounds for Minecraft & Bukkit quirks - * - * @author Peter Güttinger - */ -public abstract class Workarounds { - private Workarounds() {} - - public static void init() {} - - static { - if (!Skript.isRunningMinecraft(1, 9)) { - // allows to properly remove a player's tool in right click events - Bukkit.getPluginManager().registerEvents(new Listener() { - @SuppressWarnings("deprecation") - @EventHandler(priority = EventPriority.HIGHEST) - public void onInteract(final PlayerInteractEvent e) { - if (e.hasItem() && (e.getPlayer().getInventory().getItemInHand().getType() == Material.AIR || e.getPlayer().getInventory().getItemInHand().getAmount() == 0)) - e.setUseItemInHand(Result.DENY); - } - }, Skript.getInstance()); - } - } - -} diff --git a/src/main/java/ch/njol/skript/bukkitutil/block/BlockCompat.java b/src/main/java/ch/njol/skript/bukkitutil/block/BlockCompat.java index 216172eb2ac..5ffb1063284 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/block/BlockCompat.java +++ b/src/main/java/ch/njol/skript/bukkitutil/block/BlockCompat.java @@ -39,8 +39,7 @@ public interface BlockCompat { /** * Instance of BlockCompat for current Minecraft version. */ - static final BlockCompat INSTANCE = Skript.isRunningMinecraft(1, 13) - ? new NewBlockCompat() : new MagicBlockCompat(); + BlockCompat INSTANCE = new NewBlockCompat(); static final BlockSetter SETTER = INSTANCE.getSetter(); diff --git a/src/main/java/ch/njol/skript/bukkitutil/block/MagicBlockCompat.java b/src/main/java/ch/njol/skript/bukkitutil/block/MagicBlockCompat.java deleted file mode 100644 index 1677263557f..00000000000 --- a/src/main/java/ch/njol/skript/bukkitutil/block/MagicBlockCompat.java +++ /dev/null @@ -1,212 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.bukkitutil.block; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.util.Map; - -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.block.BlockState; -import org.bukkit.entity.FallingBlock; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.Skript; -import ch.njol.skript.aliases.ItemFlags; -import ch.njol.skript.aliases.MatchQuality; -import ch.njol.skript.bukkitutil.ItemUtils; - -/** - * Block compatibility implemented with magic numbers. No other choice until - * Spigot 1.13. - */ -public class MagicBlockCompat implements BlockCompat { - - public static final MethodHandle setRawDataMethod; - private static final MethodHandle getBlockDataMethod; - public static final MethodHandle setDataMethod; - - static { - MethodHandles.Lookup lookup = MethodHandles.lookup(); - try { - MethodHandle mh = lookup.findVirtual(BlockState.class, "setRawData", - MethodType.methodType(void.class, byte.class)); - assert mh != null; - setRawDataMethod = mh; - mh = lookup.findVirtual(FallingBlock.class, "getBlockData", - MethodType.methodType(byte.class)); - assert mh != null; - getBlockDataMethod = mh; - mh = lookup.findVirtual(Block.class, "setData", - MethodType.methodType(void.class, byte.class)); - assert mh != null; - setDataMethod = mh; - } catch (NoSuchMethodException | IllegalAccessException e) { - throw new Error(e); - } - } - - @SuppressWarnings({"deprecation"}) - private class MagicBlockValues extends BlockValues { - - private Material id; - short data; - private int itemFlags; - - public MagicBlockValues(BlockState block) { - this.id = ItemUtils.asItem(block.getType()); - this.data = block.getRawData(); // Some black magic here, please look away... - // We don't know whether block data 0 has been set explicitly - this.itemFlags = ItemFlags.CHANGED_DURABILITY; - } - - public MagicBlockValues(Material id, short data, int itemFlags) { - this.id = id; - this.data = data; - this.itemFlags = itemFlags; - } - - @Override - public boolean isDefault() { - return itemFlags == 0; // No tag or durability changes - } - - @Override - public boolean equals(@Nullable Object other) { - if (!(other instanceof MagicBlockValues)) - return false; - MagicBlockValues magic = (MagicBlockValues) other; - if (isDefault() || magic.isDefault()) { - return id == magic.id; // Compare only ids, other has not specified data constraints - } else { // Compare ids and data - return id == magic.id && data == magic.data; - } - } - - @Override - public int hashCode() { - // FindBugs reports "Scariest" bug when done with just ordinal << 8 | data - // byte -> int widening seems to be a bit weird in Java - return (id.ordinal() << 8) | (data & 0xff); - } - - @Override - public MatchQuality match(BlockValues other) { - if (!(other instanceof MagicBlockValues)) { - throw new IllegalArgumentException("wrong block compat"); - } - MagicBlockValues magic = (MagicBlockValues) other; - if (id == magic.id) { - if (data == magic.data) { - return MatchQuality.EXACT; - } else { - if ((magic.itemFlags & ItemFlags.CHANGED_DURABILITY) == 0) { - return MatchQuality.SAME_ITEM; // Other doesn't care about durability - } else { - return MatchQuality.SAME_MATERIAL; - } - } - } else { - return MatchQuality.DIFFERENT; - } - } - } - - private static class MagicBlockSetter implements BlockSetter { - - public MagicBlockSetter() {} - - @Override - public void setBlock(Block block, Material type, @Nullable BlockValues values, int flags) { - block.setType(type); - - if (values != null) { - MagicBlockValues ourValues = (MagicBlockValues) values; - try { - setDataMethod.invokeExact(block, (byte) ourValues.data); - } catch (Throwable e) { - Skript.exception(e); - } - } - } - - @Override - public void sendBlockChange(Player player, Location location, Material type, @Nullable BlockValues values) { - byte data = values != null ? (byte) ((MagicBlockValues) values).data : 0; - player.sendBlockChange(location, type, data); - } - } - - @Override - public BlockValues getBlockValues(BlockState block) { - return new MagicBlockValues(block); - } - - @SuppressWarnings("deprecation") - @Override - public BlockState fallingBlockToState(FallingBlock entity) { - BlockState state = entity.getWorld().getBlockAt(0, 0, 0).getState(); - state.setType(entity.getMaterial()); - try { - setRawDataMethod.invokeExact(state, (byte) getBlockDataMethod.invokeExact(entity)); - } catch (Throwable e) { - Skript.exception(e); - } - return state; - } - - @Nullable - @Override - public BlockValues createBlockValues(Material type, Map<String, String> states, @Nullable ItemStack item, int itemFlags) { - short damage = 0; - if (item != null) { - damage = (short) ItemUtils.getDamage(item); - } - return new MagicBlockValues(type, damage, itemFlags); - } - - @Override - public boolean isEmpty(Material type) { - return type == Material.AIR; - } - - @Override - public boolean isLiquid(Material type) { - // TODO moving water and lava - return type == Material.WATER || type == Material.LAVA; - } - - @Override - @Nullable - public BlockValues getBlockValues(ItemStack stack) { - short data = (short) ItemUtils.getDamage(stack); - return new MagicBlockValues(stack.getType(), data, ItemFlags.CHANGED_DURABILITY | ItemFlags.CHANGED_TAGS); - } - - @Override - public BlockSetter getSetter() { - return new MagicBlockSetter(); - } - -} 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 349b342851c..60c0344659b 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -284,78 +284,76 @@ public Block deserialize(final String s) { } } })); - - if (Skript.classExists("org.bukkit.block.data.BlockData")) { - Classes.registerClass(new ClassInfo<>(BlockData.class, "blockdata") - .user("block ?datas?") - .name("Block Data") - .description("Block data is the detailed information about a block, referred to in Minecraft as BlockStates, " + - "allowing for the manipulation of different aspects of the block, including shape, waterlogging, direction the block is facing, " + - "and so much more. Information regarding each block's optional data can be found on Minecraft's Wiki. Find the block you're " + - "looking for and scroll down to 'Block States'. Different states must be separated by a semicolon (see examples). " + - "The 'minecraft:' namespace is optional, as well as are underscores.") - .examples("set block at player to campfire[lit=false]", - "set target block of player to oak stairs[facing=north;waterlogged=true]", - "set block at player to grass_block[snowy=true]", - "set loop-block to minecraft:chest[facing=north]", - "set block above player to oak_log[axis=y]", - "set target block of player to minecraft:oak_leaves[distance=2;persistent=false]") - .after("itemtype") - .requiredPlugins("Minecraft 1.13+") - .since("2.5") - .parser(new Parser<BlockData>() { - @Nullable - @Override - public BlockData parse(String s, ParseContext context) { - return BlockUtils.createBlockData(s); - } - - @Override - public String toString(BlockData o, int flags) { - return o.getAsString().replace(",", ";"); - } - - @Override - public String toVariableNameString(BlockData o) { - return "blockdata:" + o.getAsString(); - } - }) - .serializer(new Serializer<BlockData>() { - @Override - public Fields serialize(BlockData o) { - Fields f = new Fields(); - f.putObject("blockdata", o.getAsString()); - return f; - } - - @Override - public void deserialize(BlockData o, Fields f) { - assert false; - } - - @Override - protected BlockData deserialize(Fields f) throws StreamCorruptedException { - String data = f.getObject("blockdata", String.class); - assert data != null; - try { - return Bukkit.createBlockData(data); - } catch (IllegalArgumentException ex) { - throw new StreamCorruptedException("Invalid block data: " + data); - } - } - - @Override - public boolean mustSyncDeserialization() { - return true; - } - - @Override - protected boolean canBeInstantiated() { - return false; + + Classes.registerClass(new ClassInfo<>(BlockData.class, "blockdata") + .user("block ?datas?") + .name("Block Data") + .description("Block data is the detailed information about a block, referred to in Minecraft as BlockStates, " + + "allowing for the manipulation of different aspects of the block, including shape, waterlogging, direction the block is facing, " + + "and so much more. Information regarding each block's optional data can be found on Minecraft's Wiki. Find the block you're " + + "looking for and scroll down to 'Block States'. Different states must be separated by a semicolon (see examples). " + + "The 'minecraft:' namespace is optional, as well as are underscores.") + .examples("set block at player to campfire[lit=false]", + "set target block of player to oak stairs[facing=north;waterlogged=true]", + "set block at player to grass_block[snowy=true]", + "set loop-block to minecraft:chest[facing=north]", + "set block above player to oak_log[axis=y]", + "set target block of player to minecraft:oak_leaves[distance=2;persistent=false]") + .after("itemtype") + .requiredPlugins("Minecraft 1.13+") + .since("2.5") + .parser(new Parser<BlockData>() { + @Nullable + @Override + public BlockData parse(String s, ParseContext context) { + return BlockUtils.createBlockData(s); + } + + @Override + public String toString(BlockData o, int flags) { + return o.getAsString().replace(",", ";"); + } + + @Override + public String toVariableNameString(BlockData o) { + return "blockdata:" + o.getAsString(); + } + }) + .serializer(new Serializer<BlockData>() { + @Override + public Fields serialize(BlockData o) { + Fields f = new Fields(); + f.putObject("blockdata", o.getAsString()); + return f; + } + + @Override + public void deserialize(BlockData o, Fields f) { + assert false; + } + + @Override + protected BlockData deserialize(Fields f) throws StreamCorruptedException { + String data = f.getObject("blockdata", String.class); + assert data != null; + try { + return Bukkit.createBlockData(data); + } catch (IllegalArgumentException ex) { + throw new StreamCorruptedException("Invalid block data: " + data); } - })); - } - + } + + @Override + public boolean mustSyncDeserialization() { + return true; + } + + @Override + protected boolean canBeInstantiated() { + return false; + } + })); + Classes.registerClass(new ClassInfo<>(Location.class, "location") .user("locations?") .name("Location") @@ -1627,37 +1625,36 @@ public String toVariableNameString(Status state) { } }) .serializer(new EnumSerializer<>(Status.class))); - - if (Skript.classExists("org.bukkit.SoundCategory")) { - EnumUtils<SoundCategory> soundCategories = new EnumUtils<>(SoundCategory.class, "sound categories"); - Classes.registerClass(new ClassInfo<>(SoundCategory.class, "soundcategory") - .user("sound ?categor(y|ies)") - .name("Sound Category") - .description("The category of a sound, they are used for sound options of Minecraft. " + - "See the <a href='effects.html#EffPlaySound'>play sound</a> and <a href='effects.html#EffStopSound'>stop sound</a> effects.") - .usage(soundCategories.getAllNames()) - .since("2.4") - .requiredPlugins("Minecraft 1.11 or newer") - .parser(new Parser<SoundCategory>() { - @Override - @Nullable - public SoundCategory parse(final String s, final ParseContext context) { - return soundCategories.parse(s); - } - - @Override - public String toString(SoundCategory state, int flags) { - return soundCategories.toString(state, flags); - } - - @SuppressWarnings("null") - @Override - public String toVariableNameString(SoundCategory category) { - return category.name(); - } - }) - .serializer(new EnumSerializer<>(SoundCategory.class))); - } + + EnumUtils<SoundCategory> soundCategories = new EnumUtils<>(SoundCategory.class, "sound categories"); + Classes.registerClass(new ClassInfo<>(SoundCategory.class, "soundcategory") + .user("sound ?categor(y|ies)") + .name("Sound Category") + .description("The category of a sound, they are used for sound options of Minecraft. " + + "See the <a href='effects.html#EffPlaySound'>play sound</a> and <a href='effects.html#EffStopSound'>stop sound</a> effects.") + .usage(soundCategories.getAllNames()) + .since("2.4") + .requiredPlugins("Minecraft 1.11 or newer") + .parser(new Parser<SoundCategory>() { + @Override + @Nullable + public SoundCategory parse(final String s, final ParseContext context) { + return soundCategories.parse(s); + } + + @Override + public String toString(SoundCategory state, int flags) { + return soundCategories.toString(state, flags); + } + + @SuppressWarnings("null") + @Override + public String toVariableNameString(SoundCategory category) { + return category.name(); + } + }) + .serializer(new EnumSerializer<>(SoundCategory.class))); + if (Skript.classExists("org.bukkit.entity.Panda$Gene")) { EnumUtils<Gene> genes = new EnumUtils<>(Gene.class, "genes"); Classes.registerClass(new ClassInfo<>(Gene.class, "gene") @@ -1742,34 +1739,32 @@ public String toVariableNameString(Cat.Type race) { .serializer(new EnumSerializer<>(Cat.Type.class))); } - if (Skript.classExists("org.bukkit.GameRule")) { - Classes.registerClass(new ClassInfo<>(GameRule.class, "gamerule") - .user("gamerules?") - .name("Gamerule") - .description("A gamerule") - .usage(Arrays.stream(GameRule.values()).map(GameRule::getName).collect(Collectors.joining(", "))) - .since("2.5") - .requiredPlugins("Minecraft 1.13 or newer") - .parser(new Parser<GameRule>() { - @Override - @Nullable - public GameRule parse(final String input, final ParseContext context) { - return GameRule.getByName(input); - } - - @Override - public String toString(GameRule o, int flags) { - return o.getName(); - } - - @Override - public String toVariableNameString(GameRule o) { - return o.getName(); - } - }) - ); - } - + Classes.registerClass(new ClassInfo<>(GameRule.class, "gamerule") + .user("gamerules?") + .name("Gamerule") + .description("A gamerule") + .usage(Arrays.stream(GameRule.values()).map(GameRule::getName).collect(Collectors.joining(", "))) + .since("2.5") + .requiredPlugins("Minecraft 1.13 or newer") + .parser(new Parser<GameRule>() { + @Override + @Nullable + public GameRule parse(final String input, final ParseContext context) { + return GameRule.getByName(input); + } + + @Override + public String toString(GameRule o, int flags) { + return o.getName(); + } + + @Override + public String toVariableNameString(GameRule o) { + return o.getName(); + } + }) + ); + // Temporarily disabled until bugs are fixed // if (Skript.classExists("org.bukkit.persistence.PersistentDataHolder")) { // Classes.registerClass(new ClassInfo<>(PersistentDataHolder.class, "persistentdataholder") @@ -1789,32 +1784,31 @@ public String toVariableNameString(GameRule o) { // .since("2.5")); // } - if (Skript.classExists("org.bukkit.enchantments.EnchantmentOffer")) { - Classes.registerClass(new ClassInfo<>(EnchantmentOffer.class, "enchantmentoffer") - .user("enchant[ment][ ]offers?") - .name("Enchantment Offer") - .description("The enchantmentoffer in an enchant prepare event.") - .examples("on enchant prepare:", - "\tset enchant offer 1 to sharpness 1", - "\tset the cost of enchant offer 1 to 10 levels") - .since("2.5") - .parser(new Parser<EnchantmentOffer>() { - @Override - public boolean canParse(ParseContext context) { - return false; - } + Classes.registerClass(new ClassInfo<>(EnchantmentOffer.class, "enchantmentoffer") + .user("enchant[ment][ ]offers?") + .name("Enchantment Offer") + .description("The enchantmentoffer in an enchant prepare event.") + .examples("on enchant prepare:", + "\tset enchant offer 1 to sharpness 1", + "\tset the cost of enchant offer 1 to 10 levels") + .since("2.5") + .parser(new Parser<EnchantmentOffer>() { + @Override + public boolean canParse(ParseContext context) { + return false; + } + + @Override + public String toString(EnchantmentOffer eo, int flags) { + return EnchantmentType.toString(eo.getEnchantment(), flags) + " " + eo.getEnchantmentLevel(); + } + + @Override + public String toVariableNameString(EnchantmentOffer eo) { + return "offer:" + EnchantmentType.toString(eo.getEnchantment()) + "=" + eo.getEnchantmentLevel(); + } + })); - @Override - public String toString(EnchantmentOffer eo, int flags) { - return EnchantmentType.toString(eo.getEnchantment(), flags) + " " + eo.getEnchantmentLevel(); - } - - @Override - public String toVariableNameString(EnchantmentOffer eo) { - return "offer:" + EnchantmentType.toString(eo.getEnchantment()) + "=" + eo.getEnchantmentLevel(); - } - })); - } EnumUtils<Attribute> attributes = new EnumUtils<>(Attribute.class, "attribute types"); Classes.registerClass(new ClassInfo<>(Attribute.class, "attributetype") .user("attribute ?types?") 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 54d1a69d818..c0cdc61f72d 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -151,9 +151,7 @@ public final class BukkitEventValues { public BukkitEventValues() {} - - private static final boolean offHandSupport = Skript.isRunningMinecraft(1, 9); - private static final boolean NAMESPACE_SUPPORT = Skript.classExists("org.bukkit.NamespacedKey"); + private static final ItemStack AIR_IS = new ItemStack(Material.AIR); static { @@ -542,15 +540,13 @@ public Block get(final EntityChangeBlockEvent e) { return e.getBlock(); } }, 0); - if (Skript.classExists("org.bukkit.event.entity.AreaEffectCloudApplyEvent")) { - EventValues.registerEventValue(AreaEffectCloudApplyEvent.class, PotionEffectType.class, new Getter<PotionEffectType, AreaEffectCloudApplyEvent>() { - @Override - @Nullable - public PotionEffectType get(AreaEffectCloudApplyEvent e) { - return e.getEntity().getBasePotionData().getType().getEffectType(); // Whoops this is a bit long call... - } - }, 0); - } + EventValues.registerEventValue(AreaEffectCloudApplyEvent.class, PotionEffectType.class, new Getter<PotionEffectType, AreaEffectCloudApplyEvent>() { + @Override + @Nullable + public PotionEffectType get(AreaEffectCloudApplyEvent e) { + return e.getEntity().getBasePotionData().getType().getEffectType(); // Whoops this is a bit long call... + } + }, 0); // ItemSpawnEvent EventValues.registerEventValue(ItemSpawnEvent.class, ItemStack.class, new Getter<ItemStack, ItemSpawnEvent>() { @Override @@ -678,48 +674,42 @@ public ItemStack get(final PlayerPickupItemEvent e) { } }, 0); // EntityPickupItemEvent - if (Skript.classExists("org.bukkit.event.entity.EntityPickupItemEvent")) { - EventValues.registerEventValue(EntityPickupItemEvent.class, Entity.class, new Getter<Entity, EntityPickupItemEvent>() { - @Override - public @Nullable Entity get(EntityPickupItemEvent e) { - return e.getEntity(); - } - }, 0); - EventValues.registerEventValue(EntityPickupItemEvent.class, Item.class, new Getter<Item, EntityPickupItemEvent>() { - @Override - @Nullable - public Item get(final EntityPickupItemEvent e) { - return e.getItem(); - } - }, 0); - EventValues.registerEventValue(EntityPickupItemEvent.class, ItemType.class, new Getter<ItemType, EntityPickupItemEvent>() { - @Override - @Nullable - public ItemType get(final EntityPickupItemEvent e) { - return new ItemType(e.getItem().getItemStack()); - } - }, 0); - } + EventValues.registerEventValue(EntityPickupItemEvent.class, Entity.class, new Getter<Entity, EntityPickupItemEvent>() { + @Override + public @Nullable Entity get(EntityPickupItemEvent e) { + return e.getEntity(); + } + }, 0); + EventValues.registerEventValue(EntityPickupItemEvent.class, Item.class, new Getter<Item, EntityPickupItemEvent>() { + @Override + @Nullable + public Item get(final EntityPickupItemEvent e) { + return e.getItem(); + } + }, 0); + EventValues.registerEventValue(EntityPickupItemEvent.class, ItemType.class, new Getter<ItemType, EntityPickupItemEvent>() { + @Override + @Nullable + public ItemType get(final EntityPickupItemEvent e) { + return new ItemType(e.getItem().getItemStack()); + } + }, 0); // PlayerItemConsumeEvent - if (Skript.supports("org.bukkit.event.player.PlayerItemConsumeEvent")) { - EventValues.registerEventValue(PlayerItemConsumeEvent.class, ItemStack.class, new Getter<ItemStack, PlayerItemConsumeEvent>() { - @Override - @Nullable - public ItemStack get(final PlayerItemConsumeEvent e) { - return e.getItem(); - } - }, 0); - } + EventValues.registerEventValue(PlayerItemConsumeEvent.class, ItemStack.class, new Getter<ItemStack, PlayerItemConsumeEvent>() { + @Override + @Nullable + public ItemStack get(final PlayerItemConsumeEvent e) { + return e.getItem(); + } + }, 0); // PlayerItemBreakEvent - if (Skript.supports("org.bukkit.event.player.PlayerItemBreakEvent")) { - EventValues.registerEventValue(PlayerItemBreakEvent.class, ItemStack.class, new Getter<ItemStack, PlayerItemBreakEvent>() { - @Override - @Nullable - public ItemStack get(final PlayerItemBreakEvent e) { - return e.getBrokenItem(); - } - }, 0); - } + EventValues.registerEventValue(PlayerItemBreakEvent.class, ItemStack.class, new Getter<ItemStack, PlayerItemBreakEvent>() { + @Override + @Nullable + public ItemStack get(final PlayerItemBreakEvent e) { + return e.getBrokenItem(); + } + }, 0); // PlayerInteractEntityEvent EventValues.registerEventValue(PlayerInteractEntityEvent.class, Entity.class, new Getter<Entity, PlayerInteractEntityEvent>() { @Override @@ -732,17 +722,13 @@ public Entity get(final PlayerInteractEntityEvent e) { @Override @Nullable public ItemStack get(final PlayerInteractEntityEvent e) { - if (offHandSupport) { - EquipmentSlot hand = e.getHand(); - if (hand == EquipmentSlot.HAND) - return e.getPlayer().getInventory().getItemInMainHand(); - else if (hand == EquipmentSlot.OFF_HAND) - return e.getPlayer().getInventory().getItemInOffHand(); - else - return null; - } else { - return e.getPlayer().getItemInHand(); - } + EquipmentSlot hand = e.getHand(); + if (hand == EquipmentSlot.HAND) + return e.getPlayer().getInventory().getItemInMainHand(); + else if (hand == EquipmentSlot.OFF_HAND) + return e.getPlayer().getInventory().getItemInOffHand(); + else + return null; } }, 0); // PlayerInteractEvent @@ -791,30 +777,28 @@ public ItemStack get(PlayerItemDamageEvent event) { } }, 0); //PlayerItemMendEvent - if (Skript.classExists("org.bukkit.event.player.PlayerItemMendEvent")) { - EventValues.registerEventValue(PlayerItemMendEvent.class, Player.class, new Getter<Player, PlayerItemMendEvent>() { - @Override - @Nullable - public Player get(PlayerItemMendEvent e) { - return e.getPlayer(); - } - }, 0); - EventValues.registerEventValue(PlayerItemMendEvent.class, ItemStack.class, new Getter<ItemStack, PlayerItemMendEvent>() { - @Override - @Nullable - public ItemStack get(PlayerItemMendEvent e) { - return e.getItem(); - } - }, 0); - EventValues.registerEventValue(PlayerItemMendEvent.class, Entity.class, new Getter<Entity, PlayerItemMendEvent>() { - @Override - @Nullable - public Entity get(PlayerItemMendEvent e) { - return e.getExperienceOrb(); - } - }, 0); - } - + EventValues.registerEventValue(PlayerItemMendEvent.class, Player.class, new Getter<Player, PlayerItemMendEvent>() { + @Override + @Nullable + public Player get(PlayerItemMendEvent e) { + return e.getPlayer(); + } + }, 0); + EventValues.registerEventValue(PlayerItemMendEvent.class, ItemStack.class, new Getter<ItemStack, PlayerItemMendEvent>() { + @Override + @Nullable + public ItemStack get(PlayerItemMendEvent e) { + return e.getItem(); + } + }, 0); + EventValues.registerEventValue(PlayerItemMendEvent.class, Entity.class, new Getter<Entity, PlayerItemMendEvent>() { + @Override + @Nullable + public Entity get(PlayerItemMendEvent e) { + return e.getExperienceOrb(); + } + }, 0); + // --- HangingEvents --- // Note: will not work in HangingEntityBreakEvent due to event-entity being parsed as HangingBreakByEntityEvent#getRemover() from code down below @@ -1028,15 +1012,13 @@ public Inventory get(final InventoryClickEvent e) { } }, 0); //BlockFertilizeEvent - if(Skript.classExists("org.bukkit.event.block.BlockFertilizeEvent")) { - EventValues.registerEventValue(BlockFertilizeEvent.class, Player.class, new Getter<Player, BlockFertilizeEvent>() { - @Nullable - @Override - public Player get(BlockFertilizeEvent event) { - return event.getPlayer(); - } - }, 0); - } + EventValues.registerEventValue(BlockFertilizeEvent.class, Player.class, new Getter<Player, BlockFertilizeEvent>() { + @Nullable + @Override + public Player get(BlockFertilizeEvent event) { + return event.getPlayer(); + } + }, 0); // PrepareItemCraftEvent EventValues.registerEventValue(PrepareItemCraftEvent.class, Slot.class, new Getter<Slot, PrepareItemCraftEvent>() { @Override @@ -1071,28 +1053,26 @@ public Player get(final PrepareItemCraftEvent e) { } }, 0); // CraftEvents - recipe namespaced key strings - if (NAMESPACE_SUPPORT) { - EventValues.registerEventValue(CraftItemEvent.class, String.class, new Getter<String, CraftItemEvent>() { - @Nullable - @Override - public String get(CraftItemEvent e) { - Recipe recipe = e.getRecipe(); - if (recipe instanceof Keyed) - return ((Keyed) recipe).getKey().toString(); - return null; - } - }, 0); - EventValues.registerEventValue(PrepareItemCraftEvent.class, String.class, new Getter<String, PrepareItemCraftEvent>() { - @Nullable - @Override - public String get(PrepareItemCraftEvent e) { - Recipe recipe = e.getRecipe(); - if (recipe instanceof Keyed) - return ((Keyed) recipe).getKey().toString(); - return null; - } - }, 0); - } + EventValues.registerEventValue(CraftItemEvent.class, String.class, new Getter<String, CraftItemEvent>() { + @Nullable + @Override + public String get(CraftItemEvent e) { + Recipe recipe = e.getRecipe(); + if (recipe instanceof Keyed) + return ((Keyed) recipe).getKey().toString(); + return null; + } + }, 0); + EventValues.registerEventValue(PrepareItemCraftEvent.class, String.class, new Getter<String, PrepareItemCraftEvent>() { + @Nullable + @Override + public String get(PrepareItemCraftEvent e) { + Recipe recipe = e.getRecipe(); + if (recipe instanceof Keyed) + return ((Keyed) recipe).getKey().toString(); + return null; + } + }, 0); // CraftItemEvent EventValues.registerEventValue(CraftItemEvent.class, ItemStack.class, new Getter<ItemStack, CraftItemEvent>() { @Override @@ -1248,34 +1228,30 @@ public SpawnReason get(CreatureSpawnEvent e) { } }, 0); //FireworkExplodeEvent - if (Skript.classExists("org.bukkit.event.entity.FireworkExplodeEvent")) { - EventValues.registerEventValue(FireworkExplodeEvent.class, Firework.class, new Getter<Firework, FireworkExplodeEvent>() { - @Override - @Nullable - public Firework get(FireworkExplodeEvent e) { - return e.getEntity(); - } - }, 0); - EventValues.registerEventValue(FireworkExplodeEvent.class, FireworkEffect.class, new Getter<FireworkEffect, FireworkExplodeEvent>() { - @Override - @Nullable - public FireworkEffect get(FireworkExplodeEvent e) { - List<FireworkEffect> effects = e.getEntity().getFireworkMeta().getEffects(); - if (effects.size() == 0) - return null; - return effects.get(0); - } - }, 0); - } + EventValues.registerEventValue(FireworkExplodeEvent.class, Firework.class, new Getter<Firework, FireworkExplodeEvent>() { + @Override + @Nullable + public Firework get(FireworkExplodeEvent e) { + return e.getEntity(); + } + }, 0); + EventValues.registerEventValue(FireworkExplodeEvent.class, FireworkEffect.class, new Getter<FireworkEffect, FireworkExplodeEvent>() { + @Override + @Nullable + public FireworkEffect get(FireworkExplodeEvent e) { + List<FireworkEffect> effects = e.getEntity().getFireworkMeta().getEffects(); + if (effects.size() == 0) + return null; + return effects.get(0); + } + }, 0); //PlayerRiptideEvent - if (Skript.classExists("org.bukkit.event.player.PlayerRiptideEvent")) { - EventValues.registerEventValue(PlayerRiptideEvent.class, ItemStack.class, new Getter<ItemStack, PlayerRiptideEvent>() { - @Override - public ItemStack get(PlayerRiptideEvent e) { - return e.getItem(); - } - }, 0); - } + EventValues.registerEventValue(PlayerRiptideEvent.class, ItemStack.class, new Getter<ItemStack, PlayerRiptideEvent>() { + @Override + public ItemStack get(PlayerRiptideEvent e) { + return e.getItem(); + } + }, 0); //PlayerArmorChangeEvent if (Skript.classExists("com.destroystokyo.paper.event.player.PlayerArmorChangeEvent")) { EventValues.registerEventValue(PlayerArmorChangeEvent.class, ItemStack.class, new Getter<ItemStack, PlayerArmorChangeEvent>() { diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultChangers.java b/src/main/java/ch/njol/skript/classes/data/DefaultChangers.java index 891967a8c5d..4a3d64fb72a 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultChangers.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultChangers.java @@ -286,10 +286,7 @@ public Class<?>[] acceptChange(final ChangeMode mode) { if (mode == ChangeMode.RESET) return null; // REMIND regenerate? if (mode == ChangeMode.SET) - if (Skript.classExists("org.bukkit.block.data.BlockData")) - return CollectionUtils.array(ItemType.class, BlockData.class); - else - return CollectionUtils.array(ItemType.class); + return CollectionUtils.array(ItemType.class, BlockData.class); return CollectionUtils.array(ItemType[].class, Inventory[].class); } diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java b/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java index b3f2898ffd0..d949ae2d5ff 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java @@ -207,20 +207,18 @@ public boolean supportsOrdering() { }); // Block - BlockData - if (Skript.classExists("org.bukkit.block.data.BlockData")) { - Comparators.registerComparator(Block.class, BlockData.class, new Comparator<Block, BlockData>() { - @Override - public Relation compare(Block block, BlockData data) { - return Relation.get(block.getBlockData().matches(data)); - } + Comparators.registerComparator(Block.class, BlockData.class, new Comparator<Block, BlockData>() { + @Override + public Relation compare(Block block, BlockData data) { + return Relation.get(block.getBlockData().matches(data)); + } + + @Override + public boolean supportsOrdering() { + return false; + } + }); - @Override - public boolean supportsOrdering() { - return false; - } - }); - } - // ItemType - ItemType Comparators.registerComparator(ItemType.class, ItemType.class, new Comparator<ItemType, ItemType>() { @Override @@ -494,10 +492,8 @@ public Relation compare(DamageCause dc, ItemType t) { return Relation.get(t.equals(lava)); case MAGIC: return Relation.get(t.isOfType(Material.POTION)); - } - if (Skript.fieldExists(DamageCause.class, "HOT_FLOOR") - && dc.equals(DamageCause.HOT_FLOOR)) { - return Relation.get(t.isOfType(Material.MAGMA_BLOCK)); + case HOT_FLOOR: + return Relation.get(t.isOfType(Material.MAGMA_BLOCK)); } return Relation.NOT_EQUAL; @@ -573,31 +569,29 @@ public boolean supportsOrdering() { }); // EnchantmentOffer Comparators - if (Skript.isRunningMinecraft(1, 11)) { - // EnchantmentOffer - EnchantmentType - Comparators.registerComparator(EnchantmentOffer.class, EnchantmentType.class, new Comparator<EnchantmentOffer, EnchantmentType>() { - @Override - public Relation compare(EnchantmentOffer eo, EnchantmentType et) { - return Relation.get(eo.getEnchantment() == et.getType() && eo.getEnchantmentLevel() == et.getLevel()); - } + // EnchantmentOffer - EnchantmentType + Comparators.registerComparator(EnchantmentOffer.class, EnchantmentType.class, new Comparator<EnchantmentOffer, EnchantmentType>() { + @Override + public Relation compare(EnchantmentOffer eo, EnchantmentType et) { + return Relation.get(eo.getEnchantment() == et.getType() && eo.getEnchantmentLevel() == et.getLevel()); + } - @Override - public boolean supportsOrdering() { - return false; - } - }); - // EnchantmentOffer - Experience - Comparators.registerComparator(EnchantmentOffer.class, Experience.class, new Comparator<EnchantmentOffer, Experience>() { - @Override - public Relation compare(EnchantmentOffer eo, Experience exp) { - return Relation.get(eo.getCost() == exp.getXP()); - } + @Override + public boolean supportsOrdering() { + return false; + } + }); + // EnchantmentOffer - Experience + Comparators.registerComparator(EnchantmentOffer.class, Experience.class, new Comparator<EnchantmentOffer, Experience>() { + @Override + public Relation compare(EnchantmentOffer eo, Experience exp) { + return Relation.get(eo.getCost() == exp.getXP()); + } - @Override public boolean supportsOrdering() { - return false; - } - }); - } + @Override public boolean supportsOrdering() { + return false; + } + }); Comparators.registerComparator(Inventory.class, InventoryType.class, new Comparator<Inventory, InventoryType>() { @Override diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java b/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java index 5f9fe212dbd..305c7db0b04 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java @@ -350,16 +350,14 @@ public Direction convert(Vector vector) { }); // EnchantmentOffer Converters - if (Skript.isRunningMinecraft(1, 11)) { - // EnchantmentOffer - EnchantmentType - Converters.registerConverter(EnchantmentOffer.class, EnchantmentType.class, new Converter<EnchantmentOffer, EnchantmentType>() { - @Nullable - @Override - public EnchantmentType convert(EnchantmentOffer eo) { - return new EnchantmentType(eo.getEnchantment(), eo.getEnchantmentLevel()); - } - }); - } + // EnchantmentOffer - EnchantmentType + Converters.registerConverter(EnchantmentOffer.class, EnchantmentType.class, new Converter<EnchantmentOffer, EnchantmentType>() { + @Nullable + @Override + public EnchantmentType convert(EnchantmentOffer eo) { + return new EnchantmentType(eo.getEnchantment(), eo.getEnchantmentLevel()); + } + }); Converters.registerConverter(String.class, World.class, Bukkit::getWorld); } diff --git a/src/main/java/ch/njol/skript/command/Commands.java b/src/main/java/ch/njol/skript/command/Commands.java index 7d1935202dd..84e229bea84 100644 --- a/src/main/java/ch/njol/skript/command/Commands.java +++ b/src/main/java/ch/njol/skript/command/Commands.java @@ -47,7 +47,6 @@ import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.AsyncPlayerChatEvent; -import org.bukkit.event.player.PlayerChatEvent; import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.event.server.ServerCommandEvent; import org.bukkit.help.HelpMap; @@ -69,7 +68,6 @@ import ch.njol.skript.lang.VariableString; import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.skript.localization.ArgsMessage; -import ch.njol.skript.localization.Language; import ch.njol.skript.localization.Message; import ch.njol.skript.log.RetainingLogHandler; import ch.njol.skript.log.SkriptLogger; @@ -78,7 +76,6 @@ import ch.njol.skript.util.Timespan; import ch.njol.skript.util.Utils; import ch.njol.skript.variables.Variables; -import ch.njol.util.Callback; import ch.njol.util.NonNullPair; import ch.njol.util.StringUtils; @@ -192,49 +189,7 @@ public void onServerCommand(final ServerCommandEvent e) { } } }; - - - @Nullable - private final static Listener pre1_3chatListener = Skript.classExists("org.bukkit.event.player.AsyncPlayerChatEvent") ? null : new Listener() { - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void onPlayerChat(final PlayerChatEvent e) { - if (!SkriptConfig.enableEffectCommands.value() || !e.getMessage().startsWith(SkriptConfig.effectCommandToken.value())) - return; - if (handleEffectCommand(e.getPlayer(), e.getMessage())) - e.setCancelled(true); - } - }; - @Nullable - private final static Listener post1_3chatListener = !Skript.classExists("org.bukkit.event.player.AsyncPlayerChatEvent") ? null : new Listener() { - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void onPlayerChat(final AsyncPlayerChatEvent e) { - if (!SkriptConfig.enableEffectCommands.value() || !e.getMessage().startsWith(SkriptConfig.effectCommandToken.value())) - return; - if (!e.isAsynchronous()) { - if (handleEffectCommand(e.getPlayer(), e.getMessage())) - e.setCancelled(true); - } else { - final Future<Boolean> f = Bukkit.getScheduler().callSyncMethod(Skript.getInstance(), new Callable<Boolean>() { - @Override - public Boolean call() throws Exception { - return handleEffectCommand(e.getPlayer(), e.getMessage()); - } - }); - try { - while (true) { - try { - if (f.get()) - e.setCancelled(true); - break; - } catch (final InterruptedException e1) {} - } - } catch (final ExecutionException e1) { - Skript.exception(e1); - } - } - } - }; - + /** * @param sender * @param command full command string without the slash @@ -559,15 +514,38 @@ public static int unregisterCommands(final File script) { public static void registerListeners() { if (!registeredListeners) { Bukkit.getPluginManager().registerEvents(commandListener, Skript.getInstance()); - - Listener post13Listener = post1_3chatListener; - Listener pre13Listener = pre1_3chatListener; - if (post13Listener != null) { - Bukkit.getPluginManager().registerEvents(post13Listener, Skript.getInstance()); - } else { - assert pre13Listener != null; - Bukkit.getPluginManager().registerEvents(pre13Listener, Skript.getInstance()); - } + + Bukkit.getPluginManager().registerEvents(new Listener() { + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void onPlayerChat(final AsyncPlayerChatEvent e) { + if (!SkriptConfig.enableEffectCommands.value() || !e.getMessage().startsWith(SkriptConfig.effectCommandToken.value())) + return; + if (!e.isAsynchronous()) { + if (handleEffectCommand(e.getPlayer(), e.getMessage())) + e.setCancelled(true); + } else { + final Future<Boolean> f = Bukkit.getScheduler().callSyncMethod(Skript.getInstance(), new Callable<Boolean>() { + @Override + public Boolean call() throws Exception { + return handleEffectCommand(e.getPlayer(), e.getMessage()); + } + }); + try { + while (true) { + try { + if (f.get()) + e.setCancelled(true); + break; + } catch (final InterruptedException e1) { + } + } + } catch (final ExecutionException e1) { + Skript.exception(e1); + } + } + } + }, Skript.getInstance()); + registeredListeners = true; } } diff --git a/src/main/java/ch/njol/skript/conditions/CondHasScoreboardTag.java b/src/main/java/ch/njol/skript/conditions/CondHasScoreboardTag.java index 3b423495a96..80e855a7b2d 100644 --- a/src/main/java/ch/njol/skript/conditions/CondHasScoreboardTag.java +++ b/src/main/java/ch/njol/skript/conditions/CondHasScoreboardTag.java @@ -44,8 +44,7 @@ public class CondHasScoreboardTag extends Condition { static { - if (Skript.isRunningMinecraft(1, 11)) - PropertyCondition.register(CondHasScoreboardTag.class, PropertyType.HAVE, "[the] score[ ]board tag[s] %strings%", "entities"); + PropertyCondition.register(CondHasScoreboardTag.class, PropertyType.HAVE, "[the] score[ ]board tag[s] %strings%", "entities"); } @SuppressWarnings("null") diff --git a/src/main/java/ch/njol/skript/effects/EffToggle.java b/src/main/java/ch/njol/skript/effects/EffToggle.java index 6a6cd8a9a40..793a7795019 100644 --- a/src/main/java/ch/njol/skript/effects/EffToggle.java +++ b/src/main/java/ch/njol/skript/effects/EffToggle.java @@ -56,21 +56,7 @@ public class EffToggle extends Effect { static { Skript.registerEffect(EffToggle.class, "(close|turn off|de[-]activate) %blocks%", "(toggle|switch) [[the] state of] %blocks%", "(open|turn on|activate) %blocks%"); } - - @Nullable - private static final MethodHandle setDataMethod; - private static final boolean flattening = Skript.isRunningMinecraft(1, 13); - - static { - MethodHandle mh; - try { - mh = MethodHandles.lookup().findVirtual(Block.class, "setData", MethodType.methodType(void.class, byte.class)); - } catch (NoSuchMethodException | IllegalAccessException e) { - mh = null; - } - setDataMethod = mh; - } - + @SuppressWarnings("null") private Expression<Block> blocks; private int toggle; @@ -82,53 +68,9 @@ public boolean init(final Expression<?>[] vars, final int matchedPattern, final toggle = matchedPattern - 1; return true; } - - // Used for Minecraft 1.12 and older - private final static byte[] bitFlags = new byte[Skript.MAXBLOCKID + 1]; - private final static boolean[] doors = new boolean[Skript.MAXBLOCKID + 1]; - static { - bitFlags[28] = 0x8; // Detector rail - // Doors - bitFlags[64] = 0x4; // Oak door (block) - bitFlags[193] = 0x4; // Spruce door (block) - bitFlags[194] = 0x4; // Birch door (block) - bitFlags[195] = 0x4; // Jungle door (block) - bitFlags[196] = 0x4; // Acacia door (block) - bitFlags[197] = 0x4; // Dark oak door (block) - bitFlags[71] = 0x4; // Iron door (block) - // Redstone stuff - bitFlags[69] = 0x8; // Lever - bitFlags[70] = 0x1; // Stone pressure plate - bitFlags[72] = 0x1; // Wooden pressure plate - bitFlags[77] = 0x8; // Stone button - // Trapdoors - bitFlags[96] = 0x4; // Wooden trapdoor - bitFlags[167] = 0x4; // Iron trapdoor - // Fence gates - bitFlags[107] = 0x4; // Oak fence gate - bitFlags[183] = 0x4; // Spruce fence gate - bitFlags[184] = 0x4; // Birch fence gate - bitFlags[185] = 0x4; // Jungle fence gate - bitFlags[186] = 0x4; // Dark oak fence gate - bitFlags[187] = 0x4; // Acacia fence gate - - doors[64] = true; // Oak door (block) - doors[193] = true; // Spruce door (block) - doors[194] = true; // Birch door (block) - doors[195] = true; // Jungle door (block) - doors[196] = true; // Acacia door (block - doors[197] = true; // Dark oak door (block) - doors[71] = true; // Iron door (block) - } - + @Override protected void execute(final Event e) { - if (!flattening) { - executeLegacy(e); - return; - } - - // 1.13 and newer: use BlockData for (Block b : blocks.getArray(e)) { BlockData data = b.getBlockData(); if (toggle == -1) { @@ -151,39 +93,7 @@ else if (data instanceof Powerable) // power = NOT power b.setBlockData(data); } } - - /** - * Handles toggling blocks on 1.12 and older. - * @param e Event. - */ - private void executeLegacy(Event e) { - for (Block b : blocks.getArray(e)) { - int type = b.getType().getId(); - - byte data = b.getData(); - if (doors[type] == true && (data & 0x8) == 0x8) { - b = b.getRelative(BlockFace.DOWN); - type = b.getType().getId(); - if (doors[type] != true) - continue; - data = b.getData(); - } - - MethodHandle mh = setDataMethod; - assert mh != null; - try { - if (toggle == -1) - mh.invokeExact(b, (byte) (data & ~bitFlags[type])); - else if (toggle == 0) - mh.invokeExact(b, (byte) (data ^ bitFlags[type])); - else - mh.invokeExact(b, (byte) (data | bitFlags[type])); - } catch (Throwable ex) { - Skript.exception(ex); - } - } - } - + @Override public String toString(final @Nullable Event e, final boolean debug) { return "toggle " + blocks.toString(e, debug); diff --git a/src/main/java/ch/njol/skript/entity/EndermanData.java b/src/main/java/ch/njol/skript/entity/EndermanData.java index a29bca86760..cd8691fdded 100644 --- a/src/main/java/ch/njol/skript/entity/EndermanData.java +++ b/src/main/java/ch/njol/skript/entity/EndermanData.java @@ -44,13 +44,7 @@ public class EndermanData extends EntityData<Enderman> { static { EntityData.register(EndermanData.class, "enderman", Enderman.class, "enderman"); } - - /** - * Spigot 1.13 introduced new block data API, which must be used instead - * of the old one if targeting API version 1.13. - */ - static final boolean useBlockData = Skript.isRunningMinecraft(1, 13); - + @Nullable private ItemType[] hand = null; @@ -71,19 +65,11 @@ protected boolean init(final Literal<?>[] exprs, final int matchedPattern, final @Override protected boolean init(final @Nullable Class<? extends Enderman> c, final @Nullable Enderman e) { if (e != null) { - if (useBlockData) { - BlockData data = e.getCarriedBlock(); - if (data != null) { - Material type = data.getMaterial(); - assert type != null; - hand = new ItemType[] {new ItemType(type)}; - } - } else { - MaterialData m = e.getCarriedMaterial(); - final ItemStack i = m.toItemStack(1); - if (i == null) - return false; - hand = new ItemType[] {new ItemType(i)}; + BlockData data = e.getCarriedBlock(); + if (data != null) { + Material type = data.getMaterial(); + assert type != null; + hand = new ItemType[] {new ItemType(type)}; } } return true; @@ -96,13 +82,8 @@ public void set(final Enderman entity) { assert t != null; final ItemStack i = t.getBlock().getRandom(); if (i != null) { - if (useBlockData) { // 1.13: item->block usually keeps only material - entity.setCarriedBlock(Bukkit.createBlockData(i.getType())); - } else { - MaterialData data = i.getData(); - assert data != null; - entity.setCarriedMaterial(data); - } + // 1.13: item->block usually keeps only material + entity.setCarriedBlock(Bukkit.createBlockData(i.getType())); } } @@ -115,10 +96,7 @@ public boolean match(final Enderman entity) { @Override public boolean check(final @Nullable ItemType t) { // TODO {Block/Material}Data -> Material conversion is not 100% accurate, needs a better solution - if (useBlockData) - return t != null && t.isOfType(entity.getCarriedBlock().getMaterial()); - else - return t != null && t.isOfType(entity.getCarriedMaterial().getItemType()); + return t != null && t.isOfType(entity.getCarriedBlock().getMaterial()); } }, false, false); } diff --git a/src/main/java/ch/njol/skript/entity/EntityData.java b/src/main/java/ch/njol/skript/entity/EntityData.java index ef305776c08..81eb71be0fc 100644 --- a/src/main/java/ch/njol/skript/entity/EntityData.java +++ b/src/main/java/ch/njol/skript/entity/EntityData.java @@ -464,9 +464,9 @@ public static <E extends Entity> E[] getAll(final EntityData<?>[] types, final C assert types.length > 0; if (type == Player.class) { if (worlds == null && types.length == 1 && types[0] instanceof PlayerData && ((PlayerData) types[0]).op == 0) - return (E[]) PlayerUtils.getOnlinePlayers().toArray(new Player[0]); + return (E[]) Bukkit.getOnlinePlayers().toArray(new Player[0]); final List<Player> list = new ArrayList<>(); - for (final Player p : PlayerUtils.getOnlinePlayers()) { + for (final Player p : Bukkit.getOnlinePlayers()) { if (worlds != null && !CollectionUtils.contains(worlds, p.getWorld())) continue; for (final EntityData<?> t : types) { diff --git a/src/main/java/ch/njol/skript/entity/FishData.java b/src/main/java/ch/njol/skript/entity/FishData.java index ec199857d7d..2f06ff3e449 100644 --- a/src/main/java/ch/njol/skript/entity/FishData.java +++ b/src/main/java/ch/njol/skript/entity/FishData.java @@ -34,9 +34,8 @@ public class FishData extends EntityData<Fish> { static { - if (Skript.isRunningMinecraft(1, 13)) - register(FishData.class, "fish", Fish.class, 0, - "fish", "cod", "puffer fish", "salmon", "tropical fish"); + register(FishData.class, "fish", Fish.class, 0, + "fish", "cod", "puffer fish", "salmon", "tropical fish"); } private boolean init = true; diff --git a/src/main/java/ch/njol/skript/entity/GuardianData.java b/src/main/java/ch/njol/skript/entity/GuardianData.java deleted file mode 100644 index 98ec1e9f1d5..00000000000 --- a/src/main/java/ch/njol/skript/entity/GuardianData.java +++ /dev/null @@ -1,98 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.entity; - -import org.bukkit.entity.Guardian; - -import ch.njol.skript.Skript; -import ch.njol.skript.lang.Literal; -import ch.njol.skript.lang.SkriptParser.ParseResult; - - -public class GuardianData extends EntityData<Guardian> { - - static { - if(Skript.classExists("org.bukkit.entity.Guardian") && !Skript.isRunningMinecraft(1, 11)){ - EntityData.register(GuardianData.class, "guardian", Guardian.class, 1, "normal guardian", "guardian", "elder guardian"); - } - } - - - - private boolean isElder = false; - - @Override - protected boolean init(Literal<?>[] exprs, int matchedPattern, ParseResult parseResult) { - isElder = matchedPattern == 2; - return true; - } - - @SuppressWarnings({"null", "deprecation"}) - @Override - protected boolean init(Class<? extends Guardian> c, Guardian e) { - if(e != null) - isElder = e.isElder(); - return true; - } - - @SuppressWarnings("deprecation") - @Override - public void set(Guardian entity) { - if(isElder) - entity.setElder(true); - - } - - @SuppressWarnings("deprecation") - @Override - protected boolean match(Guardian entity) { - return entity.isElder() == isElder; - } - - @Override - public Class<? extends Guardian> getType() { - return Guardian.class; - } - - @Override - public EntityData getSuperType() { - return new GuardianData(); - } - - @Override - protected int hashCode_i() { - return isElder ? 1 : 0; - } - - @Override - protected boolean equals_i(EntityData<?> obj) { - if (!(obj instanceof GuardianData)) - return false; - final GuardianData other = (GuardianData) obj; - return other.isElder == isElder; - } - - @Override - public boolean isSupertypeOf(final EntityData<?> e) { - if (e instanceof GuardianData) - return ((GuardianData) e).isElder == isElder; - return false; - } - -} diff --git a/src/main/java/ch/njol/skript/entity/HorseData.java b/src/main/java/ch/njol/skript/entity/HorseData.java deleted file mode 100644 index 40ed2cd0fd4..00000000000 --- a/src/main/java/ch/njol/skript/entity/HorseData.java +++ /dev/null @@ -1,172 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.entity; - -import org.bukkit.entity.Horse; -import org.bukkit.entity.Horse.Color; -import org.bukkit.entity.Horse.Style; -import org.bukkit.entity.Horse.Variant; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.Skript; -import ch.njol.skript.lang.Literal; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.variables.Variables; - -/** - * @author Peter Güttinger - */ -@SuppressWarnings("deprecation") // Until 1.12: use old deprecated methods for backwards compatibility -public class HorseData extends EntityData<Horse> { - static { - if (Skript.classExists("org.bukkit.entity.Horse")) { - if (!Skript.isRunningMinecraft(1, 11)) // For 1.11+ see SimpleEntityData - EntityData.register(HorseData.class, "horse", Horse.class, 0, - "horse", "donkey", "mule", "undead horse", "skeleton horse"); - - Variables.yggdrasil.registerSingleClass(Variant.class, "Horse.Variant"); - Variables.yggdrasil.registerSingleClass(Color.class, "Horse.Color"); - Variables.yggdrasil.registerSingleClass(Style.class, "Horse.Style"); - } - } - - @Nullable - private Variant variant; - @Nullable - private Color color; - @Nullable - private Style style; - - public HorseData() {} - - public HorseData(final @Nullable Variant variant) { - this.variant = variant; - } - - @Override - protected boolean init(final Literal<?>[] exprs, final int matchedPattern, final ParseResult parseResult) { - switch (matchedPattern) { // If Variant ordering is changed, will not break - case 0: - variant = Variant.HORSE; - break; - case 1: - variant = Variant.DONKEY; - break; - case 2: - variant = Variant.MULE; - break; - case 3: - variant = Variant.UNDEAD_HORSE; - break; - case 4: - variant = Variant.SKELETON_HORSE; - break; - } - - return true; - } - - @Override - protected boolean init(final @Nullable Class<? extends Horse> c, final @Nullable Horse e) { - if (e != null) { - variant = e.getVariant(); - color = e.getColor(); - style = e.getStyle(); - } - return true; - } - - @Override - protected boolean match(final Horse entity) { - return (variant == null || variant == entity.getVariant()) - && (color == null || color == entity.getColor()) - && (style == null || style == entity.getStyle()); - } - - @Override - public EntityData getSuperType() { - return new HorseData(variant); - } - - @Override - public void set(final Horse entity) { - if (variant != null) - entity.setVariant(variant); - if (color != null) - entity.setColor(color); - if (style != null) - entity.setStyle(style); - } - - @Override - public boolean isSupertypeOf(final EntityData<?> e) { - if (!(e instanceof HorseData)) - return false; - final HorseData d = (HorseData) e; - return (variant == null || variant == d.variant) - && (color == null || color == d.color) - && (style == null || style == d.style); - } - - @Override - public Class<? extends Horse> getType() { - return Horse.class; - } - -// return (variant == null ? "" : variant.name()) + "," + (color == null ? "" : color.name()) + "," + (style == null ? "" : style.name()); - @Override - protected boolean deserialize(final String s) { - final String[] split = s.split(","); - if (split.length != 3) - return false; - try { - variant = split[0].isEmpty() ? null : Variant.valueOf(split[0]); - color = split[1].isEmpty() ? null : Color.valueOf(split[1]); - style = split[2].isEmpty() ? null : Style.valueOf(split[2]); - } catch (final IllegalArgumentException e) { - return false; - } - return true; - } - - @Override - protected int hashCode_i() { - final int prime = 31; - int result = 1; - result = prime * result + (color != null ? color.hashCode() : 0); - result = prime * result + (style != null ? style.hashCode() : 0); - result = prime * result + (variant != null ? variant.hashCode() : 0); - return result; - } - - @Override - protected boolean equals_i(final EntityData<?> obj) { - if (!(obj instanceof HorseData)) - return false; - final HorseData other = (HorseData) obj; - if (color != other.color) - return false; - if (style != other.style) - return false; - if (variant != other.variant) - return false; - return true; - } - -} diff --git a/src/main/java/ch/njol/skript/entity/SimpleEntityData.java b/src/main/java/ch/njol/skript/entity/SimpleEntityData.java index 819035c397e..7fb1fffaa20 100644 --- a/src/main/java/ch/njol/skript/entity/SimpleEntityData.java +++ b/src/main/java/ch/njol/skript/entity/SimpleEntityData.java @@ -177,12 +177,12 @@ public boolean equals(final @Nullable Object obj) { private final static List<SimpleEntityDataInfo> types = new ArrayList<>(); - private static void addSimpleEntity(String codeName, Class<? extends Entity> entityclass) { - types.add(new SimpleEntityDataInfo(codeName, entityclass)); + private static void addSimpleEntity(String codeName, Class<? extends Entity> entityClass) { + types.add(new SimpleEntityDataInfo(codeName, entityClass)); } - private static void addSuperEntity(String codeName, Class<? extends Entity> entityclass) { - types.add(new SimpleEntityDataInfo(codeName, entityclass, true)); + private static void addSuperEntity(String codeName, Class<? extends Entity> entityClass) { + types.add(new SimpleEntityDataInfo(codeName, entityClass, true)); } static { // Simple Entities @@ -255,9 +255,8 @@ private static void addSuperEntity(String codeName, Class<? extends Entity> enti addSimpleEntity("salmon", Salmon.class); addSimpleEntity("tropical fish", TropicalFish.class); addSimpleEntity("trident", Trident.class); - - if (Skript.classExists("org.bukkit.entity.Illusioner")) // Added in 1.12 - addSimpleEntity("illusioner", Illusioner.class); + + addSimpleEntity("illusioner", Illusioner.class); if (Skript.isRunningMinecraft(1, 14)) { addSimpleEntity("pillager", Pillager.class); diff --git a/src/main/java/ch/njol/skript/entity/SkeletonData.java b/src/main/java/ch/njol/skript/entity/SkeletonData.java deleted file mode 100644 index be3d4edd579..00000000000 --- a/src/main/java/ch/njol/skript/entity/SkeletonData.java +++ /dev/null @@ -1,159 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.entity; - -import org.bukkit.entity.Skeleton; -import org.bukkit.entity.Skeleton.SkeletonType; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.Skript; -import ch.njol.skript.SkriptAPIException; -import ch.njol.skript.lang.Literal; -import ch.njol.skript.lang.SkriptParser.ParseResult; - -/** - * @author Peter Güttinger - */ -@SuppressWarnings("deprecation") // Until 1.12: use deprecated methods for compatibility -public class SkeletonData extends EntityData<Skeleton> { - - private final static boolean hasWither = Skript.methodExists(Skeleton.class, "getSkeletonType"); - private final static boolean hasStray = Skript.isRunningMinecraft(1, 10); - private final static boolean separateClasses = Skript.isRunningMinecraft(1, 11); - - static { - // Register nothing on 1.11+ (see SimpleEntityData instead) - if (!separateClasses) { - if (hasStray) - EntityData.register(SkeletonData.class, "skeleton", Skeleton.class, 0, "skeleton", "wither skeleton", "stray"); - else if (hasWither) - EntityData.register(SkeletonData.class, "skeleton", Skeleton.class, 0, "skeleton", "wither skeleton"); - else - EntityData.register(SkeletonData.class, "skeleton", Skeleton.class, "skeleton"); - } - } - - private int type; - public static final int NORMAL = 0, WITHER = 1, STRAY = 2, LAST_INDEX = STRAY; - - public SkeletonData() {} - - public SkeletonData(final int type) { - if (type > LAST_INDEX) - throw new SkriptAPIException("Unsupported skeleton type " + type); - this.type = type; - } - - public boolean isWither() { - return type == WITHER; - } - - public boolean isStray() { - return type == STRAY; - } - - @Override - protected boolean init(final Literal<?>[] exprs, final int matchedPattern, final ParseResult parseResult) { - type = matchedPattern; - return true; - } - - @Override - protected boolean init(final @Nullable Class<? extends Skeleton> c, final @Nullable Skeleton e) { - if (e == null) - return true; - - if (hasWither && e.getSkeletonType() == SkeletonType.WITHER) - type = WITHER; - if (hasStray && e.getSkeletonType() == SkeletonType.STRAY) - type = STRAY; - return true; - } - -// return wither ? "1" : "0"; - @Override - protected boolean deserialize(final String s) { - try { - int typeOffer = Integer.parseInt(s); - if (typeOffer > LAST_INDEX) - throw new SkriptAPIException("Unsupported skeleton type " + s); - } catch (NumberFormatException e) { - throw new SkriptAPIException("Cannot parse skeleton type " + s); - } - - return true; - } - - @Override - public void set(final Skeleton e) { - switch (type) { - case WITHER: - e.setSkeletonType(SkeletonType.WITHER); - break; - case STRAY: - e.setSkeletonType(SkeletonType.STRAY); - break; - default: - e.setSkeletonType(SkeletonType.NORMAL); - } - } - - @Override - protected boolean match(final Skeleton e) { - switch (type) { - case WITHER: - return e.getSkeletonType() == SkeletonType.WITHER; - case STRAY: - return e.getSkeletonType() == SkeletonType.STRAY; - default: - return e.getSkeletonType() == SkeletonType.NORMAL; - } - } - - @Override - public Class<? extends Skeleton> getType() { - return Skeleton.class; - } - - @Override - protected boolean equals_i(final EntityData<?> obj) { - if (!(obj instanceof SkeletonData)) - return false; - final SkeletonData other = (SkeletonData) obj; - return other.type == type; - } - - @Override - protected int hashCode_i() { - return type; - } - - @Override - public boolean isSupertypeOf(final EntityData<?> e) { - if (e instanceof SkeletonData) - return ((SkeletonData) e).type == type; - return false; - } - - @Override - public EntityData getSuperType() { - return new SkeletonData(type); - } - -} diff --git a/src/main/java/ch/njol/skript/entity/ThrownPotionData.java b/src/main/java/ch/njol/skript/entity/ThrownPotionData.java index 450fef3258e..632f2dc9ff6 100644 --- a/src/main/java/ch/njol/skript/entity/ThrownPotionData.java +++ b/src/main/java/ch/njol/skript/entity/ThrownPotionData.java @@ -49,7 +49,6 @@ public class ThrownPotionData extends EntityData<ThrownPotion> { } private static final Adjective m_adjective = new Adjective("entities.thrown potion.adjective"); - private static final boolean RUNNING_LEGACY = !Skript.isRunningMinecraft(1, 13); private static final boolean LINGERING_POTION_ENTITY_USED = !Skript.isRunningMinecraft(1, 14); // LingeringPotion class deprecated and marked for removal @SuppressWarnings("removal") @@ -67,8 +66,7 @@ protected boolean init(Literal<?>[] exprs, int matchedPattern, ParseResult parse if (exprs.length > 0 && exprs[0] != null) { return (types = Converters.convert((ItemType[]) exprs[0].getAll(), ItemType.class, t -> { // If the itemtype is a potion, lets make it a splash potion (required by Bukkit) - // Due to an issue with 1.12.2 and below, we have to force a lingering potion to be a splash potion - if (t.isSupertypeOf(POTION) || (t.isSupertypeOf(LINGER_POTION) && RUNNING_LEGACY)) { + if (t.isSupertypeOf(POTION)) { ItemMeta meta = t.getItemMeta(); ItemType itemType = SPLASH_POTION.clone(); itemType.setItemMeta(meta); diff --git a/src/main/java/ch/njol/skript/entity/TropicalFishData.java b/src/main/java/ch/njol/skript/entity/TropicalFishData.java index e7d4c5b7305..5df633ca0f7 100644 --- a/src/main/java/ch/njol/skript/entity/TropicalFishData.java +++ b/src/main/java/ch/njol/skript/entity/TropicalFishData.java @@ -37,13 +37,11 @@ public class TropicalFishData extends EntityData<TropicalFish> { private static Object[] patterns; static { - if (Skript.isRunningMinecraft(1, 13)) { - register(TropicalFishData.class, "tropical fish", TropicalFish.class, 0, - "tropical fish", "kob", "sunstreak", "snooper", - "dasher", "brinely", "spotty", "flopper", - "stripey", "glitter", "blockfish", "betty", "clayfish"); - patterns = Pattern.values(); - } + register(TropicalFishData.class, "tropical fish", TropicalFish.class, 0, + "tropical fish", "kob", "sunstreak", "snooper", + "dasher", "brinely", "spotty", "flopper", + "stripey", "glitter", "blockfish", "betty", "clayfish"); + patterns = Pattern.values(); } public TropicalFishData() { diff --git a/src/main/java/ch/njol/skript/entity/VillagerData.java b/src/main/java/ch/njol/skript/entity/VillagerData.java index c4b5fbdbd6d..2a686ca6784 100644 --- a/src/main/java/ch/njol/skript/entity/VillagerData.java +++ b/src/main/java/ch/njol/skript/entity/VillagerData.java @@ -43,15 +43,13 @@ public class VillagerData extends EntityData<Villager> { * for villagers. */ private static List<Profession> professions; - - private static final boolean HAS_NITWIT = Skript.isRunningMinecraft(1, 11); + static { // professions in order! // NORMAL(-1), FARMER(0), LIBRARIAN(1), PRIEST(2), BLACKSMITH(3), BUTCHER(4), NITWIT(5); Variables.yggdrasil.registerSingleClass(Profession.class, "Villager.Profession"); - if (Skript.isRunningMinecraft(1, 14)) { EntityData.register(VillagerData.class, "villager", Villager.class, 0, "villager", "normal", "armorer", "butcher", "cartographer", @@ -59,26 +57,18 @@ public class VillagerData extends EntityData<Villager> { "leatherworker", "librarian", "mason", "nitwit", "shepherd", "toolsmith", "weaponsmith"); professions = Arrays.asList(Profession.values()); - } else if (Skript.isRunningMinecraft(1, 10)) { // Post 1.10: Not all professions go for villagers + } else { // Post 1.10: Not all professions go for villagers EntityData.register(VillagerData.class, "villager", Villager.class, 0, "normal", "villager", "farmer", "librarian", "priest", "blacksmith", "butcher", "nitwit"); // Normal is for zombie villagers, but needs to be here, since someone thought changing first element in enum was good idea :( - + professions = new ArrayList<>(); for (Profession prof : Profession.values()) { // We're better off doing stringfying the constants since these don't exist in 1.14 if (!prof.toString().equals("NORMAL") && !prof.toString().equals("HUSK")) professions.add(prof); } - } else { // Pre 1.10: method Profession#isZombie() doesn't exist - EntityData.register(VillagerData.class, "villager", Villager.class, 0, - "villager", "farmer", "librarian", "priest", - "blacksmith", "butcher", "nitwit"); - - List<Profession> prof = Arrays.asList(Profession.values()); - assert prof != null; - professions = prof; } } @@ -110,7 +100,7 @@ public void set(final Villager entity) { Profession prof = profession == null ? CollectionUtils.getRandom(professions) : profession; assert prof != null; entity.setProfession(prof); - if (HAS_NITWIT && profession == Profession.NITWIT) + if (profession == Profession.NITWIT) entity.setRecipes(Collections.emptyList()); } diff --git a/src/main/java/ch/njol/skript/entity/ZombieVillagerData.java b/src/main/java/ch/njol/skript/entity/ZombieVillagerData.java index 5a93dd85cc2..3ac61bced2d 100644 --- a/src/main/java/ch/njol/skript/entity/ZombieVillagerData.java +++ b/src/main/java/ch/njol/skript/entity/ZombieVillagerData.java @@ -29,17 +29,16 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; public class ZombieVillagerData extends EntityData<ZombieVillager> { - - private final static boolean villagerSupport = Skript.isRunningMinecraft(1, 11); // TODO test on 1.9/1.10 + private final static boolean PROFESSION_UPDATE = Skript.isRunningMinecraft(1, 14); private final static Villager.Profession[] professions = Villager.Profession.values(); - + static { if (PROFESSION_UPDATE) EntityData.register(ZombieVillagerData.class, "zombie villager", ZombieVillager.class, 0, "zombie villager", "zombie armorer", "zombie butcher", "zombie cartographer", "zombie cleric", "zombie farmer", "zombie fisherman", "zombie fletcher", "zombie leatherworker", "zombie librarian", "zombie mason", "zombie nitwit", "zombie shepherd", "zombie toolsmith", "zombie weaponsmith"); - else if (villagerSupport) + else EntityData.register(ZombieVillagerData.class, "zombie villager", ZombieVillager.class, 0, "zombie villager", "zombie farmer", "zombie librarian", "zombie priest", "zombie blacksmith", "zombie butcher", "zombie nitwit"); } diff --git a/src/main/java/ch/njol/skript/events/EvtBlock.java b/src/main/java/ch/njol/skript/events/EvtBlock.java index e40c2fc7b8a..7aae8da9428 100644 --- a/src/main/java/ch/njol/skript/events/EvtBlock.java +++ b/src/main/java/ch/njol/skript/events/EvtBlock.java @@ -52,35 +52,28 @@ public class EvtBlock extends SkriptEvent { static { - if (Skript.isRunningMinecraft(1, 13)) { - // TODO 'block destroy' event for any kind of block destruction (player, water, trampling, fall (sand, toches, ...), etc) -> BlockPhysicsEvent? - // REMIND attacking an item frame first removes its item; include this in on block damage? - Skript.registerEvent("Break / Mine", EvtBlock.class, new Class[]{BlockBreakEvent.class, PlayerBucketFillEvent.class, HangingBreakEvent.class}, "[block] (break[ing]|1¦min(e|ing)) [[of] %itemtypes/blockdatas%]") - .description("Called when a block is broken by a player. If you use 'on mine', only events where the broken block dropped something will call the trigger.") - .examples("on mine:", "on break of stone:", "on mine of any ore:", "on break of chest[facing=north]:", "on break of potatoes[age=7]:") - .requiredPlugins("Minecraft 1.13+ (BlockData)") - .since("1.0 (break), <i>unknown</i> (mine), 2.6 (BlockData support)"); - Skript.registerEvent("Burn", EvtBlock.class, BlockBurnEvent.class, "[block] burn[ing] [[of] %itemtypes/blockdatas%]") - .description("Called when a block is destroyed by fire.") - .examples("on burn:", "on burn of wood, fences, or chests:", "on burn of oak_log[axis=y]:") - .requiredPlugins("Minecraft 1.13+ (BlockData)") - .since("1.0, 2.6 (BlockData support)"); - Skript.registerEvent("Place", EvtBlock.class, new Class[]{BlockPlaceEvent.class, PlayerBucketEmptyEvent.class, HangingPlaceEvent.class}, "[block] (plac(e|ing)|build[ing]) [[of] %itemtypes/blockdatas%]") - .description("Called when a player places a block.") - .examples("on place:", "on place of a furnace, workbench or chest:", "on break of chest[type=right] or chest[type=left]") - .requiredPlugins("Minecraft 1.13+ (BlockData)") - .since("1.0, 2.6 (BlockData support)"); - Skript.registerEvent("Fade", EvtBlock.class, BlockFadeEvent.class, "[block] fad(e|ing) [[of] %itemtypes/blockdatas%]") - .description("Called when a block 'fades away', e.g. ice or snow melts.") - .examples("on fade of snow or ice:", "on fade of snow[layers=2]") - .requiredPlugins("Minecraft 1.13+ (BlockData)") - .since("1.0, 2.6 (BlockData support)"); - Skript.registerEvent("Form", EvtBlock.class, BlockFormEvent.class, "[block] form[ing] [[of] %itemtypes/blockdatas%]") - .description("Called when a block is created, but not by a player, e.g. snow forms due to snowfall, water freezes in cold biomes. This isn't called when block spreads (mushroom growth, water physics etc.), as it has its own event (see <a href='#spread'>spread event</a>).") - .examples("on form of snow:", "on form of a mushroom:") - .requiredPlugins("Minecraft 1.13+ (BlockData)") - .since("1.0, 2.6 (BlockData support)"); - } + // TODO 'block destroy' event for any kind of block destruction (player, water, trampling, fall (sand, toches, ...), etc) -> BlockPhysicsEvent? + // REMIND attacking an item frame first removes its item; include this in on block damage? + Skript.registerEvent("Break / Mine", EvtBlock.class, new Class[]{BlockBreakEvent.class, PlayerBucketFillEvent.class, HangingBreakEvent.class}, "[block] (break[ing]|1¦min(e|ing)) [[of] %itemtypes/blockdatas%]") + .description("Called when a block is broken by a player. If you use 'on mine', only events where the broken block dropped something will call the trigger.") + .examples("on mine:", "on break of stone:", "on mine of any ore:", "on break of chest[facing=north]:", "on break of potatoes[age=7]:") + .since("1.0 (break), <i>unknown</i> (mine), 2.6 (BlockData support)"); + Skript.registerEvent("Burn", EvtBlock.class, BlockBurnEvent.class, "[block] burn[ing] [[of] %itemtypes/blockdatas%]") + .description("Called when a block is destroyed by fire.") + .examples("on burn:", "on burn of wood, fences, or chests:", "on burn of oak_log[axis=y]:") + .since("1.0, 2.6 (BlockData support)"); + Skript.registerEvent("Place", EvtBlock.class, new Class[]{BlockPlaceEvent.class, PlayerBucketEmptyEvent.class, HangingPlaceEvent.class}, "[block] (plac(e|ing)|build[ing]) [[of] %itemtypes/blockdatas%]") + .description("Called when a player places a block.") + .examples("on place:", "on place of a furnace, workbench or chest:", "on break of chest[type=right] or chest[type=left]") + .since("1.0, 2.6 (BlockData support)"); + Skript.registerEvent("Fade", EvtBlock.class, BlockFadeEvent.class, "[block] fad(e|ing) [[of] %itemtypes/blockdatas%]") + .description("Called when a block 'fades away', e.g. ice or snow melts.") + .examples("on fade of snow or ice:", "on fade of snow[layers=2]") + .since("1.0, 2.6 (BlockData support)"); + Skript.registerEvent("Form", EvtBlock.class, BlockFormEvent.class, "[block] form[ing] [[of] %itemtypes/blockdatas%]") + .description("Called when a block is created, but not by a player, e.g. snow forms due to snowfall, water freezes in cold biomes. This isn't called when block spreads (mushroom growth, water physics etc.), as it has its own event (see <a href='#spread'>spread event</a>).") + .examples("on form of snow:", "on form of a mushroom:") + .since("1.0, 2.6 (BlockData support)"); } @Nullable diff --git a/src/main/java/ch/njol/skript/events/EvtBlockLegacy.java b/src/main/java/ch/njol/skript/events/EvtBlockLegacy.java deleted file mode 100644 index 8d5245f5ac6..00000000000 --- a/src/main/java/ch/njol/skript/events/EvtBlockLegacy.java +++ /dev/null @@ -1,138 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.events; - -import org.bukkit.event.Event; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.block.BlockBurnEvent; -import org.bukkit.event.block.BlockEvent; -import org.bukkit.event.block.BlockFadeEvent; -import org.bukkit.event.block.BlockFormEvent; -import org.bukkit.event.block.BlockPlaceEvent; -import org.bukkit.event.hanging.HangingBreakEvent; -import org.bukkit.event.hanging.HangingEvent; -import org.bukkit.event.hanging.HangingPlaceEvent; -import org.bukkit.event.player.PlayerBucketEmptyEvent; -import org.bukkit.event.player.PlayerBucketEvent; -import org.bukkit.event.player.PlayerBucketFillEvent; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.Skript; -import ch.njol.skript.aliases.ItemType; -import ch.njol.skript.classes.Comparator.Relation; -import ch.njol.skript.classes.data.DefaultComparators; -import ch.njol.skript.entity.EntityData; -import ch.njol.skript.lang.Literal; -import ch.njol.skript.lang.SkriptEvent; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.registrations.Classes; -import ch.njol.util.Checker; - -/** - * @author Peter Güttinger - */ -// Class can be deleted when legacy support is dropped (new class = EvtBlock) -@SuppressWarnings({"deprecation", "unchecked"}) -public class EvtBlockLegacy extends SkriptEvent { - - static { - if (!Skript.isRunningMinecraft(1, 13)) { - Skript.registerEvent("Break / Mine", EvtBlockLegacy.class, new Class[]{BlockBreakEvent.class, PlayerBucketFillEvent.class, HangingBreakEvent.class}, "[block] (break[ing]|1¦min(e|ing)) [[of] %itemtypes%]") - .description("Called when a block is broken by a player. If you use 'on mine', only events where the broken block dropped something will call the trigger.") - .examples("on mine:", "on break of stone:", "on mine of any ore:") - .since("1.0 (break), <i>unknown</i> (mine)"); - Skript.registerEvent("Burn", EvtBlockLegacy.class, BlockBurnEvent.class, "[block] burn[ing] [[of] %itemtypes%]") - .description("Called when a block is destroyed by fire.") - .examples("on burn:", "on burn of wood, fences, or chests:") - .since("1.0"); - Skript.registerEvent("Place", EvtBlockLegacy.class, new Class[]{BlockPlaceEvent.class, PlayerBucketEmptyEvent.class, HangingPlaceEvent.class}, "[block] (plac(e|ing)|build[ing]) [[of] %itemtypes%]") - .description("Called when a player places a block.") - .examples("on place:", "on place of a furnace, workbench or chest:") - .since("1.0"); - Skript.registerEvent("Fade", EvtBlockLegacy.class, BlockFadeEvent.class, "[block] fad(e|ing) [[of] %itemtypes%]") - .description("Called when a block 'fades away', e.g. ice or snow melts.") - .examples("on fade of snow or ice:") - .since("1.0"); - Skript.registerEvent("Form", EvtBlockLegacy.class, BlockFormEvent.class, "[block] form[ing] [[of] %itemtypes%]") - .description("Called when a block is created, but not by a player, e.g. snow forms due to snowfall, water freezes in cold biomes. This isn't called when block spreads (mushroom growth, water physics etc.), as it has its own event (see <a href='#spread'>spread event</a>).") - .examples("on form of snow:", "on form of a mushroom:") - .since("1.0"); - } - } - - @Nullable - private Literal<ItemType> types; - - private boolean mine = false; - - @Override - public boolean init(final Literal<?>[] args, final int matchedPattern, final ParseResult parser) { - types = (Literal<ItemType>) args[0]; - mine = parser.mark == 1; - return true; - } - - @SuppressWarnings("null") - @Override - public boolean check(final Event e) { - if (mine && e instanceof BlockBreakEvent) { - if (((BlockBreakEvent) e).getBlock().getDrops(((BlockBreakEvent) e).getPlayer().getItemInHand()).isEmpty()) - return false; - } - if (types == null) - return true; - - ItemType item; - if (e instanceof BlockFormEvent) { - item = new ItemType(((BlockFormEvent) e).getNewState()); - } else if (e instanceof BlockEvent) { - item = new ItemType(((BlockEvent) e).getBlock()); - } else if (e instanceof PlayerBucketFillEvent) { - item = new ItemType(((PlayerBucketEvent) e).getBlockClicked().getRelative(((PlayerBucketEvent) e).getBlockFace())); - } else if (e instanceof PlayerBucketEmptyEvent) { - item = new ItemType(((PlayerBucketEmptyEvent) e).getItemStack()); - } else if (Skript.isRunningMinecraft(1, 4, 3) && e instanceof HangingEvent) { - final EntityData<?> d = EntityData.fromEntity(((HangingEvent) e).getEntity()); - return types.check(e, new Checker<ItemType>() { - @Override - public boolean check(final @Nullable ItemType t) { - return t != null && Relation.EQUAL.is(DefaultComparators.entityItemComparator.compare(d, t)); - } - }); - } else { - assert false; - return false; - } - - final ItemType itemF = item; - - return types.check(e, new Checker<ItemType>() { - @Override - public boolean check(final @Nullable ItemType t) { - return t != null && t.isSupertypeOf(itemF); - } - }); - } - - @Override - public String toString(final @Nullable Event e, final boolean debug) { - return "break/place/burn/fade/form of " + Classes.toString(types); - } - -} diff --git a/src/main/java/ch/njol/skript/events/EvtClick.java b/src/main/java/ch/njol/skript/events/EvtClick.java index d55bfa0b478..8f5754f7ee7 100644 --- a/src/main/java/ch/njol/skript/events/EvtClick.java +++ b/src/main/java/ch/njol/skript/events/EvtClick.java @@ -47,18 +47,7 @@ @SuppressWarnings("unchecked") public class EvtClick extends SkriptEvent { - - /** - * Two hands available. - */ - final static boolean twoHanded = Skript.isRunningMinecraft(1, 9); - - /** - * If a hand has item, it will always be used when the other hand has - * nothing. - */ - final static boolean alwaysPreferItem = !Skript.isRunningMinecraft(1, 13); - + /** * Click types. */ diff --git a/src/main/java/ch/njol/skript/expressions/ExprDurability.java b/src/main/java/ch/njol/skript/expressions/ExprDurability.java index debee5dd8c1..ab2a3ff6016 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprDurability.java +++ b/src/main/java/ch/njol/skript/expressions/ExprDurability.java @@ -49,9 +49,7 @@ "reset data value of block at player"}) @Since("1.2") public class ExprDurability extends SimplePropertyExpression<Object, Long> { - - private static final boolean LEGACY_BLOCK = !Skript.isRunningMinecraft(1, 13); - + static { register(ExprDurability.class, Long.class, "((data|damage)[s] [value[s]]|durabilit(y|ies))", "itemtypes/blocks/slots"); } @@ -65,8 +63,6 @@ public Long convert(final Object o) { } else if (o instanceof ItemType) { ItemStack item = ((ItemType) o).getRandom(); return (long) ItemUtils.getDamage(item); - } else if (LEGACY_BLOCK && o instanceof Block) { - return (long) ((Block) o).getData(); } return null; } @@ -98,8 +94,6 @@ public void change(final Event e, final @Nullable Object[] delta, final ChangeMo itemStack = ((ItemType) o).getRandom(); else if (o instanceof Slot) itemStack = ((Slot) o).getItem(); - else if (LEGACY_BLOCK) - block = (Block) o; else return; diff --git a/src/main/java/ch/njol/skript/expressions/ExprEnchantmentExpCosts.java b/src/main/java/ch/njol/skript/expressions/ExprEnchantmentExpCosts.java deleted file mode 100644 index d87228d6400..00000000000 --- a/src/main/java/ch/njol/skript/expressions/ExprEnchantmentExpCosts.java +++ /dev/null @@ -1,192 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.expressions; - -import java.util.Arrays; - -import org.bukkit.event.Event; -import org.bukkit.event.enchantment.PrepareItemEnchantEvent; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.Skript; -import ch.njol.skript.classes.Changer; -import ch.njol.skript.classes.Changer.ChangeMode; -import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Events; -import ch.njol.skript.doc.Examples; -import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.RequiredPlugins; -import ch.njol.skript.doc.Since; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.ExpressionType; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.lang.util.SimpleExpression; -import ch.njol.skript.log.ErrorQuality; -import ch.njol.skript.util.Experience; -import ch.njol.util.Kleenean; -import ch.njol.util.coll.CollectionUtils; - -@Name("Enchantment Experience Level Costs") -@Description({"The experience cost of an enchantment in an enchant prepare event.", - " If the cost is changed, it will always be at least 1."}) -@Examples({"on enchant prepare:", - "\tset the cost of enchantment 1 to 50"}) -@Since("2.5") -@Events("enchant prepare") -@RequiredPlugins("1.9 or 1.10") -@SuppressWarnings("deprecation") -public class ExprEnchantmentExpCosts extends SimpleExpression<Long> { - - static { - if (!Skript.isRunningMinecraft(1, 11)) { // This expression should only be usable on 1.9 and 1.10. - Skript.registerExpression(ExprEnchantmentExpCosts.class, Long.class, ExpressionType.SIMPLE, - "[the] cost of (enchant[ment]s|enchant[ment] offers)", - "[the] cost of enchant[ment] [offer] %number%", - "enchant[ment] [offer] %number%'[s] cost", - "[the] cost of [the] %number%(st|nd|rd|th) enchant[ment] [offer]"); - } - } - - @SuppressWarnings("null") - private Expression<Number> exprOfferNumber; - - private boolean multiple; - - @SuppressWarnings({"null", "unchecked"}) - @Override - public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - if (!getParser().isCurrentEvent(PrepareItemEnchantEvent.class)) { - Skript.error("The enchantment exp level cost is only usable in an enchant prepare event.", ErrorQuality.SEMANTIC_ERROR); - return false; - } - if (matchedPattern == 0) { - multiple = true; - } else { - exprOfferNumber = (Expression<Number>) exprs[0]; - multiple = false; - } - return true; - } - - @Override - @Nullable - protected Long[] get(Event event) { - PrepareItemEnchantEvent e = (PrepareItemEnchantEvent) event; - if (multiple) { - return Arrays.stream(e.getExpLevelCostsOffered()) - .boxed() - .toArray(Long[]::new); - } - Number offerNumber = exprOfferNumber.getSingle(e); - if (offerNumber == null) - return new Long[0]; - int offer = offerNumber.intValue(); - if (offer < 1 || offer > e.getExpLevelCostsOffered().length) - return new Long[0]; - return new Long[] {(long) e.getExpLevelCostsOffered()[offer - 1]}; - } - - @Override - @Nullable - public Class<?>[] acceptChange(Changer.ChangeMode mode) { - if (mode == ChangeMode.RESET || mode == ChangeMode.DELETE || mode == ChangeMode.REMOVE_ALL) - return null; - return CollectionUtils.array(Number.class, Experience.class); - } - - @SuppressWarnings("null") - @Override - public void change(Event event, @Nullable Object[] delta, Changer.ChangeMode mode) { - if (delta == null) - return; - Object c = delta[0]; - int cost = c instanceof Number ? ((Number) c).intValue() : ((Experience) c).getXP(); - if (cost < 1) - return; - int offer = 0; - if (exprOfferNumber != null) { - Number offerNumber = exprOfferNumber.getSingle(event); - if (offerNumber != null) // Convert to Index Form - offer = offerNumber.intValue() - 1; - } - PrepareItemEnchantEvent e = (PrepareItemEnchantEvent) event; - switch (mode) { - case SET: - if (multiple) { - for (int i = 0; i <= 2; i++) - e.getExpLevelCostsOffered()[i] = cost; - } else { - e.getExpLevelCostsOffered()[offer] = cost; - } - break; - case ADD: - int add; - if (multiple) { - for (int i = 0; i <= 2; i++) { - add = cost + e.getExpLevelCostsOffered()[i]; - if (add < 1) - continue; - e.getExpLevelCostsOffered()[i] = add; - } - } else { - add = cost + e.getExpLevelCostsOffered()[offer]; - if (add < 1) - return; - e.getExpLevelCostsOffered()[offer] = add; - } - break; - case REMOVE: - int remove; - if (multiple) { - for (int i = 0; i <= 2; i++) { - remove = cost - e.getExpLevelCostsOffered()[i]; - if (remove < 1) - continue; - e.getExpLevelCostsOffered()[i] = remove; - } - } else { - remove = cost - e.getExpLevelCostsOffered()[offer]; - if (remove < 1) - return; - e.getExpLevelCostsOffered()[offer] = remove; - } - break; - case RESET: - case DELETE: - case REMOVE_ALL: - assert false; - } - } - - @Override - public boolean isSingle() { - return !multiple; - } - - @Override - public Class<? extends Long> getReturnType() { - return Long.class; - } - - @Override - public String toString(@Nullable Event e, boolean debug) { - return multiple ? "cost of enchantment offers" : "cost of enchantment offer " + exprOfferNumber.toString(e, debug); - } - -} diff --git a/src/main/java/ch/njol/skript/expressions/ExprEntityAttribute.java b/src/main/java/ch/njol/skript/expressions/ExprEntityAttribute.java index 5ebcfda3f41..c7c81de0d5c 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprEntityAttribute.java +++ b/src/main/java/ch/njol/skript/expressions/ExprEntityAttribute.java @@ -56,9 +56,7 @@ public class ExprEntityAttribute extends PropertyExpression<Entity, Number> { "[the] %attributetype% [(1¦(total|final|modified))] attribute [value] of %entities%", "%entities%'[s] %attributetype% [(1¦(total|final|modified))] attribute [value]"); } - - private static final boolean DEFAULTVALUE_EXISTS = Skript.isRunningMinecraft(1, 11); - + @Nullable private Expression<Attribute> attributes; private boolean withModifiers; @@ -85,7 +83,7 @@ protected Number[] get(Event e, Entity[] entities) { @Override @Nullable public Class<?>[] acceptChange(ChangeMode mode) { - if (mode == ChangeMode.REMOVE_ALL || (mode == ChangeMode.RESET && !DEFAULTVALUE_EXISTS) || withModifiers) + if (mode == ChangeMode.REMOVE_ALL || withModifiers) return null; return CollectionUtils.array(Number.class); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprFacing.java b/src/main/java/ch/njol/skript/expressions/ExprFacing.java index aaac8faba33..dda20cd33a8 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprFacing.java +++ b/src/main/java/ch/njol/skript/expressions/ExprFacing.java @@ -18,18 +18,6 @@ */ package ch.njol.skript.expressions; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.block.data.BlockData; -import org.bukkit.entity.LivingEntity; -import org.bukkit.event.Event; -import org.bukkit.material.Directional; -import org.bukkit.material.MaterialData; -import org.bukkit.util.Vector; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.Skript; -import ch.njol.skript.bukkitutil.block.MagicBlockCompat; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; @@ -41,6 +29,13 @@ import ch.njol.skript.util.Direction; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Event; +import org.bukkit.util.Vector; +import org.eclipse.jdt.annotation.Nullable; /** * @author Peter Güttinger @@ -52,9 +47,7 @@ "\tset loop-block to cobblestone"}) @Since("1.4") public class ExprFacing extends SimplePropertyExpression<Object, Direction> { - - private static final boolean useBlockData = Skript.isRunningMinecraft(1, 13); - + static { register(ExprFacing.class, Direction.class, "(1¦horizontal|) facing", "livingentities/blocks"); } @@ -72,15 +65,9 @@ public boolean init(final Expression<?>[] exprs, final int matchedPattern, final @Nullable public Direction convert(final Object o) { if (o instanceof Block) { - if (useBlockData) { - BlockData data = ((Block) o).getBlockData(); - if (data instanceof org.bukkit.block.data.Directional) { - return new Direction(((org.bukkit.block.data.Directional) data).getFacing(), 1); - } - } else { - final MaterialData d = ((Block) o).getType().getNewData(((Block) o).getData()); - if (d instanceof Directional) - return new Direction(((Directional) d).getFacing(), 1); + BlockData data = ((Block) o).getBlockData(); + if (data instanceof org.bukkit.block.data.Directional) { + return new Direction(((org.bukkit.block.data.Directional) data).getFacing(), 1); } return null; } else if (o instanceof LivingEntity) { @@ -119,22 +106,10 @@ public void change(final Event e, final @Nullable Object[] delta, final ChangeMo final Block b = (Block) getExpr().getSingle(e); if (b == null) return; - if (useBlockData) { - BlockData data = b.getBlockData(); - if (data instanceof org.bukkit.block.data.Directional) { - ((org.bukkit.block.data.Directional) data).setFacing(toBlockFace(((Direction) delta[0]).getDirection(b))); - b.setBlockData(data, false); - } - } else { - final MaterialData d = b.getType().getNewData(b.getData()); - if (!(d instanceof Directional)) - return; - ((Directional) d).setFacingDirection(toBlockFace(((Direction) delta[0]).getDirection(b))); - try { // Quick and dirty fix for getting pre-1.13 setData(byte) - MagicBlockCompat.setDataMethod.invokeExact(b, d.getData()); - } catch (Throwable ex) { - Skript.exception(ex); - } + BlockData data = b.getBlockData(); + if (data instanceof org.bukkit.block.data.Directional) { + ((org.bukkit.block.data.Directional) data).setFacing(toBlockFace(((Direction) delta[0]).getDirection(b))); + b.setBlockData(data, false); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprGlidingState.java b/src/main/java/ch/njol/skript/expressions/ExprGlidingState.java index 1d9f8b07cf6..6334e1af835 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprGlidingState.java +++ b/src/main/java/ch/njol/skript/expressions/ExprGlidingState.java @@ -37,8 +37,7 @@ public class ExprGlidingState extends SimplePropertyExpression<LivingEntity, Boolean> { static { - if (Skript.isRunningMinecraft(1, 9)) - register(ExprGlidingState.class, Boolean.class, "(gliding|glider) [state]", "livingentities"); + register(ExprGlidingState.class, Boolean.class, "(gliding|glider) [state]", "livingentities"); } @Override diff --git a/src/main/java/ch/njol/skript/expressions/ExprGravity.java b/src/main/java/ch/njol/skript/expressions/ExprGravity.java index 19279b1b3a3..1d3de01c0d2 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprGravity.java +++ b/src/main/java/ch/njol/skript/expressions/ExprGravity.java @@ -37,8 +37,7 @@ public class ExprGravity extends SimplePropertyExpression<Entity, Boolean> { static { - if (Skript.isRunningMinecraft(1, 10)) - register(ExprGravity.class, Boolean.class, "gravity", "entities"); + register(ExprGravity.class, Boolean.class, "gravity", "entities"); } @Override diff --git a/src/main/java/ch/njol/skript/expressions/ExprMaxHealth.java b/src/main/java/ch/njol/skript/expressions/ExprMaxHealth.java index dc332de9ec1..f53d12703cc 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprMaxHealth.java +++ b/src/main/java/ch/njol/skript/expressions/ExprMaxHealth.java @@ -67,11 +67,6 @@ protected String getPropertyName() { @Override @Nullable public Class<?>[] acceptChange(final ChangeMode mode) { - if(!Skript.isRunningMinecraft(1, 5, 2)) { - Skript.error("The max health of an entity can only be changed in Minecraft 1.6 and later"); - return null; - } - if (mode != ChangeMode.DELETE && mode != ChangeMode.REMOVE_ALL) return new Class[] {Number.class}; return null; diff --git a/src/main/java/ch/njol/skript/expressions/ExprOnlinePlayersCount.java b/src/main/java/ch/njol/skript/expressions/ExprOnlinePlayersCount.java index f4380fa2516..6edae5a9ed1 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprOnlinePlayersCount.java +++ b/src/main/java/ch/njol/skript/expressions/ExprOnlinePlayersCount.java @@ -18,6 +18,7 @@ */ package ch.njol.skript.expressions; +import org.bukkit.Bukkit; import org.bukkit.event.Event; import org.bukkit.event.server.ServerListPingEvent; import org.eclipse.jdt.annotation.Nullable; @@ -79,7 +80,7 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Nullable public Long[] get(Event e) { if (isReal) - return CollectionUtils.array((long) PlayerUtils.getOnlinePlayers().size()); + return CollectionUtils.array((long) Bukkit.getOnlinePlayers().size()); else return CollectionUtils.array((long) ((PaperServerListPingEvent) e).getNumPlayers()); } @@ -120,7 +121,7 @@ public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { break; case DELETE: case RESET: - event.setNumPlayers(PlayerUtils.getOnlinePlayers().size()); + event.setNumPlayers(Bukkit.getOnlinePlayers().size()); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprScoreboardTags.java b/src/main/java/ch/njol/skript/expressions/ExprScoreboardTags.java index c9aaa9db516..14da4e073ca 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprScoreboardTags.java +++ b/src/main/java/ch/njol/skript/expressions/ExprScoreboardTags.java @@ -58,10 +58,9 @@ public class ExprScoreboardTags extends SimpleExpression<String> { static { - if (Skript.isRunningMinecraft(1, 11)) - Skript.registerExpression(ExprScoreboardTags.class, String.class, ExpressionType.PROPERTY, - "[(all [[of] the]|the)] scoreboard tags of %entities%", - "%entities%'[s] scoreboard tags"); + Skript.registerExpression(ExprScoreboardTags.class, String.class, ExpressionType.PROPERTY, + "[(all [[of] the]|the)] scoreboard tags of %entities%", + "%entities%'[s] scoreboard tags"); } @SuppressWarnings("null") diff --git a/src/main/java/ch/njol/skript/expressions/ExprSpeed.java b/src/main/java/ch/njol/skript/expressions/ExprSpeed.java index 9eac6244540..432b529db20 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprSpeed.java +++ b/src/main/java/ch/njol/skript/expressions/ExprSpeed.java @@ -53,10 +53,6 @@ public class ExprSpeed extends SimplePropertyExpression<Player, Number> { @Override public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { - if (!Skript.isRunningMinecraft(1, 4)) { - Skript.error("fly and walk speed can only be used in Minecraft 1.4 and newer"); - return false; - } super.init(exprs, matchedPattern, isDelayed, parseResult); walk = parseResult.mark == 0; return true; diff --git a/src/main/java/ch/njol/skript/expressions/ExprTimePlayed.java b/src/main/java/ch/njol/skript/expressions/ExprTimePlayed.java index 9a98fe96464..afd607ffa36 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTimePlayed.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTimePlayed.java @@ -48,15 +48,8 @@ @Since("2.5") public class ExprTimePlayed extends SimplePropertyExpression<OfflinePlayer, Timespan> { - private static final Statistic TIME_PLAYED; - static { register(ExprTimePlayed.class, Timespan.class, "time played", "offlineplayers"); - if (Skript.isRunningMinecraft(1, 13)) { - TIME_PLAYED = Statistic.PLAY_ONE_MINUTE; // Statistic name is misleading, it's actually measured in ticks - } else { - TIME_PLAYED = Statistic.valueOf("PLAY_ONE_TICK"); - } } @SuppressWarnings({"unchecked", "null"}) @@ -69,7 +62,7 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Nullable @Override public Timespan convert(OfflinePlayer offlinePlayer) { - return Timespan.fromTicks_i(offlinePlayer.getStatistic(TIME_PLAYED)); + return Timespan.fromTicks_i(offlinePlayer.getStatistic(Statistic.PLAY_ONE_MINUTE)); } @Nullable @@ -86,7 +79,7 @@ public Class<?>[] acceptChange(ChangeMode mode) { public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { long ticks = ((Timespan) delta[0]).getTicks_i(); for (OfflinePlayer offlinePlayer : getExpr().getArray(e)) { - long playerTicks = offlinePlayer.getStatistic(TIME_PLAYED); + long playerTicks = offlinePlayer.getStatistic(Statistic.PLAY_ONE_MINUTE); switch (mode) { case ADD: ticks = playerTicks + ticks; @@ -95,7 +88,7 @@ public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { ticks = playerTicks - ticks; break; } - offlinePlayer.setStatistic(TIME_PLAYED, (int) ticks); + offlinePlayer.setStatistic(Statistic.PLAY_ONE_MINUTE, (int) ticks); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprUUID.java b/src/main/java/ch/njol/skript/expressions/ExprUUID.java index 4eec0ac1d28..287cbe10c02 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprUUID.java +++ b/src/main/java/ch/njol/skript/expressions/ExprUUID.java @@ -49,27 +49,24 @@ " set {uuid::%name of player%} to uuid of player"}) @Since("2.1.2, 2.2 (offline players' UUIDs), 2.2-dev24 (other entities' UUIDs)") public class ExprUUID extends SimplePropertyExpression<Object, String> { - private final static boolean offlineUUIDSupported = Skript.methodExists(OfflinePlayer.class, "getUniqueId"); + static { - register(ExprUUID.class, String.class, "UUID", (offlineUUIDSupported ? "offlineplayers" : "players") + "/worlds/entities"); + register(ExprUUID.class, String.class, "UUID", "offlineplayers/worlds/entities"); } - + @Override @Nullable public String convert(final Object o) { if (o instanceof OfflinePlayer) { - if (offlineUUIDSupported) { - try { - return ((OfflinePlayer) o).getUniqueId().toString(); - } catch (UnsupportedOperationException e) { - // Some plugins (ProtocolLib) try to emulate offline players, but fail miserably - // They will throw this exception... and somehow server may freeze when this happens - Skript.warning("A script tried to get uuid of an offline player, which was faked by another plugin (probably ProtocolLib)."); - e.printStackTrace(); - return null; - } - } else - return ((Player) o).getUniqueId().toString(); + try { + return ((OfflinePlayer) o).getUniqueId().toString(); + } catch (UnsupportedOperationException e) { + // Some plugins (ProtocolLib) try to emulate offline players, but fail miserably + // They will throw this exception... and somehow server may freeze when this happens + Skript.warning("A script tried to get uuid of an offline player, which was faked by another plugin (probably ProtocolLib)."); + e.printStackTrace(); + return null; + } } else if (o instanceof Entity) { return ((Entity)o).getUniqueId().toString(); } else if (o instanceof World) { diff --git a/src/main/java/ch/njol/skript/expressions/ExprUnbreakable.java b/src/main/java/ch/njol/skript/expressions/ExprUnbreakable.java index 1eed3d1962e..0d286d8daab 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprUnbreakable.java +++ b/src/main/java/ch/njol/skript/expressions/ExprUnbreakable.java @@ -45,8 +45,6 @@ @Since("2.2-dev13b") public class ExprUnbreakable extends PropertyExpression<ItemType, ItemType> { - private static final boolean USE_DEPRECATED_METHOD = !Skript.methodExists(ItemMeta.class, "setUnbreakable", boolean.class); - @Nullable private static final MethodHandle setUnbreakableMethod; @@ -76,16 +74,7 @@ protected ItemType[] get(final Event e, final ItemType[] source) { ItemType clone = itemType.clone(); ItemMeta meta = clone.getItemMeta(); - if (USE_DEPRECATED_METHOD) { - assert setUnbreakableMethod != null; - try { - setUnbreakableMethod.invoke(true); - } catch (Throwable e1) { - Skript.exception(e1); - } - } else { - meta.setUnbreakable(true); - } + meta.setUnbreakable(true); clone.setItemMeta(meta); return clone; diff --git a/src/main/java/ch/njol/skript/hooks/biomes/BiomeHook.java b/src/main/java/ch/njol/skript/hooks/biomes/BiomeHook.java deleted file mode 100644 index 71afdbaaf3e..00000000000 --- a/src/main/java/ch/njol/skript/hooks/biomes/BiomeHook.java +++ /dev/null @@ -1,73 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.hooks.biomes; - -import java.io.IOException; - -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.Skript; -import ch.njol.skript.hooks.Hook; -import ch.njol.skript.hooks.biomes.BiomeMapUtil.To19Mapping; -import ch.njol.skript.util.EnumUtils; - -/** - * Hook for using 1.13 biome names on older Minecraft versions. note that this - * class used to allow using 1.8 biome names on 1.9 or newer. - */ -public class BiomeHook extends Hook<Skript> { - - @SuppressWarnings("null") - public static BiomeHook instance; - - /** - * Used on Minecraft 1.9-1.12 to provide biome support. - */ - @Nullable - public static EnumUtils<To19Mapping> util19; - - public static EnumUtils<To19Mapping> getUtil() { - assert util19 != null; - return util19; - } - - public BiomeHook() throws IOException {} - - @Override - protected boolean init() { - instance = this; - - return true; - } - - @Override - public String getName() { - return "Skript"; - } - - @SuppressWarnings("null") - @Override - protected void loadClasses() throws IOException { - if (!Skript.isRunningMinecraft(1, 13)) {// Load only if running MC<1.13 - Skript.getAddonInstance().loadClasses(getClass().getPackage().getName()); - util19 = new EnumUtils<>(To19Mapping.class, "biomes"); - } - } - -} diff --git a/src/main/java/ch/njol/skript/hooks/biomes/BiomeMapUtil.java b/src/main/java/ch/njol/skript/hooks/biomes/BiomeMapUtil.java deleted file mode 100644 index 53d77759539..00000000000 --- a/src/main/java/ch/njol/skript/hooks/biomes/BiomeMapUtil.java +++ /dev/null @@ -1,119 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.hooks.biomes; - -import org.bukkit.block.Biome; -import org.eclipse.jdt.annotation.Nullable; - -/** - * Hooks to provide MC<1.13 support. - */ -public class BiomeMapUtil { - - public enum To19Mapping { - SWAMP("SWAMPLAND"), - FOREST(Biome.FOREST), - TAIGA(Biome.TAIGA), - DESERT(Biome.DESERT), - PLAINS(Biome.PLAINS), - NETHER("HELL"), - THE_END("SKY"), - OCEAN(Biome.OCEAN), - RIVER(Biome.RIVER), - MOUNTAINS("EXTREME_HILLS"), - FROZEN_OCEAN(Biome.FROZEN_OCEAN), - FROZEN_RIVER(Biome.FROZEN_RIVER), - SNOWY_TUNDRA("ICE_FLATS"), - SNOWY_MOUNTAINS("ICE_MOUNTAINS"), - MUSHROOM_FIELDS("MUSHROOM_ISLAND"), - MUSHROOM_FIELD_SHORE("MUSHROOM_ISLAND_SHORE"), - BEACH("BEACHES"), - DESERT_HILLS("DESERT_HILLS"), - WOODED_HILLS("FOREST_HILLS"), - TAIGA_HILLS("TAIGA_HILLS"), - MOUNTAIN_EDGE("SMALLER_EXTREME_HILLS"), - JUNGLE(Biome.JUNGLE), - JUNGLE_HILLS("JUNGLE_HILLS"), - JUNGLE_EDGE("JUNGLE_EDGE"), - DEEP_OCEAN(Biome.DEEP_OCEAN), - STONE_SHORE("STONE_BEACH"), - SNOWY_BEACH("COLD_BEACH"), - BIRCH_FOREST(Biome.BIRCH_FOREST), - BIRCH_FOREST_HILLS("BIRCH_FOREST_HILLS"), - DARK_FOREST("ROOFED_FOREST"), - SNOWY_TAIGA("TAIGA_COLD"), - SNOWY_TAIGA_HILLS("TAIGA_COLD_HILLS"), - GIANT_TREE_TAIGA("REDWOOD_TAIGA"), - GIANT_TREE_TAIGA_HILLS("REDWOOD_TAIGA_HILLS"), - WOODED_MOUNTAINS("EXTREME_HILLS_WITH_TREES"), - SAVANNA(Biome.SAVANNA), - SAVANNA_PLATEAU("SAVANNA_ROCK"), - BADLANDS("MESA"), - WOODED_BADLANDS_PLATEAU("MESA_ROCK"), - BADLANDS_PLATEAU("MESA_CLEAR_ROCK"), - SUNFLOWER_PLAINS("MUTATED_PLAINS"), - DESERT_LAKES("MUTATED_DESERT"), - FLOWER_FOREST("MUTATED_FOREST"), - TAIGA_MOUNTAINS("MUTATED_TAIGA"), - SWAMP_HILLS("MUTATED_SWAMPLAND"), - ICE_SPIKES("MUTATED_ICE_FLATS"), - MODIFIED_JUNGLE("MUTATED_JUNGLE"), - MODIFIED_JUNGLE_EDGE("MUTATED_JUNGLE_EDGE"), - SNOWY_TAIGA_MOUNTAINS("MUTATED_TAIGA_COLD"), - SHATTERED_SAVANNA("MUTATED_SAVANNA"), - SHATTERED_SAVANNA_PLATEAU("MUTATED_SAVANNA_ROCK"), - ERODED_BADLANDS("MUTATED_MESA"), - MODIFIED_WOODED_BADLANDS_PLATEAU("MUTATED_MESA_ROCK"), - MODIFIED_BADLANDS_PLATEAU("MUTATED_MESA_CLEAR_ROCK"), - TALL_BIRCH_FOREST("MUTATED_BIRCH_FOREST"), - TALL_BIRCH_HILLS("MUTATED_BIRCH_FOREST_HILLS"), - DARK_FOREST_HILLS("MUTATED_ROOFED_FOREST"), - GIANT_SPRUCE_TAIGA("MUTATED_REDWOOD_TAIGA"), - GRAVELLY_MOUNTAINS("MUTATED_EXTREME_HILLS"), - MODIFIED_GRAVELLY_MOUNTAINS("MUTATED_EXTREME_HILLS_WITH_TREES"), - GIANT_SPRUCE_TAIGA_HILLS("MUTATED_REDWOOD_TAIGA_HILLS"), - THE_VOID("VOID"); - - public static @Nullable To19Mapping getMapping(Biome biome) { - To19Mapping[] values = values(); - - for (To19Mapping value : values) { - if (value.getHandle().equals(biome)) { - return value; - } - } - - return null; - } - - private Biome handle; - - To19Mapping(Biome handle) { - this.handle = handle; - } - - To19Mapping(String name) { - this.handle = Biome.valueOf(name); - } - - public Biome getHandle() { - return this.handle; - } - } -} diff --git a/src/main/java/ch/njol/skript/hooks/biomes/package-info.java b/src/main/java/ch/njol/skript/hooks/biomes/package-info.java deleted file mode 100644 index 5c445adbb67..00000000000 --- a/src/main/java/ch/njol/skript/hooks/biomes/package-info.java +++ /dev/null @@ -1,27 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -/** - * @author Peter Güttinger - */ -@NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) -package ch.njol.skript.hooks.biomes; - -import org.eclipse.jdt.annotation.DefaultLocation; -import org.eclipse.jdt.annotation.NonNullByDefault; - diff --git a/src/main/java/ch/njol/skript/lang/Variable.java b/src/main/java/ch/njol/skript/lang/Variable.java index 9c9f0743e4c..b2184d7cf11 100644 --- a/src/main/java/ch/njol/skript/lang/Variable.java +++ b/src/main/java/ch/njol/skript/lang/Variable.java @@ -328,8 +328,6 @@ private Object get(Event e) { return l.toArray(); } - private final static boolean uuidSupported = Skript.methodExists(OfflinePlayer.class, "getUniqueId"); - /* * Workaround for player variables when a player has left and rejoined * because the player object inside the variable will be a (kinda) dead variable @@ -339,7 +337,7 @@ private Object get(Event e) { if(SkriptConfig.enablePlayerVariableFix.value() && t != null && t instanceof Player){ Player p = (Player) t; if(!p.isValid() && p.isOnline()){ - Player player = uuidSupported ? Bukkit.getPlayer(p.getUniqueId()) : Bukkit.getPlayerExact(p.getName()); + Player player = Bukkit.getPlayer(p.getUniqueId()); Variables.setVariable(key, player, event, local); return player; } diff --git a/src/main/java/ch/njol/skript/util/BiomeUtils.java b/src/main/java/ch/njol/skript/util/BiomeUtils.java index 82d97304798..43d746c1d0c 100644 --- a/src/main/java/ch/njol/skript/util/BiomeUtils.java +++ b/src/main/java/ch/njol/skript/util/BiomeUtils.java @@ -18,30 +18,27 @@ */ package ch.njol.skript.util; -import ch.njol.skript.bukkitutil.BiomeMappings; -import ch.njol.skript.localization.Language; - import org.bukkit.block.Biome; import org.eclipse.jdt.annotation.Nullable; /** - * @author Peter Güttinger + * Contains utility methods related to biomes */ -public abstract class BiomeUtils { - private BiomeUtils() {} - +public class BiomeUtils { + + private final static EnumUtils<Biome> util = new EnumUtils<>(Biome.class, "biomes"); + @Nullable - public static Biome parse(final String s) { - return BiomeMappings.parse(s); + public static Biome parse(String name) { + return util.parse(name); } - - public static String toString(final Biome b, final int flags) { - return BiomeMappings.toString(b, flags); + + public static String toString(Biome biome, int flags) { + return util.toString(biome, flags); } - - public static String getAllNames() { // This is hack for class loading order... - return "Biome names; you can use F3 ingame"; - //return BiomeMappings.getAllNames(); + + public static String getAllNames() { + return util.getAllNames(); } - + } diff --git a/src/main/java/ch/njol/skript/util/BlockStateBlock.java b/src/main/java/ch/njol/skript/util/BlockStateBlock.java index 09304441e5a..dc787ee2e33 100644 --- a/src/main/java/ch/njol/skript/util/BlockStateBlock.java +++ b/src/main/java/ch/njol/skript/util/BlockStateBlock.java @@ -20,7 +20,6 @@ import ch.njol.skript.Skript; import ch.njol.skript.bukkitutil.block.BlockCompat; -import ch.njol.skript.bukkitutil.block.MagicBlockCompat; import com.destroystokyo.paper.block.BlockSoundGroup; import org.bukkit.Bukkit; import org.bukkit.Chunk; @@ -58,7 +57,6 @@ @SuppressWarnings("deprecation") public class BlockStateBlock implements Block { - private static final boolean IS_RUNNING_1_13 = Skript.isRunningMinecraft(1, 13); private static final boolean ISPASSABLE_METHOD_EXISTS = Skript.methodExists(Block.class, "isPassable"); final BlockState state; @@ -104,10 +102,6 @@ public byte getData() { return state.getRawData(); } - public void setData(byte data) throws Throwable { - MagicBlockCompat.setRawDataMethod.invokeExact(state, data); - } - @Override public Block getRelative(int modX, int modY, int modZ) { return state.getBlock().getRelative(modX, modY, modZ); @@ -404,10 +398,6 @@ public Location getLocation(@Nullable Location loc) { @Override public void setType(Material type, boolean applyPhysics) { - if (!IS_RUNNING_1_13) { - throw new IllegalStateException("not on 1.13"); - } - if (delayChanges) { Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.getInstance(), new Runnable() { @Override @@ -422,19 +412,11 @@ public void run() { @Override public BlockData getBlockData() { - if (!IS_RUNNING_1_13) { - throw new IllegalStateException("not on 1.13"); - } - return state.getBlockData(); } @Override public void setBlockData(BlockData data) { - if (!IS_RUNNING_1_13) { - throw new IllegalStateException("not on 1.13"); - } - if (delayChanges) { Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.getInstance(), new Runnable() { @Override @@ -449,10 +431,6 @@ public void run() { @Override public void setBlockData(BlockData data, boolean applyPhysics) { - if (!IS_RUNNING_1_13) { - throw new IllegalStateException("not on 1.13"); - } - if (delayChanges) { Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.getInstance(), new Runnable() { @Override diff --git a/src/main/java/ch/njol/skript/util/DelayedChangeBlock.java b/src/main/java/ch/njol/skript/util/DelayedChangeBlock.java index 3d9671f0aa6..58aa7746075 100644 --- a/src/main/java/ch/njol/skript/util/DelayedChangeBlock.java +++ b/src/main/java/ch/njol/skript/util/DelayedChangeBlock.java @@ -20,7 +20,6 @@ import ch.njol.skript.Skript; import ch.njol.skript.bukkitutil.block.BlockCompat; -import ch.njol.skript.bukkitutil.block.MagicBlockCompat; import com.destroystokyo.paper.block.BlockSoundGroup; import org.bukkit.Bukkit; import org.bukkit.Chunk; @@ -104,10 +103,6 @@ public byte getData() { return b.getData(); } - public void setData(byte data) throws Throwable { - MagicBlockCompat.setDataMethod.invokeExact(b, data); - } - @Override public Block getRelative(int modX, int modY, int modZ) { return b.getRelative(modX, modY, modZ); diff --git a/src/main/java/ch/njol/skript/util/Direction.java b/src/main/java/ch/njol/skript/util/Direction.java index cfefbd0e195..eb8a5f8142f 100644 --- a/src/main/java/ch/njol/skript/util/Direction.java +++ b/src/main/java/ch/njol/skript/util/Direction.java @@ -64,8 +64,6 @@ public class Direction implements YggdrasilRobustSerializable { public final static BlockFace BF_X = findFace(1, 0, 0), BF_Y = findFace(0, 1, 0), BF_Z = findFace(0, 0, 1); - private static final boolean IS_LEGACY_EXISTS = Skript.methodExists(Material.class, "isLegacy"); - private static BlockFace findFace(final int x, final int y, final int z) { for (final BlockFace f : BlockFace.values()) { if (f.getModX() == x && f.getModY() == y && f.getModZ() == z) @@ -217,17 +215,10 @@ public static float getYaw(final double yaw) { */ @SuppressWarnings("deprecation") public static BlockFace getFacing(Block b) { - if (IS_LEGACY_EXISTS) { - BlockData blockData = b.getBlockData(); - if (!(blockData instanceof org.bukkit.block.data.Directional)) - return BlockFace.SELF; - return ((org.bukkit.block.data.Directional) blockData).getFacing(); - } else { - Material m = b.getType(); - if (!Directional.class.isAssignableFrom(m.getData())) - return BlockFace.SELF; - return ((Directional) m.getNewData(b.getData())).getFacing(); - } + BlockData blockData = b.getBlockData(); + if (!(blockData instanceof org.bukkit.block.data.Directional)) + return BlockFace.SELF; + return ((org.bukkit.block.data.Directional) blockData).getFacing(); } public static BlockFace getFacing(final double yaw, final double pitch) { diff --git a/src/main/java/ch/njol/skript/util/SkriptColor.java b/src/main/java/ch/njol/skript/util/SkriptColor.java index 424112a6694..a4006a386eb 100644 --- a/src/main/java/ch/njol/skript/util/SkriptColor.java +++ b/src/main/java/ch/njol/skript/util/SkriptColor.java @@ -45,7 +45,7 @@ public enum SkriptColor implements Color { BLACK(DyeColor.BLACK, ChatColor.BLACK), DARK_GREY(DyeColor.GRAY, ChatColor.DARK_GRAY), // DyeColor.LIGHT_GRAY on 1.13, DyeColor.SILVER on earlier (dye colors were changed in 1.12) - LIGHT_GREY(DyeColor.getByColor(org.bukkit.Color.fromRGB(Skript.isRunningMinecraft(1, 12) ? 0x9D9D97 : 0x999999)), ChatColor.GRAY), + LIGHT_GREY(DyeColor.LIGHT_GRAY, ChatColor.GRAY), WHITE(DyeColor.WHITE, ChatColor.WHITE), DARK_BLUE(DyeColor.BLUE, ChatColor.DARK_BLUE), diff --git a/src/main/java/ch/njol/skript/util/Task.java b/src/main/java/ch/njol/skript/util/Task.java index aa55ae5303b..2fcff33984f 100644 --- a/src/main/java/ch/njol/skript/util/Task.java +++ b/src/main/java/ch/njol/skript/util/Task.java @@ -67,7 +67,6 @@ public Task(final Plugin plugin, final long delay, final boolean async) { * * @param delay */ - @SuppressWarnings("deprecation") private void schedule(final long delay) { assert !isAlive(); if (!Skript.getInstance().isEnabled()) @@ -75,17 +74,13 @@ private void schedule(final long delay) { if (period == -1) { if (async) { - taskID = Skript.isRunningMinecraft(1, 4, 6) ? - Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, this, delay).getTaskId() : - Bukkit.getScheduler().scheduleAsyncDelayedTask(plugin, this, delay); + taskID = Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, this, delay).getTaskId(); } else { taskID = Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, this, delay); } } else { if (async) { - taskID = Skript.isRunningMinecraft(1, 4, 6) ? - Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, this, delay, period).getTaskId() : - Bukkit.getScheduler().scheduleAsyncRepeatingTask(plugin, this, delay, period); + taskID = Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, this, delay, period).getTaskId(); } else { taskID = Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, this, delay, period); } diff --git a/src/main/java/ch/njol/skript/util/chat/BungeeConverter.java b/src/main/java/ch/njol/skript/util/chat/BungeeConverter.java index daf2c884fea..6eafc860c66 100644 --- a/src/main/java/ch/njol/skript/util/chat/BungeeConverter.java +++ b/src/main/java/ch/njol/skript/util/chat/BungeeConverter.java @@ -32,7 +32,6 @@ */ public class BungeeConverter { - private static boolean HAS_INSERTION_SUPPORT = Skript.methodExists(BaseComponent.class, "setInsertion", String.class); private static boolean HAS_FONT_SUPPORT = Skript.methodExists(BaseComponent.class, "setFont", String.class); @SuppressWarnings("null") @@ -46,15 +45,8 @@ public static BaseComponent convert(MessageComponent origin) { base.setObfuscated(origin.obfuscated); if (origin.color != null) base.setColor(origin.color); - /* - * This method doesn't exist on normal spigot 1.8 - * and it's not worth working around since people affected - * can just use paper 1.8 and it will work fine - */ - if (HAS_INSERTION_SUPPORT) { - base.setInsertion(origin.insertion); - } - + base.setInsertion(origin.insertion); + if (origin.clickEvent != null) base.setClickEvent(new ClickEvent(ClickEvent.Action.valueOf(origin.clickEvent.action.spigotName), origin.clickEvent.value)); if (origin.hoverEvent != null) From 0372ad611477b08a1b285bf8d655e643ad19b086 Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Fri, 15 Jul 2022 17:02:05 +0200 Subject: [PATCH 051/619] isCurrentEvent API changes (#4884) --- .../skript/conditions/CondIncendiary.java | 6 +++- .../skript/conditions/CondResourcePack.java | 3 ++ .../skript/effects/EffCancelCooldown.java | 3 ++ .../effects/EffHidePlayerFromServerList.java | 3 ++ .../ch/njol/skript/effects/EffIncendiary.java | 3 ++ .../effects/EffPlayerInfoVisibility.java | 3 ++ .../expressions/ExprAbsorbedBlocks.java | 6 ++++ .../expressions/ExprAppliedEnchantments.java | 6 ++++ .../njol/skript/expressions/ExprAttacked.java | 4 ++- .../skript/expressions/ExprBurnCookTime.java | 11 +++++-- .../skript/expressions/ExprChatFormat.java | 5 ++- .../expressions/ExprChatRecipients.java | 6 ++++ .../njol/skript/expressions/ExprClicked.java | 3 ++ .../ch/njol/skript/expressions/ExprDrops.java | 6 ++++ .../skript/expressions/ExprEnchantItem.java | 7 +++-- .../expressions/ExprEnchantmentOffer.java | 3 ++ .../expressions/ExprExplodedBlocks.java | 3 ++ .../expressions/ExprExplosionBlockYield.java | 5 ++- .../expressions/ExprExplosionYield.java | 5 ++- .../expressions/ExprFertilizedBlocks.java | 6 ++++ .../skript/expressions/ExprHealAmount.java | 6 ++++ .../skript/expressions/ExprHealReason.java | 3 ++ .../njol/skript/expressions/ExprHostname.java | 3 ++ .../skript/expressions/ExprHoverList.java | 6 ++++ .../ch/njol/skript/expressions/ExprIP.java | 12 ++++--- .../ch/njol/skript/expressions/ExprLevel.java | 1 - .../ch/njol/skript/expressions/ExprMOTD.java | 6 ++++ .../skript/expressions/ExprMaxPlayers.java | 6 ++++ .../ch/njol/skript/expressions/ExprMe.java | 3 ++ .../expressions/ExprMendingRepairAmount.java | 6 ++++ .../expressions/ExprOnlinePlayersCount.java | 6 ++++ .../njol/skript/expressions/ExprPortal.java | 6 ++++ .../expressions/ExprProtocolVersion.java | 6 ++++ .../skript/expressions/ExprPushedBlocks.java | 4 +++ .../expressions/ExprRespawnLocation.java | 6 ++++ .../skript/expressions/ExprServerIcon.java | 10 ++++-- .../ch/njol/skript/expressions/ExprTamer.java | 3 ++ .../skript/expressions/ExprVersionString.java | 5 +++ .../skript/lang/parser/ParserInstance.java | 31 +++++++++++++++++-- .../skript/lang/util/SimpleExpression.java | 8 ++--- .../ch/njol/util/coll/CollectionUtils.java | 11 +++++++ 41 files changed, 219 insertions(+), 26 deletions(-) diff --git a/src/main/java/ch/njol/skript/conditions/CondIncendiary.java b/src/main/java/ch/njol/skript/conditions/CondIncendiary.java index 0dc6f06aaaa..8388171783e 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIncendiary.java +++ b/src/main/java/ch/njol/skript/conditions/CondIncendiary.java @@ -72,8 +72,12 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override public boolean check(Event e) { - if (isEvent) + if (isEvent) { + if (!(e instanceof ExplosionPrimeEvent)) + return isNegated(); + return ((ExplosionPrimeEvent) e).getFire() ^ isNegated(); + } return entities.check(e, entity -> entity instanceof Explosive && ((Explosive) entity).isIncendiary(), isNegated()); } diff --git a/src/main/java/ch/njol/skript/conditions/CondResourcePack.java b/src/main/java/ch/njol/skript/conditions/CondResourcePack.java index 79857931e13..f183b1d851e 100644 --- a/src/main/java/ch/njol/skript/conditions/CondResourcePack.java +++ b/src/main/java/ch/njol/skript/conditions/CondResourcePack.java @@ -66,6 +66,9 @@ public boolean init(final Expression<?>[] exprs, final int matchedPattern, final @Override public boolean check(Event e) { + if (!(e instanceof PlayerResourcePackStatusEvent)) + return isNegated(); + Status state = ((PlayerResourcePackStatusEvent) e).getStatus(); return states.check(e, state::equals, isNegated()); } diff --git a/src/main/java/ch/njol/skript/effects/EffCancelCooldown.java b/src/main/java/ch/njol/skript/effects/EffCancelCooldown.java index b45319061b4..ed2aba11578 100644 --- a/src/main/java/ch/njol/skript/effects/EffCancelCooldown.java +++ b/src/main/java/ch/njol/skript/effects/EffCancelCooldown.java @@ -69,6 +69,9 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override protected void execute(Event e) { + if (!(e instanceof ScriptCommandEvent)) + return; + ((ScriptCommandEvent) e).setCooldownCancelled(cancel); } diff --git a/src/main/java/ch/njol/skript/effects/EffHidePlayerFromServerList.java b/src/main/java/ch/njol/skript/effects/EffHidePlayerFromServerList.java index f0e3ec5b396..59ccd415c8e 100644 --- a/src/main/java/ch/njol/skript/effects/EffHidePlayerFromServerList.java +++ b/src/main/java/ch/njol/skript/effects/EffHidePlayerFromServerList.java @@ -75,6 +75,9 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override protected void execute(Event e) { + if (!(e instanceof ServerListPingEvent)) + return; + Iterator<Player> it = ((ServerListPingEvent) e).iterator(); Iterators.removeAll(it, Arrays.asList(players.getArray(e))); } diff --git a/src/main/java/ch/njol/skript/effects/EffIncendiary.java b/src/main/java/ch/njol/skript/effects/EffIncendiary.java index 8feca9341e0..77603226bc8 100644 --- a/src/main/java/ch/njol/skript/effects/EffIncendiary.java +++ b/src/main/java/ch/njol/skript/effects/EffIncendiary.java @@ -74,6 +74,9 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override protected void execute(Event e) { if (isEvent) { + if (!(e instanceof ExplosionPrimeEvent)) + return; + ((ExplosionPrimeEvent) e).setFire(causeFire); } else { for (Entity entity : entities.getArray(e)) { diff --git a/src/main/java/ch/njol/skript/effects/EffPlayerInfoVisibility.java b/src/main/java/ch/njol/skript/effects/EffPlayerInfoVisibility.java index eb3de143793..d0b5bcfcb6a 100644 --- a/src/main/java/ch/njol/skript/effects/EffPlayerInfoVisibility.java +++ b/src/main/java/ch/njol/skript/effects/EffPlayerInfoVisibility.java @@ -77,6 +77,9 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override protected void execute(Event e) { + if (!(e instanceof PaperServerListPingEvent)) + return; + ((PaperServerListPingEvent) e).setHidePlayers(shouldHide); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprAbsorbedBlocks.java b/src/main/java/ch/njol/skript/expressions/ExprAbsorbedBlocks.java index 6ce10859ca4..efec2fc07b6 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprAbsorbedBlocks.java +++ b/src/main/java/ch/njol/skript/expressions/ExprAbsorbedBlocks.java @@ -63,6 +63,9 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable protected BlockStateBlock[] get(Event e) { + if (!(e instanceof SpongeAbsorbEvent)) + return null; + List<BlockState> bs = ((SpongeAbsorbEvent) e).getBlocks(); return bs.stream() .map(BlockStateBlock::new) @@ -72,6 +75,9 @@ protected BlockStateBlock[] get(Event e) { @Override @Nullable public Iterator<BlockStateBlock> iterator(Event e) { + if (!(e instanceof SpongeAbsorbEvent)) + return null; + List<BlockState> bs = ((SpongeAbsorbEvent) e).getBlocks(); return bs.stream() .map(BlockStateBlock::new) diff --git a/src/main/java/ch/njol/skript/expressions/ExprAppliedEnchantments.java b/src/main/java/ch/njol/skript/expressions/ExprAppliedEnchantments.java index 91b79619f1f..2b926b7cd0a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprAppliedEnchantments.java +++ b/src/main/java/ch/njol/skript/expressions/ExprAppliedEnchantments.java @@ -65,6 +65,9 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable protected EnchantmentType[] get(Event e) { + if (!(e instanceof EnchantItemEvent)) + return null; + return ((EnchantItemEvent) e).getEnchantsToAdd().entrySet().stream() .map(entry -> new EnchantmentType(entry.getKey(), entry.getValue())) .toArray(EnchantmentType[]::new); @@ -81,6 +84,9 @@ public Class<?>[] acceptChange(ChangeMode mode) { @SuppressWarnings("null") @Override public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + if (!(event instanceof EnchantItemEvent)) + return; + EnchantmentType[] enchants = new EnchantmentType[delta != null ? delta.length : 0]; if (delta != null && delta.length != 0) { for (int i = 0; i < delta.length; i++) { diff --git a/src/main/java/ch/njol/skript/expressions/ExprAttacked.java b/src/main/java/ch/njol/skript/expressions/ExprAttacked.java index 7eb09593886..67228910e4c 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprAttacked.java +++ b/src/main/java/ch/njol/skript/expressions/ExprAttacked.java @@ -96,8 +96,10 @@ protected Entity[] get(Event e) { entity = ((ProjectileHitEvent) e).getHitEntity(); else entity = ((EntityEvent) e).getEntity(); - else + else if (e instanceof VehicleEvent) entity = ((VehicleEvent) e).getVehicle(); + else + return null; if (type.isInstance(entity)) { one[0] = entity; return one; diff --git a/src/main/java/ch/njol/skript/expressions/ExprBurnCookTime.java b/src/main/java/ch/njol/skript/expressions/ExprBurnCookTime.java index f1a19afe602..27b4ab2a3e4 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprBurnCookTime.java +++ b/src/main/java/ch/njol/skript/expressions/ExprBurnCookTime.java @@ -41,7 +41,6 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser; -import ch.njol.skript.registrations.Classes; import ch.njol.skript.registrations.DefaultClasses; import ch.njol.skript.util.Timespan; import ch.njol.util.Kleenean; @@ -83,9 +82,12 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override protected Timespan[] get(Event e, Block[] source) { - if (isEvent) + if (isEvent) { + if (!(e instanceof FurnaceBurnEvent)) + return null; + return CollectionUtils.array(Timespan.fromTicks_i(((FurnaceBurnEvent) e).getBurnTime())); - else { + } else { Timespan[] result = Arrays.stream(source) .filter(block -> anyFurnace.isOfType(block)) .map(furnace -> { @@ -148,6 +150,9 @@ public void change(Event e, @Nullable Object[] delta, Changer.ChangeMode mode) { assert value != null; // It isn't going to be null but the compiler complains so if (isEvent) { + if (!(e instanceof FurnaceBurnEvent)) + return; + FurnaceBurnEvent event = (FurnaceBurnEvent) e; event.setBurnTime(value.apply(Timespan.fromTicks_i(event.getBurnTime())).getTicks()); return; diff --git a/src/main/java/ch/njol/skript/expressions/ExprChatFormat.java b/src/main/java/ch/njol/skript/expressions/ExprChatFormat.java index dd1400e4895..334c0f1db1b 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprChatFormat.java +++ b/src/main/java/ch/njol/skript/expressions/ExprChatFormat.java @@ -81,13 +81,16 @@ public String toString(@Nullable Event e, boolean debug) { @Override @Nullable protected String[] get(Event e) { + if (!(e instanceof AsyncPlayerChatEvent)) + return null; + return new String[]{convertToFriendly(((AsyncPlayerChatEvent) e).getFormat())}; } //delta[0] has to be a String unless Skript has horribly gone wrong @Override public void change(Event e, @Nullable Object[] delta, Changer.ChangeMode mode) { - if (delta == null){ + if (delta == null || !(e instanceof AsyncPlayerChatEvent)){ return; } String format = null; diff --git a/src/main/java/ch/njol/skript/expressions/ExprChatRecipients.java b/src/main/java/ch/njol/skript/expressions/ExprChatRecipients.java index 8ec86d42b25..bda0a00f294 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprChatRecipients.java +++ b/src/main/java/ch/njol/skript/expressions/ExprChatRecipients.java @@ -81,6 +81,9 @@ public String toString(@Nullable Event event, boolean debug) { @Override @Nullable protected Player[] get(Event event) { + if (!(event instanceof AsyncPlayerChatEvent)) + return null; + AsyncPlayerChatEvent ae = (AsyncPlayerChatEvent) event; Set<Player> playerSet = ae.getRecipients(); return playerSet.toArray(new Player[playerSet.size()]); @@ -88,6 +91,9 @@ protected Player[] get(Event event) { @Override public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + if (!(event instanceof AsyncPlayerChatEvent)) + return; + final Player[] recipients = (Player[]) delta; switch (mode) { case REMOVE: diff --git a/src/main/java/ch/njol/skript/expressions/ExprClicked.java b/src/main/java/ch/njol/skript/expressions/ExprClicked.java index aeb6be7832e..b336da8b8bf 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprClicked.java +++ b/src/main/java/ch/njol/skript/expressions/ExprClicked.java @@ -175,6 +175,9 @@ public Class<? extends Object> getReturnType() { @Override @Nullable protected Object[] get(Event e) { + if (!(e instanceof InventoryClickEvent) && clickable != ClickableType.BLOCK_AND_ITEMS && clickable != ClickableType.ENCHANT_BUTTON) + return null; + switch (clickable) { case BLOCK_AND_ITEMS: if (e instanceof PlayerInteractEvent) { diff --git a/src/main/java/ch/njol/skript/expressions/ExprDrops.java b/src/main/java/ch/njol/skript/expressions/ExprDrops.java index 75eb58d074a..89ae11cb8a5 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprDrops.java +++ b/src/main/java/ch/njol/skript/expressions/ExprDrops.java @@ -72,6 +72,9 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable protected ItemType[] get(Event e) { + if (!(e instanceof EntityDeathEvent)) + return null; + return ((EntityDeathEvent) e).getDrops() .stream() .map(ItemType::new) @@ -100,6 +103,9 @@ public Class<?>[] acceptChange(ChangeMode mode) { @Override public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { + if (!(e instanceof EntityDeathEvent)) + return; + List<ItemStack> drops = ((EntityDeathEvent) e).getDrops(); assert delta != null; for (Object o : delta) { diff --git a/src/main/java/ch/njol/skript/expressions/ExprEnchantItem.java b/src/main/java/ch/njol/skript/expressions/ExprEnchantItem.java index 987c2a3f173..888c1a7e964 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprEnchantItem.java +++ b/src/main/java/ch/njol/skript/expressions/ExprEnchantItem.java @@ -68,7 +68,10 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye protected ItemType[] get(Event e) { if (e instanceof PrepareItemEnchantEvent) return new ItemType[]{new ItemType(((PrepareItemEnchantEvent) e).getItem())}; - return new ItemType[]{new ItemType(((EnchantItemEvent) e).getItem())}; + else if (e instanceof EnchantItemEvent) + return new ItemType[]{new ItemType(((EnchantItemEvent) e).getItem())}; + else + return null; } @Override @@ -91,7 +94,7 @@ public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { e.getItem().setType(item.getMaterial()); e.getItem().setItemMeta(item.getItemMeta()); e.getItem().setAmount(item.getAmount()); - } else { + } else if (event instanceof EnchantItemEvent) { EnchantItemEvent e = (EnchantItemEvent) event; e.getItem().setType(item.getMaterial()); e.getItem().setItemMeta(item.getItemMeta()); diff --git a/src/main/java/ch/njol/skript/expressions/ExprEnchantmentOffer.java b/src/main/java/ch/njol/skript/expressions/ExprEnchantmentOffer.java index 28a7ec0f190..5441217ee6d 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprEnchantmentOffer.java +++ b/src/main/java/ch/njol/skript/expressions/ExprEnchantmentOffer.java @@ -91,6 +91,9 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable protected EnchantmentOffer[] get(Event e) { + if (!(e instanceof PrepareItemEnchantEvent)) + return null; + if (all) return ((PrepareItemEnchantEvent) e).getOffers(); if (exprOfferNumber == null) diff --git a/src/main/java/ch/njol/skript/expressions/ExprExplodedBlocks.java b/src/main/java/ch/njol/skript/expressions/ExprExplodedBlocks.java index 081618edf1a..d08200c7e1a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprExplodedBlocks.java +++ b/src/main/java/ch/njol/skript/expressions/ExprExplodedBlocks.java @@ -62,6 +62,9 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Nullable @Override protected Block[] get(Event e) { + if (!(e instanceof EntityExplodeEvent)) + return null; + List<Block> blockList = ((EntityExplodeEvent) e).blockList(); return blockList.toArray(new Block[blockList.size()]); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprExplosionBlockYield.java b/src/main/java/ch/njol/skript/expressions/ExprExplosionBlockYield.java index bf447c540cd..48d2905afa3 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprExplosionBlockYield.java +++ b/src/main/java/ch/njol/skript/expressions/ExprExplosionBlockYield.java @@ -66,6 +66,9 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable protected Number[] get(Event e) { + if (!(e instanceof EntityExplodeEvent)) + return null; + return new Number[]{((EntityExplodeEvent) e).getYield()}; } @@ -86,7 +89,7 @@ public Class<?>[] acceptChange(final ChangeMode mode) { @Override public void change(final Event event, final @Nullable Object[] delta, final ChangeMode mode) { float n = delta == null ? 0 : ((Number) delta[0]).floatValue(); - if (n < 0) // Yield can't be negative + if (n < 0 || !(event instanceof EntityExplodeEvent)) // Yield can't be negative return; EntityExplodeEvent e = (EntityExplodeEvent) event; // Yield can be a value from 0 to 1 diff --git a/src/main/java/ch/njol/skript/expressions/ExprExplosionYield.java b/src/main/java/ch/njol/skript/expressions/ExprExplosionYield.java index 8e6c65943b0..eea0c5fdd0a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprExplosionYield.java +++ b/src/main/java/ch/njol/skript/expressions/ExprExplosionYield.java @@ -66,6 +66,9 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable protected Number[] get(Event e) { + if (!(e instanceof ExplosionPrimeEvent)) + return null; + return new Number[]{((ExplosionPrimeEvent) e).getRadius()}; } @@ -86,7 +89,7 @@ public Class<?>[] acceptChange(final ChangeMode mode) { @Override public void change(final Event event, final @Nullable Object[] delta, final ChangeMode mode) { float f = delta == null ? 0 : ((Number) delta[0]).floatValue(); - if (f < 0) // Negative values will throw an error. + if (f < 0 || !(event instanceof ExplosionPrimeEvent)) // Negative values will throw an error. return; ExplosionPrimeEvent e = (ExplosionPrimeEvent) event; switch (mode) { diff --git a/src/main/java/ch/njol/skript/expressions/ExprFertilizedBlocks.java b/src/main/java/ch/njol/skript/expressions/ExprFertilizedBlocks.java index 1129b6c96db..c6032f35814 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprFertilizedBlocks.java +++ b/src/main/java/ch/njol/skript/expressions/ExprFertilizedBlocks.java @@ -64,6 +64,9 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Nullable @Override protected BlockStateBlock[] get(Event e) { + if (!(e instanceof BlockFertilizeEvent)) + return null; + return ((BlockFertilizeEvent) e).getBlocks().stream() .map(BlockStateBlock::new) .toArray(BlockStateBlock[]::new); @@ -72,6 +75,9 @@ protected BlockStateBlock[] get(Event e) { @Nullable @Override public Iterator<? extends BlockStateBlock> iterator(Event e) { + if (!(e instanceof BlockFertilizeEvent)) + return null; + return ((BlockFertilizeEvent) e).getBlocks().stream() .map(BlockStateBlock::new) .iterator(); diff --git a/src/main/java/ch/njol/skript/expressions/ExprHealAmount.java b/src/main/java/ch/njol/skript/expressions/ExprHealAmount.java index 40654affa36..9e74f6aa68e 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprHealAmount.java +++ b/src/main/java/ch/njol/skript/expressions/ExprHealAmount.java @@ -66,6 +66,9 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Nullable @Override protected Number[] get(Event e) { + if (!(e instanceof EntityRegainHealthEvent)) + return null; + return new Number[]{((EntityRegainHealthEvent) e).getAmount()}; } @@ -83,6 +86,9 @@ public Class<?>[] acceptChange(Changer.ChangeMode mode) { @Override public void change(Event e, @Nullable Object[] delta, Changer.ChangeMode mode) { + if (!(e instanceof EntityRegainHealthEvent)) + return; + double value = delta == null ? 0 : ((Number) delta[0]).doubleValue(); switch (mode) { case SET: diff --git a/src/main/java/ch/njol/skript/expressions/ExprHealReason.java b/src/main/java/ch/njol/skript/expressions/ExprHealReason.java index aab6acc0494..77be703000b 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprHealReason.java +++ b/src/main/java/ch/njol/skript/expressions/ExprHealReason.java @@ -60,6 +60,9 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Nullable @Override protected RegainReason[] get(Event e) { + if (!(e instanceof EntityRegainHealthEvent)) + return null; + return new RegainReason[]{((EntityRegainHealthEvent) e).getRegainReason()}; } diff --git a/src/main/java/ch/njol/skript/expressions/ExprHostname.java b/src/main/java/ch/njol/skript/expressions/ExprHostname.java index 54fdce95b4f..5019cf8764a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprHostname.java +++ b/src/main/java/ch/njol/skript/expressions/ExprHostname.java @@ -59,6 +59,9 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable protected String[] get(Event e) { + if (!(e instanceof PlayerLoginEvent)) + return null; + return new String[] {((PlayerLoginEvent) e).getHostname()}; } diff --git a/src/main/java/ch/njol/skript/expressions/ExprHoverList.java b/src/main/java/ch/njol/skript/expressions/ExprHoverList.java index afafbcb33c7..64cfca1c603 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprHoverList.java +++ b/src/main/java/ch/njol/skript/expressions/ExprHoverList.java @@ -83,6 +83,9 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable public String[] get(Event e) { + if (!(e instanceof PaperServerListPingEvent)) + return null; + return ((PaperServerListPingEvent) e).getPlayerSample().stream() .map(PlayerProfile::getName) .toArray(String[]::new); @@ -109,6 +112,9 @@ public Class<?>[] acceptChange(ChangeMode mode) { @SuppressWarnings("null") @Override public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { + if (!(e instanceof PaperServerListPingEvent)) + return; + List<PlayerProfile> values = new ArrayList<>(); if (mode != ChangeMode.DELETE && mode != ChangeMode.RESET) { for (Object o : delta) { diff --git a/src/main/java/ch/njol/skript/expressions/ExprIP.java b/src/main/java/ch/njol/skript/expressions/ExprIP.java index 6dc3521c052..f176d647ccf 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprIP.java +++ b/src/main/java/ch/njol/skript/expressions/ExprIP.java @@ -67,13 +67,13 @@ public class ExprIP extends SimpleExpression<String> { @SuppressWarnings("null") private Expression<Player> players; - private boolean isConnectEvent, isProperty; + private boolean isProperty; @SuppressWarnings({"null", "unchecked"}) @Override public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { isProperty = matchedPattern < 2; - isConnectEvent = getParser().isCurrentEvent(PlayerLoginEvent.class); + boolean isConnectEvent = getParser().isCurrentEvent(PlayerLoginEvent.class); boolean isServerPingEvent = getParser().isCurrentEvent(ServerListPingEvent.class) || (PAPER_EVENT_EXISTS && getParser().isCurrentEvent(PaperServerListPingEvent.class)); if (isProperty) { @@ -90,12 +90,14 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye protected String[] get(Event e) { if (!isProperty) { InetAddress address; - if (isConnectEvent) + if (e instanceof PlayerLoginEvent) // Return IP address of the connected player in connect event address = ((PlayerLoginEvent) e).getAddress(); - else + else if (e instanceof ServerListPingEvent) // Return IP address of the pinger in server list ping event address = ((ServerListPingEvent) e).getAddress(); + else + return null; return CollectionUtils.array(address.getHostAddress()); } @@ -111,7 +113,7 @@ private String getIP(Player player, Event e) { InetAddress address; // The player has no IP yet in a connect event, but the event has it // It is a "feature" of Spigot, apparently - if (isConnectEvent && ((PlayerLoginEvent) e).getPlayer().equals(player)) { + if (e instanceof PlayerLoginEvent && ((PlayerLoginEvent) e).getPlayer().equals(player)) { address = ((PlayerLoginEvent) e).getAddress(); } else { InetSocketAddress sockAddr = player.getAddress(); diff --git a/src/main/java/ch/njol/skript/expressions/ExprLevel.java b/src/main/java/ch/njol/skript/expressions/ExprLevel.java index 89f14ff718f..d55550616e2 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprLevel.java +++ b/src/main/java/ch/njol/skript/expressions/ExprLevel.java @@ -27,7 +27,6 @@ import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer.ChangeMode; -import ch.njol.skript.classes.Converter; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Events; import ch.njol.skript.doc.Examples; diff --git a/src/main/java/ch/njol/skript/expressions/ExprMOTD.java b/src/main/java/ch/njol/skript/expressions/ExprMOTD.java index 4a20b618f2f..819e5004fc6 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprMOTD.java +++ b/src/main/java/ch/njol/skript/expressions/ExprMOTD.java @@ -69,6 +69,9 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable public String[] get(Event e) { + if (!isDefault && !(e instanceof ServerListPingEvent)) + return null; + if (isDefault) return CollectionUtils.array(Bukkit.getMotd()); else @@ -96,6 +99,9 @@ public Class<?>[] acceptChange(ChangeMode mode) { @SuppressWarnings("null") @Override public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { + if (!(e instanceof ServerListPingEvent)) + return; + ServerListPingEvent event = (ServerListPingEvent) e; switch (mode) { case SET: diff --git a/src/main/java/ch/njol/skript/expressions/ExprMaxPlayers.java b/src/main/java/ch/njol/skript/expressions/ExprMaxPlayers.java index a676cad253c..2ecdea9346c 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprMaxPlayers.java +++ b/src/main/java/ch/njol/skript/expressions/ExprMaxPlayers.java @@ -70,6 +70,9 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable public Long[] get(Event e) { + if (!isReal && !(e instanceof ServerListPingEvent)) + return null; + if (isReal) return CollectionUtils.array((long) Bukkit.getMaxPlayers()); else @@ -99,6 +102,9 @@ public Class<?>[] acceptChange(ChangeMode mode) { @SuppressWarnings("null") @Override public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { + if (!(e instanceof ServerListPingEvent)) + return; + ServerListPingEvent event = (ServerListPingEvent) e; switch (mode) { case SET: diff --git a/src/main/java/ch/njol/skript/expressions/ExprMe.java b/src/main/java/ch/njol/skript/expressions/ExprMe.java index a143af15bb4..089fe710f5f 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprMe.java +++ b/src/main/java/ch/njol/skript/expressions/ExprMe.java @@ -53,6 +53,9 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable protected Player[] get(Event e) { + if (!(e instanceof EffectCommandEvent)) + return null; + CommandSender commandSender = ((EffectCommandEvent) e).getSender(); if (commandSender instanceof Player) return new Player[] {(Player) commandSender}; diff --git a/src/main/java/ch/njol/skript/expressions/ExprMendingRepairAmount.java b/src/main/java/ch/njol/skript/expressions/ExprMendingRepairAmount.java index b75235cb50e..b7e5d27a694 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprMendingRepairAmount.java +++ b/src/main/java/ch/njol/skript/expressions/ExprMendingRepairAmount.java @@ -60,6 +60,9 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override protected Long[] get(final Event e) { + if (!(e instanceof PlayerItemMendEvent)) + return null; + return new Long[]{(long) ((PlayerItemMendEvent) e).getRepairAmount()}; } @@ -79,6 +82,9 @@ public Class<?>[] acceptChange(ChangeMode mode) { @Override public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + if (!(event instanceof PlayerItemMendEvent)) + return; + PlayerItemMendEvent e = (PlayerItemMendEvent) event; int newLevel = delta != null ? ((Number) delta[0]).intValue() : 0; switch (mode) { diff --git a/src/main/java/ch/njol/skript/expressions/ExprOnlinePlayersCount.java b/src/main/java/ch/njol/skript/expressions/ExprOnlinePlayersCount.java index 6edae5a9ed1..ad4a6055125 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprOnlinePlayersCount.java +++ b/src/main/java/ch/njol/skript/expressions/ExprOnlinePlayersCount.java @@ -79,6 +79,9 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable public Long[] get(Event e) { + if (!isReal && !(e instanceof PaperServerListPingEvent)) + return null; + if (isReal) return CollectionUtils.array((long) Bukkit.getOnlinePlayers().size()); else @@ -108,6 +111,9 @@ public Class<?>[] acceptChange(ChangeMode mode) { @SuppressWarnings("null") @Override public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { + if (!(e instanceof PaperServerListPingEvent)) + return; + PaperServerListPingEvent event = (PaperServerListPingEvent) e; switch (mode) { case SET: diff --git a/src/main/java/ch/njol/skript/expressions/ExprPortal.java b/src/main/java/ch/njol/skript/expressions/ExprPortal.java index ae783d0d271..77254c1052c 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprPortal.java +++ b/src/main/java/ch/njol/skript/expressions/ExprPortal.java @@ -68,6 +68,9 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Nullable @Override protected Block[] get(Event e) { + if (!(e instanceof PortalCreateEvent)) + return null; + List<?> blocks = ((PortalCreateEvent) e).getBlocks(); if (USING_BLOCKSTATE) return blocks.stream() @@ -81,6 +84,9 @@ protected Block[] get(Event e) { @Nullable @Override public Iterator<Block> iterator(Event e) { + if (!(e instanceof PortalCreateEvent)) + return null; + List<?> blocks = ((PortalCreateEvent) e).getBlocks(); if (USING_BLOCKSTATE) return blocks.stream() diff --git a/src/main/java/ch/njol/skript/expressions/ExprProtocolVersion.java b/src/main/java/ch/njol/skript/expressions/ExprProtocolVersion.java index 276685c8347..405ef5759f7 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprProtocolVersion.java +++ b/src/main/java/ch/njol/skript/expressions/ExprProtocolVersion.java @@ -79,6 +79,9 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable public Long[] get(Event e) { + if (!(e instanceof PaperServerListPingEvent)) + return null; + return CollectionUtils.array((long) ((PaperServerListPingEvent) e).getProtocolVersion()); } @@ -97,6 +100,9 @@ public Class<?>[] acceptChange(ChangeMode mode) { @SuppressWarnings("null") @Override public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { + if (!(e instanceof PaperServerListPingEvent)) + return; + ((PaperServerListPingEvent) e).setProtocolVersion(((Number) delta[0]).intValue()); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprPushedBlocks.java b/src/main/java/ch/njol/skript/expressions/ExprPushedBlocks.java index f3deeb874c6..b9b15068fc5 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprPushedBlocks.java +++ b/src/main/java/ch/njol/skript/expressions/ExprPushedBlocks.java @@ -18,6 +18,7 @@ */ package ch.njol.skript.expressions; +import ch.njol.util.coll.CollectionUtils; import org.bukkit.block.Block; import org.bukkit.event.Event; import org.bukkit.event.block.BlockPistonExtendEvent; @@ -59,6 +60,9 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable protected Block[] get(Event e) { + if (!CollectionUtils.isAnyInstanceOf(e, BlockPistonExtendEvent.class, BlockPistonRetractEvent.class)) + return null; + return (e instanceof BlockPistonExtendEvent) ? ((BlockPistonExtendEvent) e).getBlocks().toArray(new Block[0]) : ((BlockPistonRetractEvent) e).getBlocks().toArray(new Block[0]); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprRespawnLocation.java b/src/main/java/ch/njol/skript/expressions/ExprRespawnLocation.java index 48bae0410e8..ad330795019 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprRespawnLocation.java +++ b/src/main/java/ch/njol/skript/expressions/ExprRespawnLocation.java @@ -61,6 +61,9 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable protected Location[] get(Event event) { + if (!(event instanceof PlayerRespawnEvent)) + return null; + return CollectionUtils.array(((PlayerRespawnEvent)event).getRespawnLocation()); } @@ -89,6 +92,9 @@ public Class<?>[] acceptChange(Changer.ChangeMode mode) { @Override public void change(Event event, @Nullable Object[] delta, Changer.ChangeMode mode) { + if (!(event instanceof PlayerRespawnEvent)) + return; + if (delta != null) ((PlayerRespawnEvent)event).setRespawnLocation((Location)delta[0]); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprServerIcon.java b/src/main/java/ch/njol/skript/expressions/ExprServerIcon.java index c69109e81ac..13d8a9240cb 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprServerIcon.java +++ b/src/main/java/ch/njol/skript/expressions/ExprServerIcon.java @@ -77,10 +77,13 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Nullable public CachedServerIcon[] get(Event e) { CachedServerIcon icon = null; - if ((isServerPingEvent && !isDefault) && PAPER_EVENT_EXISTS) + if ((isServerPingEvent && !isDefault) && PAPER_EVENT_EXISTS) { + if (!(e instanceof PaperServerListPingEvent)) + return null; icon = ((PaperServerListPingEvent) e).getServerIcon(); - else + } else { icon = Bukkit.getServerIcon(); + } if (icon == null || icon.getData() == null) return null; return CollectionUtils.array(icon); @@ -103,6 +106,9 @@ public Class<?>[] acceptChange(ChangeMode mode) { @SuppressWarnings("null") @Override public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { + if (!(e instanceof PaperServerListPingEvent)) + return; + PaperServerListPingEvent event = (PaperServerListPingEvent) e; switch (mode) { case SET: diff --git a/src/main/java/ch/njol/skript/expressions/ExprTamer.java b/src/main/java/ch/njol/skript/expressions/ExprTamer.java index 0b2d92a6f35..83afa638946 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTamer.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTamer.java @@ -57,6 +57,9 @@ public boolean init(final Expression<?>[] exprs, final int matchedPattern, final @Override protected Player[] get(final Event e) { + if (!(e instanceof EntityTameEvent)) + return null; + return new Player[] {((EntityTameEvent) e).getOwner() instanceof Player ? (Player) ((EntityTameEvent) e).getOwner() : null}; } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVersionString.java b/src/main/java/ch/njol/skript/expressions/ExprVersionString.java index 85d050ab2fd..e94202afe4b 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVersionString.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVersionString.java @@ -71,6 +71,8 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable public String[] get(Event e) { + if (!(e instanceof PaperServerListPingEvent)) + return null; return CollectionUtils.array(((PaperServerListPingEvent) e).getVersion()); } @@ -89,6 +91,9 @@ public Class<?>[] acceptChange(ChangeMode mode) { @SuppressWarnings("null") @Override public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { + if (!(e instanceof PaperServerListPingEvent)) + return; + ((PaperServerListPingEvent) e).setVersion(((String) delta[0])); } diff --git a/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java b/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java index 7d15a438cf2..e0923ca8ae2 100644 --- a/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java +++ b/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java @@ -242,16 +242,41 @@ public void deleteCurrentEvent() { setCurrentEvents(null); hasDelayBefore = Kleenean.FALSE; } - + + /** + * This method checks whether <i>at least one</i> of the current event classes + * is covered by the argument event class (i.e. equal to the class or a subclass of it). + * <br> + * Using this method in an event-specific syntax element requires a runtime check, for example <br> + * {@code if (!(e instanceof BlockBreakEvent)) return null;} + * <br> + * This check is required because there can be more than 1 event class at parse-time, but this method + * only checks if one of them matches the argument class. + * + * <br><br> + * See also {@link #isCurrentEvent(Class[])} for checking with multiple argument classes + */ public boolean isCurrentEvent(@Nullable Class<? extends Event> event) { return CollectionUtils.containsSuperclass(currentEvents, event); } - + + /** + * Same as {@link #isCurrentEvent(Class)}, but allows for plural argument input. + * <br> + * This means that this method will return whether any of the current event classes is covered + * by any of the argument classes. + * <br> + * Using this method in an event-specific syntax element {@link #isCurrentEvent(Class) requires a runtime check}, + * you can use {@link CollectionUtils#isAnyInstanceOf(Object, Class[])} for this, for example: <br> + * {@code if (!CollectionUtils.isAnyInstanceOf(e, BlockBreakEvent.class, BlockPlaceEvent.class)) return null;} + * + * @see #isCurrentEvent(Class) + */ @SafeVarargs public final boolean isCurrentEvent(Class<? extends Event>... events) { return CollectionUtils.containsAnySuperclass(currentEvents, events); } - + /* * Addon data */ diff --git a/src/main/java/ch/njol/skript/lang/util/SimpleExpression.java b/src/main/java/ch/njol/skript/lang/util/SimpleExpression.java index 6f5360dc308..5aba0270fb1 100644 --- a/src/main/java/ch/njol/skript/lang/util/SimpleExpression.java +++ b/src/main/java/ch/njol/skript/lang/util/SimpleExpression.java @@ -292,11 +292,9 @@ protected final boolean setTime(final int time, final Expression<?> mustbeDefaul } if (!mustbeDefaultVar.isDefault()) return false; - for (final Class<? extends Event> e : applicableEvents) { - if (getParser().isCurrentEvent(e)) { - this.time = time; - return true; - } + if (getParser().isCurrentEvent(applicableEvents)) { + this.time = time; + return true; } return false; } diff --git a/src/main/java/ch/njol/util/coll/CollectionUtils.java b/src/main/java/ch/njol/util/coll/CollectionUtils.java index 925a706befa..21bad6f36df 100644 --- a/src/main/java/ch/njol/util/coll/CollectionUtils.java +++ b/src/main/java/ch/njol/util/coll/CollectionUtils.java @@ -251,6 +251,17 @@ public static boolean containsAnySuperclass(final @Nullable Class<?>[] classes, } return false; } + + /** + * @return whether the given object is an instance of any of the given classes + */ + public static boolean isAnyInstanceOf(Object object, Class<?>... classes) { + for (Class<?> clazz : classes) { + if (clazz.isInstance(object)) + return true; + } + return false; + } private final static Random random = new Random(); From 70ad4f2f479b8ebba2954f33e067212d3e26b3fc Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Fri, 15 Jul 2022 09:11:24 -0600 Subject: [PATCH 052/619] Update Target (#4836) --- .../njol/skript/expressions/ExprTarget.java | 98 +++++++++++++------ src/main/java/ch/njol/skript/util/Utils.java | 83 +++++----------- 2 files changed, 94 insertions(+), 87 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprTarget.java b/src/main/java/ch/njol/skript/expressions/ExprTarget.java index ef9a6f851cf..c3852a873ee 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTarget.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTarget.java @@ -18,11 +18,14 @@ */ package ch.njol.skript.expressions; -import org.bukkit.entity.Creature; +import java.util.List; + import org.bukkit.entity.Entity; import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Mob; import org.bukkit.event.Event; import org.bukkit.event.entity.EntityTargetEvent; +import org.bukkit.util.Vector; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; @@ -39,7 +42,6 @@ import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.registrations.Classes; -import ch.njol.skript.util.Utils; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; @@ -53,80 +55,116 @@ "\tsend \"You're being followed by an %entity%!\" to target of entity"}) @Since("<i>unknown</i> (before 2.1)") public class ExprTarget extends PropertyExpression<LivingEntity, Entity> { + static { Skript.registerExpression(ExprTarget.class, Entity.class, ExpressionType.PROPERTY, "[the] target[[ed] %-*entitydata%] [of %livingentities%]", "%livingentities%'[s] target[[ed] %-*entitydata%]"); } - + @Nullable EntityData<?> type; - - @SuppressWarnings({"unchecked", "null"}) + + @SuppressWarnings("unchecked") @Override - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parser) { + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) { type = exprs[matchedPattern] == null ? null : (EntityData<?>) exprs[matchedPattern].getSingle(null); setExpr((Expression<? extends LivingEntity>) exprs[1 - matchedPattern]); return true; } - + @Override - protected Entity[] get(final Event e, final LivingEntity[] source) { + protected Entity[] get(Event e, LivingEntity[] source) { return get(source, new Converter<LivingEntity, Entity>() { @Override @Nullable - public Entity convert(final LivingEntity en) { + public Entity convert(LivingEntity en) { if (getTime() >= 0 && e instanceof EntityTargetEvent && en.equals(((EntityTargetEvent) e).getEntity()) && !Delay.isDelayed(e)) { - final Entity t = ((EntityTargetEvent) e).getTarget(); - if (t == null || type != null && !type.isInstance(t)) + Entity target = ((EntityTargetEvent) e).getTarget(); + if (target == null || type != null && !type.isInstance(target)) return null; - return t; + return target; } - return Utils.getTarget(en, type); + return getTarget(en, type); } }); } - + @Override public Class<? extends Entity> getReturnType() { return type != null ? type.getType() : Entity.class; } - + @Override - public String toString(final @Nullable Event e, final boolean debug) { + public String toString(@Nullable Event e, boolean debug) { if (e == null) return "the target" + (type == null ? "" : "ed " + type) + (getExpr().isDefault() ? "" : " of " + getExpr().toString(e, debug)); return Classes.getDebugMessage(getAll(e)); } - + @Override - public boolean setTime(final int time) { + public boolean setTime(int time) { return super.setTime(time, EntityTargetEvent.class, getExpr()); } - + @Override @Nullable - public Class<?>[] acceptChange(final ChangeMode mode) { + public Class<?>[] acceptChange(ChangeMode mode) { if (mode == ChangeMode.SET || mode == ChangeMode.DELETE) return CollectionUtils.array(LivingEntity.class); return super.acceptChange(mode); } - + @Override - public void change(final Event e, final @Nullable Object[] delta, final ChangeMode mode) { + public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { if (mode == ChangeMode.SET || mode == ChangeMode.DELETE) { - final LivingEntity target = delta == null ? null : (LivingEntity) delta[0]; - for (final LivingEntity entity : getExpr().getArray(e)) { + LivingEntity target = delta == null ? null : (LivingEntity) delta[0]; + for (LivingEntity entity : getExpr().getArray(e)) { if (getTime() >= 0 && e instanceof EntityTargetEvent && entity.equals(((EntityTargetEvent) e).getEntity()) && !Delay.isDelayed(e)) { ((EntityTargetEvent) e).setTarget(target); - } else { - if (entity instanceof Creature) - ((Creature) entity).setTarget(target); + } else if (entity instanceof Mob) { + ((Mob) entity).setTarget(target); } } - } else { - super.change(e, delta, mode); + return; } + super.change(e, delta, mode); } - + + /** + * Gets an entity's target. + * + * @param entity The entity to get the target of + * @param type Can be null for any entity + * @return The entity's target + */ + //TODO Switch this over to RayTraceResults 1.13+ when 1.12 support is dropped. + @SuppressWarnings("unchecked") + @Nullable + public static <T extends Entity> T getTarget(LivingEntity entity, @Nullable EntityData<T> type) { + if (entity instanceof Mob) + return ((Mob) entity).getTarget() == null || type != null && !type.isInstance(((Mob) entity).getTarget()) ? null : (T) ((Mob) entity).getTarget(); + + Vector direction = entity.getLocation().getDirection().normalize(); + Vector eye = entity.getEyeLocation().toVector(); + double cos45 = Math.cos(Math.PI / 4); + double targetDistanceSquared = 0; + double radiusSquared = 1; + T target = null; + + for (T other : type == null ? (List<T>) entity.getWorld().getEntities() : entity.getWorld().getEntitiesByClass(type.getType())) { + if (other == null || other == entity || type != null && !type.isInstance(other)) + continue; + + if (target == null || targetDistanceSquared > other.getLocation().distanceSquared(entity.getLocation())) { + Vector t = other.getLocation().add(0, 1, 0).toVector().subtract(eye); + if (direction.clone().crossProduct(t).lengthSquared() < radiusSquared && t.normalize().dot(direction) >= cos45) { + target = other; + targetDistanceSquared = target.getLocation().distanceSquared(entity.getLocation()); + } + } + } + return target; + } + } diff --git a/src/main/java/ch/njol/skript/util/Utils.java b/src/main/java/ch/njol/skript/util/Utils.java index 076ba17aabd..7598fb83b74 100644 --- a/src/main/java/ch/njol/skript/util/Utils.java +++ b/src/main/java/ch/njol/skript/util/Utils.java @@ -18,29 +18,6 @@ */ package ch.njol.skript.util; -import ch.njol.skript.Skript; -import ch.njol.skript.effects.EffTeleport; -import ch.njol.skript.entity.EntityData; -import ch.njol.skript.localization.Language; -import ch.njol.skript.localization.LanguageChangeListener; -import ch.njol.skript.registrations.Classes; -import ch.njol.util.*; -import ch.njol.util.coll.CollectionUtils; -import com.google.common.collect.Iterables; -import com.google.common.io.ByteArrayDataInput; -import com.google.common.io.ByteArrayDataOutput; -import com.google.common.io.ByteStreams; -import net.md_5.bungee.api.ChatColor; -import org.bukkit.Bukkit; -import org.bukkit.entity.Creature; -import org.bukkit.entity.Entity; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.plugin.messaging.Messenger; -import org.bukkit.plugin.messaging.PluginMessageListener; -import org.bukkit.util.Vector; -import org.eclipse.jdt.annotation.Nullable; - import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -52,6 +29,30 @@ import java.util.regex.Pattern; import java.util.stream.Stream; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +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; +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; + +import ch.njol.skript.Skript; +import ch.njol.skript.effects.EffTeleport; +import ch.njol.skript.localization.Language; +import ch.njol.skript.localization.LanguageChangeListener; +import ch.njol.skript.registrations.Classes; +import ch.njol.util.Callback; +import ch.njol.util.Checker; +import ch.njol.util.NonNullPair; +import ch.njol.util.Pair; +import ch.njol.util.StringUtils; +import ch.njol.util.coll.CollectionUtils; +import net.md_5.bungee.api.ChatColor; + /** * Utility class. * @@ -88,44 +89,12 @@ public static String join(final Iterable<?> objects) { return "" + b.toString(); } - + @SuppressWarnings("unchecked") public static <T> boolean isEither(@Nullable T compared, @Nullable T... types) { return CollectionUtils.contains(types, compared); } - /** - * Gets an entity's target. - * - * @param entity The entity to get the target of - * @param type Can be null for any entity - * @return The entity's target - */ - @SuppressWarnings("unchecked") - @Nullable - public static <T extends Entity> T getTarget(final LivingEntity entity, @Nullable final EntityData<T> type) { - if (entity instanceof Creature) { - return ((Creature) entity).getTarget() == null || type != null && !type.isInstance(((Creature) entity).getTarget()) ? null : (T) ((Creature) entity).getTarget(); - } - T target = null; - double targetDistanceSquared = 0; - final double radiusSquared = 1; - final Vector l = entity.getEyeLocation().toVector(), n = entity.getLocation().getDirection().normalize(); - final double cos45 = Math.cos(Math.PI / 4); - for (final T other : type == null ? (List<T>) entity.getWorld().getEntities() : entity.getWorld().getEntitiesByClass(type.getType())) { - if (other == null || other == entity || type != null && !type.isInstance(other)) - continue; - if (target == null || targetDistanceSquared > other.getLocation().distanceSquared(entity.getLocation())) { - final Vector t = other.getLocation().add(0, 1, 0).toVector().subtract(l); - if (n.clone().crossProduct(t).lengthSquared() < radiusSquared && t.normalize().dot(n) >= cos45) { - target = other; - targetDistanceSquared = target.getLocation().distanceSquared(entity.getLocation()); - } - } - } - return target; - } - - public static Pair<String, Integer> getAmount(final String s) { + public static Pair<String, Integer> getAmount(String s) { if (s.matches("\\d+ of .+")) { return new Pair<>(s.split(" ", 3)[2], Utils.parseInt("" + s.split(" ", 2)[0])); } else if (s.matches("\\d+ .+")) { From 63ab42eb7f36cf4e6bf8dd54220fbfb4fcf0992d Mon Sep 17 00:00:00 2001 From: sovdee <ethansovde@gmail.com> Date: Fri, 15 Jul 2022 09:01:03 -0700 Subject: [PATCH 053/619] Syntax for 1.18 Freezing Mechanics (#4781) --- .../njol/skript/conditions/CondIsFrozen.java | 53 +++++++++ .../skript/expressions/ExprFreezeTicks.java | 112 ++++++++++++++++++ .../expressions/ExprMaxFreezeTicks.java | 64 ++++++++++ .../syntaxes/expressions/ExprFreezeTicks.sk | 18 +++ 4 files changed, 247 insertions(+) create mode 100644 src/main/java/ch/njol/skript/conditions/CondIsFrozen.java create mode 100644 src/main/java/ch/njol/skript/expressions/ExprFreezeTicks.java create mode 100644 src/main/java/ch/njol/skript/expressions/ExprMaxFreezeTicks.java create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprFreezeTicks.sk diff --git a/src/main/java/ch/njol/skript/conditions/CondIsFrozen.java b/src/main/java/ch/njol/skript/conditions/CondIsFrozen.java new file mode 100644 index 00000000000..f5e7b69e439 --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondIsFrozen.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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.conditions; + +import ch.njol.skript.Skript; +import ch.njol.skript.conditions.base.PropertyCondition; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import org.bukkit.entity.Entity; + +@Name("Is Frozen") +@Description("Checks whether an entity is frozen.") +@Examples({ + "if player is frozen:", + "\tkill player" +}) +@Since("INSERT VERSION") +public class CondIsFrozen extends PropertyCondition<Entity> { + + static { + if (Skript.methodExists(Entity.class, "isFrozen")) + register(CondIsFrozen.class, "frozen", "entities"); + } + + @Override + public boolean check(Entity entity) { + return entity.isFrozen(); + } + + @Override + protected String getPropertyName() { + return "frozen"; + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprFreezeTicks.java b/src/main/java/ch/njol/skript/expressions/ExprFreezeTicks.java new file mode 100644 index 00000000000..e21562e72de --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprFreezeTicks.java @@ -0,0 +1,112 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.util.Timespan; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.entity.Entity; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Freeze Time") +@Description("How much time an entity has been in powdered snow for.") +@Examples({ + "player's freeze time is less than 3 seconds:", + "\tsend \"you're about to freeze!\" to the player" +}) +@Since("INSERT VERSION") +public class ExprFreezeTicks extends SimplePropertyExpression<Entity, Timespan> { + + static { + if (Skript.methodExists(Entity.class, "getFreezeTicks")) + register(ExprFreezeTicks.class, Timespan.class, "freeze time", "entities"); + } + + @Override + @Nullable + public Timespan convert(Entity entity) { + return Timespan.fromTicks_i(entity.getFreezeTicks()); + } + + @Override + @Nullable + public Class<?>[] acceptChange(ChangeMode mode) { + return (mode != ChangeMode.REMOVE_ALL) ? CollectionUtils.array(Timespan.class) : null; + } + + @Override + public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { + int time = delta == null ? 0 : (int) ((Timespan) delta[0]).getTicks_i(); + int newTime; + switch (mode) { + case ADD: + for (Entity entity : getExpr().getArray(e)) { + newTime = entity.getFreezeTicks() + time; + setFreezeTicks(entity, newTime); + } + break; + case REMOVE: + for (Entity entity : getExpr().getArray(e)) { + newTime = entity.getFreezeTicks() - time; + setFreezeTicks(entity, newTime); + } + break; + case SET: + for (Entity entity : getExpr().getArray(e)) { + setFreezeTicks(entity, time); + } + break; + case DELETE: + case RESET: + for (Entity entity : getExpr().getArray(e)) { + setFreezeTicks(entity, 0); + } + break; + default: + assert false; + } + } + + @Override + public Class<? extends Timespan> getReturnType() { + return Timespan.class; + } + + @Override + protected String getPropertyName() { + return "freeze time"; + } + + private void setFreezeTicks(Entity entity, int ticks) { + //Limit time to between 0 and max + if (ticks < 0) + ticks = 0; + if (entity.getMaxFreezeTicks() < ticks) + ticks = entity.getMaxFreezeTicks(); + // Set new time + entity.setFreezeTicks(ticks); + } +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprMaxFreezeTicks.java b/src/main/java/ch/njol/skript/expressions/ExprMaxFreezeTicks.java new file mode 100644 index 00000000000..b477fe4df4a --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprMaxFreezeTicks.java @@ -0,0 +1,64 @@ + +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.Changer; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.util.Timespan; +import org.bukkit.entity.Entity; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Maximum Freeze Time") +@Description("The maximum amount of time an entity can spend in powdered snow before taking damage.") +@Examples({ + "difference between player's freeze time and player's max freeze time is less than 1 second:", + "\tsend \"you're about to freeze!\" to the player" +}) +@Since("INSERT VERSION") +public class ExprMaxFreezeTicks extends SimplePropertyExpression<Entity, Timespan> { + + static { + if (Skript.methodExists(Entity.class, "getMaxFreezeTicks")) + register(ExprMaxFreezeTicks.class, Timespan.class, "max[imum] freeze time", "entities"); + } + + @Override + @Nullable + public Timespan convert(Entity entity) { + return Timespan.fromTicks_i(entity.getMaxFreezeTicks()); + } + + @Override + public Class<? extends Timespan> getReturnType() { + return Timespan.class; + } + + @Override + protected String getPropertyName() { + return "maximum freeze time"; + } + +} diff --git a/src/test/skript/tests/syntaxes/expressions/ExprFreezeTicks.sk b/src/test/skript/tests/syntaxes/expressions/ExprFreezeTicks.sk new file mode 100644 index 00000000000..318f97cfd77 --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprFreezeTicks.sk @@ -0,0 +1,18 @@ +test "freeze time" when running minecraft "1.18": + spawn cow at spawn of world "world": + assert freeze time of entity is set with "freeze time get failed" + set freeze time of entity to 3 seconds + assert freeze time of entity is 3 seconds with "freeze time set failed" + add 2 seconds to freeze time of entity + assert freeze time of entity is 5 seconds with "freeze time add ##1 failed" + add 10 seconds to freeze time of entity + assert freeze time of entity is 7 seconds with "freeze time add ##2 failed" # freeze time should be capped at entity's max freeze time (7 seconds for a cow) + + remove 4 seconds from freeze time of entity + assert freeze time of entity is 3 seconds with "freeze time remove ##1 failed" + remove 10 seconds from freeze time of entity + assert freeze time of entity is 0 seconds with "freeze time remove ##2 failed" # freeze time should not be negative + delete freeze time of entity + assert freeze time of entity is 0 seconds with "freeze time delete failed" + + delete entity From eb92daabda5ccd70edea2ecfc3f540523fdd9ece Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Sat, 16 Jul 2022 02:21:24 +0200 Subject: [PATCH 054/619] Update branch model (#4655) * Update branches.md --- branches.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/branches.md b/branches.md index 31cfb1a82a1..46f77efcc21 100644 --- a/branches.md +++ b/branches.md @@ -6,12 +6,16 @@ Anyone with push access can create these. Core developers may use them to work on code changes that other people would fork Skript for. Pull requests are used to merge code from feature branches to anywhere else. -## Stable -A branch that contains the last revision current stable release or current -beta release (e.g. 2.4). Only bug fixes should be merged here. This branch is -merged to master every time bugs are fixed. - ## Master -A branch that contains the next Skript release, or current alpha release. -Development on new features happens here, but bug fixes should target the -stable branch instead, unless the bugs do not exist there. \ No newline at end of file +The master branch contains the latest release plus all commits merged after it: bug fixes, enhancements and features, all of it. +Pretty much all PRs should target the master branch. + +## Development branches +All recent major releases except for the latest major will have a development branch, for example `dev/2.6`. +These branches will be updated only with bug fixes, here's how to update it: +- Create a new branch in your fork from the development branch and check it out locally +- [Cherry-pick](https://git-scm.com/docs/git-cherry-pick) the commits from master to be included (bug fixes) + - In case there are conflicts, you can manually apply the changes, but make sure to include both the full commit hash and the PR number (#1234) in the commit message +- Create a PR for your forked branch targeting the development branch +- Wait for approval +- Merge the PR, **not squash merge!** From f45b3ae9087b8db882225363a8e0d371b054c60e Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Sat, 16 Jul 2022 05:25:05 +0200 Subject: [PATCH 055/619] Fix double function signature error (#4772) --- src/main/java/ch/njol/skript/lang/function/Functions.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f325327acc9..29d04ad3e28 100644 --- a/src/main/java/ch/njol/skript/lang/function/Functions.java +++ b/src/main/java/ch/njol/skript/lang/function/Functions.java @@ -118,7 +118,7 @@ public static Function<?> loadFunction(SectionNode node) { assert definition != null; Matcher m = functionPattern.matcher(definition); if (!m.matches()) // We have checks when loading the signature, but matches() must be called anyway - return error(INVALID_FUNCTION_DEFINITION); + return null; // don't error, already done in signature loading String name = "" + m.group(1); Namespace namespace = globalFunctions.get(name); From ae4bb64620de6f2c504bd9e2449b39eaf36db01f Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Sat, 16 Jul 2022 06:10:33 +0200 Subject: [PATCH 056/619] Add last struck lightning (#3873) --- .../ch/njol/skript/effects/EffLightning.java | 8 ++- .../expressions/ExprLastSpawnedEntity.java | 67 +++++++++++++++---- 2 files changed, 61 insertions(+), 14 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffLightning.java b/src/main/java/ch/njol/skript/effects/EffLightning.java index d4ca92be3d7..56d23336ba9 100644 --- a/src/main/java/ch/njol/skript/effects/EffLightning.java +++ b/src/main/java/ch/njol/skript/effects/EffLightning.java @@ -19,6 +19,7 @@ package ch.njol.skript.effects; import org.bukkit.Location; +import org.bukkit.entity.Entity; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @@ -52,6 +53,9 @@ public class EffLightning extends Effect { private boolean effectOnly; + @Nullable + public static Entity lastSpawned = null; + @SuppressWarnings({"unchecked", "null"}) @Override public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { @@ -64,9 +68,9 @@ public boolean init(final Expression<?>[] exprs, final int matchedPattern, final protected void execute(final Event e) { for (final Location l : locations.getArray(e)) { if (effectOnly) - l.getWorld().strikeLightningEffect(l); + lastSpawned = l.getWorld().strikeLightningEffect(l); else - l.getWorld().strikeLightning(l); + lastSpawned = l.getWorld().strikeLightning(l); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprLastSpawnedEntity.java b/src/main/java/ch/njol/skript/expressions/ExprLastSpawnedEntity.java index b556cded29a..a3bcda098b5 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprLastSpawnedEntity.java +++ b/src/main/java/ch/njol/skript/expressions/ExprLastSpawnedEntity.java @@ -23,6 +23,7 @@ import ch.njol.skript.sections.EffSecSpawn; import org.bukkit.entity.Entity; import org.bukkit.entity.Item; +import org.bukkit.entity.LightningStrike; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @@ -32,6 +33,7 @@ import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.effects.EffDrop; +import ch.njol.skript.effects.EffLightning; import ch.njol.skript.effects.EffShoot; import ch.njol.skript.entity.EntityData; import ch.njol.skript.lang.Expression; @@ -45,7 +47,7 @@ * @author Peter Güttinger */ @Name("Last Spawned Entity") -@Description("Holds the entity that was spawned most recently with the spawn effect, drop with the <a href='../effects/#EffDrop'>drop effect</a> or shot with the <a href='../effects/#EffShoot'>shoot effect</a>. " + +@Description("Holds the entity that was spawned most recently with the spawn effect (section), dropped with the <a href='../effects/#EffDrop'>drop effect</a>, shot with the <a href='../effects/#EffShoot'>shoot effect</a> or created with the <a href='../effects/#EffLightning'>lightning effect</a>. " + "Please note that even though you can spawn multiple mobs simultaneously (e.g. with 'spawn 5 creepers'), only the last spawned mob is saved and can be used. " + "If you spawn an entity, shoot a projectile and drop an item you can however access all them together.") @Examples({"spawn a priest", @@ -53,11 +55,16 @@ "shoot an arrow from the last spawned entity", "ignite the shot projectile", "drop a diamond sword", - "push last dropped item upwards"}) -@Since("1.3 (spawned entity), 2.0 (shot entity), 2.2-dev26 (dropped item)") + "push last dropped item upwards", + "teleport player to last struck lightning"}) +@Since("1.3 (spawned entity), 2.0 (shot entity), 2.2-dev26 (dropped item), SINCE VERSION (struck lightning)") public class ExprLastSpawnedEntity extends SimpleExpression<Entity> { + static { - Skript.registerExpression(ExprLastSpawnedEntity.class, Entity.class, ExpressionType.SIMPLE, "[the] [last[ly]] (0¦spawned|1¦shot) %*entitydata%", "[the] [last[ly]] dropped (2¦item)"); + Skript.registerExpression(ExprLastSpawnedEntity.class, Entity.class, ExpressionType.SIMPLE, + "[the] [last[ly]] (0:spawned|1:shot) %*entitydata%", + "[the] [last[ly]] dropped (2:item)", + "[the] [last[ly]] (created|struck) (3:lightning)"); } int from; @@ -66,24 +73,43 @@ public class ExprLastSpawnedEntity extends SimpleExpression<Entity> { @SuppressWarnings("unchecked") @Override - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { - if (parseResult.mark == 2) // It's just to make an extra expression for item only + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (parseResult.mark == 2) {// It's just to make an extra expression for item only type = EntityData.fromClass(Item.class); - else + } else if (parseResult.mark == 3) { + type = EntityData.fromClass(LightningStrike.class); + } else { type = ((Literal<EntityData<?>>) exprs[0]).getSingle(); + } from = parseResult.mark; return true; } @Override @Nullable - protected Entity[] get(final Event e) { - final Entity en = from == 0 ? EffSecSpawn.lastSpawned : from == 1 ? EffShoot.lastSpawned : EffDrop.lastSpawned; + protected Entity[] get(Event e) { + Entity en; + switch (from) { + case 0: + en = EffSecSpawn.lastSpawned; + break; + case 1: + en = EffShoot.lastSpawned; + break; + case 2: + en = EffDrop.lastSpawned; + break; + case 3: + en = EffLightning.lastSpawned; + break; + default: + en = null; + } if (en == null) return null; if (!type.isInstance(en)) return null; - final Entity[] one = (Entity[]) Array.newInstance(type.getType(), 1); + Entity[] one = (Entity[]) Array.newInstance(type.getType(), 1); one[0] = en; return one; } @@ -99,8 +125,25 @@ public Class<? extends Entity> getReturnType() { } @Override - public String toString(final @Nullable Event e, final boolean debug) { - return "the last " + (from == 1 ? "spawned" : from == 1 ? "shot" : "dropped") + " " + type; + public String toString(@Nullable Event e, boolean debug) { + String word; + switch (from) { + case 0: + word = "spawned"; + break; + case 1: + word = "shot"; + break; + case 2: + word = "dropped"; + break; + case 3: + word = "struck"; + break; + default: + throw new IllegalStateException(); + } + return "the last " + word + " " + type; } } From 70df50bfe07cc813d81b774e296bdf768ad62259 Mon Sep 17 00:00:00 2001 From: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> Date: Fri, 15 Jul 2022 23:47:46 -0500 Subject: [PATCH 057/619] Add EffPathfind (#4671) --- .../ch/njol/skript/effects/EffPathfind.java | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 src/main/java/ch/njol/skript/effects/EffPathfind.java diff --git a/src/main/java/ch/njol/skript/effects/EffPathfind.java b/src/main/java/ch/njol/skript/effects/EffPathfind.java new file mode 100644 index 00000000000..5f290efe71e --- /dev/null +++ b/src/main/java/ch/njol/skript/effects/EffPathfind.java @@ -0,0 +1,100 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.effects; + +import org.bukkit.Location; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Mob; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; + +@Name("Pathfind") +@Description({"Make an entity pathfind towards a location or another entity. Not all entities can pathfind. " + + "If the pathfinding target is another entity, the entities may or may not continuously follow the target."}) +@Examples({ + "make all creepers pathfind towards player", + "make all cows stop pathfinding", + "make event-entity pathfind towards player" +}) +@Since("INSERT VERSION") +public class EffPathfind extends Effect { + + static { + if (Skript.classExists("org.bukkit.entity.Mob") && Skript.methodExists(Mob.class, "getPathfinder")) + Skript.registerEffect(EffPathfind.class, + "make %livingentities% (pathfind|move) to[wards] %livingentity/location% [at speed %-number%]", + "make %livingentities% stop (pathfinding|moving)"); + } + + private Expression<LivingEntity> entities; + + @Nullable + private Expression<Number> speed; + + @Nullable + private Expression<?> target; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + entities = (Expression<LivingEntity>) exprs[0]; + target = matchedPattern == 0 ? exprs[1] : null; + speed = matchedPattern == 0 ? (Expression<Number>) exprs[2] : null; + return true; + } + + @Override + protected void execute(Event event) { + Object target = this.target != null ? this.target.getSingle(event) : null; + int speed = this.speed != null ? this.speed.getSingle(event).intValue() : 1; + for (LivingEntity entity : entities.getArray(event)) { + if (!(entity instanceof Mob)) + continue; + if (target instanceof LivingEntity) { + ((Mob) entity).getPathfinder().moveTo((LivingEntity) target, speed); + } else if (target instanceof Location) { + ((Mob) entity).getPathfinder().moveTo((Location) target, speed); + } else if (this.target == null) { + ((Mob) entity).getPathfinder().stopPathfinding(); + } + } + } + + @Override + public String toString(@Nullable Event e, boolean debug) { + if (target == null) + return "make " + entities.toString(e, debug) + " stop pathfinding"; + + String repr = "make " + entities.toString(e, debug) + " pathfind towards " + target.toString(e, debug); + if (speed != null) + repr += " at speed " + speed.toString(e, debug); + return repr; + } + +} From baccca9233a26683248ee2d1a572d900afb21821 Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Mon, 18 Jul 2022 10:01:09 +0200 Subject: [PATCH 058/619] Fix function converters and plural arguments when single expected (#4896) * Make functions error for incorrect argument plurality (single expected, multiple provided) --- .../lang/function/ExprFunctionCall.java | 47 ++++++++++--------- .../lang/function/FunctionReference.java | 15 +++++- .../4524-function call conversion.sk | 5 ++ ...th plural argument for single parameter.sk | 5 ++ 4 files changed, 47 insertions(+), 25 deletions(-) create mode 100644 src/test/skript/tests/regressions/4524-function call conversion.sk create mode 100644 src/test/skript/tests/regressions/4859-function error with plural argument for single parameter.sk diff --git a/src/main/java/ch/njol/skript/lang/function/ExprFunctionCall.java b/src/main/java/ch/njol/skript/lang/function/ExprFunctionCall.java index 2b686cde2ba..a5135282277 100644 --- a/src/main/java/ch/njol/skript/lang/function/ExprFunctionCall.java +++ b/src/main/java/ch/njol/skript/lang/function/ExprFunctionCall.java @@ -18,54 +18,55 @@ */ package ch.njol.skript.lang.function; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.skript.registrations.Converters; +import ch.njol.skript.util.Utils; import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; -/** - * @author Peter Güttinger - */ public class ExprFunctionCall<T> extends SimpleExpression<T> { - private final FunctionReference<T> function; - - public ExprFunctionCall(final FunctionReference<T> function) { + private final FunctionReference<?> function; + private final Class<? extends T>[] returnTypes; + private final Class<T> returnType; + + @SuppressWarnings("unchecked") + public ExprFunctionCall(FunctionReference<T> function) { this.function = function; + this.returnTypes = function.returnTypes; + this.returnType = (Class<T>) Utils.getSuperType(returnTypes); } - + @Override @Nullable - protected T[] get(final Event e) { - T[] returnValue = function.execute(e); + protected T[] get(Event e) { + Object[] returnValue = function.execute(e); function.resetReturnValue(); - return returnValue; + return Converters.convertArray(returnValue, returnTypes, returnType); } - + @Override public boolean isSingle() { return function.isSingle(); } - + @Override public Class<? extends T> getReturnType() { - Class<? extends T> type = function.getReturnType(); - assert type != null : "validateFunction() let invalid reference pass"; - return type; + return returnType; } - + @Override - public String toString(@Nullable final Event e, final boolean debug) { + public String toString(@Nullable Event e, boolean debug) { return function.toString(e, debug); } - + @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) { assert false; return false; } - + } diff --git a/src/main/java/ch/njol/skript/lang/function/FunctionReference.java b/src/main/java/ch/njol/skript/lang/function/FunctionReference.java index 8027d848316..d6d0d10f80d 100644 --- a/src/main/java/ch/njol/skript/lang/function/FunctionReference.java +++ b/src/main/java/ch/njol/skript/lang/function/FunctionReference.java @@ -29,6 +29,7 @@ import ch.njol.skript.log.RetainingLogHandler; import ch.njol.skript.log.SkriptLogger; import ch.njol.skript.registrations.Classes; +import ch.njol.skript.registrations.Converters; import ch.njol.skript.util.LiteralUtils; import ch.njol.util.StringUtils; import ch.njol.util.coll.CollectionUtils; @@ -84,7 +85,7 @@ public class FunctionReference<T> { * of the function signature. */ @Nullable - private final Class<? extends T>[] returnTypes; + final Class<? extends T>[] returnTypes; /** * Node for {@link #validateFunction(boolean)} to use for logging. @@ -147,7 +148,7 @@ public boolean validateFunction(boolean first) { } return false; } - if (!CollectionUtils.containsAnySuperclass(returnTypes, rt.getC())) { + if (!Converters.converterExists(rt.getC(), returnTypes)) { if (first) { Skript.error("The returned value of the function '" + functionName + "', " + sign.returnType + ", is " + SkriptParser.notOfType(returnTypes) + "."); } else { @@ -224,6 +225,16 @@ public boolean validateFunction(boolean first) { function = previousFunction; } return false; + } else if (p.single && !e.isSingle()) { + if (first) { + Skript.error("The " + StringUtils.fancyOrderNumber(i + 1) + " argument given to the function '" + functionName + "' is plural, " + + "but a single argument was expected"); + } else { + Skript.error("The function '" + functionName + "' was redefined with different, incompatible arguments, but is still used in other script(s)." + + " These will continue to use the old version of the function until Skript restarts."); + function = previousFunction; + } + return false; } parameters[i] = e; } finally { diff --git a/src/test/skript/tests/regressions/4524-function call conversion.sk b/src/test/skript/tests/regressions/4524-function call conversion.sk new file mode 100644 index 00000000000..f9b812147ce --- /dev/null +++ b/src/test/skript/tests/regressions/4524-function call conversion.sk @@ -0,0 +1,5 @@ +function functionCallConversion() :: item: + return stone named "abc" + +test "function call conversion": + assert name of functionCallConversion() is "abc" with "Name of item from function call failed" diff --git a/src/test/skript/tests/regressions/4859-function error with plural argument for single parameter.sk b/src/test/skript/tests/regressions/4859-function error with plural argument for single parameter.sk new file mode 100644 index 00000000000..57b3a83d5bf --- /dev/null +++ b/src/test/skript/tests/regressions/4859-function error with plural argument for single parameter.sk @@ -0,0 +1,5 @@ +function pluralArgSingleParam(a: string, b: number) :: number: + return 1 + +test "function error with plural argument for single parameter": + assert pluralArgSingleParam(("abc", "def"), 1) is 1 to fail with "function call didn't error with plural argument when single expected" \ No newline at end of file From 7186ed17e2a49b72bbc84cc2d15ee3588d63375e Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Mon, 18 Jul 2022 02:29:14 -0600 Subject: [PATCH 059/619] Feature/pathfinding to be double (#4915) --- .../java/ch/njol/skript/effects/EffPathfind.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffPathfind.java b/src/main/java/ch/njol/skript/effects/EffPathfind.java index 5f290efe71e..eb53966d0f6 100644 --- a/src/main/java/ch/njol/skript/effects/EffPathfind.java +++ b/src/main/java/ch/njol/skript/effects/EffPathfind.java @@ -28,6 +28,7 @@ import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Effect; import ch.njol.skript.lang.Expression; @@ -40,9 +41,10 @@ @Examples({ "make all creepers pathfind towards player", "make all cows stop pathfinding", - "make event-entity pathfind towards player" + "make event-entity pathfind towards player at speed 1" }) @Since("INSERT VERSION") +@RequiredPlugins("Paper") public class EffPathfind extends Effect { static { @@ -72,7 +74,7 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override protected void execute(Event event) { Object target = this.target != null ? this.target.getSingle(event) : null; - int speed = this.speed != null ? this.speed.getSingle(event).intValue() : 1; + double speed = this.speed != null ? this.speed.getOptionalSingle(event).orElse(1).doubleValue() : 1; for (LivingEntity entity : entities.getArray(event)) { if (!(entity instanceof Mob)) continue; @@ -87,13 +89,12 @@ protected void execute(Event event) { } @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { if (target == null) - return "make " + entities.toString(e, debug) + " stop pathfinding"; - - String repr = "make " + entities.toString(e, debug) + " pathfind towards " + target.toString(e, debug); + return "make " + entities.toString(event, debug) + " stop pathfinding"; + String repr = "make " + entities.toString(event, debug) + " pathfind towards " + target.toString(event, debug); if (speed != null) - repr += " at speed " + speed.toString(e, debug); + repr += " at speed " + speed.toString(event, debug); return repr; } From 715a016e9f37b6186dcece8b4bab9a625f6b87cd Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Mon, 18 Jul 2022 10:43:49 +0200 Subject: [PATCH 060/619] Make scripts in directories load before rest (#4515) --- src/main/java/ch/njol/skript/ScriptLoader.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/njol/skript/ScriptLoader.java b/src/main/java/ch/njol/skript/ScriptLoader.java index f5a2842cebf..d8dd1316458 100644 --- a/src/main/java/ch/njol/skript/ScriptLoader.java +++ b/src/main/java/ch/njol/skript/ScriptLoader.java @@ -587,7 +587,7 @@ private static ScriptInfo loadScript(@Nullable Config config) { if (config == null) { // Something bad happened, hopefully got logged to console return new ScriptInfo(); } - + // When something is parsed, it goes there to be loaded later List<ScriptCommand> commands = new ArrayList<>(); List<ParsedEventData> events = new ArrayList<>(); @@ -879,17 +879,20 @@ public static List<Config> loadStructures(File directory) { File[] files = directory.listFiles(scriptFilter); Arrays.sort(files); + List<Config> loadedDirectories = new ArrayList<>(files.length); List<Config> loadedFiles = new ArrayList<>(files.length); for (File file : files) { if (file.isDirectory()) { - loadedFiles.addAll(loadStructures(file)); + loadedDirectories.addAll(loadStructures(file)); } else { Config cfg = loadStructure(file); if (cfg != null) loadedFiles.add(cfg); } } - return loadedFiles; + + loadedDirectories.addAll(loadedFiles); + return loadedDirectories; } /** From 58081ab8a77d04b3be827d8d4e2fc9e321b0543d Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <alali_ayham@yahoo.com> Date: Mon, 18 Jul 2022 11:50:05 +0300 Subject: [PATCH 061/619] Fix ExprFormatTime CCE + Support non-literals (#4664) --- .../skript/expressions/ExprFormatDate.java | 137 ++++++++++++++++++ .../skript/expressions/ExprFormatTime.java | 98 ------------- .../tests/regressions/4664-formatted time.sk | 20 +++ 3 files changed, 157 insertions(+), 98 deletions(-) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprFormatDate.java delete mode 100644 src/main/java/ch/njol/skript/expressions/ExprFormatTime.java create mode 100644 src/test/skript/tests/regressions/4664-formatted time.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprFormatDate.java b/src/main/java/ch/njol/skript/expressions/ExprFormatDate.java new file mode 100644 index 00000000000..64f07c9561f --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprFormatDate.java @@ -0,0 +1,137 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import java.text.SimpleDateFormat; + +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.VariableString; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.PropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.util.Date; +import ch.njol.skript.util.Getter; +import ch.njol.util.Kleenean; + +@Name("Formatted Date") +@Description({ + "Converts date to human-readable text format. By default, 'yyyy-MM-dd HH:mm:ss z' (e.g. '2018-03-30 16:03:12 +01') will be used. For reference, see this " + + "<a href=\"https://en.wikipedia.org/wiki/ISO_8601\">Wikipedia article</a>." +}) +@Examples({ + "command /date:", + "\ttrigger:", + "\t\tsend \"Full date: %now formatted human-readable%\" to sender", + "\t\tsend \"Short date: %now formatted as \"\"yyyy-MM-dd\"\"%\" to sender" +}) +@Since("2.2-dev31, INSERT VERSION (support variables in format)") +public class ExprFormatDate extends PropertyExpression<Date, String> { + + private static final SimpleDateFormat DEFAULT_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); + + static { + Skript.registerExpression(ExprFormatDate.class, String.class, ExpressionType.PROPERTY, + "%dates% formatted [human-readable] [(with|as) %-string%]", + "[human-readable] formatted %dates% [(with|as) %-string%]"); + } + + @SuppressWarnings("NotNullFieldNotInitialized") + private SimpleDateFormat format; + + @SuppressWarnings("NotNullFieldNotInitialized") + private Expression<String> customFormat; + + @Override + @SuppressWarnings({"null", "unchecked"}) + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + setExpr((Expression<? extends Date>) exprs[0]); + customFormat = (Expression<String>) exprs[1]; + + boolean isSimpleString = customFormat instanceof VariableString && ((VariableString) customFormat).isSimple(); + if (customFormat instanceof Literal || isSimpleString) { + String customFormatValue; + if (isSimpleString) { + customFormatValue = ((VariableString) customFormat).toString(null); + } else { + customFormatValue = ((Literal<String>) customFormat).getSingle(); + } + + if (customFormatValue != null) { + try { + format = new SimpleDateFormat(customFormatValue); + } catch (IllegalArgumentException e) { + Skript.error("Invalid date format: " + customFormatValue); + return false; + } + } + } else if (customFormat == null) { + format = DEFAULT_FORMAT; + } + + return true; + } + + @Override + protected String[] get(Event e, Date[] source) { + SimpleDateFormat format; + String formatString; + + if (customFormat != null && this.format == null) { // customFormat is not Literal or VariableString + formatString = customFormat.getSingle(e); + if (formatString == null) + return null; + + try { + format = new SimpleDateFormat(formatString); + } catch (IllegalArgumentException ex) { + return null; + } + } else { + format = this.format; + } + + return get(source, new Getter<String, Date>() { + @Override + public String get(Date date) { + return format.format(new java.util.Date(date.getTimestamp())); + } + }); + } + + @Override + public Class<? extends String> getReturnType() { + return String.class; + } + + @Override + public String toString(@Nullable Event e, boolean debug) { + return getExpr().toString(e, debug) + " formatted as " + (customFormat != null ? customFormat.toString(e, debug) + : (format != null ? format.toPattern() : DEFAULT_FORMAT.toPattern())); + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprFormatTime.java b/src/main/java/ch/njol/skript/expressions/ExprFormatTime.java deleted file mode 100644 index 471b38fe84c..00000000000 --- a/src/main/java/ch/njol/skript/expressions/ExprFormatTime.java +++ /dev/null @@ -1,98 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.expressions; - -import java.text.SimpleDateFormat; - -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.Skript; -import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Examples; -import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.Since; -import ch.njol.skript.expressions.base.PropertyExpression; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.ExpressionType; -import ch.njol.skript.lang.Literal; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.lang.VariableString; -import ch.njol.skript.util.Date; -import ch.njol.skript.util.Getter; -import ch.njol.util.Kleenean; - -@Name("Formatted Time") -@Description("Converts date to human-readable text format. By default, 'yyyy-MM-dd HH:mm:ss z' (e.g. '2018-03-30 16:03:12 +01') will be used. For reference, see this " - + "<a href=\"https://en.wikipedia.org/wiki/ISO_8601\">Wikipedia article</a>.") -@Examples("now formatted human-readable") -@Since("2.2-dev31") -public class ExprFormatTime extends PropertyExpression<Date, String> { - - private static final String defaultFormat = "yyyy-MM-dd HH:mm:ss z"; - - static { - Skript.registerExpression(ExprFormatTime.class, String.class, ExpressionType.PROPERTY, "%dates% formatted [human-readable] [(with|as) %-string%]"); - } - - @SuppressWarnings("null") - private SimpleDateFormat format; - - @Override - @SuppressWarnings("null") - public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - setExpr((Expression<? extends Date>) exprs[0]); - if (exprs[1] != null) { - if (!(exprs[1] instanceof Literal)) { - VariableString str = (VariableString) exprs[1]; - if (!str.isSimple()) { - Skript.error("Date format must not contain variables!"); - return false; - } - } - format = new SimpleDateFormat((String) exprs[1].getSingle(null)); - } else { - format = new SimpleDateFormat(defaultFormat); - } - - return true; - } - - - @Override - protected String[] get(Event e, Date[] source) { - return get(source, new Getter<String, Date>() { - @Override - public String get(Date date) { - return format.format(new java.util.Date(date.getTimestamp())); - } - }); - } - - @Override - public Class<? extends String> getReturnType() { - return String.class; - } - - @Override - public String toString(@Nullable Event e, boolean debug) { - return getExpr().toString(e, debug) + " formatted as " + format.toPattern(); - } - -} diff --git a/src/test/skript/tests/regressions/4664-formatted time.sk b/src/test/skript/tests/regressions/4664-formatted time.sk new file mode 100644 index 00000000000..329c06bb9fa --- /dev/null +++ b/src/test/skript/tests/regressions/4664-formatted time.sk @@ -0,0 +1,20 @@ +test "formatted time": + set {_default} to "yyyy-MM-dd HH:mm:ss z" + set {_now} to now + + set {_date1} to {_now} formatted + assert {_date1} = {_now} formatted as {_default} with "default date format failed ##1" + + set {_date2} to {_now} formatted human-readable + assert {_date2} = {_now} formatted as {_default} with "default date format failed ##2" + + set {_date3} to {_now} formatted as "HH:mm" + assert length of {_date3} = 5 with "custom date format failed ##1" + + set {_cFormat} to "hh:mm" + set {_date4} to {_now} formatted as {_cFormat} + assert length of {_date4} = 5 with "custom date format failed ##2" + + set {_cFormat2} to "i" + set {_date5} to {_now} formatted as {_cFormat2} + assert {_date5} is not set with "custom date format failed ##3" From 1cc568b6d3802b49c311b7a56deee0016d4390ab Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <alali_ayham@yahoo.com> Date: Mon, 18 Jul 2022 15:21:47 +0300 Subject: [PATCH 062/619] =?UTF-8?q?=E2=9A=92=EF=B8=8F=20Improve=20Time=20c?= =?UTF-8?q?lass=20(#4690)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/ch/njol/skript/util/Time.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/njol/skript/util/Time.java b/src/main/java/ch/njol/skript/util/Time.java index 6405b142acb..1572e512599 100644 --- a/src/main/java/ch/njol/skript/util/Time.java +++ b/src/main/java/ch/njol/skript/util/Time.java @@ -41,7 +41,10 @@ public class Time implements YggdrasilSerializable { private final static int HOUR_ZERO = 6 * TICKS_PER_HOUR; private final int time; - + + private static final Pattern DAY_TIME_PATTERN = Pattern.compile("(\\d?\\d)(:(\\d\\d))? ?(am|pm)", Pattern.CASE_INSENSITIVE); + private static final Pattern TIME_PATTERN = Pattern.compile("\\d?\\d:\\d\\d", Pattern.CASE_INSENSITIVE); + public Time() { time = 0; } @@ -95,7 +98,7 @@ public static Time parse(final String s) { // if (s.matches("\\d+")) { // return new Time(Integer.parseInt(s)); // } else - if (s.matches("\\d?\\d:\\d\\d")) { + if (TIME_PATTERN.matcher(s).matches()) { int hours = Utils.parseInt(s.split(":")[0]); if (hours == 24) { // allows to write 24:00 - 24:59 instead of 0:00-0:59 hours = 0; @@ -110,7 +113,7 @@ public static Time parse(final String s) { } return new Time((int) Math.round(hours * TICKS_PER_HOUR - HOUR_ZERO + minutes * TICKS_PER_MINUTE)); } else { - final Matcher m = Pattern.compile("(\\d?\\d)(:(\\d\\d))? ?(am|pm)", Pattern.CASE_INSENSITIVE).matcher(s); + final Matcher m = DAY_TIME_PATTERN.matcher(s); if (m.matches()) { int hours = Utils.parseInt(m.group(1)); if (hours == 12) { From 672bba577c21cbcc94bb923501fb57a4984ff029 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <alali_ayham@yahoo.com> Date: Tue, 19 Jul 2022 03:57:33 +0300 Subject: [PATCH 063/619] =?UTF-8?q?=F0=9F=9A=80=20Add=20all=20plugins=20ex?= =?UTF-8?q?pression=20(#4199)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../njol/skript/expressions/ExprPlugins.java | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprPlugins.java diff --git a/src/main/java/ch/njol/skript/expressions/ExprPlugins.java b/src/main/java/ch/njol/skript/expressions/ExprPlugins.java new file mode 100644 index 00000000000..e2d80244332 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprPlugins.java @@ -0,0 +1,81 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.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 org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.bukkit.plugin.Plugin; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.Arrays; + +@Name("Loaded Plugins") +@Description("An expression to obtain a list of the names of the server's loaded plugins.") +@Examples({ + "if the loaded plugins contains \"Vault\":", + "\tbroadcast \"This server uses Vault plugin!\"", + "", + "send \"Plugins (%size of loaded plugins%): %plugins%\" to player" +}) +@Since("INSERT VERSION") +public class ExprPlugins extends SimpleExpression<String> { + + static { + Skript.registerExpression(ExprPlugins.class, String.class, ExpressionType.SIMPLE, "[(all [[of] the]|the)] [loaded] plugins"); + } + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + return true; + } + + @Override + @Nullable + protected String[] get(Event e) { + return Arrays.stream(Bukkit.getPluginManager().getPlugins()) + .map(Plugin::getName) + .toArray(String[]::new); + } + + @Override + public boolean isSingle() { + return false; + } + + @Override + public Class<? extends String> getReturnType() { + return String.class; + } + + @Override + public String toString(@Nullable Event e, boolean debug) { + return "the loaded plugins"; + } + +} From 7b4c449b18669d0d39b45cb27362b3a71587da11 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <alali_ayham@yahoo.com> Date: Wed, 20 Jul 2022 22:06:52 +0300 Subject: [PATCH 064/619] =?UTF-8?q?=F0=9F=9A=80=20Support=20offline=20play?= =?UTF-8?q?ers=20in=20ExprBed=20(#4660)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ch/njol/skript/expressions/ExprBed.java | 82 +++++++++++-------- 1 file changed, 50 insertions(+), 32 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprBed.java b/src/main/java/ch/njol/skript/expressions/ExprBed.java index 361c9c4b4a0..1f9684514a8 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprBed.java +++ b/src/main/java/ch/njol/skript/expressions/ExprBed.java @@ -18,58 +18,76 @@ */ package ch.njol.skript.expressions; -import org.bukkit.Location; -import org.bukkit.entity.Player; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.Location; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; -/** - * @author Peter Güttinger - */ @Name("Bed") -@Description("The bed location of a player, i.e. the spawn point of a player if he ever slept in a bed and the bed still exists and is unobstructed.") -@Examples({"bed of player exists:", - " teleport player the the player's bed", - "else:", - " teleport the player to the world's spawn point"}) -@Since("2.0") -public class ExprBed extends SimplePropertyExpression<Player, Location> { +@Description({ + "Returns the bed location of a player, " + + "i.e. the spawn point of a player if they ever slept in a bed and the bed still exists and is unobstructed however, " + + "you can set the unsafe bed location of players and they will respawn there even if it has been obstructed or doesn't exist anymore " + + "and that's the default behavior of this expression otherwise you will need to be specific i.e. <code>safe bed location</code>.", + "", + "NOTE: Offline players can not have their bed location changed, only online players." +}) +@Examples({ + "if bed of player exists:", + "\tteleport player the the player's bed", + "else:", + "\tteleport the player to the world's spawn point", + "", + "set the bed location of player to spawn location of world(\"world\") # unsafe/invalid bed location", + "set the safe bed location of player to spawn location of world(\"world\") # safe/valid bed location" +}) +@Since("2.0, INSERT VERSION (offlineplayers, safe bed)") +public class ExprBed extends SimplePropertyExpression<OfflinePlayer, Location> { + static { - register(ExprBed.class, Location.class, "bed[s] [location[s]]", "players"); + register(ExprBed.class, Location.class, "[(safe:(safe|valid)|(unsafe|invalid))] bed[s] [location[s]]", "offlineplayers"); } - + + private boolean isSafe; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + isSafe = parseResult.hasTag("safe"); + setExpr((Expression<? extends OfflinePlayer>) exprs[0]); + return true; + } + @Override @Nullable - public Location convert(final Player p) { + public Location convert(OfflinePlayer p) { return p.getBedSpawnLocation(); } @Override @Nullable - public Class<?>[] acceptChange(final ChangeMode mode) { - if (mode == ChangeMode.SET || mode == ChangeMode.DELETE) - return new Class[] {Location.class}; - return null; + public Class<?>[] acceptChange(ChangeMode mode) { + return mode == ChangeMode.SET || mode == ChangeMode.DELETE ? CollectionUtils.array(Location.class) : null; } @Override - public void change(final Event e, @Nullable final Object[] delta, final ChangeMode mode) { - if (delta == null) { - for (final Player p : getExpr().getArray(e)) { - p.setBedSpawnLocation(null, true); - } - } else { - final Location l = (Location) delta[0]; - for (final Player p : getExpr().getArray(e)) { - p.setBedSpawnLocation(l, true); - } + public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { + Location loc = delta == null ? null : (Location) delta[0]; + for (OfflinePlayer p : getExpr().getArray(e)) { + Player op = p.getPlayer(); + if (op != null) // is online + op.setBedSpawnLocation(loc, !isSafe); } } From 59a0050394fdb2f07818fa7095e08b5de645c876 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Fri, 22 Jul 2022 11:17:59 -0600 Subject: [PATCH 065/619] Fixes test system and adds 1.19 test environment (#4845) --- .../{gradle.yml => java-17-builds.yml} | 17 ++- .github/workflows/java-8-builds.yml | 30 ++++ .github/workflows/repo.yml | 4 +- README.md | 14 +- build.gradle | 80 ++++++----- gradle.properties | 3 +- gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 59821 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- src/main/java/ch/njol/skript/Skript.java | 128 ++++++++++-------- .../java/ch/njol/skript/SkriptCommand.java | 17 ++- .../ch/njol/skript/tests/TestResults.java | 37 +++-- .../skript/tests/platform/Environment.java | 20 ++- .../skript/tests/platform/PlatformMain.java | 37 ++++- .../ch/njol/skript/tests/runner/TestMode.java | 25 ++-- .../njol/skript/tests/runner/TestTracker.java | 2 +- .../runner_data/server.properties.generic | 2 +- src/test/skript/README.md | 2 +- .../{main => java17}/paper-1.17.1.json | 0 .../{main => java17}/paper-1.18.2.json | 0 .../environments/java17/paper-1.19.json | 17 +++ .../{extra => java8}/paper-1.13.2.json | 0 .../{extra => java8}/paper-1.14.4.json | 0 .../{extra => java8}/paper-1.15.2.json | 2 +- .../{extra => java8}/paper-1.16.5.json | 0 24 files changed, 282 insertions(+), 157 deletions(-) rename .github/workflows/{gradle.yml => java-17-builds.yml} (58%) create mode 100644 .github/workflows/java-8-builds.yml rename src/test/skript/environments/{main => java17}/paper-1.17.1.json (100%) rename src/test/skript/environments/{main => java17}/paper-1.18.2.json (100%) create mode 100644 src/test/skript/environments/java17/paper-1.19.json rename src/test/skript/environments/{extra => java8}/paper-1.13.2.json (100%) rename src/test/skript/environments/{extra => java8}/paper-1.14.4.json (100%) rename src/test/skript/environments/{extra => java8}/paper-1.15.2.json (89%) rename src/test/skript/environments/{extra => java8}/paper-1.16.5.json (100%) diff --git a/.github/workflows/gradle.yml b/.github/workflows/java-17-builds.yml similarity index 58% rename from .github/workflows/gradle.yml rename to .github/workflows/java-17-builds.yml index 40ed5825fd8..4fca18351a0 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/java-17-builds.yml @@ -1,4 +1,4 @@ -name: Java CI +name: Java 17 CI (MC 1.17+) on: [push, pull_request] @@ -6,21 +6,24 @@ jobs: build: if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: recursive - name: Set up JDK 17 - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: - java-version: 17 + java-version: '17' + distribution: 'adopt' + cache: gradle + - name: Grant execute permission for gradlew + run: chmod +x gradlew - name: Build Skript run: ./gradlew nightlyRelease - name: Run test scripts - run: ./gradlew skriptTest + run: ./gradlew clean skriptTestJava17 - name: Upload Nightly Build - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v3 if: success() with: name: skript-nightly diff --git a/.github/workflows/java-8-builds.yml b/.github/workflows/java-8-builds.yml new file mode 100644 index 00000000000..0b466c141a4 --- /dev/null +++ b/.github/workflows/java-8-builds.yml @@ -0,0 +1,30 @@ +name: Java 8 CI (MC 1.13-1.16) + +on: [push, pull_request] + +jobs: + build: + if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'adopt' + cache: gradle + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Build Skript + run: ./gradlew nightlyRelease + - name: Run test scripts + run: ./gradlew clean skriptTestJava8 + - name: Upload Nightly Build + uses: actions/upload-artifact@v3 + if: success() + with: + name: skript-nightly + path: build/libs/Skript-nightly.jar diff --git a/.github/workflows/repo.yml b/.github/workflows/repo.yml index 7ce1880c651..1518620c7e7 100644 --- a/.github/workflows/repo.yml +++ b/.github/workflows/repo.yml @@ -8,11 +8,11 @@ jobs: publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: recursive - name: Set up JDK 17 - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: java-version: 17 - name: Publish Skript diff --git a/README.md b/README.md index 60a985b7529..8d335854a8d 100644 --- a/README.md +++ b/README.md @@ -61,18 +61,18 @@ only used to provide compatibility with old WorldGuard versions. ### Testing Skript has some tests written in Skript. Running them requires a Minecraft -server, but our build script can fetch it for you. Running tests is easy: +server, but our build script will create one for you. Running the tests is easy: ``` -./gradlew (quickTest|skriptTest|skriptTestFull) +./gradlew (quickTest|skriptTest|skriptTestJava8|skriptTestJava17) ``` -<code>quickTest</code> runs the test suite on newest supported server. -<code>skriptTest</code> additionally runs the tests on oldest supported -server, and on 1.12 (pre-flattening). <code>skriptTestFull</code> runs -tests on **ALL** supported versions, some of which do not work on Java 9+. +<code>quickTest</code> runs the test suite on newest supported server version. +<code>skriptTestJava17</code> (1.17+) runs the tests on the latest supported Java version. +<code>skriptTestJava8</code> (1.13-1.16) runs the tests on the oldest supported Java version. +<code>skriptTest</code> runs both skriptTestJava8 and skriptTestJava17 -By running tests, you agree to Mojang's End User License Agreement. +By running the tests, you agree to Mojang's End User License Agreement. ### Importing to Eclipse With new Eclipse versions, there is integrated Gradle support, and it actually works now. diff --git a/build.gradle b/build.gradle index 186abe84042..39506d9defb 100644 --- a/build.gradle +++ b/build.gradle @@ -5,16 +5,12 @@ import org.apache.tools.ant.filters.ReplaceTokens import java.time.LocalTime plugins { - id 'com.github.johnrengelman.shadow' version '7.0.0' + id 'com.github.johnrengelman.shadow' version '7.1.2' id 'com.github.hierynomus.license' version '0.16.1' id 'maven-publish' id 'java' } -tasks.withType(JavaCompile).configureEach { - options.compilerArgs += ["-source", "1.8", "-target", "1.8"] -} - allprojects { repositories { mavenCentral() @@ -26,7 +22,7 @@ allprojects { } dependencies { - shadow group: 'io.papermc', name: 'paperlib', version: '1.0.6' + shadow group: 'io.papermc', name: 'paperlib', version: '1.0.7' shadow group: 'org.bstats', name: 'bstats-bukkit', version: '3.0.0' implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.19-R0.1-SNAPSHOT' implementation group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.600' @@ -140,7 +136,7 @@ license { } // Task to check that test scripts are named correctly -tasks.create('testNaming') { +tasks.register('testNaming') { doLast { // Regression tests for (def file : project.file('src/test/skript/tests/regressions').listFiles()) { @@ -162,41 +158,53 @@ tasks.create('testNaming') { } } -// Create a test task with given name, environments dir/file and is it development mode task -void createTestTask(String name, String environments, boolean devMode) { - tasks.create(name) { - // Compile Skript and check test naming +// Create a test task with given name, environments dir/file, dev mode and java version. +void createTestTask(String name, String environments, boolean devMode, int javaVersion, boolean genDocs) { + tasks.register(name, JavaExec) { dependsOn jar, testNaming - doFirst { - if (devMode) { - standardInput = System.in - } - javaexec { - group = 'execution' - classpath = files([ - 'build' + File.separator + 'libs' + File.separator + 'Skript.jar', - project.configurations.runtimeClasspath.find { it.name.startsWith('gson') }, - sourceSets.main.runtimeClasspath - ]) - main = 'ch.njol.skript.tests.platform.PlatformMain' - args = [ - 'test_runners', - 'src/test/skript/tests', - 'src/test/resources/runner_data', - environments, - devMode - ] - } + javaLauncher = javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(javaVersion) + } + if (devMode) { + standardInput = System.in } + group = 'execution' + classpath = files([ + 'build' + File.separator + 'libs' + File.separator + 'Skript.jar', + project.configurations.runtimeClasspath.find { it.name.startsWith('gson') }, + sourceSets.main.runtimeClasspath + ]) + main = 'ch.njol.skript.tests.platform.PlatformMain' + args = [ + 'test_runners', + 'src/test/skript/tests', + 'src/test/resources/runner_data', + environments, + devMode, + genDocs + ] } } +def latestEnv = 'java17/paper-1.19.json' +def latestJava = 17 +def oldestJava = 8 + +tasks.withType(JavaCompile).configureEach { + options.compilerArgs += ["-source", "" + oldestJava, "-target", "" + oldestJava] +} + // Register different Skript testing tasks -createTestTask('quickTest', 'src/test/skript/environments/main/paper-1.18.2.json', false) -createTestTask('skriptTest', 'src/test/skript/environments/main', false) -createTestTask('skriptTestFull', 'src/test/skript/environments/', false) -createTestTask('skriptTestDev', 'src/test/skript/environments/main/' + (project.property('testEnv') == null - ? 'paper-1.17.json' : project.property('testEnv') + '.json'), true) +createTestTask('quickTest', 'src/test/skript/environments/' + latestEnv, false, latestJava, false) +createTestTask('skriptTestJava17', 'src/test/skript/environments/java17', false, latestJava, false) +createTestTask('skriptTestJava8', 'src/test/skript/environments/java8', false, oldestJava, false) +createTestTask('skriptTestDev', 'src/test/skript/environments/' + (project.property('testEnv') == null + ? latestEnv : project.property('testEnv') + '.json'), true, Integer.parseInt(project.property('testEnvJavaVersion') == null + ? latestJava : project.property('testEnvJavaVersion')), false) +tasks.register('skriptTest') {dependsOn skriptTestJava8, skriptTestJava17} +createTestTask('genDocs', 'src/test/skript/environments/' + (project.property('testEnv') == null + ? latestEnv : project.property('testEnv') + '.json'), false, Integer.parseInt(project.property('testEnvJavaVersion') == null + ? latestJava : project.property('testEnvJavaVersion')), true) // Build flavor configurations task githubResources(type: ProcessResources) { diff --git a/gradle.properties b/gradle.properties index 5f667188e10..903f8e9f0b9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,4 +2,5 @@ groupid=ch.njol name=skript version=2.6.3 jarName=Skript.jar -testEnv=paper-1.16.5 +testEnv=java17/paper-1.19 +testEnvJavaVersion=17 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..41d9927a4d4fb3f96a785543079b8df6723c946b 100644 GIT binary patch delta 8958 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 R_{@Vu|29OF7ypyz{{qB}Ob!45 delta 8722 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2e6e5897b52..aa991fceae6 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 8750b65d8c5..7d6b0d9827c 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -18,6 +18,63 @@ */ package ch.njol.skript; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.Thread.UncaughtExceptionHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.logging.Filter; +import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; + +import org.bstats.bukkit.Metrics; +import org.bstats.charts.SimplePie; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.Server; +import org.bukkit.command.CommandSender; +import org.bukkit.command.PluginCommand; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.server.PluginDisableEvent; +import org.bukkit.event.server.ServerCommandEvent; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.plugin.java.JavaPlugin; +import org.eclipse.jdt.annotation.Nullable; + +import com.google.gson.Gson; + import ch.njol.skript.aliases.Aliases; import ch.njol.skript.bukkitutil.BurgerHelper; import ch.njol.skript.classes.ClassInfo; @@ -87,61 +144,6 @@ import ch.njol.util.coll.CollectionUtils; import ch.njol.util.coll.iterator.CheckedIterator; import ch.njol.util.coll.iterator.EnumerationIterable; -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; -import org.bukkit.Server; -import org.bukkit.command.CommandSender; -import org.bukkit.command.PluginCommand; -import org.bukkit.entity.Player; -import org.bukkit.event.Event; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerCommandPreprocessEvent; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.event.server.PluginDisableEvent; -import org.bukkit.event.server.ServerCommandEvent; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.PluginDescriptionFile; -import org.bukkit.plugin.java.JavaPlugin; -import org.eclipse.jdt.annotation.Nullable; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.lang.Thread.UncaughtExceptionHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.logging.Filter; -import java.util.logging.Level; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.zip.ZipEntry; -import java.util.zip.ZipException; -import java.util.zip.ZipFile; // TODO meaningful error if someone uses an %expression with percent signs% outside of text or a variable @@ -609,13 +611,26 @@ protected void afterErrors() { Bukkit.getScheduler().runTaskLater(Skript.this, () -> { if (TestMode.ENABLED) { // Ignore late init (scripts, etc.) in test mode + if (TestMode.GEN_DOCS) { + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "skript gen-docs"); + String results = new Gson().toJson(TestTracker.collectResults()); + try { + Files.write(TestMode.RESULTS_FILE, results.getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + Skript.exception(e, "Failed to write test results."); + } + // Delay server shutdown to stop the server from crashing because the current tick takes a long time due to all the tests + Bukkit.getScheduler().runTaskLater(Skript.this, () -> { + Bukkit.getServer().shutdown(); + }, 5); + return; + } if (TestMode.DEV_MODE) { // Run tests NOW! info("Test development mode enabled. Test scripts are at " + TestMode.TEST_DIR); } else { info("Running all tests from " + TestMode.TEST_DIR); // Treat parse errors as fatal testing failure - @SuppressWarnings("null") CountingLogHandler errorCounter = new CountingLogHandler(Level.SEVERE); try { errorCounter.start(); @@ -651,7 +666,6 @@ protected void afterErrors() { }, 5); } - return; } }, 100); diff --git a/src/main/java/ch/njol/skript/SkriptCommand.java b/src/main/java/ch/njol/skript/SkriptCommand.java index d4f04d724ef..66c90b38a73 100644 --- a/src/main/java/ch/njol/skript/SkriptCommand.java +++ b/src/main/java/ch/njol/skript/SkriptCommand.java @@ -26,8 +26,6 @@ import java.util.List; import java.util.stream.Collectors; -import ch.njol.skript.log.TimingLogHandler; -import ch.njol.util.OpenCloseable; import org.bukkit.Bukkit; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; @@ -44,12 +42,14 @@ import ch.njol.skript.localization.Language; import ch.njol.skript.localization.PluralizingArgsMessage; import ch.njol.skript.log.RedirectingLogHandler; +import ch.njol.skript.log.TimingLogHandler; import ch.njol.skript.tests.runner.SkriptTestEvent; import ch.njol.skript.tests.runner.TestMode; import ch.njol.skript.tests.runner.TestTracker; import ch.njol.skript.util.ExceptionUtils; import ch.njol.skript.util.FileUtils; import ch.njol.skript.util.SkriptColor; +import ch.njol.util.OpenCloseable; import ch.njol.util.StringUtils; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; @@ -80,12 +80,10 @@ public class SkriptCommand implements CommandExecutor { ).add("help"); static { - if (new File(Skript.getInstance().getDataFolder() + "/doc-templates").exists()) { + if (TestMode.GEN_DOCS || new File(Skript.getInstance().getDataFolder() + "/doc-templates").exists()) skriptCommandHelp.add("gen-docs"); - } - if (TestMode.DEV_MODE) { // Add command to run individual tests + if (TestMode.DEV_MODE) // Add command to run individual tests skriptCommandHelp.add("test"); - } } private static final ArgsMessage m_reloading = new ArgsMessage(CONFIG_NODE + ".reload.reloading"); @@ -356,7 +354,8 @@ public boolean onCommand(@Nullable CommandSender sender, @Nullable Command comma } else if (args[0].equalsIgnoreCase("gen-docs")) { File templateDir = new File(Skript.getInstance().getDataFolder() + "/doc-templates/"); if (!templateDir.exists()) { - Skript.info(sender, "Documentation templates not found. Cannot generate docs!"); + Skript.error(sender, "Cannot generate docs! Documentation templates not found at 'plugins/Skript/doc-templates/'"); + TestMode.docsFailed = true; return true; } File outputDir = new File(Skript.getInstance().getDataFolder() + "/docs"); @@ -379,7 +378,7 @@ public boolean onCommand(@Nullable CommandSender sender, @Nullable Command comma TestMode.lastTestFile = script; } if (!script.exists()) { - Skript.error(sender, "Test script doesn't exist!"); + Skript.info(sender, "Test script doesn't exist!"); return true; } @@ -390,7 +389,7 @@ public boolean onCommand(@Nullable CommandSender sender, @Nullable Command comma // Code should run on server thread Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.getInstance(), () -> { Bukkit.getPluginManager().callEvent(new SkriptTestEvent()); // Run it - // ScriptLoader.disableScripts(); // Clean state for next test + ScriptLoader.disableScripts(); // Clean state for next test // Get results and show them String[] lines = TestTracker.collectResults().createReport().split("\n"); diff --git a/src/main/java/ch/njol/skript/tests/TestResults.java b/src/main/java/ch/njol/skript/tests/TestResults.java index 5449c3b1671..ecc9045f0b2 100644 --- a/src/main/java/ch/njol/skript/tests/TestResults.java +++ b/src/main/java/ch/njol/skript/tests/TestResults.java @@ -22,45 +22,52 @@ import java.util.Set; /** - * Contains test results: successes and failures. Can be serialized e.g. - * with GSON for transfering it between processes. + * Contains test results: successes, failures and doc failure. Will be serialized with Gson + * for transfering it between processes of gradle/environment and the actual spigot server. */ public class TestResults { - + /** * Succeeded tests. */ private final Set<String> succeeded; - + /** * Failed tests. */ private final Map<String, String> failed; - - public TestResults(Set<String> succeeded, Map<String, String> failed) { + + /** + * If the docs failed to generate when running gradle genDocs command. + */ + private final boolean docsFailed; + + public TestResults(Set<String> succeeded, Map<String, String> failed, boolean docs_failed) { + this.docsFailed = docs_failed; this.succeeded = succeeded; this.failed = failed; } - + public Set<String> getSucceeded() { return succeeded; } - + public Map<String, String> getFailed() { return failed; } - - @SuppressWarnings("null") + + public boolean docsFailed() { + return docsFailed; + } + public String createReport() { StringBuilder sb = new StringBuilder("Succeeded:\n"); - for (String test : succeeded) { + for (String test : succeeded) sb.append(test).append('\n'); - } sb.append("Failed:\n"); - for (Map.Entry<String, String> entry : failed.entrySet()) { + for (Map.Entry<String, String> entry : failed.entrySet()) sb.append(entry.getKey()).append(": ").append(entry.getValue()).append('\n'); - } return sb.toString(); } - + } diff --git a/src/main/java/ch/njol/skript/tests/platform/Environment.java b/src/main/java/ch/njol/skript/tests/platform/Environment.java index f0fa4c3aaec..763a9d94f90 100644 --- a/src/main/java/ch/njol/skript/tests/platform/Environment.java +++ b/src/main/java/ch/njol/skript/tests/platform/Environment.java @@ -87,7 +87,6 @@ public static class PaperResource extends Resource { @Nullable private transient String source; - @SuppressWarnings("ConstantConditions") public PaperResource(String version, String target) { super(null, target); this.version = version; @@ -220,14 +219,20 @@ public void initialize(Path dataRoot, Path runnerRoot, boolean remake) throws IO } } - public TestResults runTests(Path runnerRoot, Path testsRoot, boolean devMode, String... jvmArgs) throws IOException, InterruptedException { + @Nullable + public TestResults runTests(Path runnerRoot, Path testsRoot, boolean devMode, boolean genDocs, String... jvmArgs) throws IOException, InterruptedException { Path env = runnerRoot.resolve(name); + Path resultsPath = env.resolve("test_results.json"); + Files.deleteIfExists(resultsPath); List<String> args = new ArrayList<>(); - args.add("java"); + args.add(System.getProperty("java.home") + File.separator + "bin" + File.separator + "java"); args.add("-ea"); args.add("-Dskript.testing.enabled=true"); args.add("-Dskript.testing.dir=" + testsRoot); args.add("-Dskript.testing.devMode=" + devMode); + args.add("-Dskript.testing.genDocs=" + genDocs); + if (genDocs) + args.add("-Dskript.forceregisterhooks"); args.add("-Dskript.testing.results=test_results.json"); args.add("-Ddisable.watchdog=true"); args.addAll(Arrays.asList(jvmArgs)); @@ -258,16 +263,17 @@ public void run() { System.exit(1); } } - }, 8 * 60_000); + }, 8 * 60_000); // 8 minutes. } int code = process.waitFor(); - if (code != 0) { + if (code != 0) throw new IOException("environment returned with code " + code); - } // Read test results - TestResults results = new Gson().fromJson(new String(Files.readAllBytes(env.resolve("test_results.json"))), TestResults.class); + if (!Files.exists(resultsPath)) + return null; + TestResults results = new Gson().fromJson(new String(Files.readAllBytes(resultsPath)), TestResults.class); assert results != null; return results; } diff --git a/src/main/java/ch/njol/skript/tests/platform/PlatformMain.java b/src/main/java/ch/njol/skript/tests/platform/PlatformMain.java index af1c27fb762..0098f97200e 100644 --- a/src/main/java/ch/njol/skript/tests/platform/PlatformMain.java +++ b/src/main/java/ch/njol/skript/tests/platform/PlatformMain.java @@ -33,9 +33,12 @@ import java.util.Set; import java.util.stream.Collectors; +import org.apache.commons.lang.StringUtils; + import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonSyntaxException; + import ch.njol.skript.tests.TestResults; import ch.njol.util.NonNullPair; @@ -58,6 +61,7 @@ public static void main(String... args) throws IOException, InterruptedException Path envsRoot = Paths.get(args[3]); assert envsRoot != null; boolean devMode = "true".equals(args[4]); + boolean genDocs = "true".equals(args[5]); // Load environments List<Environment> envs; @@ -80,14 +84,26 @@ public static void main(String... args) throws IOException, InterruptedException Set<String> allTests = new HashSet<>(); Map<String, List<NonNullPair<Environment, String>>> failures = new HashMap<>(); + boolean docsFailed = false; // Run tests and collect the results envs.sort(Comparator.comparing(Environment::getName)); for (Environment env : envs) { System.out.println("Starting testing on " + env.getName()); env.initialize(dataRoot, runnerRoot, false); - TestResults results = env.runTests(runnerRoot, testsRoot, devMode, "-Xmx1G"); + TestResults results = env.runTests(runnerRoot, testsRoot, devMode, genDocs, "-Xmx5G"); + if (results == null) { + if (devMode) { + // Nothing to report, it's the dev mode environment. + System.exit(0); + return; + } + System.err.println("The test environment '" + env.getName() + "' failed to produce test results."); + System.exit(3); + return; + } // Collect results + docsFailed = results.docsFailed(); allTests.addAll(results.getSucceeded()); allTests.addAll(results.getFailed().keySet()); for (Map.Entry<String, String> fail : results.getFailed().entrySet()) { @@ -97,7 +113,19 @@ public static void main(String... args) throws IOException, InterruptedException .add(new NonNullPair<>(env, error)); } } - + + if (docsFailed) { + System.err.println("Documentation templates not found. Cannot generate docs!"); + System.exit(2); + return; + } + + // Task was to generate docs, no test results other than docsFailed. + if (genDocs) { + System.exit(0); + return; + } + // Sort results in alphabetical order List<String> succeeded = allTests.stream().filter(name -> !failures.containsKey(name)).collect(Collectors.toList()); Collections.sort(succeeded); @@ -105,9 +133,10 @@ public static void main(String... args) throws IOException, InterruptedException Collections.sort(failNames); // All succeeded tests in a single line + System.out.printf("%s Results %s%n", StringUtils.repeat("-", 25), StringUtils.repeat("-", 25)); System.out.println("Tested environments: " + String.join(", ", envs.stream().map(Environment::getName).collect(Collectors.toList()))); - System.out.println("Succeeded: " + String.join(", ", succeeded)); + System.out.println("\nSucceeded: " + String.join(", ", succeeded)); if (!failNames.isEmpty()) { // More space for failed tests, they're important System.err.println("Failed:"); for (String failed : failNames) { @@ -119,5 +148,7 @@ public static void main(String... args) throws IOException, InterruptedException } System.exit(failNames.size()); // Error code to indicate how many tests failed } + System.out.printf("%n%s", StringUtils.repeat("-", 60)); } + } diff --git a/src/main/java/ch/njol/skript/tests/runner/TestMode.java b/src/main/java/ch/njol/skript/tests/runner/TestMode.java index 731823b35cd..d4612da0b4a 100644 --- a/src/main/java/ch/njol/skript/tests/runner/TestMode.java +++ b/src/main/java/ch/njol/skript/tests/runner/TestMode.java @@ -30,39 +30,48 @@ * Static utilities for Skript's 'test mode'. */ public class TestMode { - + private static final String ROOT = "skript.testing."; - + /** * Determines if test mode is enabled. In test mode, Skript will not load * normal scripts, working with {@link #TEST_DIR} instead. */ public static final boolean ENABLED = "true".equals(System.getProperty(ROOT + "enabled")); - + /** * Root path for scripts containing tests. If {@link #DEV_MODE} is enabled, * a command will be available to run them individually or collectively. * Otherwise, all tests are run, results are written in JSON format to * {@link #RESULTS_FILE} as in {@link TestResults}. */ - @SuppressWarnings("null") public static final Path TEST_DIR = ENABLED ? Paths.get(System.getProperty(ROOT + "dir")) : null; - + /** * Enable test development mode. Skript will allow individual test scripts * to be loaded and ran, and prints results to chat or console. */ public static final boolean DEV_MODE = ENABLED && "true".equals(System.getProperty(ROOT + "devMode")); - + + /** + * If Skript should run the gen-docs command. + */ + public static final boolean GEN_DOCS = "true".equals(System.getProperty(ROOT + "genDocs")); + /** * Path to file where to save results in JSON format. */ - @SuppressWarnings("null") public static final Path RESULTS_FILE = ENABLED ? Paths.get(System.getProperty(ROOT + "results")) : null; - + /** * In development mode, file that was last run. */ @Nullable public static File lastTestFile; + + /** + * If the docs failed due to templates or other exceptions. Only updates if TestMode.GEN_DOCS is set. + */ + public static boolean docsFailed; + } diff --git a/src/main/java/ch/njol/skript/tests/runner/TestTracker.java b/src/main/java/ch/njol/skript/tests/runner/TestTracker.java index 7aeaa5003f8..a80ca913814 100644 --- a/src/main/java/ch/njol/skript/tests/runner/TestTracker.java +++ b/src/main/java/ch/njol/skript/tests/runner/TestTracker.java @@ -65,7 +65,7 @@ public static Set<String> getSucceededTests() { } public static TestResults collectResults() { - TestResults results = new TestResults(getSucceededTests(), getFailedTests()); + TestResults results = new TestResults(getSucceededTests(), getFailedTests(), TestMode.docsFailed); startedTests.clear(); failedTests.clear(); return results; diff --git a/src/test/resources/runner_data/server.properties.generic b/src/test/resources/runner_data/server.properties.generic index 08d1e59b772..84816088740 100644 --- a/src/test/resources/runner_data/server.properties.generic +++ b/src/test/resources/runner_data/server.properties.generic @@ -17,7 +17,7 @@ resource-pack= level-name=world rcon.password= player-idle-timeout=0 -motd=A Minecraft Server +motd=A Skript Server query.port=25580 debug=false force-gamemode=false diff --git a/src/test/skript/README.md b/src/test/skript/README.md index dabda3a5c85..4d676330df9 100644 --- a/src/test/skript/README.md +++ b/src/test/skript/README.md @@ -75,7 +75,7 @@ some syntaxes for test development are available. Use Gradle to launch a test development server: ``` -TERM=dumb ./gradlew skriptTestDev +TERM=dumb ./gradlew clean skriptTestDev ``` The server launched will be running at localhost:25565. You can use console diff --git a/src/test/skript/environments/main/paper-1.17.1.json b/src/test/skript/environments/java17/paper-1.17.1.json similarity index 100% rename from src/test/skript/environments/main/paper-1.17.1.json rename to src/test/skript/environments/java17/paper-1.17.1.json diff --git a/src/test/skript/environments/main/paper-1.18.2.json b/src/test/skript/environments/java17/paper-1.18.2.json similarity index 100% rename from src/test/skript/environments/main/paper-1.18.2.json rename to src/test/skript/environments/java17/paper-1.18.2.json diff --git a/src/test/skript/environments/java17/paper-1.19.json b/src/test/skript/environments/java17/paper-1.19.json new file mode 100644 index 00000000000..63525c93239 --- /dev/null +++ b/src/test/skript/environments/java17/paper-1.19.json @@ -0,0 +1,17 @@ +{ + "name": "paper-1.19", + "resources": [ + {"source": "server.properties.generic", "target": "server.properties"} + ], + "paperDownloads": [ + { + "version": "1.19", + "target": "paperclip.jar" + } + ], + "skriptTarget": "plugins/Skript.jar", + "commandLine": [ + "-Dcom.mojang.eula.agree=true", + "-jar", "paperclip.jar", "--nogui" + ] +} diff --git a/src/test/skript/environments/extra/paper-1.13.2.json b/src/test/skript/environments/java8/paper-1.13.2.json similarity index 100% rename from src/test/skript/environments/extra/paper-1.13.2.json rename to src/test/skript/environments/java8/paper-1.13.2.json diff --git a/src/test/skript/environments/extra/paper-1.14.4.json b/src/test/skript/environments/java8/paper-1.14.4.json similarity index 100% rename from src/test/skript/environments/extra/paper-1.14.4.json rename to src/test/skript/environments/java8/paper-1.14.4.json diff --git a/src/test/skript/environments/extra/paper-1.15.2.json b/src/test/skript/environments/java8/paper-1.15.2.json similarity index 89% rename from src/test/skript/environments/extra/paper-1.15.2.json rename to src/test/skript/environments/java8/paper-1.15.2.json index b7075f12c6a..08a849ff98d 100644 --- a/src/test/skript/environments/extra/paper-1.15.2.json +++ b/src/test/skript/environments/java8/paper-1.15.2.json @@ -12,6 +12,6 @@ "skriptTarget": "plugins/Skript.jar", "commandLine": [ "-Dcom.mojang.eula.agree=true", - "-jar", "paperclip.jar" + "-jar", "paperclip.jar", "--nogui" ] } diff --git a/src/test/skript/environments/extra/paper-1.16.5.json b/src/test/skript/environments/java8/paper-1.16.5.json similarity index 100% rename from src/test/skript/environments/extra/paper-1.16.5.json rename to src/test/skript/environments/java8/paper-1.16.5.json From 0871a1defa30580d5c0e7f24b8b08f298cdaa3eb Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Fri, 22 Jul 2022 11:25:12 -0600 Subject: [PATCH 066/619] Add dependabot (#4847) --- .github/dependabot.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000000..ebd2f90e622 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: +- package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" +- package-ecosystem: gradle + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 From bacbb92143016d0b650e612166137ac0cfe95205 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <alali_ayham@yahoo.com> Date: Sat, 23 Jul 2022 02:01:21 +0300 Subject: [PATCH 067/619] =?UTF-8?q?=F0=9F=9A=80=20Add=20Anvil=20Damage=20E?= =?UTF-8?q?vent=20(#4619)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../classes/data/BukkitEventValues.java | 27 +++++++++++++++++++ .../ch/njol/skript/events/SimpleEvents.java | 21 ++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) 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 c0cdc61f72d..592112b4d1c 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -34,6 +34,7 @@ import ch.njol.skript.util.Getter; import ch.njol.skript.util.slot.InventorySlot; import ch.njol.skript.util.slot.Slot; +import com.destroystokyo.paper.event.block.AnvilDamagedEvent; import com.destroystokyo.paper.event.entity.ProjectileCollideEvent; import com.destroystokyo.paper.event.player.PlayerArmorChangeEvent; import org.bukkit.Bukkit; @@ -100,6 +101,7 @@ import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.event.inventory.InventoryOpenEvent; import org.bukkit.event.inventory.InventoryPickupItemEvent; +import org.bukkit.event.inventory.PrepareAnvilEvent; import org.bukkit.event.inventory.PrepareItemCraftEvent; import org.bukkit.event.player.PlayerBedEnterEvent; import org.bukkit.event.player.PlayerBedLeaveEvent; @@ -1011,6 +1013,31 @@ public Inventory get(final InventoryClickEvent e) { return e.getClickedInventory(); } }, 0); + // PrepareAnvilEvent + EventValues.registerEventValue(PrepareAnvilEvent.class, ItemStack.class, new Getter<ItemStack, PrepareAnvilEvent>() { + @Override + @Nullable + public ItemStack get(PrepareAnvilEvent e) { + return e.getResult(); + } + }, EventValues.TIME_NOW); + EventValues.registerEventValue(PrepareAnvilEvent.class, Inventory.class, new Getter<Inventory, PrepareAnvilEvent>() { + @Override + @Nullable + public Inventory get(PrepareAnvilEvent e) { + return e.getInventory(); + } + }, EventValues.TIME_NOW); + // AnvilDamagedEvent + if (Skript.classExists("com.destroystokyo.paper.event.block.AnvilDamagedEvent")) { + EventValues.registerEventValue(AnvilDamagedEvent.class, Inventory.class, new Getter<Inventory, AnvilDamagedEvent>() { + @Override + @Nullable + public Inventory get(AnvilDamagedEvent e) { + return e.getInventory(); + } + }, EventValues.TIME_NOW); + } //BlockFertilizeEvent EventValues.registerEventValue(BlockFertilizeEvent.class, Player.class, new Getter<Player, BlockFertilizeEvent>() { @Nullable diff --git a/src/main/java/ch/njol/skript/events/SimpleEvents.java b/src/main/java/ch/njol/skript/events/SimpleEvents.java index 4ccc7c1905f..a22bde93186 100644 --- a/src/main/java/ch/njol/skript/events/SimpleEvents.java +++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java @@ -18,6 +18,7 @@ */ package ch.njol.skript.events; +import com.destroystokyo.paper.event.block.AnvilDamagedEvent; import org.bukkit.event.block.BlockCanBuildEvent; import org.bukkit.event.block.BlockDamageEvent; import org.bukkit.event.block.BlockFertilizeEvent; @@ -40,7 +41,6 @@ import org.bukkit.event.entity.EntityExplodeEvent; import org.bukkit.event.entity.EntityPortalEnterEvent; import org.bukkit.event.entity.EntityPortalEvent; -import org.bukkit.event.entity.EntityPortalExitEvent; import org.bukkit.event.entity.EntityRegainHealthEvent; import org.bukkit.event.entity.EntityResurrectEvent; import org.bukkit.event.entity.EntityTameEvent; @@ -59,6 +59,7 @@ import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.event.inventory.InventoryOpenEvent; import org.bukkit.event.inventory.InventoryPickupItemEvent; +import org.bukkit.event.inventory.PrepareAnvilEvent; import org.bukkit.event.player.PlayerAnimationEvent; import org.bukkit.event.player.PlayerBedEnterEvent; import org.bukkit.event.player.PlayerBedLeaveEvent; @@ -607,5 +608,23 @@ public class SimpleEvents { "\t\tsend \"Oops! Mending failed!\" to player") .since("2.5.1"); } + Skript.registerEvent("Anvil Prepare", SimpleEvent.class, PrepareAnvilEvent.class, "anvil prepar(e|ing)") + .description("Called when an item is put in a slot for repair by an anvil. Please note that this event is called multiple times in a single item slot move.") + .examples("on anvil prepare:", + "\tevent-item is set # result item", + "\tchance of 5%:", + "\t\tset repair cost to repair cost * 50%", + "\t\tsend \"You're LUCKY! You got 50% discount.\" to player") + .since("INSERT VERSION"); + if (Skript.classExists("com.destroystokyo.paper.event.block.AnvilDamagedEvent")) { + Skript.registerEvent("Anvil Damage", SimpleEvent.class, AnvilDamagedEvent.class, "anvil damag(e|ing)") + .description("Called when an anvil is damaged/broken from being used to repair/rename items.", + "Note: this does not include anvil damage from falling.") + .requiredPlugins("Paper") + .examples("on anvil damage:", + "\tcancel the event") + .since("INSERT VERSION"); + } } + } From 7872fd40f22a10931d0f7a26d5e00f605e2c615b Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Fri, 22 Jul 2022 17:32:35 -0600 Subject: [PATCH 068/619] Add colours to the collection expression (#4899) --- .../ch/njol/skript/expressions/ExprItems.java | 186 ----------------- .../ch/njol/skript/expressions/ExprSets.java | 190 ++++++++++++++++++ .../tests/syntaxes/expressions/ExprSets.sk | 12 ++ 3 files changed, 202 insertions(+), 186 deletions(-) delete mode 100644 src/main/java/ch/njol/skript/expressions/ExprItems.java create mode 100644 src/main/java/ch/njol/skript/expressions/ExprSets.java create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprSets.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprItems.java b/src/main/java/ch/njol/skript/expressions/ExprItems.java deleted file mode 100644 index f2ab9c58407..00000000000 --- a/src/main/java/ch/njol/skript/expressions/ExprItems.java +++ /dev/null @@ -1,186 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.expressions; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.NoSuchElementException; - -import org.bukkit.Material; -import org.bukkit.event.Event; -import org.bukkit.inventory.ItemStack; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.Skript; -import ch.njol.skript.aliases.ItemType; -import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Examples; -import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.Since; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.ExpressionType; -import ch.njol.skript.lang.Literal; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.lang.util.SimpleExpression; -import ch.njol.util.Kleenean; -import ch.njol.util.NullableChecker; -import ch.njol.util.coll.iterator.ArrayIterator; -import ch.njol.util.coll.iterator.CheckedIterator; -import ch.njol.util.coll.iterator.IteratorIterable; - -/** - * @author Peter Güttinger - */ -@Name("Items") -@Description("Items or blocks of a specific type, useful for looping.") -@Examples({"loop items of type ore and log:", - " block contains loop-item", - " message \"Theres at least one %loop-item% in this block\"", - "drop all blocks at the player # drops one of every block at the player"}) -@Since("<i>unknown</i> (before 2.1)") -public class ExprItems extends SimpleExpression<ItemStack> { - - static { - Skript.registerExpression(ExprItems.class, ItemStack.class, ExpressionType.COMBINED, - "[(all [[of] the]|the|every)] item(s|[ ]types)", "[(all [[of] the]|the)] items of type[s] %itemtypes%", - "[(all [[of] the]|the|every)] block(s|[ ]types)", "[(all [[of] the]|the)] blocks of type[s] %itemtypes%"); - } - - @Nullable - Expression<ItemType> types = null; - private boolean blocks = false; - - @SuppressWarnings("unchecked") - @Override - public boolean init(final Expression<?>[] vars, final int matchedPattern, final Kleenean isDelayed, final ParseResult parser) { - if (vars.length > 0) - types = (Expression<ItemType>) vars[0]; - blocks = matchedPattern >= 2; - if (types instanceof Literal) { - for (final ItemType t : ((Literal<ItemType>) types).getAll()) - t.setAll(true); - } - return true; - } - - @Nullable - private ItemStack[] buffer = null; - - @SuppressWarnings("null") - @Override - protected ItemStack[] get(final Event e) { - if (buffer != null) - return buffer; - final ArrayList<ItemStack> r = new ArrayList<>(); - for (final ItemStack is : new IteratorIterable<>(iterator(e))) - r.add(is); - if (types instanceof Literal) - return buffer = r.toArray(new ItemStack[r.size()]); - return r.toArray(new ItemStack[r.size()]); - } - - @Override - @Nullable - public Iterator<ItemStack> iterator(final Event e) { - Iterator<ItemStack> iter; - if (types == null) { - iter = new Iterator<ItemStack>() { - - private final Iterator<Material> iter = new ArrayIterator<>(Material.values()); - - @Override - public boolean hasNext() { - return iter.hasNext(); - } - - @Override - public ItemStack next() { - return new ItemStack(iter.next()); - } - - @Override - public void remove() {} - - }; - } else { - @SuppressWarnings("null") - final Iterator<ItemType> it = new ArrayIterator<>(types.getArray(e)); - if (!it.hasNext()) - return null; - iter = new Iterator<ItemStack>() { - - @SuppressWarnings("null") - Iterator<ItemStack> current = it.next().getAll().iterator(); - - @SuppressWarnings("null") - @Override - public boolean hasNext() { - while (!current.hasNext() && it.hasNext()) { - current = it.next().getAll().iterator(); - } - return current.hasNext(); - } - - @SuppressWarnings("null") - @Override - public ItemStack next() { - if (!hasNext()) - throw new NoSuchElementException(); - return current.next(); - } - - @Override - public void remove() {} - - }; - } - - if (!blocks) - return iter; - - return new CheckedIterator<>(iter, new NullableChecker<ItemStack>() { - @Override - public boolean check(final @Nullable ItemStack is) { - return is != null && is.getType().isBlock(); - } - }); - } - - @Override - public Class<? extends ItemStack> getReturnType() { - return ItemStack.class; - } - - @Override - public String toString(final @Nullable Event e, final boolean debug) { - final Expression<ItemType> types = this.types; - return (blocks ? "blocks" : "items") + (types != null ? " of type" + (types.isSingle() ? "" : "s") + " " + types.toString(e, debug) : ""); - } - - @Override - public boolean isSingle() { - return false; - } - - @Override - public boolean isLoopOf(final String s) { - return blocks && s.equalsIgnoreCase("block") || !blocks && s.equalsIgnoreCase("item"); - } - -} diff --git a/src/main/java/ch/njol/skript/expressions/ExprSets.java b/src/main/java/ch/njol/skript/expressions/ExprSets.java new file mode 100644 index 00000000000..0bc5e7243f3 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprSets.java @@ -0,0 +1,190 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +import org.bukkit.Material; +import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; +import org.eclipse.jdt.annotation.Nullable; + +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.skript.util.Color; +import ch.njol.skript.util.SkriptColor; +import ch.njol.util.Kleenean; +import ch.njol.util.NullableChecker; +import ch.njol.util.coll.iterator.ArrayIterator; +import ch.njol.util.coll.iterator.CheckedIterator; +import ch.njol.util.coll.iterator.IteratorIterable; + +@Name("Sets") +@Description("Collection sets of items or blocks of a specific type or colours, useful for looping.") +@Examples({ + "loop items of type ore and log:", + "\tblock contains loop-item", + "\tmessage \"Theres at least one %loop-item% in this block\"", + "drop all blocks at the player # drops one of every block at the player" +}) +@Since("<i>unknown</i> (before 1.4.2), INSERT VERSION (colors)") +public class ExprSets extends SimpleExpression<Object> { + + static { + Skript.registerExpression(ExprSets.class, Object.class, ExpressionType.COMBINED, + "[(all [[of] the]|the|every)] item(s|[ ]types)", "[(all [[of] the]|the)] items of type[s] %itemtypes%", + "[(all [[of] the]|the|every)] block(s|[ ]types)", "[(all [[of] the]|the)] blocks of type[s] %itemtypes%", + "([all [[of] the]] colo[u]rs|(the|every) colo[u]r)"); + } + + @Nullable + private Expression<ItemType> types; + private int pattern = -1; + + @SuppressWarnings("unchecked") + @Override + public boolean init(Expression<?>[] vars, int matchedPattern, Kleenean isDelayed, ParseResult parser) { + if (vars.length > 0) + types = (Expression<ItemType>) vars[0]; + pattern = matchedPattern; + if (types instanceof Literal) { + for (ItemType type : ((Literal<ItemType>) types).getAll()) + type.setAll(true); + } + return true; + } + + private Object[] buffer = null; + + @Override + protected Object[] get(Event event) { + if (buffer != null) + return buffer; + List<Object> elements = new ArrayList<>(); + for (Object element : new IteratorIterable<>(iterator(event))) + elements.add(element); + if (types instanceof Literal) + return buffer = elements.toArray(); + return elements.toArray(); + } + + @Override + @Nullable + public Iterator<Object> iterator(Event event) { + if (pattern == 4) + return new ArrayIterator<>(SkriptColor.values()); + if (pattern == 0 || pattern == 3) + return new Iterator<Object>() { + + private final Iterator<Material> iter = new ArrayIterator<>(Material.values()); + + @Override + public boolean hasNext() { + return iter.hasNext(); + } + + @Override + public ItemStack next() { + return new ItemStack(iter.next()); + } + + @Override + public void remove() {} + + }; + Iterator<ItemType> it = new ArrayIterator<>(types.getArray(event)); + if (!it.hasNext()) + return null; + Iterator<Object> iter; + iter = new Iterator<Object>() { + + Iterator<ItemStack> current = it.next().getAll().iterator(); + + @Override + public boolean hasNext() { + while (!current.hasNext() && it.hasNext()) { + current = it.next().getAll().iterator(); + } + return current.hasNext(); + } + + @Override + public ItemStack next() { + if (!hasNext()) + throw new NoSuchElementException(); + return current.next(); + } + + @Override + public void remove() {} + + }; + + return new CheckedIterator<Object>(iter, new NullableChecker<Object>() { + @Override + public boolean check(@Nullable Object ob) { + if (ob == null) + return false; + if (ob instanceof ItemStack) + if (!((ItemStack) ob).getType().isBlock()) + return false; + return true; + } + }); + } + + @Override + public Class<? extends Object> getReturnType() { + if (pattern == 4) + return Color.class; + return ItemStack.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + if (event == null) + return "sets of data. Pattern " + pattern; + if (pattern == 4) + return "colours"; + return (pattern < 2 ? "blocks" : "items") + (types != null ? " of type" + (types.isSingle() ? "" : "s") + " " + types.toString(event, debug) : ""); + } + + @Override + public boolean isSingle() { + return false; + } + + @Override + public boolean isLoopOf(String s) { + return pattern == 4 && (s.equalsIgnoreCase("color") || s.equalsIgnoreCase("colour"))|| pattern >= 2 && s.equalsIgnoreCase("block") || pattern < 2 && s.equalsIgnoreCase("item"); + } + +} diff --git a/src/test/skript/tests/syntaxes/expressions/ExprSets.sk b/src/test/skript/tests/syntaxes/expressions/ExprSets.sk new file mode 100644 index 00000000000..28446c8d17c --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprSets.sk @@ -0,0 +1,12 @@ +test "colours set": + set {_set::*} to black, dark grey, grey, white, blue, brown, cyan, light cyan, green, light green, yellow, orange, red, pink, purple and magenta + loop all colours: + # Contains loop checking in the expression. + {_set::*} contains loop-colour + add 1 to {_count} + assert {_count} is size of {_set::*} with "All the colours in the collection set expression didn't match the required output" + +test "item sets": + assert items of type ore contains gold ore, diamond ore, coal ore and emerald ore with "Ores did not contain classic ore types" + assert items of type log contains dark oak log, oak log, jungle log and spruce log with "Logs did not contain the defined log checks" + From 0db7944d7b7a46a70795c8bd4f666cd31a0c7785 Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Sat, 23 Jul 2022 11:01:58 +0200 Subject: [PATCH 069/619] Improve event values move events (#4895) * Make past location event value include PlayerMoveEvent Add future and default location event value for PlayerMoveEvent Add past, default and future event value for EntityMoveEvent --- .../classes/data/BukkitEventValues.java | 32 +++++++++++++++++-- .../java/ch/njol/skript/events/EvtMove.java | 13 ++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) 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 592112b4d1c..5930abd4400 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -37,6 +37,7 @@ import com.destroystokyo.paper.event.block.AnvilDamagedEvent; import com.destroystokyo.paper.event.entity.ProjectileCollideEvent; import com.destroystokyo.paper.event.player.PlayerArmorChangeEvent; +import io.papermc.paper.event.entity.EntityMoveEvent; import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.FireworkEffect; @@ -1231,13 +1232,38 @@ public TeleportCause get(final PlayerTeleportEvent e) { return e.getCause(); } }, 0); - EventValues.registerEventValue(PlayerTeleportEvent.class, Location.class, new Getter<Location, PlayerTeleportEvent>() { + //PlayerMoveEvent + EventValues.registerEventValue(PlayerMoveEvent.class, Location.class, new Getter<Location, PlayerMoveEvent>() { @Override @Nullable - public Location get(final PlayerTeleportEvent e) { + public Location get(PlayerMoveEvent e) { return e.getFrom(); } - }, -1); + }, EventValues.TIME_PAST); + EventValues.registerEventValue(PlayerMoveEvent.class, Location.class, new Getter<Location, PlayerMoveEvent>() { + @Override + @Nullable + public Location get(PlayerMoveEvent e) { + return e.getTo(); + } + }, EventValues.TIME_NOW); + //EntityMoveEvent + if (Skript.classExists("io.papermc.paper.event.entity.EntityMoveEvent")) { + EventValues.registerEventValue(EntityMoveEvent.class, Location.class, new Getter<Location, EntityMoveEvent>() { + @Override + @Nullable + public Location get(EntityMoveEvent e) { + return e.getFrom(); + } + }, EventValues.TIME_NOW); + EventValues.registerEventValue(EntityMoveEvent.class, Location.class, new Getter<Location, EntityMoveEvent>() { + @Override + @Nullable + public Location get(EntityMoveEvent e) { + return e.getTo(); + } + }, EventValues.TIME_FUTURE); + } //PlayerToggleFlightEvent EventValues.registerEventValue(PlayerToggleFlightEvent.class, Player.class, new Getter<Player, PlayerToggleFlightEvent>() { @Override diff --git a/src/main/java/ch/njol/skript/events/EvtMove.java b/src/main/java/ch/njol/skript/events/EvtMove.java index 7454ab76927..58dfb2f2351 100644 --- a/src/main/java/ch/njol/skript/events/EvtMove.java +++ b/src/main/java/ch/njol/skript/events/EvtMove.java @@ -87,6 +87,19 @@ public boolean check(Event event) { return false; } + @Override + @Nullable + @SuppressWarnings("unchecked") + public Class<? extends Event> [] getEventClasses() { + if (isPlayer) { + return new Class[] {PlayerMoveEvent.class}; + } else if (HAS_ENTITY_MOVE) { + return new Class[] {EntityMoveEvent.class}; + } else { + return null; + } + } + @Override public String toString(@Nullable Event e, boolean debug) { return type + " move"; From af6740a50602e94c7155de5366df42977a236aba Mon Sep 17 00:00:00 2001 From: APickledWalrus <apickledwalrus@gmail.com> Date: Sat, 23 Jul 2022 05:14:58 -0400 Subject: [PATCH 070/619] Implement warning system for long parse times (#4784) * Implement warning system for long parse times --- src/main/java/ch/njol/skript/ScriptLoader.java | 11 +++++++++++ src/main/java/ch/njol/skript/SkriptConfig.java | 3 ++- src/main/resources/config.sk | 5 +++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/ScriptLoader.java b/src/main/java/ch/njol/skript/ScriptLoader.java index d8dd1316458..0186fbe1b63 100644 --- a/src/main/java/ch/njol/skript/ScriptLoader.java +++ b/src/main/java/ch/njol/skript/ScriptLoader.java @@ -59,6 +59,7 @@ import ch.njol.skript.util.ExceptionUtils; import ch.njol.skript.util.SkriptColor; import ch.njol.skript.util.Task; +import ch.njol.skript.util.Timespan; import ch.njol.skript.variables.TypeHints; import ch.njol.skript.variables.Variables; import ch.njol.util.Kleenean; @@ -1114,9 +1115,19 @@ public static ArrayList<TriggerItem> loadItems(SectionNode node) { if (!SkriptParser.validateLine(expr)) continue; + long start = System.currentTimeMillis(); Statement stmt = Statement.parse(expr, "Can't understand this condition/effect: " + expr); if (stmt == null) continue; + long requiredTime = SkriptConfig.longParseTimeWarningThreshold.value().getMilliSeconds(); + if (requiredTime > 0) { + long timeTaken = System.currentTimeMillis() - start; + if (timeTaken > requiredTime) + Skript.warning( + "The current line took a long time to parse (" + new Timespan(timeTaken) + ")." + + " Avoid using long lines and use parentheses to create clearer instructions." + ); + } if (Skript.debug() || n.debug()) Skript.debug(SkriptColor.replaceColorChar(getParser().getIndentation() + stmt.toString(null, true))); diff --git a/src/main/java/ch/njol/skript/SkriptConfig.java b/src/main/java/ch/njol/skript/SkriptConfig.java index a54ef95412c..c1661ce5877 100644 --- a/src/main/java/ch/njol/skript/SkriptConfig.java +++ b/src/main/java/ch/njol/skript/SkriptConfig.java @@ -18,7 +18,6 @@ */ package ch.njol.skript; -import ch.njol.skript.classes.data.BukkitClasses; import ch.njol.skript.config.Config; import ch.njol.skript.config.EnumParser; import ch.njol.skript.config.Option; @@ -324,6 +323,8 @@ private static void userDisableHooks(Class<? extends Hook<?>> hookClass, boolean } }).optional(true); + public static final Option<Timespan> longParseTimeWarningThreshold = new Option<>("long parse time warning threshold", new Timespan(0)); + /** * This should only be used in special cases */ diff --git a/src/main/resources/config.sk b/src/main/resources/config.sk index 41e3e681b5e..64282a61bad 100644 --- a/src/main/resources/config.sk +++ b/src/main/resources/config.sk @@ -204,6 +204,11 @@ player name regex pattern: [a-zA-Z0-9_]{1,16} # A regex pattern that is used to match player names. # This can be used if you are using Geyser, where some usernames are prefixed by a certain character. +long parse time warning threshold: 0 seconds +# This setting determines how long a statement can take to parse before Skript produces a warning +# stating that the statement has taken a long time to parse. +# A value of 0 seconds means that this warning should be disabled. + # ==== Variables ==== databases: From 2a1b52b134c708a17eba39288291057aefca22e5 Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Sat, 23 Jul 2022 11:26:54 +0200 Subject: [PATCH 071/619] Add world null check (#4700) * Add world null check --- .../ch/njol/skript/bukkitutil/EntityUtils.java | 16 +++++++++++++++- .../java/ch/njol/skript/effects/EffTeleport.java | 5 +++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/njol/skript/bukkitutil/EntityUtils.java b/src/main/java/ch/njol/skript/bukkitutil/EntityUtils.java index edcc1baf89b..4173f0331f9 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/EntityUtils.java +++ b/src/main/java/ch/njol/skript/bukkitutil/EntityUtils.java @@ -22,6 +22,7 @@ import ch.njol.skript.entity.EntityData; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; +import org.bukkit.Location; import org.bukkit.entity.*; /** @@ -144,5 +145,18 @@ public static EntityType toBukkitEntityType(EntityData e) { public static EntityData toSkriptEntityData(EntityType e) { return SPAWNER_TYPES.inverse().get(e); } - + + /** + * Teleports the given entity to the given location. + * Teleports to the given location in the entity's world if the location's world is null. + */ + public static void teleport(Entity entity, Location location) { + if (location.getWorld() == null) { + location = location.clone(); + location.setWorld(entity.getWorld()); + } + + entity.teleport(location); + } + } diff --git a/src/main/java/ch/njol/skript/effects/EffTeleport.java b/src/main/java/ch/njol/skript/effects/EffTeleport.java index 673cebd8147..fd253af937b 100644 --- a/src/main/java/ch/njol/skript/effects/EffTeleport.java +++ b/src/main/java/ch/njol/skript/effects/EffTeleport.java @@ -19,6 +19,7 @@ package ch.njol.skript.effects; import ch.njol.skript.Skript; +import ch.njol.skript.bukkitutil.EntityUtils; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; @@ -107,7 +108,7 @@ protected TriggerItem walk(Event e) { if (!isAsync) { for (Entity entity : entityArray) { - entity.teleport(loc); + EntityUtils.teleport(entity, loc); } return next; } @@ -119,7 +120,7 @@ protected TriggerItem walk(Event e) { PaperLib.getChunkAtAsync(loc).thenAccept(chunk -> { // The following is now on the main thread for (Entity entity : entityArray) { - entity.teleport(loc); + EntityUtils.teleport(entity, loc); } // Re-set local variables From 490f3db74ec104bf52ca93a4a41780a1186d8b4e Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Mon, 25 Jul 2022 13:14:32 -0600 Subject: [PATCH 072/619] Fix nightly uploads (#4946) --- .github/workflows/java-17-builds.yml | 9 +++++++-- .github/workflows/java-8-builds.yml | 9 +++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/.github/workflows/java-17-builds.yml b/.github/workflows/java-17-builds.yml index 4fca18351a0..ba936b40b30 100644 --- a/.github/workflows/java-17-builds.yml +++ b/.github/workflows/java-17-builds.yml @@ -1,6 +1,11 @@ name: Java 17 CI (MC 1.17+) -on: [push, pull_request] +on: + push: + branches: + - main + - 'dev/**' + pull_request: jobs: build: @@ -27,4 +32,4 @@ jobs: if: success() with: name: skript-nightly - path: build/libs/Skript-nightly.jar + path: build/libs/* diff --git a/.github/workflows/java-8-builds.yml b/.github/workflows/java-8-builds.yml index 0b466c141a4..57afecccb02 100644 --- a/.github/workflows/java-8-builds.yml +++ b/.github/workflows/java-8-builds.yml @@ -1,6 +1,11 @@ name: Java 8 CI (MC 1.13-1.16) -on: [push, pull_request] +on: + push: + branches: + - main + - 'dev/**' + pull_request: jobs: build: @@ -27,4 +32,4 @@ jobs: if: success() with: name: skript-nightly - path: build/libs/Skript-nightly.jar + path: build/libs/* From a19847338f55ebf8595dd97038f32e64e5fb19c8 Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Mon, 25 Jul 2022 23:54:14 +0300 Subject: [PATCH 073/619] Added 'Raw String' expression (#4883) * Added 'Raw String' expression --- .../skript/expressions/ExprRawString.java | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprRawString.java diff --git a/src/main/java/ch/njol/skript/expressions/ExprRawString.java b/src/main/java/ch/njol/skript/expressions/ExprRawString.java new file mode 100644 index 00000000000..87eca7adf88 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprRawString.java @@ -0,0 +1,100 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.PropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionList; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.VariableString; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.skript.util.LiteralUtils; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@Name("Raw String") +@Description("Returns the string without formatting (colors etc.) and without stripping them from it, " + + "e.g. <code>raw \"&aHello There!\"</code> would output <code>&aHello There!</code>") +@Examples("send raw \"&aThis text is unformatted!\" to all players") +@Since("INSERT VERSION") +public class ExprRawString extends SimpleExpression<String> { + + static { + Skript.registerExpression(ExprRawString.class, String.class, ExpressionType.COMBINED, "raw %strings%"); + } + + @SuppressWarnings("NotNullFieldNotInitialized") + private Expression<String> expr; + @SuppressWarnings("NotNullFieldNotInitialized") + private Expression<? extends String>[] messages; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + expr = (Expression<String>) exprs[0]; + messages = expr instanceof ExpressionList<?> ? + ((ExpressionList<String>) expr).getExpressions() : new Expression[]{expr}; + for (Expression<? extends String> message : messages) { + if (message instanceof ExprColoured) { + Skript.error("The 'colored' expression may not be used in a 'raw string' expression"); + return false; + } + } + return true; + } + + @Override + protected String[] get(Event e) { + List<String> strings = new ArrayList<>(); + for (Expression<? extends String> message : messages) { + if (message instanceof VariableString) { + strings.add(((VariableString) message).toUnformattedString(e)); + continue; + } + strings.addAll(Arrays.asList(message.getArray(e))); + } + return strings.toArray(new String[0]); + } + + @Override + public boolean isSingle() { + return expr.isSingle(); + } + + @Override + public Class<? extends String> getReturnType() { + return String.class; + } + + @Override + public String toString(@Nullable Event e, boolean debug) { + return "raw " + expr.toString(e, debug); + } +} From e9d04e7604c13d0de8d210f88ea0f021cdace009 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Tue, 26 Jul 2022 10:44:40 -0600 Subject: [PATCH 074/619] Attempts to fix the random zombie health test failing (#4962) --- .../tests/syntaxes/effects/EffHealth.sk | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/test/skript/tests/syntaxes/effects/EffHealth.sk b/src/test/skript/tests/syntaxes/effects/EffHealth.sk index 12d9e63463d..41ecf33904b 100644 --- a/src/test/skript/tests/syntaxes/effects/EffHealth.sk +++ b/src/test/skript/tests/syntaxes/effects/EffHealth.sk @@ -1,20 +1,23 @@ test "health effect": + clear all entities set {_i} to diamond sword assert {_i}'s damage value is 0 with "new item durability failed" damage {_i} by 50 assert {_i}'s damage value is 50 with "damage item failed" repair {_i} by 49 assert {_i}'s damage value is 1 with "repair item failed" - spawn zombie at location(0, 64, 0, world "world") - set {_m} to last spawned zombie - assert health of {_m} is 10 with "default zombie health failed" + + spawn cow at location(0, 64, 0, world "world") + set {_m} to last spawned cow + assert health of {_m} is 5 with "default cow health failed" damage {_m} by 0.5 - assert health of {_m} is 9.5 with "damage zombie ##1 failed" + assert health of {_m} is 4.5 with "damage cow ##1 failed" damage {_m} by 99 - assert health of {_m} is 0 with "damage zombie ##2 failed" + assert health of {_m} is 0 with "damage cow ##2 failed" heal {_m} by 1 - assert health of {_m} is 1 with "heal zombie ##1 failed" + assert health of {_m} is 1 with "heal cow ##1 failed" heal {_m} by 0.5 - assert health of {_m} is 1.5 with "heal zombie ##2 failed" + assert health of {_m} is 1.5 with "heal cow ##2 failed" heal {_m} by 99 - assert health of {_m} is 10 with "heal zombie ##3 failed" + assert health of {_m} is 5 with "heal cow ##3 failed" + clear all entities From 5c41519f972db18beed4ba1244018e4a6f51a928 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Wed, 27 Jul 2022 10:59:05 -0600 Subject: [PATCH 075/619] Add support for wolf collar colours (#4921) * Added support for wolf collar colours --- .../ch/njol/skript/entity/EntityData.java | 1 - .../java/ch/njol/skript/entity/WolfData.java | 96 ++++++++++--------- src/main/resources/lang/default.lang | 10 +- 3 files changed, 58 insertions(+), 49 deletions(-) diff --git a/src/main/java/ch/njol/skript/entity/EntityData.java b/src/main/java/ch/njol/skript/entity/EntityData.java index 81eb71be0fc..b8261e26d04 100644 --- a/src/main/java/ch/njol/skript/entity/EntityData.java +++ b/src/main/java/ch/njol/skript/entity/EntityData.java @@ -21,7 +21,6 @@ import ch.njol.skript.Skript; import ch.njol.skript.SkriptAPIException; import ch.njol.skript.bukkitutil.EntityUtils; -import ch.njol.skript.bukkitutil.PlayerUtils; import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.classes.Parser; import ch.njol.skript.classes.Serializer; diff --git a/src/main/java/ch/njol/skript/entity/WolfData.java b/src/main/java/ch/njol/skript/entity/WolfData.java index 8be03f09bdf..3964c209370 100644 --- a/src/main/java/ch/njol/skript/entity/WolfData.java +++ b/src/main/java/ch/njol/skript/entity/WolfData.java @@ -18,113 +18,123 @@ */ package ch.njol.skript.entity; +import org.bukkit.DyeColor; import org.bukkit.entity.Wolf; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.util.Color; -/** - * @author Peter Güttinger - */ public class WolfData extends EntityData<Wolf> { + static { EntityData.register(WolfData.class, "wolf", Wolf.class, 1, - "angry wolf", "wolf", "peaceful wolf", + "peaceful wolf", "wolf", "angry wolf", "wild wolf", "tamed wolf"); } - + + @Nullable + private DyeColor collarColor; + private int angry = 0; -// private String owner = null; private int tamed = 0; - + + @SuppressWarnings("unchecked") @Override - protected boolean init(final Literal<?>[] exprs, final int matchedPattern, final ParseResult parseResult) { + protected boolean init(Literal<?>[] exprs, int matchedPattern, ParseResult parseResult) { if (matchedPattern <= 2) angry = matchedPattern - 1; else tamed = matchedPattern == 3 ? -1 : 1; + if (exprs[0] != null) + collarColor = ((Literal<Color>) exprs[0]).getSingle().asDyeColor(); return true; } - + @Override - protected boolean init(final @Nullable Class<? extends Wolf> c, final @Nullable Wolf e) { - angry = e == null ? 0 : e.isAngry() ? 1 : -1; - tamed = e == null ? 0 : e.isTamed() ? 1 : -1; + protected boolean init(@Nullable Class<? extends Wolf> c, @Nullable Wolf wolf) { + if (wolf != null) { + angry = wolf.isAngry() ? 1 : -1; + tamed = wolf.isTamed() ? 1 : -1; + collarColor = wolf.getCollarColor(); + } return true; } - + @Override - public void set(final Wolf entity) { + public void set(Wolf entity) { if (angry != 0) entity.setAngry(angry == 1); if (tamed != 0) entity.setTamed(tamed == 1); -// if (owner != null) { -// if (owner.isEmpty()) -// entity.setOwner(null); -// else -// entity.setOwner(Bukkit.getOfflinePlayer(owner)); -// } + if (collarColor != null) + entity.setCollarColor(collarColor); } - + @Override - public boolean match(final Wolf entity) { - return (angry == 0 || entity.isAngry() == (angry == 1)) && (tamed == 0 || entity.isTamed() == (tamed == 1)); -// && (owner == null || owner.isEmpty() && entity.getOwner() == null || entity.getOwner() != null && entity.getOwner().getName().equalsIgnoreCase(owner)); + public boolean match(Wolf entity) { + return (angry == 0 || entity.isAngry() == (angry == 1)) && (tamed == 0 || entity.isTamed() == (tamed == 1)) && (collarColor == null ? true : entity.getCollarColor() == collarColor); } - + @Override public Class<Wolf> getType() { return Wolf.class; } - + @Override protected int hashCode_i() { - final int prime = 31; - int result = 1; + int prime = 31, result = 1; result = prime * result + angry; result = prime * result + tamed; + result = prime * result + (collarColor == null ? 0 : collarColor.hashCode()); return result; } - + @Override - protected boolean equals_i(final EntityData<?> obj) { + protected boolean equals_i(EntityData<?> obj) { if (!(obj instanceof WolfData)) return false; - final WolfData other = (WolfData) obj; + WolfData other = (WolfData) obj; if (angry != other.angry) return false; if (tamed != other.tamed) return false; + if (collarColor != other.collarColor) + return false; return true; } - -// return angry + "|" + tamed; + + /** + * Note that this method is only used when changing Skript versions 2.1 to anything above. + */ + @Deprecated @Override - protected boolean deserialize(final String s) { - final String[] split = s.split("\\|"); + protected boolean deserialize(String s) { + String[] split = s.split("\\|"); if (split.length != 2) return false; try { angry = Integer.parseInt(split[0]); tamed = Integer.parseInt(split[1]); return true; - } catch (final NumberFormatException e) { + } catch (NumberFormatException e) { return false; } } - + @Override - public boolean isSupertypeOf(final EntityData<?> e) { - if (e instanceof WolfData) - return (angry == 0 || ((WolfData) e).angry == angry) && (tamed == 0 || ((WolfData) e).tamed == tamed); + public boolean isSupertypeOf(EntityData<?> entityData) { + if (entityData instanceof WolfData) { + WolfData wolfData = (WolfData) entityData; + return (angry == 0 || wolfData.angry == angry) && (tamed == 0 || wolfData.tamed == tamed) && (wolfData.collarColor == collarColor); + } return false; } - + @Override - public EntityData getSuperType() { + public EntityData<Wolf> getSuperType() { return new WolfData(); } - + } diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 2ef13897172..dddb1fdcdc7 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -769,19 +769,19 @@ entities: pattern: wither skull((|1¦s)| projectile(|1¦s)) wolf: name: wol¦f¦ves - pattern: <age> wol(f|1¦ves) + pattern: <age> wol(f|1¦ves) [[with collar] colo[u]r[ed] %-color%] tamed wolf: name: tamed wol¦f¦ves - pattern: <age> dog(|1¦s)|tamed <age> wol(f|1¦ves)|(4¦)pupp(y|1¦ies) + pattern: (<age> dog(|1¦s)|tamed <age> wol(f|1¦ves)|(4¦)pupp(y|1¦ies)) [[with collar] colo[u]r[ed] %-color%] wild wolf: name: wild wol¦f¦ves - pattern: (wild|untamed) <age> wol(f|1¦ves) + pattern: (wild|untamed) <age> wol(f|1¦ves) [[with collar] colo[u]r[ed] %-color%] angry wolf: name: angry wol¦f¦ves @an - pattern: (angry|aggressive) <age> wol(f|1¦ves) + pattern: (angry|aggressive) <age> wol(f|1¦ves) [[with collar] colo[u]r[ed] %-color%] peaceful wolf: name: peaceful wol¦f¦ves - pattern: (peaceful|neutral|unaggressive) <age> wol(f|1¦ves) + pattern: (peaceful|neutral|unaggressive) <age> wol(f|1¦ves) [[with collar] colo[u]r[ed] %-color%] zombie: name: zombie¦s pattern: <age> zombie(|1¦s)|(4¦)zombie (kid(|1¦s)|child(|1¦ren)) From 6f25742d0422f106d8ce6ffa3d5c69fca6bff0c3 Mon Sep 17 00:00:00 2001 From: APickledWalrus <apickledwalrus@gmail.com> Date: Fri, 29 Jul 2022 09:43:25 -0400 Subject: [PATCH 076/619] Fix silverfish events (#4964) --- .../ch/njol/skript/bukkitutil/ItemUtils.java | 15 +++ .../java/ch/njol/skript/effects/EffDrop.java | 18 +--- .../skript/events/EvtEntityBlockChange.java | 98 ++++++------------- 3 files changed, 49 insertions(+), 82 deletions(-) diff --git a/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java b/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java index 5e769682861..f4f9ae4f79a 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java +++ b/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java @@ -96,5 +96,20 @@ public static boolean itemStacksEqual(final @Nullable ItemStack is1, final @Null return is1.getType() == is2.getType() && ItemUtils.getDamage(is1) == ItemUtils.getDamage(is2) && is1.getItemMeta().equals(is2.getItemMeta()); } + + // Only 1.15 and versions after have Material#isAir method + private static final boolean IS_AIR_EXISTS = Skript.methodExists(Material.class, "isAir"); + // Version 1.14 have multiple air types but no Material#isAir method + private static final boolean OTHER_AIR_EXISTS = Skript.isRunningMinecraft(1, 14); + + public static boolean isAir(Material type) { + if (IS_AIR_EXISTS) { + return type.isAir(); + } else if (OTHER_AIR_EXISTS) { + return type == Material.AIR || type == Material.CAVE_AIR || type == Material.VOID_AIR; + } + // All versions prior to 1.14 only have 1 air type + return type == Material.AIR; + } } diff --git a/src/main/java/ch/njol/skript/effects/EffDrop.java b/src/main/java/ch/njol/skript/effects/EffDrop.java index ee0e99f645f..7efdd225162 100644 --- a/src/main/java/ch/njol/skript/effects/EffDrop.java +++ b/src/main/java/ch/njol/skript/effects/EffDrop.java @@ -20,6 +20,7 @@ import ch.njol.skript.Skript; import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.bukkitutil.ItemUtils; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; @@ -85,7 +86,7 @@ public void execute(Event e) { if (o instanceof ItemStack) o = new ItemType((ItemStack) o); for (ItemStack is : ((ItemType) o).getItem().getAll()) { - if (!isAir(is.getType()) && is.getAmount() > 0) { + if (!ItemUtils.isAir(is.getType()) && is.getAmount() > 0) { if (useVelocity) { lastSpawned = l.getWorld().dropItemNaturally(itemDropLoc, is); } else { @@ -101,21 +102,6 @@ public void execute(Event e) { } } - // Only 1.15 and versions after have Material#isAir method - private static final boolean IS_AIR_EXISTS = Skript.methodExists(Material.class, "isAir"); - // Version 1.14 have multiple air types but no Material#isAir method - private static final boolean OTHER_AIR_EXISTS = Skript.isRunningMinecraft(1, 14); - - private static boolean isAir(Material type) { - if (IS_AIR_EXISTS) { - return type.isAir(); - } else if (OTHER_AIR_EXISTS) { - return type == Material.AIR || type == Material.CAVE_AIR || type == Material.VOID_AIR; - } - // All versions prior to 1.14 only have 1 air type - return type == Material.AIR; - } - @Override public String toString(@Nullable Event e, boolean debug) { return "drop " + drops.toString(e, debug) + " " + locations.toString(e, debug); diff --git a/src/main/java/ch/njol/skript/events/EvtEntityBlockChange.java b/src/main/java/ch/njol/skript/events/EvtEntityBlockChange.java index 6ddcc7af27c..65d8192efb2 100644 --- a/src/main/java/ch/njol/skript/events/EvtEntityBlockChange.java +++ b/src/main/java/ch/njol/skript/events/EvtEntityBlockChange.java @@ -18,8 +18,12 @@ */ package ch.njol.skript.events; -import java.util.Locale; - +import ch.njol.skript.Skript; +import ch.njol.skript.bukkitutil.ItemUtils; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptEvent; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Checker; import org.bukkit.Material; import org.bukkit.entity.Enderman; import org.bukkit.entity.FallingBlock; @@ -29,17 +33,8 @@ import org.bukkit.event.entity.EntityChangeBlockEvent; import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.Skript; -import ch.njol.skript.aliases.Aliases; -import ch.njol.skript.aliases.ItemType; -import ch.njol.skript.lang.Literal; -import ch.njol.skript.lang.SkriptEvent; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.util.Checker; +import java.util.Locale; -/** - * @author Peter Güttinger - */ public class EvtEntityBlockChange extends SkriptEvent { static { @@ -55,83 +50,54 @@ public class EvtEntityBlockChange extends SkriptEvent { .since("<i>unknown</i>, 2.5.2 (falling block)"); } - static final ItemType monsterEgg = Aliases.javaItemType("any spawn egg"); - - private static enum ChangeEvent { - ENDERMAN_PLACE("enderman place", new Checker<EntityChangeBlockEvent>() { - @Override - public boolean check(final EntityChangeBlockEvent e) { - return e.getEntity() instanceof Enderman && e.getTo() != Material.AIR; - } - }), - ENDERMAN_PICKUP("enderman pickup", new Checker<EntityChangeBlockEvent>() { - @Override - public boolean check(final EntityChangeBlockEvent e) { - return e.getEntity() instanceof Enderman && e.getTo() == Material.AIR; - } - }), - SHEEP_EAT("sheep eat", new Checker<EntityChangeBlockEvent>() { - @Override - public boolean check(final EntityChangeBlockEvent e) { - return e.getEntity() instanceof Sheep; - } - }), - SILVERFISH_ENTER("silverfish enter", new Checker<EntityChangeBlockEvent>() { - @Override - public boolean check(final EntityChangeBlockEvent e) { - return e.getEntity() instanceof Silverfish && e.getTo() != monsterEgg.getMaterial(); - } - }), - SILVERFISH_EXIT("silverfish exit", new Checker<EntityChangeBlockEvent>() { - @Override - public boolean check(final EntityChangeBlockEvent e) { - return e.getEntity() instanceof Silverfish && e.getTo() != monsterEgg.getMaterial(); - } - }), - FALLING_BLOCK_LANDING("falling block land[ing]", new Checker<EntityChangeBlockEvent>() { - @Override - public boolean check(EntityChangeBlockEvent e) { - return e.getEntity() instanceof FallingBlock; - } - }); - + private enum ChangeEvent { + + ENDERMAN_PLACE("enderman place", e -> e.getEntity() instanceof Enderman && e.getTo() != Material.AIR), + ENDERMAN_PICKUP("enderman pickup", e -> e.getEntity() instanceof Enderman && e.getTo() == Material.AIR), + + SHEEP_EAT("sheep eat", e -> e.getEntity() instanceof Sheep), + + SILVERFISH_ENTER("silverfish enter", e -> e.getEntity() instanceof Silverfish && !ItemUtils.isAir(e.getTo())), + SILVERFISH_EXIT("silverfish exit", e -> e.getEntity() instanceof Silverfish && ItemUtils.isAir(e.getTo())), + + FALLING_BLOCK_LANDING("falling block land[ing]", e -> e.getEntity() instanceof FallingBlock); + private final String pattern; - final Checker<EntityChangeBlockEvent> checker; - - private ChangeEvent(final String pattern, final Checker<EntityChangeBlockEvent> c) { + private final Checker<EntityChangeBlockEvent> checker; + + ChangeEvent(String pattern, Checker<EntityChangeBlockEvent> c) { this.pattern = pattern; checker = c; } - - static String[] patterns; + + private static final String[] patterns; + static { patterns = new String[ChangeEvent.values().length]; - for (int i = 0; i < patterns.length; i++) { + for (int i = 0; i < patterns.length; i++) patterns[i] = values()[i].pattern; - } } } - @SuppressWarnings("null") + @SuppressWarnings("NotNullFieldNotInitialized") private ChangeEvent event; - - @SuppressWarnings("null") + @Override - public boolean init(final Literal<?>[] args, final int matchedPattern, final ParseResult parser) { + public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parser) { event = ChangeEvent.values()[matchedPattern]; return true; } @Override - public boolean check(final Event e) { + public boolean check(Event e) { if (!(e instanceof EntityChangeBlockEvent)) return false; return event.checker.check((EntityChangeBlockEvent) e); } @Override - public String toString(final @Nullable Event e, final boolean debug) { - return "" + event.name().toLowerCase(Locale.ENGLISH).replace('_', ' '); + public String toString(@Nullable Event e, boolean debug) { + return event.name().toLowerCase(Locale.ENGLISH).replace('_', ' '); } } From e74e3a1e8e10b669f9730aea4a808722e82cd62b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 Jul 2022 08:00:41 -0600 Subject: [PATCH 077/619] Bump paper-api from 1.19-R0.1-SNAPSHOT to 1.19.1-R0.1-SNAPSHOT (#4983) --- build.gradle | 4 ++-- gradle.properties | 2 +- .../java17/{paper-1.19.json => paper-1.19.1.json} | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) rename src/test/skript/environments/java17/{paper-1.19.json => paper-1.19.1.json} (85%) diff --git a/build.gradle b/build.gradle index 39506d9defb..ee1e5369a3b 100644 --- a/build.gradle +++ b/build.gradle @@ -24,7 +24,7 @@ allprojects { dependencies { shadow group: 'io.papermc', name: 'paperlib', version: '1.0.7' shadow group: 'org.bstats', name: 'bstats-bukkit', version: '3.0.0' - implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.19-R0.1-SNAPSHOT' + implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.19.1-R0.1-SNAPSHOT' implementation group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.600' implementation group: 'com.google.code.findbugs', name: 'findbugs', version: '3.0.1' implementation group: 'com.sk89q.worldguard', name: 'worldguard-legacy', version: '7.0.0-SNAPSHOT' @@ -186,7 +186,7 @@ void createTestTask(String name, String environments, boolean devMode, int javaV } } -def latestEnv = 'java17/paper-1.19.json' +def latestEnv = 'java17/paper-1.19.1.json' def latestJava = 17 def oldestJava = 8 diff --git a/gradle.properties b/gradle.properties index 903f8e9f0b9..039806b22cd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,5 +2,5 @@ groupid=ch.njol name=skript version=2.6.3 jarName=Skript.jar -testEnv=java17/paper-1.19 +testEnv=java17/paper-1.19.1 testEnvJavaVersion=17 diff --git a/src/test/skript/environments/java17/paper-1.19.json b/src/test/skript/environments/java17/paper-1.19.1.json similarity index 85% rename from src/test/skript/environments/java17/paper-1.19.json rename to src/test/skript/environments/java17/paper-1.19.1.json index 63525c93239..12c14e4f563 100644 --- a/src/test/skript/environments/java17/paper-1.19.json +++ b/src/test/skript/environments/java17/paper-1.19.1.json @@ -1,11 +1,11 @@ { - "name": "paper-1.19", + "name": "paper-1.19.1", "resources": [ {"source": "server.properties.generic", "target": "server.properties"} ], "paperDownloads": [ { - "version": "1.19", + "version": "1.19.1", "target": "paperclip.jar" } ], From 14d7e63993bd651ca88e6cd66b47a697b9792979 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Wed, 3 Aug 2022 22:45:31 -0600 Subject: [PATCH 078/619] Add flavour to Skript info command (#5003) * Add flavour to Skript info command --- src/main/java/ch/njol/skript/SkriptCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/SkriptCommand.java b/src/main/java/ch/njol/skript/SkriptCommand.java index 66c90b38a73..e63c5a5f2bf 100644 --- a/src/main/java/ch/njol/skript/SkriptCommand.java +++ b/src/main/java/ch/njol/skript/SkriptCommand.java @@ -328,7 +328,7 @@ public boolean onCommand(@Nullable CommandSender sender, @Nullable Command comma info(sender, "info.documentation"); info(sender, "info.tutorials"); info(sender, "info.server", Bukkit.getVersion()); - info(sender, "info.version", Skript.getVersion()); + info(sender, "info.version", Skript.getVersion() + " (" + Skript.getInstance().getUpdater().getCurrentRelease().flavor + ")"); info(sender, "info.addons", Skript.getAddons().isEmpty() ? "None" : ""); for (SkriptAddon addon : Skript.getAddons()) { PluginDescriptionFile desc = addon.plugin.getDescription(); From 1600c7d13ea7b8f4568d117ed053b298b07af257 Mon Sep 17 00:00:00 2001 From: APickledWalrus <apickledwalrus@gmail.com> Date: Fri, 5 Aug 2022 08:48:28 -0400 Subject: [PATCH 079/619] Implement missing methods for Block implementations (#5009) --- src/main/java/ch/njol/skript/util/BlockStateBlock.java | 10 ++++++++++ .../java/ch/njol/skript/util/DelayedChangeBlock.java | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/main/java/ch/njol/skript/util/BlockStateBlock.java b/src/main/java/ch/njol/skript/util/BlockStateBlock.java index dc787ee2e33..91d37ef43a0 100644 --- a/src/main/java/ch/njol/skript/util/BlockStateBlock.java +++ b/src/main/java/ch/njol/skript/util/BlockStateBlock.java @@ -356,6 +356,16 @@ public void run() { } } + @Override + public void tick() { + state.getBlock().tick(); + } + + @Override + public void randomTick() { + state.getBlock().randomTick(); + } + @Override public boolean applyBoneMeal(BlockFace blockFace) { return state.getBlock().applyBoneMeal(blockFace); diff --git a/src/main/java/ch/njol/skript/util/DelayedChangeBlock.java b/src/main/java/ch/njol/skript/util/DelayedChangeBlock.java index 58aa7746075..72dba53423a 100644 --- a/src/main/java/ch/njol/skript/util/DelayedChangeBlock.java +++ b/src/main/java/ch/njol/skript/util/DelayedChangeBlock.java @@ -357,6 +357,16 @@ public void run() { } } + @Override + public void tick() { + b.tick(); + } + + @Override + public void randomTick() { + b.randomTick(); + } + @Override public boolean applyBoneMeal(BlockFace blockFace) { return b.applyBoneMeal(blockFace); From c0b8e950e33536c57f182d00b887ce35a86ecda9 Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Sat, 6 Aug 2022 05:07:57 +0200 Subject: [PATCH 080/619] Fix Actions not running on master branch (#5011) Change main to master in workflow YML files --- .github/workflows/java-17-builds.yml | 2 +- .github/workflows/java-8-builds.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/java-17-builds.yml b/.github/workflows/java-17-builds.yml index ba936b40b30..8e435547f11 100644 --- a/.github/workflows/java-17-builds.yml +++ b/.github/workflows/java-17-builds.yml @@ -3,7 +3,7 @@ name: Java 17 CI (MC 1.17+) on: push: branches: - - main + - master - 'dev/**' pull_request: diff --git a/.github/workflows/java-8-builds.yml b/.github/workflows/java-8-builds.yml index 57afecccb02..6257a3ebccd 100644 --- a/.github/workflows/java-8-builds.yml +++ b/.github/workflows/java-8-builds.yml @@ -3,7 +3,7 @@ name: Java 8 CI (MC 1.13-1.16) on: push: branches: - - main + - master - 'dev/**' pull_request: From 89f79266dfc2adf1ac6dc06b37d90e5e4618c63d Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <alali_ayham@yahoo.com> Date: Sat, 6 Aug 2022 07:24:05 +0300 Subject: [PATCH 081/619] =?UTF-8?q?=E2=9A=92=EF=B8=8F=20Enhance=20and=20Fi?= =?UTF-8?q?x=20ExprAge=20(#4854)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Enhance and fix ExprAge --- .../ch/njol/skript/expressions/ExprAge.java | 157 ++++++++++++++++++ .../njol/skript/expressions/ExprBlockAge.java | 106 ------------ 2 files changed, 157 insertions(+), 106 deletions(-) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprAge.java delete mode 100644 src/main/java/ch/njol/skript/expressions/ExprBlockAge.java diff --git a/src/main/java/ch/njol/skript/expressions/ExprAge.java b/src/main/java/ch/njol/skript/expressions/ExprAge.java new file mode 100644 index 00000000000..50b77a6c8fb --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprAge.java @@ -0,0 +1,157 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.block.Block; +import org.bukkit.block.data.Ageable; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.Entity; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Age of Block/Entity") +@Description({ + "Returns the age or maximum age of blocks and age for entities (there in no maximum age for entities).", + "For blocks, 'Age' represents the different growth stages that a crop-like block can go through. " + + "A value of 0 indicates that the crop was freshly planted, whilst a value equal to 'maximum age' indicates that the crop is ripe and ready to be harvested.", + "For entities, 'Age' represents the time left for them to become adults and it's in minus increasing to be 0 which means they're adults, " + + "e.g. A baby cow needs 20 minutes to become an adult which equals to 24,000 ticks so their age will be -24000 once spawned." +}) +@Examples({ + "# Set targeted crop to fully grown crop", + "set age of targeted block to maximum age of targeted block", + " ", + "# Spawn a baby cow that will only need 1 minute to become an adult", + "spawn a baby cow at player", + "set age of last spawned entity to -1200 # in ticks = 60 seconds" +}) +@Since("INSERT VERSION") +public class ExprAge extends SimplePropertyExpression<Object, Integer> { + + static { + register(ExprAge.class, Integer.class, "[:max[imum]] age", "blocks/entities"); + } + + private boolean isMax; + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + isMax = parseResult.hasTag("max"); + setExpr(exprs[0]); + if (isMax && Entity.class.isAssignableFrom(getExpr().getReturnType())) { + Skript.error("Cannot use 'max age' expression with entities, use just the 'age' expression instead"); + return false; + } + + return true; + } + + @Override + @Nullable + public Integer convert(Object obj) { + if (obj instanceof Block) { + BlockData bd = ((Block) obj).getBlockData(); + if (!(bd instanceof Ageable)) + return null; + Ageable ageable = (Ageable) bd; + return isMax ? ageable.getMaximumAge() : ageable.getAge(); + } else if (obj instanceof org.bukkit.entity.Ageable) { + return isMax ? null : ((org.bukkit.entity.Ageable) obj).getAge(); + } + return null; + } + + @Override + @Nullable + public Class<?>[] acceptChange(ChangeMode mode) { + if (isMax || mode == ChangeMode.REMOVE_ALL || mode == ChangeMode.DELETE) + return null; + return CollectionUtils.array(Number.class); + } + + @Override + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + if (mode != ChangeMode.RESET && delta == null) + return; + + int newValue = mode != ChangeMode.RESET ? ((Number) delta[0]).intValue() : 0; + + for (Object obj : getExpr().getArray(event)) { + Number oldValue = convert(obj); + if (oldValue == null && mode != ChangeMode.RESET) + continue; + + switch (mode) { + case REMOVE: + setAge(obj, oldValue.intValue() - newValue); + break; + case ADD: + setAge(obj, oldValue.intValue() + newValue); + break; + case SET: + setAge(obj, newValue); + break; + case RESET: + // baby animals takes 20 minutes to grow up - ref: https://minecraft.fandom.com/wiki/Breeding + if (obj instanceof org.bukkit.entity.Ageable) + // it might change later on so removing entity age reset would be better unless + // bukkit adds a method returning the default age + newValue = -24000; + setAge(obj, newValue); + break; + } + } + } + + @Override + public Class<? extends Integer> getReturnType() { + return Integer.class; + } + + @Override + protected String getPropertyName() { + return (isMax ? "maximum " : "") + "age"; + } + + private static void setAge(Object obj, int value) { + if (obj instanceof Block) { + Block block = (Block) obj; + BlockData bd = block.getBlockData(); + if (bd instanceof Ageable) { + ((Ageable) bd).setAge(Math.max(Math.min(value, ((Ageable) bd).getMaximumAge()), 0)); + block.setBlockData(bd); + } + } else if (obj instanceof org.bukkit.entity.Ageable) { + // Bukkit accepts higher values than 0, they will keep going down to 0 though (some Animals type might be using that - not sure) + ((org.bukkit.entity.Ageable) obj).setAge(value); + } + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprBlockAge.java b/src/main/java/ch/njol/skript/expressions/ExprBlockAge.java deleted file mode 100644 index 749c1324229..00000000000 --- a/src/main/java/ch/njol/skript/expressions/ExprBlockAge.java +++ /dev/null @@ -1,106 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.expressions; - -import ch.njol.skript.Skript; -import ch.njol.skript.classes.Changer.ChangeMode; -import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Examples; -import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.RequiredPlugins; -import ch.njol.skript.doc.Since; -import ch.njol.skript.expressions.base.SimplePropertyExpression; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.util.Kleenean; -import ch.njol.util.coll.CollectionUtils; -import org.bukkit.block.Block; -import org.bukkit.block.data.Ageable; -import org.bukkit.block.data.BlockData; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - -@Name("Block Age") -@Description({"Returns the age or max age of a block. 'Age' represents the different growth stages that a crop-like block can go through.", - "A value of 0 indicates that the crop was freshly planted, whilst a value equal to 'maximum age' indicates that the crop is ripe and ready to be harvested."}) -@Examples("set age of targeted block to max age of targeted block") -@RequiredPlugins("Minecraft 1.13+") -@Since("INSERT VERSION") -public class ExprBlockAge extends SimplePropertyExpression<Block, Integer> { - - static { - if (Skript.classExists("org.bukkit.block.data.Ageable")) - register(ExprBlockAge.class, Integer.class, "[:max[imum]] age", "block"); - } - - private boolean isMax = false; - - @Override - @SuppressWarnings("unchecked") - public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - isMax = parseResult.hasTag("max"); - setExpr((Expression<Block>) exprs[0]); - return true; - } - - @Override - @Nullable - public Integer convert(Block block) { - BlockData bd = block.getBlockData(); - if (bd instanceof Ageable) { - if (isMax) - return ((Ageable) bd).getMaximumAge(); - else - return ((Ageable) bd).getAge(); - } - return null; - } - - @Override - @Nullable - public Class<?>[] acceptChange(ChangeMode mode) { - return !isMax && (mode == ChangeMode.SET || mode == ChangeMode.RESET) ? CollectionUtils.array(Number.class) : null; - } - - @Override - public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { - if (mode == ChangeMode.SET && (delta == null || delta[0] == null)) - return; - - int value = mode == ChangeMode.RESET ? 0 : ((Number) delta[0]).intValue(); - for (Block block : getExpr().getArray(event)) { - BlockData bd = block.getBlockData(); - if (bd instanceof Ageable) { - ((Ageable) bd).setAge(value); - } - block.setBlockData(bd); - } - } - - @Override - public Class<? extends Integer> getReturnType() { - return Integer.class; - } - - @Override - protected String getPropertyName() { - return (isMax ? "max " : "") + "age"; - } - -} From 5c37a3060133a363f6c90000487e5313e4172b5e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Aug 2022 04:03:45 -0600 Subject: [PATCH 082/619] Bump paper-api from 1.19.1-R0.1-SNAPSHOT to 1.19.2-R0.1-SNAPSHOT (#5018) * Bump paper-api from 1.19.1-R0.1-SNAPSHOT to 1.19.2-R0.1-SNAPSHOT --- build.gradle | 4 ++-- gradle.properties | 2 +- .../java17/{paper-1.19.1.json => paper-1.19.2.json} | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) rename src/test/skript/environments/java17/{paper-1.19.1.json => paper-1.19.2.json} (85%) diff --git a/build.gradle b/build.gradle index ee1e5369a3b..85367911651 100644 --- a/build.gradle +++ b/build.gradle @@ -24,7 +24,7 @@ allprojects { dependencies { shadow group: 'io.papermc', name: 'paperlib', version: '1.0.7' shadow group: 'org.bstats', name: 'bstats-bukkit', version: '3.0.0' - implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.19.1-R0.1-SNAPSHOT' + implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.19.2-R0.1-SNAPSHOT' implementation group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.600' implementation group: 'com.google.code.findbugs', name: 'findbugs', version: '3.0.1' implementation group: 'com.sk89q.worldguard', name: 'worldguard-legacy', version: '7.0.0-SNAPSHOT' @@ -186,7 +186,7 @@ void createTestTask(String name, String environments, boolean devMode, int javaV } } -def latestEnv = 'java17/paper-1.19.1.json' +def latestEnv = 'java17/paper-1.19.2.json' def latestJava = 17 def oldestJava = 8 diff --git a/gradle.properties b/gradle.properties index 039806b22cd..0d7e5940675 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,5 +2,5 @@ groupid=ch.njol name=skript version=2.6.3 jarName=Skript.jar -testEnv=java17/paper-1.19.1 +testEnv=java17/paper-1.19.2 testEnvJavaVersion=17 diff --git a/src/test/skript/environments/java17/paper-1.19.1.json b/src/test/skript/environments/java17/paper-1.19.2.json similarity index 85% rename from src/test/skript/environments/java17/paper-1.19.1.json rename to src/test/skript/environments/java17/paper-1.19.2.json index 12c14e4f563..d3502b29da9 100644 --- a/src/test/skript/environments/java17/paper-1.19.1.json +++ b/src/test/skript/environments/java17/paper-1.19.2.json @@ -1,11 +1,11 @@ { - "name": "paper-1.19.1", + "name": "paper-1.19.2", "resources": [ {"source": "server.properties.generic", "target": "server.properties"} ], "paperDownloads": [ { - "version": "1.19.1", + "version": "1.19.2", "target": "paperclip.jar" } ], From adb687d0e47d6fde82360956c42ff98b3e32572d Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Tue, 16 Aug 2022 00:49:18 +0200 Subject: [PATCH 083/619] Fix ExprFunctionCall conversion (#4986) * Change default return type and implement conversion --- .../njol/skript/expressions/ExprVelocity.java | 3 +- .../lang/function/ExprFunctionCall.java | 33 +++++++++++++++++-- .../4524-function call conversion.sk | 2 ++ 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprVelocity.java b/src/main/java/ch/njol/skript/expressions/ExprVelocity.java index d776264bcd7..1745acabbe3 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVelocity.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVelocity.java @@ -51,6 +51,7 @@ public Vector convert(Entity e) { } @Override + @Nullable @SuppressWarnings("null") public Class<?>[] acceptChange(ChangeMode mode) { if ((mode == ChangeMode.ADD || mode == ChangeMode.REMOVE || mode == ChangeMode.SET || mode == ChangeMode.DELETE || mode == ChangeMode.RESET)) @@ -61,7 +62,7 @@ public Class<?>[] acceptChange(ChangeMode mode) { @Override @SuppressWarnings("null") public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { - assert delta != null; + assert mode == ChangeMode.DELETE || mode == ChangeMode.RESET || delta != null; for (final Entity entity : getExpr().getArray(e)) { if (entity == null) return; diff --git a/src/main/java/ch/njol/skript/lang/function/ExprFunctionCall.java b/src/main/java/ch/njol/skript/lang/function/ExprFunctionCall.java index a5135282277..e585c3521e3 100644 --- a/src/main/java/ch/njol/skript/lang/function/ExprFunctionCall.java +++ b/src/main/java/ch/njol/skript/lang/function/ExprFunctionCall.java @@ -24,6 +24,7 @@ import ch.njol.skript.registrations.Converters; import ch.njol.skript.util.Utils; import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @@ -33,11 +34,24 @@ public class ExprFunctionCall<T> extends SimpleExpression<T> { private final Class<? extends T>[] returnTypes; private final Class<T> returnType; - @SuppressWarnings("unchecked") public ExprFunctionCall(FunctionReference<T> function) { + this(function, function.returnTypes); + } + + @SuppressWarnings("unchecked") + public ExprFunctionCall(FunctionReference<?> function, Class<? extends T>[] expectedReturnTypes) { this.function = function; - this.returnTypes = function.returnTypes; - this.returnType = (Class<T>) Utils.getSuperType(returnTypes); + Class<?> functionReturnType = function.getReturnType(); + assert functionReturnType != null; + if (CollectionUtils.containsSuperclass(expectedReturnTypes, functionReturnType)) { + // Function returns expected type already + this.returnTypes = new Class[] {functionReturnType}; + this.returnType = (Class<T>) functionReturnType; + } else { + // Return value needs to be converted + this.returnTypes = expectedReturnTypes; + this.returnType = (Class<T>) Utils.getSuperType(expectedReturnTypes); + } } @Override @@ -48,6 +62,19 @@ protected T[] get(Event e) { return Converters.convertArray(returnValue, returnTypes, returnType); } + @Override + @Nullable + @SuppressWarnings("unchecked") + public <R> Expression<? extends R> getConvertedExpression(Class<R>... to) { + if (CollectionUtils.containsSuperclass(to, getReturnType())) + return (Expression<? extends R>) this; + assert function.getReturnType() != null; + if (Converters.converterExists(function.getReturnType(), to)) { + return new ExprFunctionCall<>(function, to); + } + return null; + } + @Override public boolean isSingle() { return function.isSingle(); diff --git a/src/test/skript/tests/regressions/4524-function call conversion.sk b/src/test/skript/tests/regressions/4524-function call conversion.sk index f9b812147ce..95e1f8649cb 100644 --- a/src/test/skript/tests/regressions/4524-function call conversion.sk +++ b/src/test/skript/tests/regressions/4524-function call conversion.sk @@ -3,3 +3,5 @@ function functionCallConversion() :: item: test "function call conversion": assert name of functionCallConversion() is "abc" with "Name of item from function call failed" + + set {_p}'s velocity to vector({_x}, {_y}, {_z}) From c311c8ea6d1fd6a574305a0698631f1b942b6f33 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <alali_ayham@yahoo.com> Date: Tue, 16 Aug 2022 01:55:51 +0300 Subject: [PATCH 084/619] =?UTF-8?q?=F0=9F=9B=A0=20Fix=20PrepareCraft=20eve?= =?UTF-8?q?nt=20NPE=20(#4972)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix PrepareCraft NPE --- src/main/java/ch/njol/skript/events/EvtItem.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/events/EvtItem.java b/src/main/java/ch/njol/skript/events/EvtItem.java index 5e82cfb297c..6524af3f2c7 100644 --- a/src/main/java/ch/njol/skript/events/EvtItem.java +++ b/src/main/java/ch/njol/skript/events/EvtItem.java @@ -32,6 +32,7 @@ import org.bukkit.event.player.PlayerItemConsumeEvent; import org.bukkit.event.player.PlayerPickupItemEvent; import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.Recipe; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; @@ -151,7 +152,13 @@ public boolean check(final Event e) { } else if (e instanceof CraftItemEvent) { is = ((CraftItemEvent) e).getRecipe().getResult(); } else if (hasPrepareCraftEvent && e instanceof PrepareItemCraftEvent) { - is = ((PrepareItemCraftEvent) e).getRecipe().getResult(); + PrepareItemCraftEvent event = (PrepareItemCraftEvent) e; + Recipe recipe = event.getRecipe(); + if (recipe != null) { + is = recipe.getResult(); + } else { + return false; + } } else if (e instanceof EntityPickupItemEvent) { is = ((EntityPickupItemEvent) e).getItem().getItemStack(); } else if (e instanceof PlayerPickupItemEvent) { @@ -170,6 +177,10 @@ public boolean check(final Event e) { assert false; return false; } + + if (is == null) + return false; + return types.check(e, new Checker<ItemType>() { @Override public boolean check(final ItemType t) { From 8dc04919b07ee9a7fd4a1c0afca148edd030dcf6 Mon Sep 17 00:00:00 2001 From: Jay <72931234+Ankoki@users.noreply.github.com> Date: Wed, 17 Aug 2022 01:10:54 +0100 Subject: [PATCH 085/619] Add potion effect tier (#4632) * add potion effect tier --- .../expressions/ExprPotionEffectTier.java | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprPotionEffectTier.java diff --git a/src/main/java/ch/njol/skript/expressions/ExprPotionEffectTier.java b/src/main/java/ch/njol/skript/expressions/ExprPotionEffectTier.java new file mode 100644 index 00000000000..2fdee7bd42b --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprPotionEffectTier.java @@ -0,0 +1,93 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.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 org.bukkit.entity.LivingEntity; +import org.bukkit.event.Event; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.ArrayList; +import java.util.List; + +@Name("Potion Effect Tier") +@Description("An expression to obtain the amplifier of a potion effect applied to an entity.") +@Examples("if the amplifier of haste of player >= 3:") +@Since("INSERT VERSION") +public class ExprPotionEffectTier extends SimpleExpression<Integer> { + + static { + Skript.registerExpression(ExprPotionEffectTier.class, Integer.class, ExpressionType.COMBINED, + "[the] [potion] (tier|amplifier|level) of %potioneffecttypes% (of|for|on) %livingentities%" + ); + } + + private Expression<PotionEffectType> typeExpr; + private Expression<LivingEntity> entityExpr; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + typeExpr = (Expression<PotionEffectType>) exprs[0]; + entityExpr = (Expression<LivingEntity>) exprs[1]; + return true; + } + + @Override + @Nullable + protected Integer[] get(Event event) { + PotionEffectType[] types = typeExpr.getArray(event); + LivingEntity[] entities = entityExpr.getArray(event); + List<Integer> result = new ArrayList<>(); + for (LivingEntity entity : entities) { + for (PotionEffectType type : types) { + PotionEffect effect = entity.getPotionEffect(type); + result.add(effect == null ? 0 : effect.getAmplifier() + 1); + } + } + return result.toArray(new Integer[0]); + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class<? extends Integer> getReturnType() { + return Integer.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "potion tier of " + typeExpr.toString(event, debug) + " of " + entityExpr.toString(event, debug); + } + +} From 93f03a89806f82b5e96bc4e3ac2bb682a8e0b3bf Mon Sep 17 00:00:00 2001 From: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> Date: Wed, 24 Aug 2022 09:49:32 -0500 Subject: [PATCH 086/619] Add providing plugin to pattern compilation exception (#5039) --- src/main/java/ch/njol/skript/lang/SkriptParser.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/lang/SkriptParser.java b/src/main/java/ch/njol/skript/lang/SkriptParser.java index c8efa14e459..e7580b8d573 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptParser.java +++ b/src/main/java/ch/njol/skript/lang/SkriptParser.java @@ -53,6 +53,7 @@ import com.google.common.primitives.Booleans; import org.bukkit.event.EventPriority; import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.java.JavaPlugin; import org.eclipse.jdt.annotation.Nullable; import java.util.ArrayList; @@ -223,7 +224,13 @@ private <T extends SyntaxElement> T parse(Iterator<? extends SyntaxElementInfo<? try { res = parse_i(pattern, 0, 0); } catch (MalformedPatternException e) { - throw new RuntimeException("pattern compiling exception, element class: " + info.c.getName(), e); + String message = "pattern compiling exception, element class: " + info.c.getName(); + try { + JavaPlugin providingPlugin = JavaPlugin.getProvidingPlugin(info.c); + message += " (provided by " + providingPlugin.getName() + ")"; + } catch (IllegalArgumentException | IllegalStateException ignored) {} + throw new RuntimeException(message, e); + } if (res != null) { int x = -1; From 852d4e4b7803dcf724a7b4ef7c3f8ab1b766f9c4 Mon Sep 17 00:00:00 2001 From: APickledWalrus <apickledwalrus@gmail.com> Date: Wed, 24 Aug 2022 13:57:13 -0400 Subject: [PATCH 087/619] Add testing community section to README (#5044) --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 8d335854a8d..3e5049eba42 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,9 @@ latest version of Skript. Please see our [contribution guidelines](https://github.com/SkriptLang/Skript/blob/master/.github/contributing.md) before reporting issues. +## Help Us Test +We have an [official Discord community](https://discord.gg/ZPsZAg6ygu) for testing Skript's new features and releases. + ## A Note About Add-ons We don't support add-ons here, even though some of Skript developers have also developed their own add-ons. From 224cd6281ac9546d116f56a8c92f054a015c379f Mon Sep 17 00:00:00 2001 From: APickledWalrus <apickledwalrus@gmail.com> Date: Wed, 31 Aug 2022 16:49:12 -0400 Subject: [PATCH 088/619] Egg API ! (#4970) * Egg API ! --- .../classes/data/BukkitEventValues.java | 11 ++ .../njol/skript/conditions/CondWillHatch.java | 74 +++++++++++ .../ch/njol/skript/events/SimpleEvents.java | 8 +- .../skript/expressions/EffMakeEggHatch.java | 79 ++++++++++++ .../ch/njol/skript/expressions/ExprEgg.java | 53 ++++++++ .../expressions/ExprHatchingNumber.java | 115 ++++++++++++++++++ .../skript/expressions/ExprHatchingType.java | 110 +++++++++++++++++ 7 files changed, 447 insertions(+), 3 deletions(-) create mode 100644 src/main/java/ch/njol/skript/conditions/CondWillHatch.java create mode 100644 src/main/java/ch/njol/skript/expressions/EffMakeEggHatch.java create mode 100644 src/main/java/ch/njol/skript/expressions/ExprEgg.java create mode 100644 src/main/java/ch/njol/skript/expressions/ExprHatchingNumber.java create mode 100644 src/main/java/ch/njol/skript/expressions/ExprHatchingType.java 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 5930abd4400..b7d339051e5 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -49,6 +49,7 @@ import org.bukkit.block.BlockFace; import org.bukkit.block.BlockState; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Egg; import org.bukkit.entity.Entity; import org.bukkit.entity.Firework; import org.bukkit.entity.Hanging; @@ -111,6 +112,7 @@ import org.bukkit.event.player.PlayerChangedWorldEvent; import org.bukkit.event.player.PlayerDropItemEvent; import org.bukkit.event.player.PlayerEditBookEvent; +import org.bukkit.event.player.PlayerEggThrowEvent; import org.bukkit.event.player.PlayerEvent; import org.bukkit.event.player.PlayerInteractEntityEvent; import org.bukkit.event.player.PlayerInteractEvent; @@ -1374,5 +1376,14 @@ public World get(PlayerChangedWorldEvent e) { return e.getFrom(); } }, -1); + + // PlayerEggThrowEvent + EventValues.registerEventValue(PlayerEggThrowEvent.class, Egg.class, new Getter<Egg, PlayerEggThrowEvent>() { + @Override + @Nullable + public Egg get(PlayerEggThrowEvent event) { + return event.getEgg(); + } + }, EventValues.TIME_NOW); } } diff --git a/src/main/java/ch/njol/skript/conditions/CondWillHatch.java b/src/main/java/ch/njol/skript/conditions/CondWillHatch.java new file mode 100644 index 00000000000..5379ed22b81 --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondWillHatch.java @@ -0,0 +1,74 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.conditions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Events; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Condition; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.bukkit.event.player.PlayerEggThrowEvent; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Egg Will Hatch") +@Description("Whether the egg will hatch in a Player Egg Throw event.") +@Examples({ + "on player egg throw:", + "\tif an entity won't hatch:", + "\t\tsend \"Better luck next time!\" to the player" +}) +@Events("Egg Throw") +@Since("INSERT VERSION") +public class CondWillHatch extends Condition { + + static { + Skript.registerCondition(CondWillHatch.class, + "[the] egg (:will|will not|won't) hatch" + ); + } + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(PlayerEggThrowEvent.class)) { + Skript.error("You can't use the 'egg will hatch' condition outside of a Player Egg Throw event."); + return false; + } + setNegated(!parseResult.hasTag("will")); + return true; + } + + @Override + public boolean check(Event event) { + if (!(event instanceof PlayerEggThrowEvent)) + return false; + return ((PlayerEggThrowEvent) event).isHatching() ^ isNegated(); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the egg " + (isNegated() ? "will" : "will not") + " hatch"; + } + +} diff --git a/src/main/java/ch/njol/skript/events/SimpleEvents.java b/src/main/java/ch/njol/skript/events/SimpleEvents.java index a22bde93186..d1b193ac5b0 100644 --- a/src/main/java/ch/njol/skript/events/SimpleEvents.java +++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java @@ -256,9 +256,11 @@ public class SimpleEvents { .description("Called when a player fills a bucket.") .examples("on player filling a bucket:") .since("1.0"); - Skript.registerEvent("Throwing of an Egg", SimpleEvent.class, PlayerEggThrowEvent.class, "throw[ing] [of] [an] egg", "[player] egg throw") - .description("Called when a player throws an egg. You can just use the <a href='#shoot'>shoot event</a> in most cases, " + - "as this event is intended to support changing the hatched mob and its chance to hatch, but Skript does not yet support that.") + Skript.registerEvent("Egg Throw", SimpleEvent.class, PlayerEggThrowEvent.class, "throw[ing] [of] [an] egg", "[player] egg throw") + .description( + "Called when a player throws an egg and it lands. You can just use the <a href='#shoot'>shoot event</a> in most cases." + + " However, this event allows modification of properties like the hatched entity type and the number of entities to hatch." + ) .examples("on throw of an egg:") .since("1.0"); // TODO improve - on fish [of %entitydata%] (and/or itemtype), on reel, etc. diff --git a/src/main/java/ch/njol/skript/expressions/EffMakeEggHatch.java b/src/main/java/ch/njol/skript/expressions/EffMakeEggHatch.java new file mode 100644 index 00000000000..796b54d92f6 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/EffMakeEggHatch.java @@ -0,0 +1,79 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Events; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.bukkit.event.player.PlayerEggThrowEvent; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Make Egg Hatch") +@Description("Makes the egg hatch in a Player Egg Throw event.") +@Examples({ + "on player egg throw:", + "\t# EGGS FOR DAYZ!", + "\tmake the egg hatch" +}) +@Events("Egg Throw") +@Since("INSERT VERSION") +public class EffMakeEggHatch extends Effect { + + static { + Skript.registerEffect(EffMakeEggHatch.class, + "make [the] egg [:not] hatch" + ); + } + + private boolean not; + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(PlayerEggThrowEvent.class)) { + Skript.error("You can't use the 'make the egg hatch' effect outside of a Player Egg Throw event."); + return false; + } + not = parseResult.hasTag("not"); + return true; + } + + @Override + protected void execute(Event e) { + if (e instanceof PlayerEggThrowEvent) { + PlayerEggThrowEvent event = (PlayerEggThrowEvent) e; + event.setHatching(!not); + if (!not && event.getNumHatches() == 0) // Make it hatch something! + event.setNumHatches((byte) 1); + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "make the egg " + (not ? "not " : "") + "hatch"; + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprEgg.java b/src/main/java/ch/njol/skript/expressions/ExprEgg.java new file mode 100644 index 00000000000..20e6ba0907f --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprEgg.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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Events; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.EventValueExpression; +import ch.njol.skript.lang.ExpressionType; +import org.bukkit.entity.Egg; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("The Egg") +@Description("The egg thrown in a Player Egg Throw event.") +@Examples("spawn an egg at the egg") +@Events("Egg Throw") +@Since("INSERT VERSION") +public class ExprEgg extends EventValueExpression<Egg> { + + static { + Skript.registerExpression(ExprEgg.class, Egg.class, ExpressionType.SIMPLE, "[the] [thrown] egg"); + } + + public ExprEgg() { + super(Egg.class); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the egg"; + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprHatchingNumber.java b/src/main/java/ch/njol/skript/expressions/ExprHatchingNumber.java new file mode 100644 index 00000000000..709a3377ce8 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprHatchingNumber.java @@ -0,0 +1,115 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Events; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.bukkit.event.player.PlayerEggThrowEvent; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Hatching Number") +@Description({ + "The number of entities that will be hatched in a Player Egg Throw event.", + "Please note that no more than 127 entities can be hatched at once." +}) +@Examples({ + "on player egg throw:", + "\tset the hatching number to 10" +}) +@Events("Egg Throw") +@Since("INSERT VERSION") +public class ExprHatchingNumber extends SimpleExpression<Byte> { + + static { + Skript.registerExpression(ExprHatchingNumber.class, Byte.class, ExpressionType.SIMPLE, + "[the] hatching number" + ); + } + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(PlayerEggThrowEvent.class)) { + Skript.error("You can't use 'the hatching number' outside of a Player Egg Throw event."); + return false; + } + return true; + } + + @Override + @Nullable + protected Byte[] get(Event event) { + if (!(event instanceof PlayerEggThrowEvent)) + return new Byte[0]; + return new Byte[]{((PlayerEggThrowEvent) event).getNumHatches()}; + } + + @Override + @Nullable + public Class<?>[] acceptChange(ChangeMode mode) { + if (mode == ChangeMode.SET || mode == ChangeMode.ADD || mode == ChangeMode.REMOVE) + return CollectionUtils.array(Number.class); + return null; + } + + @Override + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + //noinspection ConstantConditions + if (!(event instanceof PlayerEggThrowEvent) || delta == null) + return; + + assert delta[0] != null; + + int value = ((Number) delta[0]).intValue(); + if (mode != ChangeMode.SET) { + if (mode == ChangeMode.REMOVE) + value *= -1; + value = ((int) ((PlayerEggThrowEvent) event).getNumHatches()) + value; + } + + ((PlayerEggThrowEvent) event).setNumHatches((byte) Math.min(Math.max(0, value), Byte.MAX_VALUE)); + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class<? extends Byte> getReturnType() { + return Byte.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the hatching number"; + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprHatchingType.java b/src/main/java/ch/njol/skript/expressions/ExprHatchingType.java new file mode 100644 index 00000000000..b6e6edba2b8 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprHatchingType.java @@ -0,0 +1,110 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * 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 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.Since; +import ch.njol.skript.entity.EntityData; +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 org.bukkit.entity.EntityType; +import org.bukkit.event.Event; +import org.bukkit.event.player.PlayerEggThrowEvent; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Hatching Entity Type") +@Description("The type of the entity that will be hatched in a Player Egg Throw event.") +@Examples({ + "on player egg throw:", + "\tset the hatching entity type to a primed tnt" +}) +@Events("Egg Throw") +@Since("INSERT VERSION") +public class ExprHatchingType extends SimpleExpression<EntityData<?>> { + + static { + //noinspection unchecked + Skript.registerExpression(ExprHatchingType.class, (Class<EntityData<?>>) (Class<?>) EntityData.class, ExpressionType.SIMPLE, + "[the] hatching entity [type]" + ); + } + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(PlayerEggThrowEvent.class)) { + Skript.error("You can't use 'the hatching entity type' outside of a Player Egg Throw event."); + return false; + } + return true; + } + + @Override + @Nullable + protected EntityData<?>[] get(Event event) { + if (!(event instanceof PlayerEggThrowEvent)) + return new EntityData[0]; + return new EntityData[]{EntityUtils.toSkriptEntityData(((PlayerEggThrowEvent) event).getHatchingType())}; + } + + @Override + @Nullable + public Class<?>[] acceptChange(ChangeMode mode) { + if (mode == ChangeMode.SET || mode == ChangeMode.RESET) + return CollectionUtils.array(EntityData.class); + return null; + } + + @Override + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + if (!(event instanceof PlayerEggThrowEvent)) + return; + //noinspection ConstantConditions + EntityType entityType = delta != null ? EntityUtils.toBukkitEntityType((EntityData<?>) delta[0]) : EntityType.CHICKEN; + if (!entityType.isSpawnable()) + return; + ((PlayerEggThrowEvent) event).setHatchingType(entityType); + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class<? extends EntityData<?>> getReturnType() { + //noinspection unchecked + return (Class<EntityData<?>>) (Class<?>) EntityData.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the hatching entity type"; + } + +} From f1e7db5dee38717c1968510c19d4fac20d6b8abb Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <alali_ayham@yahoo.com> Date: Wed, 14 Sep 2022 01:31:23 +0300 Subject: [PATCH 089/619] =?UTF-8?q?=E2=9A=92=EF=B8=8F=20Improve=20ExprBala?= =?UTF-8?q?nce=20(#4640)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ⚒️ Improve ExprBalance --- .../economy/expressions/ExprBalance.java | 64 ++++++++----------- 1 file changed, 28 insertions(+), 36 deletions(-) diff --git a/src/main/java/ch/njol/skript/hooks/economy/expressions/ExprBalance.java b/src/main/java/ch/njol/skript/hooks/economy/expressions/ExprBalance.java index a10ccd8286a..e23f4c1334f 100644 --- a/src/main/java/ch/njol/skript/hooks/economy/expressions/ExprBalance.java +++ b/src/main/java/ch/njol/skript/hooks/economy/expressions/ExprBalance.java @@ -32,28 +32,27 @@ import ch.njol.skript.hooks.VaultHook; import ch.njol.skript.hooks.economy.classes.Money; -/** - * @author Peter Güttinger - */ @Name("Money") -@Description("How much virtual money a player has (can be changed). This expression requires Vault and a compatible economy plugin to be installed.") -@Examples({"message \"You have %player's money%\" # the currency name will be added automatically", - "remove 20$ from the player's balance # replace '$' by whatever currency you use", - "add 200 to the player's account # or omit the currency alltogether"}) -@Since("2.0, 2.5 (offline player support)") -@RequiredPlugins({"Vault", "a permission plugin that supports Vault"}) +@Description("How much virtual money a player has (can be changed).") +@Examples({ + "message \"You have %player's money%\" # the currency name will be added automatically", + "remove 20$ from the player's balance # replace '$' by whatever currency you use", + "add 200 to the player's account # or omit the currency altogether" +}) +@Since("2.0, 2.5 (offline players)") +@RequiredPlugins({"Vault", "an economy plugin that supports Vault"}) public class ExprBalance extends SimplePropertyExpression<OfflinePlayer, Money> { + static { register(ExprBalance.class, Money.class, "(money|balance|[bank] account)", "offlineplayers"); } - @SuppressWarnings("deprecation") @Override - public Money convert(final OfflinePlayer p) { + public Money convert(OfflinePlayer player) { try { - return new Money(VaultHook.economy.getBalance(p)); - }catch(Exception e){ - return new Money(VaultHook.economy.getBalance(p.getName())); + return new Money(VaultHook.economy.getBalance(player)); + } catch (Exception ex) { + return new Money(VaultHook.economy.getBalance(player.getName())); } } @@ -69,44 +68,37 @@ protected String getPropertyName() { @Override @Nullable - public Class<?>[] acceptChange(final ChangeMode mode) { + public Class<?>[] acceptChange(ChangeMode mode) { if (mode == ChangeMode.REMOVE_ALL) return null; return new Class[] {Money.class, Number.class}; } - @SuppressWarnings("deprecation") @Override - public void change(final Event e, final @Nullable Object[] delta, final ChangeMode mode) throws UnsupportedOperationException { - assert mode != ChangeMode.REMOVE_ALL; - - if (delta == null) { - for (final OfflinePlayer p : getExpr().getArray(e)) - VaultHook.economy.withdrawPlayer(p.getName(), VaultHook.economy.getBalance(p.getName())); + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + if (delta == null) { // RESET/DELETE + for (OfflinePlayer p : getExpr().getArray(event)) + VaultHook.economy.withdrawPlayer(p, VaultHook.economy.getBalance(p)); return; } - - final double m = delta[0] instanceof Number ? ((Number) delta[0]).doubleValue() : ((Money) delta[0]).getAmount(); - for (final OfflinePlayer p : getExpr().getArray(e)) { + + double money = delta[0] instanceof Number ? ((Number) delta[0]).doubleValue() : ((Money) delta[0]).getAmount(); + for (OfflinePlayer player : getExpr().getArray(event)) { switch (mode) { case SET: - final double b = VaultHook.economy.getBalance(p.getName()); - if (b < m) { - VaultHook.economy.depositPlayer(p.getName(), m - b); - } else if (b > m) { - VaultHook.economy.withdrawPlayer(p.getName(), b - m); + double balance = VaultHook.economy.getBalance(player); + if (balance < money) { + VaultHook.economy.depositPlayer(player, money - balance); + } else if (balance > money) { + VaultHook.economy.withdrawPlayer(player, balance - money); } break; case ADD: - VaultHook.economy.depositPlayer(p.getName(), m); + VaultHook.economy.depositPlayer(player, money); break; case REMOVE: - VaultHook.economy.withdrawPlayer(p.getName(), m); + VaultHook.economy.withdrawPlayer(player, money); break; - case DELETE: - case REMOVE_ALL: - case RESET: - assert false; } } } From 6bf23414910250bfaa65933adbacd9311733b536 Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Wed, 14 Sep 2022 00:43:00 +0200 Subject: [PATCH 090/619] Remove Dependabot language label (#5084) Change indentation to same as on Dependabot docs (4 spaces per indent, dashes within indent) --- .github/dependabot.yml | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ebd2f90e622..7df8e33c9b1 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,11 +1,15 @@ version: 2 updates: -- package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "daily" -- package-ecosystem: gradle - directory: "/" - schedule: - interval: daily - open-pull-requests-limit: 10 + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + labels: + - "dependencies" + - package-ecosystem: gradle + directory: "/" + schedule: + interval: daily + labels: + - "dependencies" + open-pull-requests-limit: 10 From 0b1e4e1cf7a9e066e503d4866c660b2c6eea50ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Sep 2022 14:20:29 -0600 Subject: [PATCH 091/619] Bump org.eclipse.jdt.annotation from 2.2.600 to 2.2.700 (#5100) Bumps [org.eclipse.jdt.annotation](https://github.com/eclipse-jdt/eclipse.jdt.core) from 2.2.600 to 2.2.700. - [Release notes](https://github.com/eclipse-jdt/eclipse.jdt.core/releases) - [Commits](https://github.com/eclipse-jdt/eclipse.jdt.core/commits) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 85367911651..bf9c0b4c9a7 100644 --- a/build.gradle +++ b/build.gradle @@ -25,7 +25,7 @@ dependencies { shadow group: 'io.papermc', name: 'paperlib', version: '1.0.7' shadow group: 'org.bstats', name: 'bstats-bukkit', version: '3.0.0' implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.19.2-R0.1-SNAPSHOT' - implementation group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.600' + implementation group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.700' implementation group: 'com.google.code.findbugs', name: 'findbugs', version: '3.0.1' implementation group: 'com.sk89q.worldguard', name: 'worldguard-legacy', version: '7.0.0-SNAPSHOT' implementation group: 'net.milkbowl.vault', name: 'Vault', version: '1.7.1', { From a26e902b16a8eb087b8e76befcdde06914f868b0 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Thu, 15 Sep 2022 21:58:41 -0600 Subject: [PATCH 092/619] Plus symbol new line standard (#5012) * Plus symbol new line standard --- code-conventions.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/code-conventions.md b/code-conventions.md index c9b7fa40b19..d172db0e268 100644 --- a/code-conventions.md +++ b/code-conventions.md @@ -62,6 +62,11 @@ code. Contributors should also see the dedicated * When there are multiple annotations, place them in order: - @Override -> @Nullable -> @SuppressWarnings - For other annotations, doesn't matter; let your IDE decide +* When splitting Strings into multiple lines the last part of the string must be (space character included) " " + + ```java + String string = "example string " + + "with more to add"; + ``` * When extending one of following classes: SimpleExpression, SimplePropertyExpression, Effect, Condition... - Put overridden methods in order From 97646113c1cd5645c67caabb68c1319a0d212e8d Mon Sep 17 00:00:00 2001 From: Romain <romitou@protonmail.com> Date: Fri, 16 Sep 2022 06:04:57 +0200 Subject: [PATCH 093/619] Add french translation (#5035) * French translation --- src/main/resources/config.sk | 2 +- src/main/resources/lang/french.lang | 190 ++++++++++++++++++++++++++++ 2 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/lang/french.lang diff --git a/src/main/resources/config.sk b/src/main/resources/config.sk index 64282a61bad..f09ec790bda 100644 --- a/src/main/resources/config.sk +++ b/src/main/resources/config.sk @@ -31,7 +31,7 @@ # ==== General Options ==== language: english -# Which language to use. Currently english, german and korean are included in the download, but custom languages can be created as well. +# Which language to use. Currently English, German, Korean and French are included in the download, but custom languages can be created as well. # Please note that not everything can be translated yet, i.e. parts of Skript will still be english if you use another language. # If you want to translate Skript to your language please read the readme.txt located in the /lang/ folder in the jar # (open the jar as zip or rename it to Skript.zip to access it) diff --git a/src/main/resources/lang/french.lang b/src/main/resources/lang/french.lang new file mode 100644 index 00000000000..58774d40d3d --- /dev/null +++ b/src/main/resources/lang/french.lang @@ -0,0 +1,190 @@ +# Default French 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: <none> + +# -- Skript -- +skript: + copyright: ~ créé par & © Peter Güttinger alias Njol ~ + prefix: <gray>[<gold>Skript<gray>] # not used + quotes error: Utilisation incorrecte des guillemets ("). Si vous voulez utiliser des guillemets dans du texte "entre guillemets", doublez-les : "". + brackets error: Quantité ou emplacement des accolades incorrect. Assurez-vous que chaque accolade ouvrante '{' corresponde à une accolade fermante '}'. + invalid reload: Skript ne peut être rechargé qu'avec la commande '/reload' de Bukkit ou la commande '/skript reload' de Skript. + no scripts: Aucun script n'a été trouvé, vous devriez peut-être en écrire quelques-uns ;) + no errors: Tous les scripts ont été chargés sans erreur. + scripts loaded: %s ¦script a été chargé¦scripts ont été chargés¦ avec un total de %s déclencheur¦¦s¦ et de %s commande¦¦s¦ en %s + finished loading: Chargement terminé. + +# -- Skript command -- +skript command: + usage: Utilisation : + help: + description: Commande principale de Skript + help: Affiche ce message d'aide. Utilisez '/skript reload/enable/disable/update' pour obtenir plus d'informations + reload: + description: Recharge la configuration, tous les scripts, tout, ou un script spécifique + all: Recharge la configuration, toutes les configurations d'alias et tous les scripts + config: Recharge la configuration principale + aliases: Recharge les configurations d'alias (aliases-english.zip ou jar du plugin) + scripts: Recharge tous les scripts + <script>: Recharge un script spécifique ou un dossier de scripts + enable: + description: Active tous les scripts ou un script spécifique + all: Active tous les scripts + <script>: Active un script spécifique ou un dossier de scripts + disable: + description: Désactive tous les scripts ou un script spécifique + all: Désactive tous les scripts + <script>: Désactive un script spécifique ou un dossier de scripts + update: + description: Vérifie les mises à jour, liste le journal des modifications ou télécharge la dernière version de Skript + check: Vérifie la présence d'une nouvelle version + changes: Liste toutes les modifications apportées depuis la version actuelle + download: Télécharge la dernière version + info: Affiche un message contenant les liens vers les alias et la documentation de Skript + gen-docs: Génère la documentation en utilisant doc-templates dans le dossier du plugin + test: Utilisé pour exécuter les tests Skript + + invalid script: Impossible de trouver le script <grey>'<gold>%s<grey>'<red> dans le dossier des scripts ! + invalid folder: Impossible de trouver le dossier <grey>'<gold>%s<grey>'<red> dans le dossier des scripts ! + reload: + warning line info: <gold><bold>Ligne %s :<gray> (%s)<reset>\n + error line info: <light red><bold>Ligne %s :<gray> (%s)<reset>\n + reloading: Rechargement de <gold>%s<reset>... + reloaded: <gold>%s<lime> rechargé(s) avec succès. <gray>(<gold>%2$sms<gray>) + error: <gold>%2$s <light red>¦erreur rencontrée¦erreurs rencontrées¦ lors du rechargement de <gold>%1$s<light red>! <gray>(<gold>%3$sms<gray>) + script disabled: <gold>%s<reset> est actuellement désactivé. Utilisez <gray>/<gold>skript <cyan>enable <red>%s<reset> pour l'activer. + warning details: <yellow> %s<reset>\n + error details: <light red> %s<reset>\n + other details: <white> %s<reset>\n + line details: <gold> Ligne : <gray>%s<reset>\n <reset> + + config, aliases and scripts: la configuration, les alias et tous les scripts + scripts: tous les scripts + main config: la configuration principale + aliases: les alias + script: <gold>%s<reset> + scripts in folder: tous les scripts dans <gold>%s<reset> + x scripts in folder: <gold>%2$s <reset>script¦¦s¦ dans <gold>%1$s<reset> + empty folder: <gold>%s<reset> ne contient aucun script activé. + enable: + all: + enabling: Activation de tous les scripts désactivés... + enabled: Activation et analyse réussies de tous les scripts précédemment désactivés. + error: %s ¦erreur rencontrée¦erreurs rencontrées¦ lors de l'analyse des scripts désactivés ! + io error: Impossible de charger les scripts (certains scripts ont peut-être déjà été renommés et seront activés au redémarrage du serveur) : %s + single: + already enabled: <gold>%s<reset> est déjà activé ! Utilisez <gray>/<gold>skript <cyan>reload <red>%s<reset> pour le recharger s'il a été modifié. + enabling: Activation de <gold>%s<reset>... + enabled: <gold>%s<reset> activé et analysé avec succès. + error: %2$s ¦erreur rencontrée¦erreurs rencontrées¦ lors de l'analyse de <gold>%1$s<red> ! + io error: Impossible d'activer <gold>%s<red>: %s + folder: + empty: <gold>%s<reset> ne contient aucun script désactivé. + enabling: Activation de <gold>%2$s <reset>script¦¦s¦ dans <gold>%1$s<reset>... + enabled: Activation et analyse réussies de <gold>%2$s<reset> script(s) précédemment désactivé(s) dans <gold>%1$s<reset>. + error: %2$s ¦erreur rencontrée¦erreurs rencontrées¦ lors de l'analyse des scripts dans <gold>%1$s<red> ! + io error: Erreur lors de l'activation des scripts dans <gold>%s<red> (certains scripts pourraient être activés lors du redémarrage du serveur) : %s + disable: + all: + disabled: Désactivation de tous les scripts réussie. + io error: Impossible de renommer tous les scripts - certains scripts seront réactivés lorsque vous redémarrerez le serveur : %s + single: + already disabled: <gold>%s<reset> est déjà désactivé ! + disabled: <gold>%s<reset> désactivé avec succès. + io error: Impossible de renommer <gold>%s<red>, il sera à nouveau activé lorsque vous redémarrerez le serveur : %s + folder: + empty: <gold>%s<reset> ne contient aucun script activé. + disabled: Désactivation réussie de <gold>%2$s<reset> script(s) dans <gold>%1$s<reset>. + io error: Impossible de désactiver les scripts <gold>%s<red> (certains scripts pourraient être désactivés lors du redémarrage du serveur): %s + update: + # check/download: see Updater + changes: + # multiple versions: + # title: <gold>%s<r> update¦ has¦s have¦ been released since this version (<gold>%s<r>) of Skript: + # footer: To show the changelog of a version type <gold>/skript update changes <version><reset> + # invalid version: No changelog for the version <gold>%s<red> available + title: <bold><cyan>%s<reset> (%s) + next page: <grey>page %s sur %s. Utilisez <gold>/skript update changes %s<gray> pour la page suivante (astuce : utilisez la touche flèche du haut) + info: + aliases: Les alias de Skript se trouvent ici : <aqua>https://github.com/SkriptLang/skript-aliases<reset> (en anglais) + documentation: La documentation de Skript se trouve ici : <aqua>https://docs.skriptlang.org/<reset> (en anglais) + tutorials: Les tutoriels de Skript se trouvent ici : <aqua>https://docs.skriptlang.org/tutorials<reset> (en anglais) + version: Version de Skript : <aqua>%s + server: Version du serveur : <aqua>%s + addons: Addons Skript installés : <aqua>%s + dependencies: Dépendances installées : <aqua>%s + +# -- Updater -- +updater: + not started: Skript n'a pas encore vérifié la dernière version. Utilisez <gold>/skript update check<reset> pour vérifier. + checking: Vérification de la dernière version de Skript... + check in progress: La vérification d'une nouvelle version est actuellement en cours. + updater disabled: Le système de mise à jour étant désactivé, la vérification de la dernière version de Skript n'a pas été effectuée. + check error: <red>Une erreur s'est produite lors de la vérification de la dernière version de Skript :<light red> %s + running latest version: Vous utilisez actuellement la dernière version stable de Skript. + running latest version (beta): Vous utilisez actuellement une version <i>bêta<r> de Skript et aucune nouvelle version <i>stable<r> n'est disponible. Notez que vous devez mettre à jour les nouvelles versions bêta manuellement ! + update available: Une nouvelle version de Skript est disponible : <gold>%s<reset> (vous utilisez actuellement <gold>%s<reset>) + downloading: Téléchargement de Skript <gold>%s<reset>... + download in progress: La dernière version de Skript est en cours de téléchargement. + download error: <red>Une erreur s'est produite lors du téléchargement de la dernière version de Skript :<light red> %s + downloaded: La dernière version de Skript a été téléchargée ! Redémarrez le serveur ou utilisez /reload pour appliquer les changements. + internal error: Une erreur interne s'est produite lors de la vérification de la dernière version de Skript. Veuillez consulter le journal du serveur pour plus de détails. + custom version: Vous utilisez actuellement une version personnalisée de Skript. Aucune mise à jour ne sera installée automatiquement. + nightly build: Vous utilisez actuellement une version de développement de Skript. Aucune mise à jour ne sera installée automatiquement. + +# -- Commands -- +commands: + no permission message: Vous n'avez pas la permission d'utiliser cette commande + cooldown message: Vous utilisez cette commande trop souvent, veuillez réessayer plus tard + executable by players: Cette commande ne peut être utilisée que par des joueurs + executable by console: Cette commande ne peut être utilisée que par la console + correct usage: Utilisation correcte : + invalid argument: Argument invalide <gray>'%s<gray>'<reset>. Sont autorisés : + too many arguments: Cette commande ne peut accepter qu'un seul %2$s. + internal error: Une erreur interne s'est produite lors de la tentative d'exécution de cette commande. + no player starts with: Il n'y a aucun joueur en ligne dont le nom commence par '%s' + multiple players start with: Il y a plusieurs joueurs en ligne dont le nom commence par '%s' + +# -- Hooks -- +hooks: + hooked: Liaison avec %s effectuée avec succès + error: Impossible d'effectuer la liaison avec %1$s. Cela peut se produire si Skript ne prend pas en charge la version installée de %1$s + +# -- Aliases -- +aliases: + # Errors and warnings + empty string: '' n'est pas un item type + invalid item type: '%s' n'est pas un item type + empty name: Un alias doit avoir un nom + brackets error: Utilisation incorrecte des accolades '{' ou '}' + not enough brackets: La section commençant au caractère %s ('%s') doit être fermée + too many brackets: Le caractère %s ('%s') ferme une section inexistante + unknown variation: La variante %s n'a pas été définie avant + missing aliases: Les identifiants Minecraft suivants n'ont pas d'alias : + empty alias: L'alias n'a pas d'identifiant Minecraft ou de tags définis + invalid minecraft id: L'identifiant Minecraft %s est invalide + useless variation: La variante n'a pas d'identifiant Minecraft ou de balises spécifiés, elle est donc inutile + invalid tags: Les balises spécifiées ne sont pas définies dans un JSON valide + unexpected section: Les sections ne sont pas autorisées ici + invalid variation section: Une section devrait être une section de variante, mais %s n'est pas un nom de variante valide. + outside section: Les alias doivent être placés dans des sections + + # Other messages + loaded x aliases from: %s alias anglais chargés de %s + loaded x aliases: %s alias anglais ont été chargés + +# -- Time -- +time: + errors: + 24 hours: Un jour n'a que 24 heures + 12 hours: L'utilisation du format 12 heures ne permet pas de dépasser les 12 heures + 60 minutes: Une heure ne compte que 60 minutes + +# -- IO Exceptions -- +io exceptions: + unknownhostexception: Impossible de se connecter à %s + accessdeniedexception: Accès refusé pour %s From f56409325c2d1e12fd6161f8849027b6761ae5fc Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <alali_ayham@yahoo.com> Date: Fri, 16 Sep 2022 07:12:52 +0300 Subject: [PATCH 094/619] =?UTF-8?q?=E2=9A=92=EF=B8=8F=20Fix=20Time=20Playe?= =?UTF-8?q?d=20NSME=20(#4938)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix Time Played NSME --- .../skript/expressions/ExprTimePlayed.java | 84 ++++++++++++------- .../tests/regressions/4938-time played.sk | 6 ++ 2 files changed, 58 insertions(+), 32 deletions(-) create mode 100644 src/test/skript/tests/regressions/4938-time played.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprTimePlayed.java b/src/main/java/ch/njol/skript/expressions/ExprTimePlayed.java index afd607ffa36..7e6d2cf1831 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTimePlayed.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTimePlayed.java @@ -18,68 +18,73 @@ */ package ch.njol.skript.expressions; -import ch.njol.skript.classes.Changer.ChangeMode; -import org.bukkit.OfflinePlayer; -import org.bukkit.Statistic; -import org.bukkit.entity.Player; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; -import ch.njol.skript.classes.Changer; +import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.SimplePropertyExpression; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.util.Timespan; -import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; +import org.bukkit.OfflinePlayer; +import org.bukkit.Statistic; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; @Name("Time Played") -@Description("The amount of time a player has played for on the server. This info is stored in the player's statistics in " + - "the main world's data folder. Changing this will also change the player's stats which can be views in the client's statistics menu.") -@Examples({"set {_t} to time played of player", +@Description({ + "The amount of time a player has played for on the server. This info is stored in the player's statistics in " + + "the main world's data folder. Changing this will also change the player's stats which can be views in the client's statistics menu.", + "Using this expression on offline players on Minecraft 1.14 and below will return nothing <code><none></code>." +}) +@Examples({ + "set {_t} to time played of player", "if player's time played is greater than 10 minutes:", "\tgive player a diamond sword", - "set player's time played to 0 seconds"}) -@Since("2.5") + "", + "set player's time played to 0 seconds" +}) +@RequiredPlugins("MC 1.15+ (offline players)") +@Since("2.5, INSERT VERSION (offline players)") public class ExprTimePlayed extends SimplePropertyExpression<OfflinePlayer, Timespan> { + private static final boolean IS_OFFLINE_SUPPORTED = Skript.methodExists(OfflinePlayer.class, "getStatistic", Statistic.class); + static { register(ExprTimePlayed.class, Timespan.class, "time played", "offlineplayers"); } - @SuppressWarnings({"unchecked", "null"}) - @Override - public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - setExpr((Expression<Player>) exprs[0]); - return true; - } - @Nullable @Override public Timespan convert(OfflinePlayer offlinePlayer) { - return Timespan.fromTicks_i(offlinePlayer.getStatistic(Statistic.PLAY_ONE_MINUTE)); + return getTimePlayed(offlinePlayer); } @Nullable @Override public Class<?>[] acceptChange(ChangeMode mode) { - if (mode == ChangeMode.SET || mode == ChangeMode.ADD || mode == ChangeMode.REMOVE) { + if (mode == ChangeMode.SET || mode == ChangeMode.ADD || mode == ChangeMode.REMOVE) return CollectionUtils.array(Timespan.class); - } else { - return null; - } + return null; } @Override - public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + if (delta == null) + return; + long ticks = ((Timespan) delta[0]).getTicks_i(); - for (OfflinePlayer offlinePlayer : getExpr().getArray(e)) { - long playerTicks = offlinePlayer.getStatistic(Statistic.PLAY_ONE_MINUTE); + for (OfflinePlayer offlinePlayer : getExpr().getArray(event)) { + if (!IS_OFFLINE_SUPPORTED && !offlinePlayer.isOnline()) + continue; + + Timespan playerTimespan = getTimePlayed(offlinePlayer); + if (playerTimespan == null) + continue; + + long playerTicks = playerTimespan.getTicks_i(); switch (mode) { case ADD: ticks = playerTicks + ticks; @@ -88,7 +93,12 @@ public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { ticks = playerTicks - ticks; break; } - offlinePlayer.setStatistic(Statistic.PLAY_ONE_MINUTE, (int) ticks); + + if (IS_OFFLINE_SUPPORTED) { + offlinePlayer.setStatistic(Statistic.PLAY_ONE_MINUTE, (int) ticks); + } else if (offlinePlayer.isOnline()) { + offlinePlayer.getPlayer().setStatistic(Statistic.PLAY_ONE_MINUTE, (int) ticks); // No NPE due to isOnline check + } } } @@ -101,5 +111,15 @@ public Class<? extends Timespan> getReturnType() { protected String getPropertyName() { return "time played"; } + + @Nullable + private Timespan getTimePlayed(OfflinePlayer offlinePlayer) { + if (IS_OFFLINE_SUPPORTED) { + return Timespan.fromTicks_i(offlinePlayer.getStatistic(Statistic.PLAY_ONE_MINUTE)); + } else if (offlinePlayer.isOnline()) { + return Timespan.fromTicks_i(offlinePlayer.getPlayer().getStatistic(Statistic.PLAY_ONE_MINUTE)); + } + return null; + } } diff --git a/src/test/skript/tests/regressions/4938-time played.sk b/src/test/skript/tests/regressions/4938-time played.sk new file mode 100644 index 00000000000..dd1a9de21d5 --- /dev/null +++ b/src/test/skript/tests/regressions/4938-time played.sk @@ -0,0 +1,6 @@ +test "time played": + set {_time} to time played of "Notch" parsed as offlineplayer + if running minecraft "1.15": + assert {_time} is equal to 0 seconds with "Notch hacked your server and built a dirt house on your server (time: %{_time}%)" + else: + assert {_time} is not set with "Played time of offline players are not supported but seems like Notch is a hacker! (time: %{_time}%)" From d5ffe68a4ca3b43104e46cada48ae17006076773 Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Sat, 17 Sep 2022 07:39:27 +0300 Subject: [PATCH 095/619] allow multiple livingentities in EffEquip's pattern (#5091) --- .../java/ch/njol/skript/effects/EffEquip.java | 61 ++++++++++--------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffEquip.java b/src/main/java/ch/njol/skript/effects/EffEquip.java index a65fdfa8ab4..bcb0a9590ec 100644 --- a/src/main/java/ch/njol/skript/effects/EffEquip.java +++ b/src/main/java/ch/njol/skript/effects/EffEquip.java @@ -56,32 +56,33 @@ @Description("Equips an entity with some given armor. This will replace any armor that the entity is wearing.") @Examples({"equip player with diamond helmet", "equip player with all diamond armor"}) -@Since("1.0") +@Since("1.0, INSERT VERSION (multiple entities)") public class EffEquip extends Effect implements Testable { + static { Skript.registerEffect(EffEquip.class, - "equip [%livingentity%] with %itemtypes%", - "make %livingentity% wear %itemtypes%"); + "equip [%livingentities%] with %itemtypes%", + "make %livingentities% wear %itemtypes%"); } - + @SuppressWarnings("null") private Expression<LivingEntity> entities; @SuppressWarnings("null") private Expression<ItemType> types; - + @SuppressWarnings({"unchecked", "null"}) @Override - public boolean init(final Expression<?>[] vars, final int matchedPattern, final Kleenean isDelayed, final ParseResult parser) { - entities = (Expression<LivingEntity>) vars[0]; - types = (Expression<ItemType>) vars[1]; + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) { + entities = (Expression<LivingEntity>) exprs[0]; + types = (Expression<ItemType>) exprs[1]; return true; } - + private static final boolean SUPPORTS_HORSES = Skript.classExists("org.bukkit.entity.Horse"); private static final boolean NEW_HORSES = Skript.classExists("org.bukkit.entity.AbstractHorse"); private static final boolean SUPPORTS_LLAMAS = Skript.classExists("org.bukkit.entity.Llama"); private static final boolean SUPPORTS_STEERABLE = Skript.classExists("org.bukkit.entity.Steerable"); - + private static final ItemType HELMET = Aliases.javaItemType("helmet"); private static final ItemType CHESTPLATE = Aliases.javaItemType("chestplate"); private static final ItemType LEGGINGS = Aliases.javaItemType("leggings"); @@ -90,12 +91,12 @@ public boolean init(final Expression<?>[] vars, final int matchedPattern, final private static final ItemType SADDLE = Aliases.javaItemType("saddle"); private static final ItemType CHEST = Aliases.javaItemType("chest"); private static final ItemType CARPET = Aliases.javaItemType("carpet"); - - @SuppressWarnings("deprecation") + @Override - protected void execute(final Event e) { - final ItemType[] ts = types.getArray(e); - for (final LivingEntity en : entities.getArray(e)) { + @SuppressWarnings("deprecation") + protected void execute(Event event) { + ItemType[] ts = types.getArray(event); + for (LivingEntity en : entities.getArray(event)) { if (SUPPORTS_STEERABLE && en instanceof Steerable) { for (ItemType it : ts) { if (SADDLE.isOfType(it.getMaterial())) { @@ -103,7 +104,7 @@ protected void execute(final Event e) { } } } else if (en instanceof Pig) { - for (final ItemType t : ts) { + for (ItemType t : ts) { if (t.isOfType(Material.SADDLE)) { ((Pig) en).setSaddle(true); break; @@ -124,9 +125,9 @@ protected void execute(final Event e) { continue; } else if (NEW_HORSES && en instanceof AbstractHorse) { // Spigot's API is bad, just bad... Abstract horse doesn't have horse inventory! - final Inventory invi = ((AbstractHorse) en).getInventory(); - for (final ItemType t : ts) { - for (final ItemStack item : t.getAll()) { + Inventory invi = ((AbstractHorse) en).getInventory(); + for (ItemType t : ts) { + for (ItemStack item : t.getAll()) { if (SADDLE.isOfType(item)) { invi.setItem(0, item); // Slot 0=saddle } else if (HORSE_ARMOR.isOfType(item)) { @@ -138,9 +139,9 @@ protected void execute(final Event e) { } continue; } else if (SUPPORTS_HORSES && en instanceof Horse) { - final HorseInventory invi = ((Horse) en).getInventory(); - for (final ItemType t : ts) { - for (final ItemStack item : t.getAll()) { + HorseInventory invi = ((Horse) en).getInventory(); + for (ItemType t : ts) { + for (ItemStack item : t.getAll()) { if (SADDLE.isOfType(item)) { invi.setSaddle(item); } else if (HORSE_ARMOR.isOfType(item)) { @@ -155,8 +156,8 @@ protected void execute(final Event e) { EntityEquipment equip = en.getEquipment(); if (equip == null) continue; - for (final ItemType t : ts) { - for (final ItemStack item : t.getAll()) { + for (ItemType t : ts) { + for (ItemStack item : t.getAll()) { // Blocks are visible in head slot, too // TODO skulls; waiting for decoration aliases if (HELMET.isOfType(item) || item.getType().isBlock()) @@ -176,9 +177,9 @@ else if (BOOTS.isOfType(item)) PlayerUtils.updateInventory((Player) en); } } - + @Override - public boolean test(final Event e) { + public boolean test(Event e) { // final Iterable<Player> ps = players.getArray(e); // for (final ItemType t : types.getArray(e)) { // for (final Player p : ps) { @@ -187,10 +188,10 @@ public boolean test(final Event e) { // } return false; } - + @Override - public String toString(final @Nullable Event e, final boolean debug) { - return "equip " + entities.toString(e, debug) + " with " + types.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "equip " + entities.toString(event, debug) + " with " + types.toString(event, debug); } - + } From ab59b2aa53a7eea2f0e9fc65f993aa1af2f8ec00 Mon Sep 17 00:00:00 2001 From: Abe <69663458+AbeTGT@users.noreply.github.com> Date: Sat, 17 Sep 2022 16:49:44 +1200 Subject: [PATCH 096/619] Add Paper's PlayerTradeEvent in SimpleEvents (#5049) * Added Paper PlayerTradeEvent --- .../njol/skript/classes/data/BukkitEventValues.java | 12 ++++++++++++ .../java/ch/njol/skript/events/SimpleEvents.java | 11 +++++++++++ 2 files changed, 23 insertions(+) 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 b7d339051e5..3ae4e8f8c7f 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -38,6 +38,7 @@ import com.destroystokyo.paper.event.entity.ProjectileCollideEvent; import com.destroystokyo.paper.event.player.PlayerArmorChangeEvent; import io.papermc.paper.event.entity.EntityMoveEvent; +import io.papermc.paper.event.player.PlayerTradeEvent; import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.FireworkEffect; @@ -59,6 +60,7 @@ import org.bukkit.entity.Player; import org.bukkit.entity.Projectile; import org.bukkit.entity.Vehicle; +import org.bukkit.entity.AbstractVillager; import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.block.BlockCanBuildEvent; import org.bukkit.event.block.BlockDamageEvent; @@ -1368,6 +1370,16 @@ public Entity get(HorseJumpEvent evt) { return evt.getEntity(); } }, 0); + // PlayerTradeEvent + if (Skript.classExists("io.papermc.paper.event.player.PlayerTradeEvent")) { + EventValues.registerEventValue(PlayerTradeEvent.class, AbstractVillager.class, new Getter<AbstractVillager, PlayerTradeEvent>() { + @Override + @Nullable + public AbstractVillager get(PlayerTradeEvent event) { + return event.getVillager(); + } + }, EventValues.TIME_NOW); + } // PlayerChangedWorldEvent EventValues.registerEventValue(PlayerChangedWorldEvent.class, World.class, new Getter<World, PlayerChangedWorldEvent>() { @Nullable diff --git a/src/main/java/ch/njol/skript/events/SimpleEvents.java b/src/main/java/ch/njol/skript/events/SimpleEvents.java index d1b193ac5b0..6c9e56adcee 100644 --- a/src/main/java/ch/njol/skript/events/SimpleEvents.java +++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java @@ -108,6 +108,7 @@ import com.destroystokyo.paper.event.player.PlayerArmorChangeEvent; import com.destroystokyo.paper.event.player.PlayerJumpEvent; import com.destroystokyo.paper.event.server.PaperServerListPingEvent; +import io.papermc.paper.event.player.PlayerTradeEvent; import ch.njol.skript.Skript; import ch.njol.skript.SkriptEventHandler; import ch.njol.skript.lang.util.SimpleEvent; @@ -618,6 +619,16 @@ public class SimpleEvents { "\t\tset repair cost to repair cost * 50%", "\t\tsend \"You're LUCKY! You got 50% discount.\" to player") .since("INSERT VERSION"); + if (Skript.classExists("io.papermc.paper.event.player.PlayerTradeEvent")) { + Skript.registerEvent("Player Trade", SimpleEvent.class, PlayerTradeEvent.class, "player trad(e|ing)") + .description("Called when a player has traded with a villager.") + .requiredPlugins("Paper 1.16.5+") + .examples("on player trade:", + "\tchance of 50%:", + "\t\tcancel event", + "\t\tsend \"The trade was somehow denied!\" to player") + .since("INSERT VERSION"); + } if (Skript.classExists("com.destroystokyo.paper.event.block.AnvilDamagedEvent")) { Skript.registerEvent("Anvil Damage", SimpleEvent.class, AnvilDamagedEvent.class, "anvil damag(e|ing)") .description("Called when an anvil is damaged/broken from being used to repair/rename items.", From b9434f9a659946c54d375e94617023811f1ee105 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Wed, 21 Sep 2022 18:06:30 -0600 Subject: [PATCH 097/619] Update tests readme (#4967) --- src/test/skript/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/skript/README.md b/src/test/skript/README.md index 4d676330df9..3955abe012a 100644 --- a/src/test/skript/README.md +++ b/src/test/skript/README.md @@ -75,12 +75,12 @@ some syntaxes for test development are available. Use Gradle to launch a test development server: ``` -TERM=dumb ./gradlew clean skriptTestDev +gradlew clean skriptTestDev --console=plain ``` The server launched will be running at localhost:25565. You can use console as normal, though there is some lag due to Gradle. If you're having trouble, -try without <code>TERM=dumb</code>. +try without <code>--console=plain</code>. To run individual test files, use <code>/sk test \<file\></code>. To run last used file again, just use <code>/sk test</code>. From 69ef6de6adf1e7dbfb76a1dbb830d955c04ef382 Mon Sep 17 00:00:00 2001 From: Fusezion <fusezionstream@gmail.com> Date: Mon, 26 Sep 2022 10:12:42 -0400 Subject: [PATCH 098/619] Move EffMakeHatch to effect folder (#5115) --- .../njol/skript/{expressions => effects}/EffMakeEggHatch.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/main/java/ch/njol/skript/{expressions => effects}/EffMakeEggHatch.java (98%) diff --git a/src/main/java/ch/njol/skript/expressions/EffMakeEggHatch.java b/src/main/java/ch/njol/skript/effects/EffMakeEggHatch.java similarity index 98% rename from src/main/java/ch/njol/skript/expressions/EffMakeEggHatch.java rename to src/main/java/ch/njol/skript/effects/EffMakeEggHatch.java index 796b54d92f6..1f46d878ed6 100644 --- a/src/main/java/ch/njol/skript/expressions/EffMakeEggHatch.java +++ b/src/main/java/ch/njol/skript/effects/EffMakeEggHatch.java @@ -16,7 +16,7 @@ * * Copyright Peter Güttinger, SkriptLang team and contributors */ -package ch.njol.skript.expressions; +package ch.njol.skript.effects; import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; From c760e0a26b20de6477b0722a79dcff8e24ea072b Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Wed, 28 Sep 2022 17:24:19 -0400 Subject: [PATCH 099/619] EffSecSpawn improvements and fixes (#4911) * EffSecSpawn improvements and fixes --- .../java/ch/njol/skript/effects/Delay.java | 126 +++++++++--------- .../skript/effects/IndeterminateDelay.java | 43 +++--- .../java/ch/njol/skript/lang/Section.java | 23 ++++ .../java/ch/njol/skript/lang/Trigger.java | 29 ++-- .../ch/njol/skript/sections/EffSecSpawn.java | 73 +++++----- .../tests/syntaxes/sections/EffSecSpawn.sk | 15 ++- 6 files changed, 181 insertions(+), 128 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/Delay.java b/src/main/java/ch/njol/skript/effects/Delay.java index a732f15f387..95025faa534 100644 --- a/src/main/java/ch/njol/skript/effects/Delay.java +++ b/src/main/java/ch/njol/skript/effects/Delay.java @@ -18,14 +18,6 @@ */ package ch.njol.skript.effects; -import java.util.Collections; -import java.util.Set; -import java.util.WeakHashMap; - -import org.bukkit.Bukkit; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; @@ -41,28 +33,36 @@ import ch.njol.skript.util.Timespan; import ch.njol.skript.variables.Variables; import ch.njol.util.Kleenean; +import org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.Collections; +import java.util.Set; +import java.util.WeakHashMap; -/** - * @author Peter Güttinger - */ @Name("Delay") @Description("Delays the script's execution by a given timespan. Please note that delays are not persistent, e.g. trying to create a tempban script with <code>ban player → wait 7 days → unban player</code> will not work if you restart your server anytime within these 7 days. You also have to be careful even when using small delays!") -@Examples({"wait 2 minutes", - "halt for 5 minecraft hours", - "wait a tick"}) +@Examples({ + "wait 2 minutes", + "halt for 5 minecraft hours", + "wait a tick" +}) @Since("1.4") public class Delay extends Effect { + static { Skript.registerEffect(Delay.class, "(wait|halt) [for] %timespan%"); } - @SuppressWarnings("null") + @SuppressWarnings("NotNullFieldNotInitialized") protected Expression<Timespan> duration; @SuppressWarnings({"unchecked", "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) { getParser().setHasDelayBefore(Kleenean.TRUE); + duration = (Expression<Timespan>) exprs[0]; if (duration instanceof Literal) { // If we can, do sanity check for delays long millis = ((Literal<Timespan>) duration).getSingle().getMilliSeconds(); @@ -70,71 +70,77 @@ public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Skript.warning("Delays less than one tick are not possible, defaulting to one tick."); } } + return true; } @Override @Nullable - protected TriggerItem walk(final Event e) { - debug(e, true); - final long start = Skript.debug() ? System.nanoTime() : 0; - final TriggerItem next = getNext(); + protected TriggerItem walk(Event event) { + debug(event, true); + long start = Skript.debug() ? System.nanoTime() : 0; + TriggerItem next = getNext(); if (next != null && Skript.getInstance().isEnabled()) { // See https://github.com/SkriptLang/Skript/issues/3702 - delayed.add(e); - final Timespan d = duration.getSingle(e); - if (d == null) + addDelayedEvent(event); + + Timespan duration = this.duration.getSingle(event); + if (duration == null) return null; // Back up local variables - Object localVars = Variables.removeLocals(e); + Object localVars = Variables.removeLocals(event); - Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.getInstance(), new Runnable() { - @Override - public void run() { - if (Skript.debug()) - Skript.info(getIndentation() + "... continuing after " + (System.nanoTime() - start) / 1000000000. + "s"); - - // Re-set local variables - if (localVars != null) - Variables.setLocalVariables(e, localVars); - - Object timing = null; - if (SkriptTimings.enabled()) { // getTrigger call is not free, do it only if we must - Trigger trigger = getTrigger(); - if (trigger != null) { - timing = SkriptTimings.start(trigger.getDebugLabel()); - } - } - - TriggerItem.walk(next, e); - Variables.removeLocals(e); // Clean up local vars, we may be exiting now - - SkriptTimings.stop(timing); // Stop timing if it was even started + Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.getInstance(), () -> { + Skript.debug(getIndentation() + "... continuing after " + (System.nanoTime() - start) / 1_000_000_000. + "s"); + + // Re-set local variables + if (localVars != null) + Variables.setLocalVariables(event, localVars); + + Object timing = null; // Timings reference must be kept so that it can be stopped after TriggerItem execution + if (SkriptTimings.enabled()) { // getTrigger call is not free, do it only if we must + Trigger trigger = getTrigger(); + if (trigger != null) + timing = SkriptTimings.start(trigger.getDebugLabel()); } - }, d.getTicks_i() < 1 ? 1 : d.getTicks_i()); // Minimum delay is one tick, less than it is useless! + + TriggerItem.walk(next, event); + Variables.removeLocals(event); // Clean up local vars, we may be exiting now + + SkriptTimings.stop(timing); // Stop timing if it was even started + }, Math.max(duration.getTicks_i(), 1)); // Minimum delay is one tick, less than it is useless! } return null; } - @SuppressWarnings("null") - protected final static Set<Event> delayed = Collections.newSetFromMap(new WeakHashMap<Event, Boolean>()); - - public static boolean isDelayed(final Event e) { - return delayed.contains(e); + @Override + protected void execute(Event event) { + throw new UnsupportedOperationException(); } - public static void addDelayedEvent(Event event){ - delayed.add(event); + @Override + public String toString(@Nullable Event event, boolean debug) { + return "wait for " + duration.toString(event, debug) + (event == null ? "" : "..."); } - @Override - protected void execute(final Event e) { - throw new UnsupportedOperationException(); + private static final Set<Event> DELAYED = + Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<>())); + + /** + * The main method for checking if the execution of {@link TriggerItem}s has been delayed. + * @param event The event to check for a delay. + * @return Whether {@link TriggerItem} execution has been delayed. + */ + public static boolean isDelayed(Event event) { + return DELAYED.contains(event); } - @Override - public String toString(final @Nullable Event e, final boolean debug) { - return "wait for " + duration.toString(e, debug) + (e == null ? "" : "..."); + /** + * The main method for marking the execution of {@link TriggerItem}s as delayed. + * @param event The event to mark as delayed. + */ + public static void addDelayedEvent(Event event) { + DELAYED.add(event); } } diff --git a/src/main/java/ch/njol/skript/effects/IndeterminateDelay.java b/src/main/java/ch/njol/skript/effects/IndeterminateDelay.java index 17a2ff81605..37dee92b1eb 100644 --- a/src/main/java/ch/njol/skript/effects/IndeterminateDelay.java +++ b/src/main/java/ch/njol/skript/effects/IndeterminateDelay.java @@ -34,38 +34,37 @@ public class IndeterminateDelay extends Delay { @Override @Nullable - protected TriggerItem walk(final Event e) { - debug(e, true); - final long start = Skript.debug() ? System.nanoTime() : 0; - final TriggerItem next = getNext(); + protected TriggerItem walk(Event event) { + debug(event, true); + + long start = Skript.debug() ? System.nanoTime() : 0; + TriggerItem next = getNext(); + if (next != null && Skript.getInstance().isEnabled()) { // See https://github.com/SkriptLang/Skript/issues/3702 - delayed.add(e); - final Timespan d = duration.getSingle(e); - if (d == null) + Delay.addDelayedEvent(event); + Timespan duration = this.duration.getSingle(event); + if (duration == null) return null; // Back up local variables - Object localVars = Variables.removeLocals(e); + Object localVars = Variables.removeLocals(event); - Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.getInstance(), new Runnable() { - @Override - public void run() { - if (Skript.debug()) - Skript.info(getIndentation() + "... continuing after " + (System.nanoTime() - start) / 1000000000. + "s"); - - // Re-set local variables - if (localVars != null) - Variables.setLocalVariables(e, localVars); - - TriggerItem.walk(next, e); - } - }, d.getTicks_i()); + Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.getInstance(), () -> { + Skript.debug(getIndentation() + "... continuing after " + (System.nanoTime() - start) / 1_000_000_000. + "s"); + + // Re-set local variables + if (localVars != null) + Variables.setLocalVariables(event, localVars); + + TriggerItem.walk(next, event); + }, duration.getTicks_i()); } + return null; } @Override - public String toString(@Nullable final Event e, final boolean debug) { + public String toString(@Nullable Event event, boolean debug) { return "wait for operation to finish"; } diff --git a/src/main/java/ch/njol/skript/lang/Section.java b/src/main/java/ch/njol/skript/lang/Section.java index c631b686b35..364ff43bf8f 100644 --- a/src/main/java/ch/njol/skript/lang/Section.java +++ b/src/main/java/ch/njol/skript/lang/Section.java @@ -108,6 +108,26 @@ protected void loadCode(SectionNode sectionNode) { */ @SafeVarargs protected final Trigger loadCode(SectionNode sectionNode, String name, Class<? extends Event>... events) { + return loadCode(sectionNode, name, null, events); + } + + /** + * Loads the code in the given {@link SectionNode}, + * appropriately modifying {@link ParserInstance#getCurrentSections()}. + * + * This method differs from {@link #loadCode(SectionNode)} in that it + * is meant for code that will be executed in a different event. + * + * @param sectionNode The section node to load. + * @param name The name of the event(s) being used. + * @param afterLoading A Runnable to execute after the SectionNode has been loaded. + * This occurs before {@link ParserInstance} states are reset. + * @param events The event(s) during the section's execution. + * @return A trigger containing the loaded section. This should be stored and used + * to run the section one or more times. + */ + @SafeVarargs + protected final Trigger loadCode(SectionNode sectionNode, String name, @Nullable Runnable afterLoading, Class<? extends Event>... events) { ParserInstance parser = getParser(); String previousName = parser.getCurrentEventName(); @@ -123,6 +143,9 @@ protected final Trigger loadCode(SectionNode sectionNode, String name, Class<? e parser.setHasDelayBefore(Kleenean.FALSE); List<TriggerItem> triggerItems = ScriptLoader.loadItems(sectionNode); + if (afterLoading != null) + afterLoading.run(); + //noinspection ConstantConditions - We are resetting it to what it was parser.setCurrentEvent(previousName, previousEvents); parser.setCurrentSkriptEvent(previousSkriptEvent); diff --git a/src/main/java/ch/njol/skript/lang/Trigger.java b/src/main/java/ch/njol/skript/lang/Trigger.java index e81865c6c4f..1c41e2c2c0f 100644 --- a/src/main/java/ch/njol/skript/lang/Trigger.java +++ b/src/main/java/ch/njol/skript/lang/Trigger.java @@ -46,29 +46,30 @@ public Trigger(final @Nullable File script, final String name, final SkriptEvent this.event = event; this.debugLabel = "unknown trigger"; } - + /** - * Executes this trigger for certain event. - * @param e Event. - * @return false if an exception occurred + * Executes this trigger for a certain event. + * @param event The event to execute this Trigger with. + * @return false if an exception occurred. */ - public boolean execute(final Event e) { - boolean success = TriggerItem.walk(this, e); + public boolean execute(Event event) { + boolean success = TriggerItem.walk(this, event); + // Clear local variables - Variables.removeLocals(e); + Variables.removeLocals(event); /* * Local variables can be used in delayed effects by backing reference * of VariablesMap up. Basically: - * - * Object localVars = Variables.removeLocals(e); - * + * + * Object localVars = Variables.removeLocals(event); + * * ... and when you want to continue execution: - * - * Variables.setLocalVariables(e, localVars); - * + * + * Variables.setLocalVariables(event, localVars); + * * See Delay effect for reference. */ - + return success; } diff --git a/src/main/java/ch/njol/skript/sections/EffSecSpawn.java b/src/main/java/ch/njol/skript/sections/EffSecSpawn.java index 2f48ce2ffdd..878fe70d1db 100644 --- a/src/main/java/ch/njol/skript/sections/EffSecSpawn.java +++ b/src/main/java/ch/njol/skript/sections/EffSecSpawn.java @@ -44,18 +44,21 @@ import org.jetbrains.annotations.NotNull; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; -// TODO this won't show up in the docs, sections don't have a tab. We should create a tab for them, -// and maybe add EffectSections to the effects page as well @Name("Spawn") -@Description({"Spawn a creature. This can be used as an effect and as a section.", +@Description({ + "Spawn a creature. This can be used as an effect and as a section.", "If it is used as a section, the section is run before the entity is added to the world.", "You can modify the entity in this section, using for example 'event-entity' or 'cow'. ", - "Do note that other event values, such as 'player', won't work in this section."}) -@Examples({"spawn 3 creepers at the targeted block", + "Do note that other event values, such as 'player', won't work in this section." +}) +@Examples({ + "spawn 3 creepers at the targeted block", "spawn a ghast 5 meters above the player", "spawn a zombie at the player:", - "\tset name of the zombie to \"\""}) + "\tset name of the zombie to \"\"" +}) @Since("1.0, 2.6.1 (with section)") public class EffSecSpawn extends EffectSection { @@ -71,7 +74,8 @@ public Entity getEntity() { } @Override - public @NotNull HandlerList getHandlers() { + @NotNull + public HandlerList getHandlers() { throw new IllegalStateException(); } } @@ -79,17 +83,16 @@ public Entity getEntity() { static { Skript.registerSection(EffSecSpawn.class, "(spawn|summon) %entitytypes% [%directions% %locations%]", - "(spawn|summon) %number% of %entitytypes% [%directions% %locations%]"); + "(spawn|summon) %number% of %entitytypes% [%directions% %locations%]" + ); EventValues.registerEventValue(SpawnEvent.class, Entity.class, new Getter<Entity, SpawnEvent>() { @Override public Entity get(SpawnEvent spawnEvent) { return spawnEvent.getEntity(); } - }, 0); + }, EventValues.TIME_NOW); } - private static final boolean BUKKIT_CONSUMER_EXISTS = Skript.classExists("org.bukkit.util.Consumer"); - @Nullable public static Entity lastSpawned = null; @@ -116,12 +119,13 @@ public boolean init(Expression<?>[] exprs, locations = Direction.combine((Expression<? extends Direction>) exprs[1 + matchedPattern], (Expression<? extends Location>) exprs[2 + matchedPattern]); if (sectionNode != null) { - if (!BUKKIT_CONSUMER_EXISTS) { - Skript.error("The spawn section isn't available on your Minecraft version, use a spawn effect instead"); + AtomicBoolean delayed = new AtomicBoolean(false); + Runnable afterLoading = () -> delayed.set(!getParser().getHasDelayBefore().isFalse()); + trigger = loadCode(sectionNode, "spawn", afterLoading, SpawnEvent.class); + if (delayed.get()) { + Skript.error("Delays can't be used within a Spawn Effect Section"); return false; } - - trigger = loadCode(sectionNode, "spawn", SpawnEvent.class); } return true; @@ -130,10 +134,10 @@ public boolean init(Expression<?>[] exprs, @Override @Nullable @SuppressWarnings({"unchecked", "rawtypes"}) - protected TriggerItem walk(Event e) { + protected TriggerItem walk(Event event) { lastSpawned = null; - Object localVars = Variables.copyLocalVariables(e); + Object localVars = Variables.copyLocalVariables(event); Consumer<? extends Entity> consumer; if (trigger != null) { @@ -142,33 +146,40 @@ protected TriggerItem walk(Event e) { SpawnEvent spawnEvent = new SpawnEvent(o); // Copy the local variables from the calling code to this section Variables.setLocalVariables(spawnEvent, localVars); - trigger.execute(spawnEvent); + TriggerItem.walk(trigger, spawnEvent); + Variables.setLocalVariables(event, Variables.copyLocalVariables(spawnEvent)); + // Clear spawnEvent's local variables as it won't be done automatically + Variables.removeLocals(spawnEvent); }; } else { consumer = null; } - Number a = amount != null ? amount.getSingle(e) : 1; - if (a != null) { - EntityType[] ts = types.getArray(e); - for (Location l : locations.getArray(e)) { - for (EntityType type : ts) { - for (int i = 0; i < a.doubleValue() * type.getAmount(); i++) { - if (consumer != null) - type.data.spawn(l, (Consumer) consumer); // lastSpawned set within Consumer - else - lastSpawned = type.data.spawn(l); + Number numberAmount = amount != null ? amount.getSingle(event) : 1; + if (numberAmount != null) { + double amount = numberAmount.doubleValue(); + EntityType[] types = this.types.getArray(event); + for (Location location : locations.getArray(event)) { + for (EntityType type : types) { + double typeAmount = amount * type.getAmount(); + for (int i = 0; i < typeAmount; i++) { + if (consumer != null) { + type.data.spawn(location, (Consumer) consumer); // lastSpawned set within Consumer + } else { + lastSpawned = type.data.spawn(location); + } } } } } - return super.walk(e, false); + return super.walk(event, false); } @Override - public String toString(@Nullable Event e, boolean debug) { - return "spawn " + (amount != null ? amount.toString(e, debug) + " of " : "") + types.toString(e, debug) + " " + locations.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "spawn " + (amount != null ? amount.toString(event, debug) + " of " : "") + + types.toString(event, debug) + " " + locations.toString(event, debug); } } diff --git a/src/test/skript/tests/syntaxes/sections/EffSecSpawn.sk b/src/test/skript/tests/syntaxes/sections/EffSecSpawn.sk index ddbe29961a8..f151f09821b 100644 --- a/src/test/skript/tests/syntaxes/sections/EffSecSpawn.sk +++ b/src/test/skript/tests/syntaxes/sections/EffSecSpawn.sk @@ -1,5 +1,12 @@ -test "spawn section" when minecraft version is not "1.9.4": +test "spawn section": + set {_before} to 5 spawn a pig at spawn of "world": + + # Make sure variables carry over properly + assert {_before} is 5 with "value of {_before} should be 5 (got '%{_before}%')" + add 5 to {_before} + set {_new var} to 5 + assert event-entity is a pig with "entity not a pig" set {_test} to event-entity assert {_test} is set with "entity not set" @@ -7,3 +14,9 @@ test "spawn section" when minecraft version is not "1.9.4": set {_location} to event-location assert {_location} is set with "location not set" assert y-coord of spawn of "world" is y-coord of {_location} with "location y coordinate not right" + + delete the last spawned pig + + # Make sure variables carry over properly + assert {_before} is 10 with "value of {_before} should be 10 (got '%{_before}%')" + assert {_new var} is 5 with "value of {_new var} should be 5 (got '%{_new var}%')" From 9f6ce128b422203df328147825a7dff1561b2261 Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Thu, 29 Sep 2022 08:38:25 +0300 Subject: [PATCH 100/619] ExprJoinSplit config case sensitivity (#5066) * add case sensitivity pattern to syntax and cleanup class --- .../skript/expressions/ExprJoinSplit.java | 56 +++++++++++-------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprJoinSplit.java b/src/main/java/ch/njol/skript/expressions/ExprJoinSplit.java index b0089d870ae..207a3ab70de 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprJoinSplit.java +++ b/src/main/java/ch/njol/skript/expressions/ExprJoinSplit.java @@ -20,6 +20,7 @@ import java.util.regex.Pattern; +import ch.njol.skript.SkriptConfig; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @@ -40,65 +41,72 @@ */ @Name("Join & Split") @Description("Joins several texts with a common delimiter (e.g. \", \"), or splits a text into multiple texts at a given delimiter.") -@Examples({"message \"Online players: %join all players with \"\" | \"\"%\" # %all players% would use the default \"x, y, and z\"", - "set {_s::*} to the string argument split at \",\""}) -@Since("2.1, 2.5.2 (regex support)") +@Examples({ + "message \"Online players: %join all players with \"\" | \"\"%\" # %all players% would use the default \"x, y, and z\"", + "set {_s::*} to the string argument split at \",\"" +}) +@Since("2.1, 2.5.2 (regex support), INSERT VERSION (case sensitivity)") public class ExprJoinSplit extends SimpleExpression<String> { - + static { Skript.registerExpression(ExprJoinSplit.class, String.class, ExpressionType.COMBINED, "(concat[enate]|join) %strings% [(with|using|by) [[the] delimiter] %-string%]", - "split %string% (at|using|by) [[the] delimiter] %string%", - "%string% split (at|using|by) [[the] delimiter] %string%", + "split %string% (at|using|by) [[the] delimiter] %string% [case:with case sensitivity]", + "%string% split (at|using|by) [[the] delimiter] %string% [case:with case sensitivity]", "regex split %string% (at|using|by) [[the] delimiter] %string%", "regex %string% split (at|using|by) [[the] delimiter] %string%"); } - + private boolean join; private boolean regex; - + private boolean caseSensitivity; + @SuppressWarnings("null") private Expression<String> strings; @Nullable private Expression<String> delimiter; - - @SuppressWarnings({"unchecked", "null"}) + @Override - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { + @SuppressWarnings({"unchecked", "null"}) + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { join = matchedPattern == 0; regex = matchedPattern >= 3; + caseSensitivity = SkriptConfig.caseSensitive.value() || parseResult.hasTag("case"); strings = (Expression<String>) exprs[0]; delimiter = (Expression<String>) exprs[1]; return true; } - + @Override @Nullable - protected String[] get(final Event e) { - final String[] s = strings.getArray(e); - final String d = delimiter != null ? delimiter.getSingle(e) : ""; - if (s.length == 0 || d == null) + protected String[] get(Event event) { + String[] strings = this.strings.getArray(event); + String delimiter = this.delimiter != null ? this.delimiter.getSingle(event) : ""; + if (strings.length == 0 || delimiter == null) return new String[0]; if (join) { - return new String[] {StringUtils.join(s, d)}; + return new String[] {StringUtils.join(strings, delimiter)}; } else { - return s[0].split(regex ? d : Pattern.quote(d), -1); + return strings[0].split(regex ? delimiter : (caseSensitivity ? "" : "(?i)") + Pattern.quote(delimiter), -1); } } - + @Override public boolean isSingle() { return join; } - + @Override public Class<? extends String> getReturnType() { return String.class; } - + @Override - public String toString(final @Nullable Event e, final boolean debug) { - return join ? "join " + strings.toString(e, debug) + (delimiter != null ? " with " + delimiter.toString(e, debug) : "") : ((regex ? "regex " : "") + "split " + strings.toString(e, debug) + (delimiter != null ? " at " + delimiter.toString(e, debug) : "")); + public String toString(@Nullable Event event, boolean debug) { + if (join) + return "join " + strings.toString(event, debug) + (delimiter != null ? " with " + delimiter.toString(event, debug) : ""); + return (regex ? "regex " : "") + "split " + strings.toString(event, debug) + (delimiter != null ? " at " + delimiter.toString(event, debug) : "") + + (regex ? "" : "(case sensitive: " + caseSensitivity + ")"); } - + } From 4cf9918367d9b2c1a98437aff3c6b7a8576aafbf Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Sat, 1 Oct 2022 02:33:56 +0300 Subject: [PATCH 101/619] Fix falling block NPE (#5057) --- src/main/java/ch/njol/skript/entity/FallingBlockData.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/entity/FallingBlockData.java b/src/main/java/ch/njol/skript/entity/FallingBlockData.java index 66e2c9378f6..f911133d415 100644 --- a/src/main/java/ch/njol/skript/entity/FallingBlockData.java +++ b/src/main/java/ch/njol/skript/entity/FallingBlockData.java @@ -52,7 +52,7 @@ public class FallingBlockData extends EntityData<FallingBlock> { private final static Message m_not_a_block_error = new Message("entities.falling block.not a block error"); private final static Adjective m_adjective = new Adjective("entities.falling block.adjective"); - + @Nullable private ItemType[] types = null; @@ -88,6 +88,8 @@ public ItemType convert(ItemType t) { Skript.error(m_not_a_block_error.toString()); return false; } + } else { + types = new ItemType[] {new ItemType(Material.STONE)}; } return true; } From 877cb294187f00e409a344f75fe695ea74957301 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Sat, 1 Oct 2022 13:40:26 -0400 Subject: [PATCH 102/619] Improve command expressions for command events and script commands (#4087) --- .../ch/njol/skript/command/ScriptCommand.java | 2 +- .../skript/command/ScriptCommandEvent.java | 45 ++- .../njol/skript/expressions/ExprArgument.java | 308 ++++++++++++------ .../expressions/ExprCmdCooldownInfo.java | 4 +- .../njol/skript/expressions/ExprCommand.java | 47 +-- 5 files changed, 267 insertions(+), 139 deletions(-) diff --git a/src/main/java/ch/njol/skript/command/ScriptCommand.java b/src/main/java/ch/njol/skript/command/ScriptCommand.java index 791472d0b78..83c93ebc9ee 100644 --- a/src/main/java/ch/njol/skript/command/ScriptCommand.java +++ b/src/main/java/ch/njol/skript/command/ScriptCommand.java @@ -209,7 +209,7 @@ public boolean execute(final CommandSender sender, final String commandLabel, fi } } - final ScriptCommandEvent event = new ScriptCommandEvent(ScriptCommand.this, sender); + final ScriptCommandEvent event = new ScriptCommandEvent(ScriptCommand.this, sender, commandLabel, rest); if (!permission.isEmpty() && !sender.hasPermission(permission)) { if (sender instanceof Player) { diff --git a/src/main/java/ch/njol/skript/command/ScriptCommandEvent.java b/src/main/java/ch/njol/skript/command/ScriptCommandEvent.java index 0ba5c5d0149..91b8f16567c 100644 --- a/src/main/java/ch/njol/skript/command/ScriptCommandEvent.java +++ b/src/main/java/ch/njol/skript/command/ScriptCommandEvent.java @@ -25,23 +25,46 @@ import org.bukkit.event.HandlerList; public class ScriptCommandEvent extends CommandEvent { - - private final ScriptCommand skriptCommand; + + private final ScriptCommand scriptCommand; + private final String commandLabel; + private final String rest; private final Date executionDate = new Date(); private boolean cooldownCancelled; - public ScriptCommandEvent(ScriptCommand command, CommandSender sender) { - super(sender, command.getLabel(), null); - skriptCommand = command; + /** + * @param scriptCommand The script command executed. + * @param sender The executor of this script command. + * @param commandLabel The command name (may be the used alias) + * @param rest The rest of the command string (the arguments) + */ + public ScriptCommandEvent(ScriptCommand scriptCommand, CommandSender sender, String commandLabel, String rest) { + super(sender, scriptCommand.getLabel(), rest.split(" ")); + this.scriptCommand = scriptCommand; + this.commandLabel = commandLabel; + this.rest = rest; } - public ScriptCommand getSkriptCommand() { - return skriptCommand; + /** + * @return The script command executed. + */ + public ScriptCommand getScriptCommand() { + return scriptCommand; } - @Override - public String[] getArgs() { - throw new UnsupportedOperationException(); + /** + * @return The used command label. This may be a command alias. + */ + public String getCommandLabel() { + return commandLabel; + } + + /** + * @return The arguments combined into one string. + * @see CommandEvent#getArgs() + */ + public String getArgsString() { + return rest; } /** @@ -56,7 +79,7 @@ public void setCooldownCancelled(boolean cooldownCancelled) { CommandSender sender = getSender(); if (sender instanceof Player) { Date date = cooldownCancelled ? null : executionDate; - skriptCommand.setLastUsage(((Player) sender).getUniqueId(), this, date); + scriptCommand.setLastUsage(((Player) sender).getUniqueId(), this, date); } } else { this.cooldownCancelled = cooldownCancelled; diff --git a/src/main/java/ch/njol/skript/expressions/ExprArgument.java b/src/main/java/ch/njol/skript/expressions/ExprArgument.java index e8eaf7457a7..60d545650f1 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprArgument.java +++ b/src/main/java/ch/njol/skript/expressions/ExprArgument.java @@ -22,6 +22,8 @@ import java.util.regex.MatchResult; import org.bukkit.event.Event; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; +import org.bukkit.event.server.ServerCommandEvent; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; @@ -44,148 +46,250 @@ import ch.njol.util.Kleenean; import ch.njol.util.StringUtils; -/** - * @author Peter Güttinger - */ @Name("Argument") -@Description({"Only usable in command events. Holds the value of an argument given to the command, " + - "e.g. if the command \"/tell <player> <text>\" is used like \"/tell Njol Hello Njol!\" argument 1 is the player named \"Njol\" and argument 2 is \"Hello Njol!\".", - "One can also use the type of the argument instead of its index to address the argument, e.g. in the above example 'player-argument' is the same as 'argument 1'."}) -@Examples({"give the item-argument to the player-argument", - "damage the player-argument by the number-argument", - "give a diamond pickaxe to the argument", - "add argument 1 to argument 2", - "heal the last argument"}) -@Since("1.0") +@Description({ + "Usable in script commands and command events. Holds the value of an argument given to the command, " + + "e.g. if the command \"/tell <player> <text>\" is used like \"/tell Njol Hello Njol!\" argument 1 is the player named \"Njol\" and argument 2 is \"Hello Njol!\".", + "One can also use the type of the argument instead of its index to address the argument, e.g. in the above example 'player-argument' is the same as 'argument 1'.", + "Please note that specifying the argument type is only supported in script commands." +}) +@Examples({ + "give the item-argument to the player-argument", + "damage the player-argument by the number-argument", + "give a diamond pickaxe to the argument", + "add argument 1 to argument 2", + "heal the last argument" +}) +@Since("1.0, INSERT VERSION (support for command events)") public class ExprArgument extends SimpleExpression<Object> { + static { Skript.registerExpression(ExprArgument.class, Object.class, ExpressionType.SIMPLE, - "[the] last arg[ument][s]", - "[the] arg[ument][s](-| )<(\\d+)>", "[the] <(\\d*1)st|(\\d*2)nd|(\\d*3)rd|(\\d*[4-90])th> arg[ument][s]", - "[the] arg[ument][s]", - "[the] %*classinfo%( |-)arg[ument][( |-)<\\d+>]", "[the] arg[ument]( |-)%*classinfo%[( |-)<\\d+>]"); + "[the] last arg[ument]", // LAST + "[the] arg[ument](-| )<(\\d+)>", // ORDINAL + "[the] <(\\d*1)st|(\\d*2)nd|(\\d*3)rd|(\\d*[4-90])th> arg[ument][s]", // ORDINAL + "[(all [[of] the]|the)] arg[ument][(1:s)]", // SINGLE OR ALL + "[the] %*classinfo%( |-)arg[ument][( |-)<\\d+>]", // CLASSINFO + "[the] arg[ument]( |-)%*classinfo%[( |-)<\\d+>]" // CLASSINFO + ); } - - @SuppressWarnings("null") - private Argument<?> arg; + + private static final int LAST = 0, ORDINAL = 1, SINGLE = 2, ALL = 3, CLASSINFO = 4; + private int what; + + @Nullable + private Argument<?> argument; + + private int ordinal = -1; // Available in ORDINAL and sometimes CLASSINFO @Override - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parser) { - final List<Argument<?>> currentArguments = Commands.currentArguments; - if (currentArguments == null || !getParser().isCurrentEvent(ScriptCommandEvent.class)) { - Skript.error("The expression 'argument' can only be used within a command", ErrorQuality.SEMANTIC_ERROR); + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + boolean scriptCommand = getParser().isCurrentEvent(ScriptCommandEvent.class); + if (!scriptCommand && !getParser().isCurrentEvent(PlayerCommandPreprocessEvent.class, ServerCommandEvent.class)) { + Skript.error("The 'argument' expression can only be used in a script command or command event"); return false; } - if (currentArguments.size() == 0) { - Skript.error("This command doesn't have any arguments", ErrorQuality.SEMANTIC_ERROR); - return false; - } - Argument<?> arg = null; + switch (matchedPattern) { case 0: - arg = currentArguments.get(currentArguments.size() - 1); + what = LAST; break; case 1: case 2: - // Figure out in which format (1st, 2nd, 3rd, Nth) argument was given in - MatchResult regex = parser.regexes.get(0); - String argMatch = null; - for (int i = 1; i <= 4; i++) { - argMatch = regex.group(i); - if (argMatch != null) { - break; // Found format - } - } - assert argMatch != null; - int i = Utils.parseInt(argMatch); - - if (i > currentArguments.size()) { - Skript.error("The command doesn't have a " + StringUtils.fancyOrderNumber(i) + " argument", ErrorQuality.SEMANTIC_ERROR); - return false; - } else if (i < 1) { - Skript.error("Command arguments start from one; argument number " + i + " is invalid", ErrorQuality.SEMANTIC_ERROR); - return false; - } - arg = currentArguments.get(i - 1); + what = ORDINAL; break; case 3: - if (currentArguments.size() == 1) { - arg = currentArguments.get(0); - } else { - Skript.error("'argument(s)' cannot be used if the command has multiple arguments. Use 'argument 1', 'argument 2', etc. instead", ErrorQuality.SEMANTIC_ERROR); - return false; - } + what = parseResult.mark == 1 ? ALL : SINGLE; break; case 4: case 5: - @SuppressWarnings("unchecked") - final ClassInfo<?> c = ((Literal<ClassInfo<?>>) exprs[0]).getSingle(); - @SuppressWarnings("null") - final int num = parser.regexes.size() > 0 ? Utils.parseInt(parser.regexes.get(0).group()) : -1; - int j = 1; - for (final Argument<?> a : currentArguments) { - if (!c.getC().isAssignableFrom(a.getType())) - continue; - if (arg != null) { - Skript.error("There are multiple " + c + " arguments in this command", ErrorQuality.SEMANTIC_ERROR); + what = CLASSINFO; + break; + default: + assert false; + } + + if (!scriptCommand && what == CLASSINFO) { + Skript.error("Command event arguments are strings, meaning type specification is useless"); + return false; + } + + List<Argument<?>> currentArguments = Commands.currentArguments; + if (scriptCommand && (currentArguments == null || currentArguments.isEmpty())) { + Skript.error("This command doesn't have any arguments", ErrorQuality.SEMANTIC_ERROR); + return false; + } + + if (what == ORDINAL) { + // Figure out in which format (1st, 2nd, 3rd, Nth) argument was given in + MatchResult regex = parseResult.regexes.get(0); + String argMatch = null; + for (int i = 1; i <= 4; i++) { + argMatch = regex.group(i); + if (argMatch != null) { + break; // Found format + } + } + assert argMatch != null; + ordinal = Utils.parseInt(argMatch); + if (scriptCommand && ordinal > currentArguments.size()) { // Only check if it's a script command as we know nothing of command event arguments + Skript.error("This command doesn't have a " + StringUtils.fancyOrderNumber(ordinal) + " argument", ErrorQuality.SEMANTIC_ERROR); + return false; + } + } + + if (scriptCommand) { // Handle before execution + switch (what) { + case LAST: + argument = currentArguments.get(currentArguments.size() - 1); + break; + case ORDINAL: + argument = currentArguments.get(ordinal - 1); + break; + case SINGLE: + if (currentArguments.size() == 1) { + argument = currentArguments.get(0); + } else { + Skript.error("This command has multiple arguments, meaning it is not possible to get the 'argument'. Use 'argument 1', 'argument 2', etc. instead", ErrorQuality.SEMANTIC_ERROR); return false; } - if (j < num) { - j++; - continue; + break; + case ALL: + Skript.error("'arguments' cannot be used for script commands. Use 'argument 1', 'argument 2', etc. instead", ErrorQuality.SEMANTIC_ERROR); + return false; + case CLASSINFO: + ClassInfo<?> c = ((Literal<ClassInfo<?>>) exprs[0]).getSingle(); + if (parseResult.regexes.size() > 0) { + ordinal = Utils.parseInt(parseResult.regexes.get(0).group()); + if (ordinal > currentArguments.size()) { + Skript.error("This command doesn't have a " + StringUtils.fancyOrderNumber(ordinal) + " " + c + " argument", ErrorQuality.SEMANTIC_ERROR); + return false; + } } - arg = a; - if (j == num) - break; - } - if (arg == null) { - j--; - if (num == -1 || j == 0) + + Argument<?> arg = null; + int argAmount = 0; + for (Argument<?> a : currentArguments) { + if (!c.getC().isAssignableFrom(a.getType())) // This argument is not of the required type + continue; + + if (ordinal == -1 && argAmount == 2) { // The user said '<type> argument' without specifying which, and multiple arguments for the type exist + Skript.error("There are multiple " + c + " arguments in this command", ErrorQuality.SEMANTIC_ERROR); + return false; + } + + arg = a; + + argAmount++; + if (argAmount == ordinal) { // There is argNum argument for the required type (ex: "string argument 2" would exist) + break; + } + } + + if (argAmount == 0) { Skript.error("There is no " + c + " argument in this command", ErrorQuality.SEMANTIC_ERROR); - else if (j == 1) - Skript.error("There is only one " + c + " argument in this command", ErrorQuality.SEMANTIC_ERROR); - else - Skript.error("There are only " + j + " " + c + " arguments in this command", ErrorQuality.SEMANTIC_ERROR); + return false; + } else if (ordinal > argAmount) { // The user wanted an argument number that didn't exist for the given type + if (argAmount == 1) { + Skript.error("There is only one " + c + " argument in this command", ErrorQuality.SEMANTIC_ERROR); + } else { + Skript.error("There are only " + argAmount + " " + c + " arguments in this command", ErrorQuality.SEMANTIC_ERROR); + } + return false; + } + + // 'arg' will never be null here + argument = arg; + break; + default: + assert false : what; return false; - } - break; - default: - assert false : matchedPattern; - return false; + } } - assert arg != null; - this.arg = arg; + return true; } @Override @Nullable protected Object[] get(final Event e) { - if (!(e instanceof ScriptCommandEvent)) - return null; - return arg.getCurrent(e); + if (argument != null) { + return argument.getCurrent(e); + } + + String fullCommand; + if (e instanceof PlayerCommandPreprocessEvent) { + fullCommand = ((PlayerCommandPreprocessEvent) e).getMessage().substring(1).trim(); + } else if (e instanceof ServerCommandEvent) { // It's a ServerCommandEvent then + fullCommand = ((ServerCommandEvent) e).getCommand().trim(); + } else { + return new Object[0]; + } + + String[] arguments; + int firstSpace = fullCommand.indexOf(' '); + if (firstSpace != -1) { + fullCommand = fullCommand.substring(firstSpace + 1); + arguments = fullCommand.split(" "); + } else { // No arguments, just the command + return new String[0]; + } + + switch (what) { + case LAST: + if (arguments.length > 0) + return new String[]{arguments[arguments.length - 1]}; + break; + case ORDINAL: + if (arguments.length >= ordinal) + return new String[]{arguments[ordinal - 1]}; + break; + case SINGLE: + if (arguments.length == 1) + return new String[]{arguments[arguments.length - 1]}; + break; + case ALL: + return arguments; + } + + return new Object[0]; } - + @Override - public Class<? extends Object> getReturnType() { - return arg.getType(); + public boolean isSingle() { + return argument != null ? argument.isSingle() : what != ALL; } @Override - public String toString(final @Nullable Event e, final boolean debug) { - if (e == null) - return "the " + StringUtils.fancyOrderNumber(arg.getIndex() + 1) + " argument"; - return Classes.getDebugMessage(getArray(e)); + public Class<?> getReturnType() { + return argument != null ? argument.getType() : String.class; } - + @Override - public boolean isSingle() { - return arg.isSingle(); + public boolean isLoopOf(String s) { + return s.equalsIgnoreCase("argument"); } - + @Override - public boolean isLoopOf(final String s) { - return s.equalsIgnoreCase("argument"); + public String toString(@Nullable Event e, boolean debug) { + switch (what) { + case LAST: + return "the last argument"; + case ORDINAL: + return "the " + StringUtils.fancyOrderNumber(ordinal) + " argument"; + case SINGLE: + return "the argument"; + case ALL: + return "the arguments"; + case CLASSINFO: + assert argument != null; + ClassInfo<?> ci = Classes.getExactClassInfo(argument.getType()); + assert ci != null; // If it was null, that would be very bad + return "the " + ci + " argument " + (ordinal != -1 ? ordinal : ""); // Add the argument number if the user gave it before + default: + return "argument"; + } } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprCmdCooldownInfo.java b/src/main/java/ch/njol/skript/expressions/ExprCmdCooldownInfo.java index 23a000301a7..c6a6a40d1b1 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprCmdCooldownInfo.java +++ b/src/main/java/ch/njol/skript/expressions/ExprCmdCooldownInfo.java @@ -81,7 +81,7 @@ protected Object[] get(Event e) { if (!(e instanceof ScriptCommandEvent)) return null; ScriptCommandEvent event = ((ScriptCommandEvent) e); - ScriptCommand scriptCommand = event.getSkriptCommand(); + ScriptCommand scriptCommand = event.getScriptCommand(); CommandSender sender = event.getSender(); if (scriptCommand.getCooldown() == null || !(sender instanceof Player)) @@ -133,7 +133,7 @@ public void change(Event e, @Nullable Object[] delta, Changer.ChangeMode mode) { if (!(e instanceof ScriptCommandEvent)) return; ScriptCommandEvent commandEvent = (ScriptCommandEvent) e; - ScriptCommand command = commandEvent.getSkriptCommand(); + ScriptCommand command = commandEvent.getScriptCommand(); Timespan cooldown = command.getCooldown(); CommandSender sender = commandEvent.getSender(); if (cooldown == null || !(sender instanceof Player)) diff --git a/src/main/java/ch/njol/skript/expressions/ExprCommand.java b/src/main/java/ch/njol/skript/expressions/ExprCommand.java index b5a0e5b9f36..f7cfd7b3a78 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprCommand.java +++ b/src/main/java/ch/njol/skript/expressions/ExprCommand.java @@ -18,6 +18,7 @@ */ package ch.njol.skript.expressions; +import ch.njol.skript.command.ScriptCommandEvent; import org.bukkit.event.Event; import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.event.server.ServerCommandEvent; @@ -46,25 +47,26 @@ "\t\tif the command is not \"exit\":", "\t\t\tmessage \"You're not allowed to use commands during the game\"", "\t\t\tcancel the event"}) -@Since("2.0") +@Since("2.0, INSERT VERSION (support for script commands)") @Events("command") public class ExprCommand extends SimpleExpression<String> { + static { Skript.registerExpression(ExprCommand.class, String.class, ExpressionType.SIMPLE, - "[the] (full|complete|whole) command", "[the] command [label]", "[the] arguments"); + "[the] (full|complete|whole) command", + "[the] command [(label|alias)]" + ); } - - private final static int FULL = 0, LABEL = 1, ARGS = 2; - private int what; + + private boolean fullCommand; @Override - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { - what = matchedPattern; - if (!getParser().isCurrentEvent(PlayerCommandPreprocessEvent.class, ServerCommandEvent.class)) { - if (what != ARGS) // ExprArgument has the same syntax - Skript.error("The 'command' expression can only be used in a command event"); + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(PlayerCommandPreprocessEvent.class, ServerCommandEvent.class, ScriptCommandEvent.class)) { + Skript.error("The 'command' expression can only be used in a script command or command event"); return false; } + fullCommand = matchedPattern == 0; return true; } @@ -72,23 +74,22 @@ public boolean init(final Expression<?>[] exprs, final int matchedPattern, final @Nullable protected String[] get(final Event e) { final String s; + if (e instanceof PlayerCommandPreprocessEvent) { s = ((PlayerCommandPreprocessEvent) e).getMessage().substring(1).trim(); } else if (e instanceof ServerCommandEvent) { s = ((ServerCommandEvent) e).getCommand().trim(); - } else { - return new String[0]; + } else { // It's a script command event + ScriptCommandEvent event = (ScriptCommandEvent) e; + s = event.getCommandLabel() + " " + event.getArgsString(); } - if (what == FULL) - return new String[] {s}; - final int c = s.indexOf(' '); - if (what == ARGS) { - if (c == -1) - return new String[0]; - return new String[] {s.substring(c + 1).trim()}; + + if (fullCommand) { + return new String[]{s}; + } else { + int c = s.indexOf(' '); + return new String[] {c == -1 ? s : s.substring(0, c)}; } - assert what == LABEL; - return new String[] {c == -1 ? s : s.substring(0, c)}; } @Override @@ -102,8 +103,8 @@ public Class<? extends String> getReturnType() { } @Override - public String toString(final @Nullable Event e, final boolean debug) { - return what == 0 ? "the full command" : what == 1 ? "the command" : "the arguments"; + public String toString(@Nullable Event e, boolean debug) { + return fullCommand ? "the full command" : "the command"; } } From 3a4cc8efd0b935b91978ea47cb4ff091096f12b0 Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Sun, 2 Oct 2022 18:05:12 +0200 Subject: [PATCH 103/619] Allow parse tags to be expanded onto optional pattern elements (#5089) --- .../patterns/OptionalPatternElement.java | 4 +++ .../patterns/ParseTagPatternElement.java | 34 ++++++++++++------- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/main/java/ch/njol/skript/patterns/OptionalPatternElement.java b/src/main/java/ch/njol/skript/patterns/OptionalPatternElement.java index e07a61b0e9f..55c0456d90a 100644 --- a/src/main/java/ch/njol/skript/patterns/OptionalPatternElement.java +++ b/src/main/java/ch/njol/skript/patterns/OptionalPatternElement.java @@ -46,6 +46,10 @@ public MatchResult match(String expr, MatchResult matchResult) { return matchNext(expr, matchResult); } + public PatternElement getPatternElement() { + return patternElement; + } + @Override public String toString() { return "[" + patternElement.toFullString() + "]"; diff --git a/src/main/java/ch/njol/skript/patterns/ParseTagPatternElement.java b/src/main/java/ch/njol/skript/patterns/ParseTagPatternElement.java index d8807549d32..bb53a39b5f7 100644 --- a/src/main/java/ch/njol/skript/patterns/ParseTagPatternElement.java +++ b/src/main/java/ch/njol/skript/patterns/ParseTagPatternElement.java @@ -51,18 +51,28 @@ void setNext(@Nullable PatternElement next) { if (next instanceof LiteralPatternElement) { // (:a) tag = next.toString().trim(); - } else if (next instanceof GroupPatternElement && ((GroupPatternElement) next).getPatternElement() instanceof ChoicePatternElement) { - // :(a|b) - ChoicePatternElement choicePatternElement = (ChoicePatternElement) ((GroupPatternElement) next).getPatternElement(); - List<PatternElement> patternElements = choicePatternElement.getPatternElements(); - for (int i = 0; i < patternElements.size(); i++) { - PatternElement patternElement = patternElements.get(i); - // Prevent a pattern such as :(a|b|) from being turned into (a:a|b:b|:), instead (a:a|b:b|) - if (patternElement instanceof LiteralPatternElement && !patternElement.toString().isEmpty()) { - ParseTagPatternElement newTag = new ParseTagPatternElement(patternElement.toString().trim()); - newTag.setNext(patternElement); - newTag.originalNext = patternElement; - patternElements.set(i, newTag); + } else { + // Get the inner element from either a group or optional pattern element + PatternElement inner = null; + if (next instanceof GroupPatternElement) { + inner = ((GroupPatternElement) next).getPatternElement(); + } else if (next instanceof OptionalPatternElement) { + inner = ((OptionalPatternElement) next).getPatternElement(); + } + + if (inner instanceof ChoicePatternElement) { + // :(a|b) or :[a|b] + ChoicePatternElement choicePatternElement = (ChoicePatternElement) inner; + List<PatternElement> patternElements = choicePatternElement.getPatternElements(); + for (int i = 0; i < patternElements.size(); i++) { + PatternElement patternElement = patternElements.get(i); + // Prevent a pattern such as :(a|b|) from being turned into (a:a|b:b|:), instead (a:a|b:b|) + if (patternElement instanceof LiteralPatternElement && !patternElement.toString().isEmpty()) { + ParseTagPatternElement newTag = new ParseTagPatternElement(patternElement.toString().trim()); + newTag.setNext(patternElement); + newTag.originalNext = patternElement; + patternElements.set(i, newTag); + } } } } From 43449100093c256bb6aaf1cb08ee6ad4527a395e Mon Sep 17 00:00:00 2001 From: _Mads <75088349+TFSMads@users.noreply.github.com> Date: Sun, 2 Oct 2022 19:56:52 +0200 Subject: [PATCH 104/619] Fixed only highest event super class getting registered. (#4797) --- .../ch/njol/skript/SkriptEventHandler.java | 202 +++++++++++------- .../ch/njol/skript/events/EvtCommand.java | 11 +- 2 files changed, 133 insertions(+), 80 deletions(-) diff --git a/src/main/java/ch/njol/skript/SkriptEventHandler.java b/src/main/java/ch/njol/skript/SkriptEventHandler.java index 1c363fb0d0f..c3782e498c7 100644 --- a/src/main/java/ch/njol/skript/SkriptEventHandler.java +++ b/src/main/java/ch/njol/skript/SkriptEventHandler.java @@ -18,34 +18,38 @@ */ package ch.njol.skript; -import java.io.File; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; - +import ch.njol.skript.ScriptLoader.ScriptInfo; +import ch.njol.skript.command.Commands; +import ch.njol.skript.lang.SelfRegisteringSkriptEvent; +import ch.njol.skript.lang.Trigger; +import ch.njol.skript.timings.SkriptTimings; +import ch.njol.util.NonNullPair; import org.bukkit.Bukkit; import org.bukkit.event.Cancellable; import org.bukkit.event.Event; import org.bukkit.event.Event.Result; import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import org.bukkit.event.block.Action; import org.bukkit.event.player.PlayerArmorStandManipulateEvent; import org.bukkit.event.player.PlayerInteractAtEntityEvent; import org.bukkit.event.player.PlayerInteractEntityEvent; import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.event.server.ServerCommandEvent; import org.bukkit.plugin.EventExecutor; +import org.bukkit.plugin.RegisteredListener; import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.ScriptLoader.ScriptInfo; -import ch.njol.skript.command.Commands; -import ch.njol.skript.lang.SelfRegisteringSkriptEvent; -import ch.njol.skript.lang.Trigger; -import ch.njol.skript.timings.SkriptTimings; -import ch.njol.util.NonNullPair; +import java.io.File; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; /** * @author Peter Güttinger @@ -58,30 +62,24 @@ public abstract class SkriptEventHandler { * the {@link EventExecutor} to be used with this listener. */ public static class PriorityListener implements Listener { - + public final EventPriority priority; - public final Set<Class<? extends Event>> registeredEvents = new HashSet<>(); - - @Nullable - private Event lastEvent; + public final EventExecutor executor = (listener, event) -> { - if (lastEvent == event) // an event is received multiple times if multiple superclasses of it are registered - return; - lastEvent = event; check(event, ((PriorityListener) listener).priority); }; - + public PriorityListener(EventPriority priority) { this.priority = priority; } - + } - + /** * Stores one {@link PriorityListener} per {@link EventPriority} */ private static final PriorityListener[] listeners; - + static { EventPriority[] priorities = EventPriority.values(); listeners = new PriorityListener[priorities.length]; @@ -89,23 +87,25 @@ public PriorityListener(EventPriority priority) { listeners[i] = new PriorityListener(priorities[i]); } } - + private static final List<NonNullPair<Class<? extends Event>, Trigger>> triggers = new ArrayList<>(); - + private static final List<Trigger> selfRegisteredTriggers = new ArrayList<>(); - + private static Iterator<Trigger> getTriggers(Class<? extends Event> event) { + HandlerList eventHandlerList = getHandlerList(event); + return new ArrayList<>(triggers).stream() - .filter(pair -> pair.getFirst().isAssignableFrom(event)) + .filter(pair -> pair.getFirst().isAssignableFrom(event) && eventHandlerList == getHandlerList(pair.getFirst())) .map(NonNullPair::getSecond) .iterator(); } - + private static void check(Event e, EventPriority priority) { Iterator<Trigger> ts = getTriggers(e.getClass()); if (!ts.hasNext()) return; - + if (Skript.logVeryHigh()) { boolean hasTrigger = false; while (ts.hasNext()) { @@ -119,37 +119,38 @@ private static void check(Event e, EventPriority priority) { return; Class<? extends Event> c = e.getClass(); ts = getTriggers(c); - + logEventStart(e); } - if (e instanceof Cancellable && ((Cancellable) e).isCancelled() && !listenCancelled.contains(e.getClass()) && - !(e instanceof PlayerInteractEvent && (((PlayerInteractEvent) e).getAction() == Action.LEFT_CLICK_AIR || ((PlayerInteractEvent) e).getAction() == Action.RIGHT_CLICK_AIR) && ((PlayerInteractEvent) e).useItemInHand() != Result.DENY) - || e instanceof ServerCommandEvent && (((ServerCommandEvent) e).getCommand().isEmpty() || ((ServerCommandEvent) e).isCancelled())) { + boolean isCancelled = e instanceof Cancellable && ((Cancellable) e).isCancelled() && !listenCancelled.contains(e.getClass()); + boolean isResultDeny = !(e instanceof PlayerInteractEvent && (((PlayerInteractEvent) e).getAction() == Action.LEFT_CLICK_AIR || ((PlayerInteractEvent) e).getAction() == Action.RIGHT_CLICK_AIR) && ((PlayerInteractEvent) e).useItemInHand() != Result.DENY); + + if (isCancelled && isResultDeny) { if (Skript.logVeryHigh()) Skript.info(" -x- was cancelled"); return; } - + while (ts.hasNext()) { Trigger t = ts.next(); if (t.getEvent().getEventPriority() != priority || !t.getEvent().check(e)) continue; - + logTriggerStart(t); Object timing = SkriptTimings.start(t.getDebugLabel()); - + t.execute(e); - + SkriptTimings.stop(timing); logTriggerEnd(t); } - + logEventEnd(); } - + private static long startEvent; - + public static void logEventStart(Event e) { startEvent = System.nanoTime(); if (!Skript.logVeryHigh()) @@ -157,22 +158,22 @@ public static void logEventStart(Event e) { Skript.info(""); Skript.info("== " + e.getClass().getName() + " =="); } - + public static void logEventEnd() { if (!Skript.logVeryHigh()) return; Skript.info("== took " + 1. * (System.nanoTime() - startEvent) / 1000000. + " milliseconds =="); } - + private static long startTrigger; - + public static void logTriggerStart(Trigger t) { startTrigger = System.nanoTime(); if (!Skript.logVeryHigh()) return; Skript.info("# " + t.getName()); } - + public static void logTriggerEnd(Trigger t) { if (!Skript.logVeryHigh()) return; @@ -184,25 +185,25 @@ public static void addTrigger(Class<? extends Event>[] events, Trigger trigger) triggers.add(new NonNullPair<>(e, trigger)); } } - + /** * Stores a self registered trigger to allow for it to be unloaded later on. - * + * * @param t Trigger that has already been registered to its event */ public static void addSelfRegisteringTrigger(Trigger t) { assert t.getEvent() instanceof SelfRegisteringSkriptEvent; selfRegisteredTriggers.add(t); } - + static ScriptInfo removeTriggers(File script) { ScriptInfo info = new ScriptInfo(); info.files = 1; - + int previousSize = triggers.size(); triggers.removeIf(pair -> script.equals(pair.getSecond().getScript())); info.triggers += previousSize - triggers.size(); - + for (int i = 0; i < selfRegisteredTriggers.size(); i++) { Trigger t = selfRegisteredTriggers.get(i); if (script.equals(t.getScript())) { @@ -212,62 +213,111 @@ static ScriptInfo removeTriggers(File script) { i--; } } - + info.commands = Commands.unregisterCommands(script); - + return info; } - + static void removeAllTriggers() { triggers.clear(); for (Trigger t : selfRegisteredTriggers) ((SelfRegisteringSkriptEvent) t.getEvent()).unregisterAll(); selfRegisteredTriggers.clear(); } - + /** * Registers event handlers for all events which currently loaded * triggers are using. */ - @SuppressWarnings({"unchecked", "rawtypes"}) static void registerBukkitEvents() { for (NonNullPair<Class<? extends Event>, Trigger> pair : triggers) { assert pair.getFirst() != null; Class<? extends Event> e = pair.getFirst(); - + EventPriority priority; priority = pair.getSecond().getEvent().getEventPriority(); - + PriorityListener listener = listeners[priority.ordinal()]; EventExecutor executor = listener.executor; - - Set<Class<? extends Event>> registeredEvents = listener.registeredEvents; - + + HandlerList handlerList = getHandlerList(e); + + if (handlerList == null) + continue; + // PlayerInteractEntityEvent has a subclass we need for armor stands if (e.equals(PlayerInteractEntityEvent.class)) { - if (!registeredEvents.contains(e)) { - registeredEvents.add(e); + if (!isEventRegistered(handlerList, priority)) { Bukkit.getPluginManager().registerEvent(e, listener, priority, executor, Skript.getInstance()); Bukkit.getPluginManager().registerEvent(PlayerInteractAtEntityEvent.class, listener, priority, executor, Skript.getInstance()); } continue; } - if (e.equals(PlayerInteractAtEntityEvent.class) || e.equals(PlayerArmorStandManipulateEvent.class)) { + if (e.equals(PlayerInteractAtEntityEvent.class) || e.equals(PlayerArmorStandManipulateEvent.class)) continue; // Ignore, registered above - } - - if (!containsSuperclass((Set) registeredEvents, e)) { // I just love Java's generics + + if (!isEventRegistered(handlerList, priority)) // Check if event is registered Bukkit.getPluginManager().registerEvent(e, listener, priority, executor, Skript.getInstance()); - registeredEvents.add(e); + } + } + + /** + * A cache for the getHandlerList methods of Event classes + */ + private static final Map<Class<? extends Event>, Method> handlerListMethods = new HashMap<>(); + + @Nullable + @SuppressWarnings("ThrowableNotThrown") + private static HandlerList getHandlerList(Class<? extends Event> eventClass) { + try { + Method method = getHandlerListMethod(eventClass); + method.setAccessible(true); + return (HandlerList) method.invoke(null); + } catch (Exception ex) { + Skript.exception(ex, "Failed to get HandlerList for event " + eventClass.getName()); + return null; + } + } + + private static Method getHandlerListMethod(Class<? extends Event> eventClass) { + Method method; + + synchronized (handlerListMethods) { + method = handlerListMethods.get(eventClass); + if (method == null) { + method = getHandlerListMethod_i(eventClass); + if (method != null) + method.setAccessible(true); + handlerListMethods.put(eventClass, method); + } + } + + if (method == null) + throw new RuntimeException("No getHandlerList method found"); + + return method; + } + + @Nullable + private static Method getHandlerListMethod_i(Class<? extends Event> eventClass) { + try { + return eventClass.getDeclaredMethod("getHandlerList"); + } catch (NoSuchMethodException e) { + if (eventClass.getSuperclass() != null + && !eventClass.getSuperclass().equals(Event.class) + && Event.class.isAssignableFrom(eventClass.getSuperclass())) { + return getHandlerListMethod(eventClass.getSuperclass().asSubclass(Event.class)); + } else { + return null; } } } - - public static boolean containsSuperclass(Set<Class<?>> classes, Class<?> c) { - if (classes.contains(c)) - return true; - for (Class<?> cl : classes) { - if (cl.isAssignableFrom(c)) + + private static boolean isEventRegistered(HandlerList handlerList, EventPriority priority) { + for (RegisteredListener rl : handlerList.getRegisteredListeners()) { + Listener l = rl.getListener(); + if (rl.getPlugin() == Skript.getInstance() && l instanceof PriorityListener && ((PriorityListener) l).priority == priority) return true; } return false; @@ -277,5 +327,5 @@ public static boolean containsSuperclass(Set<Class<?>> classes, Class<?> c) { * Events which are listened even if they are cancelled. */ public static final Set<Class<? extends Event>> listenCancelled = new HashSet<>(); - + } diff --git a/src/main/java/ch/njol/skript/events/EvtCommand.java b/src/main/java/ch/njol/skript/events/EvtCommand.java index 67fb0335aa2..4d9b327aa8f 100644 --- a/src/main/java/ch/njol/skript/events/EvtCommand.java +++ b/src/main/java/ch/njol/skript/events/EvtCommand.java @@ -44,9 +44,9 @@ public class EvtCommand extends SkriptEvent { // TODO condition to check whether @Nullable private String command = null; - - @SuppressWarnings("null") + @Override + @SuppressWarnings("null") public boolean init(final Literal<?>[] args, final int matchedPattern, final ParseResult parser) { if (args[0] != null) { command = ((Literal<String>) args[0]).getSingle(); @@ -55,10 +55,13 @@ public boolean init(final Literal<?>[] args, final int matchedPattern, final Par } return true; } - - @SuppressWarnings("null") + @Override + @SuppressWarnings("null") public boolean check(final Event e) { + if (e instanceof ServerCommandEvent && ((ServerCommandEvent) e).getCommand().isEmpty()) + return false; + if (command == null) return true; final String message; From a8df952a2c0b954b392998acbd507e61a35c295c Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Mon, 3 Oct 2022 15:40:29 -0400 Subject: [PATCH 105/619] Add lang file updates on version change (#5072) --- src/main/java/ch/njol/skript/Skript.java | 11 ++- .../java/ch/njol/skript/config/Config.java | 11 +++ .../ch/njol/skript/config/SectionNode.java | 89 +++++++++++++------ .../ch/njol/skript/localization/Language.java | 46 ++++++++-- 4 files changed, 115 insertions(+), 42 deletions(-) diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 7d6b0d9827c..15f0cd34b2a 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -1218,12 +1218,11 @@ public static Collection<SkriptAddon> getAddons() { * @return A {@link SkriptAddon} representing Skript. */ public static SkriptAddon getAddonInstance() { - final SkriptAddon a = addon; - if (a == null) - return addon = new SkriptAddon(Skript.getInstance()) - .setLanguageFileDirectory("lang"); - else - return a; + if (addon == null) { + addon = new SkriptAddon(Skript.getInstance()); + addon.setLanguageFileDirectory("lang"); + } + return addon; } // ================ CONDITIONS & EFFECTS & SECTIONS ================ diff --git a/src/main/java/ch/njol/skript/config/Config.java b/src/main/java/ch/njol/skript/config/Config.java index d996f61c9a9..6ec44491749 100644 --- a/src/main/java/ch/njol/skript/config/Config.java +++ b/src/main/java/ch/njol/skript/config/Config.java @@ -186,6 +186,17 @@ public boolean setValues(final Config other) { public boolean setValues(final Config other, final String... excluded) { return getMainNode().setValues(other.getMainNode(), excluded); } + + /** + * Compares the keys and values of this Config and another. + * @param other The other Config. + * @param excluded Keys to exclude from this comparison. + * @return True if there are differences in the keys and their values + * of this Config and the other Config. + */ + public boolean compareValues(Config other, String... excluded) { + return getMainNode().compareValues(other.getMainNode(), excluded); + } @Nullable public File getFile() { diff --git a/src/main/java/ch/njol/skript/config/SectionNode.java b/src/main/java/ch/njol/skript/config/SectionNode.java index 798ed62cf5e..8cd73347b7e 100644 --- a/src/main/java/ch/njol/skript/config/SectionNode.java +++ b/src/main/java/ch/njol/skript/config/SectionNode.java @@ -446,45 +446,80 @@ HashMap<String, String> toMap(final String prefix, final String separator) { } return r; } - + /** - * @param other - * @param excluded keys and sections to exclude - * @return <tt>false</tt> if this and the other SectionNode contain the exact same set of keys + * Updates the values of this SectionNode based on the values of another SectionNode. + * @param other The other SectionNode. + * @param excluded Keys to exclude from this update. + * @return True if there are differences in the keys of this SectionNode and the other SectionNode. */ - public boolean setValues(final SectionNode other, final String... excluded) { - boolean r = false; - for (final Node n : this) { - if (CollectionUtils.containsIgnoreCase(excluded, n.key)) + public boolean setValues(SectionNode other, String... excluded) { + return modify(other, false, excluded); + } + + /** + * Compares the keys and values of this SectionNode and another. + * @param other The other SectionNode. + * @param excluded Keys to exclude from this comparison. + * @return True if there are no differences in the keys and their values + * of this SectionNode and the other SectionNode. + */ + public boolean compareValues(SectionNode other, String... excluded) { + return !modify(other, true, excluded); // invert as "modify" returns true if different + } + + private boolean modify(SectionNode other, boolean compareValues, String... excluded) { + boolean different = false; + + for (Node node : this) { + if (CollectionUtils.containsIgnoreCase(excluded, node.key)) continue; - final Node o = other.get(n.key); - if (o == null) { - r = true; - } else { - if (n instanceof SectionNode) { - if (o instanceof SectionNode) { - r |= ((SectionNode) n).setValues((SectionNode) o); - } else { - r = true; + + Node otherNode = other.get(node.key); + if (otherNode != null) { // other has this key + if (node instanceof SectionNode) { + if (otherNode instanceof SectionNode) { + different |= ((SectionNode) node).modify((SectionNode) otherNode, compareValues); + } else { // Our node type is different from the old one + different = true; + if (compareValues) // Counting values means we don't need to copy over values + break; } - } else if (n instanceof EntryNode) { - if (o instanceof EntryNode) { - ((EntryNode) n).setValue(((EntryNode) o).getValue()); - } else { - r = true; + } else if (node instanceof EntryNode) { + if (otherNode instanceof EntryNode) { + String ourValue = ((EntryNode) node).getValue(); + String theirValue = ((EntryNode) otherNode).getValue(); + if (compareValues) { + if (!ourValue.equals(theirValue)) { + different = true; + break; // Counting values means we don't need to copy over values + } + } else { // If we don't care about values, just copy over the old one + ((EntryNode) node).setValue(theirValue); + } + } else { // Our node type is different from the old one + different = true; + if (compareValues) // Counting values means we don't need to copy over values + break; } } + } else { // other is missing this key (which means we have a new key) + different = true; + if (compareValues) // Counting values means we don't need to copy over values + break; } } - if (!r) { - for (final Node o : other) { - if (this.get(o.key) == null) { - r = true; + + if (!different) { + for (Node otherNode : other) { + if (this.get(otherNode.key) == null) { + different = true; break; } } } - return r; + + return different; } } diff --git a/src/main/java/ch/njol/skript/localization/Language.java b/src/main/java/ch/njol/skript/localization/Language.java index 74e095a4c77..3f32698071a 100644 --- a/src/main/java/ch/njol/skript/localization/Language.java +++ b/src/main/java/ch/njol/skript/localization/Language.java @@ -22,6 +22,7 @@ import ch.njol.skript.SkriptAddon; import ch.njol.skript.config.Config; import ch.njol.skript.util.ExceptionUtils; +import ch.njol.skript.util.FileUtils; import ch.njol.skript.util.Version; import org.bukkit.plugin.Plugin; import org.eclipse.jdt.annotation.Nullable; @@ -197,8 +198,8 @@ public static void loadDefault(SkriptAddon addon) { englishIs = null; } } - HashMap<String, String> def = load(defaultIs, "default"); - HashMap<String, String> en = load(englishIs, "english"); + HashMap<String, String> def = load(defaultIs, "default", false); + HashMap<String, String> en = load(englishIs, "english", addon == Skript.getAddonInstance()); String v = def.get("version"); if (v == null) @@ -220,9 +221,9 @@ public static boolean load(String name) { name = "" + name.toLowerCase(Locale.ENGLISH); localizedLanguage = new HashMap<>(); - boolean exists = load(Skript.getAddonInstance(), name); + boolean exists = load(Skript.getAddonInstance(), name, true); for (SkriptAddon addon : Skript.getAddons()) { - exists |= load(addon, name); + exists |= load(addon, name, false); } if (!exists) { if (name.equals("english")) { @@ -241,18 +242,18 @@ public static boolean load(String name) { return true; } - private static boolean load(SkriptAddon addon, String name) { + private static boolean load(SkriptAddon addon, String name, boolean tryUpdate) { if (addon.getLanguageFileDirectory() == null) return false; // Backwards addon compatibility if (name.equals("english") && addon.plugin.getResource(addon.getLanguageFileDirectory() + "/default.lang") == null) return true; - HashMap<String, String> l = load(addon.plugin.getResource(addon.getLanguageFileDirectory() + "/" + name + ".lang"), name); + HashMap<String, String> l = load(addon.plugin.getResource(addon.getLanguageFileDirectory() + "/" + name + ".lang"), name, tryUpdate); File file = new File(addon.plugin.getDataFolder(), addon.getLanguageFileDirectory() + File.separator + name + ".lang"); try { if (file.exists()) - l.putAll(load(new FileInputStream(file), name)); + l.putAll(load(new FileInputStream(file), name, tryUpdate)); } catch (FileNotFoundException e) { assert false; } @@ -289,11 +290,38 @@ private static boolean load(SkriptAddon addon, String name) { return true; } - private static HashMap<String, String> load(@Nullable InputStream in, String name) { + private static HashMap<String, String> load(@Nullable InputStream in, String name, boolean tryUpdate) { if (in == null) return new HashMap<>(); + try { - return new Config(in, name + ".lang", false, false, ":").toMap("."); + Config langConfig = new Config(in, name + ".lang", false, false, ":"); + + if (tryUpdate && !Skript.getVersion().toString().equals(langConfig.get("version"))) { + String langFileName = "lang/" + name + ".lang"; + + InputStream newConfigIn = Skript.getInstance().getResource(langFileName); + if (newConfigIn == null) { + Skript.error("The lang file '" + name + ".lang' is outdated, but Skript couldn't find the newest version of it in its jar."); + return new HashMap<>(); + } + Config newLangConfig = new Config(newConfigIn, "Skript.jar/" + langFileName, false, false, ":"); + newConfigIn.close(); + + File langFile = new File(Skript.getInstance().getDataFolder(), langFileName); + if (!newLangConfig.compareValues(langConfig, "version")) { + File langFileBackup = FileUtils.backup(langFile); + newLangConfig.getMainNode().set("version", Skript.getVersion().toString()); + langConfig = newLangConfig; + langConfig.save(langFile); + Skript.info("The lang file '" + name + ".lang' has been updated to the latest version. A backup of your old lang file has been created as " + langFileBackup.getName()); + } else { // Only the version changed, don't bother creating a backup + langConfig.getMainNode().set("version", Skript.getVersion().toString()); + langConfig.save(langFile); + } + } + + return langConfig.toMap("."); } catch (IOException e) { //noinspection ThrowableNotThrown Skript.exception(e, "Could not load the language file '" + name + ".lang': " + ExceptionUtils.toString(e)); From aa62e8984ceb749df17f1a2d1a6f8da0cbc9c1ff Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Mon, 3 Oct 2022 21:47:10 +0200 Subject: [PATCH 106/619] Structure API (#4108) One small step for a Skripter... ONE GIANT LEAP FOR SKRIPTKIND!!! --- .../java/ch/njol/skript/ScriptLoader.java | 1277 ++++++++--------- src/main/java/ch/njol/skript/Skript.java | 320 +++-- .../java/ch/njol/skript/SkriptCommand.java | 458 +++--- .../skript/SkriptCommandTabCompleter.java | 103 +- .../ch/njol/skript/SkriptEventHandler.java | 206 +-- .../java/ch/njol/skript/aliases/Aliases.java | 100 +- .../ch/njol/skript/aliases/ScriptAliases.java | 6 +- .../java/ch/njol/skript/command/Commands.java | 334 +---- .../ch/njol/skript/command/ScriptCommand.java | 28 +- .../skript/conditions/CondScriptLoaded.java | 33 +- .../ch/njol/skript/effects/EffChange.java | 9 +- .../java/ch/njol/skript/effects/EffLog.java | 12 +- .../ch/njol/skript/effects/EffScriptFile.java | 108 +- .../skript/effects/EffSuppressWarnings.java | 52 +- .../java/ch/njol/skript/events/EvtAtTime.java | 2 +- .../java/ch/njol/skript/events/EvtBlock.java | 10 +- .../java/ch/njol/skript/events/EvtClick.java | 2 +- .../java/ch/njol/skript/events/EvtDamage.java | 2 +- .../java/ch/njol/skript/events/EvtEntity.java | 4 +- .../ch/njol/skript/events/EvtGameMode.java | 2 +- .../java/ch/njol/skript/events/EvtItem.java | 18 +- .../ch/njol/skript/events/EvtPlantGrowth.java | 2 +- .../njol/skript/events/EvtWeatherChange.java | 2 +- .../njol/skript/expressions/ExprScript.java | 44 +- .../njol/skript/expressions/ExprScripts.java | 32 +- .../java/ch/njol/skript/lang/Section.java | 11 +- .../lang/SelfRegisteringSkriptEvent.java | 19 +- .../java/ch/njol/skript/lang/SkriptEvent.java | 186 ++- .../ch/njol/skript/lang/SkriptEventInfo.java | 3 +- .../ch/njol/skript/lang/SkriptParser.java | 126 +- .../java/ch/njol/skript/lang/Trigger.java | 28 +- .../java/ch/njol/skript/lang/TriggerItem.java | 15 +- .../java/ch/njol/skript/lang/Variable.java | 12 +- .../ch/njol/skript/lang/VariableString.java | 19 + .../njol/skript/lang/function/Functions.java | 137 +- .../njol/skript/lang/function/Namespace.java | 7 + .../skript/lang/function/ScriptFunction.java | 5 +- .../skript/lang/parser/ParserInstance.java | 515 ++++--- .../skript/lang/util/ContextlessEvent.java | 55 + .../njol/skript/sections/SecConditional.java | 8 +- .../njol/skript/structures/StructAliases.java | 83 ++ .../njol/skript/structures/StructCommand.java | 350 +++++ .../skript/structures/StructFunction.java | 115 ++ .../njol/skript/structures/StructOptions.java | 157 ++ .../skript/structures/StructVariables.java | 167 +++ .../njol/skript/structures/package-info.java | 27 + .../ch/njol/skript/util/ScriptOptions.java | 54 - .../util/coll/iterator/ConsumingIterator.java | 54 + .../skript/lang/entry/EntryContainer.java | 173 +++ .../skript/lang/entry/EntryData.java | 98 ++ .../skript/lang/entry/EntryValidator.java | 235 +++ .../skript/lang/entry/KeyValueEntryData.java | 89 ++ .../skript/lang/entry/SectionEntryData.java | 67 + .../skript/lang/entry/package-info.java | 23 + .../lang/entry/util/ExpressionEntryData.java | 95 ++ .../lang/entry/util/LiteralEntryData.java | 52 + .../lang/entry/util/TriggerEntryData.java | 91 ++ .../entry/util/VariableStringEntryData.java | 93 ++ .../skript/lang/entry/util/package-info.java | 23 + .../skriptlang/skript/lang/script/Script.java | 165 +++ .../skript/lang/script/ScriptData.java | 25 + .../lang/script/ScriptEventHandler.java | 46 + .../skript/lang/script/ScriptWarning.java | 32 + .../skript/lang/script/package-info.java | 23 + .../skript/lang/structure/Structure.java | 220 +++ .../skript/lang/structure/StructureInfo.java | 43 + .../skript/lang/structure/package-info.java | 23 + src/main/resources/lang/english.lang | 2 +- src/main/resources/lang/french.lang | 2 +- src/main/resources/lang/german.lang | 2 +- src/main/resources/lang/korean.lang | 2 +- 71 files changed, 4815 insertions(+), 2128 deletions(-) create mode 100644 src/main/java/ch/njol/skript/lang/util/ContextlessEvent.java create mode 100644 src/main/java/ch/njol/skript/structures/StructAliases.java create mode 100644 src/main/java/ch/njol/skript/structures/StructCommand.java create mode 100644 src/main/java/ch/njol/skript/structures/StructFunction.java create mode 100644 src/main/java/ch/njol/skript/structures/StructOptions.java create mode 100644 src/main/java/ch/njol/skript/structures/StructVariables.java create mode 100644 src/main/java/ch/njol/skript/structures/package-info.java delete mode 100644 src/main/java/ch/njol/skript/util/ScriptOptions.java create mode 100644 src/main/java/ch/njol/util/coll/iterator/ConsumingIterator.java create mode 100644 src/main/java/org/skriptlang/skript/lang/entry/EntryContainer.java create mode 100644 src/main/java/org/skriptlang/skript/lang/entry/EntryData.java create mode 100644 src/main/java/org/skriptlang/skript/lang/entry/EntryValidator.java create mode 100644 src/main/java/org/skriptlang/skript/lang/entry/KeyValueEntryData.java create mode 100644 src/main/java/org/skriptlang/skript/lang/entry/SectionEntryData.java create mode 100644 src/main/java/org/skriptlang/skript/lang/entry/package-info.java create mode 100644 src/main/java/org/skriptlang/skript/lang/entry/util/ExpressionEntryData.java create mode 100644 src/main/java/org/skriptlang/skript/lang/entry/util/LiteralEntryData.java create mode 100644 src/main/java/org/skriptlang/skript/lang/entry/util/TriggerEntryData.java create mode 100644 src/main/java/org/skriptlang/skript/lang/entry/util/VariableStringEntryData.java create mode 100644 src/main/java/org/skriptlang/skript/lang/entry/util/package-info.java create mode 100644 src/main/java/org/skriptlang/skript/lang/script/Script.java create mode 100644 src/main/java/org/skriptlang/skript/lang/script/ScriptData.java create mode 100644 src/main/java/org/skriptlang/skript/lang/script/ScriptEventHandler.java create mode 100644 src/main/java/org/skriptlang/skript/lang/script/ScriptWarning.java create mode 100644 src/main/java/org/skriptlang/skript/lang/script/package-info.java create mode 100644 src/main/java/org/skriptlang/skript/lang/structure/Structure.java create mode 100644 src/main/java/org/skriptlang/skript/lang/structure/StructureInfo.java create mode 100644 src/main/java/org/skriptlang/skript/lang/structure/package-info.java diff --git a/src/main/java/ch/njol/skript/ScriptLoader.java b/src/main/java/ch/njol/skript/ScriptLoader.java index 0186fbe1b63..b054ad219f7 100644 --- a/src/main/java/ch/njol/skript/ScriptLoader.java +++ b/src/main/java/ch/njol/skript/ScriptLoader.java @@ -18,61 +18,42 @@ */ package ch.njol.skript; -import ch.njol.skript.aliases.Aliases; -import ch.njol.skript.aliases.ScriptAliases; -import ch.njol.skript.bukkitutil.CommandReloader; -import ch.njol.skript.classes.ClassInfo; -import ch.njol.skript.command.CommandEvent; -import ch.njol.skript.command.Commands; -import ch.njol.skript.command.ScriptCommand; import ch.njol.skript.config.Config; -import ch.njol.skript.config.EntryNode; import ch.njol.skript.config.Node; import ch.njol.skript.config.SectionNode; import ch.njol.skript.config.SimpleNode; import ch.njol.skript.events.bukkit.PreScriptLoadEvent; -import ch.njol.skript.lang.ParseContext; +import ch.njol.skript.log.SkriptLogger; +import org.skriptlang.skript.lang.script.Script; import ch.njol.skript.lang.Section; -import ch.njol.skript.lang.SelfRegisteringSkriptEvent; -import ch.njol.skript.lang.SkriptEvent; -import ch.njol.skript.lang.SkriptEventInfo; import ch.njol.skript.lang.SkriptParser; import ch.njol.skript.lang.Statement; -import ch.njol.skript.lang.Trigger; import ch.njol.skript.lang.TriggerItem; import ch.njol.skript.lang.TriggerSection; -import ch.njol.skript.lang.function.Function; -import ch.njol.skript.lang.function.FunctionEvent; -import ch.njol.skript.lang.function.Functions; import ch.njol.skript.lang.parser.ParserInstance; -import ch.njol.skript.localization.Message; -import ch.njol.skript.localization.PluralizingArgsMessage; +import org.skriptlang.skript.lang.structure.Structure; +import ch.njol.skript.lang.util.ContextlessEvent; import ch.njol.skript.log.CountingLogHandler; import ch.njol.skript.log.LogEntry; -import ch.njol.skript.log.ParseLogHandler; import ch.njol.skript.log.RetainingLogHandler; -import ch.njol.skript.log.SkriptLogger; -import ch.njol.skript.registrations.Classes; -import ch.njol.skript.registrations.Converters; import ch.njol.skript.sections.SecLoop; -import ch.njol.skript.util.Date; +import ch.njol.skript.structures.StructOptions; import ch.njol.skript.util.ExceptionUtils; import ch.njol.skript.util.SkriptColor; import ch.njol.skript.util.Task; import ch.njol.skript.util.Timespan; import ch.njol.skript.variables.TypeHints; -import ch.njol.skript.variables.Variables; import ch.njol.util.Kleenean; import ch.njol.util.NonNullPair; import ch.njol.util.OpenCloseable; import ch.njol.util.StringUtils; +import ch.njol.util.coll.CollectionUtils; import org.bukkit.Bukkit; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; import java.io.File; import java.io.FileFilter; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; @@ -81,73 +62,45 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; +import java.util.Comparator; import java.util.HashSet; import java.util.List; -import java.util.Locale; -import java.util.Map; import java.util.Set; +import java.util.TreeSet; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; -import java.util.logging.Level; -import java.util.regex.Matcher; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * The main class for loading, unloading and reloading scripts. - * - * @author Peter Güttinger */ public class ScriptLoader { - - private static final Message m_no_errors = new Message("skript.no errors"), - m_no_scripts = new Message("skript.no scripts"); - private static final PluralizingArgsMessage m_scripts_loaded = - new PluralizingArgsMessage("skript.scripts loaded"); - - /** - * Clears triggers, commands, functions and variable names - */ - static void disableScripts() { - SkriptEventHandler.removeAllTriggers(); - Commands.clearCommands(); - Functions.clearFunctions(); - } + + public static final String DISABLED_SCRIPT_PREFIX = "-"; + public static final int DISABLED_SCRIPT_PREFIX_LENGTH = DISABLED_SCRIPT_PREFIX.length(); /** - * A class for keeping track of a the general content of a script: + * A class for keeping track of the general content of a script: * <ul> * <li>The amount of files</li> - * <li>The amount of triggers</li> - * <li>The amount of commands</li> - * <li>The amount of functions</li> - * <li>The names of the declared commands</li> + * <li>The amount of structures</li> * </ul> */ public static class ScriptInfo { - public int files, triggers, commands, functions; - - /** - * Command names. They're collected to see if commands need to be - * sent to clients on Minecraft 1.13 and newer. Note that add/subtract - * don't operate with command names! - */ - public final Set<String> commandNames; - + public int files, structures; + public ScriptInfo() { - commandNames = new HashSet<>(); + } - public ScriptInfo(int numFiles, int numTriggers, int numCommands, int numFunctions) { + public ScriptInfo(int numFiles, int numStructures) { files = numFiles; - triggers = numTriggers; - commands = numCommands; - functions = numFunctions; - commandNames = new HashSet<>(); + structures = numStructures; } /** @@ -156,42 +109,24 @@ public ScriptInfo(int numFiles, int numTriggers, int numCommands, int numFunctio */ public ScriptInfo(ScriptInfo other) { files = other.files; - triggers = other.triggers; - commands = other.commands; - functions = other.functions; - commandNames = new HashSet<>(other.commandNames); + structures = other.structures; } public void add(ScriptInfo other) { files += other.files; - triggers += other.triggers; - commands += other.commands; - functions += other.functions; + structures += other.structures; } public void subtract(ScriptInfo other) { files -= other.files; - triggers -= other.triggers; - commands -= other.commands; - functions -= other.functions; + structures -= other.structures; } @Override public String toString() { - return "ScriptInfo{files=" + files + ",triggers=" + triggers + ",commands=" + commands + ",functions:" + functions + "}"; + return "ScriptInfo{files=" + files + ",structures=" + structures + "}"; } } - - /** - * Must be synchronized - */ - private static final ScriptInfo loadedScripts = new ScriptInfo(); - - /** - * Command names by script names. Used to figure out when commands need - * to be re-sent to clients on MC 1.13+. - */ - private static final Map<String, Set<String>> commandNames = new HashMap<>(); /** * @see ParserInstance#get() @@ -200,51 +135,118 @@ private static ParserInstance getParser() { return ParserInstance.get(); } - /* * Enabled/disabled script tracking */ + + // TODO We need to track scripts in the process of loading so that they may not be [re]loaded while they are already loading (for async loading) + /** - * All loaded script files. + * All loaded scripts. */ @SuppressWarnings("null") - private static final Set<File> loadedFiles = Collections.synchronizedSet(new HashSet<>()); + private static final Set<Script> loadedScripts = Collections.synchronizedSortedSet(new TreeSet<>(new Comparator<Script>() { + @Override + public int compare(Script s1, Script s2) { + File f1 = s1.getConfig().getFile(); + File f2 = s2.getConfig().getFile(); + if (f1 == null || f2 == null) + throw new IllegalArgumentException("Scripts will null config files cannot be sorted."); + + File f1Parent = f1.getParentFile(); + File f2Parent = f2.getParentFile(); + + if (isSubDir(f1Parent, f2Parent)) + return -1; + + if (isSubDir(f2Parent, f1Parent)) + return 1; + + return f1.compareTo(f2); + } + + private boolean isSubDir(File directory, File subDir) { + for (File parentDir = directory.getParentFile(); parentDir != null; parentDir = parentDir.getParentFile()) { + if (subDir.equals(parentDir)) + return true; + } + return false; + } + })); /** - * Filter for enabled scripts & folders. + * Filter for loaded scripts and folders. */ - private static final FileFilter scriptFilter = + private static final FileFilter loadedScriptFilter = f -> f != null && (f.isDirectory() && !f.getName().startsWith(".") || !f.isDirectory() && StringUtils.endsWithIgnoreCase(f.getName(), ".sk")) - && !f.getName().startsWith("-") && !f.isHidden(); + && !f.getName().startsWith(DISABLED_SCRIPT_PREFIX) && !f.isHidden(); + + /** + * Searches through the loaded scripts to find the script loaded from the provided file. + * @param file The file containing the script to find. Must not be a directory. + * @return The script loaded from the provided file, or null if no script was found. + */ + @Nullable + public static Script getScript(File file) { + if (!file.isFile()) + throw new IllegalArgumentException("Something other than a file was provided."); + for (Script script : loadedScripts) { + if (file.equals(script.getConfig().getFile())) + return script; + } + return null; + } + + /** + * Searches through the loaded scripts to find all scripts loaded from the files contained within the provided directory. + * @param directory The directory containing scripts to find. + * @return The scripts loaded from the files of the provided directory. + * Empty if no scripts were found. + */ + public static Set<Script> getScripts(File directory) { + if (!directory.isDirectory()) + throw new IllegalArgumentException("Something other than a directory was provided."); + Set<Script> scripts = new HashSet<>(); + //noinspection ConstantConditions - If listFiles still manages to return null, we should probably let the exception print + for (File file : directory.listFiles(loadedScriptFilter)) { + if (file.isDirectory()) { + scripts.addAll(getScripts(file)); + } else { + Script script = getScript(file); + if (script != null) + scripts.add(script); + } + } + return scripts; + } /** * All disabled script files. */ - private static final Set<File> disabledFiles = Collections.synchronizedSet(new HashSet<>()); + private static final Set<File> disabledScripts = Collections.synchronizedSet(new HashSet<>()); /** - * Filter for disabled scripts & folders. + * Filter for disabled scripts and folders. */ - private static final FileFilter disabledFilter = + private static final FileFilter disabledScriptFilter = f -> f != null && (f.isDirectory() && !f.getName().startsWith(".") || !f.isDirectory() && StringUtils.endsWithIgnoreCase(f.getName(), ".sk")) - && f.getName().startsWith("-") && !f.isHidden(); + && f.getName().startsWith(DISABLED_SCRIPT_PREFIX) && !f.isHidden(); /** - * Reevaluates {@link #disabledFiles}. + * Reevaluates {@link #disabledScripts}. * @param path the scripts folder to use for the reevaluation. */ - private static void updateDisabledScripts(Path path) { - disabledFiles.clear(); - try { - // TODO handle AccessDeniedException - Files.walk(path) - .map(Path::toFile) - .filter(disabledFilter::accept) - .forEach(disabledFiles::add); - } catch (IOException e) { - e.printStackTrace(); + static void updateDisabledScripts(Path path) { + disabledScripts.clear(); + try (Stream<Path> files = Files.walk(path)) { + files.map(Path::toFile) + .filter(disabledScriptFilter::accept) + .forEach(disabledScripts::add); + } catch (Exception e) { + //noinspection ThrowableNotThrown + Skript.exception(e, "An error occurred while trying to update the list of disabled scripts!"); } } @@ -252,6 +254,7 @@ private static void updateDisabledScripts(Path path) { /* * Async loading */ + /** * The tasks that should be executed by the async loaders. * <br> @@ -260,15 +263,18 @@ private static void updateDisabledScripts(Path path) { * @see AsyncLoaderThread */ private static final BlockingQueue<Runnable> loadQueue = new LinkedBlockingQueue<>(); + /** * The {@link ThreadGroup} all async loaders belong to. * @see AsyncLoaderThread */ private static final ThreadGroup asyncLoaderThreadGroup = new ThreadGroup("Skript async loaders"); + /** * All active {@link AsyncLoaderThread}s. */ private static final List<AsyncLoaderThread> loaderThreads = new ArrayList<>(); + /** * The current amount of loader threads. * <br> @@ -440,98 +446,62 @@ private static <T> CompletableFuture<T> makeFuture(Supplier<T> supplier, OpenClo /* - * Script loading methods + * Script Loading Methods */ + /** - * Loads all scripts in the scripts folder using {@link #loadScripts(List, OpenCloseable)}, - * sending info/error messages when done. - */ - static CompletableFuture<Void> loadScripts(OpenCloseable openCloseable) { - File scriptsFolder = new File(Skript.getInstance().getDataFolder(), Skript.SCRIPTSFOLDER + File.separator); - if (!scriptsFolder.isDirectory()) - //noinspection ResultOfMethodCallIgnored - scriptsFolder.mkdirs(); - - Date start = new Date(); - - updateDisabledScripts(scriptsFolder.toPath()); - - Set<File> oldLoadedFiles = new HashSet<>(loadedFiles); - - List<Config> configs; - - CountingLogHandler logHandler = new CountingLogHandler(Level.SEVERE).start(); - try { - configs = loadStructures(scriptsFolder); - } finally { - logHandler.stop(); - } - - return loadScripts(configs, OpenCloseable.combine(openCloseable, logHandler)) - .thenAccept(scriptInfo -> { - // Success - if (logHandler.getCount() == 0) - Skript.info(m_no_errors.toString()); - - // Now, make sure that old files that are no longer there are unloaded - // Only if this is done using async loading, though! - if (isAsync()) { - oldLoadedFiles.removeAll(loadedFiles); - for (File script : oldLoadedFiles) { - if (script == null) - throw new NullPointerException(); - - // Use internal unload method which does not call validateFunctions() - unloadScript_(script); - String name = Skript.getInstance().getDataFolder().toPath().toAbsolutePath() - .resolve(Skript.SCRIPTSFOLDER).relativize(script.toPath()).toString(); - assert name != null; - Functions.clearFunctions(name); - } - Functions.validateFunctions(); // Manually validate functions - } - - if (scriptInfo.files == 0) - Skript.warning(m_no_scripts.toString()); - if (Skript.logNormal() && scriptInfo.files > 0) - Skript.info(m_scripts_loaded.toString( - scriptInfo.files, - scriptInfo.triggers, - scriptInfo.commands, - start.difference(new Date()) - )); - }); + * Loads the Script present at the file using {@link #loadScripts(List, OpenCloseable)}, + * sending info/error messages when done. + * @param file The file to load. If this is a directory, all scripts within the directory and any subdirectories will be loaded. + * @param openCloseable An {@link OpenCloseable} that will be called before and after + * each individual script load (see {@link #makeFuture(Supplier, OpenCloseable)}). + */ + public static CompletableFuture<ScriptInfo> loadScripts(File file, OpenCloseable openCloseable) { + return loadScripts(loadStructures(file), openCloseable); + } + + /** + * Loads the Scripts present at the files using {@link #loadScripts(List, OpenCloseable)}, + * sending info/error messages when done. + * @param files The files to load. If any file is a directory, all scripts within the directory and any subdirectories will be loaded. + * @param openCloseable An {@link OpenCloseable} that will be called before and after + * each individual script load (see {@link #makeFuture(Supplier, OpenCloseable)}). + */ + public static CompletableFuture<ScriptInfo> loadScripts(Collection<File> files, OpenCloseable openCloseable) { + return loadScripts(files.stream() + .sorted() + .map(ScriptLoader::loadStructures) + .flatMap(List::stream) + .collect(Collectors.toList()), openCloseable); } /** * Loads the specified scripts. * - * @param configs Configs for scripts, loaded by {@link #loadStructures(File[])} + * @param configs Configs representing scripts. * @param openCloseable An {@link OpenCloseable} that will be called before and after * each individual script load (see {@link #makeFuture(Supplier, OpenCloseable)}). * @return Info on the loaded scripts. */ - public static CompletableFuture<ScriptInfo> loadScripts(List<Config> configs, OpenCloseable openCloseable) { - AtomicBoolean syncCommands = new AtomicBoolean(); - + private static CompletableFuture<ScriptInfo> loadScripts(List<Config> configs, OpenCloseable openCloseable) { + if (configs.isEmpty()) // Nothing to load + return CompletableFuture.completedFuture(new ScriptInfo()); + Bukkit.getPluginManager().callEvent(new PreScriptLoadEvent(configs)); ScriptInfo scriptInfo = new ScriptInfo(); - + + List<Script> scripts = new ArrayList<>(); + List<CompletableFuture<Void>> scriptInfoFutures = new ArrayList<>(); for (Config config : configs) { if (config == null) throw new NullPointerException(); CompletableFuture<Void> future = makeFuture(() -> { - ScriptInfo info = loadScript(config); - - // Check if commands have been changed and a re-send is needed - if (!info.commandNames.equals(commandNames.get(config.getFileName()))) { - syncCommands.set(true); // Sync once after everything has been loaded - commandNames.put(config.getFileName(), info.commandNames); // These will soon be sent to clients - } - + Script script = new Script(config); + ScriptInfo info = loadScript(script); + scripts.add(script); scriptInfo.add(info); return null; }, openCloseable); @@ -541,290 +511,159 @@ public static CompletableFuture<ScriptInfo> loadScripts(List<Config> configs, Op return CompletableFuture.allOf(scriptInfoFutures.toArray(new CompletableFuture[0])) .thenApply(unused -> { - SkriptEventHandler.registerBukkitEvents(); - - // After we've loaded everything, refresh commands their names changed - if (syncCommands.get()) { - 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)"); - } else { - Skript.debug("Commands unchanged, not syncing them to clients"); + // TODO in the future this won't work when parallel loading is fixed + // It does now though so let's avoid calling getParser() a bunch. + ParserInstance parser = getParser(); + + try { + openCloseable.open(); + + scripts.stream() + .flatMap(script -> { // Flatten each entry down to a stream of Config-Structure pairs + return script.getStructures().stream() + .map(structure -> new NonNullPair<>(script, structure)); + }) + .sorted(Comparator.comparing(pair -> pair.getSecond().getPriority())) + .forEach(pair -> { + Script script = pair.getFirst(); + Structure structure = pair.getSecond(); + + parser.setActive(script); + parser.setCurrentStructure(structure); + + try { + if (!structure.preLoad()) + script.getStructures().remove(structure); + } catch (Exception e) { + //noinspection ThrowableNotThrown + Skript.exception(e, "An error occurred while trying to load a Structure."); + script.getStructures().remove(structure); + } + }); + + parser.setInactive(); + + // TODO in the future, Structure#load should be split across multiple threads if parallel loading is enabled. + // However, this is not possible right now as reworks in multiple areas will be needed. + // For example, the "Commands" class still uses a static list for currentArguments that is cleared between loads. + // Until these reworks happen, limiting main loading to asynchronous (not parallel) is the only choice we have. + for (Script script : scripts) { + parser.setActive(script); + script.getStructures().removeIf(structure -> { + parser.setCurrentStructure(structure); + parser.setNode(structure.getEntryContainer().getSource()); + try { + return !structure.load(); + } catch (Exception e) { + //noinspection ThrowableNotThrown + Skript.exception(e, "An error occurred while trying to load a Structure."); + return true; + } + }); + } + + parser.setInactive(); + + for (Script script : scripts) { + parser.setActive(script); + script.getStructures().removeIf(structure -> { + parser.setCurrentStructure(structure); + parser.setNode(structure.getEntryContainer().getSource()); + try { + return !structure.postLoad(); + } catch (Exception e) { + //noinspection ThrowableNotThrown + Skript.exception(e, "An error occurred while trying to load a Structure."); + return true; + } + }); + } + + return scriptInfo; + } catch (Exception e) { + // Something went wrong, we need to make sure the exception is printed + throw Skript.exception(e); + } finally { + parser.setInactive(); + + openCloseable.close(); } - - return scriptInfo; }); } - - /** - * Represents data for event which is waiting to be loaded. - */ - private static class ParsedEventData { - public final NonNullPair<SkriptEventInfo<?>, SkriptEvent> info; - public final String event; - public final SectionNode node; - public final List<TriggerItem> items; - - public ParsedEventData(NonNullPair<SkriptEventInfo<?>, SkriptEvent> info, - String event, - SectionNode node, - List<TriggerItem> items) { - this.info = info; - this.event = event; - this.node = node; - this.items = items; - } - } - + /** - * Loads one script. Only for internal use, as this doesn't register/update - * event handlers. - * @param config Config for script to be loaded. - * @return Info about script that is loaded + * Loads one script. Only for internal use, as this doesn't register/update event handlers. + * @param script The script to be loaded. + * @return Statistics for the script loaded. */ // Whenever you call this method, make sure to also call PreScriptLoadEvent - private static ScriptInfo loadScript(@Nullable Config config) { - if (config == null) { // Something bad happened, hopefully got logged to console + private static ScriptInfo loadScript(@Nullable Script script) { + if (script == null) { // Something bad happened, hopefully got logged to console return new ScriptInfo(); } - // When something is parsed, it goes there to be loaded later - List<ScriptCommand> commands = new ArrayList<>(); - List<ParsedEventData> events = new ArrayList<>(); - // Track what is loaded ScriptInfo scriptInfo = new ScriptInfo(); scriptInfo.files = 1; // Loading one script - + + Config config = script.getConfig(); + ParserInstance parser = getParser(); + parser.setActive(script); + try { if (SkriptConfig.keepConfigsLoaded.value()) SkriptConfig.configs.add(config); - getParser().getCurrentOptions().clear(); - getParser().setCurrentScript(config); - try (CountingLogHandler ignored = new CountingLogHandler(SkriptLogger.SEVERE).start()) { for (Node cnode : config.getMainNode()) { if (!(cnode instanceof SectionNode)) { Skript.error("invalid line - all code has to be put into triggers"); continue; } - + SectionNode node = ((SectionNode) cnode); - String event = node.getKey(); - if (event == null) - continue; - - if (event.equalsIgnoreCase("aliases")) { - node.convertToEntries(0, "="); - - // Initialize and load script aliases - ScriptAliases aliases = Aliases.createScriptAliases(); - Aliases.setScriptAliases(aliases); - aliases.parser.load(node); - continue; - } else if (event.equalsIgnoreCase("options")) { - node.convertToEntries(0); - for (Node n : node) { - if (!(n instanceof EntryNode)) { - Skript.error("invalid line in options"); - continue; - } - getParser().getCurrentOptions().put(n.getKey(), ((EntryNode) n).getValue()); - } - continue; - } else if (event.equalsIgnoreCase("variables")) { - // TODO allow to make these override existing variables - node.convertToEntries(0, "="); - for (Node n : node) { - if (!(n instanceof EntryNode)) { - Skript.error("Invalid line in variables section"); - continue; - } - String name = n.getKey().toLowerCase(Locale.ENGLISH); - if (name.startsWith("{") && name.endsWith("}")) - name = "" + name.substring(1, name.length() - 1); - String var = name; - name = StringUtils.replaceAll(name, "%(.+)?%", m -> { - if (m.group(1).contains("{") || m.group(1).contains("}") || m.group(1).contains("%")) { - Skript.error("'" + var + "' is not a valid name for a default variable"); - return null; - } - ClassInfo<?> ci = Classes.getClassInfoFromUserInput("" + m.group(1)); - if (ci == null) { - Skript.error("Can't understand the type '" + m.group(1) + "'"); - return null; - } - return "<" + ci.getCodeName() + ">"; - }); - if (name == null) { - continue; - } else if (name.contains("%")) { - Skript.error("Invalid use of percent signs in variable name"); - continue; - } - if (Variables.getVariable(name, null, false) != null) - continue; - Object o; - ParseLogHandler log = SkriptLogger.startParseLogHandler(); - try { - o = Classes.parseSimple(((EntryNode) n).getValue(), Object.class, ParseContext.SCRIPT); - if (o == null) { - log.printError("Can't understand the value '" + ((EntryNode) n).getValue() + "'"); - continue; - } - log.printLog(); - } finally { - log.stop(); - } - ClassInfo<?> ci = Classes.getSuperClassInfo(o.getClass()); - if (ci.getSerializer() == null) { - Skript.error("Can't save '" + ((EntryNode) n).getValue() + "' in a variable"); - continue; - } else if (ci.getSerializeAs() != null) { - ClassInfo<?> as = Classes.getExactClassInfo(ci.getSerializeAs()); - if (as == null) { - assert false : ci; - continue; - } - o = Converters.convert(o, as.getC()); - if (o == null) { - Skript.error("Can't save '" + ((EntryNode) n).getValue() + "' in a variable"); - continue; - } - } - Variables.setVariable(name, o, null, false); - } - continue; - } - - if (!SkriptParser.validateLine(event)) - continue; - - if (event.toLowerCase(Locale.ENGLISH).startsWith("command ")) { - - getParser().setCurrentEvent("command", CommandEvent.class); - - ScriptCommand c = Commands.loadCommand(node, false); - if (c != null) { - commands.add(c); - scriptInfo.commandNames.add(c.getName()); // For tab completion - scriptInfo.commands++; - } - - getParser().deleteCurrentEvent(); - + String line = node.getKey(); + if (line == null) continue; - } else if (event.toLowerCase(Locale.ENGLISH).startsWith("function ")) { - - getParser().setCurrentEvent("function", FunctionEvent.class); - - Function<?> func = Functions.loadFunction(node); - if (func != null) { - scriptInfo.functions++; - } - - getParser().deleteCurrentEvent(); - + + if (!SkriptParser.validateLine(line)) continue; - } - + if (Skript.logVeryHigh() && !Skript.debug()) - Skript.info("loading trigger '" + event + "'"); - - if (StringUtils.startsWithIgnoreCase(event, "on ")) - event = "" + event.substring("on ".length()); - - event = replaceOptions(event); - - NonNullPair<SkriptEventInfo<?>, SkriptEvent> parsedEvent = SkriptParser.parseEvent(event, "Can't understand this event: '" + node.getKey() + "'"); - if (parsedEvent == null || !parsedEvent.getSecond().shouldLoadEvent()) + Skript.info("loading trigger '" + line + "'"); + + line = replaceOptions(line); + + Structure structure = Structure.parse(line, node, "Can't understand this structure: " + line); + + if (structure == null) continue; - - if (Skript.debug() || node.debug()) - Skript.debug(SkriptColor.replaceColorChar(event + " (" + parsedEvent.getSecond().toString(null, true) + "):")); - - Class<? extends Event>[] eventClasses = parsedEvent.getSecond().getEventClasses(); - if (eventClasses == null) - eventClasses = parsedEvent.getFirst().events; - try { - getParser().setCurrentEvent(parsedEvent.getFirst().getName().toLowerCase(Locale.ENGLISH), eventClasses); - getParser().setCurrentSkriptEvent(parsedEvent.getSecond()); - events.add(new ParsedEventData(parsedEvent, event, node, loadItems(node))); - } finally { - getParser().deleteCurrentEvent(); - getParser().deleteCurrentSkriptEvent(); - } - - if (parsedEvent.getSecond() instanceof SelfRegisteringSkriptEvent) { - ((SelfRegisteringSkriptEvent) parsedEvent.getSecond()).afterParse(config); - } - - scriptInfo.triggers++; + + script.getStructures().add(structure); + + scriptInfo.structures++; } if (Skript.logHigh()) - Skript.info("loaded " + scriptInfo.triggers + " trigger" + (scriptInfo.triggers == 1 ? "" : "s")+ " and " + scriptInfo.commands + " command" + (scriptInfo.commands == 1 ? "" : "s") + " from '" + config.getFileName() + "'"); - - getParser().setCurrentScript(null); - Aliases.setScriptAliases(null); // These are per-script + Skript.info("loaded " + scriptInfo.structures + " structure" + (scriptInfo.structures == 1 ? "" : "s") + " from '" + config.getFileName() + "'"); } } catch (Exception e) { //noinspection ThrowableNotThrown Skript.exception(e, "Could not load " + config.getFileName()); } finally { - SkriptLogger.setNode(null); + parser.setInactive(); } // In always sync task, enable stuff Callable<Void> callable = () -> { - // Unload script IF we're doing async stuff - // (else it happened already) - File file = config.getFile(); - if (isAsync()) { - if (file != null) - unloadScript_(file); - } - - // Now, enable everything! - for (ScriptCommand command : commands) { - Commands.registerCommand(command); - } - - for (ParsedEventData event : events) { - Class<? extends Event>[] eventClasses = event.info.getSecond().getEventClasses(); - if (eventClasses == null) - eventClasses = event.info.getFirst().events; - getParser().setCurrentEvent(event.info.getFirst().getName().toLowerCase(Locale.ENGLISH), eventClasses); - getParser().setCurrentSkriptEvent(event.info.getSecond()); - - Trigger trigger; - try { - trigger = new Trigger(config.getFile(), event.event, event.info.getSecond(), event.items); - trigger.setLineNumber(event.node.getLine()); // Set line number for debugging - trigger.setDebugLabel(config.getFileName() + ": line " + event.node.getLine()); - } finally { - getParser().deleteCurrentEvent(); - } - - if (event.info.getSecond() instanceof SelfRegisteringSkriptEvent) { - ((SelfRegisteringSkriptEvent) event.info.getSecond()).register(trigger); - SkriptEventHandler.addSelfRegisteringTrigger(trigger); - } else { - SkriptEventHandler.addTrigger(event.info.getFirst().events, trigger); - } - - getParser().deleteCurrentEvent(); - getParser().deleteCurrentSkriptEvent(); - } - // Remove the script from the disabled scripts list - File disabledFile = new File(file.getParentFile(), "-" + file.getName()); - disabledFiles.remove(disabledFile); + File file = config.getFile(); + assert file != null; + File disabledFile = new File(file.getParentFile(), DISABLED_SCRIPT_PREFIX + file.getName()); + disabledScripts.remove(disabledFile); // Add to loaded files to use for future reloads - loadedFiles.add(file); + loadedScripts.add(script); return null; }; @@ -841,43 +680,34 @@ private static ScriptInfo loadScript(@Nullable Config config) { return scriptInfo; } - - + /* - * Structure loading methods - */ - /** - * Loads structures of specified scripts. - * - * @param files the scripts to load + * Script Structure Loading Methods */ - public static List<Config> loadStructures(File[] files) { - Arrays.sort(files); - - List<Config> loadedFiles = new ArrayList<>(files.length); - for (File f : files) { - assert f != null : Arrays.toString(files); - Config config = loadStructure(f); - if (config != null) - loadedFiles.add(config); - } - - return loadedFiles; - } /** - * Loads structures of all scripts in the given directory, or of the passed script if it's a normal file. - * - * @param directory a directory or a single file - * @see #loadStructure(File). + * Creates a script structure for every file contained within the provided directory. + * If a directory is not actually provided, the file itself will be used. + * @param directory The directory to create structures from. + * @see ScriptLoader#loadStructure(File) + * @return A list of all successfully loaded structures. */ - public static List<Config> loadStructures(File directory) { + private static List<Config> loadStructures(File directory) { if (!directory.isDirectory()) { Config config = loadStructure(directory); return config != null ? Collections.singletonList(config) : Collections.emptyList(); } + + try { + directory = directory.getCanonicalFile(); + } catch (IOException e) { + //noinspection ThrowableNotThrown + Skript.exception(e, "An exception occurred while trying to get the canonical file of: " + directory); + return new ArrayList<>(); + } - File[] files = directory.listFiles(scriptFilter); + File[] files = directory.listFiles(loadedScriptFilter); + assert files != null; Arrays.sort(files); List<Config> loadedDirectories = new ArrayList<>(files.length); @@ -897,224 +727,191 @@ public static List<Config> loadStructures(File directory) { } /** - * Loads structure of given script, currently only for functions. Must be called before - * actually loading that script. - * @param f Script file. + * Creates a script structure from the provided file. + * This must be done before actually loading a script. + * @param file The script to load the structure of. + * @return The loaded structure or null if an error occurred. */ - @SuppressWarnings("resource") // Stream is closed in Config constructor called in loadStructure @Nullable - public static Config loadStructure(File f) { - if (!f.exists()) { // If file does not exist... - unloadScript(f); // ... it might be good idea to unload it now + private static Config loadStructure(File file) { + try { + file = file.getCanonicalFile(); + } catch (IOException e) { + //noinspection ThrowableNotThrown + Skript.exception(e, "An exception occurred while trying to get the canonical file of: " + file); + return null; + } + + if (!file.exists()) { // If file does not exist... + Script script = getScript(file); + if (script != null) + unloadScript(script); // ... it might be good idea to unload it now return null; } try { String name = Skript.getInstance().getDataFolder().toPath().toAbsolutePath() - .resolve(Skript.SCRIPTSFOLDER).relativize(f.toPath().toAbsolutePath()).toString(); - assert name != null; - return loadStructure(new FileInputStream(f), name); + .resolve(Skript.SCRIPTSFOLDER).relativize(file.toPath().toAbsolutePath()).toString(); + return loadStructure(Files.newInputStream(file.toPath()), name); } catch (IOException e) { - Skript.error("Could not load " + f.getName() + ": " + ExceptionUtils.toString(e)); + Skript.error("Could not load " + file.getName() + ": " + ExceptionUtils.toString(e)); } return null; } /** - * Loads structure of given script, currently only for functions. Must be called before - * actually loading that script. + * Creates a script structure from the provided source. + * This must be done before actually loading a script. * @param source Source input stream. * @param name Name of source "file". + * @return The loaded structure or null if an error occurred. */ @Nullable - public static Config loadStructure(InputStream source, String name) { + private static Config loadStructure(InputStream source, String name) { try { - Config config = new Config( + return new Config( source, name, - Skript.getInstance().getDataFolder().toPath().resolve(Skript.SCRIPTSFOLDER).resolve(name).toFile(), + Skript.getInstance().getDataFolder().toPath().resolve(Skript.SCRIPTSFOLDER).resolve(name).toFile().getCanonicalFile(), true, false, ":" ); - return loadStructure(config); } catch (IOException e) { Skript.error("Could not load " + name + ": " + ExceptionUtils.toString(e)); } return null; } - - /** - * Loads structure of given script, currently only for functions. Must be called before - * actually loading that script. - * @param config Config object for the script. - */ - @Nullable - public static Config loadStructure(Config config) { - try { - for (Node cnode : config.getMainNode()) { - if (!(cnode instanceof SectionNode)) { - // Don't spit error yet, we are only pre-parsing... - continue; - } - - SectionNode node = ((SectionNode) cnode); - String event = node.getKey(); - if (event == null) - continue; - - if (!SkriptParser.validateLine(event)) - continue; - - if (event.toLowerCase(Locale.ENGLISH).startsWith("function ")) { - - getParser().setCurrentEvent("function", FunctionEvent.class); - - Functions.loadSignature(config.getFileName(), node); - - getParser().deleteCurrentEvent(); - } - } - - getParser().setCurrentScript(null); - SkriptLogger.setNode(null); - return config; - } catch (Exception e) { - Skript.exception(e, "Could not load " + config.getFileName()); - } finally { - SkriptLogger.setNode(null); - } - return null; // Oops something went wrong - } - - + /* - * Script unloading methods + * Script Unloading Methods */ + /** - * Unloads the scripts in a folder. - * @return The {@link ScriptInfo} of all unloaded scripts combined. + * Unloads all scripts present in the provided collection. + * @param scripts The scripts to unload. + * @return Combined statistics for the unloaded scripts. + * This data is calculated by using {@link ScriptInfo#add(ScriptInfo)}. */ - private static ScriptInfo unloadScripts_(File folder) { + public static ScriptInfo unloadScripts(Set<Script> scripts) { + ParserInstance parser = getParser(); ScriptInfo info = new ScriptInfo(); - for (File f : folder.listFiles(scriptFilter)) { - if (f.isDirectory()) { - info.add(unloadScripts_(f)); - } else { - info.add(unloadScript_(f)); - } + + scripts = new HashSet<>(scripts); // Don't modify the list we were provided with + + for (Script script : scripts) { + parser.setActive(script); + for (Structure structure : script.getStructures()) + structure.unload(); } + + parser.setInactive(); + + for (Script script : scripts) { + List<Structure> structures = script.getStructures(); + + info.files++; + info.structures += structures.size(); + + parser.setActive(script); + for (Structure structure : script.getStructures()) + structure.postUnload(); + structures.clear(); + parser.setInactive(); + + loadedScripts.remove(script); // We just unloaded it, so... + File scriptFile = script.getConfig().getFile(); + assert scriptFile != null; + disabledScripts.add(new File(scriptFile.getParentFile(), DISABLED_SCRIPT_PREFIX + scriptFile.getName())); + } + return info; } /** - * Unloads the specified script. - * - * @param script - * @return Info on the unloaded script + * Unloads the provided script. + * @param script The script to unload. + * @return Statistics for the unloaded script. */ - public static ScriptInfo unloadScript(File script) { - ScriptInfo r = unloadScript_(script); - Functions.validateFunctions(); - return r; - } - - private static ScriptInfo unloadScript_(File script) { - if (loadedFiles.contains(script)) { - ScriptInfo info = SkriptEventHandler.removeTriggers(script); // Remove triggers - synchronized (loadedScripts) { // Update global script info - loadedScripts.subtract(info); - } - - loadedFiles.remove(script); // We just unloaded it, so... - disabledFiles.add(new File(script.getParentFile(), "-" + script.getName())); - - // Clear functions, DO NOT validate them yet - // If unloading, our caller will do this immediately after we return - // However, if reloading, new version of this script is first loaded - String name = Skript.getInstance().getDataFolder().toPath().toAbsolutePath() - .resolve(Skript.SCRIPTSFOLDER).relativize(script.toPath().toAbsolutePath()).toString(); - assert name != null; - Functions.clearFunctions(name); - - return info; // Return how much we unloaded - } - - return new ScriptInfo(); // Return that we unloaded literally nothing + public static ScriptInfo unloadScript(Script script) { + return unloadScripts(Collections.singleton(script)); } - /* - * Script reloading methods + * Script Reloading Methods */ + /** - * Reloads a single script. - * @param script Script file. - * @return Future of statistics of the newly loaded script. + * Reloads a single Script. + * @param script The Script to reload. + * @return Info on the loaded Script. */ - public static CompletableFuture<ScriptInfo> reloadScript(File script, OpenCloseable openCloseable) { - if (!isAsync()) { - unloadScript_(script); - } - Config config = loadStructure(script); - Functions.validateFunctions(); - if (config == null) - return CompletableFuture.completedFuture(new ScriptInfo()); - return loadScripts(Collections.singletonList(config), openCloseable); + public static CompletableFuture<ScriptInfo> reloadScript(Script script, OpenCloseable openCloseable) { + return reloadScripts(Collections.singleton(script), openCloseable); } - + /** - * Reloads all scripts in the given folder and its subfolders. - * @param folder A folder. - * @return Future of statistics of newly loaded scripts. + * Reloads all provided Scripts. + * @param scripts The Scripts to reload. + * @param openCloseable An {@link OpenCloseable} that will be called before and after + * each individual Script load (see {@link #makeFuture(Supplier, OpenCloseable)}). + * @return Info on the loaded Scripts. */ - public static CompletableFuture<ScriptInfo> reloadScripts(File folder, OpenCloseable openCloseable) { - if (!isAsync()) { - unloadScripts_(folder); + public static CompletableFuture<ScriptInfo> reloadScripts(Set<Script> scripts, OpenCloseable openCloseable) { + unloadScripts(scripts); + + List<Config> configs = new ArrayList<>(); + for (Script script : scripts) { + //noinspection ConstantConditions - getFile should never return null + Config config = loadStructure(script.getConfig().getFile()); + if (config == null) + return CompletableFuture.completedFuture(new ScriptInfo()); + configs.add(config); } - List<Config> configs = loadStructures(folder); - Functions.validateFunctions(); + return loadScripts(configs, openCloseable); } - /* - * Code loading methods + * Code Loading Methods */ + /** * Replaces options in a string. + * Options are gotten from {@link ch.njol.skript.structures.StructOptions#getOptions(Script)}. */ + // TODO this system should eventually be replaced with a more generalized "node processing" system public static String replaceOptions(String s) { - String r = StringUtils.replaceAll(s, "\\{@(.+?)\\}", m -> { - String option = getParser().getCurrentOptions().get(m.group(1)); - if (option == null) { - Skript.error("undefined option " + m.group()); - return m.group(); - } - return Matcher.quoteReplacement(option); - }); - assert r != null; - return r; + ParserInstance parser = getParser(); + if (!parser.isActive()) // getCurrentScript() is not safe to use + return s; + return StructOptions.replaceOptions(parser.getCurrentScript(), s); } /** * Loads a section by converting it to {@link TriggerItem}s. */ public static ArrayList<TriggerItem> loadItems(SectionNode node) { + ParserInstance parser = getParser(); + if (Skript.debug()) - getParser().setIndentation(getParser().getIndentation() + " "); + parser.setIndentation(parser.getIndentation() + " "); ArrayList<TriggerItem> items = new ArrayList<>(); - for (Node n : node) { - SkriptLogger.setNode(n); - if (n instanceof SimpleNode) { - String expr = replaceOptions("" + n.getKey()); - if (!SkriptParser.validateLine(expr)) - continue; + for (Node subNode : node) { + parser.setNode(subNode); + + String subNodeKey = subNode.getKey(); + if (subNodeKey == null) + throw new IllegalArgumentException("Encountered node with null key: '" + subNode + "'"); + String expr = replaceOptions(subNodeKey); + if (!SkriptParser.validateLine(expr)) + continue; + if (subNode instanceof SimpleNode) { long start = System.currentTimeMillis(); Statement stmt = Statement.parse(expr, "Can't understand this condition/effect: " + expr); if (stmt == null) @@ -1129,22 +926,19 @@ public static ArrayList<TriggerItem> loadItems(SectionNode node) { ); } - if (Skript.debug() || n.debug()) - Skript.debug(SkriptColor.replaceColorChar(getParser().getIndentation() + stmt.toString(null, true))); + if (Skript.debug() || subNode.debug()) + Skript.debug(SkriptColor.replaceColorChar(parser.getIndentation() + stmt.toString(null, true))); items.add(stmt); - } else if (n instanceof SectionNode) { - String expr = replaceOptions("" + n.getKey()); - if (!SkriptParser.validateLine(expr)) - continue; + } else if (subNode instanceof SectionNode) { TypeHints.enterScope(); // Begin conditional type hints - Section section = Section.parse(expr, "Can't understand this section: " + expr, (SectionNode) n, items); + Section section = Section.parse(expr, "Can't understand this section: " + expr, (SectionNode) subNode, items); if (section == null) continue; - if (Skript.debug() || n.debug()) - Skript.debug(SkriptColor.replaceColorChar(getParser().getIndentation() + section.toString(null, true))); + if (Skript.debug() || subNode.debug()) + Skript.debug(SkriptColor.replaceColorChar(parser.getIndentation() + section.toString(null, true))); items.add(section); @@ -1155,85 +949,49 @@ public static ArrayList<TriggerItem> loadItems(SectionNode node) { for (int i = 0; i < items.size() - 1; i++) items.get(i).setNext(items.get(i + 1)); - - SkriptLogger.setNode(node); + + parser.setNode(node); if (Skript.debug()) - getParser().setIndentation("" + getParser().getIndentation().substring(0, getParser().getIndentation().length() - 4)); + parser.setIndentation(parser.getIndentation().substring(0, parser.getIndentation().length() - 4)); return items; } - + + /* + * Other Utility Methods + */ + /** - * For unit testing - * - * @param node - * @return The loaded Trigger + * @return An unmodifiable set containing a snapshot of the currently loaded scripts. + * Any changes to loaded scripts will not be reflected in the returned set. */ - @Nullable - static Trigger loadTrigger(SectionNode node) { - String event = node.getKey(); - if (event == null) { - assert false : node; - return null; - } - if (event.toLowerCase(Locale.ENGLISH).startsWith("on ")) - event = "" + event.substring("on ".length()); - - NonNullPair<SkriptEventInfo<?>, SkriptEvent> parsedEvent = - SkriptParser.parseEvent(event, "Can't understand this event: '" + node.getKey() + "'"); - if (parsedEvent == null) { - assert false; - return null; - } - - getParser().setCurrentEvent("unit test", parsedEvent.getFirst().events); - try { - return new Trigger(null, event, parsedEvent.getSecond(), loadItems(node)); - } finally { - getParser().deleteCurrentEvent(); - } + public static Set<Script> getLoadedScripts() { + return Collections.unmodifiableSet(new HashSet<>(loadedScripts)); } - - - /* - * Loaded script statistics + + /** + * @return An unmodifiable set containing a snapshot of the currently disabled scripts. + * Any changes to disabled scripts will not be reflected in the returned set. */ - @SuppressWarnings("null") // Collections methods don't return nulls, ever - public static Collection<File> getLoadedFiles() { - return Collections.unmodifiableCollection(loadedFiles); - } - - @SuppressWarnings("null") - public static Collection<File> getDisabledFiles() { - return Collections.unmodifiableCollection(disabledFiles); - } - - public static int loadedScripts() { - synchronized (loadedScripts) { - return loadedScripts.files; - } + public static Set<File> getDisabledScripts() { + return Collections.unmodifiableSet(new HashSet<>(disabledScripts)); } - - public static int loadedCommands() { - synchronized (loadedScripts) { - return loadedScripts.commands; - } - } - - public static int loadedFunctions() { - synchronized (loadedScripts) { - return loadedScripts.functions; - } + + /** + * @return A FileFilter defining the naming conditions of a loaded script. + */ + public static FileFilter getLoadedScriptsFilter() { + return loadedScriptFilter; } - - public static int loadedTriggers() { - synchronized (loadedScripts) { - return loadedScripts.triggers; - } + + /** + * @return A FileFilter defining the naming conditions of a disabled script. + */ + public static FileFilter getDisabledScriptsFilter() { + return disabledScriptFilter; } - - + /* * Deprecated stuff * @@ -1243,26 +1001,98 @@ public static int loadedTriggers() { * Some methods have been replaced by ParserInstance, some * by new methods in this class. */ + + /** + * Reloads a single script. + * @param scriptFile The file representing the script to reload. + * @return Future of statistics of the newly loaded script. + * @deprecated Use {@link #reloadScript(Script, OpenCloseable)}. + */ + @Deprecated + public static CompletableFuture<ScriptInfo> reloadScript(File scriptFile, OpenCloseable openCloseable) { + Script script = getScript(scriptFile); + if (script == null) + return CompletableFuture.completedFuture(new ScriptInfo()); + return reloadScript(script, openCloseable); + } + + /** + * Unloads the provided script. + * @param scriptFile The file representing the script to unload. + * @return Statistics for the unloaded script. + * @deprecated Use {@link #unloadScript(Script)}. + */ + @Deprecated + public static ScriptInfo unloadScript(File scriptFile) { + Script script = getScript(scriptFile); + if (script != null) + return unloadScript(script); + return new ScriptInfo(); + } + /** - * @see #loadScripts(OpenCloseable) + * Unloads all scripts present in the provided folder. + * @param folder The folder containing scripts to unload. + * @return Combined statistics for the unloaded scripts. + * This data is calculated by using {@link ScriptInfo#add(ScriptInfo)}. + * @deprecated Use {@link #unloadScripts(Set)}. + */ + @Deprecated + private static ScriptInfo unloadScripts(File folder) { + return unloadScripts(getScripts(folder)); + } + + /** + * Reloads all scripts in the given folder and its subfolders. + * @param folder A folder. + * @return Future of statistics of newly loaded scripts. + * @deprecated Use {@link #reloadScripts}. + */ + @Deprecated + public static CompletableFuture<ScriptInfo> reloadScripts(File folder, OpenCloseable openCloseable) { + unloadScripts(folder); + return loadScripts(loadStructures(folder), openCloseable); + } + + /** + * @deprecated Use <b>{@link #getLoadedScripts()}.size()</b>. + */ + @Deprecated + public static int loadedScripts() { + return getLoadedScripts().size(); + } + + /** + * @deprecated Use <b>{@link #getLoadedScripts()}</b> and <b>{@link Script#getStructures()}.size()</b>. + * Please note that a Structure may have multiple triggers, and this is only an estimate. + */ + @Deprecated + public static int loadedTriggers() { + int loaded = 0; + for (Script script : getLoadedScripts()) + loaded += script.getStructures().size(); + return loaded; + } + + /** + * @deprecated Use {@link #loadScripts(File, OpenCloseable)} */ @Deprecated static void loadScripts() { - if (!isAsync()) - disableScripts(); - loadScripts(OpenCloseable.EMPTY).join(); + unloadScripts(loadedScripts); + loadScripts(Skript.getInstance().getScriptsFolder(), OpenCloseable.EMPTY).join(); } - + /** - * @see #loadScripts(List, OpenCloseable) + * @deprecated Callers should not be using configs. Use {@link #loadScripts(Collection, OpenCloseable)}. */ @Deprecated public static ScriptInfo loadScripts(List<Config> configs) { return loadScripts(configs, OpenCloseable.EMPTY).join(); } - + /** - * @see #loadScripts(List, OpenCloseable) + * @deprecated Callers should not be using configs. Use {@link #loadScripts(Collection, OpenCloseable)}. * @see RetainingLogHandler */ @Deprecated @@ -1274,82 +1104,83 @@ public static ScriptInfo loadScripts(List<Config> configs, List<LogEntry> logOut logOut.addAll(logHandler.getLog()); } } - + /** - * @see #loadScripts(List, OpenCloseable) + * @deprecated Callers should not be using configs. Use {@link #loadScripts(Collection, OpenCloseable)}. */ @Deprecated public static ScriptInfo loadScripts(Config... configs) { return loadScripts(Arrays.asList(configs), OpenCloseable.EMPTY).join(); } - + /** - * @see #reloadScript(File, OpenCloseable) + * @deprecated Use {@link #reloadScript(Script, OpenCloseable)}. */ @Deprecated public static ScriptInfo reloadScript(File script) { return reloadScript(script, OpenCloseable.EMPTY).join(); } - + /** - * @see #reloadScripts(File, OpenCloseable) + * @deprecated Use {@link #reloadScripts(Set, OpenCloseable)}. */ @Deprecated public static ScriptInfo reloadScripts(File folder) { return reloadScripts(folder, OpenCloseable.EMPTY).join(); } - + /** - * @see ParserInstance#getHasDelayBefore() + * @deprecated Use {@link ParserInstance#getHasDelayBefore()}. */ @Deprecated public static Kleenean getHasDelayBefore() { return getParser().getHasDelayBefore(); } - + /** - * @see ParserInstance#setHasDelayBefore(Kleenean) + * @deprecated Use {@link ParserInstance#setHasDelayBefore(Kleenean)}. */ @Deprecated public static void setHasDelayBefore(Kleenean hasDelayBefore) { getParser().setHasDelayBefore(hasDelayBefore); } - + /** - * @see ParserInstance#getCurrentScript() + * @deprecated Use {@link ParserInstance#getCurrentScript()}. */ @Nullable @Deprecated public static Config getCurrentScript() { - return getParser().getCurrentScript(); + ParserInstance parser = getParser(); + return parser.isActive() ? parser.getCurrentScript().getConfig() : null; } - + /** - * @see ParserInstance#setCurrentScript(Config) + * @deprecated Addons should no longer be modifying this. */ @Deprecated public static void setCurrentScript(@Nullable Config currentScript) { getParser().setCurrentScript(currentScript); } - + /** - * @see ParserInstance#getCurrentSections() + * @deprecated Use {@link ParserInstance#getCurrentSections()}. */ @Deprecated public static List<TriggerSection> getCurrentSections() { return getParser().getCurrentSections(); } - + /** - * @see ParserInstance#setCurrentSections(List) + * @deprecated Use {@link ParserInstance#setCurrentSections(List)}. */ @Deprecated public static void setCurrentSections(List<TriggerSection> currentSections) { getParser().setCurrentSections(currentSections); } - + /** - * @see ParserInstance#getCurrentSections(Class) + * @deprecated Use {@link ParserInstance#getCurrentSections(Class)}. */ @Deprecated public static List<SecLoop> getCurrentLoops() { @@ -1357,61 +1188,73 @@ public static List<SecLoop> getCurrentLoops() { } /** - * Never use this method, it has no effect. + * @deprecated Never use this method, it has no effect. */ @Deprecated public static void setCurrentLoops(List<SecLoop> currentLoops) { } - + /** - * @see ParserInstance#getCurrentEventName() + * @deprecated Use {@link ParserInstance#getCurrentEventName()}. */ @Nullable @Deprecated public static String getCurrentEventName() { return getParser().getCurrentEventName(); } - + /** - * @see ParserInstance#setCurrentEvent(String, Class[]) + * @deprecated Use {@link ParserInstance#setCurrentEvent(String, Class[])}. */ @SafeVarargs @Deprecated public static void setCurrentEvent(String name, @Nullable Class<? extends Event>... events) { - getParser().setCurrentEvent(name, events); + if (events.length == 0) { + getParser().setCurrentEvent(name, CollectionUtils.array(ContextlessEvent.class)); + } else { + getParser().setCurrentEvent(name, events); + } } - + /** - * @see ParserInstance#deleteCurrentEvent() + * @deprecated Use {@link ParserInstance#deleteCurrentEvent()}. */ @Deprecated public static void deleteCurrentEvent() { getParser().deleteCurrentEvent(); } - + /** - * @see ParserInstance#isCurrentEvent(Class) + * @deprecated Use {@link ParserInstance#isCurrentEvent(Class)} */ @Deprecated public static boolean isCurrentEvent(@Nullable Class<? extends Event> event) { return getParser().isCurrentEvent(event); } - + /** - * @see ParserInstance#isCurrentEvent(Class[]) + * @deprecated Use {@link ParserInstance#isCurrentEvent(Class[])}. */ @SafeVarargs @Deprecated public static boolean isCurrentEvent(Class<? extends Event>... events) { return getParser().isCurrentEvent(events); } - + /** - * @see ParserInstance#getCurrentEvents() + * @deprecated Use {@link ParserInstance#getCurrentEvents()}. */ @Nullable @Deprecated public static Class<? extends Event>[] getCurrentEvents() { return getParser().getCurrentEvents(); } - + + /** + * @deprecated This method has no functionality, it just returns its input. + */ + @Deprecated + public static Config loadStructure(Config config) { + return config; + } + } diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 15f0cd34b2a..a147a0e358e 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -18,63 +18,6 @@ */ package ch.njol.skript; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.lang.Thread.UncaughtExceptionHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.logging.Filter; -import java.util.logging.Level; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.zip.ZipEntry; -import java.util.zip.ZipException; -import java.util.zip.ZipFile; - -import org.bstats.bukkit.Metrics; -import org.bstats.charts.SimplePie; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.Material; -import org.bukkit.Server; -import org.bukkit.command.CommandSender; -import org.bukkit.command.PluginCommand; -import org.bukkit.entity.Player; -import org.bukkit.event.Event; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerCommandPreprocessEvent; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.event.server.PluginDisableEvent; -import org.bukkit.event.server.ServerCommandEvent; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.PluginDescriptionFile; -import org.bukkit.plugin.java.JavaPlugin; -import org.eclipse.jdt.annotation.Nullable; - -import com.google.gson.Gson; - import ch.njol.skript.aliases.Aliases; import ch.njol.skript.bukkitutil.BurgerHelper; import ch.njol.skript.classes.ClassInfo; @@ -88,7 +31,6 @@ import ch.njol.skript.classes.data.JavaClasses; import ch.njol.skript.classes.data.SkriptClasses; import ch.njol.skript.command.Commands; -import ch.njol.skript.config.Config; import ch.njol.skript.doc.Documentation; import ch.njol.skript.events.EvtSkript; import ch.njol.skript.hooks.Hook; @@ -107,6 +49,7 @@ import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.skript.localization.Language; import ch.njol.skript.localization.Message; +import ch.njol.skript.localization.PluralizingArgsMessage; import ch.njol.skript.log.BukkitLoggerFilter; import ch.njol.skript.log.CountingLogHandler; import ch.njol.skript.log.ErrorDescLogHandler; @@ -126,6 +69,7 @@ import ch.njol.skript.update.ReleaseManifest; import ch.njol.skript.update.ReleaseStatus; import ch.njol.skript.update.UpdateManifest; +import ch.njol.skript.util.Date; import ch.njol.skript.util.EmptyStacktraceException; import ch.njol.skript.util.ExceptionUtils; import ch.njol.skript.util.FileUtils; @@ -141,9 +85,68 @@ import ch.njol.util.NullableChecker; import ch.njol.util.OpenCloseable; import ch.njol.util.StringUtils; -import ch.njol.util.coll.CollectionUtils; import ch.njol.util.coll.iterator.CheckedIterator; import ch.njol.util.coll.iterator.EnumerationIterable; +import com.google.gson.Gson; +import org.bstats.bukkit.Metrics; +import org.bstats.charts.SimplePie; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.Server; +import org.bukkit.command.CommandSender; +import org.bukkit.command.PluginCommand; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.server.PluginDisableEvent; +import org.bukkit.event.server.ServerCommandEvent; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.plugin.java.JavaPlugin; +import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.entry.EntryValidator; +import org.skriptlang.skript.lang.script.Script; +import org.skriptlang.skript.lang.structure.Structure; +import org.skriptlang.skript.lang.structure.StructureInfo; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.Thread.UncaughtExceptionHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.logging.Filter; +import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; // TODO meaningful error if someone uses an %expression with percent signs% outside of text or a variable @@ -229,8 +232,12 @@ public static Version getVersion() { return v; } - public final static Message m_invalid_reload = new Message("skript.invalid reload"), - m_finished_loading = new Message("skript.finished loading"); + public static final Message + m_invalid_reload = new Message("skript.invalid reload"), + m_finished_loading = new Message("skript.finished loading"), + m_no_errors = new Message("skript.no errors"), + m_no_scripts = new Message("skript.no scripts"); + private static final PluralizingArgsMessage m_scripts_loaded = new PluralizingArgsMessage("skript.scripts loaded"); public static ServerPlatform getServerPlatform() { if (classExists("net.glowstone.GlowServer")) { @@ -342,6 +349,23 @@ public static void disableHookRegistration(Class<? extends Hook<?>>... hooks) { } Collections.addAll(disabledHookRegistrations, hooks); } + + /** + * The folder containing all Scripts. + * Never reference this field directly. Use {@link #getScriptsFolder()}. + */ + @SuppressWarnings("NotNullFieldNotInitialized") + private File scriptsFolder; + + /** + * @return The folder containing all Scripts. + */ + public File getScriptsFolder() { + if (!scriptsFolder.isDirectory()) + //noinspection ResultOfMethodCallIgnored + scriptsFolder.mkdirs(); + return scriptsFolder; + } @Override public void onEnable() { @@ -368,18 +392,18 @@ public void onEnable() { if (!getDataFolder().isDirectory()) getDataFolder().mkdirs(); - - File scripts = new File(getDataFolder(), SCRIPTSFOLDER); + + scriptsFolder = new File(getDataFolder(), SCRIPTSFOLDER + File.separator); File config = new File(getDataFolder(), "config.sk"); File features = new File(getDataFolder(), "features.sk"); File lang = new File(getDataFolder(), "lang"); - if (!scripts.isDirectory() || !config.exists() || !features.exists() || !lang.exists()) { + if (!scriptsFolder.isDirectory() || !config.exists() || !features.exists() || !lang.exists()) { ZipFile f = null; try { boolean populateExamples = false; - if (!scripts.isDirectory()) { - if (!scripts.mkdirs()) - throw new IOException("Could not create the directory " + scripts); + if (!scriptsFolder.isDirectory()) { + if (!scriptsFolder.mkdirs()) + throw new IOException("Could not create the directory " + scriptsFolder); populateExamples = true; } @@ -395,14 +419,16 @@ public void onEnable() { if (e.isDirectory()) continue; File saveTo = null; - if (populateExamples && e.getName().startsWith(SCRIPTSFOLDER + "/")) { - String fileName = e.getName().substring(e.getName().lastIndexOf('/') + 1); - saveTo = new File(scripts, (fileName.startsWith("-") ? "" : "-") + fileName); + if (populateExamples && e.getName().startsWith(SCRIPTSFOLDER + File.separator)) { + String fileName = e.getName().substring(e.getName().lastIndexOf(File.separatorChar) + 1); + if (fileName.startsWith(ScriptLoader.DISABLED_SCRIPT_PREFIX)) + fileName = ScriptLoader.DISABLED_SCRIPT_PREFIX + fileName; + saveTo = new File(scriptsFolder, fileName); } else if (populateLanguageFiles - && e.getName().startsWith("lang/") + && e.getName().startsWith("lang" + File.separator) && e.getName().endsWith(".lang") - && !e.getName().endsWith("/default.lang")) { - String fileName = e.getName().substring(e.getName().lastIndexOf('/') + 1); + && !e.getName().endsWith(File.separator + "default.lang")) { + String fileName = e.getName().substring(e.getName().lastIndexOf(File.separatorChar) + 1); saveTo = new File(lang, fileName); } else if (e.getName().equals("config.sk")) { if (!config.exists()) @@ -504,7 +530,8 @@ public void onEnable() { ChatMessages.registerListeners(); try { - getAddonInstance().loadClasses("ch.njol.skript", "conditions", "effects", "events", "expressions", "entity", "sections"); + getAddonInstance().loadClasses("ch.njol.skript", + "conditions", "effects", "events", "expressions", "entity", "sections", "structures"); } catch (final Exception e) { exception(e, "Could not load required .class files: " + e.getLocalizedMessage()); setEnabled(false); @@ -636,8 +663,7 @@ protected void afterErrors() { errorCounter.start(); File testDir = TestMode.TEST_DIR.toFile(); assert testDir != null; - List<Config> configs = ScriptLoader.loadStructures(testDir); - ScriptLoader.loadScripts(configs, errorCounter).join(); + ScriptLoader.loadScripts(testDir, errorCounter); } finally { errorCounter.stop(); } @@ -729,30 +755,51 @@ protected void afterErrors() { /* * Start loading scripts */ - ScriptLoader.loadScripts(OpenCloseable.EMPTY) - .thenAccept(unused -> { - Skript.info(m_finished_loading.toString()); - - // EvtSkript.onSkriptStart should be called on main server thread - if (!ScriptLoader.isAsync()) { - EvtSkript.onSkriptStart(); - - // Suppresses the "can't keep up" warning after loading all scripts - // Only for non-asynchronous loading - Filter filter = record -> { - if (record == null) - return false; - return record.getMessage() == null - || !record.getMessage().toLowerCase(Locale.ENGLISH).startsWith("can't keep up!"); - }; - BukkitLoggerFilter.addFilter(filter); - Bukkit.getScheduler().scheduleSyncDelayedTask( - Skript.this, - () -> BukkitLoggerFilter.removeFilter(filter), - 1); - } else { - Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.this, - EvtSkript::onSkriptStart); + Date start = new Date(); + CountingLogHandler logHandler = new CountingLogHandler(Level.SEVERE); + + File scriptsFolder = getScriptsFolder(); + ScriptLoader.updateDisabledScripts(scriptsFolder.toPath()); + ScriptLoader.loadScripts(scriptsFolder, OpenCloseable.EMPTY) + .thenAccept(scriptInfo -> { + try { + if (logHandler.getCount() == 0) + Skript.info(m_no_errors.toString()); + if (scriptInfo.files == 0) + Skript.warning(m_no_scripts.toString()); + if (Skript.logNormal() && scriptInfo.files > 0) + Skript.info(m_scripts_loaded.toString( + scriptInfo.files, + scriptInfo.structures, + start.difference(new Date()) + )); + + Skript.info(m_finished_loading.toString()); + + // EvtSkript.onSkriptStart should be called on main server thread + if (!ScriptLoader.isAsync()) { + EvtSkript.onSkriptStart(); + + // Suppresses the "can't keep up" warning after loading all scripts + // Only for non-asynchronous loading + Filter filter = record -> { + if (record == null) + return false; + return record.getMessage() == null + || !record.getMessage().toLowerCase(Locale.ENGLISH).startsWith("can't keep up!"); + }; + BukkitLoggerFilter.addFilter(filter); + Bukkit.getScheduler().scheduleSyncDelayedTask( + Skript.this, + () -> BukkitLoggerFilter.removeFilter(filter), + 1); + } else { + Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.this, + EvtSkript::onSkriptStart); + } + } catch (Exception e) { + // Something went wrong, we need to make sure the exception is printed + throw Skript.exception(e); } }); @@ -1061,7 +1108,7 @@ private void beforeDisable() { partDisabled = true; EvtSkript.onSkriptStop(); // TODO [code style] warn user about delays in Skript stop events - ScriptLoader.disableScripts(); + ScriptLoader.unloadScripts(ScriptLoader.getLoadedScripts()); } @Override @@ -1224,13 +1271,13 @@ public static SkriptAddon getAddonInstance() { } return addon; } - + // ================ CONDITIONS & EFFECTS & SECTIONS ================ - private final static Collection<SyntaxElementInfo<? extends Condition>> conditions = new ArrayList<>(50); - private final static Collection<SyntaxElementInfo<? extends Effect>> effects = new ArrayList<>(50); - private final static Collection<SyntaxElementInfo<? extends Statement>> statements = new ArrayList<>(100); - private final static Collection<SyntaxElementInfo<? extends Section>> sections = new ArrayList<>(50); + private static final Collection<SyntaxElementInfo<? extends Condition>> conditions = new ArrayList<>(50); + private static final Collection<SyntaxElementInfo<? extends Effect>> effects = new ArrayList<>(50); + private static final Collection<SyntaxElementInfo<? extends Statement>> statements = new ArrayList<>(100); + private static final Collection<SyntaxElementInfo<? extends Section>> sections = new ArrayList<>(50); /** * registers a {@link Condition}. @@ -1339,8 +1386,8 @@ public boolean check(final @Nullable ExpressionInfo<?, ?> i) { } // ================ EVENTS ================ - - private final static Collection<SkriptEventInfo<?>> events = new ArrayList<>(50); + + private static final List<StructureInfo<? extends Structure>> structures = new ArrayList<>(10); /** * Registers an event. @@ -1352,13 +1399,9 @@ public boolean check(final @Nullable ExpressionInfo<?, ?> i) { * @param patterns Skript patterns to match this event * @return A SkriptEventInfo representing the registered event. Used to generate Skript's documentation. */ - public static <E extends SkriptEvent> SkriptEventInfo<E> registerEvent(final String name, final Class<E> c, final Class<? extends Event> event, final String... patterns) { - checkAcceptRegistrations(); - String originClassPath = Thread.currentThread().getStackTrace()[2].getClassName(); - assert originClassPath != null; - final SkriptEventInfo<E> r = new SkriptEventInfo<>(name, patterns, c, originClassPath, CollectionUtils.array(event)); - events.add(r); - return r; + @SuppressWarnings("unchecked") + public static <E extends SkriptEvent> SkriptEventInfo<E> registerEvent(String name, Class<E> c, Class<? extends Event> event, String... patterns) { + return registerEvent(name, c, new Class[] {event}, patterns); } /** @@ -1370,19 +1413,47 @@ public static <E extends SkriptEvent> SkriptEventInfo<E> registerEvent(final Str * @param patterns Skript patterns to match this event * @return A SkriptEventInfo representing the registered event. Used to generate Skript's documentation. */ - public static <E extends SkriptEvent> SkriptEventInfo<E> registerEvent(final String name, final Class<E> c, final Class<? extends Event>[] events, final String... patterns) { + public static <E extends SkriptEvent> SkriptEventInfo<E> registerEvent(String name, Class<E> c, Class<? extends Event>[] events, String... patterns) { checkAcceptRegistrations(); String originClassPath = Thread.currentThread().getStackTrace()[2].getClassName(); - assert originClassPath != null; - final SkriptEventInfo<E> r = new SkriptEventInfo<>(name, patterns, c, originClassPath, events); - Skript.events.add(r); + + String[] transformedPatterns = new String[patterns.length]; + for (int i = 0; i < patterns.length; i++) + transformedPatterns[i] = "[on] " + SkriptEvent.fixPattern(patterns[i]) + " [with priority (lowest|low|normal|high|highest|monitor)]"; + + SkriptEventInfo<E> r = new SkriptEventInfo<>(name, transformedPatterns, c, originClassPath, events); + structures.add(r); return r; } - + + public static <E extends Structure> void registerStructure(Class<E> c, String... patterns) { + checkAcceptRegistrations(); + String originClassPath = Thread.currentThread().getStackTrace()[2].getClassName(); + StructureInfo<E> structureInfo = new StructureInfo<>(patterns, c, originClassPath); + structures.add(structureInfo); + } + + public static <E extends Structure> void registerStructure(Class<E> c, EntryValidator entryValidator, String... patterns) { + checkAcceptRegistrations(); + String originClassPath = Thread.currentThread().getStackTrace()[2].getClassName(); + StructureInfo<E> structureInfo = new StructureInfo<>(patterns, c, originClassPath, entryValidator); + structures.add(structureInfo); + } + + /** + * Modifications made to the returned Collection will not be reflected in the events available for parsing. + */ public static Collection<SkriptEventInfo<?>> getEvents() { - return events; + // Only used in documentation generation, so generating a new list each time is fine + return (Collection<SkriptEventInfo<?>>) (Collection<?>) structures.stream() + .filter(info -> info instanceof SkriptEventInfo) + .collect(Collectors.toList()); } - + + public static List<StructureInfo<? extends Structure>> getStructures() { + return structures; + } + // ================ COMMANDS ================ /** @@ -1696,9 +1767,8 @@ public static EmptyStacktraceException exception(@Nullable Throwable cause, fina logEx("Current item: " + (item == null ? "null" : item.toString(null, true))); if (item != null && item.getTrigger() != null) { Trigger trigger = item.getTrigger(); - assert trigger != null; - File script = trigger.getScript(); - logEx("Current trigger: " + trigger.toString(null, true) + " (" + (script == null ? "null" : script.getName()) + ", line " + trigger.getLineNumber() + ")"); + Script script = trigger.getScript(); + logEx("Current trigger: " + trigger.toString(null, true) + " (" + (script == null ? "null" : script.getConfig().getFileName()) + ", line " + trigger.getLineNumber() + ")"); } logEx(); logEx("Thread: " + (thread == null ? Thread.currentThread() : thread).getName()); diff --git a/src/main/java/ch/njol/skript/SkriptCommand.java b/src/main/java/ch/njol/skript/SkriptCommand.java index e63c5a5f2bf..d29523cfd4d 100644 --- a/src/main/java/ch/njol/skript/SkriptCommand.java +++ b/src/main/java/ch/njol/skript/SkriptCommand.java @@ -18,25 +18,8 @@ */ package ch.njol.skript; -import java.io.File; -import java.io.IOException; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -import org.bukkit.Bukkit; -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.PluginDescriptionFile; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.aliases.Aliases; import ch.njol.skript.command.CommandHelp; -import ch.njol.skript.config.Config; import ch.njol.skript.doc.HTMLGenerator; import ch.njol.skript.localization.ArgsMessage; import ch.njol.skript.localization.Language; @@ -51,7 +34,23 @@ import ch.njol.skript.util.SkriptColor; import ch.njol.util.OpenCloseable; import ch.njol.util.StringUtils; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginDescriptionFile; +import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.script.Script; + +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; public class SkriptCommand implements CommandExecutor { @@ -59,7 +58,7 @@ public class SkriptCommand implements CommandExecutor { // TODO /skript scripts show/list - lists all enabled and/or disabled scripts in the scripts folder and/or subfolders (maybe add a pattern [using * and **]) // TODO document this command on the website - private static final CommandHelp skriptCommandHelp = new CommandHelp("<gray>/<gold>skript", SkriptColor.LIGHT_CYAN, CONFIG_NODE + ".help") + private static final CommandHelp SKRIPT_COMMAND_HELP = new CommandHelp("<gray>/<gold>skript", SkriptColor.LIGHT_CYAN, CONFIG_NODE + ".help") .add(new CommandHelp("reload", SkriptColor.DARK_RED) .add("all") .add("config") @@ -80,10 +79,13 @@ public class SkriptCommand implements CommandExecutor { ).add("help"); static { + // Add command to generate documentation if (TestMode.GEN_DOCS || new File(Skript.getInstance().getDataFolder() + "/doc-templates").exists()) - skriptCommandHelp.add("gen-docs"); - if (TestMode.DEV_MODE) // Add command to run individual tests - skriptCommandHelp.add("test"); + SKRIPT_COMMAND_HELP.add("gen-docs"); + + // Add command to run individual tests + if (TestMode.DEV_MODE) + SKRIPT_COMMAND_HELP.add("test"); } private static final ArgsMessage m_reloading = new ArgsMessage(CONFIG_NODE + ".reload.reloading"); @@ -115,79 +117,102 @@ private static void error(CommandSender sender, String what, Object... args) { what = args.length == 0 ? Language.get(CONFIG_NODE + "." + what) : PluralizingArgsMessage.format(Language.format(CONFIG_NODE + "." + what, args)); Skript.error(sender, StringUtils.fixCapitalization(what)); } - + @Override - @SuppressFBWarnings("REC_CATCH_EXCEPTION") - public boolean onCommand(@Nullable CommandSender sender, @Nullable Command command, @Nullable String label, @Nullable String[] args) { - if (sender == null || command == null || label == null || args == null) - throw new IllegalArgumentException(); - if (!skriptCommandHelp.test(sender, args)) + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!SKRIPT_COMMAND_HELP.test(sender, args)) return true; - try (RedirectingLogHandler logHandler = new RedirectingLogHandler(sender, "").start(); - TimingLogHandler timingLogHandler = new TimingLogHandler().start()) { + + try ( + RedirectingLogHandler logHandler = new RedirectingLogHandler(sender, "").start(); + TimingLogHandler timingLogHandler = new TimingLogHandler().start() + ) { + if (args[0].equalsIgnoreCase("reload")) { + if (args[1].equalsIgnoreCase("all")) { reloading(sender, "config, aliases and scripts"); SkriptConfig.load(); Aliases.clear(); Aliases.load(); - - if (!ScriptLoader.isAsync()) - ScriptLoader.disableScripts(); - - ScriptLoader.loadScripts(OpenCloseable.combine(logHandler, timingLogHandler)) - .thenAccept(unused -> - reloaded(sender, logHandler, timingLogHandler, "config, aliases and scripts")); - } else if (args[1].equalsIgnoreCase("scripts")) { + + ScriptLoader.unloadScripts(ScriptLoader.getLoadedScripts()); + ScriptLoader.loadScripts(Skript.getInstance().getScriptsFolder(), OpenCloseable.combine(logHandler, timingLogHandler)) + .thenAccept(info -> { + if (info.files == 0) + Skript.warning(Skript.m_no_scripts.toString()); + reloaded(sender, logHandler, timingLogHandler, "config, aliases and scripts"); + }); + } + + else if (args[1].equalsIgnoreCase("scripts")) { reloading(sender, "scripts"); - - if (!ScriptLoader.isAsync()) - ScriptLoader.disableScripts(); - - ScriptLoader.loadScripts(OpenCloseable.combine(logHandler, timingLogHandler)) - .thenAccept(unused -> - reloaded(sender, logHandler, timingLogHandler, "scripts")); - } else if (args[1].equalsIgnoreCase("config")) { + + ScriptLoader.unloadScripts(ScriptLoader.getLoadedScripts()); + ScriptLoader.loadScripts(Skript.getInstance().getScriptsFolder(), OpenCloseable.combine(logHandler, timingLogHandler)) + .thenAccept(info -> { + if (info.files == 0) + Skript.warning(Skript.m_no_scripts.toString()); + reloaded(sender, logHandler, timingLogHandler, "scripts"); + }); + } + + else if (args[1].equalsIgnoreCase("config")) { reloading(sender, "main config"); SkriptConfig.load(); reloaded(sender, logHandler, timingLogHandler, "main config"); - } else if (args[1].equalsIgnoreCase("aliases")) { + } + + else if (args[1].equalsIgnoreCase("aliases")) { reloading(sender, "aliases"); Aliases.clear(); Aliases.load(); reloaded(sender, logHandler, timingLogHandler, "aliases"); - } else { - File f = getScriptFromArgs(sender, args, 1); - if (f == null) + } + + else { // Reloading an individual Script or folder + File scriptFile = getScriptFromArgs(sender, args); + if (scriptFile == null) return true; - if (!f.isDirectory()) { - if (f.getName().startsWith("-")) { - info(sender, "reload.script disabled", f.getName().substring(1), StringUtils.join(args, " ", 1, args.length)); + + if (!scriptFile.isDirectory()) { + if (ScriptLoader.getDisabledScriptsFilter().accept(scriptFile)) { + info(sender, "reload.script disabled", scriptFile.getName().substring(ScriptLoader.DISABLED_SCRIPT_PREFIX_LENGTH), StringUtils.join(args, " ", 1, args.length)); return true; } - reloading(sender, "script", f.getName()); - ScriptLoader.reloadScript(f, OpenCloseable.combine(logHandler, timingLogHandler)) + + reloading(sender, "script", scriptFile.getName()); + + Script script = ScriptLoader.getScript(scriptFile); + if (script != null) + ScriptLoader.unloadScript(script); + ScriptLoader.loadScripts(scriptFile, OpenCloseable.combine(logHandler, timingLogHandler)) .thenAccept(scriptInfo -> - reloaded(sender, logHandler, timingLogHandler, "script", f.getName())); + reloaded(sender, logHandler, timingLogHandler, "script", scriptFile.getName()) + ); } else { - reloading(sender, "scripts in folder", f.getName()); - ScriptLoader.reloadScripts(f, OpenCloseable.combine(logHandler, timingLogHandler)) + final String fileName = scriptFile.getName(); + reloading(sender, "scripts in folder", fileName); + ScriptLoader.unloadScripts(ScriptLoader.getScripts(scriptFile)); + ScriptLoader.loadScripts(scriptFile, OpenCloseable.combine(logHandler, timingLogHandler)) .thenAccept(scriptInfo -> { - int enabled = scriptInfo.files; - if (enabled == 0) - info(sender, "reload.empty folder", f.getName()); - else - reloaded(sender, logHandler, timingLogHandler, "x scripts in folder", f.getName(), enabled); + if (scriptInfo.files == 0) { + info(sender, "reload.empty folder", fileName); + } else { + reloaded(sender, logHandler, timingLogHandler, "x scripts in folder", fileName, scriptInfo.files); + } }); } } - } else if (args[0].equalsIgnoreCase("enable")) { + + } + + else if (args[0].equalsIgnoreCase("enable")) { + if (args[1].equalsIgnoreCase("all")) { try { info(sender, "enable.all.enabling"); - File[] files = toggleScripts(new File(Skript.getInstance().getDataFolder(), Skript.SCRIPTSFOLDER), true).toArray(new File[0]); - List<Config> configs = ScriptLoader.loadStructures(files); - ScriptLoader.loadScripts(configs, logHandler) + ScriptLoader.loadScripts(toggleFiles(Skript.getInstance().getScriptsFolder(), true), logHandler) .thenAccept(scriptInfo -> { if (logHandler.numErrors() == 0) { info(sender, "enable.all.enabled"); @@ -198,30 +223,29 @@ public boolean onCommand(@Nullable CommandSender sender, @Nullable Command comma } catch (IOException e) { error(sender, "enable.all.io error", ExceptionUtils.toString(e)); } - } else { - File f = getScriptFromArgs(sender, args, 1); - if (f == null) + } + + else { + File scriptFile = getScriptFromArgs(sender, args); + if (scriptFile == null) return true; - if (!f.isDirectory()) { - if (!f.getName().startsWith("-")) { - info(sender, "enable.single.already enabled", f.getName(), StringUtils.join(args, " ", 1, args.length)); + + if (!scriptFile.isDirectory()) { + if (ScriptLoader.getLoadedScriptsFilter().accept(scriptFile)) { + info(sender, "enable.single.already enabled", scriptFile.getName(), StringUtils.join(args, " ", 1, args.length)); return true; } - + try { - f = FileUtils.move(f, new File(f.getParentFile(), f.getName().substring(1)), false); + scriptFile = toggleFile(scriptFile, true); } catch (IOException e) { - error(sender, "enable.single.io error", f.getName().substring(1), ExceptionUtils.toString(e)); + error(sender, "enable.single.io error", scriptFile.getName().substring(ScriptLoader.DISABLED_SCRIPT_PREFIX_LENGTH), ExceptionUtils.toString(e)); return true; } - - info(sender, "enable.single.enabling", f.getName()); - Config config = ScriptLoader.loadStructure(f); - if (config == null) - return true; - - String fileName = f.getName(); - ScriptLoader.loadScripts(Collections.singletonList(config), logHandler) + + final String fileName = scriptFile.getName(); + info(sender, "enable.single.enabling", fileName); + ScriptLoader.loadScripts(scriptFile, logHandler) .thenAccept(scriptInfo -> { if (logHandler.numErrors() == 0) { info(sender, "enable.single.enabled", fileName); @@ -229,28 +253,23 @@ public boolean onCommand(@Nullable CommandSender sender, @Nullable Command comma error(sender, "enable.single.error", fileName, logHandler.numErrors()); } }); - return true; } else { - Collection<File> scripts; + Collection<File> scriptFiles; try { - scripts = toggleScripts(f, true); + scriptFiles = toggleFiles(scriptFile, true); } catch (IOException e) { - error(sender, "enable.folder.io error", f.getName(), ExceptionUtils.toString(e)); + error(sender, "enable.folder.io error", scriptFile.getName(), ExceptionUtils.toString(e)); return true; } - if (scripts.isEmpty()) { - info(sender, "enable.folder.empty", f.getName()); + + if (scriptFiles.isEmpty()) { + info(sender, "enable.folder.empty", scriptFile.getName()); return true; } - info(sender, "enable.folder.enabling", f.getName(), scripts.size()); - File[] ss = scripts.toArray(new File[0]); - - List<Config> configs = ScriptLoader.loadStructures(ss); - if (configs.size() == 0) - return true; - - String fileName = f.getName(); - ScriptLoader.loadScripts(configs, logHandler) + + final String fileName = scriptFile.getName(); + info(sender, "enable.folder.enabling", fileName, scriptFiles.size()); + ScriptLoader.loadScripts(scriptFiles, logHandler) .thenAccept(scriptInfo -> { if (logHandler.numErrors() == 0) { info(sender, "enable.folder.enabled", fileName, scriptInfo.files); @@ -258,59 +277,70 @@ public boolean onCommand(@Nullable CommandSender sender, @Nullable Command comma error(sender, "enable.folder.error", fileName, logHandler.numErrors()); } }); - return true; } } - } else if (args[0].equalsIgnoreCase("disable")) { + + } + + else if (args[0].equalsIgnoreCase("disable")) { + if (args[1].equalsIgnoreCase("all")) { - ScriptLoader.disableScripts(); + ScriptLoader.unloadScripts(ScriptLoader.getLoadedScripts()); try { - toggleScripts(new File(Skript.getInstance().getDataFolder(), Skript.SCRIPTSFOLDER), false); + toggleFiles(Skript.getInstance().getScriptsFolder(), false); info(sender, "disable.all.disabled"); } catch (IOException e) { error(sender, "disable.all.io error", ExceptionUtils.toString(e)); } - } else { - File f = getScriptFromArgs(sender, args, 1); - if (f == null) // TODO allow disabling deleted/renamed scripts + } + + else { + File scriptFile = getScriptFromArgs(sender, args); + if (scriptFile == null) // TODO allow disabling deleted/renamed scripts return true; - - if (!f.isDirectory()) { - if (f.getName().startsWith("-")) { - info(sender, "disable.single.already disabled", f.getName().substring(1)); + + if (!scriptFile.isDirectory()) { + if (ScriptLoader.getDisabledScriptsFilter().accept(scriptFile)) { + info(sender, "disable.single.already disabled", scriptFile.getName().substring(ScriptLoader.DISABLED_SCRIPT_PREFIX_LENGTH)); return true; } - - ScriptLoader.unloadScript(f); - + + Script script = ScriptLoader.getScript(scriptFile); + if (script != null) + ScriptLoader.unloadScript(script); + + String fileName = scriptFile.getName(); + try { - FileUtils.move(f, new File(f.getParentFile(), "-" + f.getName()), false); + toggleFile(scriptFile, false); } catch (IOException e) { - error(sender, "disable.single.io error", f.getName(), ExceptionUtils.toString(e)); + error(sender, "disable.single.io error", scriptFile.getName(), ExceptionUtils.toString(e)); return true; } - info(sender, "disable.single.disabled", f.getName()); + info(sender, "disable.single.disabled", fileName); } else { + ScriptLoader.unloadScripts(ScriptLoader.getScripts(scriptFile)); + Collection<File> scripts; try { - scripts = toggleScripts(f, false); + scripts = toggleFiles(scriptFile, false); } catch (IOException e) { - error(sender, "disable.folder.io error", f.getName(), ExceptionUtils.toString(e)); + error(sender, "disable.folder.io error", scriptFile.getName(), ExceptionUtils.toString(e)); return true; } + if (scripts.isEmpty()) { - info(sender, "disable.folder.empty", f.getName()); + info(sender, "disable.folder.empty", scriptFile.getName()); return true; } - - for (File script : scripts) - ScriptLoader.unloadScript(new File(script.getParentFile(), script.getName().substring(1))); - - info(sender, "disable.folder.disabled", f.getName(), scripts.size()); + + info(sender, "disable.folder.disabled", scriptFile.getName(), scripts.size()); } - return true; } - } else if (args[0].equalsIgnoreCase("update")) { + + } + + else if (args[0].equalsIgnoreCase("update")) { SkriptUpdater updater = Skript.getInstance().getUpdater(); if (updater == null) { // Oh. That is bad Skript.info(sender, "" + SkriptUpdater.m_internal_error); @@ -323,35 +353,48 @@ public boolean onCommand(@Nullable CommandSender sender, @Nullable Command comma } else if (args[1].equalsIgnoreCase("download")) { updater.updateCheck(sender); } - } else if (args[0].equalsIgnoreCase("info")) { + } + + else if (args[0].equalsIgnoreCase("info")) { info(sender, "info.aliases"); info(sender, "info.documentation"); info(sender, "info.tutorials"); info(sender, "info.server", Bukkit.getVersion()); - info(sender, "info.version", Skript.getVersion() + " (" + Skript.getInstance().getUpdater().getCurrentRelease().flavor + ")"); - info(sender, "info.addons", Skript.getAddons().isEmpty() ? "None" : ""); - for (SkriptAddon addon : Skript.getAddons()) { + + SkriptUpdater updater = Skript.getInstance().getUpdater(); + if (updater != null) { + info(sender, "info.version", Skript.getVersion() + " (" + updater.getCurrentRelease().flavor + ")"); + } else { + info(sender, "info.version", Skript.getVersion()); + } + + Collection<SkriptAddon> addons = Skript.getAddons(); + info(sender, "info.addons", addons.isEmpty() ? "None" : ""); + for (SkriptAddon addon : addons) { PluginDescriptionFile desc = addon.plugin.getDescription(); String web = desc.getWebsite(); Skript.info(sender, " - " + desc.getFullName() + (web != null ? " (" + web + ")" : "")); } + List<String> dependencies = Skript.getInstance().getDescription().getSoftDepend(); boolean dependenciesFound = false; for (String dep : dependencies) { // Check if any dependency is found in the server plugins - if (Bukkit.getPluginManager().getPlugin(dep) != null) - dependenciesFound = true; - } - info(sender, "info.dependencies", dependenciesFound ? "" : "None"); - for (String dep : dependencies) { Plugin plugin = Bukkit.getPluginManager().getPlugin(dep); if (plugin != null) { + if (!dependenciesFound) { + dependenciesFound = true; + info(sender, "info.dependencies", ""); + } String ver = plugin.getDescription().getVersion(); Skript.info(sender, " - " + plugin.getName() + " v" + ver); } } - } else if (args[0].equalsIgnoreCase("help")) { - skriptCommandHelp.showHelp(sender); - } else if (args[0].equalsIgnoreCase("gen-docs")) { + if (!dependenciesFound) + info(sender, "info.dependencies", "None"); + + } + + else if (args[0].equalsIgnoreCase("gen-docs")) { File templateDir = new File(Skript.getInstance().getDataFolder() + "/doc-templates/"); if (!templateDir.exists()) { Skript.error(sender, "Cannot generate docs! Documentation templates not found at 'plugins/Skript/doc-templates/'"); @@ -364,44 +407,53 @@ public boolean onCommand(@Nullable CommandSender sender, @Nullable Command comma Skript.info(sender, "Generating docs..."); generator.generate(); // Try to generate docs... hopefully Skript.info(sender, "Documentation generated!"); - } else if (args[0].equalsIgnoreCase("test") && TestMode.DEV_MODE) { - File script; + } + + else if (args[0].equalsIgnoreCase("test") && TestMode.DEV_MODE) { + File scriptFile; if (args.length == 1) { - script = TestMode.lastTestFile; - if (script == null) { + scriptFile = TestMode.lastTestFile; + if (scriptFile == null) { Skript.error(sender, "No test script has been run yet!"); return true; } } else { - script = TestMode.TEST_DIR.resolve( - Arrays.stream(args).skip(1).collect(Collectors.joining(" ")) + ".sk").toFile(); - TestMode.lastTestFile = script; + scriptFile = TestMode.TEST_DIR.resolve( + Arrays.stream(args).skip(1).collect(Collectors.joining(" ")) + ".sk" + ).toFile(); + TestMode.lastTestFile = scriptFile; } - if (!script.exists()) { - Skript.info(sender, "Test script doesn't exist!"); + + if (!scriptFile.exists()) { + Skript.error(sender, "Test script doesn't exist!"); return true; } - - Config config = ScriptLoader.loadStructure(script); - if (config != null) { - ScriptLoader.loadScripts(Collections.singletonList(config), logHandler) - .thenAccept(scriptInfo -> - // Code should run on server thread - Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.getInstance(), () -> { - Bukkit.getPluginManager().callEvent(new SkriptTestEvent()); // Run it - ScriptLoader.disableScripts(); // Clean state for next test - - // Get results and show them - String[] lines = TestTracker.collectResults().createReport().split("\n"); - for (String line : lines) { - Skript.info(sender, line); - } - })); - } + + ScriptLoader.loadScripts(scriptFile, logHandler) + .thenAccept(scriptInfo -> + // Code should run on server thread + Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.getInstance(), () -> { + Bukkit.getPluginManager().callEvent(new SkriptTestEvent()); // Run it + ScriptLoader.unloadScripts(ScriptLoader.getLoadedScripts()); + + // Get results and show them + String[] lines = TestTracker.collectResults().createReport().split("\n"); + for (String line : lines) { + Skript.info(sender, line); + } + }) + ); + } + + else if (args[0].equalsIgnoreCase("help")) { + SKRIPT_COMMAND_HELP.showHelp(sender); } + } catch (Exception e) { + //noinspection ThrowableNotThrown Skript.exception(e, "Exception occurred in Skript's main command", "Used command: /" + label + " " + StringUtils.join(args, " ")); } + return true; } @@ -409,46 +461,80 @@ public boolean onCommand(@Nullable CommandSender sender, @Nullable Command comma private static final ArgsMessage m_invalid_folder = new ArgsMessage(CONFIG_NODE + ".invalid folder"); @Nullable - private static File getScriptFromArgs(CommandSender sender, String[] args, int start) { - String script = StringUtils.join(args, " ", start, args.length); + private static File getScriptFromArgs(CommandSender sender, String[] args) { + String script = StringUtils.join(args, " ", 1, args.length); File f = getScriptFromName(script); - if (f == null){ - Skript.error(sender, (script.endsWith("/") || script.endsWith("\\") ? m_invalid_folder : m_invalid_script).toString(script)); + if (f == null) { + // Always allow '/' and '\' regardless of OS + boolean directory = script.endsWith("/") || script.endsWith("\\") || script.endsWith(File.separator); + Skript.error(sender, (directory ? m_invalid_folder : m_invalid_script).toString(script)); return null; } return f; } @Nullable - public static File getScriptFromName(String script){ - boolean isFolder = script.endsWith("/") || script.endsWith("\\"); - if (isFolder) { + public static File getScriptFromName(String script) { + if (script.endsWith("/") || script.endsWith("\\")) { // Always allow '/' and '\' regardless of OS script = script.replace('/', File.separatorChar).replace('\\', File.separatorChar); } else if (!StringUtils.endsWithIgnoreCase(script, ".sk")) { int dot = script.lastIndexOf('.'); - if (dot > 0 && !script.substring(dot+1).equals("")) { + if (dot > 0 && !script.substring(dot + 1).equals("")) return null; - } script = script + ".sk"; } - if (script.startsWith("-")) - script = script.substring(1); - File f = new File(Skript.getInstance().getDataFolder(), Skript.SCRIPTSFOLDER + File.separator + script); - if (!f.exists()) { - f = new File(f.getParentFile(), "-" + f.getName()); - if (!f.exists()) { + + if (script.startsWith(ScriptLoader.DISABLED_SCRIPT_PREFIX)) + script = script.substring(ScriptLoader.DISABLED_SCRIPT_PREFIX_LENGTH); + + File scriptFile = new File(Skript.getInstance().getScriptsFolder(), script); + if (!scriptFile.exists()) { + scriptFile = new File(scriptFile.getParentFile(), ScriptLoader.DISABLED_SCRIPT_PREFIX + scriptFile.getName()); + if (!scriptFile.exists()) { return null; } } - return f; + try { + return scriptFile.getCanonicalFile(); + } catch (IOException e) { + throw Skript.exception(e, "An exception occurred while trying to get the script file from the string '" + script + "'"); + } + } + + private static File toggleFile(File file, boolean enable) throws IOException { + if (enable) + return FileUtils.move( + file, + new File(file.getParentFile(), file.getName().substring(ScriptLoader.DISABLED_SCRIPT_PREFIX_LENGTH)), + false + ); + return FileUtils.move( + file, + new File(file.getParentFile(), ScriptLoader.DISABLED_SCRIPT_PREFIX + file.getName()), + false + ); } - private static Collection<File> toggleScripts(File folder, boolean enable) throws IOException { - return FileUtils.renameAll(folder, name -> { - if (StringUtils.endsWithIgnoreCase(name, ".sk") && name.startsWith("-") == enable) - return enable ? name.substring(1) : "-" + name; - return null; - }); + private static Collection<File> toggleFiles(File folder, boolean enable) throws IOException { + FileFilter filter = enable ? ScriptLoader.getDisabledScriptsFilter() : ScriptLoader.getLoadedScriptsFilter(); + + List<File> changed = new ArrayList<>(); + for (File file : folder.listFiles()) { + if (file.isDirectory()) { + changed.addAll(toggleFiles(file, enable)); + } else { + if (filter.accept(file)) { + String fileName = file.getName(); + changed.add(FileUtils.move( + file, + new File(file.getParentFile(), enable ? fileName.substring(ScriptLoader.DISABLED_SCRIPT_PREFIX_LENGTH) : ScriptLoader.DISABLED_SCRIPT_PREFIX + fileName), + false + )); + } + } + } + + return changed; } } diff --git a/src/main/java/ch/njol/skript/SkriptCommandTabCompleter.java b/src/main/java/ch/njol/skript/SkriptCommandTabCompleter.java index 10cc7649a6f..177a8e319ea 100644 --- a/src/main/java/ch/njol/skript/SkriptCommandTabCompleter.java +++ b/src/main/java/ch/njol/skript/SkriptCommandTabCompleter.java @@ -26,68 +26,85 @@ import javax.annotation.Nullable; import java.io.File; -import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.stream.Stream; public class SkriptCommandTabCompleter implements TabCompleter { - + @Override @Nullable public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) { ArrayList<String> options = new ArrayList<>(); - if (!command.getName().equalsIgnoreCase("skript")) { + if (!command.getName().equalsIgnoreCase("skript")) return null; - } if (args[0].equalsIgnoreCase("update") && args.length == 2) { options.add("check"); options.add("changes"); options.add("download"); - } else if (args[0].matches("(?i)(reload|disable|enable)") && args.length == 2) { - File scripts = new File(Skript.getInstance().getDataFolder(), Skript.SCRIPTSFOLDER); - String scriptArg = StringUtils.join(args, " ", 1, args.length); + } else if (args[0].matches("(?i)(reload|disable|enable)") && args.length >= 2) { + File scripts = Skript.getInstance().getScriptsFolder(); + String scriptsPathString = scripts.toPath().toString(); + int scriptsPathLength = scriptsPathString.length(); + + String scriptArg = StringUtils.join(args, " ", 1, args.length); String fs = File.separator; - - try { - // Live update, this will get all old and new (even not loaded) scripts - Files.walk(scripts.toPath()) - .map(Path::toFile) - .filter(f -> (!f.isDirectory() && StringUtils.endsWithIgnoreCase(f.getName(), ".sk")) || - f.isDirectory()) // filter folders and skript files only - .filter(f -> { // Filtration for enable, disable and reload - if (args[0].equalsIgnoreCase("enable")) - return f.getName().startsWith("-"); - else // reload & disable both accepts only non-hyphened files and not hidden folders - return !f.getAbsolutePath().matches(".*?(\\\\-|/-|^-).*") && (f.isDirectory() && - !f.getAbsolutePath().matches(".*?(\\\\\\.|/\\.|^\\.).*") || !f.isDirectory()); - }) - .filter(f -> { // Autocomplete incomplete script name arg - return scriptArg.length() > 0 ? f.getName().startsWith(scriptArg) : true; - }) + + boolean enable = args[0].equalsIgnoreCase("enable"); + + // Live update, this will get all old and new (even not loaded) scripts + // TODO Find a better way for caching, it isn't exactly ideal to be calling this method constantly + try (Stream<Path> files = Files.walk(scripts.toPath())) { + files.map(Path::toFile) .forEach(f -> { - if (!f.toString().equals(scripts.toString())) - options.add(f.toString() - .replace(scripts.toPath() + (!f.isDirectory() && f.getParentFile().toPath().toString() - .equals(scripts.toPath().toString()) ? fs : ""), "") // Extract file short path, and remove '/' from the beginning of files in root only - + (f.isDirectory() && f.toString().length() > 0 ? fs : "")); // add File.separator at the end of directories - }); - - // TODO handle file permissions - } catch (IOException e) { - e.printStackTrace(); + if (!(enable ? ScriptLoader.getDisabledScriptsFilter() : ScriptLoader.getLoadedScriptsFilter()).accept(f)) + return; + + String fileString = f.toString().substring(scriptsPathLength); + if (fileString.isEmpty()) + return; + + if (f.isDirectory()) { + fileString = fileString + fs; // Add file separator at the end of directories + } else if (f.getParentFile().toPath().toString().equals(scriptsPathString)) { + fileString = fileString.substring(1); // Remove file separator from the beginning of files or directories in root only + if (fileString.isEmpty()) + return; + } + + // Make sure the user's argument matches with the file's name or beginning of file path + if (scriptArg.length() > 0 && !f.getName().startsWith(scriptArg) && !fileString.startsWith(scriptArg)) + return; + + // Trim off previous arguments if needed + if (args.length > 2 && fileString.length() >= scriptArg.length()) + fileString = fileString.substring(scriptArg.lastIndexOf(" ") + 1); + + // Just in case + if (fileString.isEmpty()) + return; + + options.add(fileString); + }); + } catch (Exception e) { + //noinspection ThrowableNotThrown + Skript.exception(e, "An error occurred while trying to update the list of disabled scripts!"); } // These will be added even if there are incomplete script arg - options.add("all"); - if (args[0].equalsIgnoreCase("reload")) { - options.add("config"); - options.add("aliases"); - options.add("scripts"); + if (args.length == 2) { + options.add("all"); + if (args[0].equalsIgnoreCase("reload")) { + options.add("config"); + options.add("aliases"); + options.add("scripts"); + } } + } else if (args.length == 1) { options.add("help"); options.add("reload"); @@ -95,15 +112,13 @@ public List<String> onTabComplete(CommandSender sender, Command command, String options.add("disable"); options.add("update"); options.add("info"); - if (new File(Skript.getInstance().getDataFolder() + "/doc-templates").exists()) { + if (new File(Skript.getInstance().getDataFolder() + "/doc-templates").exists()) options.add("gen-docs"); - } - if (TestMode.DEV_MODE) { + if (TestMode.DEV_MODE) options.add("test"); - } } return options; } - + } diff --git a/src/main/java/ch/njol/skript/SkriptEventHandler.java b/src/main/java/ch/njol/skript/SkriptEventHandler.java index c3782e498c7..1707b958cca 100644 --- a/src/main/java/ch/njol/skript/SkriptEventHandler.java +++ b/src/main/java/ch/njol/skript/SkriptEventHandler.java @@ -18,9 +18,6 @@ */ package ch.njol.skript; -import ch.njol.skript.ScriptLoader.ScriptInfo; -import ch.njol.skript.command.Commands; -import ch.njol.skript.lang.SelfRegisteringSkriptEvent; import ch.njol.skript.lang.Trigger; import ch.njol.skript.timings.SkriptTimings; import ch.njol.util.NonNullPair; @@ -40,10 +37,8 @@ import org.bukkit.plugin.RegisteredListener; import org.eclipse.jdt.annotation.Nullable; -import java.io.File; import java.lang.reflect.Method; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -51,13 +46,12 @@ import java.util.Map; import java.util.Set; -/** - * @author Peter Güttinger - */ -public abstract class SkriptEventHandler { +public final class SkriptEventHandler { + + private SkriptEventHandler() { } /** - * A event listener for one priority. + * An event listener for one priority. * Also stores the registered events for this listener, and * the {@link EventExecutor} to be used with this listener. */ @@ -65,9 +59,7 @@ public static class PriorityListener implements Listener { public final EventPriority priority; - public final EventExecutor executor = (listener, event) -> { - check(event, ((PriorityListener) listener).priority); - }; + public final EventExecutor executor = (listener, event) -> check(event, ((PriorityListener) listener).priority); public PriorityListener(EventPriority priority) { this.priority = priority; @@ -76,7 +68,7 @@ public PriorityListener(EventPriority priority) { } /** - * Stores one {@link PriorityListener} per {@link EventPriority} + * Stores one {@link PriorityListener} per {@link EventPriority}. */ private static final PriorityListener[] listeners; @@ -88,19 +80,32 @@ public PriorityListener(EventPriority priority) { } } + /** + * A list tracking what Triggers are paired with what Events. + */ private static final List<NonNullPair<Class<? extends Event>, Trigger>> triggers = new ArrayList<>(); - private static final List<Trigger> selfRegisteredTriggers = new ArrayList<>(); - + /** + * A utility method to get all Triggers paired with the provided Event class. + * @param event The event to find pairs from. + * @return An iterator containing all Triggers paired with the provided Event class. + */ private static Iterator<Trigger> getTriggers(Class<? extends Event> event) { HandlerList eventHandlerList = getHandlerList(event); - + assert eventHandlerList != null; // It had one at some point so this should remain true return new ArrayList<>(triggers).stream() .filter(pair -> pair.getFirst().isAssignableFrom(event) && eventHandlerList == getHandlerList(pair.getFirst())) .map(NonNullPair::getSecond) .iterator(); } + /** + * This method is used for validating that the provided Event may be handled by Skript. + * If validation is successful, all Triggers associated with the provided Event are executed. + * A Trigger will only be executed if its priority matches the provided EventPriority. + * @param e The Event to check. + * @param priority The priority of the Event. + */ private static void check(Event e, EventPriority priority) { Iterator<Trigger> ts = getTriggers(e.getClass()); if (!ts.hasNext()) @@ -151,14 +156,24 @@ private static void check(Event e, EventPriority priority) { private static long startEvent; - public static void logEventStart(Event e) { + /** + * Logs that the provided Event has started. + * Requires {@link Skript#logVeryHigh()} to be true to log anything. + * @param event The Event that started. + */ + public static void logEventStart(Event event) { startEvent = System.nanoTime(); if (!Skript.logVeryHigh()) return; Skript.info(""); - Skript.info("== " + e.getClass().getName() + " =="); + Skript.info("== " + event.getClass().getName() + " =="); } + /** + * Logs that the last logged Event start has ended. + * Includes the number of milliseconds execution took. + * Requires {@link Skript#logVeryHigh()} to be true to log anything. + */ public static void logEventEnd() { if (!Skript.logVeryHigh()) return; @@ -167,100 +182,96 @@ public static void logEventEnd() { private static long startTrigger; - public static void logTriggerStart(Trigger t) { + /** + * Logs that the provided Trigger has begun execution. + * Requires {@link Skript#logVeryHigh()} to be true. + * @param trigger The Trigger that execution has begun for. + */ + public static void logTriggerStart(Trigger trigger) { startTrigger = System.nanoTime(); if (!Skript.logVeryHigh()) return; - Skript.info("# " + t.getName()); + Skript.info("# " + trigger.getName()); } + /** + * Logs that the last logged Trigger execution has ended. + * Includes the number of milliseconds execution took. + * Requires {@link Skript#logVeryHigh()} to be true to log anything. + */ public static void logTriggerEnd(Trigger t) { if (!Skript.logVeryHigh()) return; Skript.info("# " + t.getName() + " took " + 1. * (System.nanoTime() - startTrigger) / 1000000. + " milliseconds"); } - public static void addTrigger(Class<? extends Event>[] events, Trigger trigger) { - for (Class<? extends Event> e : events) { - triggers.add(new NonNullPair<>(e, trigger)); - } - } + /** + * @deprecated This method no longer does anything as self registered Triggers + * are unloaded when the {@link ch.njol.skript.lang.SkriptEvent} is unloaded (no need to keep tracking them here). + */ + @Deprecated + public static void addSelfRegisteringTrigger(Trigger t) { } /** - * Stores a self registered trigger to allow for it to be unloaded later on. - * - * @param t Trigger that has already been registered to its event + * A utility method that calls {@link #registerBukkitEvent(Trigger, Class)} for each Event class provided. + * For specific details of the process, see the javadoc of that method. + * @param trigger The Trigger to run when the Event occurs. + * @param events The Event to listen for. + * @see #registerBukkitEvent(Trigger, Class) + * @see #unregisterBukkitEvents(Trigger) */ - public static void addSelfRegisteringTrigger(Trigger t) { - assert t.getEvent() instanceof SelfRegisteringSkriptEvent; - selfRegisteredTriggers.add(t); + public static void registerBukkitEvents(Trigger trigger, Class<? extends Event>[] events) { + for (Class<? extends Event> event : events) + registerBukkitEvent(trigger, event); } - static ScriptInfo removeTriggers(File script) { - ScriptInfo info = new ScriptInfo(); - info.files = 1; - - int previousSize = triggers.size(); - triggers.removeIf(pair -> script.equals(pair.getSecond().getScript())); - info.triggers += previousSize - triggers.size(); - - for (int i = 0; i < selfRegisteredTriggers.size(); i++) { - Trigger t = selfRegisteredTriggers.get(i); - if (script.equals(t.getScript())) { - info.triggers++; - ((SelfRegisteringSkriptEvent) t.getEvent()).unregister(t); - selfRegisteredTriggers.remove(i); - i--; + /** + * Registers a {@link PriorityListener} with Bukkit for the provided Event. + * Marks that the provided Trigger should be executed when the provided Event occurs. + * @param trigger The Trigger to run when the Event occurs. + * @param event The Event to listen for. + * @see #registerBukkitEvents(Trigger, Class[]) + * @see #unregisterBukkitEvents(Trigger) + */ + public static void registerBukkitEvent(Trigger trigger, Class<? extends Event> event) { + HandlerList handlerList = getHandlerList(event); + if (handlerList == null) + return; + + triggers.add(new NonNullPair<>(event, trigger)); + + EventPriority priority = trigger.getEvent().getEventPriority(); + PriorityListener listener = listeners[priority.ordinal()]; + EventExecutor executor = listener.executor; + + // PlayerInteractEntityEvent has a subclass we need for armor stands + if (event.equals(PlayerInteractEntityEvent.class)) { + if (!isEventRegistered(handlerList, priority)) { + Bukkit.getPluginManager().registerEvent(event, listener, priority, executor, Skript.getInstance()); + Bukkit.getPluginManager().registerEvent(PlayerInteractAtEntityEvent.class, listener, priority, executor, Skript.getInstance()); } + return; } - info.commands = Commands.unregisterCommands(script); + if (event.equals(PlayerInteractAtEntityEvent.class) || event.equals(PlayerArmorStandManipulateEvent.class)) + return; // Ignore, registered above - return info; + if (!isEventRegistered(handlerList, priority)) // Check if event is registered + Bukkit.getPluginManager().registerEvent(event, listener, priority, executor, Skript.getInstance()); } - static void removeAllTriggers() { - triggers.clear(); - for (Trigger t : selfRegisteredTriggers) - ((SelfRegisteringSkriptEvent) t.getEvent()).unregisterAll(); - selfRegisteredTriggers.clear(); + /** + * Unregisters all events tied to the provided Trigger. + * @param trigger The Trigger to unregister events for. + */ + public static void unregisterBukkitEvents(Trigger trigger) { + triggers.removeIf(pair -> pair.getSecond() == trigger); } /** - * Registers event handlers for all events which currently loaded - * triggers are using. + * Events which are listened even if they are cancelled. */ - static void registerBukkitEvents() { - for (NonNullPair<Class<? extends Event>, Trigger> pair : triggers) { - assert pair.getFirst() != null; - Class<? extends Event> e = pair.getFirst(); - - EventPriority priority; - priority = pair.getSecond().getEvent().getEventPriority(); - - PriorityListener listener = listeners[priority.ordinal()]; - EventExecutor executor = listener.executor; - - HandlerList handlerList = getHandlerList(e); - - if (handlerList == null) - continue; - - // PlayerInteractEntityEvent has a subclass we need for armor stands - if (e.equals(PlayerInteractEntityEvent.class)) { - if (!isEventRegistered(handlerList, priority)) { - Bukkit.getPluginManager().registerEvent(e, listener, priority, executor, Skript.getInstance()); - Bukkit.getPluginManager().registerEvent(PlayerInteractAtEntityEvent.class, listener, priority, executor, Skript.getInstance()); - } - continue; - } - if (e.equals(PlayerInteractAtEntityEvent.class) || e.equals(PlayerArmorStandManipulateEvent.class)) - continue; // Ignore, registered above - - if (!isEventRegistered(handlerList, priority)) // Check if event is registered - Bukkit.getPluginManager().registerEvent(e, listener, priority, executor, Skript.getInstance()); - } - } + public static final Set<Class<? extends Event>> listenCancelled = new HashSet<>(); /** * A cache for the getHandlerList methods of Event classes @@ -304,9 +315,11 @@ private static Method getHandlerListMethod_i(Class<? extends Event> eventClass) try { return eventClass.getDeclaredMethod("getHandlerList"); } catch (NoSuchMethodException e) { - if (eventClass.getSuperclass() != null + if ( + eventClass.getSuperclass() != null && !eventClass.getSuperclass().equals(Event.class) - && Event.class.isAssignableFrom(eventClass.getSuperclass())) { + && Event.class.isAssignableFrom(eventClass.getSuperclass()) + ) { return getHandlerListMethod(eventClass.getSuperclass().asSubclass(Event.class)); } else { return null; @@ -315,17 +328,16 @@ private static Method getHandlerListMethod_i(Class<? extends Event> eventClass) } private static boolean isEventRegistered(HandlerList handlerList, EventPriority priority) { - for (RegisteredListener rl : handlerList.getRegisteredListeners()) { - Listener l = rl.getListener(); - if (rl.getPlugin() == Skript.getInstance() && l instanceof PriorityListener && ((PriorityListener) l).priority == priority) + for (RegisteredListener registeredListener : handlerList.getRegisteredListeners()) { + Listener listener = registeredListener.getListener(); + if ( + registeredListener.getPlugin() == Skript.getInstance() + && listener instanceof PriorityListener + && ((PriorityListener) listener).priority == priority + ) return true; } return false; } - /** - * Events which are listened even if they are cancelled. - */ - public static final Set<Class<? extends Event>> listenCancelled = new HashSet<>(); - } diff --git a/src/main/java/ch/njol/skript/aliases/Aliases.java b/src/main/java/ch/njol/skript/aliases/Aliases.java index d812ce14aa3..58aa0c9faeb 100644 --- a/src/main/java/ch/njol/skript/aliases/Aliases.java +++ b/src/main/java/ch/njol/skript/aliases/Aliases.java @@ -18,26 +18,6 @@ */ package ch.njol.skript.aliases; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.bukkit.ChatColor; -import org.bukkit.Material; -import org.bukkit.NamespacedKey; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.SkriptAddon; import ch.njol.skript.SkriptConfig; @@ -45,32 +25,46 @@ import ch.njol.skript.config.Node; import ch.njol.skript.config.SectionNode; import ch.njol.skript.entity.EntityData; +import org.skriptlang.skript.lang.script.Script; +import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.skript.localization.ArgsMessage; import ch.njol.skript.localization.Language; import ch.njol.skript.localization.Message; import ch.njol.skript.localization.Noun; import ch.njol.skript.localization.RegexMessage; import ch.njol.skript.log.BlockingLogHandler; -import ch.njol.skript.log.SkriptLogger; import ch.njol.skript.util.EnchantmentType; import ch.njol.skript.util.Utils; import ch.njol.skript.util.Version; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.eclipse.jdt.annotation.Nullable; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public abstract class Aliases { private static final AliasesProvider provider = createProvider(10000, null); private static final AliasesParser parser = createParser(provider); - /** - * Current script aliases. - */ - @Nullable - private static ScriptAliases scriptAliases; - @Nullable private static ItemType getAlias_i(final String s) { // Check script aliases first - ScriptAliases aliases = scriptAliases; + ScriptAliases aliases = getScriptAliases(); if (aliases != null) { return aliases.provider.getAlias(s); // Delegates to global provider if needed } @@ -178,7 +172,7 @@ static String concatenate(final String... parts) { @Nullable private static MaterialName getMaterialNameData(ItemData type) { // Check script aliases first - ScriptAliases aliases = scriptAliases; + ScriptAliases aliases = getScriptAliases(); if (aliases != null) { return aliases.provider.getMaterialName(type); } @@ -518,7 +512,7 @@ public static void load(Config config) { */ @Nullable public static String getMinecraftId(ItemData data) { - ScriptAliases aliases = scriptAliases; + ScriptAliases aliases = getScriptAliases(); if (aliases != null) { return aliases.provider.getMinecraftId(data); } @@ -533,7 +527,7 @@ public static String getMinecraftId(ItemData data) { */ @Nullable public static EntityData<?> getRelatedEntity(ItemData data) { - ScriptAliases aliases = scriptAliases; + ScriptAliases aliases = getScriptAliases(); if (aliases != null) { return aliases.provider.getRelatedEntity(data); } @@ -592,20 +586,44 @@ public static AliasesProvider getAddonProvider(@Nullable SkriptAddon addon) { } /** - * Creates script aliases. - * @return Script aliases, ready to be added to. + * Creates script aliases for the provided Script. + * @return Script aliases that are ready to be added to. */ - public static ScriptAliases createScriptAliases() { + public static ScriptAliases createScriptAliases(Script script) { AliasesProvider localProvider = createProvider(10, provider); - return new ScriptAliases(localProvider, createParser(localProvider)); + ScriptAliases aliases = new ScriptAliases(localProvider, createParser(localProvider)); + script.addData(aliases); + return aliases; } - + + /** + * Clears any stored custom aliases for the provided Script. + * @param script The script to clear aliases for. + */ + public static void clearScriptAliases(Script script) { + script.removeData(ScriptAliases.class); + } + + /** + * Internal method for obtaining ScriptAliases. Checks {@link ParserInstance#isActive()}. + * @return The obtained aliases, or null if the script has no custom aliases. + */ + @Nullable + private static ScriptAliases getScriptAliases() { + ParserInstance parser = ParserInstance.get(); + if (parser.isActive()) + return getScriptAliases(parser.getCurrentScript()); + return null; + } + /** - * Sets script aliases to be used for lookups. Remember to set them to - * null when the script changes. - * @param aliases Script aliases. + * Method for obtaining the ScriptAliases instance of a {@link Script}. + * @param script The script to obtain aliases from. + * @return The obtained aliases, or null if the script has no custom aliases. */ - public static void setScriptAliases(@Nullable ScriptAliases aliases) { - scriptAliases = aliases; + @Nullable + public static ScriptAliases getScriptAliases(Script script) { + return script.getData(ScriptAliases.class); } + } diff --git a/src/main/java/ch/njol/skript/aliases/ScriptAliases.java b/src/main/java/ch/njol/skript/aliases/ScriptAliases.java index 632b04a29d1..6a5386f3b94 100644 --- a/src/main/java/ch/njol/skript/aliases/ScriptAliases.java +++ b/src/main/java/ch/njol/skript/aliases/ScriptAliases.java @@ -19,10 +19,12 @@ package ch.njol.skript.aliases; +import org.skriptlang.skript.lang.script.ScriptData; + /** * Per-script aliases provider and parser container. */ -public class ScriptAliases { +public class ScriptAliases implements ScriptData { /** * Aliases provider. @@ -34,7 +36,7 @@ public class ScriptAliases { */ public final AliasesParser parser; - public ScriptAliases(AliasesProvider provider, AliasesParser parser) { + ScriptAliases(AliasesProvider provider, AliasesParser parser) { this.provider = provider; this.parser = parser; } diff --git a/src/main/java/ch/njol/skript/command/Commands.java b/src/main/java/ch/njol/skript/command/Commands.java index 84e229bea84..bd28ed59288 100644 --- a/src/main/java/ch/njol/skript/command/Commands.java +++ b/src/main/java/ch/njol/skript/command/Commands.java @@ -18,23 +18,20 @@ */ package ch.njol.skript.command; -import java.io.File; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - +import ch.njol.skript.ScriptLoader; +import ch.njol.skript.Skript; +import ch.njol.skript.SkriptConfig; +import ch.njol.skript.config.validate.SectionValidator; +import ch.njol.skript.lang.Effect; +import org.skriptlang.skript.lang.script.Script; +import ch.njol.skript.lang.TriggerItem; +import ch.njol.skript.lang.parser.ParserInstance; +import ch.njol.skript.localization.ArgsMessage; +import ch.njol.skript.localization.Message; +import ch.njol.skript.log.RetainingLogHandler; +import ch.njol.skript.log.SkriptLogger; import ch.njol.skript.util.SkriptColor; +import ch.njol.skript.variables.Variables; import org.apache.commons.lang.Validate; import org.bukkit.Bukkit; import org.bukkit.ChatColor; @@ -54,30 +51,18 @@ import org.bukkit.plugin.SimplePluginManager; import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.ScriptLoader; -import ch.njol.skript.Skript; -import ch.njol.skript.SkriptConfig; -import ch.njol.skript.classes.ClassInfo; -import ch.njol.skript.classes.Parser; -import ch.njol.skript.config.SectionNode; -import ch.njol.skript.config.validate.SectionValidator; -import ch.njol.skript.lang.Effect; -import ch.njol.skript.lang.ParseContext; -import ch.njol.skript.lang.SkriptParser; -import ch.njol.skript.lang.TriggerItem; -import ch.njol.skript.lang.VariableString; -import ch.njol.skript.lang.parser.ParserInstance; -import ch.njol.skript.localization.ArgsMessage; -import ch.njol.skript.localization.Message; -import ch.njol.skript.log.RetainingLogHandler; -import ch.njol.skript.log.SkriptLogger; -import ch.njol.skript.registrations.Classes; -import ch.njol.skript.util.StringMode; -import ch.njol.skript.util.Timespan; -import ch.njol.skript.util.Utils; -import ch.njol.skript.variables.Variables; -import ch.njol.util.NonNullPair; -import ch.njol.util.StringUtils; +import java.io.File; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.regex.Pattern; //TODO option to disable replacement of <color>s in command arguments? @@ -139,19 +124,6 @@ private static void init() { } } - private final static SectionValidator commandStructure = new SectionValidator() - .addEntry("usage", true) - .addEntry("description", true) - .addEntry("permission", true) - .addEntry("permission message", true) - .addEntry("cooldown", true) - .addEntry("cooldown message", true) - .addEntry("cooldown bypass", true) - .addEntry("cooldown storage", true) - .addEntry("aliases", true) - .addEntry("executable by", true) - .addSection("trigger", false); - @Nullable public static List<Argument<?>> currentArguments = null; @@ -160,11 +132,11 @@ private static void init() { @SuppressWarnings("null") private final static Pattern unescape = Pattern.compile("\\\\[" + Pattern.quote("(|)<>%\\") + "]"); - private static String escape(final String s) { + public static String escape(String s) { return "" + escape.matcher(s).replaceAll("\\\\$0"); } - private static String unescape(final String s) { + public static String unescape(String s) { return "" + unescape.matcher(s).replaceAll("$0"); } @@ -264,206 +236,10 @@ static boolean handleEffectCommand(final CommandSender sender, String command) { return true; } } - - @SuppressWarnings("null") - private final static Pattern commandPattern = Pattern.compile("(?i)^command /?(\\S+)\\s*(\\s+(.+))?$"), - argumentPattern = Pattern.compile("<\\s*(?:([^>]+?)\\s*:\\s*)?(.+?)\\s*(?:=\\s*(" + SkriptParser.wildcard + "))?\\s*>"); - - @Nullable - public static ScriptCommand loadCommand(final SectionNode node) { - return loadCommand(node, true); - } - - @Nullable - public static ScriptCommand loadCommand(final SectionNode node, final boolean alsoRegister) { - final String key = node.getKey(); - if (key == null) - return null; - final String s = ScriptLoader.replaceOptions(key); - - int level = 0; - for (int i = 0; i < s.length(); i++) { - if (s.charAt(i) == '[') { - level++; - } else if (s.charAt(i) == ']') { - if (level == 0) { - Skript.error("Invalid placement of [optional brackets]"); - return null; - } - level--; - } - } - if (level > 0) { - Skript.error("Invalid amount of [optional brackets]"); - return null; - } - - Matcher m = commandPattern.matcher(s); - final boolean a = m.matches(); - assert a; - - final String command = "" + m.group(1).toLowerCase(Locale.ENGLISH); - final ScriptCommand existingCommand = commands.get(command); - if (alsoRegister && existingCommand != null && existingCommand.getLabel().equals(command)) { - final File f = existingCommand.getScript(); - Skript.error("A command with the name /" + existingCommand.getName() + " is already defined" + (f == null ? "" : " in " + f.getName())); - return null; - } - - final String arguments = m.group(3) == null ? "" : m.group(3); - final StringBuilder pattern = new StringBuilder(); - - List<Argument<?>> currentArguments = Commands.currentArguments = new ArrayList<>(); //Mirre - m = argumentPattern.matcher(arguments); - int lastEnd = 0; - int optionals = 0; - for (int i = 0; m.find(); i++) { - pattern.append(escape("" + arguments.substring(lastEnd, m.start()))); - optionals += StringUtils.count(arguments, '[', lastEnd, m.start()); - optionals -= StringUtils.count(arguments, ']', lastEnd, m.start()); - - lastEnd = m.end(); - - ClassInfo<?> c; - c = Classes.getClassInfoFromUserInput("" + m.group(2)); - final NonNullPair<String, Boolean> p = Utils.getEnglishPlural("" + m.group(2)); - if (c == null) - c = Classes.getClassInfoFromUserInput(p.getFirst()); - if (c == null) { - Skript.error("Unknown type '" + m.group(2) + "'"); - return null; - } - final Parser<?> parser = c.getParser(); - if (parser == null || !parser.canParse(ParseContext.COMMAND)) { - Skript.error("Can't use " + c + " as argument of a command"); - return null; - } - - final Argument<?> arg = Argument.newInstance(m.group(1), c, m.group(3), i, !p.getSecond(), optionals > 0); - if (arg == null) - return null; - currentArguments.add(arg); - - if (arg.isOptional() && optionals == 0) { - pattern.append('['); - optionals++; - } - pattern.append("%" + (arg.isOptional() ? "-" : "") + Utils.toEnglishPlural(c.getCodeName(), p.getSecond()) + "%"); - } - - pattern.append(escape("" + arguments.substring(lastEnd))); - optionals += StringUtils.count(arguments, '[', lastEnd); - optionals -= StringUtils.count(arguments, ']', lastEnd); - for (int i = 0; i < optionals; i++) - pattern.append(']'); - - String desc = "/" + command + " "; - - desc += StringUtils.replaceAll(pattern, "(?<!\\\\)%-?(.+?)%", m1 -> { - assert m1 != null; - NonNullPair<String, Boolean> p = Utils.getEnglishPlural("" + m1.group(1)); - String s1 = p.getFirst(); - return "<" + Classes.getClassInfo(s1).getName().toString(p.getSecond()) + ">"; - }); - - desc = unescape(desc); - desc = "" + desc.trim(); - - node.convertToEntries(0); - commandStructure.validate(node); - if (!(node.get("trigger") instanceof SectionNode)) - return null; - - final String usage = ScriptLoader.replaceOptions(node.get("usage", m_correct_usage + " " + desc)); - final String description = ScriptLoader.replaceOptions(node.get("description", "")); - ArrayList<String> aliases = new ArrayList<>(Arrays.asList(ScriptLoader.replaceOptions(node.get("aliases", "")).split("\\s*,\\s*/?"))); - if (aliases.get(0).startsWith("/")) - aliases.set(0, aliases.get(0).substring(1)); - else if (aliases.get(0).isEmpty()) - aliases = new ArrayList<>(0); - final String permission = ScriptLoader.replaceOptions(node.get("permission", "")); - - String rawPermissionMessage = ScriptLoader.replaceOptions(node.get("permission message", "")) - .replace("\"", "\"\""); - - VariableString permissionMessage = rawPermissionMessage.isEmpty() ? - null : VariableString.newInstance(rawPermissionMessage); - - final SectionNode trigger = (SectionNode) node.get("trigger"); - if (trigger == null) - return null; - final String[] by = ScriptLoader.replaceOptions(node.get("executable by", "console,players")).split("\\s*,\\s*|\\s+(and|or)\\s+"); - int executableBy = 0; - for (final String b : by) { - if (b.equalsIgnoreCase("console") || b.equalsIgnoreCase("the console")) { - executableBy |= ScriptCommand.CONSOLE; - } else if (b.equalsIgnoreCase("players") || b.equalsIgnoreCase("player")) { - executableBy |= ScriptCommand.PLAYERS; - } else { - Skript.warning("'executable by' should be either be 'players', 'console', or both, but found '" + b + "'"); - } - } - - final String cooldownString = ScriptLoader.replaceOptions(node.get("cooldown", "")); - Timespan cooldown = null; - if (!cooldownString.isEmpty()) { - // ParseContext doesn't matter for Timespan's parser - cooldown = Classes.parse(cooldownString, Timespan.class, ParseContext.DEFAULT); - if (cooldown == null) { - Skript.warning("'" + cooldownString + "' is an invalid timespan for the cooldown"); - } - } - - String cooldownMessageString = ScriptLoader.replaceOptions(node.get("cooldown message", "")) - .replace("\"", "\"\""); - boolean usingCooldownMessage = !cooldownMessageString.isEmpty(); - VariableString cooldownMessage = null; - if (usingCooldownMessage) { - cooldownMessage = VariableString.newInstance(cooldownMessageString); - } - String cooldownBypass = ScriptLoader.replaceOptions(node.get("cooldown bypass", "")); - - if (permissionMessage != null && permission.isEmpty()) { - Skript.warning("command /" + command + " has a permission message set, but not a permission"); - } - - if (usingCooldownMessage && cooldownString.isEmpty()) { - Skript.warning("command /" + command + " has a cooldown message set, but not a cooldown"); - } - - String cooldownStorageString = ScriptLoader.replaceOptions(node.get("cooldown storage", "")); - VariableString cooldownStorage = null; - if (!cooldownStorageString.isEmpty()) { - cooldownStorage = VariableString.newInstance(cooldownStorageString, StringMode.VARIABLE_NAME); - } - - if (Skript.debug() || node.debug()) - Skript.debug("command " + desc + ":"); - - final File config = node.getConfig().getFile(); - if (config == null) { - assert false; - return null; - } - - Commands.currentArguments = currentArguments; - ScriptCommand c; - try { - c = new ScriptCommand(config, command, pattern.toString(), currentArguments, description, usage, - aliases, permission, permissionMessage, cooldown, cooldownMessage, cooldownBypass, cooldownStorage, - executableBy, ScriptLoader.loadItems(trigger)); - c.trigger.setLineNumber(node.getLine()); - } finally { - Commands.currentArguments = null; - } - - if (alsoRegister) - registerCommand(c); - - if (Skript.logVeryHigh() && !Skript.debug()) - Skript.info("registered command " + desc); - return c; + @Nullable + public static ScriptCommand getScriptCommand(String key) { + return commands.get(key); } public static boolean skriptCommandExists(final String command) { @@ -475,8 +251,10 @@ public static void registerCommand(final ScriptCommand command) { // Validate that there are no duplicates final ScriptCommand existingCommand = commands.get(command.getLabel()); if (existingCommand != null && existingCommand.getLabel().equals(command.getLabel())) { - final File f = existingCommand.getScript(); - Skript.error("A command with the name /" + existingCommand.getName() + " is already defined" + (f == null ? "" : " in " + f.getName())); + Script script = existingCommand.getScript(); + Skript.error("A command with the name /" + existingCommand.getName() + " is already defined" + + (script != null ? (" in " + script.getConfig().getFileName()) : "") + ); return; } @@ -490,24 +268,27 @@ public static void registerCommand(final ScriptCommand command) { } command.registerHelp(); } - - public static int unregisterCommands(final File script) { + + @Deprecated + public static int unregisterCommands(File script) { int numCommands = 0; - final Iterator<ScriptCommand> commandsIter = commands.values().iterator(); - while (commandsIter.hasNext()) { - final ScriptCommand c = commandsIter.next(); - if (script.equals(c.getScript())) { + for (ScriptCommand c : new ArrayList<>(commands.values())) { + if (c.getScript() != null && c.getScript().equals(ScriptLoader.getScript(script))) { numCommands++; - c.unregisterHelp(); - if (commandMap != null) { - assert cmKnownCommands != null;// && cmAliases != null; - c.unregister(commandMap, cmKnownCommands, cmAliases); - } - commandsIter.remove(); + unregisterCommand(c); } } return numCommands; } + + public static void unregisterCommand(ScriptCommand scriptCommand) { + scriptCommand.unregisterHelp(); + if (commandMap != null) { + assert cmKnownCommands != null;// && cmAliases != null; + scriptCommand.unregister(commandMap, cmKnownCommands, cmAliases); + } + commands.values().remove(scriptCommand); + } private static boolean registeredListeners = false; @@ -550,25 +331,10 @@ public Boolean call() throws Exception { } } - public static void clearCommands() { - final SimpleCommandMap commandMap = Commands.commandMap; - if (commandMap != null) { - final Map<String, Command> cmKnownCommands = Commands.cmKnownCommands; - final Set<String> cmAliases = Commands.cmAliases; - assert cmKnownCommands != null;// && cmAliases != null; - for (final ScriptCommand c : commands.values()) - c.unregister(commandMap, cmKnownCommands, cmAliases); - } - for (final ScriptCommand c : commands.values()) { - c.unregisterHelp(); - } - commands.clear(); - } - /** * copied from CraftBukkit (org.bukkit.craftbukkit.help.CommandAliasHelpTopic) */ - public final static class CommandAliasHelpTopic extends HelpTopic { + public static final class CommandAliasHelpTopic extends HelpTopic { private final String aliasFor; private final HelpMap helpMap; diff --git a/src/main/java/ch/njol/skript/command/ScriptCommand.java b/src/main/java/ch/njol/skript/command/ScriptCommand.java index 83c93ebc9ee..cd366e7fc08 100644 --- a/src/main/java/ch/njol/skript/command/ScriptCommand.java +++ b/src/main/java/ch/njol/skript/command/ScriptCommand.java @@ -18,7 +18,6 @@ */ package ch.njol.skript.command; -import java.io.File; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.ArrayList; @@ -33,6 +32,9 @@ import java.util.Set; import java.util.UUID; +import ch.njol.skript.ScriptLoader; +import ch.njol.skript.config.SectionNode; +import org.skriptlang.skript.lang.script.Script; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.OfflinePlayer; @@ -57,7 +59,6 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser; import ch.njol.skript.lang.Trigger; -import ch.njol.skript.lang.TriggerItem; import ch.njol.skript.lang.VariableString; import ch.njol.skript.lang.util.SimpleEvent; import ch.njol.skript.lang.util.SimpleLiteral; @@ -100,7 +101,7 @@ public class ScriptCommand implements TabExecutor { private final Expression<String> cooldownStorage; final String usage; - final Trigger trigger; + private final Trigger trigger; private final String pattern; private final List<Argument<?>> arguments; @@ -123,14 +124,16 @@ public class ScriptCommand implements TabExecutor { * @param aliases /alias1, /alias2, ... * @param permission permission or null if none * @param permissionMessage message to display if the player doesn't have the given permission - * @param items trigger to execute + * @param node the node to parse and load into a Trigger */ - public ScriptCommand(final File script, final String name, final String pattern, final List<Argument<?>> arguments, - final String description, final String usage, final ArrayList<String> aliases, - final String permission, @Nullable final VariableString permissionMessage, @Nullable final Timespan cooldown, - @Nullable final VariableString cooldownMessage, final String cooldownBypass, - @Nullable VariableString cooldownStorage, final int executableBy, final List<TriggerItem> items) { - Validate.notNull(name, pattern, arguments, description, usage, aliases, items); + public ScriptCommand( + Script script, String name, String pattern, List<Argument<?>> arguments, + String description, String usage, List<String> aliases, + String permission, @Nullable VariableString permissionMessage, @Nullable Timespan cooldown, + @Nullable VariableString cooldownMessage, String cooldownBypass, + @Nullable VariableString cooldownStorage, int executableBy, SectionNode node + ) { + Validate.notNull(name, pattern, arguments, description, usage, aliases, node); this.name = name; label = "" + name.toLowerCase(Locale.ENGLISH); this.permission = permission; @@ -162,7 +165,8 @@ public ScriptCommand(final File script, final String name, final String pattern, this.pattern = pattern; this.arguments = arguments; - trigger = new Trigger(script, "command /" + name, new SimpleEvent(), items); + trigger = new Trigger(script, "command /" + name, new SimpleEvent(), ScriptLoader.loadItems(node)); + trigger.setLineNumber(node.getLine()); bukkitCommand = setupBukkitCommand(); } @@ -512,7 +516,7 @@ public PluginCommand getBukkitCommand() { } @Nullable - public File getScript() { + public Script getScript() { return trigger.getScript(); } diff --git a/src/main/java/ch/njol/skript/conditions/CondScriptLoaded.java b/src/main/java/ch/njol/skript/conditions/CondScriptLoaded.java index 027850b3195..2590c71eb3c 100644 --- a/src/main/java/ch/njol/skript/conditions/CondScriptLoaded.java +++ b/src/main/java/ch/njol/skript/conditions/CondScriptLoaded.java @@ -18,12 +18,6 @@ */ package ch.njol.skript.conditions; -import java.io.File; - -import ch.njol.skript.config.Config; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.ScriptLoader; import ch.njol.skript.Skript; import ch.njol.skript.SkriptCommand; @@ -33,12 +27,18 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Condition; import ch.njol.skript.lang.Expression; +import org.skriptlang.skript.lang.script.Script; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; @Name("Is Script Loaded") @Description("Check if the current script, or another script, is currently loaded.") -@Examples({"script is loaded", "script \"example.sk\" is loaded"}) +@Examples({ + "script is loaded", + "script \"example.sk\" is loaded" +}) @Since("2.2-dev31") public class CondScriptLoaded extends Condition { @@ -51,45 +51,40 @@ public class CondScriptLoaded extends Condition { @Nullable private Expression<String> scripts; @Nullable - private File currentScriptFile; + private Script currentScript; @Override @SuppressWarnings({"unchecked"}) public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { scripts = (Expression<String>) exprs[0]; - setNegated(matchedPattern == 1); - Config cs = getParser().getCurrentScript(); - if (cs == null && scripts == null) { + currentScript = getParser().getCurrentScript(); + if (scripts == null) { Skript.error("The condition 'script loaded' requires a script name argument when used outside of script files"); return false; - } else if (cs != null) { - currentScriptFile = cs.getFile(); } + setNegated(matchedPattern == 1); return true; } @Override public boolean check(Event e) { if (scripts == null) { - if (currentScriptFile == null) + if (currentScript == null) return isNegated(); - return ScriptLoader.getLoadedFiles().contains(currentScriptFile) ^ isNegated(); + return ScriptLoader.getLoadedScripts().contains(currentScript) ^ isNegated(); } - return scripts.check(e, - scriptName -> ScriptLoader.getLoadedFiles().contains(SkriptCommand.getScriptFromName(scriptName)), + scriptName -> ScriptLoader.getLoadedScripts().contains(ScriptLoader.getScript(SkriptCommand.getScriptFromName(scriptName))), isNegated()); } @Override public String toString(@Nullable Event e, boolean debug) { String scriptName; - if (scripts == null) scriptName = "script"; else scriptName = (scripts.isSingle() ? "script" : "scripts" + " " + scripts.toString(e, debug)); - boolean isSingle = scripts == null || scripts.isSingle(); if (isSingle) return scriptName + (isNegated() ? " isn't" : " is") + " loaded"; diff --git a/src/main/java/ch/njol/skript/effects/EffChange.java b/src/main/java/ch/njol/skript/effects/EffChange.java index bb79b1b6eb8..1af18c8f36f 100644 --- a/src/main/java/ch/njol/skript/effects/EffChange.java +++ b/src/main/java/ch/njol/skript/effects/EffChange.java @@ -21,6 +21,8 @@ import java.util.Arrays; import java.util.logging.Level; +import org.skriptlang.skript.lang.script.Script; +import org.skriptlang.skript.lang.script.ScriptWarning; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @@ -44,7 +46,6 @@ import ch.njol.skript.log.SkriptLogger; import ch.njol.skript.registrations.Classes; import ch.njol.skript.util.Patterns; -import ch.njol.skript.util.ScriptOptions; import ch.njol.skript.util.Utils; import ch.njol.util.Kleenean; @@ -257,11 +258,7 @@ else if (mode == ChangeMode.SET) if (changed instanceof Variable && !((Variable<?>) changed).isLocal() && (mode == ChangeMode.SET || ((Variable<?>) changed).isList() && mode == ChangeMode.ADD)) { final ClassInfo<?> ci = Classes.getSuperClassInfo(ch.getReturnType()); if (ci.getC() != Object.class && ci.getSerializer() == null && ci.getSerializeAs() == null && !SkriptConfig.disableObjectCannotBeSavedWarnings.value()) { - if (getParser().getCurrentScript() != null) { - if (!ScriptOptions.getInstance().suppressesWarning(getParser().getCurrentScript().getFile(), "instance var")) { - Skript.warning(ci.getName().withIndefiniteArticle() + " cannot be saved, i.e. the contents of the variable " + changed + " will be lost when the server stops."); - } - } else { + if (!getParser().getCurrentScript().suppressesWarning(ScriptWarning.VARIABLE_SAVE)) { Skript.warning(ci.getName().withIndefiniteArticle() + " cannot be saved, i.e. the contents of the variable " + changed + " will be lost when the server stops."); } } diff --git a/src/main/java/ch/njol/skript/effects/EffLog.java b/src/main/java/ch/njol/skript/effects/EffLog.java index 944372d2de7..bce9d3bae76 100644 --- a/src/main/java/ch/njol/skript/effects/EffLog.java +++ b/src/main/java/ch/njol/skript/effects/EffLog.java @@ -27,6 +27,7 @@ import java.util.Locale; import java.util.logging.Level; +import org.skriptlang.skript.lang.script.Script; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @@ -114,9 +115,14 @@ protected void execute(final Event e) { w.flush(); } } else { - final Trigger t = getTrigger(); - final File script = t == null ? null : t.getScript(); - Skript.info("[" + (script != null ? script.getName() : "---") + "] " + message); + Trigger t = getTrigger(); + String scriptName = "---"; + if (t != null) { + Script script = t.getScript(); + if (script != null) + scriptName = script.getConfig().getFileName(); + } + Skript.info("[" + scriptName + "] " + message); } } } diff --git a/src/main/java/ch/njol/skript/effects/EffScriptFile.java b/src/main/java/ch/njol/skript/effects/EffScriptFile.java index 3c2db926a6a..0a38111c5ce 100644 --- a/src/main/java/ch/njol/skript/effects/EffScriptFile.java +++ b/src/main/java/ch/njol/skript/effects/EffScriptFile.java @@ -18,108 +18,128 @@ */ package ch.njol.skript.effects; -import java.io.File; -import java.io.IOException; -import java.util.Collections; - -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.ScriptLoader; import ch.njol.skript.Skript; import ch.njol.skript.SkriptCommand; -import ch.njol.skript.config.Config; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Effect; import ch.njol.skript.lang.Expression; +import org.skriptlang.skript.lang.script.Script; import ch.njol.skript.lang.SkriptParser; import ch.njol.skript.util.FileUtils; import ch.njol.util.Kleenean; import ch.njol.util.OpenCloseable; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.io.File; +import java.io.IOException; @Name("Enable/Disable/Reload Script File") @Description("Enables, disables, or reloads a script file.") -@Examples({"reload script \"test\"", - "enable script file \"testing\"", - "unload script file \"script.sk\""}) +@Examples({ + "reload script \"test\"", + "enable script file \"testing\"", + "unload script file \"script.sk\"" +}) @Since("2.4") public class EffScriptFile extends Effect { + static { - Skript.registerEffect(EffScriptFile.class, "(1¦enable|1¦load|2¦reload|3¦disable|3¦unload) s(c|k)ript [file] %string%"); + Skript.registerEffect(EffScriptFile.class, + "(1:(enable|load)|2:reload|3:(disable|unload)) s(c|k)ript [file] %string%" + ); } private static final int ENABLE = 1, RELOAD = 2, DISABLE = 3; private int mark; - @Nullable + + @SuppressWarnings("NotNullFieldNotInitialized") private Expression<String> fileName; - - @SuppressWarnings("unchecked") + @Override + @SuppressWarnings("unchecked") public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { mark = parseResult.mark; fileName = (Expression<String>) exprs[0]; return true; } - @Override - public String toString(@Nullable Event e, boolean debug) { - return (mark == ENABLE ? "enable" : mark == RELOAD ? "disable" : mark == DISABLE ? "unload" : "") + " script file " + (fileName != null ? fileName.toString(e, debug) : ""); - } - @Override protected void execute(Event e) { - String name = fileName != null ? fileName.getSingle(e) : ""; - File file = SkriptCommand.getScriptFromName(name != null ? name : ""); - if (file == null) { + String name = fileName.getSingle(e); + if (name == null) return; - } + File scriptFile = SkriptCommand.getScriptFromName(name); + if (scriptFile == null) + return; + switch (mark) { case ENABLE: { - if (!file.getName().startsWith("-")) { + if (ScriptLoader.getLoadedScriptsFilter().accept(scriptFile)) return; - } - + try { - file = FileUtils.move(file, new File(file.getParentFile(), file.getName().substring(1)), false); - } catch (final IOException ex) { + // TODO Central methods to be used between here and SkriptCommand should be created for enabling/disabling (renaming) files + scriptFile = FileUtils.move( + scriptFile, + new File(scriptFile.getParentFile(), scriptFile.getName().substring(ScriptLoader.DISABLED_SCRIPT_PREFIX_LENGTH)), + false + ); + } catch (IOException ex) { + //noinspection ThrowableNotThrown Skript.exception(ex, "Error while enabling script file: " + name); return; } - Config config = ScriptLoader.loadStructure(file); - if (config != null) - ScriptLoader.loadScripts(Collections.singletonList(config), OpenCloseable.EMPTY); + + ScriptLoader.loadScripts(scriptFile, OpenCloseable.EMPTY); break; } case RELOAD: { - if (file.getName().startsWith("-")) { + if (ScriptLoader.getDisabledScriptsFilter().accept(scriptFile)) return; - } + + Script script = ScriptLoader.getScript(scriptFile); + if (script != null) + ScriptLoader.unloadScript(script); - ScriptLoader.reloadScript(file, OpenCloseable.EMPTY); + ScriptLoader.loadScripts(scriptFile, OpenCloseable.EMPTY); break; } case DISABLE: { - if (file.getName().startsWith("-")) { + if (ScriptLoader.getDisabledScriptsFilter().accept(scriptFile)) return; - } - - ScriptLoader.unloadScript(file); + + Script script = ScriptLoader.getScript(scriptFile); + if (script != null) + ScriptLoader.unloadScript(script); + try { - FileUtils.move(file, new File(file.getParentFile(), "-" + file.getName()), false); - } catch (final IOException ex) { + FileUtils.move( + scriptFile, + new File(scriptFile.getParentFile(), ScriptLoader.DISABLED_SCRIPT_PREFIX + scriptFile.getName()), + false + ); + } catch (IOException ex) { + //noinspection ThrowableNotThrown Skript.exception(ex, "Error while disabling script file: " + name); return; } break; } - default: { + default: assert false; - return; - } } } + + @Override + public String toString(@Nullable Event e, boolean debug) { + return (mark == ENABLE ? "enable" : mark == RELOAD ? "disable" : mark == DISABLE ? "unload" : "") + + " script file " + fileName.toString(e, debug); + } + } diff --git a/src/main/java/ch/njol/skript/effects/EffSuppressWarnings.java b/src/main/java/ch/njol/skript/effects/EffSuppressWarnings.java index cbdb713d99e..9137bafee06 100644 --- a/src/main/java/ch/njol/skript/effects/EffSuppressWarnings.java +++ b/src/main/java/ch/njol/skript/effects/EffSuppressWarnings.java @@ -19,7 +19,6 @@ package ch.njol.skript.effects; import ch.njol.skript.Skript; -import ch.njol.skript.config.Config; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; @@ -27,59 +26,36 @@ import ch.njol.skript.lang.Effect; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser; -import ch.njol.skript.util.ScriptOptions; import ch.njol.util.Kleenean; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; - -import java.io.File; +import org.skriptlang.skript.lang.script.ScriptWarning; @Name("Locally Suppress Warning") @Description("Suppresses target warnings from the current script.") -@Examples({"locally suppress conflict warnings", - "suppress the variable save warnings"}) +@Examples({ + "locally suppress missing conjunction warnings", + "suppress the variable save warnings" +}) @Since("2.3") public class EffSuppressWarnings extends Effect { static { - Skript.registerEffect(EffSuppressWarnings.class, "[local[ly]] suppress [the] (1¦conflict|2¦variable save|3¦[missing] conjunction[s]|4¦starting [with] expression[s]) warning[s]"); + Skript.registerEffect(EffSuppressWarnings.class, + "[local[ly]] suppress [the] (1:conflict|2:variable save|3:[missing] conjunction[s]|4:starting [with] expression[s]) warning[s]" + ); } - private final int CONFLICT = 1; - private final int INSTANCE = 2; - private final int CONJUNCTION = 3; - private final int STARTEXPR = 4; + private static final int CONFLICT = 1, INSTANCE = 2, CONJUNCTION = 3, START_EXPR = 4; private int mark = 0; @Override public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { - Config cs = getParser().getCurrentScript(); - if (cs == null) { - Skript.error("You can only suppress warnings for script files!"); - return false; - } - File scriptFile = cs.getFile(); mark = parseResult.mark; - switch (parseResult.mark) { - case CONFLICT: { // Possible variable conflicts - Skript.warning("Variable conflict warnings no longer need suppression, as they have been removed altogether"); - break; - } - case INSTANCE: { // Variables cannot be saved - ScriptOptions.getInstance().setSuppressWarning(scriptFile, "instance var"); - break; - } - case CONJUNCTION: { // Missing "and" or "or" - ScriptOptions.getInstance().setSuppressWarning(scriptFile, "conjunction"); - break; - } - case STARTEXPR: { // Variable starts with expression - ScriptOptions.getInstance().setSuppressWarning(scriptFile, "start expression"); - break; - } - default: { - throw new AssertionError(); - } + if (mark == 1) { + Skript.warning("Variable conflict warnings no longer need suppression, as they have been removed altogether"); + } else { + getParser().getCurrentScript().suppressWarning(ScriptWarning.values()[mark - 2]); } return true; } @@ -100,7 +76,7 @@ public String toString(@Nullable Event e, boolean debug) { case CONJUNCTION: word = "missing conjunction"; break; - case STARTEXPR: + case START_EXPR: word = "starting expression"; break; default: diff --git a/src/main/java/ch/njol/skript/events/EvtAtTime.java b/src/main/java/ch/njol/skript/events/EvtAtTime.java index 6d7768f64d1..ac826cb4368 100644 --- a/src/main/java/ch/njol/skript/events/EvtAtTime.java +++ b/src/main/java/ch/njol/skript/events/EvtAtTime.java @@ -47,7 +47,7 @@ @SuppressFBWarnings("EQ_COMPARETO_USE_OBJECT_EQUALS") public class EvtAtTime extends SelfRegisteringSkriptEvent implements Comparable<EvtAtTime> { static { - Skript.registerEvent("*At Time", EvtAtTime.class, ScheduledEvent.class, "at %time% [in %worlds%]") + Skript.registerEvent("*At Time", EvtAtTime.class, ScheduledEvent.class, "at %time% [in %-worlds%]") .description("An event that occurs at a given <a href='../classes.html#time'>minecraft time</a> in every world or only in specific worlds.") .examples("at 18:00", "at 7am in \"world\"") .since("1.3.4"); diff --git a/src/main/java/ch/njol/skript/events/EvtBlock.java b/src/main/java/ch/njol/skript/events/EvtBlock.java index 7aae8da9428..6c9ee44c102 100644 --- a/src/main/java/ch/njol/skript/events/EvtBlock.java +++ b/src/main/java/ch/njol/skript/events/EvtBlock.java @@ -54,23 +54,23 @@ public class EvtBlock extends SkriptEvent { static { // TODO 'block destroy' event for any kind of block destruction (player, water, trampling, fall (sand, toches, ...), etc) -> BlockPhysicsEvent? // REMIND attacking an item frame first removes its item; include this in on block damage? - Skript.registerEvent("Break / Mine", EvtBlock.class, new Class[]{BlockBreakEvent.class, PlayerBucketFillEvent.class, HangingBreakEvent.class}, "[block] (break[ing]|1¦min(e|ing)) [[of] %itemtypes/blockdatas%]") + Skript.registerEvent("Break / Mine", EvtBlock.class, new Class[]{BlockBreakEvent.class, PlayerBucketFillEvent.class, HangingBreakEvent.class}, "[block] (break[ing]|1¦min(e|ing)) [[of] %-itemtypes/blockdatas%]") .description("Called when a block is broken by a player. If you use 'on mine', only events where the broken block dropped something will call the trigger.") .examples("on mine:", "on break of stone:", "on mine of any ore:", "on break of chest[facing=north]:", "on break of potatoes[age=7]:") .since("1.0 (break), <i>unknown</i> (mine), 2.6 (BlockData support)"); - Skript.registerEvent("Burn", EvtBlock.class, BlockBurnEvent.class, "[block] burn[ing] [[of] %itemtypes/blockdatas%]") + Skript.registerEvent("Burn", EvtBlock.class, BlockBurnEvent.class, "[block] burn[ing] [[of] %-itemtypes/blockdatas%]") .description("Called when a block is destroyed by fire.") .examples("on burn:", "on burn of wood, fences, or chests:", "on burn of oak_log[axis=y]:") .since("1.0, 2.6 (BlockData support)"); - Skript.registerEvent("Place", EvtBlock.class, new Class[]{BlockPlaceEvent.class, PlayerBucketEmptyEvent.class, HangingPlaceEvent.class}, "[block] (plac(e|ing)|build[ing]) [[of] %itemtypes/blockdatas%]") + Skript.registerEvent("Place", EvtBlock.class, new Class[]{BlockPlaceEvent.class, PlayerBucketEmptyEvent.class, HangingPlaceEvent.class}, "[block] (plac(e|ing)|build[ing]) [[of] %-itemtypes/blockdatas%]") .description("Called when a player places a block.") .examples("on place:", "on place of a furnace, workbench or chest:", "on break of chest[type=right] or chest[type=left]") .since("1.0, 2.6 (BlockData support)"); - Skript.registerEvent("Fade", EvtBlock.class, BlockFadeEvent.class, "[block] fad(e|ing) [[of] %itemtypes/blockdatas%]") + Skript.registerEvent("Fade", EvtBlock.class, BlockFadeEvent.class, "[block] fad(e|ing) [[of] %-itemtypes/blockdatas%]") .description("Called when a block 'fades away', e.g. ice or snow melts.") .examples("on fade of snow or ice:", "on fade of snow[layers=2]") .since("1.0, 2.6 (BlockData support)"); - Skript.registerEvent("Form", EvtBlock.class, BlockFormEvent.class, "[block] form[ing] [[of] %itemtypes/blockdatas%]") + Skript.registerEvent("Form", EvtBlock.class, BlockFormEvent.class, "[block] form[ing] [[of] %-itemtypes/blockdatas%]") .description("Called when a block is created, but not by a player, e.g. snow forms due to snowfall, water freezes in cold biomes. This isn't called when block spreads (mushroom growth, water physics etc.), as it has its own event (see <a href='#spread'>spread event</a>).") .examples("on form of snow:", "on form of a mushroom:") .since("1.0, 2.6 (BlockData support)"); diff --git a/src/main/java/ch/njol/skript/events/EvtClick.java b/src/main/java/ch/njol/skript/events/EvtClick.java index 8f5754f7ee7..6cf04e0a47e 100644 --- a/src/main/java/ch/njol/skript/events/EvtClick.java +++ b/src/main/java/ch/njol/skript/events/EvtClick.java @@ -67,7 +67,7 @@ public class EvtClick extends SkriptEvent { Class<? extends PlayerEvent>[] eventTypes = CollectionUtils.array(PlayerInteractEvent.class, PlayerInteractEntityEvent.class); Skript.registerEvent("Click", EvtClick.class, eventTypes, - "[(" + RIGHT + "¦right|" + LEFT + "¦left)(| |-)][mouse(| |-)]click[ing] [on %-entitydata/itemtype%] [(with|using|holding) %itemtype%]", + "[(" + RIGHT + "¦right|" + LEFT + "¦left)(| |-)][mouse(| |-)]click[ing] [on %-entitydata/itemtype%] [(with|using|holding) %-itemtype%]", "[(" + RIGHT + "¦right|" + LEFT + "¦left)(| |-)][mouse(| |-)]click[ing] (with|using|holding) %itemtype% on %entitydata/itemtype%") .description("Called when a user clicks on a block, an entity or air with or without an item in their hand.", "Please note that rightclick events with an empty hand while not looking at a block are not sent to the server, so there's no way to detect them.") diff --git a/src/main/java/ch/njol/skript/events/EvtDamage.java b/src/main/java/ch/njol/skript/events/EvtDamage.java index c24a6db1982..0a79634999d 100644 --- a/src/main/java/ch/njol/skript/events/EvtDamage.java +++ b/src/main/java/ch/njol/skript/events/EvtDamage.java @@ -40,7 +40,7 @@ public class EvtDamage extends SkriptEvent { static { - Skript.registerEvent("Damage", EvtDamage.class, EntityDamageEvent.class, "damag(e|ing) [of %entitydata%] [by %entitydata%]") + Skript.registerEvent("Damage", EvtDamage.class, EntityDamageEvent.class, "damag(e|ing) [of %-entitydata%] [by %-entitydata%]") .description("Called when an entity receives damage, e.g. by an attack from another entity, lava, fire, drowning, fall, suffocation, etc.") .examples("on damage:", "on damage of a player:", "on damage of player by zombie:") .since("1.0, INSERT VERSION (by entity)"); diff --git a/src/main/java/ch/njol/skript/events/EvtEntity.java b/src/main/java/ch/njol/skript/events/EvtEntity.java index e10210dc3e9..f1ebb815da9 100644 --- a/src/main/java/ch/njol/skript/events/EvtEntity.java +++ b/src/main/java/ch/njol/skript/events/EvtEntity.java @@ -42,14 +42,14 @@ public final class EvtEntity extends SkriptEvent { static { - Skript.registerEvent("Death", EvtEntity.class, EntityDeathEvent.class, "death [of %entitydatas%]") + Skript.registerEvent("Death", EvtEntity.class, EntityDeathEvent.class, "death [of %-entitydatas%]") .description("Called when a living entity (including players) dies.") .examples("on death:", "on death of player:", "on death of a wither or ender dragon:", " broadcast \"A %entity% has been slain in %world%!\"") .since("1.0"); - Skript.registerEvent("Spawn", EvtEntity.class, EntitySpawnEvent.class, "spawn[ing] [of %entitydatas%]") + Skript.registerEvent("Spawn", EvtEntity.class, EntitySpawnEvent.class, "spawn[ing] [of %-entitydatas%]") .description("Called when an entity spawns (excluding players).") .examples("on spawn of a zombie:", "on spawn of an ender dragon:", diff --git a/src/main/java/ch/njol/skript/events/EvtGameMode.java b/src/main/java/ch/njol/skript/events/EvtGameMode.java index af3979d05f8..cd9233bb8f9 100644 --- a/src/main/java/ch/njol/skript/events/EvtGameMode.java +++ b/src/main/java/ch/njol/skript/events/EvtGameMode.java @@ -36,7 +36,7 @@ */ public final class EvtGameMode extends SkriptEvent { static { - Skript.registerEvent("Gamemode Change", EvtGameMode.class, PlayerGameModeChangeEvent.class, "game[ ]mode change [to %gamemode%]") + Skript.registerEvent("Gamemode Change", EvtGameMode.class, PlayerGameModeChangeEvent.class, "game[ ]mode change [to %-gamemode%]") .description("Called when a player's <a href='../classes.html#gamemode'>gamemode</a> changes.") .examples("on gamemode change:", "on gamemode change to adventure:") .since("1.0"); diff --git a/src/main/java/ch/njol/skript/events/EvtItem.java b/src/main/java/ch/njol/skript/events/EvtItem.java index 6524af3f2c7..c0cfd16cae6 100644 --- a/src/main/java/ch/njol/skript/events/EvtItem.java +++ b/src/main/java/ch/njol/skript/events/EvtItem.java @@ -51,40 +51,40 @@ public class EvtItem extends SkriptEvent { private final static boolean hasEntityPickupItemEvent = Skript.classExists("org.bukkit.event.entity.EntityPickupItemEvent"); static { - Skript.registerEvent("Dispense", EvtItem.class, BlockDispenseEvent.class, "dispens(e|ing) [[of] %itemtypes%]") + Skript.registerEvent("Dispense", EvtItem.class, BlockDispenseEvent.class, "dispens(e|ing) [[of] %-itemtypes%]") .description("Called when a dispenser dispenses an item.") .examples("on dispense of iron block:", "\tsend \"that'd be 19.99 please!\"") .since("<i>unknown</i> (before 2.1)"); - Skript.registerEvent("Item Spawn", EvtItem.class, ItemSpawnEvent.class, "item spawn[ing] [[of] %itemtypes%]") + Skript.registerEvent("Item Spawn", EvtItem.class, ItemSpawnEvent.class, "item spawn[ing] [[of] %-itemtypes%]") .description("Called whenever an item stack is spawned in a world, e.g. as drop of a block or mob, a player throwing items out of their inventory, or a dispenser dispensing an item (not shooting it).") .examples("on item spawn of iron sword:", "\tbroadcast \"Someone dropped an iron sword!\"") .since("<i>unknown</i> (before 2.1)"); - Skript.registerEvent("Drop", EvtItem.class, PlayerDropItemEvent.class, "[player] drop[ing] [[of] %itemtypes%]") + Skript.registerEvent("Drop", EvtItem.class, PlayerDropItemEvent.class, "[player] drop[ing] [[of] %-itemtypes%]") .description("Called when a player drops an item from their inventory.") .examples("on drop:") .since("<i>unknown</i> (before 2.1)"); if (hasPrepareCraftEvent) { // Must be loaded before CraftItemEvent - Skript.registerEvent("Prepare Craft", EvtItem.class, PrepareItemCraftEvent.class, "[player] (preparing|beginning) craft[ing] [[of] %itemtypes%]") + Skript.registerEvent("Prepare Craft", EvtItem.class, PrepareItemCraftEvent.class, "[player] (preparing|beginning) craft[ing] [[of] %-itemtypes%]") .description("Called just before displaying crafting result to player. Note that setting the result item might or might not work due to Bukkit bugs.") .examples("on preparing craft of torch:") .since("2.2-Fixes-V10"); } // TODO limit to InventoryAction.PICKUP_* and similar (e.g. COLLECT_TO_CURSOR) - Skript.registerEvent("Craft", EvtItem.class, CraftItemEvent.class, "[player] craft[ing] [[of] %itemtypes%]") + Skript.registerEvent("Craft", EvtItem.class, CraftItemEvent.class, "[player] craft[ing] [[of] %-itemtypes%]") .description("Called when a player crafts an item.") .examples("on craft:") .since("<i>unknown</i> (before 2.1)"); if (hasEntityPickupItemEvent) { Skript.registerEvent("Pick Up", EvtItem.class, CollectionUtils.array(PlayerPickupItemEvent.class, EntityPickupItemEvent.class), - "[(player|1¦entity)] (pick[ ]up|picking up) [[of] %itemtypes%]") + "[(player|1¦entity)] (pick[ ]up|picking up) [[of] %-itemtypes%]") .description("Called when a player/entity picks up an item. Please note that the item is still on the ground when this event is called.") .examples("on pick up:", "on entity pickup of wheat:") .since("<i>unknown</i> (before 2.1), 2.5 (entity)") .requiredPlugins("1.12.2+ for entity"); } else { - Skript.registerEvent("Pick Up", EvtItem.class, PlayerPickupItemEvent.class, "[player] (pick[ ]up|picking up) [[of] %itemtypes%]") + Skript.registerEvent("Pick Up", EvtItem.class, PlayerPickupItemEvent.class, "[player] (pick[ ]up|picking up) [[of] %-itemtypes%]") .description("Called when a player picks up an item. Please note that the item is still on the ground when this event is called.") .examples("on pick up:") .since("<i>unknown</i> (before 2.1)"); @@ -95,12 +95,12 @@ public class EvtItem extends SkriptEvent { // .examples("on brew:") // .since("2.0"); if (hasConsumeEvent) { - Skript.registerEvent("Consume", EvtItem.class, PlayerItemConsumeEvent.class, "[player] ((eat|drink)[ing]|consum(e|ing)) [[of] %itemtypes%]") + Skript.registerEvent("Consume", EvtItem.class, PlayerItemConsumeEvent.class, "[player] ((eat|drink)[ing]|consum(e|ing)) [[of] %-itemtypes%]") .description("Called when a player is done eating/drinking something, e.g. an apple, bread, meat, milk or a potion.") .examples("on consume:") .since("2.0"); } - Skript.registerEvent("Inventory Click", EvtItem.class, InventoryClickEvent.class, "[player] inventory(-| )click[ing] [[at] %itemtypes%]") + Skript.registerEvent("Inventory Click", EvtItem.class, InventoryClickEvent.class, "[player] inventory(-| )click[ing] [[at] %-itemtypes%]") .description("Called when clicking on inventory slot.") .examples("on inventory click:", "\tif event-item is stone:", diff --git a/src/main/java/ch/njol/skript/events/EvtPlantGrowth.java b/src/main/java/ch/njol/skript/events/EvtPlantGrowth.java index 5c4c5409dd1..d3b902bafd5 100644 --- a/src/main/java/ch/njol/skript/events/EvtPlantGrowth.java +++ b/src/main/java/ch/njol/skript/events/EvtPlantGrowth.java @@ -31,7 +31,7 @@ public class EvtPlantGrowth extends SkriptEvent { static { - Skript.registerEvent("Block Growth", EvtPlantGrowth.class, BlockGrowEvent.class, "(plant|crop|block) grow[(th|ing)] [[of] %itemtypes%]") + Skript.registerEvent("Block Growth", EvtPlantGrowth.class, BlockGrowEvent.class, "(plant|crop|block) grow[(th|ing)] [[of] %-itemtypes%]") .description("Called when a crop grows. Alternative to new form of generic grow event.") .examples("on crop growth:") .since("2.2-Fixes-V10"); diff --git a/src/main/java/ch/njol/skript/events/EvtWeatherChange.java b/src/main/java/ch/njol/skript/events/EvtWeatherChange.java index e3dac4e22a2..39859f7451a 100644 --- a/src/main/java/ch/njol/skript/events/EvtWeatherChange.java +++ b/src/main/java/ch/njol/skript/events/EvtWeatherChange.java @@ -37,7 +37,7 @@ @SuppressWarnings("unchecked") public class EvtWeatherChange extends SkriptEvent { static { - Skript.registerEvent("Weather Change", EvtWeatherChange.class, CollectionUtils.array(WeatherChangeEvent.class, ThunderChangeEvent.class), "weather change [to %weathertypes%]") + Skript.registerEvent("Weather Change", EvtWeatherChange.class, CollectionUtils.array(WeatherChangeEvent.class, ThunderChangeEvent.class), "weather change [to %-weathertypes%]") .description("Called when a world's weather changes.") .examples("on weather change:", "on weather change to sunny:") .since("1.0"); diff --git a/src/main/java/ch/njol/skript/expressions/ExprScript.java b/src/main/java/ch/njol/skript/expressions/ExprScript.java index ba4c177d8bd..4ca07457c85 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprScript.java +++ b/src/main/java/ch/njol/skript/expressions/ExprScript.java @@ -18,11 +18,7 @@ */ package ch.njol.skript.expressions; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; -import ch.njol.skript.config.Config; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Events; import ch.njol.skript.doc.Examples; @@ -30,49 +26,47 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; +import org.skriptlang.skript.lang.script.Script; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; -import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; -/** - * @author Peter Güttinger - */ @Name("Script Name") @Description("Holds the current script's name (the file name without '.sk').") -@Examples({"on script load:", - " set {running::%script%} to true", - "on script unload:", - " set {running::%script%} to false"}) +@Examples({ + "on script load:", + "\tset {running::%script%} to true", + "on script unload:", + "\tset {running::%script%} to false" +}) @Since("2.0") @Events("Script Load/Unload") public class ExprScript extends SimpleExpression<String> { static { Skript.registerExpression(ExprScript.class, String.class, ExpressionType.SIMPLE, - "[the] script[['s] name]", "name of [the] script"); + "[the] script[['s] name]", + "name of [the] script" + ); } - @SuppressWarnings("null") + @SuppressWarnings("NotNullFieldNotIntialized") private String name; @Override - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { - final Config script = getParser().getCurrentScript(); - if (script == null) { - assert false; - return false; - } - String name = script.getFileName(); + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + String name = getParser().getCurrentScript().getConfig().getFileName(); if (name.contains(".")) - name = "" + name.substring(0, name.lastIndexOf('.')); + name = name.substring(0, name.lastIndexOf('.')); this.name = name; return true; } @Override - protected String[] get(final Event e) { - return CollectionUtils.array(name); + protected String[] get(Event e) { + return new String[]{name}; } @Override @@ -86,7 +80,7 @@ public Class<? extends String> getReturnType() { } @Override - public String toString(final @Nullable Event e, final boolean debug) { + public String toString(@Nullable Event e, boolean debug) { return "the script's name"; } diff --git a/src/main/java/ch/njol/skript/expressions/ExprScripts.java b/src/main/java/ch/njol/skript/expressions/ExprScripts.java index c59efdd91ad..1300216bf9a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprScripts.java +++ b/src/main/java/ch/njol/skript/expressions/ExprScripts.java @@ -21,12 +21,12 @@ import ch.njol.skript.ScriptLoader; import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Events; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; +import org.skriptlang.skript.lang.script.Script; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; @@ -42,19 +42,21 @@ @Name("All Scripts") @Description("Returns all of the scripts, or just the enabled or disabled ones.") -@Examples({"command /scripts:", - "\ttrigger:", - "\t\tsend \"All Scripts: %scripts%\" to player", - "\t\tsend \"Loaded Scripts: %enabled scripts%\" to player", - "\t\tsend \"Unloaded Scripts: %disabled scripts%\" to player"}) +@Examples({ + "command /scripts:", + "\ttrigger:", + "\t\tsend \"All Scripts: %scripts%\" to player", + "\t\tsend \"Loaded Scripts: %enabled scripts%\" to player", + "\t\tsend \"Unloaded Scripts: %disabled scripts%\" to player" +}) @Since("2.5") public class ExprScripts extends SimpleExpression<String> { static { Skript.registerExpression(ExprScripts.class, String.class, ExpressionType.SIMPLE, - "[all [of the]] scripts [(1¦without ([subdirectory] paths|parents))]", - "[all [of the]] (enabled|loaded) scripts [(1¦without ([subdirectory] paths|parents))]", - "[all [of the]] (disabled|unloaded) scripts [(1¦without ([subdirectory] paths|parents))]"); + "[all [of the]] scripts [(1:without ([subdirectory] paths|parents))]", + "[all [of the]] (enabled|loaded) scripts [(1:without ([subdirectory] paths|parents))]", + "[all [of the]] (disabled|unloaded) scripts [(1:without ([subdirectory] paths|parents))]"); } private static final String SCRIPTS_PATH = new File(Skript.getInstance().getDataFolder(), Skript.SCRIPTSFOLDER).getPath() + File.separator; @@ -64,7 +66,7 @@ public class ExprScripts extends SimpleExpression<String> { private boolean noPaths; @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) { includeEnabled = matchedPattern <= 1; includeDisabled = matchedPattern != 1; noPaths = parseResult.mark == 1; @@ -74,10 +76,12 @@ public boolean init(final Expression<?>[] exprs, final int matchedPattern, final @Override protected String[] get(Event event) { List<File> scripts = new ArrayList<>(); - if (includeEnabled) - scripts.addAll(ScriptLoader.getLoadedFiles()); + if (includeEnabled) { + for (Script script : ScriptLoader.getLoadedScripts()) + scripts.add(script.getConfig().getFile()); + } if (includeDisabled) - scripts.addAll(ScriptLoader.getDisabledFiles()); + scripts.addAll(ScriptLoader.getDisabledScripts()); return formatFiles(scripts); } @@ -99,7 +103,7 @@ public Class<? extends String> getReturnType() { } @Override - public String toString(final @Nullable Event e, final boolean debug) { + public String toString(@Nullable Event e, boolean debug) { return "scripts"; } diff --git a/src/main/java/ch/njol/skript/lang/Section.java b/src/main/java/ch/njol/skript/lang/Section.java index 364ff43bf8f..a151e83ab4c 100644 --- a/src/main/java/ch/njol/skript/lang/Section.java +++ b/src/main/java/ch/njol/skript/lang/Section.java @@ -20,13 +20,13 @@ import ch.njol.skript.ScriptLoader; import ch.njol.skript.Skript; -import ch.njol.skript.config.Config; import ch.njol.skript.config.SectionNode; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.util.Kleenean; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.structure.Structure; import java.util.ArrayList; import java.util.Iterator; @@ -132,13 +132,13 @@ protected final Trigger loadCode(SectionNode sectionNode, String name, @Nullable String previousName = parser.getCurrentEventName(); Class<? extends Event>[] previousEvents = parser.getCurrentEvents(); - SkriptEvent previousSkriptEvent = parser.getCurrentSkriptEvent(); + Structure previousStructure = parser.getCurrentStructure(); List<TriggerSection> previousSections = parser.getCurrentSections(); Kleenean previousDelay = parser.getHasDelayBefore(); parser.setCurrentEvent(name, events); SkriptEvent skriptEvent = new SectionSkriptEvent(name, this); - parser.setCurrentSkriptEvent(skriptEvent); + parser.setCurrentStructure(skriptEvent); parser.setCurrentSections(new ArrayList<>()); parser.setHasDelayBefore(Kleenean.FALSE); List<TriggerItem> triggerItems = ScriptLoader.loadItems(sectionNode); @@ -148,12 +148,11 @@ protected final Trigger loadCode(SectionNode sectionNode, String name, @Nullable //noinspection ConstantConditions - We are resetting it to what it was parser.setCurrentEvent(previousName, previousEvents); - parser.setCurrentSkriptEvent(previousSkriptEvent); + parser.setCurrentStructure(previousStructure); parser.setCurrentSections(previousSections); parser.setHasDelayBefore(previousDelay); - Config script = parser.getCurrentScript(); - return new Trigger(script != null ? script.getFile() : null, name, skriptEvent, triggerItems); + return new Trigger(parser.getCurrentScript(), name, skriptEvent, triggerItems); } /** diff --git a/src/main/java/ch/njol/skript/lang/SelfRegisteringSkriptEvent.java b/src/main/java/ch/njol/skript/lang/SelfRegisteringSkriptEvent.java index e17ee7ecfe7..24816203653 100644 --- a/src/main/java/ch/njol/skript/lang/SelfRegisteringSkriptEvent.java +++ b/src/main/java/ch/njol/skript/lang/SelfRegisteringSkriptEvent.java @@ -18,9 +18,10 @@ */ package ch.njol.skript.lang; +import ch.njol.skript.config.Config; import org.bukkit.event.Event; -import ch.njol.skript.config.Config; +import java.util.Objects; public abstract class SelfRegisteringSkriptEvent extends SkriptEvent { @@ -29,14 +30,14 @@ public abstract class SelfRegisteringSkriptEvent extends SkriptEvent { * * @param t the trigger to register to this event */ - public abstract void register(final Trigger t); + public abstract void register(Trigger t); /** * This method is called to unregister this event registered through {@link #register(Trigger)}. * * @param t the same trigger which was registered for this event */ - public abstract void unregister(final Trigger t); + public abstract void unregister(Trigger t); /** * This method is called to unregister all events registered through {@link #register(Trigger)}. @@ -45,6 +46,14 @@ public abstract class SelfRegisteringSkriptEvent extends SkriptEvent { */ public abstract void unregisterAll(); + @Override + public boolean load() { + boolean load = super.load(); + if (load) + afterParse(Objects.requireNonNull(getParser().getCurrentScript()).getConfig()); + return load; + } + @Override public final boolean check(Event e) { throw new UnsupportedOperationException(); @@ -54,9 +63,11 @@ public final boolean check(Event e) { * This method is called when this event is parsed. Overriding this is * optional, and usually not needed. * @param config Script that is being parsed + * @deprecated Use {@link #postLoad()} instead. */ + @Deprecated public void afterParse(Config config) { - // DO NOTHING + } @Override diff --git a/src/main/java/ch/njol/skript/lang/SkriptEvent.java b/src/main/java/ch/njol/skript/lang/SkriptEvent.java index fc018f59417..263b4ade193 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptEvent.java +++ b/src/main/java/ch/njol/skript/lang/SkriptEvent.java @@ -18,16 +18,25 @@ */ package ch.njol.skript.lang; +import ch.njol.skript.ScriptLoader; import ch.njol.skript.Skript; import ch.njol.skript.SkriptConfig; +import ch.njol.skript.SkriptEventHandler; +import ch.njol.skript.config.SectionNode; import ch.njol.skript.events.EvtClick; import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.lang.parser.ParserInstance; -import ch.njol.util.Kleenean; +import org.skriptlang.skript.lang.script.Script; +import org.skriptlang.skript.lang.entry.EntryContainer; +import org.skriptlang.skript.lang.structure.Structure; +import ch.njol.util.StringUtils; import org.bukkit.event.Event; import org.bukkit.event.EventPriority; import org.eclipse.jdt.annotation.Nullable; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + /** * A SkriptEvent is like a condition. It is called when any of the registered events occurs. * An instance of this class should then check whether the event applies @@ -38,28 +47,141 @@ * @see Skript#registerEvent(String, Class, Class, String...) * @see Skript#registerEvent(String, Class, Class[], String...) */ -public abstract class SkriptEvent implements SyntaxElement, Debuggable { +@SuppressWarnings("NotNullFieldNotInitialized") +public abstract class SkriptEvent extends Structure { + + public static final Priority PRIORITY = new Priority(600); + private String expr; + @Nullable + protected EventPriority eventPriority; + private SkriptEventInfo<?> skriptEventInfo; + @Nullable + private List<TriggerItem> items; @Nullable - EventPriority eventPriority; + private Trigger trigger; @Override - public final boolean init(ch.njol.skript.lang.Expression<?>[] vars, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - throw new UnsupportedOperationException(); + public final boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult, EntryContainer entryContainer) { + String expr = parseResult.expr; + if (StringUtils.startsWithIgnoreCase(expr, "on ")) + expr = expr.substring("on ".length()); + + String[] split = expr.split(" with priority "); + if (split.length != 1) { + if (!isEventPrioritySupported()) { + Skript.error("This event doesn't support event priority"); + return false; + } + + expr = String.join(" with priority ", Arrays.copyOfRange(split, 0, split.length - 1)); + + String priorityString = split[split.length - 1]; + try { + eventPriority = EventPriority.valueOf(priorityString.toUpperCase()); + } catch (IllegalArgumentException e) { + throw new IllegalStateException(e); + } + } else { + eventPriority = null; + } + + this.expr = parseResult.expr = expr; + + SyntaxElementInfo<? extends Structure> syntaxElementInfo = getParser().getData(StructureData.class).getStructureInfo(); + if (!(syntaxElementInfo instanceof SkriptEventInfo)) + throw new IllegalStateException(); + skriptEventInfo = (SkriptEventInfo<?>) syntaxElementInfo; + + return init(args, matchedPattern, parseResult); } /** - * called just after the constructor - * - * @param args + * Called just after the constructor */ public abstract boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult); + /** + * This method handles the loading of the Structure's syntax elements. + * Only override this method if you know what you are doing! + */ + @Override + public boolean load() { + if (!shouldLoadEvent()) + return false; + + SectionNode source = getEntryContainer().getSource(); + if (Skript.debug() || source.debug()) + Skript.debug(expr + " (" + this + "):"); + + Class<? extends Event>[] eventClasses = getEventClasses(); + + try { + getParser().setCurrentEvent(skriptEventInfo.getName().toLowerCase(Locale.ENGLISH), eventClasses); + + items = ScriptLoader.loadItems(source); + } finally { + getParser().deleteCurrentEvent(); + } + + return true; + } + + /** + * This method handles the registration of this event with Skript and Bukkit. + * Only override this method if you know what you are doing! + */ + @Override + public boolean postLoad() { + getParser().setCurrentEvent(skriptEventInfo.getName().toLowerCase(Locale.ENGLISH), getEventClasses()); + + Script script = getParser().getCurrentScript(); + + try { + assert items != null; // This method will only be called if 'load' was successful, meaning 'items' will be set + trigger = new Trigger(script, expr, this, items); + int lineNumber = getEntryContainer().getSource().getLine(); + trigger.setLineNumber(lineNumber); // Set line number for debugging + trigger.setDebugLabel(script + ": line " + lineNumber); + } finally { + getParser().deleteCurrentEvent(); + } + + if (this instanceof SelfRegisteringSkriptEvent) { + ((SelfRegisteringSkriptEvent) this).register(trigger); + } else { + SkriptEventHandler.registerBukkitEvents(trigger, getEventClasses()); + } + + getParser().deleteCurrentEvent(); + + return true; + } + + /** + * This method handles the unregistration of this event with Skript and Bukkit. + * Only override this method if you know what you are doing! + */ + @Override + public void unload() { + if (trigger == null) + return; + + if (this instanceof SelfRegisteringSkriptEvent) { + ((SelfRegisteringSkriptEvent) this).unregister(trigger); + } else { + SkriptEventHandler.unregisterBukkitEvents(trigger); + } + } + + @Override + public Priority getPriority() { + return PRIORITY; + } + /** * Checks whether the given Event applies, e.g. the leftclick event is only part of the PlayerInteractEvent, and this checks whether the player leftclicked or not. This method * will only be called for events this SkriptEvent is registered for. - * - * @param e * @return true if this is SkriptEvent is represented by the Bukkit Event or false if not */ public abstract boolean check(Event e); @@ -74,16 +196,10 @@ public boolean shouldLoadEvent() { } /** - * @return the Event classes to use in {@link ch.njol.skript.lang.parser.ParserInstance}, - * or {@code null} if the Event classes this SkriptEvent was registered with should be used. + * @return the Event classes to use in {@link ch.njol.skript.lang.parser.ParserInstance}. */ - public Class<? extends Event> @Nullable[] getEventClasses() { - return null; - } - - @Override - public String toString() { - return toString(null, false); + public Class<? extends Event>[] getEventClasses() { + return skriptEventInfo.events; } /** @@ -101,4 +217,34 @@ public boolean isEventPrioritySupported() { return true; } + /** + * Fixes patterns in event by modifying every {@link ch.njol.skript.patterns.TypePatternElement} + * to be nullable. + */ + public static String fixPattern(String pattern) { + char[] chars = pattern.toCharArray(); + StringBuilder stringBuilder = new StringBuilder(); + + boolean inType = false; + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + stringBuilder.append(c); + + if (c == '%') { + // toggle inType + inType = !inType; + + // add the dash character if it's not already present + // a type specification can have two prefix characters for modification + if (inType && i + 2 < chars.length && chars[i + 1] != '-' && chars[i + 2] != '-') + stringBuilder.append('-'); + } else if (c == '\\' && i + 1 < chars.length) { + // Make sure we don't toggle inType for escape percentage signs + stringBuilder.append(chars[i + 1]); + i++; + } + } + return stringBuilder.toString(); + } + } diff --git a/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java b/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java index 4c44d8875dd..91ae4610622 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java +++ b/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java @@ -20,13 +20,14 @@ import java.util.Locale; +import org.skriptlang.skript.lang.structure.StructureInfo; import org.bukkit.event.Event; import org.bukkit.event.player.PlayerInteractAtEntityEvent; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.SkriptAPIException; -public final class SkriptEventInfo<E extends SkriptEvent> extends SyntaxElementInfo<E> { +public final class SkriptEventInfo<E extends SkriptEvent> extends StructureInfo<E> { public Class<? extends Event>[] events; public final String name; diff --git a/src/main/java/ch/njol/skript/lang/SkriptParser.java b/src/main/java/ch/njol/skript/lang/SkriptParser.java index e7580b8d573..1679b65d507 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptParser.java +++ b/src/main/java/ch/njol/skript/lang/SkriptParser.java @@ -26,8 +26,9 @@ import ch.njol.skript.command.Commands; import ch.njol.skript.command.ScriptCommand; import ch.njol.skript.command.ScriptCommandEvent; -import ch.njol.skript.config.Config; import ch.njol.skript.expressions.ExprParse; +import org.skriptlang.skript.lang.script.Script; +import org.skriptlang.skript.lang.script.ScriptWarning; import ch.njol.skript.lang.function.ExprFunctionCall; import ch.njol.skript.lang.function.FunctionReference; import ch.njol.skript.lang.function.Functions; @@ -38,13 +39,11 @@ import ch.njol.skript.log.ErrorQuality; import ch.njol.skript.log.LogEntry; import ch.njol.skript.log.ParseLogHandler; -import ch.njol.skript.log.RetainingLogHandler; import ch.njol.skript.log.SkriptLogger; import ch.njol.skript.patterns.MalformedPatternException; import ch.njol.skript.patterns.PatternCompiler; import ch.njol.skript.patterns.SkriptPattern; import ch.njol.skript.registrations.Classes; -import ch.njol.skript.util.ScriptOptions; import ch.njol.skript.util.Utils; import ch.njol.util.Kleenean; import ch.njol.util.NonNullPair; @@ -57,14 +56,13 @@ import org.eclipse.jdt.annotation.Nullable; import java.util.ArrayList; -import java.util.Arrays; import java.util.Deque; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; -import java.util.Locale; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.regex.MatchResult; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -124,7 +122,7 @@ public SkriptParser(final SkriptParser other, final String expr) { public final static class ParseResult { public final Expression<?>[] exprs; public final List<MatchResult> regexes = new ArrayList<>(1); - public final String expr; + public String expr; /** * Defaults to 0. Any marks encountered in the pattern will be XORed with the existing value, in particular if only one mark is encountered this value will be set to that * mark. @@ -188,16 +186,22 @@ public static <T extends SyntaxElement> T parse(String expr, final Iterator<? ex } @Nullable - public static <T extends SyntaxElement> T parseStatic(String expr, final Iterator<? extends SyntaxElementInfo<? extends T>> source, final @Nullable String defaultError) { - expr = "" + expr.trim(); + public static <T extends SyntaxElement> T parseStatic(String expr, Iterator<? extends SyntaxElementInfo<? extends T>> source, @Nullable String defaultError) { + return parseStatic(expr, source, ParseContext.DEFAULT, defaultError); + } + + @Nullable + public static <T extends SyntaxElement> T parseStatic(String expr, Iterator<? extends SyntaxElementInfo<? extends T>> source, ParseContext parseContext, @Nullable String defaultError) { + expr = expr.trim(); if (expr.isEmpty()) { Skript.error(defaultError); return null; } - final ParseLogHandler log = SkriptLogger.startParseLogHandler(); - final T e; + + ParseLogHandler log = SkriptLogger.startParseLogHandler(); + T e; try { - e = new SkriptParser(expr, PARSE_LITERALS).parse(source); + e = new SkriptParser(expr, PARSE_LITERALS, parseContext).parse(source); if (e != null) { log.printLog(); return e; @@ -208,7 +212,7 @@ public static <T extends SyntaxElement> T parseStatic(String expr, final Iterato log.stop(); } } - + @Nullable private <T extends SyntaxElement> T parse(Iterator<? extends SyntaxElementInfo<? extends T>> source) { ParseLogHandler log = SkriptLogger.startParseLogHandler(); @@ -718,14 +722,10 @@ public final <T> Expression<? extends T> parseExpression(final Class<? extends T return ts.get(0); if (and.isUnknown() && !suppressMissingAndOrWarnings) { - if (getParser().getCurrentScript() != null) { - Config cs = getParser().getCurrentScript(); - if (!ScriptOptions.getInstance().suppressesWarning(cs.getFile(), "conjunction")) { - Skript.warning(MISSING_AND_OR + ": " + expr); - } - } else { + ParserInstance parser = getParser(); + Script currentScript = parser.isActive() ? parser.getCurrentScript() : null; + if (currentScript == null || !currentScript.suppressesWarning(ScriptWarning.MISSING_CONJUNCTION)) Skript.warning(MISSING_AND_OR + ": " + expr); - } } final Class<? extends T>[] exprRetTypes = new Class[ts.size()]; @@ -854,13 +854,10 @@ public final Expression<?> parseExpression(final ExprInfo vi) { } if (and.isUnknown() && !suppressMissingAndOrWarnings) { - if (getParser().getCurrentScript() != null) { - Config cs = getParser().getCurrentScript(); - if (!ScriptOptions.getInstance().suppressesWarning(cs.getFile(), "conjunction")) - Skript.warning(MISSING_AND_OR + ": " + expr); - } else { + ParserInstance parser = getParser(); + Script currentScript = parser.isActive() ? parser.getCurrentScript() : null; + if (currentScript == null || !currentScript.suppressesWarning(ScriptWarning.MISSING_CONJUNCTION)) Skript.warning(MISSING_AND_OR + ": " + expr); - } } final Class<?>[] exprRetTypes = new Class[ts.size()]; @@ -959,8 +956,10 @@ public final <T> FunctionReference<T> parseFunction(final @Nullable Class<? exte // } // @SuppressWarnings("null") + ParserInstance parser = getParser(); + Script currentScript = parser.isActive() ? parser.getCurrentScript() : null; final FunctionReference<T> e = new FunctionReference<>(functionName, SkriptLogger.getNode(), - getParser().getCurrentScript() != null ? getParser().getCurrentScript().getFileName() : null, types, params);//.toArray(new Expression[params.size()])); + currentScript != null ? currentScript.getConfig().getFileName() : null, types, params);//.toArray(new Expression[params.size()])); if (!e.validateFunction(true)) { log.printError(); return null; @@ -1001,79 +1000,6 @@ public static boolean parseArguments(final String args, final ScriptCommand comm public static ParseResult parse(final String text, final String pattern) { return new SkriptParser(text, PARSE_LITERALS, ParseContext.COMMAND).parse_i(pattern, 0, 0); } - - @Nullable - public static NonNullPair<SkriptEventInfo<?>, SkriptEvent> parseEvent(String event, String defaultError) { - RetainingLogHandler log = SkriptLogger.startRetainingLog(); - try { - String[] split = event.split(" with priority "); - EventPriority priority; - if (split.length != 1) { - event = String.join(" with priority ", Arrays.copyOfRange(split, 0, split.length - 1)); - - String priorityString = split[split.length - 1]; - try { - priority = EventPriority.valueOf(priorityString.toUpperCase(Locale.ENGLISH)); - } catch (IllegalArgumentException e) { // Priority doesn't exist - log.printErrors("The priority " + priorityString + " doesn't exist"); - return null; - } - } else { - priority = null; - } - - NonNullPair<SkriptEventInfo<?>, SkriptEvent> e = new SkriptParser(event, PARSE_LITERALS, ParseContext.EVENT).parseEvent(priority); - if (e != null) { - if (priority != null && !e.getSecond().isEventPrioritySupported()) { - log.printErrors("This event doesn't support event priority"); - return null; - } - - log.printLog(); - return e; - } - log.printErrors(defaultError); - return null; - } finally { - log.stop(); - } - } - - @Nullable - private NonNullPair<SkriptEventInfo<?>, SkriptEvent> parseEvent(@Nullable EventPriority eventPriority) { - assert context == ParseContext.EVENT; - assert flags == PARSE_LITERALS; - ParseLogHandler log = SkriptLogger.startParseLogHandler(); - try { - for (SkriptEventInfo<?> info : Skript.getEvents()) { - for (int i = 0; i < info.patterns.length; i++) { - log.clear(); - try { - String pattern = info.patterns[i]; - assert pattern != null; - ParseResult res = parse_i(pattern, 0, 0); - if (res != null) { - SkriptEvent e = info.c.newInstance(); - e.eventPriority = eventPriority; - Literal<?>[] ls = Arrays.copyOf(res.exprs, res.exprs.length, Literal[].class); - if (!e.init(ls, i, res)) { - log.printError(); - return null; - } - log.printLog(); - return new NonNullPair<>(info, e); - } - } catch (InstantiationException | IllegalAccessException e) { - assert false; - } - } - } - log.printError(null); - return null; - } finally { - log.stop(); - } - } /** * Finds the closing bracket of the group at <tt>start</tt> (i.e. <tt>start</tt> has to be <i>in</i> a group). @@ -1250,7 +1176,7 @@ public static int next(final String expr, final int i, final ParseContext contex return i + 1; } - private static final Map<String, SkriptPattern> patterns = new HashMap<>(); + private static final Map<String, SkriptPattern> patterns = new ConcurrentHashMap<>(); @Nullable private ParseResult parse_i(String pattern, int i, int j) { diff --git a/src/main/java/ch/njol/skript/lang/Trigger.java b/src/main/java/ch/njol/skript/lang/Trigger.java index 1c41e2c2c0f..50099e1386f 100644 --- a/src/main/java/ch/njol/skript/lang/Trigger.java +++ b/src/main/java/ch/njol/skript/lang/Trigger.java @@ -18,28 +18,24 @@ */ package ch.njol.skript.lang; -import java.io.File; -import java.util.List; - +import org.skriptlang.skript.lang.script.Script; +import ch.njol.skript.variables.Variables; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.variables.Variables; +import java.util.List; -/** - * @author Peter Güttinger - */ public class Trigger extends TriggerSection { private final String name; private final SkriptEvent event; @Nullable - private final File script; + private final Script script; private int line = -1; // -1 is default: it means there is no line number available private String debugLabel; - public Trigger(final @Nullable File script, final String name, final SkriptEvent event, final List<TriggerItem> items) { + public Trigger(@Nullable Script script, String name, SkriptEvent event, List<TriggerItem> items) { super(items); this.script = script; this.name = name; @@ -85,8 +81,7 @@ public String toString(final @Nullable Event e, final boolean debug) { } /** - * Gets name of this trigger. - * @return Name of trigger. + * @return The name of this trigger. */ public String getName() { return name; @@ -95,9 +90,12 @@ public String getName() { public SkriptEvent getEvent() { return event; } - + + /** + * @return The script this trigger was created from. + */ @Nullable - public File getScript() { + public Script getScript() { return script; } @@ -111,9 +109,7 @@ public void setLineNumber(int line) { } /** - * Gets line number for this trigger's start. - * Only use it for debugging! - * @return Line number. + * @return The line number where this trigger starts. This should ONLY be used for debugging! */ public int getLineNumber() { return line; diff --git a/src/main/java/ch/njol/skript/lang/TriggerItem.java b/src/main/java/ch/njol/skript/lang/TriggerItem.java index d862f024c8b..6322cb63524 100644 --- a/src/main/java/ch/njol/skript/lang/TriggerItem.java +++ b/src/main/java/ch/njol/skript/lang/TriggerItem.java @@ -20,6 +20,7 @@ import java.io.File; +import org.skriptlang.skript.lang.script.Script; import ch.njol.skript.util.SkriptColor; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @@ -90,9 +91,17 @@ public static boolean walk(final TriggerItem start, final Event e) { return true; } catch (final StackOverflowError err) { - final Trigger t = start.getTrigger(); - final File sc = t == null ? null : t.getScript(); - Skript.adminBroadcast("<red>The script '<gold>" + (sc == null ? "<unknown>" : sc.getName()) + "<red>' infinitely (or excessively) repeated itself!"); + Trigger t = start.getTrigger(); + String scriptName = "<unknown>"; + if (t != null) { + Script script = t.getScript(); + if (script != null) { + File scriptFile = script.getConfig().getFile(); + if (scriptFile != null) + scriptName = scriptFile.getName(); + } + } + Skript.adminBroadcast("<red>The script '<gold>" + scriptName + "<red>' infinitely (or excessively) repeated itself!"); if (Skript.debug()) err.printStackTrace(); } catch (final Exception ex) { diff --git a/src/main/java/ch/njol/skript/lang/Variable.java b/src/main/java/ch/njol/skript/lang/Variable.java index b2184d7cf11..bf4fbbae19c 100644 --- a/src/main/java/ch/njol/skript/lang/Variable.java +++ b/src/main/java/ch/njol/skript/lang/Variable.java @@ -27,14 +27,14 @@ import ch.njol.skript.classes.Changer.ChangerUtils; import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.classes.Comparator.Relation; -import ch.njol.skript.config.Config; +import org.skriptlang.skript.lang.script.Script; +import org.skriptlang.skript.lang.script.ScriptWarning; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.skript.registrations.Classes; import ch.njol.skript.registrations.Comparators; import ch.njol.skript.registrations.Converters; -import ch.njol.skript.util.ScriptOptions; import ch.njol.skript.util.StringMode; import ch.njol.skript.util.Utils; import ch.njol.skript.variables.TypeHints; @@ -47,7 +47,6 @@ import ch.njol.util.coll.iterator.EmptyIterator; import ch.njol.util.coll.iterator.SingleItemIterator; import org.bukkit.Bukkit; -import org.bukkit.OfflinePlayer; import org.bukkit.World; import org.bukkit.entity.Player; import org.bukkit.event.Event; @@ -181,14 +180,15 @@ public static <T> Variable<T> newInstance(String name, Class<? extends T>[] type boolean isLocal = name.startsWith(LOCAL_VARIABLE_TOKEN); boolean isPlural = name.endsWith(SEPARATOR + "*"); - Config currentScript = ParserInstance.get().getCurrentScript(); + ParserInstance parser = ParserInstance.get(); + Script currentScript = parser.isActive() ? parser.getCurrentScript() : null; if (currentScript != null && !SkriptConfig.disableVariableStartingWithExpressionWarnings.value() - && !ScriptOptions.getInstance().suppressesWarning(currentScript.getFile(), "start expression") + && !currentScript.suppressesWarning(ScriptWarning.VARIABLE_STARTS_WITH_EXPRESSION) && (isLocal ? name.substring(LOCAL_VARIABLE_TOKEN.length()) : name).startsWith("%")) { Skript.warning("Starting a variable's name with an expression is discouraged ({" + name + "}). " + "You could prefix it with the script's name: " + - "{" + StringUtils.substring(currentScript.getFileName(), 0, -3) + "." + name + "}"); + "{" + StringUtils.substring(currentScript.getConfig().getFileName(), 0, -3) + "." + name + "}"); } // Check for local variable type hints diff --git a/src/main/java/ch/njol/skript/lang/VariableString.java b/src/main/java/ch/njol/skript/lang/VariableString.java index 30615c4f955..51f8191e144 100644 --- a/src/main/java/ch/njol/skript/lang/VariableString.java +++ b/src/main/java/ch/njol/skript/lang/VariableString.java @@ -131,6 +131,25 @@ public static VariableString newInstance(String s) { return newInstance(s, StringMode.MESSAGE); } + /** + * Attempts to properly quote a string (e.g. double the double quotations). + * Please note that the string itself will not be surrounded with double quotations. + * @param string The string to properly quote. + * @return The input where all double quotations outside of expressions have been doubled. + */ + public static String quote(String string) { + StringBuilder fixed = new StringBuilder(); + boolean inExpression = false; + for (char c : string.toCharArray()) { + if (c == '%') // If we are entering an expression, quotes should NOT be doubled + inExpression = !inExpression; + if (!inExpression && c == '"') + fixed.append('"'); + fixed.append(c); + } + return fixed.toString(); + } + /** * Tests whether a string is correctly quoted, i.e. only has doubled double quotes in it. * Singular double quotes are only allowed between percentage signs. 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 29d04ad3e28..6bbf70b2014 100644 --- a/src/main/java/ch/njol/skript/lang/function/Functions.java +++ b/src/main/java/ch/njol/skript/lang/function/Functions.java @@ -18,18 +18,6 @@ */ package ch.njol.skript.lang.function; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.ScriptLoader; import ch.njol.skript.Skript; import ch.njol.skript.SkriptAPIException; @@ -37,12 +25,24 @@ import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.config.SectionNode; import ch.njol.skript.lang.ParseContext; +import org.skriptlang.skript.lang.script.Script; import ch.njol.skript.lang.SkriptParser; import ch.njol.skript.log.SkriptLogger; import ch.njol.skript.registrations.Classes; import ch.njol.skript.util.Utils; import ch.njol.util.NonNullPair; import ch.njol.util.StringUtils; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Static methods to work with functions. @@ -56,32 +56,32 @@ public abstract class Functions { "Refer to the documentation for more information."; private Functions() {} - + @Nullable public static ScriptFunction<?> currentFunction = null; - + /** * Function namespaces. */ private static final Map<Namespace.Key, Namespace> namespaces = new HashMap<>(); - + /** * Namespace of Java functions. */ private static final Namespace javaNamespace; - + static { javaNamespace = new Namespace(); namespaces.put(new Namespace.Key(Namespace.Origin.JAVA, "unknown"), javaNamespace); } - + /** * Namespaces of functions that are globally available. */ private static final Map<String, Namespace> globalFunctions = new HashMap<>(); - + static boolean callFunctionEvents = false; - + /** * Registers a function written in Java. * @@ -95,23 +95,23 @@ public static JavaFunction<?> registerFunction(JavaFunction<?> function) { javaNamespace.addSignature(function.getSignature()); javaNamespace.addFunction(function); globalFunctions.put(function.getName(), javaNamespace); - + return function; } - + public final static String functionNamePattern = "[\\p{IsAlphabetic}][\\p{IsAlphabetic}\\p{IsDigit}_]*"; - + @SuppressWarnings("null") private final static Pattern functionPattern = Pattern.compile("function (" + functionNamePattern + ")\\((.*)\\)(?: :: (.+))?", Pattern.CASE_INSENSITIVE), - paramPattern = Pattern.compile("\\s*(.+?)\\s*:(?=[^:]*$)\\s*(.+?)(?:\\s*=\\s*(.+))?\\s*"); - + paramPattern = Pattern.compile("\\s*(.+?)\\s*:(?=[^:]*$)\\s*(.+?)(?:\\s*=\\s*(.+))?\\s*"); + /** * Loads a script function from given node. * @param node Section node. * @return Script function, or null if something went wrong. */ @Nullable - public static Function<?> loadFunction(SectionNode node) { + public static Function<?> loadFunction(Script script, SectionNode node) { SkriptLogger.setNode(node); String key = node.getKey(); String definition = ScriptLoader.replaceOptions(key == null ? "" : key); @@ -120,7 +120,7 @@ public static Function<?> loadFunction(SectionNode node) { if (!m.matches()) // We have checks when loading the signature, but matches() must be called anyway return null; // don't error, already done in signature loading String name = "" + m.group(1); - + Namespace namespace = globalFunctions.get(name); if (namespace == null) { return null; // Probably duplicate signature; reported before @@ -130,19 +130,19 @@ public static Function<?> loadFunction(SectionNode node) { return null; // This has been reported before... Parameter<?>[] params = sign.parameters; ClassInfo<?> c = sign.returnType; - + if (Skript.debug() || node.debug()) Skript.debug("function " + name + "(" + StringUtils.join(params, ", ") + ")" + (c != null ? " :: " + (sign.isSingle() ? c.getName().getSingular() : c.getName().getPlural()) : "") + ":"); - - Function<?> f = new ScriptFunction<>(sign, node); - + + Function<?> f = new ScriptFunction<>(sign, script, node); + // Register the function for signature namespace.addFunction(f); - + return f; } - + /** * Loads the signature of function from given node. * @param script Script file name (<b>might</b> be used for some checks). @@ -158,7 +158,7 @@ public static Signature<?> loadSignature(String script, SectionNode node) { if (!m.matches()) return signError(INVALID_FUNCTION_DEFINITION); String name = "" + m.group(1); - + // Ensure there are no duplicate functions if (globalFunctions.containsKey(name)) { Namespace namespace = globalFunctions.get(name); @@ -170,7 +170,7 @@ public static Signature<?> loadSignature(String script, SectionNode node) { return signError("A function named '" + name + "' already exists in script '" + sign.script + "'"); } } - + String args = m.group(2); String returnType = m.group(3); List<Parameter<?>> params = new ArrayList<>(); @@ -180,10 +180,10 @@ public static Signature<?> loadSignature(String script, SectionNode node) { return signError("Invalid text/variables/parentheses in the arguments of this function"); if (i == args.length() || args.charAt(i) == ',') { String arg = args.substring(j, i); - + if (arg.isEmpty()) // Zero-argument function break; - + // One ore more arguments for this function Matcher n = paramPattern.matcher(arg); if (!n.matches()) @@ -201,18 +201,18 @@ public static Signature<?> loadSignature(String script, SectionNode node) { if (c == null) return signError("Cannot recognise the type '" + n.group(2) + "'"); String rParamName = paramName.endsWith("*") ? paramName.substring(0, paramName.length() - 3) + - (!pl.getSecond() ? "::1" : "") : paramName; + (!pl.getSecond() ? "::1" : "") : paramName; Parameter<?> p = Parameter.newInstance(rParamName, c, !pl.getSecond(), n.group(3)); if (p == null) return null; params.add(p); - + j = i + 1; } if (i == args.length()) break; } - + // Parse return type if one exists ClassInfo<?> returnClass; boolean singleReturn; @@ -229,7 +229,7 @@ public static Signature<?> loadSignature(String script, SectionNode node) { return signError("Cannot recognise the type '" + returnType + "'"); } } - + @SuppressWarnings({"unchecked", "null"}) Signature<?> sign = new Signature<>(script, name, params.toArray(new Parameter[0]), (ClassInfo<Object>) returnClass, singleReturn); @@ -239,11 +239,11 @@ public static Signature<?> loadSignature(String script, SectionNode node) { Namespace namespace = namespaces.computeIfAbsent(namespaceKey, k -> new Namespace()); namespace.addSignature(sign); globalFunctions.put(name, namespace); - + Skript.debug("Registered function signature: " + name); return sign; } - + /** * Creates an error and returns Function null. * @param error Error message. @@ -254,7 +254,7 @@ private static Function<?> error(String error) { Skript.error(error); return null; } - + /** * Creates an error and returns Signature null. * @param error Error message. @@ -265,7 +265,7 @@ private static Signature<?> signError(String error) { Skript.error(error); return null; } - + /** * Gets a function, if it exists. Note that even if function exists in scripts, * it might not have been parsed yet. If you want to check for existance, @@ -281,7 +281,7 @@ public static Function<?> getFunction(String name) { } return namespace.getFunction(name); } - + /** * Gets a signature of function with given name. * @param name Name of function. @@ -295,24 +295,25 @@ public static Signature<?> getSignature(String name) { } return namespace.getSignature(name); } - + private final static Collection<FunctionReference<?>> toValidate = new ArrayList<>(); - + /** * Remember to call {@link #validateFunctions()} after calling this * * @return How many functions were removed */ + @Deprecated public static int clearFunctions(String script) { // Get and remove function namespace of script Namespace namespace = namespaces.remove(new Namespace.Key(Namespace.Origin.SCRIPT, script)); if (namespace == null) { // No functions defined return 0; } - + // Remove references to this namespace from global functions globalFunctions.values().removeIf(loopedNamespaced -> loopedNamespaced == namespace); - + // Queue references to signatures we have for revalidation // Can't validate here, because other scripts might be loaded soon for (Signature<?> sign : namespace.getSignatures()) { @@ -324,31 +325,53 @@ public static int clearFunctions(String script) { } return namespace.getSignatures().size(); } - + + public static void unregisterFunction(Signature<?> signature) { + Iterator<Namespace> namespaceIterator = namespaces.values().iterator(); + while (namespaceIterator.hasNext()) { + Namespace namespace = namespaceIterator.next(); + if (namespace.removeSignature(signature)) { + globalFunctions.remove(signature.getName()); + + // remove the namespace if it is empty + if (namespace.getSignatures().isEmpty()) + namespaceIterator.remove(); + + break; + } + } + + for (FunctionReference<?> ref : signature.calls) { + if (!signature.script.equals(ref.script)) + toValidate.add(ref); + } + } + public static void validateFunctions() { for (FunctionReference<?> c : toValidate) c.validateFunction(false); toValidate.clear(); } - + /** * Clears all function calls and removes script functions. */ + @Deprecated public static void clearFunctions() { - // Keep Java functions, remove everything else + // Keep Java functions, remove everything else globalFunctions.values().removeIf(namespace -> namespace != javaNamespace); namespaces.clear(); - + assert toValidate.isEmpty() : toValidate; toValidate.clear(); } - + @SuppressWarnings({"unchecked"}) public static Collection<JavaFunction<?>> getJavaFunctions() { // We know there are only Java functions in that namespace return (Collection<JavaFunction<?>>) (Object) javaNamespace.getFunctions(); } - + /** * Normally, function calls do not cause actual Bukkit events to be * called. If an addon requires such functionality, it should call this @@ -357,7 +380,7 @@ public static Collection<JavaFunction<?>> getJavaFunctions() { * <p> * Note that calling events is not free; performance might vary * once you have enabled that. - * + * * @param addon Addon instance. */ @SuppressWarnings({"null", "unused"}) @@ -365,7 +388,7 @@ public static void enableFunctionEvents(SkriptAddon addon) { if (addon == null) { throw new SkriptAPIException("enabling function events requires addon instance"); } - + callFunctionEvents = true; } } diff --git a/src/main/java/ch/njol/skript/lang/function/Namespace.java b/src/main/java/ch/njol/skript/lang/function/Namespace.java index 672b589b781..5ee8a3f9e34 100644 --- a/src/main/java/ch/njol/skript/lang/function/Namespace.java +++ b/src/main/java/ch/njol/skript/lang/function/Namespace.java @@ -119,6 +119,13 @@ public void addSignature(Signature<?> sign) { } signatures.put(sign.getName(), sign); } + + public boolean removeSignature(Signature<?> sign) { + if (signatures.get(sign.getName()) != sign) + return false; + signatures.remove(sign.getName()); + return true; + } @SuppressWarnings("null") public Collection<Signature<?>> getSignatures() { 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 77b13c7ec0f..259c8bcad85 100644 --- a/src/main/java/ch/njol/skript/lang/function/ScriptFunction.java +++ b/src/main/java/ch/njol/skript/lang/function/ScriptFunction.java @@ -18,6 +18,7 @@ */ package ch.njol.skript.lang.function; +import org.skriptlang.skript.lang.script.Script; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.ScriptLoader; @@ -34,13 +35,13 @@ public class ScriptFunction<T> extends Function<T> { private final Trigger trigger; - public ScriptFunction(Signature<T> sign, SectionNode node) { + public ScriptFunction(Signature<T> sign, Script script, SectionNode node) { super(sign); Functions.currentFunction = this; try { trigger = new Trigger( - node.getConfig().getFile(), + script, "function " + sign.getName(), new SimpleEvent(), ScriptLoader.loadItems(node) diff --git a/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java b/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java index e0923ca8ae2..25687c5ba16 100644 --- a/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java +++ b/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java @@ -18,132 +18,260 @@ */ package ch.njol.skript.lang.parser; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Function; - -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; -import org.jetbrains.annotations.NotNull; - +import ch.njol.skript.ScriptLoader; +import ch.njol.skript.SkriptAPIException; import ch.njol.skript.config.Config; import ch.njol.skript.config.Node; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptEvent; import ch.njol.skript.lang.SkriptParser; import ch.njol.skript.lang.TriggerSection; +import ch.njol.skript.lang.util.ContextlessEvent; import ch.njol.skript.log.HandlerList; +import ch.njol.skript.structures.StructOptions; import ch.njol.util.Kleenean; +import ch.njol.util.Validate; import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.NotNull; +import org.skriptlang.skript.lang.script.Script; +import org.skriptlang.skript.lang.structure.Structure; -public class ParserInstance { - - private static final ThreadLocal<ParserInstance> parserInstances = ThreadLocal.withInitial(ParserInstance::new); +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +public final class ParserInstance { + private static final ThreadLocal<ParserInstance> PARSER_INSTANCES = ThreadLocal.withInitial(ParserInstance::new); + /** * @return The {@link ParserInstance} for this thread. */ // TODO maybe make a one-thread cache (e.g. Pair<Thread, ParserInstance>) if it's better for performance (test) public static ParserInstance get() { - return parserInstances.get(); + return PARSER_INSTANCES.get(); } - - // Logging - private final HandlerList handlers = new HandlerList(); - @Nullable - private Node node; - - // Script - @Nullable - private Config currentScript; - private final HashMap<String, String> currentOptions = new HashMap<>(); - - // Event - @Nullable - private String currentEventName; - @Nullable - private Class<? extends Event>[] currentEvents; + + private boolean isActive = false; + + /** + * Internal method for updating a ParserInstance's {@link #isActive()} status! + * You probably don't need to use this method! + */ + public void setInactive() { + this.isActive = false; + setCurrentScript((Script) null); + setCurrentStructure(null); + deleteCurrentEvent(); + getCurrentSections().clear(); + setNode(null); + } + + /** + * Internal method for updating a ParserInstance's {@link #isActive()} status! + * You probably don't need to use this method! + */ + public void setActive(Script script) { + this.isActive = true; + setCurrentScript(script); + setNode(null); + } + + /** + * @return Whether this ParserInstance is currently active. + * An active ParserInstance may be loading, parsing, or unloading scripts. + * Please note that some methods may be unavailable if this method returns <code>false</code>. + * You should consult the documentation of the method you are calling. + */ + public boolean isActive() { + return isActive; + } + + // Script API + @Nullable - private SkriptEvent currentSkriptEvent; - - // Sections - private List<TriggerSection> currentSections = new ArrayList<>(); - private Kleenean hasDelayBefore = Kleenean.FALSE; - private String indentation = ""; - - // Getters + private Script currentScript = null; + /** - * You probably shouldn't use this method. - * - * @return The {@link HandlerList} containing all active log handlers. + * Internal method for updating the current script. Allows null parameter. + * @param currentScript The new Script to mark as the current Script. + * Please note that this method will do nothing if the current Script is the same as the new Script. */ - public HandlerList getHandlers() { - return handlers; + private void setCurrentScript(@Nullable Script currentScript) { + if (currentScript == this.currentScript) // Do nothing as it's the same script + return; + + Script previous = this.currentScript; + this.currentScript = currentScript; + getDataInstances().forEach( + data -> data.onCurrentScriptChange(currentScript != null ? currentScript.getConfig() : null) + ); + + // "Script" events + if (previous != null) + previous.getEventHandlers().forEach(eventHandler -> eventHandler.whenMadeInactive(currentScript)); + if (currentScript != null) + currentScript.getEventHandlers().forEach(eventHandler -> eventHandler.whenMadeActive(previous)); } - + + /** + * @return The Script currently being handled by this ParserInstance. + * @throws SkriptAPIException If this ParserInstance is not {@link #isActive()}. + */ + public Script getCurrentScript() { + if (currentScript == null) + throw new SkriptAPIException("This ParserInstance is not currently parsing/loading something!"); + return currentScript; + } + + // Structure API + @Nullable - public Node getNode() { - return node; + private Structure currentStructure = null; + + /** + * Updates the Structure currently being handled by this ParserInstance. + * @param structure The new Structure to be handled. + */ + public void setCurrentStructure(@Nullable Structure structure) { + currentStructure = structure; } - + + /** + * @return The Structure currently being handled by this ParserInstance. + */ @Nullable - public Config getCurrentScript() { - return currentScript; + public Structure getCurrentStructure() { + return currentStructure; } - - public HashMap<String, String> getCurrentOptions() { - return currentOptions; + + /** + * @return Whether {@link #getCurrentStructure()} is an instance of the given Structure class. + */ + public boolean isCurrentStructure(Class<? extends Structure> structureClass) { + return structureClass.isInstance(currentStructure); } - + + /** + * @return Whether {@link #getCurrentStructure()} is an instance of one of the given Structure classes. + */ + @SafeVarargs + public final boolean isCurrentStructure(Class<? extends Structure>... structureClasses) { + for (Class<? extends Structure> structureClass : structureClasses) { + if (isCurrentStructure(structureClass)) + return true; + } + return false; + } + + // Event API + + @Nullable + private String currentEventName; + private Class<? extends Event>[] currentEvents = CollectionUtils.array(ContextlessEvent.class); + + public void setCurrentEventName(@Nullable String currentEventName) { + this.currentEventName = currentEventName; + } + @Nullable public String getCurrentEventName() { return currentEventName; } - - @Nullable + + /** + * @param currentEvents The events that may be present during execution. + * An instance of the events present in the provided array MUST be used to execute any loaded items. + * If items are to be loaded without context, use {@link ContextlessEvent}. + */ + public void setCurrentEvents(Class<? extends Event>[] currentEvents) { + Validate.isTrue(currentEvents.length != 0, "'currentEvents' may not be empty!"); + this.currentEvents = currentEvents; + getDataInstances().forEach(data -> data.onCurrentEventsChange(currentEvents)); + } + + @SafeVarargs + public final void setCurrentEvent(String name, Class<? extends Event>... events) { + Validate.isTrue(events.length != 0, "'events' may not be empty!"); + currentEventName = name; + setCurrentEvents(events); + setHasDelayBefore(Kleenean.FALSE); + } + + public void deleteCurrentEvent() { + currentEventName = null; + setCurrentEvents(CollectionUtils.array(ContextlessEvent.class)); + setHasDelayBefore(Kleenean.FALSE); + } + public Class<? extends Event>[] getCurrentEvents() { return currentEvents; } - - @Nullable - public SkriptEvent getCurrentSkriptEvent() { - return currentSkriptEvent; + + /** + * This method checks whether <i>at least one</i> of the current event classes + * is covered by the argument event class (i.e. equal to the class or a subclass of it). + * <br> + * Using this method in an event-specific syntax element requires a runtime check, for example <br> + * {@code if (!(e instanceof BlockBreakEvent)) return null;} + * <br> + * This check is required because there can be more than 1 event class at parse-time, but this method + * only checks if one of them matches the argument class. + * + * <br><br> + * See also {@link #isCurrentEvent(Class[])} for checking with multiple argument classes + */ + public boolean isCurrentEvent(@Nullable Class<? extends Event> event) { + return CollectionUtils.containsSuperclass(currentEvents, event); } - - public List<TriggerSection> getCurrentSections() { - return currentSections; + + /** + * Same as {@link #isCurrentEvent(Class)}, but allows for plural argument input. + * <br> + * This means that this method will return whether any of the current event classes is covered + * by any of the argument classes. + * <br> + * Using this method in an event-specific syntax element {@link #isCurrentEvent(Class) requires a runtime check}, + * you can use {@link CollectionUtils#isAnyInstanceOf(Object, Class[])} for this, for example: <br> + * {@code if (!CollectionUtils.isAnyInstanceOf(e, BlockBreakEvent.class, BlockPlaceEvent.class)) return null;} + * + * @see #isCurrentEvent(Class) + */ + @SafeVarargs + public final boolean isCurrentEvent(Class<? extends Event>... events) { + return CollectionUtils.containsAnySuperclass(currentEvents, events); } + // Section API + + private List<TriggerSection> currentSections = new ArrayList<>(); + /** - * @return whether {@link #getCurrentSections()} contains - * an section instance of the given class (or subclass). + * Updates the list of sections currently being handled by this ParserInstance. + * @param currentSections A new list of sections to handle. */ - public boolean isCurrentSection(Class<? extends TriggerSection> sectionClass) { - for (TriggerSection triggerSection : currentSections) { - if (sectionClass.isInstance(triggerSection)) - return true; - } - return false; + public void setCurrentSections(List<TriggerSection> currentSections) { + this.currentSections = currentSections; } - @SafeVarargs - public final boolean isCurrentSection(Class<? extends TriggerSection>... sectionClasses) { - for (Class<? extends TriggerSection> sectionClass : sectionClasses) { - if (isCurrentSection(sectionClass)) - return true; - } - return false; + /** + * @return A list of all sections this ParserInstance is currently within. + */ + public List<TriggerSection> getCurrentSections() { + return currentSections; } /** - * @return the outermost section which is an instance of the given class. + * @return The outermost section which is an instance of the given class. * Returns {@code null} if {@link #isCurrentSection(Class)} returns {@code false}. * @see #getCurrentSections() */ - @SuppressWarnings("unchecked") @Nullable + @SuppressWarnings("unchecked") public <T extends TriggerSection> T getCurrentSection(Class<T> sectionClass) { for (int i = currentSections.size(); i-- > 0;) { TriggerSection triggerSection = currentSections.get(i); @@ -158,8 +286,8 @@ public <T extends TriggerSection> T getCurrentSection(Class<T> sectionClass) { * Modifications to the returned list are not saved. * @see #getCurrentSections() */ - @SuppressWarnings("unchecked") @NotNull + @SuppressWarnings("unchecked") public <T extends TriggerSection> List<T> getCurrentSections(Class<T> sectionClass) { List<T> list = new ArrayList<>(); for (TriggerSection triggerSection : currentSections) { @@ -168,53 +296,36 @@ public <T extends TriggerSection> List<T> getCurrentSections(Class<T> sectionCla } return list; } - + /** - * @return whether this trigger has had delays before. - * Any syntax elements that modify event-values, should use this - * (or the {@link Kleenean} provided to in - * {@link ch.njol.skript.lang.SyntaxElement#init(Expression[], int, Kleenean, SkriptParser.ParseResult)}) - * to make sure the event can't be modified when it has passed. + * @return Whether {@link #getCurrentSections()} contains + * a section instance of the given class (or subclass). */ - public Kleenean getHasDelayBefore() { - return hasDelayBefore; - } - - public String getIndentation() { - return indentation; - } - - // Setters - public void setNode(@Nullable Node node) { - this.node = node == null || node.getParent() == null ? null : node; - } - - public void setCurrentScript(@Nullable Config currentScript) { - this.currentScript = currentScript; - getDataInstances().forEach(data -> data.onCurrentScriptChange(currentScript)); - } - - public void setCurrentEventName(@Nullable String currentEventName) { - this.currentEventName = currentEventName; - } - - public void setCurrentEvents(@Nullable Class<? extends Event>[] currentEvents) { - this.currentEvents = currentEvents; - getDataInstances().forEach(data -> data.onCurrentEventsChange(currentEvents)); - } - - public void setCurrentSkriptEvent(@Nullable SkriptEvent currentSkriptEvent) { - this.currentSkriptEvent = currentSkriptEvent; - } - - public void deleteCurrentSkriptEvent() { - this.currentSkriptEvent = null; + public boolean isCurrentSection(Class<? extends TriggerSection> sectionClass) { + for (TriggerSection triggerSection : currentSections) { + if (sectionClass.isInstance(triggerSection)) + return true; + } + return false; } - - public void setCurrentSections(List<TriggerSection> currentSections) { - this.currentSections = currentSections; + + /** + * @return Whether {@link #getCurrentSections()} contains + * a section instance of one of the given classes (or subclasses). + */ + @SafeVarargs + public final boolean isCurrentSection(Class<? extends TriggerSection>... sectionClasses) { + for (Class<? extends TriggerSection> sectionClass : sectionClasses) { + if (isCurrentSection(sectionClass)) + return true; + } + return false; } - + + // Delay API + + private Kleenean hasDelayBefore = Kleenean.FALSE; + /** * This method should be called to indicate that * the trigger will (possibly) be delayed from this point on. @@ -224,66 +335,66 @@ public void setCurrentSections(List<TriggerSection> currentSections) { public void setHasDelayBefore(Kleenean hasDelayBefore) { this.hasDelayBefore = hasDelayBefore; } - - public void setIndentation(String indentation) { - this.indentation = indentation; - } - - // Other - @SafeVarargs - public final void setCurrentEvent(String name, @Nullable Class<? extends Event>... events) { - currentEventName = name; - setCurrentEvents(events); - hasDelayBefore = Kleenean.FALSE; - } - - public void deleteCurrentEvent() { - currentEventName = null; - setCurrentEvents(null); - hasDelayBefore = Kleenean.FALSE; + + /** + * @return whether this trigger has had delays before. + * Any syntax elements that modify event-values, should use this + * (or the {@link Kleenean} provided to in + * {@link ch.njol.skript.lang.SyntaxElement#init(Expression[], int, Kleenean, SkriptParser.ParseResult)}) + * to make sure the event can't be modified when it has passed. + */ + public Kleenean getHasDelayBefore() { + return hasDelayBefore; } + // Miscellaneous + + private final HandlerList handlers = new HandlerList(); + /** - * This method checks whether <i>at least one</i> of the current event classes - * is covered by the argument event class (i.e. equal to the class or a subclass of it). - * <br> - * Using this method in an event-specific syntax element requires a runtime check, for example <br> - * {@code if (!(e instanceof BlockBreakEvent)) return null;} - * <br> - * This check is required because there can be more than 1 event class at parse-time, but this method - * only checks if one of them matches the argument class. + * You probably shouldn't use this method. * - * <br><br> - * See also {@link #isCurrentEvent(Class[])} for checking with multiple argument classes + * @return The {@link HandlerList} containing all active log handlers. */ - public boolean isCurrentEvent(@Nullable Class<? extends Event> event) { - return CollectionUtils.containsSuperclass(currentEvents, event); + public HandlerList getHandlers() { + return handlers; } + @Nullable + private Node node; + /** - * Same as {@link #isCurrentEvent(Class)}, but allows for plural argument input. - * <br> - * This means that this method will return whether any of the current event classes is covered - * by any of the argument classes. - * <br> - * Using this method in an event-specific syntax element {@link #isCurrentEvent(Class) requires a runtime check}, - * you can use {@link CollectionUtils#isAnyInstanceOf(Object, Class[])} for this, for example: <br> - * {@code if (!CollectionUtils.isAnyInstanceOf(e, BlockBreakEvent.class, BlockPlaceEvent.class)) return null;} - * - * @see #isCurrentEvent(Class) + * @param node The node to mark as being handled. This is mainly used for logging. + * Null means to mark it as no node currently being handled (that the ParserInstance is aware of). */ - @SafeVarargs - public final boolean isCurrentEvent(Class<? extends Event>... events) { - return CollectionUtils.containsAnySuperclass(currentEvents, events); + public void setNode(@Nullable Node node) { + this.node = (node == null || node.getParent() == null) ? null : node; } - /* - * Addon data + /** + * @return The node currently marked as being handled. This is mainly used for logging. + * Null indicates no node is currently being handled (that the ParserInstance is aware of). */ + @Nullable + public Node getNode() { + return node; + } + + private String indentation = ""; + + public void setIndentation(String indentation) { + this.indentation = indentation; + } + + public String getIndentation() { + return indentation; + } + + // ParserInstance Data API + /** * An abstract class for addons that want to add data bound to a ParserInstance. - * Extending classes may listen to the events {@link #onCurrentScriptChange(Config)} - * and {@link #onCurrentEventsChange(Class[])}. + * Extending classes may listen to the events like {@link #onCurrentEventsChange(Class[])}. * It is recommended you make a constructor with a {@link ParserInstance} parameter that * sends that parser instance upwards in a super call, so you can use * {@code ParserInstance.registerData(MyData.class, MyData::new)} @@ -299,10 +410,14 @@ public Data(ParserInstance parserInstance) { protected final ParserInstance getParser() { return parserInstance; } - + + /** + * @deprecated See {@link org.skriptlang.skript.lang.script.ScriptEventHandler}. + */ + @Deprecated public void onCurrentScriptChange(@Nullable Config currentScript) { } - - public void onCurrentEventsChange(@Nullable Class<? extends Event>[] currentEvents) { } + + public void onCurrentEventsChange(Class<? extends Event>[] currentEvents) { } } @@ -353,5 +468,61 @@ private List<? extends Data> getDataInstances() { } return dataList; } + + // Deprecated API + + /** + * @deprecated Use {@link ch.njol.skript.structures.StructOptions#getOptions(Script)} instead. + */ + @Deprecated + public HashMap<String, String> getCurrentOptions() { + if (!isActive()) + return new HashMap<>(0); + HashMap<String, String> options = StructOptions.getOptions(getCurrentScript()); + if (options == null) + return new HashMap<>(0); + return options; + } + + /** + * @deprecated Use {@link #getCurrentStructure()} + */ + @Nullable + @Deprecated + public SkriptEvent getCurrentSkriptEvent() { + Structure structure = getCurrentStructure(); + if (structure instanceof SkriptEvent) + return (SkriptEvent) structure; + return null; + } + + /** + * @deprecated Use {@link #setCurrentStructure(Structure)}. + */ + @Deprecated + public void setCurrentSkriptEvent(@Nullable SkriptEvent currentSkriptEvent) { + setCurrentStructure(currentSkriptEvent); + } + + /** + * @deprecated Use {@link #setCurrentStructure(Structure)} with 'null'. + */ + @Deprecated + public void deleteCurrentSkriptEvent() { + setCurrentStructure(null); + } + + /** + * @deprecated Addons should no longer be modifying this. + */ + @Deprecated + public void setCurrentScript(@Nullable Config currentScript) { + if (currentScript == null) + return; + //noinspection ConstantConditions - shouldn't be null + Script script = ScriptLoader.getScript(currentScript.getFile()); + if (script != null) + setActive(script); + } } diff --git a/src/main/java/ch/njol/skript/lang/util/ContextlessEvent.java b/src/main/java/ch/njol/skript/lang/util/ContextlessEvent.java new file mode 100644 index 00000000000..3e9208a2694 --- /dev/null +++ b/src/main/java/ch/njol/skript/lang/util/ContextlessEvent.java @@ -0,0 +1,55 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.lang.util; + +import ch.njol.skript.lang.parser.ParserInstance; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * This class is intended for usage in places of Skript that require an Event. + * Of course, not everything is always context/event dependent. + * For example, if one were to load a SectionNode or parse something into a {@link ch.njol.skript.lang.SyntaxElement}, + * and {@link ParserInstance#getCurrentEvents()} was null or empty, the resulting elements + * would not be dependent upon a specific Event. Thus, there would be no reason for an Event to be required. + * So, this classes exists to avoid dangerously passing null in these places. + * @see #get() + */ +public final class ContextlessEvent extends Event { + + private ContextlessEvent() { } + + /** + * @return A new ContextlessEvent instance to be used for context-less {@link ch.njol.skript.lang.SyntaxElement}s. + */ + public static ContextlessEvent get() { + return new ContextlessEvent(); + } + + /** + * This method should never be called. + */ + @Override + @NotNull + public HandlerList getHandlers() { + throw new IllegalStateException(); + } + +} diff --git a/src/main/java/ch/njol/skript/sections/SecConditional.java b/src/main/java/ch/njol/skript/sections/SecConditional.java index c33bb6d5d98..b841dbe7dd9 100644 --- a/src/main/java/ch/njol/skript/sections/SecConditional.java +++ b/src/main/java/ch/njol/skript/sections/SecConditional.java @@ -24,13 +24,13 @@ import ch.njol.skript.lang.Condition; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.Section; -import ch.njol.skript.lang.SkriptEvent; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.TriggerItem; import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.util.Kleenean; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.structure.Structure; import java.util.ArrayList; import java.util.List; @@ -70,14 +70,14 @@ public boolean init(Expression<?>[] exprs, ParserInstance parser = getParser(); Class<? extends Event>[] currentEvents = parser.getCurrentEvents(); String currentEventName = parser.getCurrentEventName(); - SkriptEvent currentSkriptEvent = parser.getCurrentSkriptEvent(); + Structure currentStructure = parser.getCurrentStructure(); // Change event if using 'parse if' if (parseIf) { //noinspection unchecked parser.setCurrentEvents(new Class[]{SkriptParseEvent.class}); parser.setCurrentEventName("parse"); - parser.setCurrentSkriptEvent(null); + parser.setCurrentStructure(null); } // Don't print a default error if 'if' keyword wasn't provided condition = Condition.parse(expr, parseResult.mark != 0 ? "Can't understand this condition: '" + expr + "'" : null); @@ -85,7 +85,7 @@ public boolean init(Expression<?>[] exprs, if (parseIf) { parser.setCurrentEvents(currentEvents); parser.setCurrentEventName(currentEventName); - parser.setCurrentSkriptEvent(currentSkriptEvent); + parser.setCurrentStructure(currentStructure); } if (condition == null) diff --git a/src/main/java/ch/njol/skript/structures/StructAliases.java b/src/main/java/ch/njol/skript/structures/StructAliases.java new file mode 100644 index 00000000000..7086e044601 --- /dev/null +++ b/src/main/java/ch/njol/skript/structures/StructAliases.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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.structures; + +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.Aliases; +import ch.njol.skript.config.SectionNode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.lang.entry.EntryContainer; +import org.skriptlang.skript.lang.structure.Structure; + +@Name("Aliases") +@Description("Used for registering custom aliases for a script.") +@Examples({ + "aliases:", + "\tblacklisted items = TNT, bedrock, obsidian, mob spawner, lava, lava bucket", + "\tshiny swords = gold sword, iron sword, diamond sword" +}) +@Since("1.0") +public class StructAliases extends Structure { + + public static final Priority PRIORITY = new Priority(200); + + static { + Skript.registerStructure(StructAliases.class, "aliases"); + } + + @Override + public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult, EntryContainer entryContainer) { + SectionNode node = entryContainer.getSource(); + node.convertToEntries(0, "="); + + // Initialize and load script aliases + Aliases.createScriptAliases(getParser().getCurrentScript()).parser.load(node); + + return true; + } + + @Override + public boolean load() { + return true; + } + + @Override + public void unload() { + // Unload aliases when this Script is unloaded + Aliases.clearScriptAliases(getParser().getCurrentScript()); + } + + @Override + public Priority getPriority() { + return PRIORITY; + } + + @Override + public String toString(@Nullable Event e, boolean debug) { + return "aliases"; + } + +} diff --git a/src/main/java/ch/njol/skript/structures/StructCommand.java b/src/main/java/ch/njol/skript/structures/StructCommand.java new file mode 100644 index 00000000000..9a7c5a8f47d --- /dev/null +++ b/src/main/java/ch/njol/skript/structures/StructCommand.java @@ -0,0 +1,350 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.structures; + +import ch.njol.skript.ScriptLoader; +import ch.njol.skript.Skript; +import ch.njol.skript.bukkitutil.CommandReloader; +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.classes.Parser; +import ch.njol.skript.command.Argument; +import ch.njol.skript.command.CommandEvent; +import ch.njol.skript.command.Commands; +import ch.njol.skript.command.ScriptCommand; +import ch.njol.skript.config.SectionNode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.ParseContext; +import org.skriptlang.skript.lang.script.Script; +import ch.njol.skript.lang.SkriptParser; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.VariableString; +import org.skriptlang.skript.lang.entry.EntryContainer; +import org.skriptlang.skript.lang.entry.KeyValueEntryData; +import org.skriptlang.skript.lang.structure.Structure; +import org.skriptlang.skript.lang.entry.EntryValidator; +import org.skriptlang.skript.lang.entry.util.LiteralEntryData; +import org.skriptlang.skript.lang.entry.util.VariableStringEntryData; +import ch.njol.skript.registrations.Classes; +import ch.njol.skript.util.StringMode; +import ch.njol.skript.util.Timespan; +import ch.njol.skript.util.Utils; +import ch.njol.util.NonNullPair; +import ch.njol.util.StringUtils; +import org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Name("Command") +@Description("Used for registering custom commands.") +@Examples({ + "command /broadcast <string>:", + "\tusage: A command for broadcasting a message to all players.", + "\tpermission: skript.command.broadcast", + "\tpermission message: You don't have permission to broadcast messages", + "\taliases: /bc", + "\texecutable by: players and console", + "\tcooldown: 15 seconds", + "\tcooldown message: You last broadcast a message %elapsed time% ago. You can broadcast another message in %remaining time%.", + "\tcooldown bypass: skript.command.broadcast.admin", + "\tcooldown storage: {cooldown::%player%}", + "\ttrigger:", + "\t\tbroadcast the argument" +}) +@Since("1.0") +public class StructCommand extends Structure { + + public static final Priority PRIORITY = new Priority(500); + + private static final Pattern + COMMAND_PATTERN = Pattern.compile("(?i)^command /?(\\S+)\\s*(\\s+(.+))?$"), + ARGUMENT_PATTERN = Pattern.compile("<\\s*(?:([^>]+?)\\s*:\\s*)?(.+?)\\s*(?:=\\s*(" + SkriptParser.wildcard + "))?\\s*>"), + DESCRIPTION_PATTERN = Pattern.compile("(?<!\\\\)%-?(.+?)%"); + + private static final AtomicBoolean syncCommands = new AtomicBoolean(); + + static { + Skript.registerStructure( + StructCommand.class, + EntryValidator.builder() + .addEntry("usage", null, true) + .addEntry("description", "", true) + .addEntry("permission", "", true) + .addEntryData(new VariableStringEntryData("permission message", null, true, CommandEvent.class)) + .addEntryData(new KeyValueEntryData<List<String>>("aliases", new ArrayList<>(), true) { + private final Pattern pattern = Pattern.compile("\\s*,\\s*/?"); + + @Override + protected List<String> getValue(String value) { + List<String> aliases = new ArrayList<>(Arrays.asList(pattern.split(value))); + if (aliases.get(0).startsWith("/")) { + aliases.set(0, aliases.get(0).substring(1)); + } else if (aliases.get(0).isEmpty()) { + aliases = new ArrayList<>(0); + } + return aliases; + } + }) + .addEntryData(new KeyValueEntryData<Integer>("executable by", ScriptCommand.CONSOLE | ScriptCommand.PLAYERS, true) { + private final Pattern pattern = Pattern.compile("\\s*,\\s*|\\s+(and|or)\\s+"); + + @Override + @Nullable + protected Integer getValue(String value) { + int executableBy = 0; + for (String b : pattern.split(value)) { + if (b.equalsIgnoreCase("console") || b.equalsIgnoreCase("the console")) { + executableBy |= ScriptCommand.CONSOLE; + } else if (b.equalsIgnoreCase("players") || b.equalsIgnoreCase("player")) { + executableBy |= ScriptCommand.PLAYERS; + } else { + return null; + } + } + return executableBy; + } + }) + .addEntryData(new LiteralEntryData<>("cooldown", null, true, Timespan.class)) + .addEntryData(new VariableStringEntryData("cooldown message", null, true, CommandEvent.class)) + .addEntry("cooldown bypass", null,true) + .addEntryData(new VariableStringEntryData("cooldown storage", null, true, StringMode.VARIABLE_NAME, CommandEvent.class)) + .addSection("trigger", false) + .build(), + "command <.+>" + ); + } + + @Nullable + private ScriptCommand scriptCommand; + + @Override + public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult, EntryContainer entryContainer) { + return true; + } + + @Override + @SuppressWarnings("unchecked") + public boolean load() { + getParser().setCurrentEvent("command", CommandEvent.class); + + EntryContainer entryContainer = getEntryContainer(); + + String fullCommand = entryContainer.getSource().getKey(); + assert fullCommand != null; + fullCommand = ScriptLoader.replaceOptions(fullCommand); + + int level = 0; + for (int i = 0; i < fullCommand.length(); i++) { + if (fullCommand.charAt(i) == '[') { + level++; + } else if (fullCommand.charAt(i) == ']') { + if (level == 0) { + Skript.error("Invalid placement of [optional brackets]"); + getParser().deleteCurrentEvent(); + return false; + } + level--; + } + } + if (level > 0) { + Skript.error("Invalid amount of [optional brackets]"); + getParser().deleteCurrentEvent(); + return false; + } + + Matcher matcher = COMMAND_PATTERN.matcher(fullCommand); + boolean matches = matcher.matches(); + assert matches; + + String command = matcher.group(1).toLowerCase(); + ScriptCommand existingCommand = Commands.getScriptCommand(command); + if (existingCommand != null && existingCommand.getLabel().equals(command)) { + Script script = existingCommand.getScript(); + Skript.error("A command with the name /" + existingCommand.getName() + " is already defined" + + (script != null ? (" in " + script.getConfig().getFileName()) : "") + ); + getParser().deleteCurrentEvent(); + return false; + } + + String arguments = matcher.group(3) == null ? "" : matcher.group(3); + StringBuilder pattern = new StringBuilder(); + + List<Argument<?>> currentArguments = Commands.currentArguments = new ArrayList<>(); //Mirre + matcher = ARGUMENT_PATTERN.matcher(arguments); + int lastEnd = 0; + int optionals = 0; + for (int i = 0; matcher.find(); i++) { + pattern.append(Commands.escape(arguments.substring(lastEnd, matcher.start()))); + optionals += StringUtils.count(arguments, '[', lastEnd, matcher.start()); + optionals -= StringUtils.count(arguments, ']', lastEnd, matcher.start()); + + lastEnd = matcher.end(); + + ClassInfo<?> c; + c = Classes.getClassInfoFromUserInput(matcher.group(2)); + NonNullPair<String, Boolean> p = Utils.getEnglishPlural(matcher.group(2)); + if (c == null) + c = Classes.getClassInfoFromUserInput(p.getFirst()); + if (c == null) { + Skript.error("Unknown type '" + matcher.group(2) + "'"); + getParser().deleteCurrentEvent(); + return false; + } + Parser<?> parser = c.getParser(); + if (parser == null || !parser.canParse(ParseContext.COMMAND)) { + Skript.error("Can't use " + c + " as argument of a command"); + getParser().deleteCurrentEvent(); + return false; + } + + Argument<?> arg = Argument.newInstance(matcher.group(1), c, matcher.group(3), i, !p.getSecond(), optionals > 0); + if (arg == null) { + getParser().deleteCurrentEvent(); + return false; + } + currentArguments.add(arg); + + if (arg.isOptional() && optionals == 0) { + pattern.append('['); + optionals++; + } + pattern.append("%").append(arg.isOptional() ? "-" : "").append(Utils.toEnglishPlural(c.getCodeName(), p.getSecond())).append("%"); + } + + pattern.append(Commands.escape("" + arguments.substring(lastEnd))); + optionals += StringUtils.count(arguments, '[', lastEnd); + optionals -= StringUtils.count(arguments, ']', lastEnd); + for (int i = 0; i < optionals; i++) + pattern.append(']'); + + String desc = "/" + command + " "; + desc += StringUtils.replaceAll(pattern, DESCRIPTION_PATTERN, m1 -> { + assert m1 != null; + NonNullPair<String, Boolean> p = Utils.getEnglishPlural("" + m1.group(1)); + String s1 = p.getFirst(); + return "<" + Classes.getClassInfo(s1).getName().toString(p.getSecond()) + ">"; + }); + desc = Commands.unescape(desc).trim(); + + String usage = entryContainer.getOptional("usage", String.class, false); + if (usage == null) { + usage = Commands.m_correct_usage + " " + desc; + } + + String description = entryContainer.get("description", String.class, true); + + String permission = entryContainer.get("permission", String.class, true); + VariableString permissionMessage = entryContainer.getOptional("permission message", VariableString.class, false); + if (permissionMessage != null && permission.isEmpty()) + Skript.warning("command /" + command + " has a permission message set, but not a permission"); + + List<String> aliases = (List<String>) entryContainer.get("aliases", true); + int executableBy = (Integer) entryContainer.get("executable by", true); + + Timespan cooldown = entryContainer.getOptional("cooldown", Timespan.class, false); + VariableString cooldownMessage = entryContainer.getOptional("cooldown message", VariableString.class, false); + if (cooldownMessage != null && cooldown == null) + Skript.warning("command /" + command + " has a cooldown message set, but not a cooldown"); + String cooldownBypass = entryContainer.getOptional("cooldown bypass", String.class, false); + if (cooldownBypass == null) { + cooldownBypass = ""; + } else if (cooldownBypass.isEmpty() && cooldown == null) { + Skript.warning("command /" + command + " has a cooldown bypass set, but not a cooldown"); + } + VariableString cooldownStorage = entryContainer.getOptional("cooldown storage", VariableString.class, false); + if (cooldownStorage != null && cooldown == null) + Skript.warning("command /" + command + " has a cooldown storage set, but not a cooldown"); + + SectionNode node = entryContainer.getSource(); + + if (Skript.debug() || node.debug()) + Skript.debug("command " + desc + ":"); + + Commands.currentArguments = currentArguments; + try { + scriptCommand = new ScriptCommand(getParser().getCurrentScript(), command, pattern.toString(), currentArguments, description, usage, + aliases, permission, permissionMessage, cooldown, cooldownMessage, cooldownBypass, cooldownStorage, + executableBy, entryContainer.get("trigger", SectionNode.class, false)); + } finally { + Commands.currentArguments = null; + } + + if (Skript.logVeryHigh() && !Skript.debug()) + Skript.info("Registered command " + desc); + + getParser().deleteCurrentEvent(); + + Commands.registerCommand(scriptCommand); + syncCommands.set(true); + + return true; + } + + @Override + public boolean postLoad() { + attemptCommandSync(); + return true; + } + + @Override + public void unload() { + if (scriptCommand != null) { + Commands.unregisterCommand(scriptCommand); + syncCommands.set(true); + } + } + + @Override + public void postUnload() { + attemptCommandSync(); + } + + private void attemptCommandSync() { + if (syncCommands.get()) { + syncCommands.set(false); + 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; + } + + @Override + public String toString(@Nullable Event e, boolean debug) { + return "command"; + } + +} diff --git a/src/main/java/ch/njol/skript/structures/StructFunction.java b/src/main/java/ch/njol/skript/structures/StructFunction.java new file mode 100644 index 00000000000..cbc1dd34822 --- /dev/null +++ b/src/main/java/ch/njol/skript/structures/StructFunction.java @@ -0,0 +1,115 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.structures; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.function.FunctionEvent; +import ch.njol.skript.lang.function.Functions; +import ch.njol.skript.lang.function.Signature; +import ch.njol.skript.lang.parser.ParserInstance; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.lang.entry.EntryContainer; +import org.skriptlang.skript.lang.structure.Structure; + +import java.util.concurrent.atomic.AtomicBoolean; + +@Name("Function") +@Description({ + "Functions are structures that can be executed with arguments/parameters to run code.", + "They can also return a value to the trigger that is executing the function." +}) +@Examples({ + "function sayMessage(message: text):", + "\tbroadcast {_message} # our message argument is available in '{_message}'", + "function giveApple(amount: number) :: item:", + "\treturn {_amount} of apple" +}) +@Since("2.2") +public class StructFunction extends Structure { + + public static final Priority PRIORITY = new Priority(400); + + private static final AtomicBoolean validateFunctions = new AtomicBoolean(); + + static { + Skript.registerStructure(StructFunction.class, "function <.+>"); + } + + @Nullable + private Signature<?> signature; + + @Override + public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult, EntryContainer entryContainer) { + return true; + } + + @Override + public boolean preLoad() { + signature = Functions.loadSignature(getParser().getCurrentScript().getConfig().getFileName(), getEntryContainer().getSource()); + return true; + } + + @Override + public boolean load() { + ParserInstance parser = getParser(); + parser.setCurrentEvent("function", FunctionEvent.class); + + Functions.loadFunction(parser.getCurrentScript(), getEntryContainer().getSource()); + + parser.deleteCurrentEvent(); + + validateFunctions.set(true); + + return true; + } + + @Override + public boolean postLoad() { + if (validateFunctions.get()) { + validateFunctions.set(false); + Functions.validateFunctions(); + } + return true; + } + + @Override + public void unload() { + if (signature != null) + Functions.unregisterFunction(signature); + validateFunctions.set(true); + } + + @Override + public Priority getPriority() { + return PRIORITY; + } + + @Override + public String toString(@Nullable Event e, boolean debug) { + return "function"; + } + +} diff --git a/src/main/java/ch/njol/skript/structures/StructOptions.java b/src/main/java/ch/njol/skript/structures/StructOptions.java new file mode 100644 index 00000000000..ef5efa21063 --- /dev/null +++ b/src/main/java/ch/njol/skript/structures/StructOptions.java @@ -0,0 +1,157 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.structures; + +import ch.njol.skript.Skript; +import ch.njol.skript.config.EntryNode; +import ch.njol.skript.config.Node; +import ch.njol.skript.config.SectionNode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import org.skriptlang.skript.lang.script.Script; +import org.skriptlang.skript.lang.script.ScriptData; +import org.skriptlang.skript.lang.entry.EntryContainer; +import org.skriptlang.skript.lang.structure.Structure; +import ch.njol.util.StringUtils; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; + +@Name("Options") +@Description({ + "Options are used for replacing parts of a script with something else.", + "For example, an option may represent a message that appears in multiple locations.", + "Take a look at the example below that showcases this." +}) +@Examples({ + "options:", + "\tno_permission: You're missing the required permission to execute this command!", + "command /ping:", + "\tpermission: command.ping", + "\tpermission message: {@no_permission}", + "\ttrigger:", + "\t\tmessage \"Pong!\"", + "command /pong:", + "\tpermission: command.pong", + "\tpermission message: {@no_permission}", + "\ttrigger:", + "\t\tmessage \"Ping!\"" +}) +@Since("1.0") +public class StructOptions extends Structure { + + public static final Priority PRIORITY = new Priority(100); + + static { + Skript.registerStructure(StructOptions.class, "options"); + } + + @Override + public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult, EntryContainer entryContainer) { + SectionNode node = entryContainer.getSource(); + node.convertToEntries(-1); + + OptionsData optionsData = new OptionsData(); + loadOptions(node, "", optionsData.options); + getParser().getCurrentScript().addData(optionsData); + + return true; + } + + private void loadOptions(SectionNode sectionNode, String prefix, Map<String, String> options) { + for (Node n : sectionNode) { + if (n instanceof EntryNode) { + options.put(prefix + n.getKey(), ((EntryNode) n).getValue()); + } else if (n instanceof SectionNode) { + loadOptions((SectionNode) n, prefix + n.getKey() + ".", options); + } else { + Skript.error("Invalid line in options"); + } + } + } + + @Override + public boolean load() { + return true; + } + + @Override + public void unload() { + getParser().getCurrentScript().removeData(OptionsData.class); + } + + @Override + public Priority getPriority() { + return PRIORITY; + } + + @Override + public String toString(@Nullable Event e, boolean debug) { + return "options"; + } + + /** + * A method to obtain all options registered within a Script. + * @param script The Script to obtain options from. + * @return The options of this Script, or null if there are none. + */ + @Nullable + public static HashMap<String, String> getOptions(Script script) { + OptionsData optionsData = script.getData(OptionsData.class); + return optionsData != null ? optionsData.options : null; + } + + /** + * Replaces all options in the provided String using the options of the provided Script. + * @param script The Script to obtain options from. + * @param string The String to replace options in. + * @return A String with all options replaced, or the original String if the provided Script has no options. + */ + public static String replaceOptions(Script script, String string) { + Map<String, String> options = getOptions(script); + if (options == null) + return string; + + String replaced = StringUtils.replaceAll(string, "\\{@(.+?)\\}", m -> { + String option = options.get(m.group(1)); + if (option == null) { + Skript.error("undefined option " + m.group()); + return m.group(); + } + return Matcher.quoteReplacement(option); + }); + + assert replaced != null; + return replaced; + } + + private static final class OptionsData implements ScriptData { + + public final HashMap<String, String> options = new HashMap<>(15); + + } + +} diff --git a/src/main/java/ch/njol/skript/structures/StructVariables.java b/src/main/java/ch/njol/skript/structures/StructVariables.java new file mode 100644 index 00000000000..c5154d8d0b9 --- /dev/null +++ b/src/main/java/ch/njol/skript/structures/StructVariables.java @@ -0,0 +1,167 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.structures; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.config.EntryNode; +import ch.njol.skript.config.Node; +import ch.njol.skript.config.SectionNode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.ParseContext; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import org.skriptlang.skript.lang.entry.EntryContainer; +import org.skriptlang.skript.lang.structure.Structure; +import ch.njol.skript.log.ParseLogHandler; +import ch.njol.skript.log.SkriptLogger; +import ch.njol.skript.registrations.Classes; +import ch.njol.skript.registrations.Converters; +import ch.njol.skript.variables.Variables; +import ch.njol.util.NonNullPair; +import ch.njol.util.StringUtils; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +@Name("Variables") +@Description({ + "Used for defining variables present within a script.", + "This section is not required, but it ensures that a variable has a value if it doesn't exist when the script is loaded." +}) +@Examples({ + "variables:", + "\t{joins} = 0", + "on join:", + "\tadd 1 to {joins}" +}) +@Since("1.0") +public class StructVariables extends Structure { + + public static final Priority PRIORITY = new Priority(300); + + static { + Skript.registerStructure(StructVariables.class, "variables"); + } + + private final List<NonNullPair<String, Object>> variables = new ArrayList<>(); + + @Override + public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult, EntryContainer entryContainer) { + // TODO allow to make these override existing variables + SectionNode node = entryContainer.getSource(); + node.convertToEntries(0, "="); + for (Node n : node) { + if (!(n instanceof EntryNode)) { + Skript.error("Invalid line in variables section"); + continue; + } + + String name = n.getKey().toLowerCase(Locale.ENGLISH); + if (name.startsWith("{") && name.endsWith("}")) + name = name.substring(1, name.length() - 1); + + String var = name; + + name = StringUtils.replaceAll(name, "%(.+)?%", m -> { + if (m.group(1).contains("{") || m.group(1).contains("}") || m.group(1).contains("%")) { + Skript.error("'" + var + "' is not a valid name for a default variable"); + return null; + } + ClassInfo<?> ci = Classes.getClassInfoFromUserInput("" + m.group(1)); + if (ci == null) { + Skript.error("Can't understand the type '" + m.group(1) + "'"); + return null; + } + return "<" + ci.getCodeName() + ">"; + }); + + if (name == null) { + continue; + } else if (name.contains("%")) { + Skript.error("Invalid use of percent signs in variable name"); + continue; + } + + Object o; + ParseLogHandler log = SkriptLogger.startParseLogHandler(); + try { + o = Classes.parseSimple(((EntryNode) n).getValue(), Object.class, ParseContext.SCRIPT); + if (o == null) { + log.printError("Can't understand the value '" + ((EntryNode) n).getValue() + "'"); + continue; + } + log.printLog(); + } finally { + log.stop(); + } + + ClassInfo<?> ci = Classes.getSuperClassInfo(o.getClass()); + if (ci.getSerializer() == null) { + Skript.error("Can't save '" + ((EntryNode) n).getValue() + "' in a variable"); + continue; + } else if (ci.getSerializeAs() != null) { + ClassInfo<?> as = Classes.getExactClassInfo(ci.getSerializeAs()); + if (as == null) { + assert false : ci; + continue; + } + o = Converters.convert(o, as.getC()); + if (o == null) { + Skript.error("Can't save '" + ((EntryNode) n).getValue() + "' in a variable"); + continue; + } + } + + variables.add(new NonNullPair<>(name, o)); + } + return true; + } + + @Override + public boolean load() { + for (NonNullPair<String, Object> pair : variables) { + String name = pair.getKey(); + Object o = pair.getValue(); + + if (Variables.getVariable(name, null, false) != null) + continue; + + Variables.setVariable(name, o, null, false); + } + return true; + } + + @Override + public Priority getPriority() { + return PRIORITY; + } + + @Override + public String toString(@Nullable Event e, boolean debug) { + return "variables"; + } + +} diff --git a/src/main/java/ch/njol/skript/structures/package-info.java b/src/main/java/ch/njol/skript/structures/package-info.java new file mode 100644 index 00000000000..22271bc6b9f --- /dev/null +++ b/src/main/java/ch/njol/skript/structures/package-info.java @@ -0,0 +1,27 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +/** + * Support for script-based testing. + */ +@NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) +package ch.njol.skript.structures; + +import org.eclipse.jdt.annotation.DefaultLocation; +import org.eclipse.jdt.annotation.NonNullByDefault; + diff --git a/src/main/java/ch/njol/skript/util/ScriptOptions.java b/src/main/java/ch/njol/skript/util/ScriptOptions.java deleted file mode 100644 index b45f34b9508..00000000000 --- a/src/main/java/ch/njol/skript/util/ScriptOptions.java +++ /dev/null @@ -1,54 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.util; - -import java.io.File; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Set; - -import org.eclipse.jdt.annotation.Nullable; - -/** - * @author Mirreducki - * - */ -public final class ScriptOptions { - - private final HashMap<File, Set<String>> localWarningSuppression = new HashMap<>(); - - @Nullable - private static ScriptOptions instance; - - private ScriptOptions() { } - - @SuppressWarnings("null") - public static ScriptOptions getInstance() { - return instance == null ? (instance = new ScriptOptions()) : instance; - } - - public boolean suppressesWarning(@Nullable File scriptFile, String warning) { - Set<String> suppressed = localWarningSuppression.get(scriptFile); - return suppressed != null && suppressed.contains(warning); - } - - public void setSuppressWarning(@Nullable File scriptFile, String warning) { - localWarningSuppression.computeIfAbsent(scriptFile, k -> new HashSet<>()).add(warning); - } -} diff --git a/src/main/java/ch/njol/util/coll/iterator/ConsumingIterator.java b/src/main/java/ch/njol/util/coll/iterator/ConsumingIterator.java new file mode 100644 index 00000000000..5ad15280ade --- /dev/null +++ b/src/main/java/ch/njol/util/coll/iterator/ConsumingIterator.java @@ -0,0 +1,54 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.util.coll.iterator; + +import java.util.Iterator; +import java.util.function.Consumer; + +/** + * An {@link Iterator} that also calls {@link Consumer#accept(Object)} on each object provided by the given {@link Iterator}. + */ +public class ConsumingIterator<E> implements Iterator<E> { + + private final Iterator<E> iterator; + private final Consumer<E> consumer; + + public ConsumingIterator(Iterator<E> iterator, Consumer<E> consumer) { + this.iterator = iterator; + this.consumer = consumer; + } + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public E next() { + E value = iterator.next(); + consumer.accept(value); + return value; + } + + @Override + public void remove() { + iterator.remove(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/entry/EntryContainer.java b/src/main/java/org/skriptlang/skript/lang/entry/EntryContainer.java new file mode 100644 index 00000000000..77856618d05 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/entry/EntryContainer.java @@ -0,0 +1,173 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.entry; + +import ch.njol.skript.config.Node; +import ch.njol.skript.config.SectionNode; +import ch.njol.skript.lang.parser.ParserInstance; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * An EntryContainer is a data container for obtaining the values of the entries of a {@link SectionNode}. + */ +public class EntryContainer { + + private final SectionNode source; + @Nullable + private final EntryValidator entryValidator; + @Nullable + private final Map<String, Node> handledNodes; + private final List<Node> unhandledNodes; + + EntryContainer( + SectionNode source, @Nullable EntryValidator entryValidator, @Nullable Map<String, Node> handledNodes, List<Node> unhandledNodes + ) { + this.source = source; + this.entryValidator = entryValidator; + this.handledNodes = handledNodes; + this.unhandledNodes = unhandledNodes; + } + + /** + * Used for creating an EntryContainer when no {@link EntryValidator} exists. + * @param source The SectionNode to create a container for. + * @return An EntryContainer where <i>all</i> nodes will be {@link EntryContainer#getUnhandledNodes()}. + */ + public static EntryContainer withoutValidator(SectionNode source) { + List<Node> unhandledNodes = new ArrayList<>(); + for (Node node : source) // All nodes are unhandled + unhandledNodes.add(node); + return new EntryContainer(source, null, null, unhandledNodes); + } + + /** + * @return The SectionNode containing the entries associated with this EntryValidator. + */ + public SectionNode getSource() { + return source; + } + + /** + * @return Any nodes unhandled by the EntryValidator. + * {@link EntryValidator#allowsUnknownEntries()} or {@link EntryValidator#allowsUnknownSections()} must be true + * for this list to contain any values. The 'unhandled node' would represent any entry provided by the user that the validator + * is not explicitly aware of. + */ + public List<Node> getUnhandledNodes() { + return unhandledNodes; + } + + /** + * A method for obtaining a non-null, typed entry value. + * This method should ONLY be called if there is no way the entry could return null. + * In general, this means that the entry has a default value (and 'useDefaultValue' is true). This is because even + * though an entry may be required, parsing errors may occur that mean no value can be returned. + * It can also mean that the entry data is simple enough such that it will never return a null value. + * @param key The key associated with the entry. + * @param expectedType The class representing the expected type of the entry's value. + * @param useDefaultValue Whether the default value should be used if parsing failed. + * @return The entry's value. + * @throws RuntimeException If the entry's value is null, or if it is not of the expected type. + */ + public <T> T get(String key, Class<T> expectedType, boolean useDefaultValue) { + T parsed = getOptional(key, expectedType, useDefaultValue); + if (parsed == null) + throw new RuntimeException("Null value for asserted non-null value"); + return parsed; + } + + /** + * A method for obtaining a non-null entry value with an unknown type. + * This method should ONLY be called if there is no way the entry could return null. + * In general, this means that the entry has a default value (and 'useDefaultValue' is true). This is because even + * though an entry may be required, parsing errors may occur that mean no value can be returned. + * It can also mean that the entry data is simple enough such that it will never return a null value. + * @param key The key associated with the entry. + * @param useDefaultValue Whether the default value should be used if parsing failed. + * @return The entry's value. + * @throws RuntimeException If the entry's value is null. + */ + public Object get(String key, boolean useDefaultValue) { + Object parsed = getOptional(key, useDefaultValue); + if (parsed == null) + throw new RuntimeException("Null value for asserted non-null value"); + return parsed; + } + + /** + * A method for obtaining a nullable, typed entry value. + * @param key The key associated with the entry. + * @param expectedType The class representing the expected type of the entry's value. + * @param useDefaultValue Whether the default value should be used if parsing failed. + * @return The entry's value. May be null if the entry is missing or a parsing error occurred. + * @throws RuntimeException If the entry's value is not of the expected type. + */ + @Nullable + @SuppressWarnings("unchecked") + public <T> T getOptional(String key, Class<T> expectedType, boolean useDefaultValue) { + Object parsed = getOptional(key, useDefaultValue); + if (parsed == null) + return null; + if (!expectedType.isInstance(parsed)) + throw new RuntimeException("Expected entry with key '" + key + "' to be '" + expectedType + "', but got '" + parsed.getClass() + "'"); + return (T) parsed; + } + + /** + * A method for obtaining a nullable entry value with an unknown type. + * @param key The key associated with the entry. + * @param useDefaultValue Whether the default value should be used if parsing failed. + * @return The entry's value. May be null if the entry is missing or a parsing error occurred. + */ + @Nullable + public Object getOptional(String key, boolean useDefaultValue) { + if (entryValidator == null || handledNodes == null) + return null; + + EntryData<?> entryData = null; + for (EntryData<?> data : entryValidator.getEntryData()) { + if (data.getKey().equals(key)) { + entryData = data; + break; + } + } + if (entryData == null) + return null; + + Node node = handledNodes.get(key); + if (node == null) + return entryData.getDefaultValue(); + + // Update ParserInstance node for parsing + ParserInstance parser = ParserInstance.get(); + Node oldNode = parser.getNode(); + parser.setNode(node); + Object value = entryData.getValue(node); + if (value == null && useDefaultValue) + value = entryData.getDefaultValue(); + parser.setNode(oldNode); + + return value; + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/entry/EntryData.java b/src/main/java/org/skriptlang/skript/lang/entry/EntryData.java new file mode 100644 index 00000000000..1ce59972987 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/entry/EntryData.java @@ -0,0 +1,98 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.entry; + +import ch.njol.skript.config.Node; +import ch.njol.skript.config.SectionNode; +import org.eclipse.jdt.annotation.Nullable; + +/** + * EntryData is used for defining the different entries of for a {@link SectionNode}. + * {@link org.skriptlang.skript.lang.structure.Structure}'s are a primary user of this system. + * Take a look at this example: + * <pre> + * command /example: # this is the SectionNode + * description: this is an example of an entry + * trigger: # this is also an example of an entry + * # code goes here (not entry data!) + * </pre> + * From the above, it can be seen that EntryData is found at the level immediately after a {@link SectionNode}. + * It can also be seen that entries come in many forms. + * In fact, all entries are based upon a {@link Node}. + * This could be something like a {@link ch.njol.skript.config.SimpleNode} or {@link SectionNode}, + * but it may also be something totally different. + * Every entry data class must define a validator-type method for {@link Node}s, along with + * a method of obtaining a value from that {@link Node}. + * Every entry data instance must contain some sort of key. This key is the main identifier + * of an entry data instance within a {@link EntryValidator}. + * @param <T> The type of the value returned by this entry data. + */ +public abstract class EntryData<T> { + + private final String key; + @Nullable + private final T defaultValue; + private final boolean optional; + + public EntryData(String key, @Nullable T defaultValue, boolean optional) { + this.key = key; + this.defaultValue = defaultValue; + this.optional = optional; + } + + /** + * @return The key that identifies and defines this entry data. + */ + public String getKey() { + return key; + } + + /** + * @return The default value of this entry node to be used if {@link #getValue(Node)} is null, + * or if the user does not include an entry for this entry data within their {@link SectionNode}. + */ + @Nullable + public T getDefaultValue() { + return defaultValue; + } + + /** + * @return Whether this entry data <b>must</b> be included within a {@link SectionNode}. + */ + public boolean isOptional() { + return optional; + } + + /** + * Obtains a value from the provided node using the methods of this entry data. + * @param node The node to obtain a value from. + * @return The value obtained from the provided node. + */ + @Nullable + public abstract T getValue(Node node); + + /** + * A method to be implemented by all entry data classes that determines whether + * the provided node may be used with the entry data type to obtain a value. + * @param node The node to check. + * @return Whether the provided node may be used with this entry data to obtain a value. + */ + public abstract boolean canCreateWith(Node node); + +} diff --git a/src/main/java/org/skriptlang/skript/lang/entry/EntryValidator.java b/src/main/java/org/skriptlang/skript/lang/entry/EntryValidator.java new file mode 100644 index 00000000000..cfb5a99704c --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/entry/EntryValidator.java @@ -0,0 +1,235 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.entry; + +import ch.njol.skript.Skript; +import ch.njol.skript.config.Node; +import ch.njol.skript.config.SectionNode; +import ch.njol.skript.config.SimpleNode; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A validator for storing {@link EntryData}. + * It can be used to validate whether a {@link SectionNode} contains the required entries. + * This validation process will return an {@link EntryContainer} for accessing entry values. + * @see EntryValidatorBuilder + */ +public class EntryValidator { + + /** + * @return A builder to be used for creating an {@link EntryValidator}. + */ + public static EntryValidatorBuilder builder() { + return new EntryValidatorBuilder(); + } + + private final List<EntryData<?>> entryData; + private final boolean allowUnknownEntries, allowUnknownSections; + + private EntryValidator(List<EntryData<?>> entryData, boolean allowUnknownEntries, boolean allowUnknownSections) { + this.entryData = entryData; + this.allowUnknownEntries = allowUnknownEntries; + this.allowUnknownSections = allowUnknownSections; + } + + /** + * @return An unmodifiable list containing all {@link EntryData} of this validator. + */ + public List<EntryData<?>> getEntryData() { + return Collections.unmodifiableList(entryData); + } + + /** + * @return Whether this validator allows {@link SimpleNode}-based entries not declared in the entry data map. + */ + public boolean allowsUnknownEntries() { + return allowUnknownEntries; + } + + /** + * @return Whether this validator allows {@link SectionNode}-based entries not declared in the entry data map. + */ + public boolean allowsUnknownSections() { + return allowUnknownSections; + } + + /** + * Validates a node using this entry validator. + * @param sectionNode The node to validate. + * @return A pair containing a map of handled nodes and a list of unhandled nodes (if this validator permits unhandled nodes) + * The returned map uses the matched entry data's key as a key and uses a pair containing the entry data and matching node + * Will return null if the provided node couldn't be validated. + */ + @Nullable + public EntryContainer validate(SectionNode sectionNode) { + List<EntryData<?>> entries = new ArrayList<>(entryData); + Map<String, Node> handledNodes = new HashMap<>(); + List<Node> unhandledNodes = new ArrayList<>(); + + boolean ok = true; + nodeLoop: for (Node node : sectionNode) { + if (node.getKey() == null) + continue; + + // The first step is to determine if the node is present in the entry data list + + for (EntryData<?> data : entryData) { + if (data.canCreateWith(node)) { // Determine if it's a match + handledNodes.put(data.getKey(), node); // This is a known node, mark it as such + entries.remove(data); + continue nodeLoop; + } + } + + // We found no matching entry data + + if ( + (!allowUnknownEntries && node instanceof SimpleNode) + || (!allowUnknownSections && node instanceof SectionNode) + ) { + ok = false; // Instead of terminating here, we should try and print all errors possible + Skript.error("Unexpected entry '" + node.getKey() + "'. Check whether it's spelled correctly or remove it"); + } else { // This validator allows this type of node to be unhandled + unhandledNodes.add(node); + } + } + + // Now we're going to check for missing entries that are *required* + + for (EntryData<?> entryData : entries) { + if (!entryData.isOptional()) { + Skript.error("Required entry '" + entryData.getKey() + "' is missing"); + ok = false; + } + } + + if (!ok) // We printed an error at some point + return null; + + return new EntryContainer(sectionNode, this, handledNodes, unhandledNodes); + } + + /** + * A utility builder for creating an entry validator that can be used to parse and validate a {@link SectionNode}. + * @see EntryValidator#builder() + */ + public static class EntryValidatorBuilder { + + /** + * The default separator used for all {@link KeyValueEntryData}. + */ + public static final String DEFAULT_ENTRY_SEPARATOR = ": "; + + private EntryValidatorBuilder() { } + + private final List<EntryData<?>> entryData = new ArrayList<>(); + private String entrySeparator = DEFAULT_ENTRY_SEPARATOR; + private boolean allowUnknownEntries, allowUnknownSections; + + /** + * Updates the separator to be used when creating KeyValue entries. Please note + * that this will not update the separator for already registered KeyValue entries. + * @param separator The new separator for KeyValue entries. + * @return The builder instance. + */ + public EntryValidatorBuilder entrySeparator(String separator) { + this.entrySeparator = separator; + return this; + } + + /** + * Sets that the validator should accept {@link SimpleNode}-based entries not declared in the entry data map. + * @return The builder instance. + */ + public EntryValidatorBuilder allowUnknownEntries() { + allowUnknownEntries = true; + return this; + } + + /** + * Sets that the validator should accept {@link SectionNode}-based entries not declared in the entry data map. + * @return The builder instance. + */ + public EntryValidatorBuilder allowUnknownSections() { + allowUnknownSections = true; + return this; + } + + /** + * Adds a new {@link KeyValueEntryData} to this validator that returns the raw, unhandled String value. + * The added entry is optional and will use the provided default value as a backup. + * @param key The key of the entry. + * @param defaultValue The default value of this entry to use if the user does not include this entry. + * @return The builder instance. + */ + public EntryValidatorBuilder addEntry(String key, @Nullable String defaultValue, boolean optional) { + entryData.add(new KeyValueEntryData<String>(key, defaultValue, optional) { + @Override + protected String getValue(String value) { + return value; + } + + @Override + public String getSeparator() { + return entrySeparator; + } + }); + return this; + } + + /** + * Adds a new, potentially optional {@link SectionEntryData} to this validator. + * @param key The key of the section entry. + * @param optional Whether this section entry should be optional. + * @return The builder instance. + */ + public EntryValidatorBuilder addSection(String key, boolean optional) { + entryData.add(new SectionEntryData(key, null, optional)); + return this; + } + + /** + * A method to add custom {@link EntryData} to a validator. + * Custom entry data should be preferred when the default methods included in this builder are not expansive enough. + * Please note that for custom {@link KeyValueEntryData} implementations, the default entry separator + * value of this builder will not be used. Instead, {@link #DEFAULT_ENTRY_SEPARATOR} will be used. + * @param entryData The custom entry data to include in this validator. + * @return The builder instance. + */ + public EntryValidatorBuilder addEntryData(EntryData<?> entryData) { + this.entryData.add(entryData); + return this; + } + + /** + * @return The final, built entry validator. + */ + public EntryValidator build() { + return new EntryValidator(entryData, allowUnknownEntries, allowUnknownSections); + } + + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/entry/KeyValueEntryData.java b/src/main/java/org/skriptlang/skript/lang/entry/KeyValueEntryData.java new file mode 100644 index 00000000000..964b3ae76b3 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/entry/KeyValueEntryData.java @@ -0,0 +1,89 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.entry; + +import ch.njol.skript.ScriptLoader; +import ch.njol.skript.config.Node; +import ch.njol.skript.config.SimpleNode; +import org.skriptlang.skript.lang.entry.EntryValidator.EntryValidatorBuilder; +import org.eclipse.jdt.annotation.Nullable; + +/** + * An entry based on {@link SimpleNode}s containing a key and a value. + * Unlike a traditional {@link ch.njol.skript.config.EntryNode}, this entry data + * may have a value that is <i>not</i> a String. + * @param <T> The type of the value. + */ +public abstract class KeyValueEntryData<T> extends EntryData<T> { + + public KeyValueEntryData(String key, @Nullable T defaultValue, boolean optional) { + super(key, defaultValue, optional); + } + + /** + * Used to obtain and parse the value of a {@link SimpleNode}. This method accepts + * any type of node, but assumes the input to be a {@link SimpleNode}. Before calling this method, + * the caller should first check {@link #canCreateWith(Node)} to make sure that the node is viable. + * @param node A {@link SimpleNode} to obtain (and possibly convert) the value of. + * @return The value obtained from the provided {@link SimpleNode}. + */ + @Override + @Nullable + public final T getValue(Node node) { + assert node instanceof SimpleNode; + String key = node.getKey(); + if (key == null) + throw new IllegalArgumentException("EntryData#getValue() called with invalid node."); + return getValue(ScriptLoader.replaceOptions(key).substring(getKey().length() + getSeparator().length())); + } + + /** + * Parses a String value using this entry data. + * @param value The String value to parse. + * @return The parsed value. + */ + @Nullable + protected abstract T getValue(String value); + + /** + * @return The String acting as a separator between the key and the value. + */ + public String getSeparator() { + return EntryValidatorBuilder.DEFAULT_ENTRY_SEPARATOR; + } + + /** + * Checks whether the provided node can have its value obtained using this entry data. + * A check is done to verify that the node is a {@link SimpleNode}, and that it starts + * with the necessary key. + * @param node The node to check. + * @return Whether the provided {@link Node} works with this entry data. + */ + @Override + public boolean canCreateWith(Node node) { + if (!(node instanceof SimpleNode)) + return false; + String key = node.getKey(); + if (key == null) + return false; + key = ScriptLoader.replaceOptions(key); + return key.startsWith(getKey() + getSeparator()); + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/entry/SectionEntryData.java b/src/main/java/org/skriptlang/skript/lang/entry/SectionEntryData.java new file mode 100644 index 00000000000..2348315fd9e --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/entry/SectionEntryData.java @@ -0,0 +1,67 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.entry; + +import ch.njol.skript.ScriptLoader; +import ch.njol.skript.config.Node; +import ch.njol.skript.config.SectionNode; +import ch.njol.skript.config.SimpleNode; +import org.eclipse.jdt.annotation.Nullable; + +/** + * A simple entry data class for handling {@link SectionNode}s. + */ +public class SectionEntryData extends EntryData<SectionNode> { + + public SectionEntryData(String key, @Nullable SectionNode defaultValue, boolean optional) { + super(key, defaultValue, optional); + } + + /** + * Because this entry data is for {@link SectionNode}s, no specific handling needs to be done to obtain the "value". + * This method just asserts that the provided node is actually a {@link SectionNode}. + * @param node A {@link SimpleNode} to obtain (and possibly convert) the value of. + * @return The value obtained from the provided {@link SimpleNode}. + */ + @Override + @Nullable + public SectionNode getValue(Node node) { + assert node instanceof SectionNode; + return (SectionNode) node; + } + + /** + * Checks whether the provided node can be used as the section for this entry data. + * A check is done to verify that the node is a {@link SectionNode}, and that it also + * meets the requirements of {@link EntryData#canCreateWith(Node)}. + * @param node The node to check. + * @return Whether the provided {@link Node} works with this entry data. + */ + @Override + public boolean canCreateWith(Node node) { + if (!(node instanceof SectionNode)) + return false; + String key = node.getKey(); + if (key == null) + return false; + key = ScriptLoader.replaceOptions(key); + return getKey().equalsIgnoreCase(key); + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/entry/package-info.java b/src/main/java/org/skriptlang/skript/lang/entry/package-info.java new file mode 100644 index 00000000000..58e73d603da --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/entry/package-info.java @@ -0,0 +1,23 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +@NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) +package org.skriptlang.skript.lang.entry; + +import org.eclipse.jdt.annotation.DefaultLocation; +import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/src/main/java/org/skriptlang/skript/lang/entry/util/ExpressionEntryData.java b/src/main/java/org/skriptlang/skript/lang/entry/util/ExpressionEntryData.java new file mode 100644 index 00000000000..7dbd560731b --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/entry/util/ExpressionEntryData.java @@ -0,0 +1,95 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.entry.util; + +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ParseContext; +import ch.njol.skript.lang.SkriptParser; +import ch.njol.skript.lang.parser.ParserInstance; +import org.skriptlang.skript.lang.entry.KeyValueEntryData; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +/** + * A type of {@link KeyValueEntryData} designed to parse its value as an {@link Expression}. + * This data <b>CAN</b> return null if expression parsing fails. + */ +public class ExpressionEntryData<T> extends KeyValueEntryData<Expression<? extends T>> { + + private final Class<T> returnType; + + private final int flags; + + private final Class<? extends Event>[] events; + + /** + * @param returnType The expected return type of the matched expression. + * @param events Events to be present during parsing and Trigger execution. + * This allows the usage of event-restricted syntax and event-values. + * @see ParserInstance#setCurrentEvents(Class[]) + */ + @SafeVarargs + public ExpressionEntryData( + String key, @Nullable Expression<T> defaultValue, boolean optional, + Class<T> returnType, Class<? extends Event>... events + ) { + this(key, defaultValue, optional, returnType, SkriptParser.ALL_FLAGS, events); + } + + /** + * @param returnType The expected return type of the matched expression. + * @param flags Parsing flags. See {@link SkriptParser#SkriptParser(String, int, ParseContext)} + * javadoc for more details. + * @param events Events to be present during parsing and Trigger execution. + * This allows the usage of event-restricted syntax and event-values. + * @see ParserInstance#setCurrentEvents(Class[]) + */ + @SafeVarargs + public ExpressionEntryData( + String key, @Nullable Expression<T> defaultValue, boolean optional, + Class<T> returnType, int flags, Class<? extends Event>... events + ) { + super(key, defaultValue, optional); + this.returnType = returnType; + this.flags = flags; + this.events = events; + } + + @Override + @Nullable + @SuppressWarnings("unchecked") + protected Expression<? extends T> getValue(String value) { + ParserInstance parser = ParserInstance.get(); + + Class<? extends Event>[] oldEvents = parser.getCurrentEvents(); + Kleenean oldHasDelayBefore = parser.getHasDelayBefore(); + + parser.setCurrentEvents(events); + parser.setHasDelayBefore(Kleenean.FALSE); + + Expression<? extends T> expression = new SkriptParser(value, flags, ParseContext.DEFAULT).parseExpression(returnType); + + parser.setCurrentEvents(oldEvents); + parser.setHasDelayBefore(oldHasDelayBefore); + + return expression; + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/entry/util/LiteralEntryData.java b/src/main/java/org/skriptlang/skript/lang/entry/util/LiteralEntryData.java new file mode 100644 index 00000000000..9b48a19e341 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/entry/util/LiteralEntryData.java @@ -0,0 +1,52 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.entry.util; + +import ch.njol.skript.lang.ParseContext; +import org.skriptlang.skript.lang.entry.KeyValueEntryData; +import ch.njol.skript.registrations.Classes; +import org.eclipse.jdt.annotation.Nullable; + +/** + * A specific {@link KeyValueEntryData} type designed to parse the + * entry's value as a supported literal type. + * This entry makes use of {@link Classes#parse(String, Class, ParseContext)} + * to parse the user's input using registered {@link ch.njol.skript.classes.ClassInfo}s + * and {@link ch.njol.skript.classes.Converter}s. + * This data <b>CAN</b> return null if the user's input is unable to be parsed as the expected type. + */ +public class LiteralEntryData<T> extends KeyValueEntryData<T> { + + private final Class<T> type; + + /** + * @param type The type to parse the value into. + */ + public LiteralEntryData(String key, @Nullable T defaultValue, boolean optional, Class<T> type) { + super(key, defaultValue, optional); + this.type = type; + } + + @Override + @Nullable + public T getValue(String value) { + return Classes.parse(value, type, ParseContext.DEFAULT); + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/entry/util/TriggerEntryData.java b/src/main/java/org/skriptlang/skript/lang/entry/util/TriggerEntryData.java new file mode 100644 index 00000000000..b0a856e3756 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/entry/util/TriggerEntryData.java @@ -0,0 +1,91 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.entry.util; + +import ch.njol.skript.ScriptLoader; +import ch.njol.skript.config.Node; +import ch.njol.skript.config.SectionNode; +import ch.njol.skript.lang.Trigger; +import ch.njol.skript.lang.parser.ParserInstance; +import org.skriptlang.skript.lang.entry.EntryData; +import org.skriptlang.skript.lang.entry.SectionEntryData; +import ch.njol.skript.lang.util.SimpleEvent; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; + +/** + * An entry data class designed to take a {@link SectionNode} and parse it into a Trigger. + * Events specified during construction *should* be used when the Trigger is executed. + * This data will <b>NEVER</b> return null. + * @see SectionEntryData + */ +public class TriggerEntryData extends EntryData<Trigger> { + + private final Class<? extends Event>[] events; + + /** + * @param events Events to be present during parsing and Trigger execution. + * This allows the usage of event-restricted syntax and event-values. + * @see ParserInstance#setCurrentEvents(Class[]) + */ + @SafeVarargs + public TriggerEntryData( + String key, @Nullable Trigger defaultValue, boolean optional, + Class<? extends Event>... events + ) { + super(key, defaultValue, optional); + this.events = events; + } + + @Nullable + @Override + public Trigger getValue(Node node) { + assert node instanceof SectionNode; + + ParserInstance parser = ParserInstance.get(); + + Class<? extends Event>[] oldEvents = parser.getCurrentEvents(); + Kleenean oldHasDelayBefore = parser.getHasDelayBefore(); + + parser.setCurrentEvents(events); + parser.setHasDelayBefore(Kleenean.FALSE); + + Trigger trigger = new Trigger( + parser.getCurrentScript(), "entry with key: " + getKey(), new SimpleEvent(), ScriptLoader.loadItems((SectionNode) node) + ); + + parser.setCurrentEvents(oldEvents); + parser.setHasDelayBefore(oldHasDelayBefore); + + return trigger; + } + + @Override + public boolean canCreateWith(Node node) { + if (!(node instanceof SectionNode)) + return false; + String key = node.getKey(); + if (key == null) + return false; + key = ScriptLoader.replaceOptions(key); + return getKey().equalsIgnoreCase(key); + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/entry/util/VariableStringEntryData.java b/src/main/java/org/skriptlang/skript/lang/entry/util/VariableStringEntryData.java new file mode 100644 index 00000000000..57264fd0393 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/entry/util/VariableStringEntryData.java @@ -0,0 +1,93 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.entry.util; + +import ch.njol.skript.lang.VariableString; +import ch.njol.skript.lang.parser.ParserInstance; +import org.skriptlang.skript.lang.entry.KeyValueEntryData; +import ch.njol.skript.util.StringMode; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +/** + * A type of {@link KeyValueEntryData} designed to parse its value as a {@link VariableString}. + * The {@link StringMode} may be specified during construction. + * Constructors without a StringMode parameter assume {@link StringMode#MESSAGE}. + * This data <b>CAN</b> return null if string parsing fails (e.g. the user formatted their string wrong). + */ +public class VariableStringEntryData extends KeyValueEntryData<VariableString> { + + private final StringMode stringMode; + + private final Class<? extends Event>[] events; + + /** + * @param events Events to be present during parsing and Trigger execution. + * This allows the usage of event-restricted syntax and event-values. + * @see ParserInstance#setCurrentEvents(Class[]) + */ + @SafeVarargs + public VariableStringEntryData( + String key, @Nullable VariableString defaultValue, boolean optional, + Class<? extends Event>... events + ) { + this(key, defaultValue, optional, StringMode.MESSAGE, events); + } + + /** + * @param stringMode Sets <i>how</i> to parse the string (e.g. as a variable, message, etc.). + * @param events Events to be present during parsing and Trigger execution. + * This allows the usage of event-restricted syntax and event-values. + * @see ParserInstance#setCurrentEvents(Class[]) + */ + @SafeVarargs + public VariableStringEntryData( + String key, @Nullable VariableString defaultValue, boolean optional, + StringMode stringMode, Class<? extends Event>... events + ) { + super(key, defaultValue, optional); + this.stringMode = stringMode; + this.events = events; + } + + @Override + @Nullable + protected VariableString getValue(String value) { + ParserInstance parser = ParserInstance.get(); + + Class<? extends Event>[] oldEvents = parser.getCurrentEvents(); + Kleenean oldHasDelayBefore = parser.getHasDelayBefore(); + + parser.setCurrentEvents(events); + parser.setHasDelayBefore(Kleenean.FALSE); + + // Double up quotations outside of expressions + if (stringMode != StringMode.VARIABLE_NAME) + value = VariableString.quote(value); + + VariableString variableString = VariableString.newInstance(value, stringMode); + + parser.setCurrentEvents(oldEvents); + parser.setHasDelayBefore(oldHasDelayBefore); + + return variableString; + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/entry/util/package-info.java b/src/main/java/org/skriptlang/skript/lang/entry/util/package-info.java new file mode 100644 index 00000000000..f3d29110438 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/entry/util/package-info.java @@ -0,0 +1,23 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +@NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) +package org.skriptlang.skript.lang.entry.util; + +import org.eclipse.jdt.annotation.DefaultLocation; +import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/src/main/java/org/skriptlang/skript/lang/script/Script.java b/src/main/java/org/skriptlang/skript/lang/script/Script.java new file mode 100644 index 00000000000..01f9d20c632 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/script/Script.java @@ -0,0 +1,165 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.script; + +import ch.njol.skript.config.Config; +import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.structure.Structure; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; + +/** + * Scripts are the primary container of all code. + * Every script is made up of one or more {@link Structure}s, which contain user-defined instructions and information. + * Every script also has its own internal information, such as + * custom data, suppressed warnings, and associated event handlers. + */ +public final class Script { + + private final Config config; + + private final List<Structure> structures = new ArrayList<>(); + + /** + * Creates a new Script to be used across the API. + * Only one script should be created per Config. A loaded script may be obtained through {@link ch.njol.skript.ScriptLoader}. + * @param config The Config containing the contents of this script. + */ + public Script(Config config) { + this.config = config; + } + + /** + * @return The Config representing the structure of this script. + */ + public Config getConfig() { + return config; + } + + /** + * @return A list of all Structures within this Script. + */ + public List<Structure> getStructures() { + return structures; + } + + // Warning Suppressions + + private final Set<ScriptWarning> suppressedWarnings = new HashSet<>(ScriptWarning.values().length); + + /** + * @param warning Suppresses the provided warning for this script. + */ + public void suppressWarning(ScriptWarning warning) { + suppressedWarnings.add(warning); + } + + /** + * @param warning Allows the provided warning for this script. + */ + public void allowWarning(ScriptWarning warning) { + suppressedWarnings.remove(warning); + } + + /** + * @param warning The warning to check. + * @return Whether this script suppresses the provided warning. + */ + public boolean suppressesWarning(ScriptWarning warning) { + return suppressedWarnings.contains(warning); + } + + // Script Data + + private final Map<Class<? extends ScriptData>, ScriptData> scriptData = new ConcurrentHashMap<>(5); + + /** + * Adds new ScriptData to this Script's data map. + * @param data The data to add. + */ + public void addData(ScriptData data) { + scriptData.put(data.getClass(), data); + } + + /** + * Removes the ScriptData matching the specified data type. + * @param dataType The type of the data to remove. + */ + public void removeData(Class<? extends ScriptData> dataType) { + scriptData.remove(dataType); + } + + /** + * A method to obtain ScriptData matching the specified data type. + * @param dataType The class representing the ScriptData to obtain. + * @return ScriptData found matching the provided class, or null if no data is present. + */ + @Nullable + @SuppressWarnings("unchecked") + public <Type extends ScriptData> Type getData(Class<Type> dataType) { + return (Type) scriptData.get(dataType); + } + + /** + * A method that always obtains ScriptData matching the specified data type. + * By using the mapping supplier, it will also add ScriptData of the provided type if it is not already present. + * @param dataType The class representing the ScriptData to obtain. + * @param mapper A supplier to create ScriptData of the provided type if such ScriptData is not already present. + * @return Existing ScriptData found matching the provided class, or new data provided by the mapping function. + */ + @SuppressWarnings("unchecked") + public <Value extends ScriptData> Value getData(Class<? extends Value> dataType, Supplier<Value> mapper) { + return (Value) scriptData.computeIfAbsent(dataType, clazz -> mapper.get()); + } + + // Script Events + + private final Set<ScriptEventHandler> eventHandlers = new HashSet<>(5); + + /** + * Adds the provided event handler to this Script. + * @param eventHandler The event handler to add. + */ + public void addEventHandler(ScriptEventHandler eventHandler) { + eventHandlers.add(eventHandler); + } + + /** + * Removes the provided event handler from this Script. + * @param eventHandler The event handler to remove. + */ + public void removeEventHandler(ScriptEventHandler eventHandler) { + eventHandlers.remove(eventHandler); + } + + /** + * @return An unmodifiable set of all event handlers. + */ + public Set<ScriptEventHandler> getEventHandlers() { + return Collections.unmodifiableSet(eventHandlers); + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/script/ScriptData.java b/src/main/java/org/skriptlang/skript/lang/script/ScriptData.java new file mode 100644 index 00000000000..58c12ac10cf --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/script/ScriptData.java @@ -0,0 +1,25 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.script; + +/** + * To be implemented on data objects for {@link Script}'s Data API. + * @see Script#addData(ScriptData) + */ +public interface ScriptData { } diff --git a/src/main/java/org/skriptlang/skript/lang/script/ScriptEventHandler.java b/src/main/java/org/skriptlang/skript/lang/script/ScriptEventHandler.java new file mode 100644 index 00000000000..5362077216e --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/script/ScriptEventHandler.java @@ -0,0 +1,46 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.script; + +import ch.njol.skript.lang.parser.ParserInstance; +import org.eclipse.jdt.annotation.Nullable; + +/** + * A ScriptEventHandler is used for listening to and performing actions for different Script events. + * @see Script#addEventHandler(ScriptEventHandler) + */ +public abstract class ScriptEventHandler { + + /** + * Called when this Script is made active in a {@link ParserInstance}. + * + * @param oldScript The Script that was just made inactive. + * Null if the {@link ParserInstance} handling this Script was not {@link ParserInstance#isActive()}. + */ + public void whenMadeActive(@Nullable Script oldScript) { } + + /** + * Called when this Script is made inactive in a {@link ParserInstance}. + * + * @param newScript The Script that will be made active after this one is completely inactive. + * Null if the {@link ParserInstance} handling this Script will be not {@link ParserInstance#isActive()}. + */ + public void whenMadeInactive(@Nullable Script newScript) { } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/script/ScriptWarning.java b/src/main/java/org/skriptlang/skript/lang/script/ScriptWarning.java new file mode 100644 index 00000000000..f56971b7940 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/script/ScriptWarning.java @@ -0,0 +1,32 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.script; + +/** + * An enum containing {@link Script} warnings that can be suppressed. + */ +public enum ScriptWarning { + + VARIABLE_SAVE, // Variable cannot be saved (the ClassInfo is not serializable) + + MISSING_CONJUNCTION, // Missing "and" or "or" + + VARIABLE_STARTS_WITH_EXPRESSION // Variable starts with an Expression + +} diff --git a/src/main/java/org/skriptlang/skript/lang/script/package-info.java b/src/main/java/org/skriptlang/skript/lang/script/package-info.java new file mode 100644 index 00000000000..d97829458ca --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/script/package-info.java @@ -0,0 +1,23 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +@NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) +package org.skriptlang.skript.lang.script; + +import org.eclipse.jdt.annotation.DefaultLocation; +import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/src/main/java/org/skriptlang/skript/lang/structure/Structure.java b/src/main/java/org/skriptlang/skript/lang/structure/Structure.java new file mode 100644 index 00000000000..bf7afe348c8 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/structure/Structure.java @@ -0,0 +1,220 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.structure; + +import ch.njol.skript.Skript; +import ch.njol.skript.config.Node; +import ch.njol.skript.config.SectionNode; +import ch.njol.skript.lang.Debuggable; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.ParseContext; +import ch.njol.skript.lang.SelfRegisteringSkriptEvent; +import ch.njol.skript.lang.SkriptParser; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.SyntaxElement; +import ch.njol.skript.lang.Trigger; +import ch.njol.skript.lang.parser.ParserInstance; +import ch.njol.skript.log.ParseLogHandler; +import ch.njol.skript.log.SkriptLogger; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.iterator.ConsumingIterator; +import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.NotNull; +import org.skriptlang.skript.lang.entry.EntryContainer; +import org.skriptlang.skript.lang.entry.EntryData; +import org.skriptlang.skript.lang.entry.EntryValidator; + +import java.util.Arrays; +import java.util.Iterator; + +/** + * Structures are the root elements in every script. They are essentially the "headers". + * Events and functions are both a type of Structure. However, each one has its own + * parsing requirements, order, and defined structure within. + * + * Structures may also contain "entries" that hold values or sections of code. + * The values of these entries can be obtained by parsing the Structure's sub{@link Node}s + * through registered {@link EntryData}. + */ +// TODO STRUCTURE add Structures to docs +public abstract class Structure implements SyntaxElement, Debuggable { + + /** + * The default {@link Priority} of every registered Structure. + */ + public static final Priority DEFAULT_PRIORITY = new Priority(1000); + + /** + * Priorities are used to determine the order in which Structures should be loaded. + * As the priority approaches 0, it becomes more important. Example: + * priority of 1 (loads first), priority of 2 (loads second), priority of 3 (loads third) + */ + public static class Priority implements Comparable<Priority> { + + private final int priority; + + public Priority(int priority) { + this.priority = priority; + } + + public int getPriority() { + return priority; + } + + @Override + public int compareTo(@NotNull Structure.Priority o) { + return Integer.compare(this.priority, o.priority); + } + + } + + @Nullable + private EntryContainer entryContainer = null; + + /** + * @return An EntryContainer containing this Structure's {@link EntryData} and {@link Node} parse results. + * Please note that this Structure <b>MUST</b> have been initialized for this to work. + */ + public final EntryContainer getEntryContainer() { + if (entryContainer == null) + throw new IllegalStateException("This Structure hasn't been initialized!"); + return entryContainer; + } + + @Override + public final boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + StructureData structureData = getParser().getData(StructureData.class); + + Literal<?>[] literals = Arrays.copyOf(exprs, exprs.length, Literal[].class); + + StructureInfo<? extends Structure> structureInfo = structureData.structureInfo; + assert structureInfo != null; + EntryValidator entryValidator = structureInfo.entryValidator; + + if (entryValidator == null) { // No validation necessary, the structure itself will handle it + entryContainer = EntryContainer.withoutValidator(structureData.sectionNode); + } else { // Okay, now it's time for validation + EntryContainer entryContainer = entryValidator.validate(structureData.sectionNode); + if (entryContainer == null) + return false; + this.entryContainer = entryContainer; + } + + return init(literals, matchedPattern, parseResult, entryContainer); + } + + public abstract boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult, EntryContainer entryContainer); + + /** + * The first phase of Structure loading. + * During this phase, all Structures across all loading scripts are loaded with respect to their priorities. + * @return Whether preloading was successful. An error should be printed prior to returning false to specify the cause. + */ + public boolean preLoad() { + return true; + } + + /** + * The second phase of Structure loading. + * During this phase, Structures are loaded script by script. + * The order they are loaded in for each script is based on the Structure's priority. + * @return Whether loading was successful. An error should be printed prior to returning false to specify the cause. + */ + public abstract boolean load(); + + /** + * The third and final phase of Structure loading. + * The loading order and method is the same as {@link #load()}. + * This method is primarily designed for Structures that wish to execute actions after + * most other Structures have finished loading. + * @return Whether postLoading was successful. An error should be printed prior to returning false to specify the cause. + */ + public boolean postLoad() { + return true; + } + + /** + * Called when this structure is unloaded, similar to {@link SelfRegisteringSkriptEvent#unregister(Trigger)}. + */ + public void unload() { } + + /** + * Called when this structure is unloaded, similar to {@link SelfRegisteringSkriptEvent#unregister(Trigger)}. + * This method is primarily designed for Structures that wish to execute actions after + * most other Structures have finished unloading. + */ + public void postUnload() { } + + /** + * The priority of a Structure determines the order in which it should be loaded. + * For more information, see the javadoc of {@link Priority}. + * @return The priority of this Structure. By default, this is {@link Structure#DEFAULT_PRIORITY}. + */ + public Priority getPriority() { + return DEFAULT_PRIORITY; + } + + @Override + public String toString() { + return toString(null, false); + } + + @Nullable + public static Structure parse(String expr, SectionNode sectionNode, @Nullable String defaultError) { + ParserInstance.get().getData(StructureData.class).sectionNode = sectionNode; + + Iterator<StructureInfo<? extends Structure>> iterator = + new ConsumingIterator<>(Skript.getStructures().iterator(), + elementInfo -> ParserInstance.get().getData(StructureData.class).structureInfo = elementInfo); + + try (ParseLogHandler parseLogHandler = SkriptLogger.startParseLogHandler()) { + Structure structure = SkriptParser.parseStatic(expr, iterator, ParseContext.EVENT, defaultError); + if (structure != null) { + parseLogHandler.printLog(); + return structure; + } + parseLogHandler.printError(); + return null; + } + } + + static { + ParserInstance.registerData(StructureData.class, StructureData::new); + } + + @SuppressWarnings("NotNullFieldNotInitialized") + public static class StructureData extends ParserInstance.Data { + + private SectionNode sectionNode; + @Nullable + private StructureInfo<? extends Structure> structureInfo; + + public StructureData(ParserInstance parserInstance) { + super(parserInstance); + } + + @Nullable + public StructureInfo<? extends Structure> getStructureInfo() { + return structureInfo; + } + + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/structure/StructureInfo.java b/src/main/java/org/skriptlang/skript/lang/structure/StructureInfo.java new file mode 100644 index 00000000000..af57831ebee --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/structure/StructureInfo.java @@ -0,0 +1,43 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.structure; + +import ch.njol.skript.lang.SyntaxElementInfo; +import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.entry.EntryValidator; + +/** + * Special {@link SyntaxElementInfo} for {@link Structure}s that may contain information such as the {@link EntryValidator}. + */ +public class StructureInfo<E extends Structure> extends SyntaxElementInfo<E> { + + @Nullable + public final EntryValidator entryValidator; + + public StructureInfo(String[] patterns, Class<E> c, String originClassPath) throws IllegalArgumentException { + super(patterns, c, originClassPath); + entryValidator = null; + } + + public StructureInfo(String[] patterns, Class<E> c, String originClassPath, EntryValidator entryValidator) throws IllegalArgumentException { + super(patterns, c, originClassPath); + this.entryValidator = entryValidator; + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/structure/package-info.java b/src/main/java/org/skriptlang/skript/lang/structure/package-info.java new file mode 100644 index 00000000000..fd8d0e2d4d0 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/structure/package-info.java @@ -0,0 +1,23 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +@NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) +package org.skriptlang.skript.lang.structure; + +import org.eclipse.jdt.annotation.DefaultLocation; +import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/src/main/resources/lang/english.lang b/src/main/resources/lang/english.lang index 6673775e7ba..1757854317a 100644 --- a/src/main/resources/lang/english.lang +++ b/src/main/resources/lang/english.lang @@ -15,7 +15,7 @@ skript: invalid reload: Skript may only be reloaded by either Bukkit's '/reload' or Skript's '/skript reload' command. no scripts: No scripts were found, maybe you should write some ;) no errors: All scripts loaded without errors. - scripts loaded: Loaded %s script¦¦s¦ with a total of %s trigger¦¦s¦ and %s command¦¦s¦ in %s + scripts loaded: Loaded %s script¦¦s¦ with a total of %s structure¦¦s¦ in %s finished loading: Finished loading. # -- Skript command -- diff --git a/src/main/resources/lang/french.lang b/src/main/resources/lang/french.lang index 58774d40d3d..d2ec47c381e 100644 --- a/src/main/resources/lang/french.lang +++ b/src/main/resources/lang/french.lang @@ -15,7 +15,7 @@ skript: invalid reload: Skript ne peut être rechargé qu'avec la commande '/reload' de Bukkit ou la commande '/skript reload' de Skript. no scripts: Aucun script n'a été trouvé, vous devriez peut-être en écrire quelques-uns ;) no errors: Tous les scripts ont été chargés sans erreur. - scripts loaded: %s ¦script a été chargé¦scripts ont été chargés¦ avec un total de %s déclencheur¦¦s¦ et de %s commande¦¦s¦ en %s + scripts loaded: %s ¦script a été chargé¦scripts ont été chargés¦ avec un total de %s structure¦¦s¦ en %s finished loading: Chargement terminé. # -- Skript command -- diff --git a/src/main/resources/lang/german.lang b/src/main/resources/lang/german.lang index 89a14b0110a..1594e3c0a88 100644 --- a/src/main/resources/lang/german.lang +++ b/src/main/resources/lang/german.lang @@ -15,7 +15,7 @@ skript: invalid reload: Skript darf nur mittles Bukkits '/reload' oder Skripts '/skript reload' neu geladen werden. no scripts: Keine Skripte wurden gefunden, vielleicht solltest du einige schreiben ;) no errors: Alle Skripte wurden fehlerfrei geladen. - scripts loaded: %s Skript¦¦e¦ mit insgesamt %s Trigger¦¦n¦ und %s Befehl¦¦en¦ wurden in %s geladen + scripts loaded: Es wurden %s Scripte geladen, von denen %s Strukturen sind innerhalb von %s finished loading: Laden abgeschlossen. # -- Skript command -- diff --git a/src/main/resources/lang/korean.lang b/src/main/resources/lang/korean.lang index 0d40df4d485..60bd1eb36d9 100644 --- a/src/main/resources/lang/korean.lang +++ b/src/main/resources/lang/korean.lang @@ -15,7 +15,7 @@ skript: invalid reload: Skript는 Bukkit의 '/reload' 또는 스크립트의 '/skript reload' 로만 다시 로드할 수 있습니다. no scripts: 스크립트를 찾을 수 없습니다. 일부 스크립트를 작성해야 합니다 ;) no errors: 모든 스크립트가 오류없이 로드되었습니다. - scripts loaded: %s개의 스크립트 중 총 %s개의 트리거, %s개의 명령어를 %s 만에 로드했습니다. + scripts loaded: %s개의 스크립트에서 총 %s개의 구조를 %s 만에 로드했습니다. finished loading: 로딩을 완료했습니다. # -- Skript command -- From eea0e1a9df4ace12949da597d15ec9d56fc1d3ab Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Thu, 6 Oct 2022 23:26:18 +0200 Subject: [PATCH 107/619] Fix toString of TypePatternElement (#5136) --- src/main/java/ch/njol/skript/patterns/TypePatternElement.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/njol/skript/patterns/TypePatternElement.java b/src/main/java/ch/njol/skript/patterns/TypePatternElement.java index bdeb7f5dc09..418ff05cdf7 100644 --- a/src/main/java/ch/njol/skript/patterns/TypePatternElement.java +++ b/src/main/java/ch/njol/skript/patterns/TypePatternElement.java @@ -165,9 +165,9 @@ public String toString() { if (isNullable) stringBuilder.append("-"); if (flagMask != ~0) { - if ((flagMask & SkriptParser.PARSE_LITERALS) != 0) + if ((flagMask & SkriptParser.PARSE_LITERALS) == 0) stringBuilder.append("~"); - else if ((flagMask & SkriptParser.PARSE_EXPRESSIONS) != 0) + else if ((flagMask & SkriptParser.PARSE_EXPRESSIONS) == 0) stringBuilder.append("*"); } for (int i = 0; i < classes.length; i++) { From ebe6aeba0a5e8fe9684646c232632bdae670d8c2 Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Thu, 6 Oct 2022 23:32:51 +0200 Subject: [PATCH 108/619] Make defendExpression create new ExpressionList (#5047) --- src/main/java/ch/njol/skript/util/LiteralUtils.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/njol/skript/util/LiteralUtils.java b/src/main/java/ch/njol/skript/util/LiteralUtils.java index 3f0ce4c31e6..c004157b48e 100644 --- a/src/main/java/ch/njol/skript/util/LiteralUtils.java +++ b/src/main/java/ch/njol/skript/util/LiteralUtils.java @@ -43,9 +43,16 @@ public class LiteralUtils { @SuppressWarnings("unchecked") public static <T> Expression<T> defendExpression(Expression<?> expr) { if (expr instanceof ExpressionList) { - Expression<?>[] expressions = ((ExpressionList) expr).getExpressions(); - for (int i = 0; i < expressions.length; i++) - expressions[i] = LiteralUtils.defendExpression(expressions[i]); + Expression<?>[] oldExpressions = ((ExpressionList<?>) expr).getExpressions(); + + Expression<? extends T>[] newExpressions = new Expression[oldExpressions.length]; + Class<?>[] returnTypes = new Class[oldExpressions.length]; + + for (int i = 0; i < oldExpressions.length; i++) { + newExpressions[i] = LiteralUtils.defendExpression(oldExpressions[i]); + returnTypes[i] = newExpressions[i].getReturnType(); + } + return new ExpressionList<>(newExpressions, (Class<T>) Utils.getSuperType(returnTypes), expr.getAnd()); } else if (expr instanceof UnparsedLiteral) { Literal<?> parsedLiteral = ((UnparsedLiteral) expr).getConvertedExpression(Object.class); return (Expression<T>) (parsedLiteral == null ? expr : parsedLiteral); From 685d8203662b01f34b9a8dbc96be846b93fdb3de Mon Sep 17 00:00:00 2001 From: "Mr. Darth" <66969562+Mr-Darth@users.noreply.github.com> Date: Fri, 7 Oct 2022 00:37:29 +0300 Subject: [PATCH 109/619] Escape colons in ExprParse (#5071) --- .../ch/njol/skript/expressions/ExprParse.java | 104 +++++++++--------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprParse.java b/src/main/java/ch/njol/skript/expressions/ExprParse.java index 14df7ceaf02..72516bcf0e2 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprParse.java +++ b/src/main/java/ch/njol/skript/expressions/ExprParse.java @@ -40,16 +40,12 @@ import ch.njol.skript.lang.VariableString; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.skript.localization.Language; -import ch.njol.skript.log.ErrorQuality; import ch.njol.skript.log.LogEntry; import ch.njol.skript.log.ParseLogHandler; import ch.njol.skript.log.SkriptLogger; import ch.njol.util.Kleenean; import ch.njol.util.NonNullPair; -/** - * @author Peter Güttinger - */ @Name("Parse") @Description({"Parses text as a given type, or as a given pattern.", "This expression can be used in two different ways: One which parses the entire text as a single instance of a type, e.g. as a number, " + @@ -61,38 +57,41 @@ "- You <i>have to</i> save the expression's value in a list variable, e.g. <code>set {parsed::*} to message parsed as \"...\"</code>.", "- The list variable will contain the parsed values from all %types% in the pattern in order. If a type was plural, e.g. %items%, the variable's value at the respective index will be a list variable," + " e.g. the values will be stored in {parsed::1::*}, not {parsed::1}."}) -@Examples({"set {var} to line 1 parsed as number", - "on chat:", - " set {var::*} to message parsed as \"buying %items% for %money%\"", - " if parse error is set:", - " message \"%parse error%\"", - " else if {var::*} is set:", - " cancel event", - " remove {var::2} from the player's balance", - " give {var::1::*} to the player"}) +@Examples({ + "set {var} to line 1 parsed as number", + "on chat:", + "\tset {var::*} to message parsed as \"buying %items% for %money%\"", + "\tif parse error is set:", + "\t\tmessage \"%parse error%\"", + "\telse if {var::*} is set:", + "\t\tcancel event", + "\t\tremove {var::2} from the player's balance", + "\t\tgive {var::1::*} to the player" +}) @Since("2.0") public class ExprParse extends SimpleExpression<Object> { + static { Skript.registerExpression(ExprParse.class, Object.class, ExpressionType.COMBINED, "%string% parsed as (%-*classinfo%|\"<.*>\")"); } - + @Nullable static String lastError = null; - + @SuppressWarnings("NotNullFieldNotInitialized") private Expression<String> text; - + @Nullable private String pattern; @Nullable private boolean[] plurals; - + @Nullable - private ClassInfo<?> c; - - @SuppressWarnings({"unchecked", "null"}) + private ClassInfo<?> classInfo; + @Override + @SuppressWarnings({"unchecked", "null"}) public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { text = (Expression<String>) exprs[0]; if (exprs[1] == null) { @@ -101,13 +100,13 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye Skript.error("Invalid amount and/or placement of double quotes in '" + pattern + "'"); return false; } - + NonNullPair<String, boolean[]> p = SkriptParser.validatePattern(pattern); if (p == null) return false; pattern = p.getFirst(); - - // escape '¦' + + // Escape '¦' and ':' (used for parser tags/marks) StringBuilder b = new StringBuilder(pattern.length()); for (int i = 0; i < pattern.length(); i++) { char c = pattern.charAt(i); @@ -115,48 +114,49 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye b.append(c); b.append(pattern.charAt(i + 1)); i++; - } else if (c == '¦') { - b.append("\\¦"); + } else if (c == '¦' || c == ':') { + b.append("\\"); + b.append(c); } else { b.append(c); } } - pattern = "" + b.toString(); - + pattern = b.toString(); + this.pattern = pattern; plurals = p.getSecond(); } else { - c = ((Literal<ClassInfo<?>>) exprs[1]).getSingle(); - if (c.getC() == String.class) { + classInfo = ((Literal<ClassInfo<?>>) exprs[1]).getSingle(); + if (classInfo.getC() == String.class) { Skript.error("Parsing as text is useless as only things that are already text may be parsed"); return false; } - Parser<?> p = c.getParser(); + Parser<?> p = classInfo.getParser(); if (p == null || !p.canParse(ParseContext.COMMAND)) { // TODO special parse context? - Skript.error("Text cannot be parsed as " + c.getName().withIndefiniteArticle()); + Skript.error("Text cannot be parsed as " + classInfo.getName().withIndefiniteArticle()); return false; } } return true; } - - @SuppressWarnings("null") + @Override @Nullable - protected Object[] get(Event e) { - String t = text.getSingle(e); + @SuppressWarnings("null") + protected Object[] get(Event event) { + String t = text.getSingle(event); if (t == null) return null; ParseLogHandler h = SkriptLogger.startParseLogHandler(); try { lastError = null; - - if (c != null) { - Parser<?> p = c.getParser(); - assert p != null; // checked in init() - Object o = p.parse(t, ParseContext.COMMAND); + + if (classInfo != null) { + Parser<?> parser = classInfo.getParser(); + assert parser != null; // checked in init() + Object o = parser.parse(t, ParseContext.COMMAND); if (o != null) { - Object[] one = (Object[]) Array.newInstance(c.getC(), 1); + Object[] one = (Object[]) Array.newInstance(classInfo.getC(), 1); one[0] = o; return one; } @@ -164,19 +164,19 @@ protected Object[] get(Event e) { assert pattern != null && plurals != null; ParseResult r = SkriptParser.parse(t, pattern); if (r != null) { - assert plurals.length == r.exprs.length; + assert plurals.length == r.exprs.length; int resultCount = 0; for (int i = 0; i < r.exprs.length; i++) { if (r.exprs[i] != null) // Ignore missing optional parts resultCount++; } - + Object[] os = new Object[resultCount]; for (int i = 0, slot = 0; i < r.exprs.length; i++) { if (r.exprs[i] != null) os[slot++] = plurals[i] ? r.exprs[i].getArray(null) : r.exprs[i].getSingle(null); } - + return os; } } @@ -184,8 +184,8 @@ protected Object[] get(Event e) { if (err != null) { lastError = err.toString(); } else { - if (c != null) { - lastError = t + " could not be parsed as " + c.getName().withIndefiniteArticle(); + if (classInfo != null) { + lastError = t + " could not be parsed as " + classInfo.getName().withIndefiniteArticle(); } else { lastError = t + " could not be parsed as \"" + pattern + "\""; } @@ -196,20 +196,20 @@ protected Object[] get(Event e) { h.printLog(); } } - + @Override public boolean isSingle() { return pattern == null; } - + @Override public Class<?> getReturnType() { - return c != null ? c.getC() : Object[].class; + return classInfo != null ? classInfo.getC() : Object[].class; } - + @Override public String toString(@Nullable Event e, boolean debug) { - return text.toString(e, debug) + " parsed as " + (c != null ? c.toString(Language.F_INDEFINITE_ARTICLE) : pattern); + return text.toString(e, debug) + " parsed as " + (classInfo != null ? classInfo.toString(Language.F_INDEFINITE_ARTICLE) : pattern); } - + } From 5fb088fe267b9a17aa121fe5a11e722a44f4a6f3 Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Thu, 6 Oct 2022 23:50:40 +0200 Subject: [PATCH 110/619] Fix wrong array component type for ExprEntities (#5131) --- .../njol/skript/expressions/ExprEntities.java | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprEntities.java b/src/main/java/ch/njol/skript/expressions/ExprEntities.java index e65d6f257a2..5752b8ed98c 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprEntities.java +++ b/src/main/java/ch/njol/skript/expressions/ExprEntities.java @@ -18,22 +18,6 @@ */ package ch.njol.skript.expressions; -import java.lang.reflect.Array; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; - -import org.bukkit.Bukkit; -import org.bukkit.Chunk; -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; @@ -50,7 +34,20 @@ import ch.njol.util.Kleenean; import ch.njol.util.StringUtils; import ch.njol.util.coll.iterator.CheckedIterator; -import ch.njol.util.coll.iterator.NonNullIterator; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; @Name("Entities") @Description("All entities in all worlds, in a specific world, in a chunk or in a radius around a certain location, " + @@ -138,7 +135,7 @@ protected Entity[] get(Event e) { if (isUsingRadius) { Iterator<? extends Entity> iter = iterator(e); if (iter == null || !iter.hasNext()) - return new Entity[0]; + return null; List<Entity> l = new ArrayList<>(); while (iter.hasNext()) From 65c550aa42516a59cceb5e0af383ae149b515403 Mon Sep 17 00:00:00 2001 From: Fusezion <fusezionstream@gmail.com> Date: Thu, 6 Oct 2022 18:33:50 -0400 Subject: [PATCH 111/619] Fix pattern issue with EffPotion (#5118) --- .../ch/njol/skript/effects/EffPotion.java | 46 +++++++++---------- .../tests/syntaxes/effects/EffPotion.sk | 15 ++++++ 2 files changed, 38 insertions(+), 23 deletions(-) create mode 100644 src/test/skript/tests/syntaxes/effects/EffPotion.sk diff --git a/src/main/java/ch/njol/skript/effects/EffPotion.java b/src/main/java/ch/njol/skript/effects/EffPotion.java index 255e0581468..09c7af5912a 100644 --- a/src/main/java/ch/njol/skript/effects/EffPotion.java +++ b/src/main/java/ch/njol/skript/effects/EffPotion.java @@ -42,25 +42,25 @@ @Name("Potion Effects") @Description("Apply or remove potion effects to/from entities.") @Examples({"apply swiftness 2 to the player", - "remove haste from the victim", - "on join:", - "\tapply potion of strength of tier {strength.%player%} to the player for 999 days", - "apply potion effects of player's tool to player"}) + "remove haste from the victim", + "on join:", + "\tapply potion of strength of tier {strength.%player%} to the player for 999 days", + "apply potion effects of player's tool to player"}) @Since("2.0, 2.2-dev27 (ambient and particle-less potion effects), 2.5 (replacing existing effect), 2.5.2 (potion effects)") public class EffPotion extends Effect { static { Skript.registerEffect(EffPotion.class, - "apply [potion of] %potioneffecttypes% [potion] [[[of] tier] %-number%] to %livingentities% [for %-timespan%] [(1¦replacing [the] existing effect)]", - "apply ambient [potion of] %potioneffecttypes% [potion] [[[of] tier] %-number%] to %livingentities% [for %-timespan%] [(1¦replacing [the] existing effect)]", - "apply [potion of] %potioneffecttypes% [potion] [[[of] tier] %-number%] without [any] particles to %livingentities% [for %-timespan%] [(1¦replacing [the] existing effect)]", - "apply %potioneffects% to %livingentities%" - //, "apply %itemtypes% to %livingentities%" - /*,"remove %potioneffecttypes% from %livingentities%"*/); + "apply %potioneffects% to %livingentities%", + "apply [potion of] %potioneffecttypes% [potion] [[[of] tier] %-number%] to %livingentities% [for %-timespan%] [(1¦replacing [the] existing effect)]", + "apply ambient [potion of] %potioneffecttypes% [potion] [[[of] tier] %-number%] to %livingentities% [for %-timespan%] [(1¦replacing [the] existing effect)]", + "apply [potion of] %potioneffecttypes% [potion] [[[of] tier] %-number%] without [any] particles to %livingentities% [for %-timespan%] [(1¦replacing [the] existing effect)]" + //, "apply %itemtypes% to %livingentities%" + /*,"remove %potioneffecttypes% from %livingentities%"*/); } - + private final static int DEFAULT_DURATION = 15 * 20; // 15 seconds, same as EffPoison private boolean replaceExisting; - + @SuppressWarnings("null") private Expression<PotionEffectType> potions; @Nullable @@ -75,12 +75,12 @@ public class EffPotion extends Effect { private boolean ambient; // Ambient means less particles private boolean particles; // Particles or no particles? private boolean potionEffect; // PotionEffects rather than PotionEffectTypes - + @SuppressWarnings({"unchecked", "null"}) @Override public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { - apply = matchedPattern < 3; - potionEffect = matchedPattern == 3; + apply = matchedPattern > 0; + potionEffect = matchedPattern == 0; replaceExisting = parseResult.mark == 1; if (potionEffect) { potionEffects = (Expression<PotionEffect>) exprs[0]; @@ -94,26 +94,26 @@ public boolean init(final Expression<?>[] exprs, final int matchedPattern, final potions = (Expression<PotionEffectType>) exprs[0]; entities = (Expression<LivingEntity>) exprs[1]; } - + // Ambience and particles switch (matchedPattern) { - case 0: + case 1: ambient = false; particles = true; break; - case 1: + case 2: ambient = true; particles = true; break; - case 2: + case 3: ambient = false; particles = false; break; } - + return true; } - + @Override protected void execute(final Event e) { if (potionEffect) { @@ -164,7 +164,7 @@ protected void execute(final Event e) { } } } - + @Override public String toString(final @Nullable Event e, final boolean debug) { if (potionEffect) @@ -174,5 +174,5 @@ else if (apply) else return "remove " + potions.toString(e, debug) + " from " + entities.toString(e, debug); } - + } diff --git a/src/test/skript/tests/syntaxes/effects/EffPotion.sk b/src/test/skript/tests/syntaxes/effects/EffPotion.sk new file mode 100644 index 00000000000..0db81c9d2d7 --- /dev/null +++ b/src/test/skript/tests/syntaxes/effects/EffPotion.sk @@ -0,0 +1,15 @@ +test "potion effect": + spawn a pig at spawn of world "world" + set {_pig} to last spawned pig + set {_potion} to potion effect of slowness of tier 5 for 10 seconds + apply {_potion} to {_pig} + assert {_pig} has potion effect slowness with "pig failed to apply slowness" + apply potion effect of speed of tier 5 for 10 seconds to {_pig} + assert {_pig} has potion effect speed with "pig failed to apply speed" + apply haste of tier 5 to {_pig} for 10 seconds + assert {_pig} has potion effect haste with "pig failed to apply haste" + apply ambient strength of tier 5 to {_pig} for 10 seconds + assert {_pig} has potion effect strength with "pig failed to apply strength" + apply jump boost of tier 5 without particles to {_pig} for 10 seconds + assert {_pig} has potion effect slowness with "pig failed to apply jump boost" + delete last spawned pig \ No newline at end of file From 53004ad17a55b1895210fd92d1b62ebdfcc96214 Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Sat, 8 Oct 2022 01:43:51 +0300 Subject: [PATCH 112/619] Pickup Delay Expression (#5132) --- .../skript/expressions/ExprPickupDelay.java | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprPickupDelay.java diff --git a/src/main/java/ch/njol/skript/expressions/ExprPickupDelay.java b/src/main/java/ch/njol/skript/expressions/ExprPickupDelay.java new file mode 100644 index 00000000000..7fffb26312b --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprPickupDelay.java @@ -0,0 +1,108 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.util.Timespan; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Item; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Pickup Delay") +@Description("The amount of time before a dropped item can be picked up by an entity.") +@Examples({ + "drop diamond sword at {_location} without velocity", + "set pickup delay of last dropped item to 5 seconds" +}) +@Since("INSERT VERSION") +public class ExprPickupDelay extends SimplePropertyExpression<Entity, Timespan> { + + static { + register(ExprPickupDelay.class, Timespan.class, "pick[ ]up delay", "entities"); + } + + @Override + @Nullable + public Timespan convert(Entity entity) { + if (!(entity instanceof Item)) + return null; + return Timespan.fromTicks_i(((Item) entity).getPickupDelay()); + } + + + @Override + @Nullable + public Class<?>[] acceptChange(ChangeMode mode) { + switch (mode) { + case SET: + case ADD: + case RESET: + case DELETE: + case REMOVE: + return CollectionUtils.array(Timespan.class); + } + return null; + } + + @Override + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + Entity[] entities = getExpr().getArray(event); + int change = delta == null ? 0 : (int) ((Timespan) delta[0]).getTicks_i(); + switch (mode) { + case REMOVE: + change = -change; + case ADD: + for (Entity entity : entities) { + if (entity instanceof Item) { + Item item = (Item) entity; + item.setPickupDelay(item.getPickupDelay() + change); + } + } + break; + case DELETE: + case RESET: + case SET: + for (Entity entity : entities) { + if (entity instanceof Item) + ((Item) entity).setPickupDelay(change); + } + break; + default: + assert false; + } + } + + @Override + public Class<? extends Timespan> getReturnType() { + return Timespan.class; + } + + @Override + protected String getPropertyName() { + return "pickup delay"; + } + +} From 2ec733efb67869690da9fe4716eacc139b586d01 Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Tue, 11 Oct 2022 16:52:25 +0200 Subject: [PATCH 113/619] Fix isQuotedCorrectly, to account for 1-char string `"` (#5150) --- src/main/java/ch/njol/skript/lang/VariableString.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/lang/VariableString.java b/src/main/java/ch/njol/skript/lang/VariableString.java index 51f8191e144..6eea122a90e 100644 --- a/src/main/java/ch/njol/skript/lang/VariableString.java +++ b/src/main/java/ch/njol/skript/lang/VariableString.java @@ -159,7 +159,7 @@ public static String quote(String string) { * @return Whether the string is quoted correctly */ public static boolean isQuotedCorrectly(String s, boolean withQuotes) { - if (withQuotes && (!s.startsWith("\"") || !s.endsWith("\""))) + if (withQuotes && (!s.startsWith("\"") || !s.endsWith("\"") || s.length() < 2)) return false; boolean quote = false; boolean percentage = false; From 70ab148219328cae6065e09f406d62bb51b07baf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Lomiak?= <m.lomiak@gmail.com> Date: Sat, 15 Oct 2022 06:50:11 +0200 Subject: [PATCH 114/619] Add translation to the Polish language (#5164) * Create polish.lang Co-authored-by: 3meraldK <48335651+3meraldK@users.noreply.github.com> Co-authored-by: Max White <31369842+Zabujca997@users.noreply.github.com> --- src/main/resources/config.sk | 2 +- src/main/resources/lang/english.lang | 14 +- src/main/resources/lang/polish.lang | 190 +++++++++++++++++++++++++++ 3 files changed, 198 insertions(+), 8 deletions(-) create mode 100644 src/main/resources/lang/polish.lang diff --git a/src/main/resources/config.sk b/src/main/resources/config.sk index f09ec790bda..28825f8e331 100644 --- a/src/main/resources/config.sk +++ b/src/main/resources/config.sk @@ -31,7 +31,7 @@ # ==== General Options ==== language: english -# Which language to use. Currently English, German, Korean and French are included in the download, but custom languages can be created as well. +# Which language to use. Currently English, German, Korean, French and Polish are included in the download, but custom languages can be created as well. # Please note that not everything can be translated yet, i.e. parts of Skript will still be english if you use another language. # If you want to translate Skript to your language please read the readme.txt located in the /lang/ folder in the jar # (open the jar as zip or rename it to Skript.zip to access it) diff --git a/src/main/resources/lang/english.lang b/src/main/resources/lang/english.lang index 1757854317a..80b1846fa0c 100644 --- a/src/main/resources/lang/english.lang +++ b/src/main/resources/lang/english.lang @@ -25,7 +25,7 @@ skript command: description: Skript's main command help: Prints this help message. Use '/skript reload/enable/disable/update' to get more info reload: - description: Reloads the config, all scripts, everything, or a specific script + description: Reloads a specific script, all scripts, the config, or everything all: Reloads the config, all aliases configs and all scripts config: Reloads the main config aliases: Reloads the aliases configs (aliases-english.zip or plugin jar) @@ -46,7 +46,7 @@ skript command: download: Download the newest version info: Prints a message with links to Skript's aliases and documentation gen-docs: Generates documentation using doc-templates in plugin folder - test: Used for running Skript tests + test: Used for running internal Skript tests invalid script: Can't find the script <grey>'<gold>%s<grey>'<red> in the scripts folder! invalid folder: Can't find the folder <grey>'<gold>%s<grey>'<red> in the scripts folder! @@ -75,7 +75,7 @@ skript command: enabling: Enabling all disabled scripts... enabled: Successfully enabled & parsed all previously disabled scripts. error: Encountered %s error¦¦s¦ while parsing disabled scripts! - io error: Could not load any scripts (some scripts might have been renamed already and will be enabled when the server restarts): %s + io error: Could not load one or more scripts - some scripts might have been renamed already and will be enabled when the server restarts: %s single: already enabled: <gold>%s<reset> is already enabled! Use <gray>/<gold>skript <cyan>reload <red>%s<reset> to reload it if it was changed. enabling: Enabling <gold>%s<reset>... @@ -87,19 +87,19 @@ skript command: enabling: Enabling <gold>%2$s <reset>script¦¦s¦ in <gold>%1$s<reset>... enabled: Successfully enabled & parsed <gold>%2$s<reset> previously disabled scripts in <gold>%1$s<reset>. error: Encountered %2$s error¦¦s¦ while parsing scripts in <gold>%1$s<red>! - io error: Error while enabling scripts in <gold>%s<red> (some scripts might get enabled when the server restarts): %s + io error: Error while enabling one or more scripts in <gold>%s<red> (some scripts might get enabled when the server restarts): %s disable: all: disabled: Successfully disabled all scripts. - io error: Could not rename all scripts - some scripts will be enabled again when you restart the server: %s + io error: Could not rename one or more scripts - some scripts might have been renamed already and will be disabled when the server restarts: %s single: already disabled: <gold>%s<reset> is already disabled! disabled: Successfully disabled <gold>%s<reset>. io error: Could not rename <gold>%s<red>, it will be enabled again when you restart the server: %s folder: - empty: <gold>%s<reset> does not contain any enabled scripts, + empty: <gold>%s<reset> does not contain any enabled scripts. disabled: Successfully disabled <gold>%2$s<reset> script(s) in <gold>%1$s<reset>. - io error: Could not disable any script in <gold>%s<red> (some scripts might get disabled when the server restarts): %s + io error: Could not disable one or more scripts in <gold>%s<red> (some scripts might get disabled when the server restarts): %s update: # check/download: see Updater changes: diff --git a/src/main/resources/lang/polish.lang b/src/main/resources/lang/polish.lang new file mode 100644 index 00000000000..acbefd751f9 --- /dev/null +++ b/src/main/resources/lang/polish.lang @@ -0,0 +1,190 @@ +# Default Polish 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: <none> + +# -- Skript -- +skript: + copyright: ~ stworzony przez & © Peter "Njol" Güttinger ~ + prefix: <gray>[<gold>Skript<gray>] # not used + quotes error: Nieprawidłowe użycie cudzysłowu ("). Jeśli chcesz użyć cudzysłowu w "cytowanym tekście", podwój go: "". + brackets error: Nieprawidłowa liczba lub rozmieszczenie nawiasów. Upewnij się, że każdy nawias otwierający ma odpowiadający mu nawias zamykający. + invalid reload: Skript może być przeładowany tylko przez polecenie '/reload' Bukkita lub '/skript reload'. + no scripts: Nie znaleziono żadnych skryptów, może warto jakieś napisać ;) + no errors: Wszystkie skrypty zostały załadowane bez błędów. + scripts loaded: Załadowano %s skrypt¦¦ów¦ posiadających łącznie %s struktur¦ę¦¦ w %s + finished loading: Zakończone ładowanie. + +# -- Skript command -- +skript command: + usage: Sposób użycia: + help: + description: Główne polecenie Skripta. + help: Wyświetla tę stronę pomocy. Użyj '/skript reload/enable/disable/update' aby otrzymać więcej informacji + reload: + description: Przeładowuje wybrany skrypt, każdy ze skryptów, konfigurację albo wszystko + all: Przeładowuje każdy ze skryptów, konfigurację i wszystkie konfiguracje aliasów + config: Przeładowuje główną konfigurację + aliases: Przeładowuje konfiguracje aliasów (aliases-*.sk lub JAR Skripta) + scripts: Przeładowuje wszystkie skrypty + <script>: Przeładowuje wybrany skrypt lub folder skryptów + enable: + description: Włącza wszystkie skrypty lub określony skrypt + all: Włącza wszystkie skrypty + <script>: Włącza określony skrypt lub folder skryptów + disable: + description: Wyłącza wszystkie skrypty lub wybrany skrypt + all: Wyłącza wszystkie skrypty + <script>: Wyłącza określony skrypt lub folder skryptów + update: + description: Sprawdź aktualizację, przeczytaj listę zmian lub pobierz najnowszą wersję Skripta + check: Sprawdza dostępność nowszej wersji + changes: Wypisuje nowe zmiany od obecnej wersji + download: Pobiera najnowszą stabilną wersję + info: Wyświetla linki do aliasów i oficjalnej dokumentacji Skripta + gen-docs: Generuje dokumentację używając szablonów w folderze Skripta + test: Służy do uruchamiania wewnętrznych testów Skripta + + invalid script: Nie znaleziono skryptu <grey>'<gold>%s<grey>'<red> w folderze scripts! + invalid folder: Nie znaleziono folderu <grey>'<gold>%s<grey>'<red> w folderze scripts! + reload: + warning line info: <gold><bold>Linia %s:<gray> (%s)<reset>\n + error line info: <light red><bold>Linia %s:<gray> (%s)<reset>\n + reloading: Przeładowywanie <gold>%s<reset>... + reloaded: <lime>Pomyślnie przeładowano <gold>%s<lime>. <gray>(<gold>%2$sms<gray>) + error: <light red>Napotkano <gold>%2$s <light red>bł¦ąd¦ędów¦ podczas przeładowywania <gold>%1$s<light red>! <gray>(<gold>%3$sms<gray>) + script disabled: <gold>%s<reset> jest obecnie wyłączony. Użyj najpierw <gray>/<gold>skript <cyan>enable <red>%s<reset> aby go włączyć. + warning details: <yellow> %s<reset>\n + error details: <light red> %s<reset>\n + other details: <white> %s<reset>\n + line details: <gold> Linia: <gray>%s<reset>\n <reset> + + config, aliases and scripts: konfiguracja, aliasy i wszystkie skrypty + scripts: wszystkie skrypty + main config: główna konfiguracja + aliases: aliasy + script: <gold>%s<reset> + scripts in folder: wszystkie skrypty w <gold>%s<reset> + x scripts in folder: <gold>%2$s <reset>skrypt¦¦ów¦ w <gold>%1$s<reset> + empty folder: <gold>%s<reset> nie ma w sobie żadnych włączonych skryptów. + enable: + all: + enabling: Włączanie wszystkich wyłączonych skryptów... + enabled: Pomyślnie włączono i zinterpretowano wszystkie wyłączone skrypty. + error: Znaleziono %s bł¦ąd¦ędów¦ podczas interpretacji wyłączonych skryptów! + io error: Nie udało się włączyć jednego lub większej liczby skryptów (niektóre skrypty mogą pozostać wyłączone po restarcie serwera): %s + single: + already enabled: <gold>%s<reset> jest już włączony! Użyj <gray>/<gold>skript <cyan>reload <red>%s<reset> aby odświeżyć skrypt, jeśli został edytowany. + enabling: Włączanie <gold>%s<reset>... + enabled: Pomyślnie włączono i zinterpretowano <gold>%s<reset>. + error: Znaleziono %2$s bł¦ąd¦ędów¦ podczas interpretacji <gold>%1$s<red>! + io error: Nie udało się włączyć <gold>%s<red>: %s + folder: + empty: <gold>%s<reset> nie zawiera w sobie żadnych wyłączonych skryptów. + enabling: Włączanie <gold>%2$s <reset>skrypt¦¦ów¦ in <gold>%1$s<reset>... + enabled: Pomyślnie włączono i zinterpretowano <gold>%2$s<reset> poprzednio wyłączonych skryptów <gold>%1$s<reset>. + error: Znaleziono %2$s bł¦ąd¦ędów¦ podczas interpretacji skryptów w folderze <gold>%1$s<red>! + io error: Nie udało się włączyć jednego lub większej liczby skryptów w folderze <gold>%s<red> (niektóre skrypty mogą pozostać wyłączone po restarcie serwera): %s + disable: + all: + disabled: Pomyślnie wyłączono wszystkie skrypty. + io error: Nie udało się zmienić nazwy jednego lub większej liczby skryptów - niektóre skrypty mogą pozostać włączone po restarcie serwera: %s + single: + already disabled: <gold>%s<reset> jest już wyłączony! + disabled: Pomyślnie wyłączono <gold>%s<reset>. + io error: Nie udało się zmienić nazwy <gold>%s<red>, skrypt pozostanie włączony po restarcie serwera: %s + folder: + empty: <gold>%s<reset> nie zawiera żadnych włączonych skryptów. + disabled: Pomyślnie wyłączono <gold>%2$s<reset> skrypt¦¦ów¦ w <gold>%1$s<reset>. + io error: Nie udało się wyłączyć jednego lub większej liczby skryptów w folderze <gold>%s<red> (niektóre skrypty mogą pozostać włączone po restarcie serwera): %s + update: + # check/download: see Updater + changes: + # multiple versions: + # title: <gold>%s<r> aktualizacj¦a¦i¦ zostało wydanych od wydania tej wersji (<gold>%s<r>) Skripta: + # footer: Aby pokazać listę zmian wersji, napisz <gold>/skript update changes <version><reset> + # invalid version: Nie ma listy zmian dla wersji <gold>%s<red> + title: <bold><cyan>%s<reset> (%s) + next page: <grey>strona %s. z %s. Wpisz <gold>/skript update changes %s<gray> by przewinąć stronę (podpowiedź: użyj strzałki w górę na klawiaturze) + info: + aliases: Aliasy Skripta mogą zostać znalezione tutaj: <aqua>https://github.com/SkriptLang/skript-aliases + documentation: Dokumentację Skripta można znaleźć tutaj: <aqua>https://skriptlang.github.io/Skript + tutorials: Poradniki do Skripta można znaleźć tutaj: <aqua>https://docs.skriptlang.org/tutorials + version: Wersja Skripta: <aqua>%s + server: Wersja serwera: <aqua>%s + addons: Zainstalowane dodatki do Skripta: <aqua>%s + dependencies: Zainstalowane zależności: <aqua>%s + +# -- Updater -- +updater: + not started: Skript jeszcze nie sprawdził najnowszej stabilnej wersji. Użyj <gold>/skript update check<reset> aby to zrobić. + checking: Sprawdzanie najnowszej wersji Skripta... + check in progress: Sprawdzanie najnowszej wersji Skripta obecnie trwa. + updater disabled: Aktualizator jest obecnie wyłączony, więc sprawdzenie najnowszej wersji Skripta nie zostało wykonane. + check error: <red>Wystąpił błąd podczas sprawdzania najnowszej wersji Skripta:<light red> %s + running latest version: Na tym serwerze jest obecnie najnowsza stabilna wersja Skripta. + running latest version (beta): Na tym serwerze obecnie jest wersja <i>beta<r> Skript'a, a nie ma jeszcze dostępnej nowszej od niej wersji <i>stabilnej<r>. Miej na uwadze, że ewentualnych aktualizacji do nowszych wersji beta dokonywać należy ręcznie! + update available: Nowa wersja Skripta dostępna: <gold>%s<reset> (obecnie na tym serwerze jest <gold>%s<reset>) + downloading: Pobieranie Skripta <gold>%s<reset>... + download in progress: Obecnie trwa pobieranie najnowszej wersji Skripta. + download error: <red>Podczas pobierania najnowszej wersji Skripta wystąpił błąd:<light red> %s + downloaded: Najnowsza wersja Skripta została pobrana! Zrestartuj serwer lub użyj polecenia /reload aby zastosować zmiany. + internal error: Wystąpił wewnętrzny błąd podczas sprawdzania najnowszej wersji Skripta. Sprawdź konsolę lub logi serwera po więcej szczegółów. + custom version: Na twoim serwerze jest obecnie zmodyfikowana, nieoficjalna wersja Skripta. Aktualizacje nie będą automatycznie instalowane. + nightly build: Na twoim serwerze jest obecnie rozwojowa wersja Skripta. Aktualizacje nie będą automatycznie instalowane. + +# -- Commands -- +commands: + no permission message: Nie masz odpowiednich uprawnień, aby użyć tego polecenia. + cooldown message: Używasz tego polecenia zbyt często, spróbuj ponownie później + executable by players: To polecenie może zostać użyte tylko przez graczy + executable by console: To polecenie może zostać użyte tylko z konsoli + correct usage: Prawidłowe użycie: + invalid argument: Niepoprawny argument <gray>'%s<gray>'<reset>. Dozwolone są: + too many arguments: To polecenie przyjmuje tylko jeden argument %2$s. + internal error: Podczas próby wykonania tego polecenia wystąpił wewnętrzny błąd. + no player starts with: Nie ma obecnie gracza, którego nick zaczyna się na '%s' + multiple players start with: Na serwerze jest obecnie kilku graczy, których nicki zaczynają się na '%s' + +# -- Hooks -- +hooks: + hooked: Udało się podpiąć do %s + error: Nie udało się podpiąć do %1$s. Może się to zdarzyć w przypadku, gdy Skript nie wspiera zainstalowanej wersji %1$s + +# -- Aliases -- +aliases: + # Errors and warnings + empty string: '' nie jest rodzajem przedmiotu + invalid item type: '%s' nie jest rodzajem przedmiotu + empty name: Alias musi mieć nazwę + brackets error: Nieprawidłowe użycie nawiasów + not enough brackets: Sekcja zaczynająca się znakiem %s ('%s') musi być zamknięta + too many brackets: Znak %s ('%s') zamyka nieistniejącą sekcję + unknown variation: Wariacja %s nie została zdefiniowana wcześniej + missing aliases: Następujące minecraftowe ID nie mają żadnych zdefiniowanych aliasów: + empty alias: Alias nie ma zdefiniowanych tagów ani minecraftowych ID + invalid minecraft id: minecraftowe ID %s nie jest prawidłowe + useless variation: Wariacja nie ma zdefiniowanych tagów ani minecraftowych ID, więc jest bezużyteczna + invalid tags: Podane tagi nie są zdefiniowane w prawidłowym formacie JSON + unexpected section: Sekcje nie są tu dozwolone + invalid variation section: Sekcja powinna być sekcją wariacyjną, ale %s nie jest prawidłową nazwą wariantu + outside section: Aliasy muszą być umieszczone w sekcjach + + # Other messages + loaded x aliases from: Załadowano %s aliasów z %s + loaded x aliases: Załadowano łącznie %s aliasów + +# -- Time -- +time: + errors: + 24 hours: Dzień ma tylko 24 godziny + 12 hours: Używanie 12-godzinnego formatu nie pozwala na użycie więcej niż 12 godzin + 60 minutes: Godzina ma tylko 60 minut + +# -- IO Exceptions -- +io exceptions: + unknownhostexception: Nie udało się połaczyć z %s + accessdeniedexception: Odmowa dostępu dla %s From cbdff19fbdb745da7db9881ff698ea9657d591c5 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Sat, 15 Oct 2022 01:09:03 -0400 Subject: [PATCH 115/619] Cleanup ExprSpawn (#4966) * Cleanup ExprSpawn --- .../ch/njol/skript/expressions/ExprSpawn.java | 108 +++++++++--------- 1 file changed, 55 insertions(+), 53 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprSpawn.java b/src/main/java/ch/njol/skript/expressions/ExprSpawn.java index ff09df72162..44c5284546c 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprSpawn.java +++ b/src/main/java/ch/njol/skript/expressions/ExprSpawn.java @@ -18,15 +18,8 @@ */ package ch.njol.skript.expressions; -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.event.Event; -import org.bukkit.event.world.SpawnChangeEvent; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer.ChangeMode; -import ch.njol.skript.classes.Converter; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; @@ -38,75 +31,84 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.event.Event; +import org.bukkit.event.world.SpawnChangeEvent; +import org.eclipse.jdt.annotation.Nullable; -/** - * @author Peter Güttinger - */ @Name("Spawn") @Description("The spawn point of a world.") -@Examples({"teleport all players to spawn", - "set the spawn point of \"world\" to the player's location"}) +@Examples({ + "teleport all players to spawn", + "set the spawn point of \"world\" to the player's location" +}) @Since("1.4.2") public class ExprSpawn extends PropertyExpression<World, Location> { + static { - Skript.registerExpression(ExprSpawn.class, Location.class, ExpressionType.PROPERTY, "[the] spawn[s] [(point|location)[s]] [of %worlds%]", "%worlds%'[s] spawn[s] [(point|location)[s]]"); + Skript.registerExpression(ExprSpawn.class, Location.class, ExpressionType.PROPERTY, + "[the] spawn[s] [(point|location)[s]] [of %worlds%]", + "%worlds%'[s] spawn[s] [(point|location)[s]]" + ); } - - @SuppressWarnings({"unchecked", "null"}) + @Override - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { setExpr((Expression<? extends World>) exprs[0]); return true; } - - @Override - protected Location[] get(final Event e, final World[] source) { - if (getTime() == -1 && e instanceof SpawnChangeEvent && !Delay.isDelayed(e)) { - return new Location[] {((SpawnChangeEvent) e).getPreviousLocation()}; - } - return get(source, new Converter<World, Location>() { - @Override - @Nullable - public Location convert(final World w) { - return w.getSpawnLocation(); - } - }); - } - - @Override - public Class<? extends Location> getReturnType() { - return Location.class; - } - + @Override - public String toString(final @Nullable Event e, final boolean debug) { - return "spawn of " + getExpr().toString(e, debug); + protected Location[] get(Event event, World[] source) { + if (getTime() == -1 && event instanceof SpawnChangeEvent && !Delay.isDelayed(event)) + return new Location[] {((SpawnChangeEvent) event).getPreviousLocation()}; + return get(source, World::getSpawnLocation); } - + @Override @Nullable - public Class<?>[] acceptChange(final ChangeMode mode) { + public Class<?>[] acceptChange(ChangeMode mode) { if (mode == ChangeMode.SET) return CollectionUtils.array(Location.class); return null; } - + @Override - public void change(final Event e, final @Nullable Object[] delta, final ChangeMode mode) { - assert mode == ChangeMode.SET; - assert delta != null; - - final Location l = (Location) delta[0]; - final int x = l.getBlockX(), y = l.getBlockY(), z = l.getBlockZ(); - for (final World w : getExpr().getArray(e)) { - w.setSpawnLocation(x, y, z); + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + //noinspection ConstantConditions + if (delta == null) + return; + + Location originalLocation = (Location) delta[0]; + assert originalLocation != null; + for (World world : getExpr().getArray(event)) { + Location location = originalLocation.clone(); + World locationWorld = location.getWorld(); + if (locationWorld == null) { + location.setWorld(world); + world.setSpawnLocation(location); + } else if (locationWorld.equals(world)) { + world.setSpawnLocation(location); + } } } - - @SuppressWarnings("unchecked") + @Override - public boolean setTime(final int time) { + @SuppressWarnings("unchecked") + public boolean setTime(int time) { return super.setTime(time, getExpr(), SpawnChangeEvent.class); } - + + @Override + public Class<? extends Location> getReturnType() { + return Location.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the spawn point of " + getExpr().toString(event, debug); + } + } From 58e62664401cf32c72dcf5ce431ce9239975dfd5 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <alali_ayham@yahoo.com> Date: Sat, 15 Oct 2022 08:32:57 +0300 Subject: [PATCH 116/619] =?UTF-8?q?=F0=9F=9A=80=20Add=20`last=20launched?= =?UTF-8?q?=20firework`=20(#4976)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🚀 Add last launched firework --- .../skript/effects/EffFireworkLaunch.java | 42 +++++++------ .../expressions/ExprLastSpawnedEntity.java | 60 ++++++++++++------- 2 files changed, 62 insertions(+), 40 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffFireworkLaunch.java b/src/main/java/ch/njol/skript/effects/EffFireworkLaunch.java index 2d8e82eb8af..721be87af15 100644 --- a/src/main/java/ch/njol/skript/effects/EffFireworkLaunch.java +++ b/src/main/java/ch/njol/skript/effects/EffFireworkLaunch.java @@ -20,6 +20,8 @@ import org.bukkit.FireworkEffect; import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Entity; import org.bukkit.entity.Firework; import org.bukkit.event.Event; import org.bukkit.inventory.meta.FireworkMeta; @@ -45,15 +47,18 @@ public class EffFireworkLaunch extends Effect { Skript.registerEffect(EffFireworkLaunch.class, "(launch|deploy) [[a] firework [with effect[s]]] %fireworkeffects% at %locations% [([with] (duration|power)|timed) %number%]"); } - @SuppressWarnings("null") + @Nullable + public static Entity lastSpawned = null; + + @SuppressWarnings("NotNullFieldNotInitialized") private Expression<FireworkEffect> effects; - @SuppressWarnings("null") + @SuppressWarnings("NotNullFieldNotInitialized") private Expression<Location> locations; - @SuppressWarnings("null") + @SuppressWarnings("NotNullFieldNotInitialized") private Expression<Number> lifetime; - @SuppressWarnings({"unchecked", "null"}) @Override + @SuppressWarnings("unchecked") public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { effects = (Expression<FireworkEffect>) exprs[0]; locations = (Expression<Location>) exprs[1]; @@ -62,24 +67,27 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye } @Override - protected void execute(Event e) { - Number power = lifetime.getSingle(e); - if (power == null) - power = 1; - for (Location location : locations.getArray(e)) { - Firework firework = location.getWorld().spawn(location, Firework.class); + protected void execute(Event event) { + FireworkEffect[] effects = this.effects.getArray(event); + int power = lifetime.getOptionalSingle(event).orElse(1).intValue(); + for (Location location : locations.getArray(event)) { + World world = location.getWorld(); + if (world == null) + continue; + Firework firework = world.spawn(location, Firework.class); FireworkMeta meta = firework.getFireworkMeta(); - meta.addEffects(effects.getArray(e)); - meta.setPower(power.intValue()); + meta.addEffects(effects); + meta.setPower(power); firework.setFireworkMeta(meta); + lastSpawned = firework; } } @Override - public String toString(@Nullable Event e, boolean debug) { - return "Launch firework(s) " + effects.toString(e, debug) + - " at location(s) " + locations.toString(e, debug) + - " timed " + lifetime.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "Launch firework(s) " + effects.toString(event, debug) + + " at location(s) " + locations.toString(event, debug) + + " timed " + lifetime.toString(event, debug); } -} \ No newline at end of file +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprLastSpawnedEntity.java b/src/main/java/ch/njol/skript/expressions/ExprLastSpawnedEntity.java index a3bcda098b5..1dc3ef9df7e 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprLastSpawnedEntity.java +++ b/src/main/java/ch/njol/skript/expressions/ExprLastSpawnedEntity.java @@ -20,8 +20,11 @@ import java.lang.reflect.Array; +import ch.njol.skript.effects.EffFireworkLaunch; import ch.njol.skript.sections.EffSecSpawn; +import ch.njol.util.coll.CollectionUtils; import org.bukkit.entity.Entity; +import org.bukkit.entity.Firework; import org.bukkit.entity.Item; import org.bukkit.entity.LightningStrike; import org.bukkit.event.Event; @@ -43,51 +46,54 @@ import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; -/** - * @author Peter Güttinger - */ @Name("Last Spawned Entity") @Description("Holds the entity that was spawned most recently with the spawn effect (section), dropped with the <a href='../effects/#EffDrop'>drop effect</a>, shot with the <a href='../effects/#EffShoot'>shoot effect</a> or created with the <a href='../effects/#EffLightning'>lightning effect</a>. " + "Please note that even though you can spawn multiple mobs simultaneously (e.g. with 'spawn 5 creepers'), only the last spawned mob is saved and can be used. " + "If you spawn an entity, shoot a projectile and drop an item you can however access all them together.") -@Examples({"spawn a priest", - "set {healer::%spawned priest%} to true", - "shoot an arrow from the last spawned entity", - "ignite the shot projectile", - "drop a diamond sword", - "push last dropped item upwards", - "teleport player to last struck lightning"}) -@Since("1.3 (spawned entity), 2.0 (shot entity), 2.2-dev26 (dropped item), SINCE VERSION (struck lightning)") +@Examples({ + "spawn a priest", + "set {healer::%spawned priest%} to true", + "shoot an arrow from the last spawned entity", + "ignite the shot projectile", + "drop a diamond sword", + "push last dropped item upwards", + "teleport player to last struck lightning", + "delete last launched firework" +}) +@Since("1.3 (spawned entity), 2.0 (shot entity), 2.2-dev26 (dropped item), INSERT VERSION (struck lightning, firework)") public class ExprLastSpawnedEntity extends SimpleExpression<Entity> { static { Skript.registerExpression(ExprLastSpawnedEntity.class, Entity.class, ExpressionType.SIMPLE, "[the] [last[ly]] (0:spawned|1:shot) %*entitydata%", "[the] [last[ly]] dropped (2:item)", - "[the] [last[ly]] (created|struck) (3:lightning)"); + "[the] [last[ly]] (created|struck) (3:lightning)", + "[the] [last[ly]] (launched|deployed) (4:firework)"); } - int from; - @SuppressWarnings("null") + @SuppressWarnings("NotNullFieldNotInitialized") private EntityData<?> type; - - @SuppressWarnings("unchecked") + private int from; + @Override + @SuppressWarnings("unchecked") public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - if (parseResult.mark == 2) {// It's just to make an extra expression for item only + from = parseResult.mark; + if (from == 2) { // It's just to make an extra expression for item only type = EntityData.fromClass(Item.class); - } else if (parseResult.mark == 3) { + } else if (from == 3) { type = EntityData.fromClass(LightningStrike.class); + } else if (from == 4) { + type = EntityData.fromClass(Firework.class); } else { type = ((Literal<EntityData<?>>) exprs[0]).getSingle(); } - from = parseResult.mark; return true; } @Override @Nullable - protected Entity[] get(Event e) { + protected Entity[] get(Event event) { Entity en; switch (from) { case 0: @@ -102,13 +108,18 @@ protected Entity[] get(Event e) { case 3: en = EffLightning.lastSpawned; break; + case 4: + en = EffFireworkLaunch.lastSpawned; + break; default: en = null; } + if (en == null) return null; if (!type.isInstance(en)) return null; + Entity[] one = (Entity[]) Array.newInstance(type.getType(), 1); one[0] = en; return one; @@ -125,8 +136,8 @@ public Class<? extends Entity> getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - String word; + public String toString(@Nullable Event event, boolean debug) { + String word = ""; switch (from) { case 0: word = "spawned"; @@ -140,8 +151,11 @@ public String toString(@Nullable Event e, boolean debug) { case 3: word = "struck"; break; + case 4: + word = "launched"; + break; default: - throw new IllegalStateException(); + assert false; } return "the last " + word + " " + type; } From 0ebf136959714a2a962497d9fb915fbf294c8f66 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Sat, 15 Oct 2022 01:54:48 -0400 Subject: [PATCH 117/619] Implement unregistering event listeners when possible (#5141) * Implement unregistering listeners when possible --- .../ch/njol/skript/SkriptEventHandler.java | 56 ++++++++++++------- .../java/ch/njol/skript/events/EvtClick.java | 4 +- 2 files changed, 38 insertions(+), 22 deletions(-) diff --git a/src/main/java/ch/njol/skript/SkriptEventHandler.java b/src/main/java/ch/njol/skript/SkriptEventHandler.java index 1707b958cca..660556ba035 100644 --- a/src/main/java/ch/njol/skript/SkriptEventHandler.java +++ b/src/main/java/ch/njol/skript/SkriptEventHandler.java @@ -29,9 +29,6 @@ import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import org.bukkit.event.block.Action; -import org.bukkit.event.player.PlayerArmorStandManipulateEvent; -import org.bukkit.event.player.PlayerInteractAtEntityEvent; -import org.bukkit.event.player.PlayerInteractEntityEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.plugin.EventExecutor; import org.bukkit.plugin.RegisteredListener; @@ -241,23 +238,11 @@ public static void registerBukkitEvent(Trigger trigger, Class<? extends Event> e triggers.add(new NonNullPair<>(event, trigger)); EventPriority priority = trigger.getEvent().getEventPriority(); - PriorityListener listener = listeners[priority.ordinal()]; - EventExecutor executor = listener.executor; - - // PlayerInteractEntityEvent has a subclass we need for armor stands - if (event.equals(PlayerInteractEntityEvent.class)) { - if (!isEventRegistered(handlerList, priority)) { - Bukkit.getPluginManager().registerEvent(event, listener, priority, executor, Skript.getInstance()); - Bukkit.getPluginManager().registerEvent(PlayerInteractAtEntityEvent.class, listener, priority, executor, Skript.getInstance()); - } - return; - } - if (event.equals(PlayerInteractAtEntityEvent.class) || event.equals(PlayerArmorStandManipulateEvent.class)) - return; // Ignore, registered above - - if (!isEventRegistered(handlerList, priority)) // Check if event is registered - Bukkit.getPluginManager().registerEvent(event, listener, priority, executor, Skript.getInstance()); + if (!isEventRegistered(handlerList, priority)) { // Check if event is registered + PriorityListener listener = listeners[priority.ordinal()]; + Bukkit.getPluginManager().registerEvent(event, listener, priority, listener.executor, Skript.getInstance()); + } } /** @@ -265,7 +250,35 @@ public static void registerBukkitEvent(Trigger trigger, Class<? extends Event> e * @param trigger The Trigger to unregister events for. */ public static void unregisterBukkitEvents(Trigger trigger) { - triggers.removeIf(pair -> pair.getSecond() == trigger); + triggers.removeIf(pair -> { + if (pair.getSecond() != trigger) + return false; + + HandlerList handlerList = getHandlerList(pair.getFirst()); + assert handlerList != null; + + EventPriority priority = trigger.getEvent().getEventPriority(); + if (triggers.stream().noneMatch(pair2 -> + trigger != pair2.getSecond() // Don't match the trigger we are unregistering + && pair2.getFirst().isAssignableFrom(pair.getFirst()) // Basic similarity check + && priority == pair2.getSecond().getEvent().getEventPriority() // Ensure same priority + && handlerList == getHandlerList(pair2.getFirst()) // Ensure same handler list + )) { // We can attempt to unregister this listener + Skript skript = Skript.getInstance(); + for (RegisteredListener registeredListener : handlerList.getRegisteredListeners()) { + Listener listener = registeredListener.getListener(); + if ( + registeredListener.getPlugin() == skript + && listener instanceof PriorityListener + && ((PriorityListener) listener).priority == priority + ) { + handlerList.unregister(listener); + } + } + } + + return true; + }); } /** @@ -334,8 +347,9 @@ private static boolean isEventRegistered(HandlerList handlerList, EventPriority registeredListener.getPlugin() == Skript.getInstance() && listener instanceof PriorityListener && ((PriorityListener) listener).priority == priority - ) + ) { return true; + } } return false; } diff --git a/src/main/java/ch/njol/skript/events/EvtClick.java b/src/main/java/ch/njol/skript/events/EvtClick.java index 6cf04e0a47e..c41de507c30 100644 --- a/src/main/java/ch/njol/skript/events/EvtClick.java +++ b/src/main/java/ch/njol/skript/events/EvtClick.java @@ -64,7 +64,9 @@ public class EvtClick extends SkriptEvent { private static final ClickEventTracker entityInteractTracker = new ClickEventTracker(Skript.getInstance()); static { - Class<? extends PlayerEvent>[] eventTypes = CollectionUtils.array(PlayerInteractEvent.class, PlayerInteractEntityEvent.class); + Class<? extends PlayerEvent>[] eventTypes = CollectionUtils.array( + PlayerInteractEvent.class, PlayerInteractEntityEvent.class, PlayerInteractAtEntityEvent.class + ); Skript.registerEvent("Click", EvtClick.class, eventTypes, "[(" + RIGHT + "¦right|" + LEFT + "¦left)(| |-)][mouse(| |-)]click[ing] [on %-entitydata/itemtype%] [(with|using|holding) %-itemtype%]", From 0d49ab4d9bb988bde670c4af16e775c1b3a5dbea Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Mon, 17 Oct 2022 03:21:42 -0400 Subject: [PATCH 118/619] Fix ItemData Serialization (#4961) --- .../java/ch/njol/skript/aliases/ItemData.java | 83 +++++++------------ .../njol/skript/aliases/MaterialRegistry.java | 1 + .../skript/bukkitutil/block/BlockValues.java | 8 +- .../bukkitutil/block/NewBlockCompat.java | 47 +++++++++-- .../tests/misc/item data serialization.sk | 6 ++ 5 files changed, 79 insertions(+), 66 deletions(-) create mode 100644 src/test/skript/tests/misc/item data serialization.sk diff --git a/src/main/java/ch/njol/skript/aliases/ItemData.java b/src/main/java/ch/njol/skript/aliases/ItemData.java index 2d74e0015f9..8b7f4d261a0 100644 --- a/src/main/java/ch/njol/skript/aliases/ItemData.java +++ b/src/main/java/ch/njol/skript/aliases/ItemData.java @@ -18,47 +18,40 @@ */ package ch.njol.skript.aliases; -import java.io.IOException; -import java.io.NotSerializableException; -import java.io.StreamCorruptedException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; - +import ch.njol.skript.Skript; +import ch.njol.skript.bukkitutil.BukkitUnsafe; +import ch.njol.skript.bukkitutil.ItemUtils; +import ch.njol.skript.bukkitutil.block.BlockCompat; +import ch.njol.skript.bukkitutil.block.BlockValues; +import ch.njol.skript.localization.Message; +import ch.njol.skript.variables.Variables; +import ch.njol.yggdrasil.Fields; +import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilExtendedSerializable; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.OfflinePlayer; import org.bukkit.block.Block; import org.bukkit.block.BlockState; import org.bukkit.enchantments.Enchantment; -import org.bukkit.entity.EntityType; import org.bukkit.inventory.ItemFactory; import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.PotionMeta; import org.bukkit.inventory.meta.SkullMeta; -import org.bukkit.inventory.meta.SpawnEggMeta; import org.bukkit.potion.PotionData; import org.eclipse.jdt.annotation.Nullable; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import ch.njol.util.EnumTypeAdapter; -import ch.njol.skript.Skript; -import ch.njol.skript.bukkitutil.BukkitUnsafe; -import ch.njol.skript.bukkitutil.ItemUtils; -import ch.njol.skript.bukkitutil.block.BlockCompat; -import ch.njol.skript.bukkitutil.block.BlockValues; -import ch.njol.skript.localization.Message; -import ch.njol.skript.variables.Variables; -import ch.njol.yggdrasil.Fields; -import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilExtendedSerializable; +import java.io.IOException; +import java.io.NotSerializableException; +import java.io.StreamCorruptedException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; public class ItemData implements Cloneable, YggdrasilExtendedSerializable { @@ -79,36 +72,15 @@ public static class OldItemData { static final ItemFactory itemFactory = Bukkit.getServer().getItemFactory(); - static final MaterialRegistry materialRegistry; - // Load or create material registry static { - Gson gson = new GsonBuilder().registerTypeAdapterFactory(EnumTypeAdapter.factory).serializeNulls().create(); Path materialsFile = Paths.get(Skript.getInstance().getDataFolder().getAbsolutePath(), "materials.json"); if (Files.exists(materialsFile)) { - String content = null; try { - content = new String(Files.readAllBytes(materialsFile), StandardCharsets.UTF_8); + Files.delete(materialsFile); } catch (IOException e) { - Skript.exception(e, "Loading material registry failed!"); + Skript.exception(e, "Failed to remove legacy material registry file!"); } - if (content != null) { - String[] names = gson.fromJson(content, String[].class); - assert names != null; - materialRegistry = MaterialRegistry.load(names); - } else { - materialRegistry = new MaterialRegistry(); - } - } else { - materialRegistry = new MaterialRegistry(); - } - - // Always rewrite material registry, in case some updates got applied to it - String content = gson.toJson(materialRegistry.getMaterials()); - try { - Files.write(materialsFile, content.getBytes(StandardCharsets.UTF_8)); - } catch (IOException e) { - Skript.exception(e, "Saving material registry failed!"); } } @@ -142,7 +114,7 @@ public static class OldItemData { * allow comparing it against other blocks. */ @Nullable - transient BlockValues blockValues; + BlockValues blockValues; /** * Whether this represents an item (that definitely cannot have @@ -590,20 +562,23 @@ public boolean matchPlain(ItemData other) { @Override public Fields serialize() throws NotSerializableException { Fields fields = new Fields(this); // ItemStack is transient, will be ignored - fields.putPrimitive("id", materialRegistry.getId(type)); + fields.putPrimitive("id", type.ordinal()); fields.putObject("meta", stack.getItemMeta()); return fields; } + private static final Material[] materials = Material.values(); + @Override public void deserialize(Fields fields) throws StreamCorruptedException, NotSerializableException { - this.type = materialRegistry.getMaterial(fields.getAndRemovePrimitive("id", int.class)); + this.type = materials[fields.getAndRemovePrimitive("id", int.class)]; ItemMeta meta = fields.getAndRemoveObject("meta", ItemMeta.class); - fields.setFields(this); // Everything but ItemStack and Material - + // Initialize ItemStack this.stack = new ItemStack(type); stack.setItemMeta(meta); // Just set meta to it + + fields.setFields(this); // Everything but ItemStack and Material } /** diff --git a/src/main/java/ch/njol/skript/aliases/MaterialRegistry.java b/src/main/java/ch/njol/skript/aliases/MaterialRegistry.java index 2843b7eb2d6..d677715d4ce 100644 --- a/src/main/java/ch/njol/skript/aliases/MaterialRegistry.java +++ b/src/main/java/ch/njol/skript/aliases/MaterialRegistry.java @@ -29,6 +29,7 @@ * Manages Skript's own number -> Material mappings. They are used to save * items as variables. */ +@Deprecated public class MaterialRegistry { /** diff --git a/src/main/java/ch/njol/skript/bukkitutil/block/BlockValues.java b/src/main/java/ch/njol/skript/bukkitutil/block/BlockValues.java index d14cd483e3b..c8f1c4539bb 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/block/BlockValues.java +++ b/src/main/java/ch/njol/skript/bukkitutil/block/BlockValues.java @@ -18,16 +18,14 @@ */ package ch.njol.skript.bukkitutil.block; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.aliases.MatchQuality; +import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilExtendedSerializable; +import org.eclipse.jdt.annotation.Nullable; /** * Contains all data block has that is needed for comparisions. */ -public abstract class BlockValues { +public abstract class BlockValues implements YggdrasilExtendedSerializable { public abstract boolean isDefault(); diff --git a/src/main/java/ch/njol/skript/bukkitutil/block/NewBlockCompat.java b/src/main/java/ch/njol/skript/bukkitutil/block/NewBlockCompat.java index bef3b585528..0610022d1c9 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/block/NewBlockCompat.java +++ b/src/main/java/ch/njol/skript/bukkitutil/block/NewBlockCompat.java @@ -18,8 +18,12 @@ */ package ch.njol.skript.bukkitutil.block; -import java.util.Map; - +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.Aliases; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.aliases.MatchQuality; +import ch.njol.skript.variables.Variables; +import ch.njol.yggdrasil.Fields; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; @@ -34,12 +38,11 @@ import org.bukkit.entity.FallingBlock; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; +import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.Skript; -import ch.njol.skript.aliases.Aliases; -import ch.njol.skript.aliases.ItemType; -import ch.njol.skript.aliases.MatchQuality; +import java.io.StreamCorruptedException; +import java.util.Map; /** * 1.13+ block compat. @@ -48,15 +51,26 @@ public class NewBlockCompat implements BlockCompat { private static class NewBlockValues extends BlockValues { + static { + Variables.yggdrasil.registerSingleClass(NewBlockValues.class, "NewBlockValues"); + } + Material type; BlockData data; boolean isDefault; public NewBlockValues(Material type, BlockData data, boolean isDefault) { + if (type != data.getMaterial()) + throw new IllegalArgumentException("'type' does not match material of 'data'"); this.type = type; this.data = data; this.isDefault = isDefault; } + + /** + * For Serialization - INTERNAL USAGE ONLY!! + */ + private NewBlockValues() { } @Override public boolean isDefault() { @@ -104,7 +118,26 @@ public MatchQuality match(BlockValues other) { return MatchQuality.DIFFERENT; } } - + + @Override + public Fields serialize() { + Fields fields = new Fields(); + fields.putObject("data", data.getAsString()); + fields.putPrimitive("isDefault", isDefault); + return fields; + } + + @Override + public void deserialize(@NonNull Fields fields) throws StreamCorruptedException { + String data = fields.getObject("data", String.class); + boolean isDefault = fields.getPrimitive("isDefault", Boolean.class); + if (data == null) + throw new StreamCorruptedException("'data' is missing."); + + this.data = Bukkit.createBlockData(data); + this.type = this.data.getMaterial(); + this.isDefault = isDefault; + } } private static class NewBlockSetter implements BlockSetter { diff --git a/src/test/skript/tests/misc/item data serialization.sk b/src/test/skript/tests/misc/item data serialization.sk new file mode 100644 index 00000000000..d79e1713e62 --- /dev/null +++ b/src/test/skript/tests/misc/item data serialization.sk @@ -0,0 +1,6 @@ +test "item data serialization": + + # No assertion needed, it will be done internally + set {a::1} to a dirt block named "DIRT" with lore "LORE1" and "LORE2" + + delete {a::1} From c52f0558a6ab3bd250c52997f479239b44eac824 Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Thu, 20 Oct 2022 02:45:54 +0200 Subject: [PATCH 119/619] Fix conversion for list-modifying expressions (#5052) Add getConvertedExpression for list-modifying expressions --- .../skript/expressions/ExprReversedList.java | 26 ++++++++++-- .../skript/expressions/ExprShuffledList.java | 42 ++++++++++++++----- .../skript/expressions/ExprSortedList.java | 38 +++++++++++++---- 3 files changed, 85 insertions(+), 21 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprReversedList.java b/src/main/java/ch/njol/skript/expressions/ExprReversedList.java index 11b8d6b0034..3af5f3d93df 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprReversedList.java +++ b/src/main/java/ch/njol/skript/expressions/ExprReversedList.java @@ -29,13 +29,11 @@ import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.skript.util.LiteralUtils; import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; import java.lang.reflect.Array; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; @Name("Reversed List") @Description("Reverses given list.") @@ -50,6 +48,14 @@ public class ExprReversedList extends SimpleExpression<Object> { @SuppressWarnings("NotNullFieldNotInitialized") private Expression<?> list; + @SuppressWarnings("unused") + public ExprReversedList() { + } + + public ExprReversedList(Expression<?> list) { + this.list = list; + } + @Override public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { list = LiteralUtils.defendExpression(exprs[0]); @@ -66,6 +72,20 @@ protected Object[] get(Event e) { return array; } + @Override + @Nullable + @SuppressWarnings("unchecked") + public <R> Expression<? extends R> getConvertedExpression(Class<R>... to) { + if (CollectionUtils.containsSuperclass(to, getReturnType())) + return (Expression<? extends R>) this; + + Expression<? extends R> convertedList = list.getConvertedExpression(to); + if (convertedList != null) + return (Expression<? extends R>) new ExprReversedList(convertedList); + + return null; + } + private void reverse(Object[] array) { for (int i = 0; i < array.length / 2; i++) { Object temp = array[i]; diff --git a/src/main/java/ch/njol/skript/expressions/ExprShuffledList.java b/src/main/java/ch/njol/skript/expressions/ExprShuffledList.java index 1db4729942f..edeee89739a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprShuffledList.java +++ b/src/main/java/ch/njol/skript/expressions/ExprShuffledList.java @@ -18,15 +18,6 @@ */ package ch.njol.skript.expressions; -import java.lang.reflect.Array; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import ch.njol.skript.util.LiteralUtils; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; @@ -36,7 +27,16 @@ import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.skript.util.LiteralUtils; import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; @Name("Shuffled List") @Description("Shuffles given list randomly. This is done by replacing indices by random numbers in resulting list.") @@ -51,6 +51,14 @@ public class ExprShuffledList extends SimpleExpression<Object> { @SuppressWarnings("NotNullFieldNotInitialized") private Expression<?> list; + @SuppressWarnings("unused") + public ExprShuffledList() { + } + + public ExprShuffledList(Expression<?> list) { + this.list = list; + } + @Override public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { list = LiteralUtils.defendExpression(exprs[0]); @@ -69,7 +77,21 @@ protected Object[] get(Event e) { } @Override - public Class<? extends Object> getReturnType() { + @Nullable + @SuppressWarnings("unchecked") + public <R> Expression<? extends R> getConvertedExpression(Class<R>... to) { + if (CollectionUtils.containsSuperclass(to, getReturnType())) + return (Expression<? extends R>) this; + + Expression<? extends R> convertedList = list.getConvertedExpression(to); + if (convertedList != null) + return (Expression<? extends R>) new ExprShuffledList(convertedList); + + return null; + } + + @Override + public Class<?> getReturnType() { return list.getReturnType(); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprSortedList.java b/src/main/java/ch/njol/skript/expressions/ExprSortedList.java index 2e8f3506a18..a19779c0774 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprSortedList.java +++ b/src/main/java/ch/njol/skript/expressions/ExprSortedList.java @@ -18,13 +18,6 @@ */ package ch.njol.skript.expressions; -import java.lang.reflect.Array; -import java.util.Arrays; - -import ch.njol.skript.util.LiteralUtils; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; @@ -34,7 +27,14 @@ import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.skript.util.LiteralUtils; import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.lang.reflect.Array; +import java.util.Arrays; @Name("Sorted List") @Description({"Sorts given list in natural order. All objects in list must be comparable;", @@ -51,6 +51,14 @@ public class ExprSortedList extends SimpleExpression<Object> { @SuppressWarnings("NotNullFieldNotInitialized") private Expression<?> list; + @SuppressWarnings("unused") + public ExprSortedList() { + } + + public ExprSortedList(Expression<?> list) { + this.list = list; + } + @Override public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { list = LiteralUtils.defendExpression(exprs[0]); @@ -67,7 +75,7 @@ protected Object[] get(Event e) { Object value = unsorted[i]; if (value instanceof Long) { // Hope it fits to the double... - sorted[i] = Double.valueOf(((Long) value).longValue()); + sorted[i] = (double) (Long) value; } else { // No conversion needed sorted[i] = value; @@ -82,6 +90,20 @@ protected Object[] get(Event e) { return sorted; } + @Override + @Nullable + @SuppressWarnings("unchecked") + public <R> Expression<? extends R> getConvertedExpression(Class<R>... to) { + if (CollectionUtils.containsSuperclass(to, getReturnType())) + return (Expression<? extends R>) this; + + Expression<? extends R> convertedList = list.getConvertedExpression(to); + if (convertedList != null) + return (Expression<? extends R>) new ExprSortedList(convertedList); + + return null; + } + @Override public boolean isSingle() { return false; From 145bc59ff69d999883015bc5c3e318cd9607f7ff Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Fri, 21 Oct 2022 00:56:57 +0200 Subject: [PATCH 120/619] Fix variable names with long numbers (#4737) --- .../njol/skript/variables/VariablesMap.java | 89 +++++++++++++------ .../4729-large numbers in list indices.sk | 20 +++++ 2 files changed, 82 insertions(+), 27 deletions(-) create mode 100644 src/test/skript/tests/regressions/4729-large numbers in list indices.sk diff --git a/src/main/java/ch/njol/skript/variables/VariablesMap.java b/src/main/java/ch/njol/skript/variables/VariablesMap.java index 0abad152d00..1b0ca621358 100644 --- a/src/main/java/ch/njol/skript/variables/VariablesMap.java +++ b/src/main/java/ch/njol/skript/variables/VariablesMap.java @@ -18,18 +18,16 @@ */ package ch.njol.skript.variables; +import ch.njol.skript.lang.Variable; +import ch.njol.util.StringUtils; +import org.eclipse.jdt.annotation.Nullable; + import java.util.Comparator; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.lang.Variable; -import ch.njol.skript.util.Utils; -import ch.njol.util.StringUtils; - final class VariablesMap { final static Comparator<String> variableNameComparator = new Comparator<String>() { @@ -45,49 +43,86 @@ public int compare(@Nullable String s1, @Nullable String s2) { int j = 0; boolean lastNumberNegative = false; + boolean afterDecimalPoint = false; while (i < s1.length() && j < s2.length()) { char c1 = s1.charAt(i); char c2 = s2.charAt(j); if ('0' <= c1 && c1 <= '9' && '0' <= c2 && c2 <= '9') { // Numbers/digits are treated differently from other characters. + + // The index after the last digit int i2 = StringUtils.findLastDigit(s1, i); int j2 = StringUtils.findLastDigit(s2, j); - long n1 = Utils.parseLong("" + s1.substring(i, i2)); - long n2 = Utils.parseLong("" + s2.substring(j, j2)); + // Amount of leading zeroes + int z1 = 0; + int z2 = 0; + + // Skip leading zeroes (except for the last if all 0's) + if (!afterDecimalPoint) { + if (c1 == '0') { + while (i < i2 - 1 && s1.charAt(i) == '0') { + i++; + z1++; + } + } + if (c2 == '0') { + while (j < j2 - 1 && s2.charAt(j) == '0') { + j++; + z2++; + } + } + } + // Keep in mind that c1 and c2 may not have the right value (e.g. s1.charAt(i)) for the rest of this block // If the number is prefixed by a '-', it should be treated as negative, thus inverting the order. // If the previous number was negative, and the only thing separating them was a '.', // then this number should also be in inverted order. boolean previousNegative = lastNumberNegative; - lastNumberNegative = i > 0 && s1.charAt(i - 1) == '-'; + // i - z1 contains the first digit, so i - z1 - 1 may contain a `-` indicating this number is negative + lastNumberNegative = i - z1 > 0 && s1.charAt(i - z1 - 1) == '-'; int isPositive = (lastNumberNegative | previousNegative) ? -1 : 1; - if (n1 > n2) - return isPositive; + // Different length numbers (99 > 9) + if (!afterDecimalPoint && i2 - i != j2 - j) + return ((i2 - i) - (j2 - j)) * isPositive; + + // Iterate over the digits + while (i < i2 && j < j2) { + char d1 = s1.charAt(i); + char d2 = s2.charAt(j); + + // If the digits differ, return a value dependent on the sign + if (d1 != d2) + return (d1 - d2) * isPositive; + + i++; + j++; + } - if (n1 < n2) - return -1 * isPositive; + // Different length numbers (1.99 > 1.9) + if (afterDecimalPoint && i2 - i != j2 - j) + return ((i2 - i) - (j2 - j)) * isPositive; - // Represent same number, but different length, indicating leading zeros - if (i2 - i > j2 - j) - return -1; - if (i2 - i < j2 - j) - return 1; + // If the numbers are equal, but either has leading zeroes, + // more leading zeroes is a lesser number (01 < 1) + if (z1 != 0 || z2 != 0) + return (z1 - z2) * isPositive; - i = i2; - j = j2; + afterDecimalPoint = true; } else { // Normal characters - if (c1 > c2) - return 1; - if (c1 < c2) - return -1; - // Reset the last number flag if we're exiting a number. - if (c1 != '.') + if (c1 != c2) + return c1 - c2; + + // Reset the last number flags if we're exiting a number. + if (c1 != '.') { lastNumberNegative = false; + afterDecimalPoint = false; + } + i++; j++; } @@ -99,7 +134,7 @@ public int compare(@Nullable String s1, @Nullable String s2) { return 0; } }; - + final HashMap<String, Object> hashMap = new HashMap<>(); final TreeMap<String, Object> treeMap = new TreeMap<>(); diff --git a/src/test/skript/tests/regressions/4729-large numbers in list indices.sk b/src/test/skript/tests/regressions/4729-large numbers in list indices.sk new file mode 100644 index 00000000000..ed179aefd4a --- /dev/null +++ b/src/test/skript/tests/regressions/4729-large numbers in list indices.sk @@ -0,0 +1,20 @@ +test "large numbers in list indices": + set {_l::999999999999999999999999999999999999999999999} to 3 + assert {_l::888888888888888888888888888888888888888888} isn't set with "Two large numbers in variable indices were treated as equal" + +test "order of numerical list indices": + set {_l::-0.99} to 1 + set {_l::-0.51} to 2 + set {_l::-0.5} to 3 + set {_l::-0.49} to 4 + set {_l::-0.4} to 5 + set {_l::0.0} to 6 + set {_l::0.01} to 7 + set {_l::0.1} to 8 + set {_l::0.12} to 9 + set {_l::0.69} to 10 + + set {_i} to 1 + loop {_l::*}: + loop-value is {_i} + add 1 to {_i} From 0e1455b2152047add53b66b67f820e8a995d66c6 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Thu, 20 Oct 2022 23:28:23 -0400 Subject: [PATCH 121/619] Remove Relational Variables (#5158) Remove relational variable stuff --- .../skript/classes/data/BukkitClasses.java | 19 - .../conditions/CondHasRelationalVariable.java | 102 --- .../expressions/ExprRelationalVariable.java | 273 -------- .../util/ListVariablePersistentDataType.java | 121 ---- .../njol/skript/util/PersistentDataUtils.java | 615 ------------------ .../SingleVariablePersistentDataType.java | 85 --- src/main/resources/lang/default.lang | 1 - .../expressions/ExprRelationalVariable.sk | 149 ----- 8 files changed, 1365 deletions(-) delete mode 100644 src/main/java/ch/njol/skript/conditions/CondHasRelationalVariable.java delete mode 100644 src/main/java/ch/njol/skript/expressions/ExprRelationalVariable.java delete mode 100644 src/main/java/ch/njol/skript/util/ListVariablePersistentDataType.java delete mode 100644 src/main/java/ch/njol/skript/util/PersistentDataUtils.java delete mode 100644 src/main/java/ch/njol/skript/util/SingleVariablePersistentDataType.java delete mode 100644 src/test/skript/tests/syntaxes/expressions/ExprRelationalVariable.sk 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 60c0344659b..ee4be13c3cd 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -1765,25 +1765,6 @@ public String toVariableNameString(GameRule o) { }) ); -// Temporarily disabled until bugs are fixed -// if (Skript.classExists("org.bukkit.persistence.PersistentDataHolder")) { -// Classes.registerClass(new ClassInfo<>(PersistentDataHolder.class, "persistentdataholder") -// .user("persistent data ?holders?") -// .name("Persistent Data Holder") -// .description( -// "Represents something that can have persistent data. " -// + "The following can all hold persistent data: " -// + "entities, projectiles, items, banners, barrels, beds, beehives (1.15), bells, blast furnaces, " -// + "brewing stands, campfires, chests, command blocks, comparators, conduits, mob spawners, " -// + "daylight detectors, dispensers, droppers, enchanting tables, ender chests, end gateways, furnaces, " -// + "hoppers, jigsaw blocks, jukeboxes, lecterns, shulker boxes, signs, skulls, smokers, and structure blocks. " -// + "For the source list, <a href='https://hub.spigotmc.org/javadocs/spigot/org/bukkit/persistence/PersistentDataHolder.html'>see this page</a>." -// ) -// .examples("set persistent data value \"epic\" of player to true") -// .requiredPlugins("1.14 or newer") -// .since("2.5")); -// } - Classes.registerClass(new ClassInfo<>(EnchantmentOffer.class, "enchantmentoffer") .user("enchant[ment][ ]offers?") .name("Enchantment Offer") diff --git a/src/main/java/ch/njol/skript/conditions/CondHasRelationalVariable.java b/src/main/java/ch/njol/skript/conditions/CondHasRelationalVariable.java deleted file mode 100644 index f022db5dde9..00000000000 --- a/src/main/java/ch/njol/skript/conditions/CondHasRelationalVariable.java +++ /dev/null @@ -1,102 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.conditions; - -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.Skript; -import ch.njol.skript.conditions.base.PropertyCondition; -import ch.njol.skript.conditions.base.PropertyCondition.PropertyType; -import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Examples; -import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.RequiredPlugins; -import ch.njol.skript.doc.Since; -import ch.njol.skript.lang.Condition; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.ExpressionList; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.lang.Variable; -import ch.njol.skript.log.ErrorQuality; -import ch.njol.skript.util.PersistentDataUtils; -import ch.njol.util.Kleenean; - -@Name("Has Relational Variable") -@Description({"Checks whether the given relation variables are present on the given holders.", - "See <a href='classes.html#persistentdataholder'>persistent data holder</a> for a list of all holders." -}) -@Examples({"player holds relational variable {isAdmin}", - "player holds relational variable {oldNames::*}"}) -@RequiredPlugins("1.14 or newer") -@Since("2.5") -public class CondHasRelationalVariable extends Condition { - - static { - // Temporarily disabled until bugs are fixed - if (false && Skript.isRunningMinecraft(1, 14)) { - Skript.registerCondition(CondHasRelationalVariable.class, - "%persistentdataholders/itemtypes/blocks% (has|have|holds) [(relational|relation( |-)based) variable[s]] %objects%", - "%persistentdataholders/itemtypes/blocks% (doesn't|does not|do not|don't) (have|hold) [(relational|relation( |-)based) variable[s]] %objects%" - ); - } - } - - @SuppressWarnings("null") - private Expression<Object> holders; - @SuppressWarnings("null") - private ExpressionList<Variable<?>> variables; - - @Override - @SuppressWarnings({"unchecked", "null"}) - public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - ExpressionList<?> exprList = exprs[1] instanceof ExpressionList ? (ExpressionList<?>) exprs[1] : new ExpressionList<>(new Expression<?>[]{exprs[1]}, Object.class, false); - for (Expression<?> expr : exprList.getExpressions()) { - if (!(expr instanceof Variable<?>)) { // Input not a variable - return false; - } else if (((Variable<?>) expr).isLocal()) { // Input is a variable, but it's local - Skript.error("Setting a relational variable using a local variable is not supported." - + " If you are trying to set a value temporarily, consider using metadata", ErrorQuality.SEMANTIC_ERROR - ); - return false; - } - } - variables = (ExpressionList<Variable<?>>) exprList; - holders = (Expression<Object>) exprs[0]; - setNegated(matchedPattern == 1); - return true; - } - - @Override - public boolean check(Event e) { - for (Expression<?> expr : variables.getExpressions()) { - if (!(holders.check(e, holder -> PersistentDataUtils.has(((Variable<?>) expr).getName().toString(e), holder), isNegated()))) - return false; - } - return true; - - } - - @Override - public String toString(@Nullable Event e, boolean debug) { - return PropertyCondition.toString(this, PropertyType.HAVE, e, debug, holders, - "relational variable(s) " + variables.toString(e, debug)); - } - -} diff --git a/src/main/java/ch/njol/skript/expressions/ExprRelationalVariable.java b/src/main/java/ch/njol/skript/expressions/ExprRelationalVariable.java deleted file mode 100644 index 001c8d42084..00000000000 --- a/src/main/java/ch/njol/skript/expressions/ExprRelationalVariable.java +++ /dev/null @@ -1,273 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.expressions; - -import java.lang.reflect.Array; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.Skript; -import ch.njol.skript.classes.Changer.ChangeMode; -import ch.njol.skript.classes.Comparator.Relation; -import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Examples; -import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.RequiredPlugins; -import ch.njol.skript.doc.Since; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.ExpressionList; -import ch.njol.skript.lang.ExpressionType; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.lang.Variable; -import ch.njol.skript.lang.util.SimpleExpression; -import ch.njol.skript.log.ErrorQuality; -import ch.njol.skript.registrations.Comparators; -import ch.njol.skript.registrations.Converters; -import ch.njol.skript.util.PersistentDataUtils; -import ch.njol.skript.util.Utils; -import ch.njol.util.Kleenean; -import ch.njol.util.coll.CollectionUtils; - -@Name("Relational Variable") -@Description({"A relational variable is a variable stored on an entity, projectile, item, or certain blocks, and it can only be accessed using that entity.", - " See <a href='classes.html#persistentdataholder'>persistent data holder</a> for a list of all holders.", - " Relational Variables will persist through a server restart, however, just like normal variables,", - " not all values can be stored permanently (e.g. entities). If the value can't be stored permanently,", - " it will be stored until the server is restarted." -}) -@Examples({"set {isAdmin} of player to true", - "set {oldNames::*} of player to \"Noob_Sl4yer\" and \"Skr1pt_M4st3r\""}) -@RequiredPlugins("1.14 or newer") -@Since("2.5") -@SuppressWarnings({"null", "unchecked"}) -public class ExprRelationalVariable<T> extends SimpleExpression<T> { - - static { - // Temporarily disabled until bugs are fixed - if (false && Skript.isRunningMinecraft(1, 14)) { - Skript.registerExpression(ExprRelationalVariable.class, Object.class, ExpressionType.PROPERTY, - "[(relational|relation( |-)based) variable[s]] %objects% of %persistentdataholders/itemtypes/blocks%" - ); - } - } - - private ExpressionList<Variable<?>> variables; - private Expression<Object> holders; - - private ExprRelationalVariable<?> source; - private Class<? extends T>[] types; - private Class<T> superType; - - public ExprRelationalVariable() { - this(null, (Class<? extends T>) Object.class); - } - - private ExprRelationalVariable(ExprRelationalVariable<?> source, Class<? extends T>... types) { - this.source = source; - if (source != null) { - this.variables = source.variables; - this.holders = source.holders; - } - this.types = types; - this.superType = (Class<T>) Utils.getSuperType(types); - } - - @Override - public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - ExpressionList<?> exprList = exprs[0] instanceof ExpressionList ? (ExpressionList<?>) exprs[0] : new ExpressionList<>(new Expression<?>[]{exprs[0]}, Object.class, false); - for (Expression<?> expr : exprList.getExpressions()) { - if (!(expr instanceof Variable<?>)) { // Input isn't a variable - return false; - } else if (((Variable<?>) expr).isLocal()) { // Input is a variable, but it's local - Skript.error("Setting a relational variable using a local variable is not supported." - + " If you are trying to set a value temporarily, consider using metadata", ErrorQuality.SEMANTIC_ERROR - ); - return false; - } - } - variables = (ExpressionList<Variable<?>>) exprList; - holders = (Expression<Object>) exprs[1]; - return true; - } - - @Override - @Nullable - public T[] get(Event e) { - List<Object> values = new ArrayList<>(); - Object[] holders = this.holders.getArray(e); - for (Expression<?> expr : variables.getExpressions()) { - String varName = ((Variable<?>) expr).getName().toString(e); - if (varName.contains(Variable.SEPARATOR)) { // It's a list - Collections.addAll(values, PersistentDataUtils.getList(varName, holders)); - } else { // It's a single variable - Collections.addAll(values, PersistentDataUtils.getSingle(varName, holders)); - } - } - try { - return Converters.convertArray(values.toArray(), types, superType); - } catch (ClassCastException ex) { - return (T[]) Array.newInstance(superType, 0); - } - } - - @Override - @Nullable - public Class<?>[] acceptChange(ChangeMode mode) { - if (mode == ChangeMode.RESET) - return null; - for (Expression<?> expr : variables.getExpressions()) { - if (!((Variable<?>) expr).isList()) { // It's a single variable - if (mode == ChangeMode.REMOVE_ALL) - return null; - return CollectionUtils.array(Object.class); - } - } - return CollectionUtils.array(Object[].class); - } - - @Override - public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { - if (delta == null && mode != ChangeMode.DELETE) - return; - Object[] holders = this.holders.getArray(e); - switch (mode) { - case SET: - for (Expression<?> expr : variables.getExpressions()) { - Variable<?> var = (Variable<?>) expr; - String varName = var.getName().toString(e); - if (var.isList()) { - varName = varName.replace("*", ""); - for (int i = 1; i <= delta.length; i++) // varName + i = var::i (e.g. exampleList::1, exampleList::2, etc.) - PersistentDataUtils.setList(varName + i, delta[i - 1], holders); - } else if (varName.contains(Variable.SEPARATOR)) { // Specific index of a list - PersistentDataUtils.setList(varName, delta[0], holders); - } else { // It's a single variable - PersistentDataUtils.setSingle(varName, delta[0], holders); - } - } - break; - case DELETE: - for (Expression<?> expr : variables.getExpressions()) { - String varName = ((Variable<?>) expr).getName().toString(e); - if (varName.contains(Variable.SEPARATOR)) { // It's a list - PersistentDataUtils.removeList(varName, holders); - } else { // It's a single variable - PersistentDataUtils.removeSingle(varName, holders); - } - } - break; - case ADD: - for (Expression<?> expr : variables.getExpressions()) { - Variable<?> var = (Variable<?>) expr; - String varName = var.getName().toString(e); - if (var.isList()) { - varName = varName.replace("*", ""); - for (Object holder : holders) { - Set<String> varIndexes = PersistentDataUtils.getListIndexes(varName + "*", holder); - if (varIndexes == null) { - // The list is empty, so we don't need to check for the next available index. - for (int i = 1; i <= delta.length; i++) { - // varName + i = var::i (e.g. exampleList::1, exampleList::2, etc.) - PersistentDataUtils.setList(varName + i, delta[i - 1], holder); - } - } else { - int start = 1 + varIndexes.size(); - for (Object value : delta) { - while (varIndexes.contains(String.valueOf(start))) - start++; - PersistentDataUtils.setList(varName + start, value, holder); - start++; - } - } - } - } else if (delta[0] instanceof Number) { - for (Object holder : holders) { - Object[] n = PersistentDataUtils.getSingle(varName, holder); - double oldValue = 0; - if (n.length != 0 && n[0] instanceof Number) - oldValue = ((Number) n[0]).doubleValue(); - PersistentDataUtils.setSingle(varName, oldValue + ((Number) delta[0]).doubleValue(), holder); - } - } - } - break; - case REMOVE: - case REMOVE_ALL: - for (Expression<?> expr : variables.getExpressions()) { - Variable<?> var = (Variable<?>) expr; - String varName = var.getName().toString(e); - if (var.isList() || mode == ChangeMode.REMOVE_ALL) { - for (Object holder : holders) { - Map<String, Object> varMap = PersistentDataUtils.getListMap(varName, holder); - int sizeBefore = varMap.size(); - if (varMap != null) { - for (Object value : delta) - varMap.entrySet().removeIf(entry -> Relation.EQUAL.is(Comparators.compare(entry.getValue(), value))); - if (sizeBefore != varMap.size()) // It changed so we should set it - PersistentDataUtils.setListMap(varName, varMap, holder); - } - } - } else if (delta[0] instanceof Number) { - for (Object holder : holders) { - Object[] n = PersistentDataUtils.getSingle(varName, holder); - double oldValue = 0; - if (n.length != 0 && n[0] instanceof Number) - oldValue = ((Number) n[0]).doubleValue(); - PersistentDataUtils.setSingle(varName, oldValue - ((Number) delta[0]).doubleValue(), holder); - } - } - } - break; - case RESET: - assert false; - } - } - - @Override - public boolean isSingle() { - return variables.isSingle() && holders.isSingle(); - } - - @Override - public Class<? extends T> getReturnType() { - return superType; - } - - @Override - public <R> Expression<? extends R> getConvertedExpression(Class<R>... to) { - return new ExprRelationalVariable<>(this, to); - } - - @Override - public Expression<?> getSource() { - return source == null ? this : source; - } - - @Override - public String toString(@Nullable Event e, boolean debug) { - return variables.toString(e, debug) + " of " + holders.toString(e, debug); - } - -} diff --git a/src/main/java/ch/njol/skript/util/ListVariablePersistentDataType.java b/src/main/java/ch/njol/skript/util/ListVariablePersistentDataType.java deleted file mode 100644 index 5d2185ded6f..00000000000 --- a/src/main/java/ch/njol/skript/util/ListVariablePersistentDataType.java +++ /dev/null @@ -1,121 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.util; - -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; - -import org.bukkit.persistence.PersistentDataAdapterContext; -import org.bukkit.persistence.PersistentDataType; - -import ch.njol.skript.variables.SerializedVariable.Value; - -/** - * This {@link PersistentDataType} is used for list variables. - * In this case, a list variable is any variable containing "::" (the separator) - * The map's key is the variable's index and the map's value is the index's value. - * With this {@link PersistentDataType}, the NamespacedKey's key is the rest of the list variable. - * e.g. {one::two::three} where "one//two" would be the {@link org.bukkit.NamespacedKey}'s key and "three" the key for the map. - * @see PersistentDataUtils#getNamespacedKey(String) - * @see PersistentDataUtils - * @author APickledWalrus - */ -public final class ListVariablePersistentDataType implements PersistentDataType<byte[], Map<String, Value>> { - - // This is how many bytes an int is. - private final int INT_LENGTH = 4; - - // Charset used for converting bytes and Strings - @SuppressWarnings("null") - private final Charset SERIALIZED_CHARSET = StandardCharsets.UTF_8; - - @Override - public Class<byte[]> getPrimitiveType() { - return byte[].class; - } - - @SuppressWarnings("unchecked") - @Override - public Class<Map<String, Value>> getComplexType() { - return (Class<Map<String, Value>>) (Class<?>) Map.class; - } - - @SuppressWarnings("null") - @Override - public byte[] toPrimitive(Map<String, Value> complex, PersistentDataAdapterContext context) { - int bufferLength = 0; - - for (Entry<String, Value> entry : complex.entrySet()) { - // Store it: index -> type -> data - bufferLength += INT_LENGTH + entry.getKey().getBytes(SERIALIZED_CHARSET).length - + INT_LENGTH + entry.getValue().type.getBytes(SERIALIZED_CHARSET).length - + INT_LENGTH + entry.getValue().data.length; - } - - ByteBuffer bb = ByteBuffer.allocate(bufferLength); - - for (Entry<String, Value> entry : complex.entrySet()) { - byte[] indexBytes = entry.getKey().getBytes(SERIALIZED_CHARSET); - byte[] typeBytes = entry.getValue().type.getBytes(SERIALIZED_CHARSET); - - bb.putInt(indexBytes.length); - bb.put(indexBytes); - - bb.putInt(typeBytes.length); - bb.put(typeBytes); - - bb.putInt(entry.getValue().data.length); - bb.put(entry.getValue().data); - } - - return bb.array(); - } - - @Override - public Map<String, Value> fromPrimitive(byte[] primitive, PersistentDataAdapterContext context) { - ByteBuffer bb = ByteBuffer.wrap(primitive); - - HashMap<String, Value> values = new HashMap<>(); - - while (bb.hasRemaining()) { - int indexLength = bb.getInt(); - byte[] indexBytes = new byte[indexLength]; - bb.get(indexBytes, 0, indexLength); - String index = new String(indexBytes, SERIALIZED_CHARSET); - - int typeLength = bb.getInt(); - byte[] typeBytes = new byte[typeLength]; - bb.get(typeBytes, 0, typeLength); - String type = new String(typeBytes, SERIALIZED_CHARSET); - - int dataLength = bb.getInt(); - byte[] dataBytes = new byte[dataLength]; - bb.get(dataBytes, 0, dataLength); - - values.put(index, new Value(type, dataBytes)); - } - - return values; - } - -} diff --git a/src/main/java/ch/njol/skript/util/PersistentDataUtils.java b/src/main/java/ch/njol/skript/util/PersistentDataUtils.java deleted file mode 100644 index 7feeb02398e..00000000000 --- a/src/main/java/ch/njol/skript/util/PersistentDataUtils.java +++ /dev/null @@ -1,615 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.util; - -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Base64; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - -import org.bukkit.NamespacedKey; -import org.bukkit.block.Block; -import org.bukkit.block.TileState; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.metadata.FixedMetadataValue; -import org.bukkit.metadata.MetadataValue; -import org.bukkit.metadata.Metadatable; -import org.bukkit.persistence.PersistentDataHolder; -import org.bukkit.persistence.PersistentDataType; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.Skript; -import ch.njol.skript.aliases.ItemType; -import ch.njol.skript.conditions.CondHasRelationalVariable; -import ch.njol.skript.expressions.ExprRelationalVariable; -import ch.njol.skript.lang.Variable; -import ch.njol.skript.registrations.Classes; -import ch.njol.skript.variables.SerializedVariable.Value; - -/** - * This class allows Persistent Data to work properly with Skript. - * In Skript, Persistent Data is formatted like variables. - * This looks like: <b>set persistent data {isAdmin} of player to true</b> - * @author APickledWalrus - * @see SingleVariablePersistentDataType - * @see ListVariablePersistentDataType - * @see ExprRelationalVariable - * @see CondHasRelationalVariable - */ -public class PersistentDataUtils { - - private final static PersistentDataType<byte[], Value> SINGLE_VARIABLE_TYPE = new SingleVariablePersistentDataType(); - private final static PersistentDataType<byte[], Map<String, Value>> LIST_VARIABLE_TYPE = new ListVariablePersistentDataType(); - - /* - * General Utility Methods - */ - - /** - * For a {@link Block} or an {@link ItemType}, only parts/some of them are actually a {@link PersistentDataHolder}. - * This gets the part that is a {@link PersistentDataHolder} from those types (e.g. ItemMeta or TileState). - * @param holders A {@link PersistentDataHolder}, a {@link Block}, or an {@link ItemType}. - * @return A map keyed by the unconverted holder with the converted holder as its value. - */ - private static Map<Object, PersistentDataHolder> getConvertedHolders(Object[] holders) { - Map<Object, PersistentDataHolder> actualHolders = new HashMap<>(); - for (Object holder : holders) { - if (holder instanceof PersistentDataHolder) { - actualHolders.put(holder, (PersistentDataHolder) holder); - } else if (holder instanceof ItemType) { - actualHolders.put(holder, ((ItemType) holder).getItemMeta()); - } else if (holder instanceof Block && ((Block) holder).getState() instanceof TileState) { - actualHolders.put(holder, ((TileState) ((Block) holder).getState())); - } - } - return actualHolders; - } - - /** - * This returns a {@link NamespacedKey} from the provided name with Skript as the namespace being used. - * The name will be encoded in Base64 to make sure the key name is valid. - * @param name The name to convert - * @return The created {@link NamespacedKey} - */ - @SuppressWarnings("null") - public static NamespacedKey getNamespacedKey(String name) { - // Encode the name in Base64 to make sure the key name is valid - name = Base64.getEncoder().encodeToString(name.getBytes(StandardCharsets.UTF_8)).replace('=', '_').replace('+', '.'); - return new NamespacedKey(Skript.getInstance(), name); - } - - /* - * Single Variable Modification Methods - */ - - /** - * Gets the Persistent Data Tag's value of the given single variable name from the given holder. - * If the value set was not serializable, it was set under Metadata and is retrieved from Metadata here. - * @param name The name of the single variable (e.g. <b>"myVariable" from {myVariable}</b>) - * @param holders The holder(s) of the Persistent Data Tag. See {@link PersistentDataUtils#getConvertedHolders(Object[])} - * @return The Persistent Data Tag's value from each holder, or an empty list if no values could be retrieved. - * @see PersistentDataUtils#setSingle(String, Object, Object...) - * @see PersistentDataUtils#removeSingle(String, Object...) - */ - public static Object[] getSingle(String name, Object... holders) { - if (name.contains(Variable.SEPARATOR)) // This is a list variable.. - return new Object[0]; - - Map<Object, PersistentDataHolder> actualHolders = getConvertedHolders(holders); - if (actualHolders.isEmpty()) - return new Object[0]; - - name = "!!SINGLE!!" + name; - NamespacedKey key = getNamespacedKey(name); - - List<Object> returnValues = new ArrayList<>(); - for (Entry<Object, PersistentDataHolder> entry : actualHolders.entrySet()) { - Object holder = entry.getKey(); - PersistentDataHolder actualHolder = entry.getValue(); - if (actualHolder.getPersistentDataContainer().has(key, SINGLE_VARIABLE_TYPE)) { - Value value = actualHolder.getPersistentDataContainer().get(key, SINGLE_VARIABLE_TYPE); - if (value != null) - returnValues.add(Classes.deserialize(value.type, value.data)); - } - // Try to get as Metadata instead - if (holder instanceof Metadatable) { - List<MetadataValue> values = ((Metadatable) holder).getMetadata(name); - for (MetadataValue mv : values) { - if (mv.getOwningPlugin() == Skript.getInstance()) // Get the latest value set by Skript - returnValues.add(mv.value()); - } - } - } - - return returnValues.toArray(); - } - - /** - * Sets the Persistent Data Tag from the given name and value for the given holder. - * @param name The name of the single variable (e.g. <b>"myVariable" from {myVariable}</b>) - * @param value The value for the Persistent Data Tag to be set to. - * @param holders The holder(s) of the Persistent Data Tag. See {@link PersistentDataUtils#getConvertedHolders(Object[])} - * If this value is not serializable (see {@link Classes#serialize(Object)}), this value will be set under Metadata. - * @see PersistentDataUtils#getSingle(String, Object...) - * @see PersistentDataUtils#removeSingle(String, Object...) - */ - public static void setSingle(String name, Object value, Object... holders) { - if (name.contains(Variable.SEPARATOR)) // This is a list variable.. - return; - - Map<Object, PersistentDataHolder> actualHolders = getConvertedHolders(holders); - if (actualHolders.isEmpty()) - return; - - name = "!!SINGLE!!" + name; - Value serialized = Classes.serialize(value); - - if (serialized != null) { // Can be serialized, set as Persistent Data - NamespacedKey key = getNamespacedKey(name); - for (Entry<Object, PersistentDataHolder> entry : actualHolders.entrySet()) { - PersistentDataHolder actualHolder = entry.getValue(); - - actualHolder.getPersistentDataContainer().set(key, SINGLE_VARIABLE_TYPE, serialized); - - // This is to store the data on the ItemType or TileState - Object holder = entry.getKey(); - if (holder instanceof ItemType) { - ((ItemType) holder).setItemMeta((ItemMeta) actualHolder); - } else if (actualHolder instanceof TileState) { - ((TileState) actualHolder).update(); - } - } - } else { // Set as Metadata instead - for (Object holder : actualHolders.keySet()) { - if (holder instanceof Metadatable) - ((Metadatable) holder).setMetadata(name, new FixedMetadataValue(Skript.getInstance(), value)); - } - } - } - - /** - * Removes the Persistent Data Tag's value for the given holder(s) from the given name and value. - * This method will check the holder's {@link org.bukkit.persistence.PersistentDataContainer} and Metadata. - * @param name The name of the single variable (e.g. <b>"myVariable" from {myVariable}</b>) - * @param holders The holder(s) of the Persistent Data Tag. See {@link PersistentDataUtils#getConvertedHolders(Object[])} - * @see PersistentDataUtils#getSingle(String, Object...) - * @see PersistentDataUtils#setSingle(String, Object, Object...) - */ - public static void removeSingle(String name, Object... holders) { - if (name.contains(Variable.SEPARATOR)) // This is a list variable.. - return; - - Map<Object, PersistentDataHolder> actualHolders = getConvertedHolders(holders); - if (actualHolders.isEmpty()) - return; - - name = "!!SINGLE!!" + name; - NamespacedKey key = getNamespacedKey(name); - - for (Entry<Object, PersistentDataHolder> entry : actualHolders.entrySet()) { - Object holder = entry.getKey(); - PersistentDataHolder actualHolder = entry.getValue(); - - if (actualHolder.getPersistentDataContainer().has(key, SINGLE_VARIABLE_TYPE)) { // Can be serialized, try to remove Persistent Data - actualHolder.getPersistentDataContainer().remove(key); - - // This is to store the data on the ItemType or TileState - if (holder instanceof ItemType) { - ((ItemType) holder).setItemMeta((ItemMeta) actualHolder); - } else if (actualHolder instanceof TileState) { - ((TileState) actualHolder).update(); - } - } else if (holder instanceof Metadatable) { // Try to remove Metadata instead - ((Metadatable) holder).removeMetadata(name, Skript.getInstance()); - } - } - } - - /* - * List Variable Modification Methods - */ - - /** - * Gets the Persistent Data Tag's value of the given list variable name from the given holder(s). - * This method may return a single value, or multiple, depending on the given name. - * If the value set was not serializable, it was set under Metadata and is retrieved from Metadata here. - * @param name The name of the list variable (e.g. <b>"myList::*" from {myList::*}</b>) - * @param holders The holder(s) of the Persistent Data Tag. See {@link PersistentDataUtils#getConvertedHolders(Object[])} - * @return The Persistent Data Tag's value(s) from the holder, or an empty array if: - * the holder was invalid, the name was invalid, the key was invalid, or if no value(s) could be found. - * @see PersistentDataUtils#setList(String, Object, Object...) - * @see PersistentDataUtils#removeList(String, Object...) - * @see PersistentDataUtils#getListMap(String, Object) - */ - @SuppressWarnings("null") - public static Object[] getList(String name, Object... holders) { - if (!name.contains(Variable.SEPARATOR)) // This is a single variable.. - return new Object[0]; - - // Format the variable for getListMap (e.g. {varName::*}) - // We don't need to worry about the name being invalid, as getListMap will handle that - String listName = name; - if (!name.endsWith("*")) - listName = name.substring(0, name.lastIndexOf(Variable.SEPARATOR) + 2) + "*"; - - List<Object> returnValues = new ArrayList<>(); - for (Object holder : holders) { - Map<String, Object> listVar = getListMap(listName, holder); - if (listVar == null) // One of our values was invalid - continue; - - String index = name.substring(name.lastIndexOf(Variable.SEPARATOR) + Variable.SEPARATOR.length()); - if (index.equals("*")) { // Return all values - returnValues.addAll(listVar.values()); - } else if (listVar.containsKey(index)){ // Return the value under the given index (if it exists) - returnValues.add(listVar.get(index)); - } - } - return returnValues.toArray(); - } - - /** - * Sets the Persistent Data Tag's value for the given holder(s) from the given list variable name and value. - * @param name The name of the list variable (e.g. <b>"myList::*" from {myList::*}</b>) - * If the index of the name is "*", then the index set in the list will be "1". - * To set a different index, format the list variable like normal (e.g. <b>"myList::index" from {myList::index}</b>) - * @param value The value for the Persistent Data Tag to be set to. - * @param holders The holder(s) of the Persistent Data Tag. See {@link PersistentDataUtils#getConvertedHolders(Object[])} - * If this value is not serializable (see {@link Classes#serialize(Object)}), this value will be set under Metadata. - * @see PersistentDataUtils#getList(String, Object...) - * @see PersistentDataUtils#removeSingle(String, Object...) - * @see PersistentDataUtils#setListMap(String, Map, Object) - */ - @SuppressWarnings("unchecked") - public static void setList(String name, Object value, Object... holders) { - if (!name.contains(Variable.SEPARATOR)) // This is a single variable.. - return; - - Map<Object, PersistentDataHolder> actualHolders = getConvertedHolders(holders); - if (actualHolders.isEmpty()) - return; - - Value serialized = Classes.serialize(value); - - String keyName = "!!LIST!!" + name.substring(0, name.lastIndexOf(Variable.SEPARATOR)); - - if (serialized != null) { // Can be serialized, set as Persistent Data - NamespacedKey key = getNamespacedKey(keyName); - for (Entry<Object, PersistentDataHolder> entry : actualHolders.entrySet()) { - PersistentDataHolder actualHolder = entry.getValue(); - - Map<String, Value> values = actualHolder.getPersistentDataContainer().get(key, LIST_VARIABLE_TYPE); - if (values == null) - values = new HashMap<>(); - - String index = name.substring(name.lastIndexOf(Variable.SEPARATOR) + Variable.SEPARATOR.length()); - if (index.equals("*")) { // Clear map and set value - values.clear(); - values.put("1", serialized); - } else { - values.put(index, serialized); - } - - actualHolder.getPersistentDataContainer().set(key, LIST_VARIABLE_TYPE, values); - - // This is to store the data on the ItemType or TileState - Object holder = entry.getKey(); - if (holder instanceof ItemType) { - ((ItemType) holder).setItemMeta((ItemMeta) actualHolder); - } else if (actualHolder instanceof TileState) { - ((TileState) actualHolder).update(); - } - } - } else { // Try to set as Metadata instead - for (Object holder : actualHolders.keySet()) { - if (holder instanceof Metadatable) { - Metadatable mHolder = (Metadatable) holder; - Map<String, Object> mMap = null; - for (MetadataValue mv : mHolder.getMetadata(keyName)) { // Get the latest value set by Skript - if (mv.getOwningPlugin() == Skript.getInstance()) { - mMap = (Map<String, Object>) mv.value(); - break; - } - } - if (mMap == null) - mMap = new HashMap<>(); - - String index = name.substring(name.lastIndexOf(Variable.SEPARATOR) + Variable.SEPARATOR.length()); - if (index.equals("*")) { // Clear map and set value - mMap.clear(); - mMap.put("1", value); - } else { - mMap.put(index, value); - } - - mHolder.setMetadata(keyName, new FixedMetadataValue(Skript.getInstance(), mMap)); - } - } - } - } - - /** - * Removes the value of the Persistent Data Tag of the given name for the given holder(s). - * This method will check the holder's {@link org.bukkit.persistence.PersistentDataContainer} and Metadata. - * @param name The name of the list variable (e.g. <b>"myList::*" from {myList::*}</b>) - * @param holders The holder(s) of the Persistent Data Tag. See {@link PersistentDataUtils#getConvertedHolders(Object[])} - * If the index of the name is "*", then the entire list will be cleared. - * To remove a specific index, format the list variable like normal (e.g. <b>"myList::index" from {myList::index}</b>) - * @see PersistentDataUtils#getList(String, Object...) - * @see PersistentDataUtils#setList(String, Object, Object...) - */ - @SuppressWarnings({"unchecked"}) - public static void removeList(String name, Object... holders) { - if (!name.contains(Variable.SEPARATOR)) // This is a single variable.. - return; - - Map<Object, PersistentDataHolder> actualHolders = getConvertedHolders(holders); - if (actualHolders.isEmpty()) - return; - - String keyName = "!!LIST!!" + name.substring(0, name.lastIndexOf(Variable.SEPARATOR)); - NamespacedKey key = getNamespacedKey(keyName); - - String index = name.substring(name.lastIndexOf(Variable.SEPARATOR) + Variable.SEPARATOR.length()); - - for (Entry<Object, PersistentDataHolder> entry : actualHolders.entrySet()) { - Object holder = entry.getKey(); - PersistentDataHolder actualHolder = entry.getValue(); - - if (actualHolder.getPersistentDataContainer().has(key, LIST_VARIABLE_TYPE)) { - if (index.equals("*")) { // Remove the whole thing - actualHolder.getPersistentDataContainer().remove(key); - } else { // Remove just some - Map<String, Value> values = actualHolder.getPersistentDataContainer().get(key, LIST_VARIABLE_TYPE); - if (values != null) { - values.remove(index); - if (values.isEmpty()) { // No point in storing an empty map. The last value was removed. - actualHolder.getPersistentDataContainer().remove(key); - } else { - actualHolder.getPersistentDataContainer().set(key, LIST_VARIABLE_TYPE, values); - } - } - } - - // This is to store the data on the ItemType or TileState - if (holder instanceof ItemType) { - ((ItemType) holder).setItemMeta((ItemMeta) actualHolder); - } else if (actualHolder instanceof TileState) { - ((TileState) actualHolder).update(); - } - } else if (holder instanceof Metadatable) { // Try metadata - Metadatable mHolder = (Metadatable) holder; - - if (index.equals("*")) { // Remove ALL values - mHolder.removeMetadata(keyName, Skript.getInstance()); - } else { // Remove just one - List<MetadataValue> mValues = mHolder.getMetadata(keyName); - - if (!mValues.isEmpty()) { - Map<String, Object> mMap = null; - for (MetadataValue mv : mValues) { // Get the latest value set by Skript - if (mv.getOwningPlugin() == Skript.getInstance()) { - mMap = (Map<String, Object>) mv.value(); - break; - } - } - - if (mMap != null) { - mMap.remove(index); - if (mMap.isEmpty()) { // No point in storing an empty map. The last value was removed. - mHolder.removeMetadata(keyName, Skript.getInstance()); - } else { - mHolder.setMetadata(keyName, new FixedMetadataValue(Skript.getInstance(), mMap)); - } - } - } - } - } - } - } - - /** - * Returns the map of a list variable. Keyed by variable index. - * This method will check the holder's {@link org.bukkit.persistence.PersistentDataContainer} and Metadata. - * @param name The full list variable (e.g. <b>"myList::*" from {myList::*}</b>) - * @param holder The holder of the Persistent Data Tag. See {@link PersistentDataUtils#getConvertedHolders(Object[])} - * If it is not provided in this format, a null value will be returned. - * @return The map of a list variable, or null if: - * If name was provided in an incorrect format, the holder is invalid, or if no value is set under that name for the holder. - * @see PersistentDataUtils#getList(String, Object...) - * @see PersistentDataUtils#setListMap(String, Map, Object) - */ - @Nullable - @SuppressWarnings({"null", "unchecked"}) - public static Map<String, Object> getListMap(String name, Object holder) { - if (!name.endsWith("*")) // Make sure we're getting a whole list - return null; - - PersistentDataHolder actualHolder = getConvertedHolders(new Object[]{holder}).get(holder); - if (actualHolder == null) - return null; - - String keyName = "!!LIST!!" + name.substring(0, name.lastIndexOf(Variable.SEPARATOR)); - NamespacedKey key = getNamespacedKey(keyName); - - if (actualHolder.getPersistentDataContainer().has(key, LIST_VARIABLE_TYPE)) { // It exists under Persistent Data - Map<String, Object> returnMap = new HashMap<>(); - for (Entry<String, Value> entry : actualHolder.getPersistentDataContainer().get(key, LIST_VARIABLE_TYPE).entrySet()) { - returnMap.put(entry.getKey(), Classes.deserialize(entry.getValue().type, entry.getValue().data)); - } - return returnMap; - } else if (holder instanceof Metadatable) { // Check Metadata - Map<String, Object> mMap = null; - for (MetadataValue mv : ((Metadatable) holder).getMetadata(keyName)) { // Get the latest value set by Skript - if (mv.getOwningPlugin() == Skript.getInstance()) { - mMap = (Map<String, Object>) mv.value(); - break; - } - } - return mMap; - } - - return null; - } - - /** - * Sets the list map of the given holder. - * This map <i>should</i> be gotten from {@link PersistentDataUtils#getListMap(String, Object)} - * This method will check the holder's {@link org.bukkit.persistence.PersistentDataContainer} and Metadata. - * @param name The full list variable (e.g. <b>"myList::*" from {myList::*}</b>) - * If it is not provided in this format, nothing will be set. - * @param varMap The new map for Persistent Data Tag of the given holder. - * If a variable map doesn't already exist in the holder's {@link org.bukkit.persistence.PersistentDataContainer}, - * this map will be set in their Metadata. - * @param holder The holder of the Persistent Data Tag. See {@link PersistentDataUtils#getConvertedHolders(Object[])} - * @see PersistentDataUtils#setList(String, Object, Object...) - * @see PersistentDataUtils#getListMap(String, Object) - */ - public static void setListMap(String name, Map<String, Object> varMap, Object holder) { - if (!name.endsWith("*")) // Make sure we're setting a whole list - return; - - if (varMap.isEmpty()) { // If the map is empty, remove the whole value instead. - removeList(name, holder); - return; - } - - PersistentDataHolder actualHolder = getConvertedHolders(new Object[]{holder}).get(holder); - if (actualHolder == null) - return; - - String keyName = "!!LIST!!" + name.substring(0, name.lastIndexOf(Variable.SEPARATOR)); - NamespacedKey key = getNamespacedKey(keyName); - - if (actualHolder.getPersistentDataContainer().has(key, LIST_VARIABLE_TYPE)) { // It exists under Persistent Data - Map<String, Value> serializedMap = new HashMap<>(); - for (Entry<String, Object> entry : varMap.entrySet()) - serializedMap.put(entry.getKey(), Classes.serialize(entry.getValue())); - actualHolder.getPersistentDataContainer().set(key, LIST_VARIABLE_TYPE, serializedMap); - } else if (holder instanceof Metadatable) { // Check Metadata - ((Metadatable) holder).setMetadata(keyName, new FixedMetadataValue(Skript.getInstance(), varMap)); - } - - // We need to update the data on an ItemType or TileState - if (holder instanceof ItemType) { - ((ItemType) holder).setItemMeta((ItemMeta) actualHolder); - } else if (actualHolder instanceof TileState) { - ((TileState) actualHolder).update(); - } - } - - /** - * This returns the indexes of a stored list. - * Mainly used for the ADD changer in {@link ExprRelationalVariable} - * @param name The full list variable (e.g. <b>"myList::*" from {myList::*}</b>) - * If it is not provided in this format, nothing will be set. - * @param holder The holder of the Persistent Data Tag. See {@link PersistentDataUtils#getConvertedHolders(Object[])} - * @return The set of indexes, or an empty String set. - */ - @Nullable - @SuppressWarnings({"null", "unchecked"}) - public static Set<String> getListIndexes(String name, Object holder) { - if (!name.endsWith("*")) // Make sure we're getting a whole list - return null; - - PersistentDataHolder actualHolder = getConvertedHolders(new Object[]{holder}).get(holder); - if (actualHolder == null) - return null; - - String keyName = "!!LIST!!" + name.substring(0, name.lastIndexOf(Variable.SEPARATOR)); - NamespacedKey key = getNamespacedKey(keyName); - - if (actualHolder.getPersistentDataContainer().has(key, LIST_VARIABLE_TYPE)) { // It exists under Persistent Data - return actualHolder.getPersistentDataContainer().get(key, LIST_VARIABLE_TYPE).keySet(); - } else if (holder instanceof Metadatable) { // Check Metadata - for (MetadataValue mv : ((Metadatable) holder).getMetadata(keyName)) { // Get the latest value set by Skript - if (mv.getOwningPlugin() == Skript.getInstance()) - return ((Map<String, Object>) mv.value()).keySet(); - } - } - - return null; - } - - /* - * Other Utility Methods - */ - - /** - * Whether the given holders have a value under the given name. - * This method will check the holder's {@link org.bukkit.persistence.PersistentDataContainer} and Metadata. - * @param name The name of the variable - * @param holders The holder(s) of the Persistent Data Tag. See {@link PersistentDataUtils#getConvertedHolders(Object[])} - * (e.g. <b>"myVariable" from {myVariable}</b> OR <b>"myList::index" from {myList::index}</b> OR <b>"myList::*" from {myList::*}</b>) - * @return True if the user has something under the Persistent Data Tag from the given name. - * This method will return false if: the holder is invalid, the name is invalid, or if no value could be found. - */ - @SuppressWarnings({"null", "unchecked"}) - public static boolean has(String name, Object... holders) { - Map<Object, PersistentDataHolder> actualHolders = getConvertedHolders(holders); - if (actualHolders.isEmpty()) - return false; - - boolean isList = name.contains(Variable.SEPARATOR); - String keyName = isList ? "!!LIST!!" + name.substring(0, name.lastIndexOf(Variable.SEPARATOR)) : "!!SINGLE!!" + name; - NamespacedKey key = getNamespacedKey(keyName); - - if (isList) { - for (Entry<Object, PersistentDataHolder> entry: actualHolders.entrySet()) { - Object holder = entry.getKey(); - PersistentDataHolder actualHolder = entry.getValue(); - - String index = name.substring(name.lastIndexOf(Variable.SEPARATOR) + Variable.SEPARATOR.length()); - - if (actualHolder.getPersistentDataContainer().has(key, LIST_VARIABLE_TYPE)) { - if (index.equals("*")) // There will NEVER be an empty map stored. - continue; - if (actualHolder.getPersistentDataContainer().get(key, LIST_VARIABLE_TYPE).containsKey(index)) - continue; - return false; - } - if (holder instanceof Metadatable) { - Metadatable mHolder = (Metadatable) holder; - if (!mHolder.hasMetadata(keyName)) { - return false; - } else { // They have something under that key name, check the values. - for (MetadataValue mv : mHolder.getMetadata(keyName)) { // Get the latest value set by Skript - if (mv.getOwningPlugin() == Skript.getInstance() && !((Map<String, Object>) mv.value()).containsKey(index)) - return false; - } - } - } - } - } else { - for (Entry<Object, PersistentDataHolder> entry: actualHolders.entrySet()) { - if (entry.getValue().getPersistentDataContainer().has(key, SINGLE_VARIABLE_TYPE)) - continue; - if (((Metadatable) entry.getKey()).hasMetadata(keyName)) - continue; - return false; - } - } - return true; - } - -} diff --git a/src/main/java/ch/njol/skript/util/SingleVariablePersistentDataType.java b/src/main/java/ch/njol/skript/util/SingleVariablePersistentDataType.java deleted file mode 100644 index eeb60b21e11..00000000000 --- a/src/main/java/ch/njol/skript/util/SingleVariablePersistentDataType.java +++ /dev/null @@ -1,85 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.util; - -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; - -import org.bukkit.persistence.PersistentDataAdapterContext; -import org.bukkit.persistence.PersistentDataType; - -import ch.njol.skript.variables.SerializedVariable.Value; - -/** - * This {@link PersistentDataType} is used for single variables. - * The {@link org.bukkit.NamespacedKey}'s key should be the variable's name. - * {hello} -> "hello" and the {@link Value} is the variable's serialized value. - * @see PersistentDataUtils#getNamespacedKey(String) - * @see PersistentDataUtils - * @author APickledWalrus - */ -public final class SingleVariablePersistentDataType implements PersistentDataType<byte[], Value> { - - // This is how many bytes an int is. - private final int INT_LENGTH = 4; - - // Charset used for converting bytes and Strings - @SuppressWarnings("null") - private final Charset SERIALIZED_CHARSET = StandardCharsets.UTF_8; - - @Override - public Class<byte[]> getPrimitiveType() { - return byte[].class; - } - - @Override - public Class<Value> getComplexType() { - return Value.class; - } - - @SuppressWarnings("null") - @Override - public byte[] toPrimitive(Value complex, PersistentDataAdapterContext context) { - byte[] type = complex.type.getBytes(SERIALIZED_CHARSET); - - ByteBuffer bb = ByteBuffer.allocate(INT_LENGTH + type.length + complex.data.length); - bb.putInt(type.length); - bb.put(type); - bb.put(complex.data); - - return bb.array(); - } - - @Override - public Value fromPrimitive(byte[] primitive, PersistentDataAdapterContext context) { - ByteBuffer bb = ByteBuffer.wrap(primitive); - - int typeLength = bb.getInt(); - byte[] typeBytes = new byte[typeLength]; - bb.get(typeBytes, 0, typeLength); - String type = new String(typeBytes, SERIALIZED_CHARSET); - - byte[] data = new byte[bb.remaining()]; - bb.get(data); - - return new Value(type, data); - } - -} diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index dddb1fdcdc7..22ef2727798 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -1907,7 +1907,6 @@ types: vector: vector¦s @a inventorytype: inventory type¦s @an metadataholder: metadata holder¦s @a - persistentdataholder: persistent data holder¦s @ spawnreason: spawn reason¦s @a cachedservericon: server icon¦s @a difficulty: difficult¦y¦ies @a diff --git a/src/test/skript/tests/syntaxes/expressions/ExprRelationalVariable.sk b/src/test/skript/tests/syntaxes/expressions/ExprRelationalVariable.sk deleted file mode 100644 index e1b6c354473..00000000000 --- a/src/test/skript/tests/syntaxes/expressions/ExprRelationalVariable.sk +++ /dev/null @@ -1,149 +0,0 @@ -test "relational variables expression/condition" when running minecraft "1.99": #temporarily disabling this test - - # Test entities holding relational variables - - # The variable names are odd to test encoding them in Base64 (this is to allow the use of more characters in NamespacedKey names) - - spawn a chicken at spawn of world "world" - set {_chicken} to the last spawned chicken - - assert {_chicken} doesn't have relational variable {cool+io man} with "(Condition) Entities should not have a relational variable before it is set" - set relational variable {cool+io man} of {_chicken} to true - assert relational variable {cool+io man} of {_chicken} = true with "(Expression) The relational variable was set to be true, but it is not" - assert {_chicken} has relational variable {cool+io man} with "(Condition) Entities should have a relational variable if it is set" - clear relational variable {cool+io man} of {_chicken} - assert {_chicken} doesn't have relational variable {cool+io man} with "(Condition) Entities should not have a relational variable after it is cleared" - - delete {_chicken} - - # Test blocks holding a serializable value - - set block at spawn of world "world" to a campfire - set {_block} to block at spawn of world "world" - - assert {_block} doesn't have relational variable {enchantment} with "(Condition) Blocks should not have a relational variable before it is set" - set relational variable {enchantment} of {_block} to sharpness 10 - assert relational variable {enchantment} of {_block} = sharpness 10 with "(Expression) The relational variable was set to be sharpness 10, but it is not" - assert {_block} has relational variable {enchantment} with "(Condition) Blocks should have a relational variable if it is set" - clear relational variable {enchantment} of {_block} - assert {_block} doesn't have relational variable {enchantment} with "(Condition) Blocks should not have a relational variable after it is cleared" - - set block at spawn of world "world" to air - - # Test entities holding a non-serializable value - - spawn a pig at spawn of world "world" - assert last spawned pig doesn't have relational variable {me} with "(Condition) Entities should not have a relational variable before it is set (Non-serializable Test)" - set relational variable {me} of last spawned pig to last spawned pig - assert relational variable {me} of last spawned pig = last spawned pig with "(Expression) The relational variable was set, but it is not (Non-serializable Test)" - assert last spawned pig has relational variable {me} with "(Condition) Entities should have a relational variable if it is set (Non-serializable Test)" - clear relational variable {me} of last spawned pig - assert last spawned pig doesn't have relational variable {me} with "(Condition) Entities should not have a relational variable after it is cleared (Non-serializable Test)" - delete last spawned pig - - # Test using list variables (Serializable) - - set block at spawn of world "world" to a campfire - set {_block} to block at spawn of world "world" - assert {_block} doesn't have relational variable {enchantment::sharpness} with "(Condition) Blocks should not have a relational variable before it is set (List Test)" - set relational variable {enchantment::sharpness} of {_block} to sharpness 10 - assert relational variable {enchantment::sharpness} of {_block} = sharpness 10 with "(Expression) The relational variable was set to be sharpness 10, but it is not (List Test)" - assert relational variable {enchantment::*} of {_block} = sharpness 10 with "(Expression) The full list should be just sharpness 10, but it is not (List Test)" - assert {_block} doesn't have relational variable {enchantment::smite} with "(Condition) Blocks should not have a relational variable if the index was not set (List Test)" - assert {_block} has relational variable {enchantment::sharpness} with "(Condition) Blocks should have a relational variable if it is set (List Test)" - clear relational variable {enchantment::sharpness} of {_block} - assert {_block} doesn't have relational variable {enchantment::sharpness} with "(Condition) Blocks should not have a relational variable after it is cleared (List Test)" - set block at spawn of world "world" to air - - # Test using list variables (Non-serializable) - - spawn a pig at spawn of world "world" - set {_pig} to the last spawned pig - - assert {_pig} doesn't have relational variable {me::1} and {me::2} with "(Condition) Entities should not have a relational variable before it is set (Non-serializable List Test)" - set relational variable {me::1} of {_pig} to {_pig} - assert relational variable {me::1} of {_pig} = {_pig} with "(Expression) The relational variable was set, but it is not (Non-serializable List Test)" - assert relational variable {me::*} of {_pig} = {_pig} with "(Expression) The full list should be just the last spawned pig, but it is not (Non-serializable List Test)" - assert {_pig} doesn't have relational variable {me::2} with "(Condition) Entities should not have a relational variable if the index was not set (Non-serializable List Test)" - assert {_pig} has relational variable {me::1} with "(Condition) Entities should have a relational variable if it is set (Non-serializable List Test)" - clear relational variable {me::1} of {_pig} - assert {_pig} doesn't have relational variable {me::1} and {me::2} with "(Condition) Entities should not have a relational variable after it is cleared (Non-serializable List Test)" - - delete {_pig} - - # Test add, remove, remove all (Serializable) - - set block at spawn of world "world" to a campfire - set {_block} to block at spawn of world "world" - - set relational variable {change::*} of {_block} to {_block} and sharpness 5 - assert relational variable {change::*} of {_block} contains ({_block} and sharpness 5) with "(Expression) The list was set, but it does not contain the set values (Changer Test)" - remove sharpness 5 from relational variable {change::*} of {_block} - assert relational variable {change::*} of {_block} = {_block} with "(Expression) The enchantment was removed from the list, but it is still in it (Changer Test)" - add sharpness 5 and protection 4 to relational variable {change::*} of {_block} - assert relational variable {change::*} of {_block} contains ({_block}, sharpness 5, and protection 4) with "(Expression) Enchanments were added to the list, but they are not present (% relational variable {change::*} of {_block}%) (Changer Test)" - remove all enchantment types from relational variable {change::*} of {_block} - assert relational variable {change::*} of {_block} = {_block} with "(Expression) The enchantments were removed from the list, but it still contains them (% relational variable {change::*} of {_block}%) (Changer Test)" - - set block at spawn of world "world" to air - - # Test add, remove, remove all (Non-serializable) - - spawn a pig at spawn of world "world" - set {_pig} to the last spawned pig - spawn a cow at spawn of world "world" - set {_cow1} to the last spawned cow - spawn a cow at spawn of world "world" - set {_cow2} to the last spawned cow - - set relational variable {change::*} of {_pig} to {_pig} and {_cow1} - assert relational variable {change::*} of {_pig} contains ({_pig} and {_cow1}) with "(Expression) The list was set, but it does not contain the set values (Non-serializable Changer Test)" - remove {_cow1} from relational variable {change::*} of {_pig} - assert relational variable {change::*} of {_pig} = {_pig} with "(Expression) The cow was removed from the list, but it is still in it (Non-serializable Changer Test)" - add {_cow1} and {_cow2} to relational variable {change::*} of {_pig} - assert relational variable {change::*} of {_pig} contains ({_pig}, {_cow1}, and {_cow2}) with "(Expression) Cows were added to the list, but they are not present (Non-serializable Changer Test)" - remove all cows from relational variable {change::*} of {_pig} - assert relational variable {change::*} of {_pig} = {_pig} with "(Expression) The cows were removed from the list, but it still contains them (Non-serializable Changer Test)" - clear relational variable {change::*} of {_pig} - - delete {_pig}, {_cow1}, and {_cow2} - - # Test adding and removing numbers - - set block at spawn of world "world" to a campfire - set {_block} to block at spawn of world "world" - - set relational variable {number} of {_block} to 10 - assert relational variable {number} of {_block} = 10 with "(Expression) The relational variable was set to 10, but it is not (Numbers Test)" - add 10.5 to relational variable {number} of {_block} - assert relational variable {number} of {_block} = 20.5 with "(Expression) 10 was added to the relational variable, but it is not 20.5 (Numbers Test)" - remove 10 from relational variable {number} of {_block} - assert relational variable {number} of {_block} = 10.5 with "(Expression) 10 was removed from the amount, but it is not 10.5 (Numbers Test)" - clear relational variable {number} of {_block} - assert {_block} doesn't have relational variable {number} with "(Condition) The relational variable was removed, but the block still has it (Numbers Test)" - - set block at spawn of world "world" to air - - # Adding to an empty list Test - - spawn a cow at spawn of world "world" - set {_cow} to the last spawned cow - - add {_cow} to relational variable {cows::*} of {_cow} - assert relational variable {cows::*} of {_cow} = {_cow} with "Adding to an empty list should work, but it didn't (Adding to an empty list Test)" - - delete {_cow} - - # Single vs. List Test - # If the player has the relational variable {myTag::1}, then checking if they have {myTag} should return false - - spawn a pig at spawn of world "world" - set {_pig} to the last spawned pig - - set relational variable {myTag::1} of {_pig} to true - assert {_pig} doesn't have relational variable {myTag} with "A holder shouldn't have {myTag} if they only have {myTag::1} (Serializable Single vs. List Test)" - - set relational variable {myTag::1} of {_pig} to {_pig} - assert {_pig} doesn't have relational variable {myTag} with "A holder shouldn't have {myTag} if they only have {myTag::1} (Non-serializable Single vs. List Test)" - - delete {_pig} From b13c29c3381e0cced02c52fddd54580529e080bc Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Fri, 21 Oct 2022 13:31:18 +0300 Subject: [PATCH 122/619] add environment expression (#5070) * add environment expression --- .../skript/classes/data/BukkitClasses.java | 27 +++++++++ .../expressions/ExprWorldEnvironment.java | 59 +++++++++++++++++++ src/main/resources/lang/default.lang | 7 +++ .../expressions/ExprWorldEnvironment.sk | 5 ++ 4 files changed, 98 insertions(+) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprWorldEnvironment.java create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprWorldEnvironment.sk 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 ee4be13c3cd..324cf87d59a 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -39,6 +39,7 @@ import org.bukkit.OfflinePlayer; import org.bukkit.SoundCategory; import org.bukkit.World; +import org.bukkit.World.Environment; import org.bukkit.attribute.Attribute; import org.bukkit.block.Biome; import org.bukkit.block.Block; @@ -1817,5 +1818,31 @@ public String toVariableNameString(Attribute a) { } }) .serializer(new EnumSerializer<>(Attribute.class))); + + EnumUtils<Environment> environments = new EnumUtils<>(Environment.class, "environments"); + Classes.registerClass(new ClassInfo<>(Environment.class, "environment") + .user("(world ?)?environments?") + .name("World Environment") + .description("Represents the environment of a world.") + .usage(environments.getAllNames()) + .since("INSERT VERSION") + .parser(new Parser<Environment>() { + @Override + @Nullable + public Environment parse(String input, ParseContext context) { + return environments.parse(input); + } + + @Override + public String toString(Environment environment, int flags) { + return environments.toString(environment, flags); + } + + @Override + public String toVariableNameString(Environment environment) { + return toString(environment, 0); + } + }) + .serializer(new EnumSerializer<>(Environment.class))); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprWorldEnvironment.java b/src/main/java/ch/njol/skript/expressions/ExprWorldEnvironment.java new file mode 100644 index 00000000000..4f57005de57 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprWorldEnvironment.java @@ -0,0 +1,59 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import org.bukkit.World; +import org.bukkit.World.Environment; +import org.eclipse.jdt.annotation.Nullable; + +@Name("World Environment") +@Description("The environment of a world") +@Examples({ + "if environment of player's world is nether:", + "\tapply fire resistance to player for 10 minutes" +}) +@Since("INSERT VERSION") +public class ExprWorldEnvironment extends SimplePropertyExpression<World, Environment> { + + static { + register(ExprWorldEnvironment.class, Environment.class, "[world] environment", "worlds"); + } + + @Override + @Nullable + public Environment convert(World world) { + return world.getEnvironment(); + } + + @Override + public Class<? extends Environment> getReturnType() { + return Environment.class; + } + + @Override + protected String getPropertyName() { + return "environment"; + } + +} diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 22ef2727798..37648c76641 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -1856,6 +1856,13 @@ attribute types: horse_jump_strength: horse jump strength zombie_spawn_reinforcements: zombie spawn reinforcements +# -- Environments -- +environments: + normal: normal, overworld, the overworld + nether: nether, the nether + the_end: end, the end + custom: custom + # -- Boolean -- boolean: true: diff --git a/src/test/skript/tests/syntaxes/expressions/ExprWorldEnvironment.sk b/src/test/skript/tests/syntaxes/expressions/ExprWorldEnvironment.sk new file mode 100644 index 00000000000..ee17394c4f2 --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprWorldEnvironment.sk @@ -0,0 +1,5 @@ +test "world environment": + assert environment of world "world" is normal with "main overworld was not environment ""normal""" + assert environment of world "world_the_end" is the end with "world_the_end was not environment ""the end""" + set {_environment} to environment of world "world" + assert {_environment} is overworld with "environment of world didn't compare with a variable" From 8d6cf1fa2545bd19bc0b3992c6e02af57662ca9c Mon Sep 17 00:00:00 2001 From: TUCAOEVER <43756134+TUCAOEVER@users.noreply.github.com> Date: Sat, 22 Oct 2022 09:10:15 +0800 Subject: [PATCH 123/619] Fix offhand tool getter in tool change event (#5095) --- src/main/java/ch/njol/skript/expressions/ExprTool.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprTool.java b/src/main/java/ch/njol/skript/expressions/ExprTool.java index 4f32c010960..150cebc97af 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTool.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTool.java @@ -82,7 +82,7 @@ protected Slot[] get(final Event e, final LivingEntity[] source) { @Nullable public Slot get(final LivingEntity ent) { if (!delayed) { - if (e instanceof PlayerItemHeldEvent && ((PlayerItemHeldEvent) e).getPlayer() == ent) { + if (!offHand && e instanceof PlayerItemHeldEvent && ((PlayerItemHeldEvent) e).getPlayer() == ent) { final PlayerInventory i = ((PlayerItemHeldEvent) e).getPlayer().getInventory(); return new InventorySlot(i, getTime() >= 0 ? ((PlayerItemHeldEvent) e).getNewSlot() : ((PlayerItemHeldEvent) e).getPreviousSlot()); } else if (e instanceof PlayerBucketEvent && ((PlayerBucketEvent) e).getPlayer() == ent) { From 7b1e9cf42dd37fadae8b3aa1c275b3df4fbaf3c3 Mon Sep 17 00:00:00 2001 From: DelayedGaming <72163224+DelayedGaming@users.noreply.github.com> Date: Wed, 26 Oct 2022 12:21:00 +0800 Subject: [PATCH 124/619] Resolves EffFireworkLaunch's IAE from out-of-range power number input (#5173) --- src/main/java/ch/njol/skript/effects/EffFireworkLaunch.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/ch/njol/skript/effects/EffFireworkLaunch.java b/src/main/java/ch/njol/skript/effects/EffFireworkLaunch.java index 721be87af15..85b1643012d 100644 --- a/src/main/java/ch/njol/skript/effects/EffFireworkLaunch.java +++ b/src/main/java/ch/njol/skript/effects/EffFireworkLaunch.java @@ -70,6 +70,7 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye protected void execute(Event event) { FireworkEffect[] effects = this.effects.getArray(event); int power = lifetime.getOptionalSingle(event).orElse(1).intValue(); + power = Math.min(127, Math.max(0, power)); for (Location location : locations.getArray(event)) { World world = location.getWorld(); if (world == null) From ed527fd33c086ca35667b1be0822cbec50fd86d8 Mon Sep 17 00:00:00 2001 From: Jay <72931234+Ankoki@users.noreply.github.com> Date: Sat, 29 Oct 2022 09:27:29 +0100 Subject: [PATCH 125/619] Is Gliding Condition (#5145) (#5153) --- .../njol/skript/conditions/CondIsGliding.java | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/main/java/ch/njol/skript/conditions/CondIsGliding.java diff --git a/src/main/java/ch/njol/skript/conditions/CondIsGliding.java b/src/main/java/ch/njol/skript/conditions/CondIsGliding.java new file mode 100644 index 00000000000..352e6986375 --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondIsGliding.java @@ -0,0 +1,49 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.conditions; + +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import org.bukkit.entity.LivingEntity; + +import ch.njol.skript.conditions.base.PropertyCondition; + +@Name("Is Gliding") +@Description("Checks whether a living entity is gliding.") +@Examples("if player is gliding") +@Since("INSERT VERSION") +public class CondIsGliding extends PropertyCondition<LivingEntity> { + + static { + register(CondIsGliding.class, "gliding", "livingentities"); + } + + @Override + public boolean check(LivingEntity entity) { + return entity.isGliding(); + } + + @Override + protected String getPropertyName() { + return "gliding"; + } + +} From 4d1ff4c3af54eb66fce58a77a65046b6f9c4506a Mon Sep 17 00:00:00 2001 From: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> Date: Tue, 8 Nov 2022 13:08:54 -0600 Subject: [PATCH 126/619] Remove Testable interface (#5182) --- .../java/ch/njol/skript/effects/EffEquip.java | 14 +------- .../java/ch/njol/skript/lang/Testable.java | 34 ------------------- 2 files changed, 1 insertion(+), 47 deletions(-) delete mode 100644 src/main/java/ch/njol/skript/lang/Testable.java diff --git a/src/main/java/ch/njol/skript/effects/EffEquip.java b/src/main/java/ch/njol/skript/effects/EffEquip.java index bcb0a9590ec..454bfab267d 100644 --- a/src/main/java/ch/njol/skript/effects/EffEquip.java +++ b/src/main/java/ch/njol/skript/effects/EffEquip.java @@ -46,7 +46,6 @@ import ch.njol.skript.lang.Effect; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.lang.Testable; import ch.njol.util.Kleenean; /** @@ -57,7 +56,7 @@ @Examples({"equip player with diamond helmet", "equip player with all diamond armor"}) @Since("1.0, INSERT VERSION (multiple entities)") -public class EffEquip extends Effect implements Testable { +public class EffEquip extends Effect { static { Skript.registerEffect(EffEquip.class, @@ -178,17 +177,6 @@ else if (BOOTS.isOfType(item)) } } - @Override - public boolean test(Event e) { -// final Iterable<Player> ps = players.getArray(e); -// for (final ItemType t : types.getArray(e)) { -// for (final Player p : ps) { -// //REMIND this + think... -// } -// } - return false; - } - @Override public String toString(@Nullable Event event, boolean debug) { return "equip " + entities.toString(event, debug) + " with " + types.toString(event, debug); diff --git a/src/main/java/ch/njol/skript/lang/Testable.java b/src/main/java/ch/njol/skript/lang/Testable.java deleted file mode 100644 index 68cd58b951f..00000000000 --- a/src/main/java/ch/njol/skript/lang/Testable.java +++ /dev/null @@ -1,34 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.lang; - -import org.bukkit.event.Event; - -/** - * Effects which can be tested implement this interface. - * <p> - * TODO implement this - * - * @author Peter Güttinger - */ -public interface Testable { - - public boolean test(Event e); - -} From 573ec232d8d733ea5c13e55c41736c3ccbd2e5aa Mon Sep 17 00:00:00 2001 From: Kenzie <admin@moderocky.com> Date: Wed, 16 Nov 2022 12:38:24 +0000 Subject: [PATCH 127/619] Update code-conventions.md --- code-conventions.md | 227 ++++++++++++++++++++++++++++---------------- 1 file changed, 146 insertions(+), 81 deletions(-) diff --git a/code-conventions.md b/code-conventions.md index d172db0e268..f79cce9387d 100644 --- a/code-conventions.md +++ b/code-conventions.md @@ -6,41 +6,80 @@ servers; security matters. Following these guidelines is *vital* for all new code. Contributors should also see the dedicated [security document](security.md). -* **NO MALICIOUS CODE** - I think you already knew this - ```java - Runtime.getRuntime().exec("rm -rf /"); // Really BAD - ``` -* **DO NOT** submit secrets (passwords, etc.) to the Git - ```java - String myApiToken = "f6dd0c930676da532136fbef805ef1553ebaca83"; // Really BAD - // Though this is not a real token - can you find what it is? - ``` - - If this happens and somehow gets past code review, you'll have to change - your secrets; we are generally unwilling to 'force push' anything -* Client or server crashes/freezes shouldn't be easy to create from scripts - ```java - if (matchedPattern == 0) - wait(1000); // BAD - ``` - - This is not to say that you can't currently crash the client, but avoid - adding any more ways to cause trouble - - Be especially wary of things people might accidentally use -* Obviously bad ideas (such as MD5 hashing) should not look safe to scripters - ```java - MessageDigest md5 = MessageDigest.getInstance("MD5"); // BAD - // ... although this is a snippet from Skript source code. Legacy stuff! - ``` - - Make it clear when scripts are doing something insecure or dangerous - - Exception: consider twice before breaking API exposed to scripts -* File or network IO, for now, should not be accessible from scripts - ```java - String fileName = file.getSingle(e); // Bad - ``` -* Code contributed must be licensed under GPLv3, by *you* - - Third party code under compatible licenses may be accepted in some cases - - We'll forward any complaints about code licensing to people who added it +Our users have an expectation of security and trust; it is important we make sure our code is safe. + + +### Malicious Code +No contributions should contain malicious code designed to harm the user or their machine. +Nothing should intend to damage or delete system files, the user's personal files or unrelated parts of the Minecraft server. +```java +Runtime.getRuntime().exec("rm -rf /"); // bad, don't do this +``` +While we expect contributors to use common sense, a general rule would be that contributions should not modify or delete + resources that do not belong to Skript or could be expected to be modified by Skript (e.g. minecraft world data). + +Contributions to Skript should not include code or syntax with an easy potential to be exploited for malicious purposes (e.g. syntax to run exec commands, broad access to the filesystem unrelated the minecraft server). +While any code has the _potential_ to be abused or cause accidental damage, we would ideally like to limit this where possible. + + +### Secrets and Personal Data +Contributions should **never** include secret tokens, passwords, personal api keys, github tokens, etc. +This code may be run across tens of thousands of servers, giving all those users access to that private resource. + +While we would expect to catch an accidental password share at the review stage, please do your best not to commit them! +Once a commit is made, the information may be publicly available forever even if deleted by us - if you _do_ commit your password, please change it as quickly as possible. + +Please do not include personal or identifying data (names, emails, addresses, etc.) in contributions. +Your git account will be automatically credited in the contributor record; you do not need to include your name or by-line in comments or documentation. + + +### Server Safety +Contributions should avoid slowing or making the server unstable. +Explicit pauses (`sleep`/`wait`) are almost never appropriate, particularly considering Skript's single-threaded design. + +Easy infinite loop traps should also be avoided where possible. +Please be especially wary of syntax people might accidentally (mis)use. + +I/O operations should **never** be exposed via syntax. \ +This includes but is not limited to: + - File syntax + - Network I/O syntax + - Remote socket syntax + +These operations are natively slow and unsafe (particularly if they involve contacting another machine) +and Skript does not currently have the tools for users to employ them safely (e.g. thread support, error catching, resource closing.) + +That said, some syntax may be required to use I/O operations (e.g. logging to a file.) \ +For contributions including necessary I/O please make sure: + 1. All resource streams are closed **safely** in a `finally` block. + 2. Output does not needlessly block the server thread. + 3. Operations respect other concurrent I/O (make sure resources are available/safe to use, be careful of concurrent access.) + +Operations that access a remote machine will almost never be appropriate for Skript. +With the exception of contacting our own resources (e.g. to check for updates) contributions should include no form of automatic installation/fetching or download of third-party resources. + + +## Licensing + +Code contributed must be licensed under GPLv3, by **you**. +We expect that any code you contribute is either owned by you or you have explicit permission to provide and license it to us. + +Third party code (under a compatible licence) _may_ be accepted in the following cases: + - It is part of a public, freely-available library or resource. + - It is somehow necessary to your contribution, and you have been given permission to include it. + - You have previously co-authored the code, the other contributors have given you permission to include it. + +If we receive complaints regarding the licensing of a contribution we will forward the complaints to you the contributor. + +If you have questions or complaints regarding the licensing or reproduction of a contribution you may contact us (the organisation) or the contributor of that code directly. -## Formatting +If, in the future, we need to relicense contributed code, we will contact all contributors involved. +If we need to remove or alter contributed code due to a licensing issue we will attempt to notify its contributor. + + +## Code Style + +### Formatting * Tabs, no spaces (unless in code imported from other projects) ** No tabs/spaces in empty lines * No trailing whitespace @@ -75,16 +114,19 @@ code. Contributors should also see the dedicated - Effect: init -> execute -> toString - Condition: init -> check -> toString -## Naming -* Classes named in CapitalCamelCase -* Fields and methods named in camelCase - - Final static fields may be named in SCREAMING_SNAKE_CASE -* Localized messages should be named like m_message_name + +### Naming +* Class names are written in `UpperCamelCase` + - The file name should match its primary class name (e.g. `MyClass` goes in `MyClass.java`.) +* Fields and methods named in `camelCase` + - Static constant fields should be named in `UPPER_SNAKE_CASE` +* Localised messages should be named in `lower_snake_case` - And that is the only place where snake_case is acceptable -* Use prefixes where their use has been already estabilished (such as ExprSomeRandomThing) - - Otherwise, use postfixes (such as LoopSection) +* Use prefixes only where their use has been already estabilished (such as `ExprSomeRandomThing`) + - Otherwise, use postfixes where necessary (such as `LoopSection`) + - Please refer to existing files for examples -## Comments +### Comments * Prefer to comment *why* you're doing things instead of how you're doing them - In case the code is particularly complex, though, do both! * Write all comments in readable English @@ -112,56 +154,79 @@ Your comments should look something like these: ``` ## Language Features -* Java 8 source and binary compatibility, even though compiling Skript requires Java 16 + +### Compatibility +* Contributions should maintain Java 8 source/binary compatibility, even though compiling Skript requires Java 16 - Users must not need JRE newer than version 8 * Versions up to and including Java 16 should work too - - Absolutely no dirty deep reflection hacks + - Please avoid using unsafe reflection * It is recommended to make fields final, if they are effectively final - - Performance impact is be minimal, but it makes code cleaner * Local variables and method parameters should not be declared final -* Methods should be declared final only rarely - - Static methods should not *ever* be declared final -* Use @Override where applicable +* Methods should be declared final only where necessary +* Use `@Override` whenever applicable -## Nullness +### Nullness * All fields, method parameters and their return values are non-null by default - Exceptions: Github API JSON mappings, Metrics * When something is nullable, mark it as so -* Only ignore nullness errors when a variable is effectively non-null, but compiler doesn't recognize it - - Most common example is syntax elements, which are not initialized using a constructor +* Only ignore nullness errors when a variable is effectively non-null - if in doubt: check + - Most common example is syntax elements, which are not initialised using a constructor * Use assertions liberally: if you're sure something is not null, assert so to the compiler - Makes finding bugs easier for developers +* Assertions must **not** have side-effects - they may be skipped in real environments * Avoid checking non-null values for nullability - Unless working around buggy addons, it is rarely necessary - This is why ignoring nullness errors is particularly dangerous -## Portability -* **Absolutely no NMS-reliant code** - - No matter *how* it is done, it belongs to addons - - Updating for new Minecraft versions is already painful enough - ```java - import net.minecraft.server.v1_13_1.*; // BAD - ``` -* Target Minecraft versions are written in README - - Skript **must** run on these MC versions, in one way or another -* Supported server implementations are Spigot and Paper - - Paper-specific functionality is acceptable - - Glowstone also works, don't intentionally break it - - CraftBukkit does not work, and please don't waste your time in fixing it -* Avoid referencing Material enum, as it changed significantly in Spigot 1.13 - - Instead, use Aliases.javaItemType - ```java - Material type = Material.DIRT; // Bad - ItemType type = Aliases.javaItemType("dirt"); // Good - ``` - - Exceptions: <code>Material.AIR</code> and <code>Material.STONE</code> - - Air is better way to represent "nothing" than null - - Stone can be used to get dummy <code>ItemMeta</code> -* Do not refer Biome enum directly, because it changed in 1.9 *and* 1.13 - ```java - Biome.MEGA_SPRUCE_TAIGA_HILLS // Bad - 1.8 edition - Biome.MUTATED_REDWOOD_TAIGA_HILLS // Bad - 1.9-1.12 edition - Biome.GIANT_SPRUCE_TAIGA_HILLS // Bad - 1.13 edition - // Yes, these are exactly same biomes. No, I have no idea why this is the case -* Skript must run with assertations enabled; use them in your development environment - - JVM flag <code>-ea</code> is used to enable them +### Assertions + +Skript must run with assertations enabled; use them in your development environment. \ +The JVM flag <code>-ea</code> is used to enable them. + + +## Minecraft Features + +### Avoid Version-Specific Code + +Contributions must **never** rely on the variable `net.minecraft.server` package. +```java +import net.minecraft.server.v1_13_1.*; // BAD +``` +This makes updating between versions excessively difficult, and requires us to write different code for all supported versions. +If a feature is not accessible via the Bukkit API it should not be included in Skript. +> For features requiring 'NMS' please consider making a third-party addon. + +### Support the Target Versions +The target Minecraft versions are written in our README. + +Skript **must** run on these MC versions, in one way or another. +If your contribution breaks compatibility for any of these versions we cannot accept it. + +Please try to make sure contributions are future-safe, to the best of your ability. +Avoid targeting features of specific versions (e.g. via reflection.) + +### Support the Target Servers + +Skript currently supports the 'Spigot' and 'Paper' server implementations. +Contributions must **not** break this cross-compatibility. +> This may change in the future as the server landscape shifts - we will note any changes here and in the README. + +Paper-specific functionality and syntax are acceptable. Please make sure these contributions do not break compatibility with Spigot. + +Skript tentatively supports Glowstone (by coincidence rather than design.) \ +While we are not making an effort to preserve this compatibility, please don't intentionally break it! + +We do not support Bukkit/CraftBukkit. + +### Class Use + +Prefer `Aliases.javaItemType` to the `Material` enum, as this may change in future versions. +```java +Material type = Material.DIRT; // Bad +ItemType type = Aliases.javaItemType("dirt"); // Good +``` + +The exceptions are `Material.AIR`, which is a good way to represent "nothing" +and `Material.STONE` which can be used to get a dummy `ItemMeta`. + +Prefer to avoid referencing the Biome enum directly, since it has changed between versons in the past. From fa3f93ba9a527029e75c201882d361862e3cfd35 Mon Sep 17 00:00:00 2001 From: sovdee <ethansovde@gmail.com> Date: Wed, 16 Nov 2022 15:31:51 -0500 Subject: [PATCH 128/619] Remove cap on freeze time in 1.18 freeze syntax (#5175) --- .../java/ch/njol/skript/expressions/ExprFreezeTicks.java | 2 -- .../skript/tests/syntaxes/expressions/ExprFreezeTicks.sk | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprFreezeTicks.java b/src/main/java/ch/njol/skript/expressions/ExprFreezeTicks.java index e21562e72de..ec7b8b79126 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprFreezeTicks.java +++ b/src/main/java/ch/njol/skript/expressions/ExprFreezeTicks.java @@ -104,8 +104,6 @@ private void setFreezeTicks(Entity entity, int ticks) { //Limit time to between 0 and max if (ticks < 0) ticks = 0; - if (entity.getMaxFreezeTicks() < ticks) - ticks = entity.getMaxFreezeTicks(); // Set new time entity.setFreezeTicks(ticks); } diff --git a/src/test/skript/tests/syntaxes/expressions/ExprFreezeTicks.sk b/src/test/skript/tests/syntaxes/expressions/ExprFreezeTicks.sk index 318f97cfd77..c6e47ec617e 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprFreezeTicks.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprFreezeTicks.sk @@ -6,10 +6,10 @@ test "freeze time" when running minecraft "1.18": add 2 seconds to freeze time of entity assert freeze time of entity is 5 seconds with "freeze time add ##1 failed" add 10 seconds to freeze time of entity - assert freeze time of entity is 7 seconds with "freeze time add ##2 failed" # freeze time should be capped at entity's max freeze time (7 seconds for a cow) + assert freeze time of entity is 15 seconds with "freeze time add ##2 failed" # freeze time should not be capped at entity's max freeze time (7 seconds for a cow) - remove 4 seconds from freeze time of entity - assert freeze time of entity is 3 seconds with "freeze time remove ##1 failed" + remove 6 seconds from freeze time of entity + assert freeze time of entity is 9 seconds with "freeze time remove ##1 failed" remove 10 seconds from freeze time of entity assert freeze time of entity is 0 seconds with "freeze time remove ##2 failed" # freeze time should not be negative delete freeze time of entity From fb9b44bb43b2d0f945c13dd34b4e233ed9518eed Mon Sep 17 00:00:00 2001 From: David Malchin <malchin459@gmail.com> Date: Wed, 16 Nov 2022 22:37:19 +0200 Subject: [PATCH 129/619] Add LivingEntity invisibility syntax (#5185) --- .../skript/conditions/CondIsInvisible.java | 61 ++++++++++++++++ .../ch/njol/skript/effects/EffInvisible.java | 72 +++++++++++++++++++ .../tests/syntaxes/effects/EffInvisible.sk | 7 ++ 3 files changed, 140 insertions(+) create mode 100644 src/main/java/ch/njol/skript/conditions/CondIsInvisible.java create mode 100644 src/main/java/ch/njol/skript/effects/EffInvisible.java create mode 100644 src/test/skript/tests/syntaxes/effects/EffInvisible.sk diff --git a/src/main/java/ch/njol/skript/conditions/CondIsInvisible.java b/src/main/java/ch/njol/skript/conditions/CondIsInvisible.java new file mode 100644 index 00000000000..f5de7b55442 --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondIsInvisible.java @@ -0,0 +1,61 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.conditions; + +import ch.njol.skript.Skript; +import ch.njol.skript.conditions.base.PropertyCondition; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.entity.LivingEntity; + +@Name("Is Invisible") +@Description("Checks whether a living entity is invisible.") +@Examples("target entity is invisible") +@Since("INSERT VERSION") +public class CondIsInvisible extends PropertyCondition<LivingEntity> { + + static { + if (Skript.methodExists(LivingEntity.class, "isInvisible")) + register(CondIsInvisible.class, PropertyType.BE, "(invisible|:visible)", "livingentities"); + } + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + setExpr((Expression<LivingEntity>) exprs[0]); + setNegated(matchedPattern == 1 ^ parseResult.hasTag("visible")); + return true; + } + + @Override + public boolean check(LivingEntity livingEntity) { + return livingEntity.isInvisible(); + } + + @Override + protected String getPropertyName() { + return "invisible"; + } + +} diff --git a/src/main/java/ch/njol/skript/effects/EffInvisible.java b/src/main/java/ch/njol/skript/effects/EffInvisible.java new file mode 100644 index 00000000000..60e0873b715 --- /dev/null +++ b/src/main/java/ch/njol/skript/effects/EffInvisible.java @@ -0,0 +1,72 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.effects; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Make Invisible") +@Description({ + "Makes a living entity visible/invisible. This is not a potion and therefore does not have features such as a time limit or particles.", + "When setting an entity to invisible while using an invisibility potion on it, the potion will be overridden and when it runs out the entity keeps its invisibility." +}) +@Examples("make target entity invisible") +@Since("INSERT VERSION") +public class EffInvisible extends Effect { + + static { + if (Skript.methodExists(LivingEntity.class, "isInvisible")) + Skript.registerEffect(EffInvisible.class, + "make %livingentities% (invisible|not visible)", + "make %livingentities% (visible|not invisible)"); + } + + private boolean invisible; + private Expression<LivingEntity> livingEntities; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + livingEntities = (Expression<LivingEntity>) exprs[0]; + invisible = matchedPattern == 0; + return true; + } + + @Override + protected void execute(Event event) { + for (LivingEntity entity : livingEntities.getArray(event)) + entity.setInvisible(invisible); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "make " + livingEntities.toString(event, debug) + " " + (invisible ? "in" : "") + "visible"; + } + +} diff --git a/src/test/skript/tests/syntaxes/effects/EffInvisible.sk b/src/test/skript/tests/syntaxes/effects/EffInvisible.sk new file mode 100644 index 00000000000..faf43e5602e --- /dev/null +++ b/src/test/skript/tests/syntaxes/effects/EffInvisible.sk @@ -0,0 +1,7 @@ +test "entity invisibility" when running minecraft "1.16.3": + spawn pig at spawn of world "world": + make entity invisible + assert entity is invisible with "failed to make pig invisible" + make entity visible + assert entity is visible with "failed to make pig visible" + delete entity From 1778672d74fdc5ed122e1dc473c28d42dc53c365 Mon Sep 17 00:00:00 2001 From: Abe <69663458+AbeTGT@users.noreply.github.com> Date: Thu, 17 Nov 2022 09:44:12 +1300 Subject: [PATCH 130/619] SimpleEvents: Add EntityJumpEvent (Paper 1.15+) (#5123) --- src/main/java/ch/njol/skript/events/SimpleEvents.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/ch/njol/skript/events/SimpleEvents.java b/src/main/java/ch/njol/skript/events/SimpleEvents.java index 6c9e56adcee..bfeb2df00fb 100644 --- a/src/main/java/ch/njol/skript/events/SimpleEvents.java +++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java @@ -108,6 +108,7 @@ import com.destroystokyo.paper.event.player.PlayerArmorChangeEvent; import com.destroystokyo.paper.event.player.PlayerJumpEvent; import com.destroystokyo.paper.event.server.PaperServerListPingEvent; +import com.destroystokyo.paper.event.entity.EntityJumpEvent; import io.papermc.paper.event.player.PlayerTradeEvent; import ch.njol.skript.Skript; import ch.njol.skript.SkriptEventHandler; @@ -629,6 +630,15 @@ public class SimpleEvents { "\t\tsend \"The trade was somehow denied!\" to player") .since("INSERT VERSION"); } + if (Skript.classExists("com.destroystokyo.paper.event.entity.EntityJumpEvent")) { + Skript.registerEvent("Entity Jump", SimpleEvent.class, EntityJumpEvent.class, "entity jump[ing]") + .description("Called when an entity jumps.") + .requiredPlugins("Paper 1.15.2+") + .examples("on entity jump:", + "\tif entity is a wither skeleton:", + "\t\tcancel event") + .since("INSERT VERSION"); + } if (Skript.classExists("com.destroystokyo.paper.event.block.AnvilDamagedEvent")) { Skript.registerEvent("Anvil Damage", SimpleEvent.class, AnvilDamagedEvent.class, "anvil damag(e|ing)") .description("Called when an anvil is damaged/broken from being used to repair/rename items.", From 2e708e28cd4de59891e4992a1ca18f50a9a82e70 Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Wed, 16 Nov 2022 23:51:52 +0300 Subject: [PATCH 131/619] Add fire ticks expression (#5059) --- .../skript/expressions/ExprFireTicks.java | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprFireTicks.java diff --git a/src/main/java/ch/njol/skript/expressions/ExprFireTicks.java b/src/main/java/ch/njol/skript/expressions/ExprFireTicks.java new file mode 100644 index 00000000000..d801320ec36 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprFireTicks.java @@ -0,0 +1,87 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.util.Timespan; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.entity.Entity; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Entity Fire Burn Duration") +@Description("How much time an entity will be burning for.") +@Examples({"send \"You will stop burning in %fire time of player%\""}) +@Since("INSERT VERSION") +public class ExprFireTicks extends SimplePropertyExpression<Entity, Timespan> { + + static { + register(ExprFireTicks.class, Timespan.class, "(burn[ing]|fire) (time|duration)", "entities"); + } + + @Override + @Nullable + public Timespan convert(Entity entity) { + return Timespan.fromTicks_i(Math.max(entity.getFireTicks(), 0)); + } + + @Override + @Nullable + public Class<?>[] acceptChange(ChangeMode mode) { + return (mode != ChangeMode.REMOVE_ALL) ? CollectionUtils.array(Timespan.class) : null; + } + + @Override + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + Entity[] entities = getExpr().getArray(event); + int change = delta == null ? 0 : (int) ((Timespan) delta[0]).getTicks_i(); + switch (mode) { + case REMOVE: + change = -change; + case ADD: + for (Entity entity : entities) + entity.setFireTicks(entity.getFireTicks() + change); + break; + case DELETE: + case RESET: + case SET: + for (Entity entity : entities) + entity.setFireTicks(change); + break; + default: + assert false; + } + } + + @Override + public Class<? extends Timespan> getReturnType() { + return Timespan.class; + } + + @Override + protected String getPropertyName() { + return "fire time"; + } + +} From d88dab18ecb5760a43a537229462cfa21f0a2e0e Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Wed, 16 Nov 2022 14:41:05 -0700 Subject: [PATCH 132/619] Add exact event checking when converting event-values in last attempt (#5171) --- .../skript/registrations/EventValues.java | 109 +++++++++++------- 1 file changed, 65 insertions(+), 44 deletions(-) diff --git a/src/main/java/ch/njol/skript/registrations/EventValues.java b/src/main/java/ch/njol/skript/registrations/EventValues.java index 7201dc86081..82089400a71 100644 --- a/src/main/java/ch/njol/skript/registrations/EventValues.java +++ b/src/main/java/ch/njol/skript/registrations/EventValues.java @@ -203,7 +203,7 @@ public static <T, E extends Event> T getEventValue(E e, Class<T> c, int time) { * <p> * Can print an error if the event value is blocked for the given event. * - * @param e the event class the getter will be getting from + * @param event the event class the getter will be getting from * @param c type of getter * @param time the event-value's time * @return A getter to get values for a given type of events @@ -211,118 +211,139 @@ public static <T, E extends Event> T getEventValue(E e, Class<T> c, int time) { * @see EventValueExpression#EventValueExpression(Class) */ @Nullable - public static <T, E extends Event> Getter<? extends T, ? super E> getEventValueGetter(Class<E> e, Class<T> c, int time) { - return getEventValueGetter(e, c, time, true); + public static <T, E extends Event> Getter<? extends T, ? super E> getEventValueGetter(Class<E> event, Class<T> c, int time) { + return getEventValueGetter(event, c, time, true); } @SuppressWarnings("unchecked") @Nullable - private static <T, E extends Event> Getter<? extends T, ? super E> getEventValueGetter(Class<E> e, Class<T> c, int time, boolean allowDefault) { + private static <T, E extends Event> Getter<? extends T, ? super E> getEventValueGetter(Class<E> event, Class<T> c, int time, boolean allowDefault) { List<EventValueInfo<?, ?>> eventValues = getEventValuesList(time); // First check for exact classes matching the parameters. - for (EventValueInfo<?, ?> ev : eventValues) { - if (!c.equals(ev.c)) + for (EventValueInfo<?, ?> eventValueInfo : eventValues) { + if (!c.equals(eventValueInfo.c)) continue; - if (!checkExcludes(ev, e)) + if (!checkExcludes(eventValueInfo, event)) return null; - if (ev.event.isAssignableFrom(e)) - return (Getter<? extends T, ? super E>) ev.getter; + if (eventValueInfo.event.isAssignableFrom(event)) + return (Getter<? extends T, ? super E>) eventValueInfo.getter; } // Second check for assignable subclasses. - for (EventValueInfo<?, ?> ev : eventValues) { - if (!c.isAssignableFrom(ev.c)) + for (EventValueInfo<?, ?> eventValueInfo : eventValues) { + if (!c.isAssignableFrom(eventValueInfo.c)) continue; - if (!checkExcludes(ev, e)) + if (!checkExcludes(eventValueInfo, event)) return null; - if (ev.event.isAssignableFrom(e)) - return (Getter<? extends T, ? super E>) ev.getter; - if (!e.isAssignableFrom(ev.event)) + if (eventValueInfo.event.isAssignableFrom(event)) + return (Getter<? extends T, ? super E>) eventValueInfo.getter; + if (!event.isAssignableFrom(eventValueInfo.event)) continue; return new Getter<T, E>() { @Override @Nullable public T get(E event) { - if (!ev.event.isInstance(event)) + if (!eventValueInfo.event.isInstance(event)) return null; - return ((Getter<? extends T, E>) ev.getter).get(event); + return ((Getter<? extends T, E>) eventValueInfo.getter).get(event); } }; } // Most checks have returned before this below is called, but Skript will attempt to convert or find an alternative. // Third check is if the returned object matches the class. - for (EventValueInfo<?, ?> ev : eventValues) { - if (!ev.c.isAssignableFrom(c)) + for (EventValueInfo<?, ?> eventValueInfo : eventValues) { + if (!eventValueInfo.c.isAssignableFrom(c)) continue; - boolean checkInstanceOf = !ev.event.isAssignableFrom(e); - if (checkInstanceOf && !e.isAssignableFrom(ev.event)) + boolean checkInstanceOf = !eventValueInfo.event.isAssignableFrom(event); + if (checkInstanceOf && !event.isAssignableFrom(eventValueInfo.event)) continue; - if (!checkExcludes(ev, e)) + if (!checkExcludes(eventValueInfo, event)) return null; return new Getter<T, E>() { @Override @Nullable public T get(E event) { - if (checkInstanceOf && !ev.event.isInstance(event)) + if (checkInstanceOf && !eventValueInfo.event.isInstance(event)) return null; - Object object = ((Getter<? super T, ? super E>) ev.getter).get(event); + Object object = ((Getter<? super T, ? super E>) eventValueInfo.getter).get(event); if (c.isInstance(object)) return (T) object; return null; } }; } - // Fourth check will attempt to convert the event value to the type. - for (EventValueInfo<?, ?> ev : eventValues) { - boolean checkInstanceOf = !ev.event.isAssignableFrom(e); - if (checkInstanceOf && !e.isAssignableFrom(ev.event)) + // Fourth check will attempt to convert the event value to the requesting type. + // This first for loop will check that the events are exact. See issue #5016 + for (EventValueInfo<?, ?> eventValueInfo : eventValues) { + if (!event.equals(eventValueInfo.event)) continue; - Getter<? extends T, ? super E> getter = (Getter<? extends T, ? super E>) getConvertedGetter(ev, c, checkInstanceOf); + Getter<? extends T, ? super E> getter = (Getter<? extends T, ? super E>) getConvertedGetter(eventValueInfo, c, false); if (getter == null) continue; - if (!checkExcludes(ev, e)) + if (!checkExcludes(eventValueInfo, event)) + return null; + return getter; + } + // This loop will attempt to look for converters assignable to the class of the provided event. + for (EventValueInfo<?, ?> eventValueInfo : eventValues) { + // The requesting event must be assignable to the event value's event. Otherwise it'll throw an error. + if (!event.isAssignableFrom(eventValueInfo.event)) + continue; + + Getter<? extends T, ? super E> getter = (Getter<? extends T, ? super E>) getConvertedGetter(eventValueInfo, c, true); + if (getter == null) + continue; + + if (!checkExcludes(eventValueInfo, event)) return null; return getter; } // If the check should try again matching event values with a 0 time (most event values). if (allowDefault && time != 0) - return getEventValueGetter(e, c, 0, false); + return getEventValueGetter(event, c, 0, false); return null; } /** * Check if the event value states to exclude events. * - * @param ev - * @param e + * @param info The event value info that will be used to grab the value from + * @param event The event class to check the excludes against. * @return boolean if true the event value passes for the events. */ - @SuppressWarnings("unchecked") - private static boolean checkExcludes(EventValueInfo<?, ?> ev, Class<? extends Event> e) { - if (ev.excludes == null) + private static boolean checkExcludes(EventValueInfo<?, ?> info, Class<? extends Event> event) { + if (info.excludes == null) return true; - for (Class<? extends Event> ex : (Class<? extends Event>[]) ev.excludes) { - if (ex.isAssignableFrom(e)) { - Skript.error(ev.excludeErrorMessage); + for (Class<? extends Event> ex : (Class<? extends Event>[]) info.excludes) { + if (ex.isAssignableFrom(event)) { + Skript.error(info.excludeErrorMessage); return false; } } return true; } - + + /** + * Return a converter wrapped in a getter that will grab the requested value by converting from the given event value info. + * + * @param info The event value info that will be used to grab the value from + * @param to The class that the converter will look for to convert the type from the event value to + * @param checkInstanceOf If the event must be an exact instance of the event value info's event or not. + * @return The found Converter wrapped in a Getter object, or null if no Converter was found. + */ @Nullable - private static <E extends Event, F, T> Getter<? extends T, ? super E> getConvertedGetter(EventValueInfo<E, F> i, Class<T> to, boolean checkInstanceOf) { - Converter<? super F, ? extends T> converter = Converters.getConverter(i.c, to); + private static <E extends Event, F, T> Getter<? extends T, ? super E> getConvertedGetter(EventValueInfo<E, F> info, Class<T> to, boolean checkInstanceOf) { + Converter<? super F, ? extends T> converter = Converters.getConverter(info.c, to); if (converter == null) return null; return new Getter<T, E>() { @Override @Nullable public T get(E e) { - if (checkInstanceOf && !i.event.isInstance(e)) + if (checkInstanceOf && !info.event.isInstance(e)) return null; - F f = i.getter.get(e); + F f = info.getter.get(e); if (f == null) return null; return converter.convert(f); From a5d53386c345bcb56daf0081fd7907b189ceed5b Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Thu, 17 Nov 2022 02:02:49 +0300 Subject: [PATCH 133/619] Local Functions (#5139) --- .../ch/njol/skript/doc/Documentation.java | 2 +- .../ch/njol/skript/lang/SkriptParser.java | 8 +- .../njol/skript/lang/function/Function.java | 2 +- .../lang/function/FunctionReference.java | 20 +- .../njol/skript/lang/function/Functions.java | 223 ++++++++---------- .../skript/lang/function/JavaFunction.java | 2 +- .../njol/skript/lang/function/Namespace.java | 94 ++++++-- .../njol/skript/lang/function/Parameter.java | 69 +++++- .../njol/skript/lang/function/Signature.java | 16 +- .../skript/structures/StructFunction.java | 43 ++-- .../syntaxes/structures/StructFunction.sk | 16 ++ 11 files changed, 325 insertions(+), 170 deletions(-) create mode 100644 src/test/skript/tests/syntaxes/structures/StructFunction.sk diff --git a/src/main/java/ch/njol/skript/doc/Documentation.java b/src/main/java/ch/njol/skript/doc/Documentation.java index 8ae1b4b3777..9ae28a437ac 100644 --- a/src/main/java/ch/njol/skript/doc/Documentation.java +++ b/src/main/java/ch/njol/skript/doc/Documentation.java @@ -425,7 +425,7 @@ private static String validateHTML(@Nullable String html, final String baseURL) continue linkLoop; } } else if (s[0].equals("../functions/")) { - if (Functions.getFunction("" + s[1]) != null) + if (Functions.getFunction("" + s[1], null) != null) continue; } else { final int i = CollectionUtils.indexOf(urls, s[0].substring("../".length(), s[0].length() - 1)); diff --git a/src/main/java/ch/njol/skript/lang/SkriptParser.java b/src/main/java/ch/njol/skript/lang/SkriptParser.java index 1679b65d507..7bf5f7e52d2 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptParser.java +++ b/src/main/java/ch/njol/skript/lang/SkriptParser.java @@ -27,8 +27,6 @@ import ch.njol.skript.command.ScriptCommand; import ch.njol.skript.command.ScriptCommandEvent; import ch.njol.skript.expressions.ExprParse; -import org.skriptlang.skript.lang.script.Script; -import org.skriptlang.skript.lang.script.ScriptWarning; import ch.njol.skript.lang.function.ExprFunctionCall; import ch.njol.skript.lang.function.FunctionReference; import ch.njol.skript.lang.function.Functions; @@ -50,10 +48,10 @@ import ch.njol.util.StringUtils; import ch.njol.util.coll.CollectionUtils; import com.google.common.primitives.Booleans; -import org.bukkit.event.EventPriority; -import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.java.JavaPlugin; import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.script.Script; +import org.skriptlang.skript.lang.script.ScriptWarning; import java.util.ArrayList; import java.util.Deque; @@ -897,7 +895,7 @@ public final <T> FunctionReference<T> parseFunction(final @Nullable Class<? exte log.printLog(); return null; } - + String functionName = "" + m.group(1); String args = m.group(2); Expression<?>[] params; diff --git a/src/main/java/ch/njol/skript/lang/function/Function.java b/src/main/java/ch/njol/skript/lang/function/Function.java index cb63bffb028..166191f5f21 100644 --- a/src/main/java/ch/njol/skript/lang/function/Function.java +++ b/src/main/java/ch/njol/skript/lang/function/Function.java @@ -161,7 +161,7 @@ public final T[] execute(Object[][] params) { @Override public String toString() { - return "function " + sign.getName(); + return (sign.local ? "local " : "") + "function " + sign.getName(); } } diff --git a/src/main/java/ch/njol/skript/lang/function/FunctionReference.java b/src/main/java/ch/njol/skript/lang/function/FunctionReference.java index d6d0d10f80d..4d1dc27b40a 100644 --- a/src/main/java/ch/njol/skript/lang/function/FunctionReference.java +++ b/src/main/java/ch/njol/skript/lang/function/FunctionReference.java @@ -25,14 +25,12 @@ import ch.njol.skript.config.Node; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser; -import ch.njol.skript.lang.UnparsedLiteral; import ch.njol.skript.log.RetainingLogHandler; import ch.njol.skript.log.SkriptLogger; import ch.njol.skript.registrations.Classes; import ch.njol.skript.registrations.Converters; import ch.njol.skript.util.LiteralUtils; import ch.njol.util.StringUtils; -import ch.njol.util.coll.CollectionUtils; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @@ -73,7 +71,7 @@ public class FunctionReference<T> { * Definitions of function parameters. */ private final Expression<?>[] parameters; - + /** * Indicates if the caller expects this function to return a single value. * Used for verifying correctness of the function signature. @@ -100,7 +98,10 @@ public class FunctionReference<T> { @Nullable public final String script; - public FunctionReference(String functionName, @Nullable Node node, @Nullable String script, @Nullable Class<? extends T>[] returnTypes, Expression<?>[] params) { + public FunctionReference( + String functionName, @Nullable Node node, @Nullable String script, + @Nullable Class<? extends T>[] returnTypes, Expression<?>[] params + ) { this.functionName = functionName; this.node = node; this.script = script; @@ -116,11 +117,13 @@ public FunctionReference(String functionName, @Nullable Node node, @Nullable Str */ @SuppressWarnings("unchecked") public boolean validateFunction(boolean first) { + if (!first && script == null) + return false; Function<? extends T> previousFunction = function; function = null; SkriptLogger.setNode(node); Skript.debug("Validating function " + functionName); - Signature<?> sign = Functions.getSignature(functionName); + Signature<?> sign = Functions.getSignature(functionName, script); // Check if the requested function exists if (sign == null) { @@ -264,11 +267,10 @@ public boolean resetReturnValue() { protected T[] execute(Event e) { // If needed, acquire the function reference if (function == null) - function = (Function<? extends T>) Functions.getFunction(functionName); - + function = (Function<? extends T>) Functions.getFunction(functionName, script); + if (function == null) { // It might be impossible to resolve functions in some cases! - Skript.error("Couldn't resolve call for '" + functionName + - "'. Be careful when using functions in 'script load' events!"); + Skript.error("Couldn't resolve call for '" + functionName + "'."); return null; // Return nothing and hope it works } 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 6bbf70b2014..78a9a5523e4 100644 --- a/src/main/java/ch/njol/skript/lang/function/Functions.java +++ b/src/main/java/ch/njol/skript/lang/function/Functions.java @@ -18,16 +18,12 @@ */ package ch.njol.skript.lang.function; -import ch.njol.skript.ScriptLoader; import ch.njol.skript.Skript; import ch.njol.skript.SkriptAPIException; import ch.njol.skript.SkriptAddon; import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.config.SectionNode; -import ch.njol.skript.lang.ParseContext; import org.skriptlang.skript.lang.script.Script; -import ch.njol.skript.lang.SkriptParser; -import ch.njol.skript.log.SkriptLogger; import ch.njol.skript.registrations.Classes; import ch.njol.skript.util.Utils; import ch.njol.util.NonNullPair; @@ -39,10 +35,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; -import java.util.Locale; import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * Static methods to work with functions. @@ -101,41 +94,31 @@ public static JavaFunction<?> registerFunction(JavaFunction<?> function) { public final static String functionNamePattern = "[\\p{IsAlphabetic}][\\p{IsAlphabetic}\\p{IsDigit}_]*"; - @SuppressWarnings("null") - private final static Pattern functionPattern = Pattern.compile("function (" + functionNamePattern + ")\\((.*)\\)(?: :: (.+))?", Pattern.CASE_INSENSITIVE), - paramPattern = Pattern.compile("\\s*(.+?)\\s*:(?=[^:]*$)\\s*(.+?)(?:\\s*=\\s*(.+))?\\s*"); - /** * Loads a script function from given node. + * @param script The script the function is declared in * @param node Section node. + * @param signature The signature of the function. Use {@link Functions#parseSignature(String, String, String, String, boolean)} + * to get a new signature instance and {@link Functions#registerSignature(Signature)} to register the signature * @return Script function, or null if something went wrong. */ @Nullable - public static Function<?> loadFunction(Script script, SectionNode node) { - SkriptLogger.setNode(node); - String key = node.getKey(); - String definition = ScriptLoader.replaceOptions(key == null ? "" : key); - assert definition != null; - Matcher m = functionPattern.matcher(definition); - if (!m.matches()) // We have checks when loading the signature, but matches() must be called anyway - return null; // don't error, already done in signature loading - String name = "" + m.group(1); - - Namespace namespace = globalFunctions.get(name); + public static Function<?> loadFunction(Script script, SectionNode node, Signature<?> signature) { + String name = signature.name; + Namespace namespace = getScriptNamespace(script.getConfig().getFileName()); if (namespace == null) { - return null; // Probably duplicate signature; reported before + namespace = globalFunctions.get(name); + if (namespace == null) + return null; // Probably duplicate signature; reported before } - Signature<?> sign = namespace.getSignature(name); - if (sign == null) // Signature parsing failed, probably: null signature - return null; // This has been reported before... - Parameter<?>[] params = sign.parameters; - ClassInfo<?> c = sign.returnType; + Parameter<?>[] params = signature.parameters; + ClassInfo<?> c = signature.returnType; if (Skript.debug() || node.debug()) - Skript.debug("function " + name + "(" + StringUtils.join(params, ", ") + ")" - + (c != null ? " :: " + (sign.isSingle() ? c.getName().getSingular() : c.getName().getPlural()) : "") + ":"); + Skript.debug((signature.local ? "local " : "") + "function " + name + "(" + StringUtils.join(params, ", ") + ")" + + (c != null ? " :: " + (signature.isSingle() ? c.getName().getSingular() : c.getName().getPlural()) : "") + ":"); - Function<?> f = new ScriptFunction<>(sign, script, node); + Function<?> f = new ScriptFunction<>(signature, script, node); // Register the function for signature namespace.addFunction(f); @@ -143,75 +126,23 @@ public static Function<?> loadFunction(Script script, SectionNode node) { return f; } + /** - * Loads the signature of function from given node. + * Parses the signature from the given arguments. * @param script Script file name (<b>might</b> be used for some checks). - * @param node Section node. - * @return Signature of function, or null if something went wrong. + * @param name The name of the function. + * @param args The parameters of the function. See {@link Parameter#parse(String)} + * @param returnType The return type of the function + * @param local If the signature of function is local. + * @return Parsed signature or null if something went wrong. + * @see Functions#registerSignature(Signature) */ @Nullable - public static Signature<?> loadSignature(String script, SectionNode node) { - SkriptLogger.setNode(node); - String key = node.getKey(); - String definition = ScriptLoader.replaceOptions(key == null ? "" : key); - Matcher m = functionPattern.matcher(definition); - if (!m.matches()) - return signError(INVALID_FUNCTION_DEFINITION); - String name = "" + m.group(1); - - // Ensure there are no duplicate functions - if (globalFunctions.containsKey(name)) { - Namespace namespace = globalFunctions.get(name); - if (namespace == javaNamespace) { // Special messages for built-in functions - return signError("Function name '" + name + "' is reserved by Skript"); - } else { - Signature<?> sign = namespace.getSignature(name); - assert sign != null : "globalFunctions points to a wrong namespace"; - return signError("A function named '" + name + "' already exists in script '" + sign.script + "'"); - } - } - - String args = m.group(2); - String returnType = m.group(3); - List<Parameter<?>> params = new ArrayList<>(); - int j = 0; - for (int i = 0; i <= args.length(); i = SkriptParser.next(args, i, ParseContext.DEFAULT)) { - if (i == -1) - return signError("Invalid text/variables/parentheses in the arguments of this function"); - if (i == args.length() || args.charAt(i) == ',') { - String arg = args.substring(j, i); - - if (arg.isEmpty()) // Zero-argument function - break; - - // One ore more arguments for this function - Matcher n = paramPattern.matcher(arg); - if (!n.matches()) - return signError("The " + StringUtils.fancyOrderNumber(params.size() + 1) + " argument's definition is invalid. It should look like 'name: type' or 'name: type = default value'."); - String paramName = "" + n.group(1); - for (Parameter<?> p : params) { - if (p.name.toLowerCase(Locale.ENGLISH).equals(paramName.toLowerCase(Locale.ENGLISH))) - return signError("Each argument's name must be unique, but the name '" + paramName + "' occurs at least twice."); - } - ClassInfo<?> c; - c = Classes.getClassInfoFromUserInput("" + n.group(2)); - NonNullPair<String, Boolean> pl = Utils.getEnglishPlural("" + n.group(2)); - if (c == null) - c = Classes.getClassInfoFromUserInput(pl.getFirst()); - if (c == null) - return signError("Cannot recognise the type '" + n.group(2) + "'"); - String rParamName = paramName.endsWith("*") ? paramName.substring(0, paramName.length() - 3) + - (!pl.getSecond() ? "::1" : "") : paramName; - Parameter<?> p = Parameter.newInstance(rParamName, c, !pl.getSecond(), n.group(3)); - if (p == null) - return null; - params.add(p); - - j = i + 1; - } - if (i == args.length()) - break; - } + @SuppressWarnings({"unchecked", "null"}) + public static Signature<?> parseSignature(String script, String name, String args, @Nullable String returnType, boolean local) { + List<Parameter<?>> parameters = Parameter.parse(args); + if (parameters == null) + return null; // Parse return type if one exists ClassInfo<?> returnClass; @@ -225,23 +156,48 @@ public static Signature<?> loadSignature(String script, SectionNode node) { singleReturn = !p.getSecond(); if (returnClass == null) returnClass = Classes.getClassInfoFromUserInput(p.getFirst()); - if (returnClass == null) { + if (returnClass == null) return signError("Cannot recognise the type '" + returnType + "'"); - } } - @SuppressWarnings({"unchecked", "null"}) - Signature<?> sign = new Signature<>(script, name, - params.toArray(new Parameter[0]), (ClassInfo<Object>) returnClass, singleReturn); + return new Signature<>(script, name, parameters.toArray(new Parameter[0]), local, (ClassInfo<Object>) returnClass, singleReturn); + } - // Register this signature - Namespace.Key namespaceKey = new Namespace.Key(Namespace.Origin.SCRIPT, script); + /** + * Registers the signature. + * @param signature The signature to register. + * @return Signature of function, or null if something went wrong. + * @see Functions#parseSignature(String, String, String, String, boolean) + */ + @Nullable + public static Signature<?> registerSignature(Signature<?> signature) { + // Ensure there are no duplicate functions + if (signature.local) { + Namespace namespace = getScriptNamespace(signature.script); + if (namespace != null && namespace.getSignature(signature.name, true) != null) + return signError("A local function named '" + signature.name + "' already exists in the script"); + } else { + if (globalFunctions.containsKey(signature.name)) { + Namespace namespace = globalFunctions.get(signature.name); + if (namespace == javaNamespace) { // Special messages for built-in functions + return signError("Function name '" + signature.name + "' is reserved by Skript"); + } else { + Signature<?> sign = namespace.getSignature(signature.name, false); + assert sign != null : "globalFunctions points to a wrong namespace"; + return signError("A global function named '" + signature.name + "' already exists in script '" + sign.script + "'"); + } + } + } + + Namespace.Key namespaceKey = new Namespace.Key(Namespace.Origin.SCRIPT, signature.script); Namespace namespace = namespaces.computeIfAbsent(namespaceKey, k -> new Namespace()); - namespace.addSignature(sign); - globalFunctions.put(name, namespace); + namespace.addSignature(signature); + if (!signature.local) + globalFunctions.put(signature.name, namespace); + + Skript.debug("Registered function signature: " + signature.name); - Skript.debug("Registered function signature: " + name); - return sign; + return signature; } /** @@ -268,32 +224,58 @@ private static Signature<?> signError(String error) { /** * Gets a function, if it exists. Note that even if function exists in scripts, - * it might not have been parsed yet. If you want to check for existance, - * then use {@link #getSignature(String)}. + * it might not have been parsed yet. If you want to check for existence, + * then use {@link #getSignature(String, String)}. + * * @param name Name of function. + * @param script The script where the function is declared in. Used to get local functions. * @return Function, or null if it does not exist. */ @Nullable - public static Function<?> getFunction(String name) { - Namespace namespace = globalFunctions.get(name); - if (namespace == null) { - return null; + public static Function<?> getFunction(String name, @Nullable String script) { + Namespace namespace = null; + Function<?> function = null; + if (script != null) { + namespace = getScriptNamespace(script); + if (namespace != null) + function = namespace.getFunction(name); + } + if (namespace == null || function == null) { + namespace = globalFunctions.get(name); + if (namespace == null) + return null; + function = namespace.getFunction(name, false); } - return namespace.getFunction(name); + return function; } /** * Gets a signature of function with given name. * @param name Name of function. + * @param script The script where the function is declared in. Used to get local functions. * @return Signature, or null if function does not exist. */ @Nullable - public static Signature<?> getSignature(String name) { - Namespace namespace = globalFunctions.get(name); - if (namespace == null) { - return null; + public static Signature<?> getSignature(String name, @Nullable String script) { + Namespace namespace = null; + Signature<?> signature = null; + if (script != null) { + namespace = getScriptNamespace(script); + if (namespace != null) + signature = namespace.getSignature(name); } - return namespace.getSignature(name); + if (namespace == null || signature == null) { + namespace = globalFunctions.get(name); + if (namespace == null) + return null; + signature = namespace.getSignature(name, false); + } + return signature; + } + + @Nullable + public static Namespace getScriptNamespace(String script) { + return namespaces.get(new Namespace.Key(Namespace.Origin.SCRIPT, script)); } private final static Collection<FunctionReference<?>> toValidate = new ArrayList<>(); @@ -331,7 +313,8 @@ public static void unregisterFunction(Signature<?> signature) { while (namespaceIterator.hasNext()) { Namespace namespace = namespaceIterator.next(); if (namespace.removeSignature(signature)) { - globalFunctions.remove(signature.getName()); + if (!signature.local) + globalFunctions.remove(signature.getName()); // remove the namespace if it is empty if (namespace.getSignatures().isEmpty()) diff --git a/src/main/java/ch/njol/skript/lang/function/JavaFunction.java b/src/main/java/ch/njol/skript/lang/function/JavaFunction.java index 0a6ba616b0e..11f7a014cb0 100644 --- a/src/main/java/ch/njol/skript/lang/function/JavaFunction.java +++ b/src/main/java/ch/njol/skript/lang/function/JavaFunction.java @@ -32,7 +32,7 @@ public JavaFunction(Signature<T> sign) { } public JavaFunction(String name, Parameter<?>[] parameters, ClassInfo<T> returnType, boolean single) { - this(new Signature<>("none", name, parameters, returnType, single)); + this(new Signature<>("none", name, parameters, false, returnType, single)); } @Override diff --git a/src/main/java/ch/njol/skript/lang/function/Namespace.java b/src/main/java/ch/njol/skript/lang/function/Namespace.java index 5ee8a3f9e34..fecbaf07998 100644 --- a/src/main/java/ch/njol/skript/lang/function/Namespace.java +++ b/src/main/java/ch/njol/skript/lang/function/Namespace.java @@ -92,38 +92,95 @@ public boolean equals(@Nullable Object obj) { return true; } } + + /** + * The key used in the signature and function maps + */ + private static class Info { + + /** + * Name of the function + */ + private final String name; + + /** + * Whether the function is local + */ + private final boolean local; + + public Info(String name, boolean local) { + this.name = name; + this.local = local; + } + + public String getName() { + return name; + } + + public boolean isLocal() { + return local; + } + + @Override + public int hashCode() { + int result = getName().hashCode(); + result = 31 * result + (isLocal() ? 1 : 0); + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof Info)) + return false; + + Info info = (Info) o; + + if (isLocal() != info.isLocal()) + return false; + return getName().equals(info.getName()); + } + } /** * Signatures of known functions. */ - private final Map<String, Signature<?>> signatures; - + private final Map<Info, Signature<?>> signatures; + /** * Known functions. Populated as function bodies are loaded. */ - private final Map<String, Function<?>> functions; - + private final Map<Info, Function<?>> functions; + public Namespace() { this.signatures = new HashMap<>(); this.functions = new HashMap<>(); } + @Nullable + public Signature<?> getSignature(String name, boolean local) { + return signatures.get(new Info(name, local)); + } + @Nullable public Signature<?> getSignature(String name) { - return signatures.get(name); + Signature<?> signature = getSignature(name, true); + return signature == null ? getSignature(name, false) : signature; } - + public void addSignature(Signature<?> sign) { - if (signatures.containsKey(sign.getName())) { + Info info = new Info(sign.getName(), sign.local); + if (signatures.containsKey(info)) throw new IllegalArgumentException("function name already used"); - } - signatures.put(sign.getName(), sign); + signatures.put(info, sign); } public boolean removeSignature(Signature<?> sign) { - if (signatures.get(sign.getName()) != sign) + Info info = new Info(sign.getName(), sign.local); + if (signatures.get(info) != sign) return false; - signatures.remove(sign.getName()); + signatures.remove(info); return true; } @@ -132,14 +189,21 @@ public Collection<Signature<?>> getSignatures() { return signatures.values(); } + @Nullable + public Function<?> getFunction(String name, boolean local) { + return functions.get(new Info(name, local)); + } + @Nullable public Function<?> getFunction(String name) { - return functions.get(name); + Function<?> function = getFunction(name, true); + return function == null ? getFunction(name, false) : function; } - + public void addFunction(Function<?> func) { - assert signatures.containsKey(func.getName()) : "missing signature for function"; - functions.put(func.getName(), func); + Info info = new Info(func.getName(), func.getSignature().local); + assert signatures.containsKey(info) : "missing signature for function"; + functions.put(info, func); } @SuppressWarnings("null") diff --git a/src/main/java/ch/njol/skript/lang/function/Parameter.java b/src/main/java/ch/njol/skript/lang/function/Parameter.java index 0182d71523a..dc61dee6f00 100644 --- a/src/main/java/ch/njol/skript/lang/function/Parameter.java +++ b/src/main/java/ch/njol/skript/lang/function/Parameter.java @@ -26,14 +26,23 @@ import ch.njol.skript.lang.Variable; import ch.njol.skript.log.RetainingLogHandler; import ch.njol.skript.log.SkriptLogger; +import ch.njol.skript.registrations.Classes; import ch.njol.skript.util.LiteralUtils; import ch.njol.skript.util.Utils; +import ch.njol.util.NonNullPair; +import ch.njol.util.StringUtils; import org.eclipse.jdt.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public final class Parameter<T> { - + + public final static Pattern PARAM_PATTERN = Pattern.compile("\\s*(.+?)\\s*:(?=[^:]*$)\\s*(.+?)(?:\\s*=\\s*(.+))?\\s*"); + /** * Name of this parameter. Will be used as name for the local variable * that contains value of it inside function. This is always in lower case; @@ -100,6 +109,64 @@ public static <T> Parameter<T> newInstance(String name, ClassInfo<T> type, boole } return new Parameter<>(name, type, single, d); } + + /** + * Parses function parameters from a string. The string should look something like this: + * <pre>"something: string, something else: number = 12"</pre> + * @param args The string to parse. + * @return The parsed parameters + */ + @Nullable + public static List<Parameter<?>> parse(String args) { + List<Parameter<?>> params = new ArrayList<>(); + int j = 0; + for (int i = 0; i <= args.length(); i = SkriptParser.next(args, i, ParseContext.DEFAULT)) { + if (i == -1) { + Skript.error("Invalid text/variables/parentheses in the arguments of this function"); + return null; + } + if (i == args.length() || args.charAt(i) == ',') { + String arg = args.substring(j, i); + + if (args.isEmpty()) // Zero-argument function + break; + + // One or more arguments for this function + Matcher n = PARAM_PATTERN.matcher(arg); + if (!n.matches()) { + Skript.error("The " + StringUtils.fancyOrderNumber(params.size() + 1) + " argument's definition is invalid. It should look like 'name: type' or 'name: type = default value'."); + return null; + } + String paramName = "" + n.group(1); + for (Parameter<?> p : params) { + if (p.name.toLowerCase(Locale.ENGLISH).equals(paramName.toLowerCase(Locale.ENGLISH))) { + Skript.error("Each argument's name must be unique, but the name '" + paramName + "' occurs at least twice."); + return null; + } + } + ClassInfo<?> c; + c = Classes.getClassInfoFromUserInput("" + n.group(2)); + NonNullPair<String, Boolean> pl = Utils.getEnglishPlural("" + n.group(2)); + if (c == null) + c = Classes.getClassInfoFromUserInput(pl.getFirst()); + if (c == null) { + Skript.error("Cannot recognise the type '" + n.group(2) + "'"); + return null; + } + String rParamName = paramName.endsWith("*") ? paramName.substring(0, paramName.length() - 3) + + (!pl.getSecond() ? "::1" : "") : paramName; + Parameter<?> p = Parameter.newInstance(rParamName, c, !pl.getSecond(), n.group(3)); + if (p == null) + return null; + params.add(p); + + j = i + 1; + } + if (i == args.length()) + break; + } + return params; + } /** * Get the name of this parameter. diff --git a/src/main/java/ch/njol/skript/lang/function/Signature.java b/src/main/java/ch/njol/skript/lang/function/Signature.java index 76cdcfe44df..396806dc7e3 100644 --- a/src/main/java/ch/njol/skript/lang/function/Signature.java +++ b/src/main/java/ch/njol/skript/lang/function/Signature.java @@ -44,7 +44,12 @@ public class Signature<T> { * Parameters taken by this function, in order. */ final Parameter<?>[] parameters; - + + /** + * Whether this function is only accessible in the script it was declared in + */ + final boolean local; + /** * Return type of this function. For functions that return nothing, this * is null. void is never used as return type, because it is not registered @@ -64,10 +69,11 @@ public class Signature<T> { */ final Collection<FunctionReference<?>> calls; - public Signature(String script, String name, Parameter<?>[] parameters, @Nullable final ClassInfo<T> returnType, boolean single) { + Signature(String script, String name, Parameter<?>[] parameters, boolean local, @Nullable final ClassInfo<T> returnType, boolean single) { this.script = script; this.name = name; this.parameters = parameters; + this.local = local; this.returnType = returnType; this.single = single; @@ -86,7 +92,11 @@ public Parameter<?> getParameter(int index) { public Parameter<?>[] getParameters() { return parameters; } - + + public boolean isLocal() { + return local; + } + @Nullable public ClassInfo<T> getReturnType() { return returnType; diff --git a/src/main/java/ch/njol/skript/structures/StructFunction.java b/src/main/java/ch/njol/skript/structures/StructFunction.java index cbc1dd34822..4641d7bbfc7 100644 --- a/src/main/java/ch/njol/skript/structures/StructFunction.java +++ b/src/main/java/ch/njol/skript/structures/StructFunction.java @@ -35,19 +35,21 @@ import org.skriptlang.skript.lang.structure.Structure; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.regex.MatchResult; @Name("Function") @Description({ "Functions are structures that can be executed with arguments/parameters to run code.", - "They can also return a value to the trigger that is executing the function." + "They can also return a value to the trigger that is executing the function.", + "Note that local functions come before global functions execution" }) @Examples({ "function sayMessage(message: text):", "\tbroadcast {_message} # our message argument is available in '{_message}'", - "function giveApple(amount: number) :: item:", + "local function giveApple(amount: number) :: item:", "\treturn {_amount} of apple" }) -@Since("2.2") +@Since("2.2, INSERT VERSION (local functions)") public class StructFunction extends Structure { public static final Priority PRIORITY = new Priority(400); @@ -55,29 +57,43 @@ public class StructFunction extends Structure { private static final AtomicBoolean validateFunctions = new AtomicBoolean(); static { - Skript.registerStructure(StructFunction.class, "function <.+>"); + Skript.registerStructure(StructFunction.class, + "[:local] function <(" + Functions.functionNamePattern + ")\\((.*)\\)(?:\\s*::\\s*(.+))?>" + ); } - @Nullable + @SuppressWarnings("NotNullFieldNotInitialized") private Signature<?> signature; + private boolean local; @Override - public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult, EntryContainer entryContainer) { - return true; + @SuppressWarnings("all") + public boolean init(Literal<?>[] literals, int matchedPattern, ParseResult parseResult, EntryContainer entryContainer) { + local = parseResult.hasTag("local"); + MatchResult regex = parseResult.regexes.get(0); + String name = regex.group(1); + String args = regex.group(2); + String returnType = regex.group(3); + + getParser().setCurrentEvent((local ? "local " : "") + "function", FunctionEvent.class); + + signature = Functions.parseSignature(getParser().getCurrentScript().getConfig().getFileName(), name, args, returnType, local); + + getParser().deleteCurrentEvent(); + return signature != null; } @Override public boolean preLoad() { - signature = Functions.loadSignature(getParser().getCurrentScript().getConfig().getFileName(), getEntryContainer().getSource()); - return true; + return Functions.registerSignature(signature) != null; } @Override public boolean load() { ParserInstance parser = getParser(); - parser.setCurrentEvent("function", FunctionEvent.class); + parser.setCurrentEvent((local ? "local " : "") + "function", FunctionEvent.class); - Functions.loadFunction(parser.getCurrentScript(), getEntryContainer().getSource()); + Functions.loadFunction(parser.getCurrentScript(), getEntryContainer().getSource(), signature); parser.deleteCurrentEvent(); @@ -97,8 +113,7 @@ public boolean postLoad() { @Override public void unload() { - if (signature != null) - Functions.unregisterFunction(signature); + Functions.unregisterFunction(signature); validateFunctions.set(true); } @@ -109,7 +124,7 @@ public Priority getPriority() { @Override public String toString(@Nullable Event e, boolean debug) { - return "function"; + return (local ? "local " : "") + "function"; } } diff --git a/src/test/skript/tests/syntaxes/structures/StructFunction.sk b/src/test/skript/tests/syntaxes/structures/StructFunction.sk new file mode 100644 index 00000000000..c1e20e5ca70 --- /dev/null +++ b/src/test/skript/tests/syntaxes/structures/StructFunction.sk @@ -0,0 +1,16 @@ +function foo() :: boolean: + return true + +function local() :: number: + return 1 + +local function local() :: number: + return 2 + +local function bar() :: boolean: + return true + +test "functions": + assert foo() is true with "function return type failed" + assert local() is not 1 with "global function parsed before local function" + assert bar() is true with "local function didn't execute correctly" From e3f65396b3a94f9f74b35291e711f859f71ae2c6 Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Thu, 17 Nov 2022 11:56:55 +0100 Subject: [PATCH 134/619] Fix EffConnect exception when given players are offline (#5189) --- src/main/java/ch/njol/skript/effects/EffConnect.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffConnect.java b/src/main/java/ch/njol/skript/effects/EffConnect.java index 4a7444fc89a..1a82a6d5b73 100644 --- a/src/main/java/ch/njol/skript/effects/EffConnect.java +++ b/src/main/java/ch/njol/skript/effects/EffConnect.java @@ -59,12 +59,14 @@ public class EffConnect extends Effect { @Override protected void execute(Event e) { String server = this.server.getSingle(e); - Player[] players = this.players.getArray(e); + Player[] players = this.players.stream(e) + .filter(Player::isOnline) + .toArray(Player[]::new); if (server == null || players.length == 0) return; // the message channel is case sensitive so let's fix that - Utils.sendPluginMessage(BUNGEE_CHANNEL, r -> GET_SERVERS_CHANNEL.equals(r.readUTF()), GET_SERVERS_CHANNEL) + Utils.sendPluginMessage(players[0], BUNGEE_CHANNEL, r -> GET_SERVERS_CHANNEL.equals(r.readUTF()), GET_SERVERS_CHANNEL) .thenAccept(response -> { // for loop isn't as pretty a stream, but will be faster with tons of servers for (String validServer : response.readUTF().split(", ")) { From 62e414dc854c46ea085c8bc222707818441ce059 Mon Sep 17 00:00:00 2001 From: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> Date: Fri, 18 Nov 2022 11:31:27 -0600 Subject: [PATCH 135/619] Add ExprNearestEntity (#4677) --- .../skript/expressions/ExprNearestEntity.java | 126 ++++++++++++++++++ .../syntaxes/expressions/ExprNearestEntity.sk | 18 +++ 2 files changed, 144 insertions(+) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprNearestEntity.java create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprNearestEntity.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprNearestEntity.java b/src/main/java/ch/njol/skript/expressions/ExprNearestEntity.java new file mode 100644 index 00000000000..be92bf52536 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprNearestEntity.java @@ -0,0 +1,126 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.entity.EntityData; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import ch.njol.util.StringUtils; +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import ch.njol.skript.lang.util.SimpleExpression; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.Arrays; + +@Name("Nearest Entity") +@Description("Gets the entity nearest to a location or another entity.") +@Examples({ + "kill the nearest pig and cow relative to player", + "teleport player to the nearest cow relative to player", + "teleport player to the nearest entity relative to player", + "", + "on click:", + "\tkill nearest pig" +}) +@Since("INSERT VERSION") +public class ExprNearestEntity extends SimpleExpression<Entity> { + + static { + Skript.registerExpression(ExprNearestEntity.class, Entity.class, ExpressionType.COMBINED, + "[the] nearest %*entitydatas% [[relative] to %entity/location%]", + "[the] %*entitydatas% nearest [to %entity/location%]"); + } + + @SuppressWarnings("NotNullFieldNotInitialized") + private EntityData<?>[] entityDatas; + + @SuppressWarnings("NotNullFieldNotInitialized") + private Expression<?> relativeTo; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + entityDatas = ((Literal<EntityData<?>>) exprs[0]).getArray(); + if (entityDatas.length != Arrays.stream(entityDatas).distinct().count()) { + Skript.error("Entity list may not contain duplicate entities"); + return false; + } + relativeTo = exprs[1]; + return true; + } + + @Override + protected Entity[] get(Event event) { + Object relativeTo = this.relativeTo.getSingle(event); + if (relativeTo == null || (relativeTo instanceof Location && ((Location) relativeTo).getWorld() == null)) + return new Entity[0]; + Entity[] nearestEntities = new Entity[entityDatas.length]; + for (int i = 0; i < nearestEntities.length; i++) { + if (relativeTo instanceof Entity) { + nearestEntities[i] = getNearestEntity(entityDatas[i], ((Entity) relativeTo).getLocation(), (Entity) relativeTo); + } else { + nearestEntities[i] = getNearestEntity(entityDatas[i], (Location) relativeTo, null); + } + } + return nearestEntities; + } + + @Override + public boolean isSingle() { + return entityDatas.length == 1; + } + + @Override + public Class<? extends Entity> getReturnType() { + return entityDatas.length == 1 ? entityDatas[0].getType() : Entity.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "nearest " + StringUtils.join(entityDatas) + " relative to " + relativeTo.toString(event, debug); + } + + @Nullable + private Entity getNearestEntity(EntityData<?> entityData, Location relativePoint, @Nullable Entity excludedEntity) { + Entity nearestEntity = null; + double nearestDistance = -1; + for (Entity entity : relativePoint.getWorld().getEntitiesByClass(entityData.getType())) { + if (entity != excludedEntity && entityData.isInstance(entity)) { + double distance = entity.getLocation().distance(relativePoint); + if (nearestEntity == null || distance < nearestDistance) { + nearestDistance = distance; + nearestEntity = entity; + } + } + } + return nearestEntity; + } + +} diff --git a/src/test/skript/tests/syntaxes/expressions/ExprNearestEntity.sk b/src/test/skript/tests/syntaxes/expressions/ExprNearestEntity.sk new file mode 100644 index 00000000000..529a8f5a699 --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprNearestEntity.sk @@ -0,0 +1,18 @@ +test "nearest entity does not allow duplicate entities": + assert nearest cow and cow relative to {_null} to fail with "duplicate entities were allowed in nearest entity expr" + +# server doesn't spawn the entities until the next tick, and we can't wait in the test system so... commented for now +#test "nearest entity": +# delete all entities +# spawn a cow at spawn of world "world" +# set {_cow} to last spawned cow +# spawn a creeper 10 blocks to the left of {_cow} +# set {_close creeper} to last spawned creeper +# spawn a creeper 15 blocks to the left of {_cow} +# set {_far creeper} to last spawned creeper +# spawn a pig 2 blocks to the left of {_cow} +# set {_really close pig} to last spawned pig +# assert nearest creeper relative to {_cow} is {_close creeper} with "incorrect nearest creeper found" +# assert nearest entity relative to {_cow} is {_pig} with "incorrect nearest entity found" +# assert nearest entity relative to (location of {_cow}) is {_cow} with "incorrect nearest entity found when using location" +# delete all entities From c5fe6f65f6949b926f6a3eb4b44a1f812684e9f2 Mon Sep 17 00:00:00 2001 From: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> Date: Fri, 18 Nov 2022 11:38:07 -0600 Subject: [PATCH 136/619] Improve ExprElement to use iterators (#5183) * Improve ExprElement to use iterators * Improve ExprElement to use iterators * Use colons in pattern * Add xth last element syntax * Add optional to * Add trailing new line --- .../njol/skript/expressions/ExprElement.java | 88 ++++++++++++------- .../tests/syntaxes/expressions/ExprElement.sk | 12 +++ 2 files changed, 70 insertions(+), 30 deletions(-) create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprElement.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprElement.java b/src/main/java/ch/njol/skript/expressions/ExprElement.java index 2daecf559bb..9ba78ee8f16 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprElement.java +++ b/src/main/java/ch/njol/skript/expressions/ExprElement.java @@ -32,23 +32,29 @@ import ch.njol.util.Kleenean; import ch.njol.util.StringUtils; import ch.njol.util.coll.CollectionUtils; +import com.google.common.collect.Iterators; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; import java.lang.reflect.Array; +import java.util.Iterator; @Name("Element of") @Description({"The first, last or a random element of a set, e.g. a list variable.", "See also: <a href='#ExprRandom'>random</a>"}) @Examples("give a random element out of {free items::*} to the player") -@Since("2.0") +@Since("2.0, INSERT VERSION (relative to last element)") public class ExprElement extends SimpleExpression<Object> { static { - Skript.registerExpression(ExprElement.class, Object.class, ExpressionType.PROPERTY, "(-1¦[the] first|1¦[the] last|0¦[a] random|2¦%-number%(st|nd|rd|th)) element [out] of %objects%"); + Skript.registerExpression(ExprElement.class, Object.class, ExpressionType.PROPERTY, "(0:[the] first|1:[the] last|2:[a] random|3:[the] %-number%(st|nd|rd|th)|4:[the] %-number%(st|nd|rd|th) [to] last) element [out] of %objects%"); } - private int element; + private enum ElementType { + FIRST, LAST, RANDOM, ORDINAL, TAIL_END_ORDINAL + } + + private ElementType type; private Expression<?> expr; @@ -58,34 +64,56 @@ public class ExprElement extends SimpleExpression<Object> { @Override @SuppressWarnings("unchecked") public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - expr = LiteralUtils.defendExpression(exprs[1]); - number = (Expression<Number>) exprs[0]; - element = parseResult.mark; + expr = LiteralUtils.defendExpression(exprs[2]); + type = ElementType.values()[parseResult.mark]; + number = (Expression<Number>) (type == ElementType.ORDINAL ? exprs[0]: exprs[1]); return LiteralUtils.canInitSafely(expr); } @Override @Nullable - protected Object[] get(Event e) { - Object[] os = expr.getArray(e); - if (os.length == 0) + protected Object[] get(Event event) { + Iterator<?> iter = expr.iterator(event); + if (iter == null || !iter.hasNext()) return null; - Object o; - if (element == -1) { - o = os[0]; - } else if (element == 1) { - o = os[os.length - 1]; - } else if (element == 2) { - Number number = this.number.getSingle(e); - if (number == null || number.intValue() - 1 >= os.length || number.intValue() - 1 < 0) - return null; - o = os[number.intValue() - 1]; - } else { - o = CollectionUtils.getRandom(os); + Object element = null; + switch (type) { + case FIRST: + element = iter.next(); + break; + case LAST: + element = Iterators.getLast(iter); + break; + case ORDINAL: + assert this.number != null; + Number number = this.number.getSingle(event); + if (number == null) + return null; + try { + element = Iterators.get(iter, number.intValue() - 1); + } catch (IndexOutOfBoundsException exception) { + return null; + } + break; + case RANDOM: + Object[] allIterValues = Iterators.toArray(iter, Object.class); + element = CollectionUtils.getRandom(allIterValues); + break; + case TAIL_END_ORDINAL: + allIterValues = Iterators.toArray(iter, Object.class); + assert this.number != null; + number = this.number.getSingle(event); + if (number == null) + return null; + int ordinal = number.intValue(); + if (ordinal <= 0 || ordinal > allIterValues.length) + return null; + element = allIterValues[allIterValues.length - ordinal]; + break; } - Object[] r = (Object[]) Array.newInstance(getReturnType(), 1); - r[0] = o; - return r; + Object[] elementArray = (Object[]) Array.newInstance(getReturnType(), 1); + elementArray[0] = element; + return elementArray; } @Override @@ -97,7 +125,7 @@ public <R> Expression<? extends R> getConvertedExpression(Class<R>... to) { return null; ExprElement exprElement = new ExprElement(); - exprElement.element = this.element; + exprElement.type = this.type; exprElement.expr = convExpr; exprElement.number = this.number; return (Expression<? extends R>) exprElement; @@ -116,17 +144,17 @@ public Class<?> getReturnType() { @Override public String toString(@Nullable Event e, boolean debug) { String prefix; - switch (element) { - case -1: + switch (type) { + case FIRST: prefix = "the first"; break; - case 1: + case LAST: prefix = "the last"; break; - case 0: + case RANDOM: prefix = "a random"; break; - case 2: + case ORDINAL: assert number != null; prefix = "the "; // Proper ordinal number diff --git a/src/test/skript/tests/syntaxes/expressions/ExprElement.sk b/src/test/skript/tests/syntaxes/expressions/ExprElement.sk new file mode 100644 index 00000000000..1cfb675595f --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprElement.sk @@ -0,0 +1,12 @@ +test "ExprElement": + set {_list::*} to "foo", "bar" and "foobar" + assert first element of {_list::*} is "foo" with "Incorrect first element" + assert last element of {_list::*} is "foobar" with "Incorrect last element" + assert 2nd element of {_list::*} is "bar" with "Incorrect 2nd element" + assert {_list::*} contains (random element of {_list::*}) with "Incorrect random element" + assert 2nd to last element of {_list::*} is "bar" with "Incorrect 2nd last element" + assert 3rd last element of {_list::*} is "foo" with "Incorrect 3rd last element" + assert 100th last element of {_list::*} is not set with "Incorrect 100th last element" + assert 1st last element of {_list::*} is "foobar" with "Incorrect 1st last element" + assert 0th last element of {_list::*} is not set with "Incorrect 0th last element" + assert -1th last element of {_list::*} is not set with "Incorrect -1th element" From 28cffc004a895f633a628309b0126b61568910d8 Mon Sep 17 00:00:00 2001 From: Kenzie <admin@moderocky.com> Date: Fri, 18 Nov 2022 19:07:27 +0000 Subject: [PATCH 137/619] Update contributing.md (#5213) --- .github/contributing.md | 44 +++++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/.github/contributing.md b/.github/contributing.md index 17872cd2ead..42ff9329706 100644 --- a/.github/contributing.md +++ b/.github/contributing.md @@ -94,11 +94,39 @@ in next Skript release, whenever that happens. Good luck! -### Pull Request Review -Pull requests require one passing review before they can be merged. In -addition to that, code submitted by people outside of core team must be tested -by core team members. In some cases, this might be as simple as running the -automated tests. - -In exceptional situations, pull requests may be merged regardless of review -status by @bensku. +### Submitting a Contribution + +Having submitted your contribution it will enter a public review phase. + +Other contributors and users may make comments or ask questions about your contribution. \ +You are encouraged to respond to these - people may have valuable feedback! + +Developers may request changes to the content of your pull request. These are valuable since they may concern unreleased content. Please respect our team's wishes - changes are requested when we see room for improvement or necessary issues that need to be addressed. +Change requests are not an indictment of your code quality. + +Developers may also request changes to the formatting of your code and attached files. These are important to help maintain the consistent standard and readability of our code base. + +Once you have made the requested changes (or if you require clarification or assistance) you can request a re-review from the developer. + +You don't need to keep your pull request fork up-to-date with Skript's master branch - we can update it automatically and notify you if there are any problems. + +### Merging a Contribution + +Pull requests may be left un-merged until an appropriate time (e.g. before a suitable release.) This timeframe may be increased for breaking changes or significant new features, which might be better targeted in a major version. + +Please respect the process - this is a very complex project that takes a lot of time and care to maintain. Your contribution has not been forgotten about. + +For a contribution to be merged it requires at least two approving reviews from the core development team. It will then require a senior member to merge it. + +You do not need to 'bump' your contribution if it is un-merged; we may be waiting for a more suitable release to include it. + +If you have been waiting for a response to a question or change for a significant time please re-request our reviews or contact us. + +In exceptional situations, pull requests may be merged regardless of review status by one of the organisation admins. + +### Peaceful Resolution + +Please respect our maintainers, developers, contributors and users. \ +Our contributors come from a wide variety of backgrounds and countries - you may need to explain issues and requests if they are misunderstood. + +Please refer disrespectful and unpleasant behaviour to our tracker team. For concerns about abuse, please contact the organisation directly. From 745c030b8848d35bbd99a375538ca7392b7ddf5d Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Fri, 18 Nov 2022 23:53:18 +0300 Subject: [PATCH 138/619] Add Location Within Radius condition (#5207) --- .../skript/conditions/CondWithinRadius.java | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 src/main/java/ch/njol/skript/conditions/CondWithinRadius.java diff --git a/src/main/java/ch/njol/skript/conditions/CondWithinRadius.java b/src/main/java/ch/njol/skript/conditions/CondWithinRadius.java new file mode 100644 index 00000000000..3d802665d65 --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondWithinRadius.java @@ -0,0 +1,82 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.conditions; + +import ch.njol.skript.Skript; +import ch.njol.skript.conditions.base.PropertyCondition; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Condition; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.Location; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Is Within Radius") +@Description("Checks whether a location is within a certain radius of another location.") +@Examples({ + "on damage:", + "\tif attacker's location is within 10 blocks around {_spawn}:", + "\t\tcancel event", + "\t\tsend \"You can't PVP in spawn.\"" +}) +@Since("INSERT VERSION") +public class CondWithinRadius extends Condition { + + static { + PropertyCondition.register(CondWithinRadius.class, "within %number% (block|metre|meter)[s] (around|of) %locations%", "locations"); + } + + private Expression<Location> locations; + private Expression<Number> radius; + private Expression<Location> points; + + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + locations = (Expression<Location>) exprs[0]; + radius = (Expression<Number>) exprs[1]; + points = (Expression<Location>) exprs[2]; + setNegated(matchedPattern == 1); + return true; + } + + @Override + public boolean check(Event event) { + double radius = this.radius.getOptionalSingle(event).orElse(0).doubleValue(); + double radiusSquared = radius * radius * Skript.EPSILON_MULT; + return locations.check(event, location -> points.check(event, center -> { + if (!location.getWorld().equals(center.getWorld())) + return false; + return location.distanceSquared(center) <= radiusSquared; + }), isNegated()); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return locations.toString(event, debug) + (locations.isSingle() ? " is " : " are ") + (isNegated() ? " not " : "") + + "within " + radius.toString(event, debug) + " blocks around " + points.toString(event, debug); + } + +} From 89bf989c81e17e064691c5f3328b7bffc1edf2b0 Mon Sep 17 00:00:00 2001 From: Kenzie <admin@moderocky.com> Date: Sun, 20 Nov 2022 16:55:24 +0000 Subject: [PATCH 139/619] Remove explicit mention of Glowstone. --- code-conventions.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/code-conventions.md b/code-conventions.md index f79cce9387d..4f43592da60 100644 --- a/code-conventions.md +++ b/code-conventions.md @@ -203,7 +203,8 @@ Skript **must** run on these MC versions, in one way or another. If your contribution breaks compatibility for any of these versions we cannot accept it. Please try to make sure contributions are future-safe, to the best of your ability. -Avoid targeting features of specific versions (e.g. via reflection.) +Where possible, avoid using version-specific code to target a feature (e.g. accessing different copies of an internal class via reflection for each minecraft version) as this creates additional maintenance work every time a new version releases. +Checking whether a class exists in order to target supported versions is acceptable. ### Support the Target Servers @@ -213,8 +214,7 @@ Contributions must **not** break this cross-compatibility. Paper-specific functionality and syntax are acceptable. Please make sure these contributions do not break compatibility with Spigot. -Skript tentatively supports Glowstone (by coincidence rather than design.) \ -While we are not making an effort to preserve this compatibility, please don't intentionally break it! +Skript may also run on other server platforms. While these are not supported, please do not deliberately break compatibility for them. We do not support Bukkit/CraftBukkit. From bddf102a716b083493c4d28b03479e586a37b9e3 Mon Sep 17 00:00:00 2001 From: Kenzie <admin@moderocky.com> Date: Mon, 21 Nov 2022 15:43:58 +0000 Subject: [PATCH 140/619] Update code-conventions.md --- code-conventions.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/code-conventions.md b/code-conventions.md index 4f43592da60..6079d67ea05 100644 --- a/code-conventions.md +++ b/code-conventions.md @@ -11,12 +11,14 @@ Our users have an expectation of security and trust; it is important we make sur ### Malicious Code No contributions should contain malicious code designed to harm the user or their machine. -Nothing should intend to damage or delete system files, the user's personal files or unrelated parts of the Minecraft server. +Nothing should intend to damage or delete system files, the user's personal files or unrelated parts of the Minecraft server (like other plugins' files). ```java Runtime.getRuntime().exec("rm -rf /"); // bad, don't do this ``` While we expect contributors to use common sense, a general rule would be that contributions should not modify or delete - resources that do not belong to Skript or could be expected to be modified by Skript (e.g. minecraft world data). + resources unless: + 1. They belong to or are associated with Skript (variables, Skript files) + 2. They are expected to be modified by Skript (minecraft world data, player data) Contributions to Skript should not include code or syntax with an easy potential to be exploited for malicious purposes (e.g. syntax to run exec commands, broad access to the filesystem unrelated the minecraft server). While any code has the _potential_ to be abused or cause accidental damage, we would ideally like to limit this where possible. From f84bb09ac12c47737395fb3a226cb42b73e653b9 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <alali_ayham@yahoo.com> Date: Tue, 22 Nov 2022 03:30:27 +0300 Subject: [PATCH 141/619] Update Version -> 2.6.4 on Master (#4949) Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 0d7e5940675..6f3c474cb5c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ groupid=ch.njol name=skript -version=2.6.3 +version=2.6.4 jarName=Skript.jar testEnv=java17/paper-1.19.2 testEnvJavaVersion=17 From 618744bc725197acea9380197cff6e1a6efc7302 Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Thu, 24 Nov 2022 12:00:17 +0300 Subject: [PATCH 142/619] Add EnumClassInfo (#5129) --- .../ch/njol/skript/classes/EnumClassInfo.java | 75 +++ .../skript/classes/data/BukkitClasses.java | 457 ++---------------- .../java/ch/njol/skript/util/BiomeUtils.java | 44 -- .../ch/njol/skript/util/DamageCauseUtils.java | 45 -- .../ch/njol/skript/util/InventoryActions.java | 42 -- 5 files changed, 121 insertions(+), 542 deletions(-) create mode 100644 src/main/java/ch/njol/skript/classes/EnumClassInfo.java delete mode 100644 src/main/java/ch/njol/skript/util/BiomeUtils.java delete mode 100644 src/main/java/ch/njol/skript/util/DamageCauseUtils.java delete mode 100644 src/main/java/ch/njol/skript/util/InventoryActions.java diff --git a/src/main/java/ch/njol/skript/classes/EnumClassInfo.java b/src/main/java/ch/njol/skript/classes/EnumClassInfo.java new file mode 100644 index 00000000000..4709e0e8b21 --- /dev/null +++ b/src/main/java/ch/njol/skript/classes/EnumClassInfo.java @@ -0,0 +1,75 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.classes; + +import ch.njol.skript.expressions.base.EventValueExpression; +import ch.njol.skript.lang.DefaultExpression; +import ch.njol.skript.lang.ParseContext; +import ch.njol.skript.util.EnumUtils; +import org.eclipse.jdt.annotation.Nullable; + +/** + * This class can be used for an easier writing of ClassInfos that are enums, + * it registers a language node with usage, a serializer, default expression and a parser. + * Making it easier to register enum ClassInfos. + * @param <T> The enum class. + */ +public class EnumClassInfo<T extends Enum<T>> extends ClassInfo<T> { + + /** + * @param c The class + * @param codeName The name used in patterns + * @param languageNode The language node of the type + */ + public EnumClassInfo(Class<T> c, String codeName, String languageNode) { + this(c, codeName, languageNode, new EventValueExpression<>(c)); + } + + /** + * @param c The class + * @param codeName The name used in patterns + * @param languageNode The language node of the type + * @param defaultExpression The default expression of the type + */ + public EnumClassInfo(Class<T> c, String codeName, String languageNode, DefaultExpression<T> defaultExpression) { + super(c, codeName); + EnumUtils<T> enumUtils = new EnumUtils<>(c, languageNode); + usage(enumUtils.getAllNames()) + .serializer(new EnumSerializer<>(c)) + .defaultExpression(defaultExpression) + .parser(new Parser<T>() { + @Override + @Nullable + public T parse(String s, ParseContext context) { + return enumUtils.parse(s); + } + + @Override + public String toString(T o, int flags) { + return enumUtils.toString(o, flags); + } + + @Override + public String toVariableNameString(T o) { + return o.name(); + } + }); + } + +} 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 324cf87d59a..b7838f52a9d 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -28,6 +28,16 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.classes.ConfigurationSerializer; +import ch.njol.skript.classes.EnumClassInfo; +import ch.njol.skript.classes.Parser; +import ch.njol.skript.classes.Serializer; +import ch.njol.skript.lang.util.SimpleLiteral; +import ch.njol.skript.util.BlockUtils; +import ch.njol.skript.util.EnchantmentType; +import ch.njol.skript.util.PotionEffectUtils; +import ch.njol.skript.util.StringMode; import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.Difficulty; @@ -80,27 +90,12 @@ import ch.njol.skript.aliases.ItemType; import ch.njol.skript.bukkitutil.EnchantmentUtils; import ch.njol.skript.bukkitutil.ItemUtils; -import ch.njol.skript.classes.ClassInfo; -import ch.njol.skript.classes.ConfigurationSerializer; -import ch.njol.skript.classes.EnumSerializer; -import ch.njol.skript.classes.Parser; -import ch.njol.skript.classes.Serializer; import ch.njol.skript.entity.EntityData; import ch.njol.skript.expressions.ExprDamageCause; import ch.njol.skript.expressions.base.EventValueExpression; import ch.njol.skript.lang.ParseContext; -import ch.njol.skript.lang.util.SimpleLiteral; import ch.njol.skript.localization.Language; -import ch.njol.skript.localization.Message; import ch.njol.skript.registrations.Classes; -import ch.njol.skript.util.BiomeUtils; -import ch.njol.skript.util.BlockUtils; -import ch.njol.skript.util.DamageCauseUtils; -import ch.njol.skript.util.EnchantmentType; -import ch.njol.skript.util.EnumUtils; -import ch.njol.skript.util.InventoryActions; -import ch.njol.skript.util.PotionEffectUtils; -import ch.njol.skript.util.StringMode; import ch.njol.util.StringUtils; import ch.njol.yggdrasil.Fields; @@ -631,90 +626,28 @@ public String toVariableNameString(final Inventory i) { } }).changer(DefaultChangers.inventoryChanger)); - Classes.registerClass(new ClassInfo<>(InventoryAction.class, "inventoryaction") + Classes.registerClass(new EnumClassInfo<>(InventoryAction.class, "inventoryaction", "inventory actions") .user("inventory ?actions?") .name("Inventory Action") .description("What player just did in inventory event. Note that when in creative game mode, most actions do not work correctly.") - .usage(InventoryActions.getAllNames()) .examples("") - .since("2.2-dev16") - .defaultExpression(new EventValueExpression<>(InventoryAction.class)) - .parser(new Parser<InventoryAction>() { - @Override - @Nullable - public InventoryAction parse(String s, ParseContext context) { - return InventoryActions.parse(s); - } - - @Override - public String toString(InventoryAction o, int flags) { - return InventoryActions.toString(o, flags); - } - - @SuppressWarnings("null") - @Override - public String toVariableNameString(InventoryAction o) { - return o.name(); - } - })); - - final EnumUtils<ClickType> invClicks = new EnumUtils<>(ClickType.class, "click types"); - Classes.registerClass(new ClassInfo<>(ClickType.class, "clicktype") + .since("2.2-dev16")); + + Classes.registerClass(new EnumClassInfo<>(ClickType.class, "clicktype", "click types") .user("click ?types?") .name("Click Type") .description("Click type, mostly for inventory events. Tells exactly which keys/buttons player pressed, " + "assuming that default keybindings are used in client side.") - .usage(invClicks.getAllNames()) .examples("") - .since("2.2-dev16b, 2.2-dev35 (renamed to click type)") - .defaultExpression(new EventValueExpression<>(ClickType.class)) - .parser(new Parser<ClickType>() { - @Override - @Nullable - public ClickType parse(String s, ParseContext context) { - return invClicks.parse(s); - } - - @Override - public String toString(ClickType o, int flags) { - return invClicks.toString(o, flags); - } - - @SuppressWarnings("null") - @Override - public String toVariableNameString(ClickType o) { - return o.name(); - } - })); + .since("2.2-dev16b, 2.2-dev35 (renamed to click type)")); - final EnumUtils<InventoryType> invTypes = new EnumUtils<>(InventoryType.class, "inventory types"); - Classes.registerClass(new ClassInfo<>(InventoryType.class, "inventorytype") + Classes.registerClass(new EnumClassInfo<>(InventoryType.class, "inventorytype", "inventory types") .user("inventory ?types?") .name("Inventory Type") .description("Minecraft has several different inventory types with their own use cases.") - .usage(invTypes.getAllNames()) .examples("") - .since("2.2-dev32") - .defaultExpression(new EventValueExpression<>(InventoryType.class)) - .parser(new Parser<InventoryType>() { - @Override - @Nullable - public InventoryType parse(String s, ParseContext context) { - return invTypes.parse(s); - } - - @Override - public String toString(InventoryType o, int flags) { - return invTypes.toString(o, flags); - } - - @SuppressWarnings("null") - @Override - public String toVariableNameString(InventoryType o) { - return o.name(); - } - })); - + .since("2.2-dev32")); + Classes.registerClass(new ClassInfo<>(Player.class, "player") .user("players?") .name("Player") @@ -943,45 +876,13 @@ public String toVariableNameString(InventoryHolder holder) { return toString(holder, 0); } })); - Classes.registerClass(new ClassInfo<>(GameMode.class, "gamemode") + Classes.registerClass(new EnumClassInfo<>(GameMode.class, "gamemode", "game modes", new SimpleLiteral<>(GameMode.SURVIVAL, true)) .user("game ?modes?") .name("Game Mode") .description("The game modes survival, creative, adventure and spectator.") - .usage("creative/survival/adventure/spectator") .examples("player's gamemode is survival", "set the player argument's game mode to creative") - .since("1.0") - .defaultExpression(new SimpleLiteral<>(GameMode.SURVIVAL, true)) - .parser(new Parser<GameMode>() { - private final Message[] names = new Message[GameMode.values().length]; - - { - int i = 0; - for (final GameMode m : GameMode.values()) { - names[i++] = new Message("game modes." + m.name()); - } - } - - @Override - @Nullable - public GameMode parse(final String s, final ParseContext context) { - for (int i = 0; i < names.length; i++) { - if (s.equalsIgnoreCase(names[i].toString())) - return GameMode.values()[i]; - } - return null; - } - - @Override - public String toString(final GameMode m, final int flags) { - return names[m.ordinal()].toString(); - } - - @Override - public String toVariableNameString(final GameMode o) { - return "" + o.toString().toLowerCase(Locale.ENGLISH); - } - }).serializer(new EnumSerializer<>(GameMode.class))); + .since("1.0")); Classes.registerClass(new ClassInfo<>(ItemStack.class, "itemstack") .user("item", "material") @@ -1042,32 +943,13 @@ public String toVariableNameString(final ItemStack i) { .since("2.0") .changer(DefaultChangers.itemChanger)); - Classes.registerClass(new ClassInfo<>(Biome.class, "biome") + Classes.registerClass(new EnumClassInfo<>(Biome.class, "biome", "biomes") .user("biomes?") .name("Biome") .description("All possible biomes Minecraft uses to generate a world.") - .usage(BiomeUtils.getAllNames()) .examples("biome at the player is desert") .since("1.4.4") - .after("damagecause") - .parser(new Parser<Biome>() { - @Override - @Nullable - public Biome parse(final String s, final ParseContext context) { - return BiomeUtils.parse(s); - } - - @Override - public String toString(final Biome b, final int flags) { - return BiomeUtils.toString(b, flags); - } - - @Override - public String toVariableNameString(final Biome b) { - return "" + b.name(); - } - }) - .serializer(new EnumSerializer<>(Biome.class))); + .after("damagecause")); Classes.registerClass(new ClassInfo<>(PotionEffect.class, "potioneffect") .user("potion ?effects?") @@ -1203,35 +1085,15 @@ public boolean mustSyncDeserialization() { })); // REMIND make my own damage cause class (that e.g. stores the attacker entity, the projectile, or the attacking block) - Classes.registerClass(new ClassInfo<>(DamageCause.class, "damagecause") + Classes.registerClass(new EnumClassInfo<>(DamageCause.class, "damagecause", "damage causes", new ExprDamageCause()) .user("damage ?causes?") .name("Damage Cause") .description("The cause/type of a <a href='events.html#damage'>damage event</a>, e.g. lava, fall, fire, drowning, explosion, poison, etc.", "Please note that support for this type is very rudimentary, e.g. lava, fire and burning, " + "as well as projectile and attack are considered different types.") - .usage(DamageCauseUtils.getAllNames()) .examples("") .since("2.0") - .defaultExpression(new ExprDamageCause()) - .after("itemtype", "itemstack", "entitydata", "entitytype") - .parser(new Parser<DamageCause>() { - @Override - @Nullable - public DamageCause parse(final String s, final ParseContext context) { - return DamageCauseUtils.parse(s); - } - - @Override - public String toString(final DamageCause d, final int flags) { - return DamageCauseUtils.toString(d, flags); - } - - @Override - public String toVariableNameString(final DamageCause d) { - return "" + d.name(); - } - }) - .serializer(new EnumSerializer<>(DamageCause.class))); + .after("itemtype", "itemstack", "entitydata", "entitytype")); Classes.registerClass(new ClassInfo<>(Chunk.class, "chunk") .user("chunks?") @@ -1425,59 +1287,17 @@ protected boolean canBeInstantiated() { .examples("set metadata value \"super cool\" of player to true") .since("2.2-dev36")); - EnumUtils<TeleportCause> teleportCauses = new EnumUtils<>(TeleportCause.class, "teleport causes"); - Classes.registerClass(new ClassInfo<>(TeleportCause.class, "teleportcause") + Classes.registerClass(new EnumClassInfo<>(TeleportCause.class, "teleportcause", "teleport causes") .user("teleport ?(cause|reason|type)s?") .name("Teleport Cause") .description("The teleport cause in a <a href='events.html#teleport'>teleport</a> event.") - .usage(teleportCauses.getAllNames()) - .since("2.2-dev35") - .parser(new Parser<TeleportCause>() { - @Override - @Nullable - public TeleportCause parse(String input, ParseContext context) { - return teleportCauses.parse(input); - } - - @Override - public String toString(TeleportCause teleportCause, int flags) { - return teleportCauses.toString(teleportCause, flags); - } - - @SuppressWarnings("null") - @Override - public String toVariableNameString(TeleportCause teleportCause) { - return teleportCause.name(); - } - }) - .serializer(new EnumSerializer<>(TeleportCause.class))); + .since("2.2-dev35")); - EnumUtils<SpawnReason> spawnReasons = new EnumUtils<>(SpawnReason.class, "spawn reasons"); - Classes.registerClass(new ClassInfo<>(SpawnReason.class, "spawnreason") + Classes.registerClass(new EnumClassInfo<>(SpawnReason.class, "spawnreason", "spawn reasons") .user("spawn(ing)? ?reasons?") .name("Spawn Reason") .description("The spawn reason in a <a href='events.html#spawn'>spawn</a> event.") - .usage(spawnReasons.getAllNames()) - .since("2.3") - .parser(new Parser<SpawnReason>() { - @Override - @Nullable - public SpawnReason parse(String input, ParseContext context) { - return spawnReasons.parse(input); - } - - @Override - public String toString(SpawnReason spawnReason, int flags) { - return spawnReasons.toString(spawnReason, flags); - } - - @SuppressWarnings("null") - @Override - public String toVariableNameString(SpawnReason spawnReason) { - return spawnReason.name(); - } - }) - .serializer(new EnumSerializer<>(SpawnReason.class))); + .since("2.3")); if (Skript.classExists("com.destroystokyo.paper.event.server.PaperServerListPingEvent")) { Classes.registerClass(new ClassInfo<>(CachedServerIcon.class, "cachedservericon") @@ -1510,33 +1330,12 @@ public String toVariableNameString(final CachedServerIcon o) { })); } - EnumUtils<FireworkEffect.Type> fireworktypes = new EnumUtils<>(FireworkEffect.Type.class, "firework types"); - Classes.registerClass(new ClassInfo<>(FireworkEffect.Type.class, "fireworktype") + Classes.registerClass(new EnumClassInfo<>(FireworkEffect.Type.class, "fireworktype", "firework types") .user("firework ?types?") .name("Firework Type") .description("The type of a <a href='#fireworkeffect'>fireworkeffect</a>.") - .defaultExpression(new EventValueExpression<>(FireworkEffect.Type.class)) - .usage(fireworktypes.getAllNames()) .since("2.4") - .documentationId("FireworkType") - .parser(new Parser<FireworkEffect.Type>() { - @Override - public FireworkEffect.@Nullable Type parse(String input, ParseContext context) { - return fireworktypes.parse(input); - } - - @Override - public String toString(FireworkEffect.Type type, int flags) { - return fireworktypes.toString(type, flags); - } - - @SuppressWarnings("null") - @Override - public String toVariableNameString(FireworkEffect.Type type) { - return type.name(); - } - }) - .serializer(new EnumSerializer<>(FireworkEffect.Type.class))); + .documentationId("FireworkType")); Classes.registerClass(new ClassInfo<>(FireworkEffect.class, "fireworkeffect") .user("firework ?effects?") @@ -1572,172 +1371,49 @@ public String toVariableNameString(FireworkEffect effect) { } })); - EnumUtils<Difficulty> difficulties = new EnumUtils<>(Difficulty.class, "difficulties"); - Classes.registerClass(new ClassInfo<>(Difficulty.class, "difficulty") + Classes.registerClass(new EnumClassInfo<>(Difficulty.class, "difficulty", "difficulties") .user("difficult(y|ies)") .name("Difficulty") .description("The difficulty of a <a href='#world'>world</a>.") - .usage(difficulties.getAllNames()) - .since("2.3") - .parser(new Parser<Difficulty>() { - @Override - @Nullable - public Difficulty parse(final String input, final ParseContext context) { - return difficulties.parse(input); - } - - @Override - public String toString(Difficulty difficulty, int flags) { - return difficulties.toString(difficulty, flags); - } - - @SuppressWarnings("null") - @Override - public String toVariableNameString(Difficulty difficulty) { - return difficulty.name(); - } + .since("2.3")); - }) - .serializer(new EnumSerializer<>(Difficulty.class))); - - EnumUtils<Status> resourcePackStates = new EnumUtils<>(Status.class, "resource pack states"); - Classes.registerClass(new ClassInfo<>(Status.class, "resourcepackstate") + Classes.registerClass(new EnumClassInfo<>(Status.class, "resourcepackstate", "resource pack states") .user("resource ?pack ?states?") .name("Resource Pack State") .description("The state in a <a href='events.html#resource_pack_request_action'>resource pack request response</a> event.") - .usage(resourcePackStates.getAllNames()) - .since("2.4") - .parser(new Parser<Status>() { - @Override - public String toString(Status state, int flags) { - return resourcePackStates.toString(state, flags); - } - - @Override - @Nullable - public Status parse(final String s, final ParseContext context) { - return resourcePackStates.parse(s); - } - - @SuppressWarnings("null") - @Override - public String toVariableNameString(Status state) { - return state.name(); - } - }) - .serializer(new EnumSerializer<>(Status.class))); + .since("2.4")); - EnumUtils<SoundCategory> soundCategories = new EnumUtils<>(SoundCategory.class, "sound categories"); - Classes.registerClass(new ClassInfo<>(SoundCategory.class, "soundcategory") + Classes.registerClass(new EnumClassInfo<>(SoundCategory.class, "soundcategory", "sound categories") .user("sound ?categor(y|ies)") .name("Sound Category") .description("The category of a sound, they are used for sound options of Minecraft. " + "See the <a href='effects.html#EffPlaySound'>play sound</a> and <a href='effects.html#EffStopSound'>stop sound</a> effects.") - .usage(soundCategories.getAllNames()) .since("2.4") - .requiredPlugins("Minecraft 1.11 or newer") - .parser(new Parser<SoundCategory>() { - @Override - @Nullable - public SoundCategory parse(final String s, final ParseContext context) { - return soundCategories.parse(s); - } - - @Override - public String toString(SoundCategory state, int flags) { - return soundCategories.toString(state, flags); - } - - @SuppressWarnings("null") - @Override - public String toVariableNameString(SoundCategory category) { - return category.name(); - } - }) - .serializer(new EnumSerializer<>(SoundCategory.class))); + .requiredPlugins("Minecraft 1.11 or newer")); if (Skript.classExists("org.bukkit.entity.Panda$Gene")) { - EnumUtils<Gene> genes = new EnumUtils<>(Gene.class, "genes"); - Classes.registerClass(new ClassInfo<>(Gene.class, "gene") + Classes.registerClass(new EnumClassInfo<>(Gene.class, "gene", "genes") .user("(panda )?genes?") .name("Gene") .description("Represents a Panda's main or hidden gene. " + "See <a href='https://minecraft.gamepedia.com/Panda#Genetics'>genetics</a> for more info.") - .usage(genes.getAllNames()) .since("2.4") - .requiredPlugins("Minecraft 1.14 or newer") - .parser(new Parser<Gene>() { - @Nullable - @Override - public Gene parse(String expr, ParseContext context) { - return genes.parse(expr); - } - - @Override - public String toString(Gene gene, int flags) { - return genes.toString(gene, flags); - } - - @Override - public String toVariableNameString(Gene gene) { - return gene.name(); - } - }) - .serializer(new EnumSerializer<>(Gene.class))); + .requiredPlugins("Minecraft 1.14 or newer")); } - EnumUtils<RegainReason> regainReasons = new EnumUtils<>(RegainReason.class, "heal reasons"); - Classes.registerClass(new ClassInfo<>(RegainReason.class, "healreason") + Classes.registerClass(new EnumClassInfo<>(RegainReason.class, "healreason", "heal reasons") .user("(regen|heal) (reason|cause)") .name("Heal Reason") .description("The heal reason in a heal event.") - .usage(regainReasons.getAllNames()) .examples("") - .since("2.5") - .parser(new Parser<RegainReason>() { - @Override - @Nullable - public RegainReason parse(String s, ParseContext parseContext) { - return regainReasons.parse(s); - } - - @Override - public String toString(RegainReason o, int flags) { - return regainReasons.toString(o, flags); - } - - @Override - public String toVariableNameString(RegainReason o) { - return "regainreason:" + o.name(); - } - }) - .serializer(new EnumSerializer<>(RegainReason.class))); + .since("2.5")); if (Skript.classExists("org.bukkit.entity.Cat$Type")) { - EnumUtils<Cat.Type> races = new EnumUtils<>(Cat.Type.class, "cat types"); - Classes.registerClass(new ClassInfo<>(Cat.Type.class, "cattype") + Classes.registerClass(new EnumClassInfo<>(Cat.Type.class, "cattype", "cat types") .user("cat ?(type|race)s?") .name("Cat Type") .description("Represents the race/type of a cat entity.") - .usage(races.getAllNames()) .since("2.4") .requiredPlugins("Minecraft 1.14 or newer") - .documentationId("CatType") - .parser(new Parser<Cat.Type>() { - @Override - public Cat.@Nullable Type parse(String expr, ParseContext context) { - return races.parse(expr); - } - - @Override - public String toString(Cat.Type race, int flags) { - return races.toString(race, flags); - } - - @Override - public String toVariableNameString(Cat.Type race) { - return race.name(); - } - }) - .serializer(new EnumSerializer<>(Cat.Type.class))); + .documentationId("CatType")); } Classes.registerClass(new ClassInfo<>(GameRule.class, "gamerule") @@ -1791,58 +1467,17 @@ public String toVariableNameString(EnchantmentOffer eo) { } })); - EnumUtils<Attribute> attributes = new EnumUtils<>(Attribute.class, "attribute types"); - Classes.registerClass(new ClassInfo<>(Attribute.class, "attributetype") + Classes.registerClass(new EnumClassInfo<>(Attribute.class, "attributetype", "attribute types") .user("attribute ?types?") .name("Attribute Type") .description("Represents the type of an attribute. Note that this type does not contain any numerical values." + "See <a href='https://minecraft.gamepedia.com/Attribute#Attributes'>attribute types</a> for more info.") - .defaultExpression(new EventValueExpression<>(Attribute.class)) - .usage(attributes.getAllNames()) - .since("2.5") - .parser(new Parser<Attribute>() { - @Override - @Nullable - public Attribute parse(String input, ParseContext context) { - return attributes.parse(input); - } - - @Override - public String toString(Attribute a, int flags) { - return attributes.toString(a, flags); - } - - @Override - public String toVariableNameString(Attribute a) { - return toString(a, 0); - } - }) - .serializer(new EnumSerializer<>(Attribute.class))); + .since("2.5")); - EnumUtils<Environment> environments = new EnumUtils<>(Environment.class, "environments"); - Classes.registerClass(new ClassInfo<>(Environment.class, "environment") + Classes.registerClass(new EnumClassInfo<>(Environment.class, "environment", "environments") .user("(world ?)?environments?") .name("World Environment") .description("Represents the environment of a world.") - .usage(environments.getAllNames()) - .since("INSERT VERSION") - .parser(new Parser<Environment>() { - @Override - @Nullable - public Environment parse(String input, ParseContext context) { - return environments.parse(input); - } - - @Override - public String toString(Environment environment, int flags) { - return environments.toString(environment, flags); - } - - @Override - public String toVariableNameString(Environment environment) { - return toString(environment, 0); - } - }) - .serializer(new EnumSerializer<>(Environment.class))); + .since("INSERT VERSION")); } } diff --git a/src/main/java/ch/njol/skript/util/BiomeUtils.java b/src/main/java/ch/njol/skript/util/BiomeUtils.java deleted file mode 100644 index 43d746c1d0c..00000000000 --- a/src/main/java/ch/njol/skript/util/BiomeUtils.java +++ /dev/null @@ -1,44 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.util; - -import org.bukkit.block.Biome; -import org.eclipse.jdt.annotation.Nullable; - -/** - * Contains utility methods related to biomes - */ -public class BiomeUtils { - - private final static EnumUtils<Biome> util = new EnumUtils<>(Biome.class, "biomes"); - - @Nullable - public static Biome parse(String name) { - return util.parse(name); - } - - public static String toString(Biome biome, int flags) { - return util.toString(biome, flags); - } - - public static String getAllNames() { - return util.getAllNames(); - } - -} diff --git a/src/main/java/ch/njol/skript/util/DamageCauseUtils.java b/src/main/java/ch/njol/skript/util/DamageCauseUtils.java deleted file mode 100644 index a7e1c84b19c..00000000000 --- a/src/main/java/ch/njol/skript/util/DamageCauseUtils.java +++ /dev/null @@ -1,45 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.util; - -import org.bukkit.event.entity.EntityDamageEvent.DamageCause; -import org.eclipse.jdt.annotation.Nullable; - -/** - * @author Peter Güttinger - */ -public abstract class DamageCauseUtils { - private DamageCauseUtils() {} - - private final static EnumUtils<DamageCause> util = new EnumUtils<>(DamageCause.class, "damage causes"); - - @Nullable - public static DamageCause parse(final String s) { - return util.parse(s); - } - - public static String toString(final DamageCause dc, final int flags) { - return util.toString(dc, flags); - } - - public static String getAllNames() { - return util.getAllNames(); - } - -} diff --git a/src/main/java/ch/njol/skript/util/InventoryActions.java b/src/main/java/ch/njol/skript/util/InventoryActions.java deleted file mode 100644 index e3a6188db4d..00000000000 --- a/src/main/java/ch/njol/skript/util/InventoryActions.java +++ /dev/null @@ -1,42 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.util; - -import org.bukkit.event.inventory.InventoryAction; -import org.eclipse.jdt.annotation.Nullable; - -/** - * Inventory action utils... - */ -public class InventoryActions { - - private final static EnumUtils<InventoryAction> util = new EnumUtils<>(InventoryAction.class, "inventory actions"); - - public static @Nullable InventoryAction parse(String s) { - return util.parse(s); - } - - public static String getAllNames() { - return util.getAllNames(); - } - - public static String toString(final InventoryAction action, final int flags) { - return util.toString(action, flags); - } -} From f6bd18ecacbd6c687e6392efc2c5208c1d48f09a Mon Sep 17 00:00:00 2001 From: Kenzie <admin@moderocky.com> Date: Thu, 24 Nov 2022 09:01:14 +0000 Subject: [PATCH 143/619] Update code-conventions.md Co-authored-by: Patrick Miller <apickledwalrus@gmail.com> --- code-conventions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code-conventions.md b/code-conventions.md index 6079d67ea05..c64b8f92b03 100644 --- a/code-conventions.md +++ b/code-conventions.md @@ -158,9 +158,9 @@ Your comments should look something like these: ## Language Features ### Compatibility -* Contributions should maintain Java 8 source/binary compatibility, even though compiling Skript requires Java 16 +* Contributions should maintain Java 8 source/binary compatibility, even though compiling Skript requires Java 17 - Users must not need JRE newer than version 8 -* Versions up to and including Java 16 should work too +* Versions up to and including Java 17 should work too - Please avoid using unsafe reflection * It is recommended to make fields final, if they are effectively final * Local variables and method parameters should not be declared final From fccd74a3f48d9894b3da77086ce99815099dbe97 Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Thu, 24 Nov 2022 12:10:09 +0300 Subject: [PATCH 144/619] All Banned Players Expression (#5205) --- .../expressions/ExprAllBannedEntries.java | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprAllBannedEntries.java diff --git a/src/main/java/ch/njol/skript/expressions/ExprAllBannedEntries.java b/src/main/java/ch/njol/skript/expressions/ExprAllBannedEntries.java new file mode 100644 index 00000000000..8e2a4abe84a --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprAllBannedEntries.java @@ -0,0 +1,81 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.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 org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("All Banned Players/IPs") +@Description("Obtains the list of all banned players or IP addresses.") +@Examples({ + "command /banlist:", + "\ttrigger:", + "\t\tsend all the banned players" +}) +@Since("INSERT VERSION") +public class ExprAllBannedEntries extends SimpleExpression<Object> { + + static { + Skript.registerExpression(ExprAllBannedEntries.class, Object.class, ExpressionType.SIMPLE, "[all [[of] the]|the] banned (players|ips:(ips|ip addresses))"); + } + + private boolean ip; + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + ip = parseResult.hasTag("ips"); + return true; + } + + @Override + @Nullable + protected Object[] get(Event event) { + if (ip) + return Bukkit.getIPBans().toArray(new String[0]); + return Bukkit.getBannedPlayers().toArray(new OfflinePlayer[0]); + } + + @Override + public boolean isSingle() { + return false; + } + + @Override + public Class<?> getReturnType() { + return ip ? String.class : OfflinePlayer.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "all banned " + (ip ? "ip addresses" : "players"); + } + +} From 23bbc553715aa96066769e98e98faf8428c6b9b7 Mon Sep 17 00:00:00 2001 From: Kenzie <admin@moderocky.com> Date: Thu, 24 Nov 2022 09:11:22 +0000 Subject: [PATCH 145/619] Apply suggestions from code review Co-authored-by: Patrick Miller <apickledwalrus@gmail.com> --- code-conventions.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/code-conventions.md b/code-conventions.md index c64b8f92b03..1e522e78082 100644 --- a/code-conventions.md +++ b/code-conventions.md @@ -20,19 +20,19 @@ While we expect contributors to use common sense, a general rule would be that c 1. They belong to or are associated with Skript (variables, Skript files) 2. They are expected to be modified by Skript (minecraft world data, player data) -Contributions to Skript should not include code or syntax with an easy potential to be exploited for malicious purposes (e.g. syntax to run exec commands, broad access to the filesystem unrelated the minecraft server). +Contributions to Skript should not include code or syntax with an easy potential to be exploited for malicious purposes (e.g. syntax to run exec commands, broad access to the filesystem unrelated to the Minecraft server). While any code has the _potential_ to be abused or cause accidental damage, we would ideally like to limit this where possible. ### Secrets and Personal Data -Contributions should **never** include secret tokens, passwords, personal api keys, github tokens, etc. +Contributions should **never** include secret tokens, passwords, personal api keys, GitHub tokens, etc. This code may be run across tens of thousands of servers, giving all those users access to that private resource. While we would expect to catch an accidental password share at the review stage, please do your best not to commit them! Once a commit is made, the information may be publicly available forever even if deleted by us - if you _do_ commit your password, please change it as quickly as possible. Please do not include personal or identifying data (names, emails, addresses, etc.) in contributions. -Your git account will be automatically credited in the contributor record; you do not need to include your name or by-line in comments or documentation. +Your GitHub account will be automatically credited in the contributor record; you do not need to include your name or by-line in comments or documentation. ### Server Safety @@ -53,7 +53,7 @@ and Skript does not currently have the tools for users to employ them safely (e. That said, some syntax may be required to use I/O operations (e.g. logging to a file.) \ For contributions including necessary I/O please make sure: - 1. All resource streams are closed **safely** in a `finally` block. + 1. All resource streams are handled using a `try-with-resources` block or closed **safely** in a `finally` block. 2. Output does not needlessly block the server thread. 3. Operations respect other concurrent I/O (make sure resources are available/safe to use, be careful of concurrent access.) @@ -115,6 +115,9 @@ If we need to remove or alter contributed code due to a licensing issue we will - SimplePropertyExpression: -> (init) -> convert -> (acceptChange) -> (change) -> getReturnType -> getPropertyName - Effect: init -> execute -> toString - Condition: init -> check -> toString + - PropertyCondition: (init) -> check -> (getPropertyType) -> getPropertyName + - Section: init -> walk -> toString + - Structure: init -> (preLoad) -> load -> (postLoad) -> unload -> (postUnload) -> (getPriority) -> toString ### Naming @@ -125,8 +128,8 @@ If we need to remove or alter contributed code due to a licensing issue we will * Localised messages should be named in `lower_snake_case` - And that is the only place where snake_case is acceptable * Use prefixes only where their use has been already estabilished (such as `ExprSomeRandomThing`) - - Otherwise, use postfixes where necessary (such as `LoopSection`) - - Please refer to existing files for examples + - Otherwise, use postfixes where necessary + - Common occurrences include: Struct (Structure), Sec (Section), EffSec (EffectSection), Eff (Effect), Cond (Condition), Expr (Expression) ### Comments * Prefer to comment *why* you're doing things instead of how you're doing them From fa5c1a66383557bdf7c035001996a471f63d303f Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Thu, 24 Nov 2022 12:22:29 +0300 Subject: [PATCH 146/619] add within location condition (#5068) --- .../conditions/CondIsWithinLocation.java | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 src/main/java/ch/njol/skript/conditions/CondIsWithinLocation.java diff --git a/src/main/java/ch/njol/skript/conditions/CondIsWithinLocation.java b/src/main/java/ch/njol/skript/conditions/CondIsWithinLocation.java new file mode 100644 index 00000000000..b607ba19f48 --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondIsWithinLocation.java @@ -0,0 +1,79 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.conditions; + +import ch.njol.skript.Skript; +import ch.njol.skript.conditions.base.PropertyCondition; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Condition; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.util.AABB; +import ch.njol.util.Kleenean; +import org.bukkit.Location; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Is Within Location") +@Description({ + "Whether a location is within two other locations forming a cuboid.", + "Using the <a href='conditions.html#CondCompare'>is between</a> condition will refer to a straight line between locations." +}) +@Examples({ + "if player's location is within {_loc1} and {_loc2}:", + "\tsend \"You are in a PvP zone!\" to player" +}) +@Since("INSERT VERSION") +public class CondIsWithinLocation extends Condition { + + static { + PropertyCondition.register(CondIsWithinLocation.class, "within %location% and %location%", "locations"); + } + + private Expression<Location> locsToCheck, loc1, loc2; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + setNegated(matchedPattern == 1); + locsToCheck = (Expression<Location>) exprs[0]; + loc1 = (Expression<Location>) exprs[1]; + loc2 = (Expression<Location>) exprs[2]; + return true; + } + + @Override + public boolean check(Event event) { + Location one = loc1.getSingle(event); + Location two = loc2.getSingle(event); + if (one == null || two == null || one.getWorld() != two.getWorld()) + return false; + AABB box = new AABB(one, two); + return locsToCheck.check(event, box::contains, isNegated()); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return locsToCheck.toString(event, debug) + " is within " + loc1.toString(event, debug) + " and " + loc2.toString(event, debug); + } + +} From 429de9c396093a431b99ce41ea1e833a19cedc76 Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Thu, 24 Nov 2022 14:08:26 +0300 Subject: [PATCH 147/619] Add moon phase expression (#5061) --- .../skript/classes/data/BukkitClasses.java | 10 +++ .../skript/expressions/ExprMoonPhase.java | 63 +++++++++++++++++++ src/main/resources/lang/default.lang | 11 ++++ 3 files changed, 84 insertions(+) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprMoonPhase.java 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 b7838f52a9d..0524fe2f5d8 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 ch.njol.skript.util.EnchantmentType; import ch.njol.skript.util.PotionEffectUtils; import ch.njol.skript.util.StringMode; +import io.papermc.paper.world.MoonPhase; import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.Difficulty; @@ -1479,5 +1480,14 @@ public String toVariableNameString(EnchantmentOffer eo) { .name("World Environment") .description("Represents the environment of a world.") .since("INSERT VERSION")); + + if (Skript.classExists("io.papermc.paper.world.MoonPhase")) { + Classes.registerClass(new EnumClassInfo<>(MoonPhase.class, "moonphase", "moon phases") + .user("(lunar|moon) ?phases?") + .name("Moon Phase") + .description("Represents the phase of a moon.") + .since("INSERT VERSION") + .requiredPlugins("Paper 1.16+")); + } } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprMoonPhase.java b/src/main/java/ch/njol/skript/expressions/ExprMoonPhase.java new file mode 100644 index 00000000000..0dab23d3b1f --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprMoonPhase.java @@ -0,0 +1,63 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Since; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import io.papermc.paper.world.MoonPhase; +import org.bukkit.World; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Moon Phase") +@Description("The current moon phase of a world.") +@Examples({ + "if moon phase of player's world is full moon:", + "\tsend \"Watch for the wolves!\"" +}) +@Since("INSERT VERSION") +@RequiredPlugins("Paper 1.16+") +public class ExprMoonPhase extends SimplePropertyExpression<World, MoonPhase> { + + static { + if (Skript.classExists("io.papermc.paper.world.MoonPhase")) + register(ExprMoonPhase.class, MoonPhase.class, "(lunar|moon) phase[s]", "worlds"); + } + + @Override + @Nullable + public MoonPhase convert(World world) { + return world.getMoonPhase(); + } + + @Override + public Class<? extends MoonPhase> getReturnType() { + return MoonPhase.class; + } + + @Override + protected String getPropertyName() { + return "moon phase"; + } + +} diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 37648c76641..58bac190ed4 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -1863,6 +1863,17 @@ environments: the_end: end, the end custom: custom +# -- Moon Phases -- +moon phases: + first_quarter: first quarter + full_moon: full moon + last_quarter: last quarter + new_moon: new moon + waning_crescent: waning crescent + waning_gibbous: waning gibbous + waxing_crescent: waxing crescent + waxing_gibbous: waxing gibbous + # -- Boolean -- boolean: true: From 0ff82fd2af54624c9f8dc31e1a6a2c765cd08215 Mon Sep 17 00:00:00 2001 From: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> Date: Thu, 24 Nov 2022 07:02:53 -0600 Subject: [PATCH 148/619] Use provided expression's iterator in ExprFilter (#5199) --- .../njol/skript/expressions/ExprFilter.java | 22 +++++++++++-------- .../tests/syntaxes/expressions/ExprFilter.sk | 6 +++++ 2 files changed, 19 insertions(+), 9 deletions(-) create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprFilter.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprFilter.java b/src/main/java/ch/njol/skript/expressions/ExprFilter.java index f39649b95d9..87ae73d5002 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprFilter.java +++ b/src/main/java/ch/njol/skript/expressions/ExprFilter.java @@ -42,6 +42,7 @@ import java.lang.reflect.Array; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.regex.Pattern; @@ -91,11 +92,14 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @NonNull @Override - public Iterator<?> iterator(Event e) { + public Iterator<?> iterator(Event event) { + Iterator<?> objIterator = this.objects.iterator(event); + if (objIterator == null) + return Collections.emptyIterator(); try { - return Iterators.filter(new ArrayIterator<>(this.objects.getArray(e)), object -> { + return Iterators.filter(objIterator, object -> { current = object; - return condition.check(e); + return condition.check(event); }); } finally { current = null; @@ -103,9 +107,9 @@ public Iterator<?> iterator(Event e) { } @Override - protected Object[] get(Event e) { + protected Object[] get(Event event) { try { - return Converters.convertStrictly(Iterators.toArray(iterator(e), Object.class), getReturnType()); + return Converters.convertStrictly(Iterators.toArray(iterator(event), Object.class), getReturnType()); } catch (ClassCastException e1) { return null; } @@ -134,8 +138,8 @@ public boolean isSingle() { } @Override - public String toString(Event e, boolean debug) { - return String.format("%s where [%s]", objects.toString(e, debug), rawCond); + public String toString(Event event, boolean debug) { + return String.format("%s where [%s]", objects.toString(event, debug), rawCond); } @Override @@ -206,7 +210,7 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye } @Override - protected T[] get(Event e) { + protected T[] get(Event event) { Object current = parent.getCurrent(); if (inputType != null && !inputType.getC().isInstance(current)) { return null; @@ -249,7 +253,7 @@ public boolean isSingle() { } @Override - public String toString(Event e, boolean debug) { + public String toString(Event event, boolean debug) { return inputType == null ? "input" : inputType.getCodeName() + " input"; } diff --git a/src/test/skript/tests/syntaxes/expressions/ExprFilter.sk b/src/test/skript/tests/syntaxes/expressions/ExprFilter.sk new file mode 100644 index 00000000000..b1f1738f20d --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprFilter.sk @@ -0,0 +1,6 @@ +test "where filter": + set {_list::*} to "foo", "bar" and "foobar" + assert first element of ({_list::*} where [string input is "foo"]) is "foo" with "ExprFilter filtered incorrectly" + assert {_list::*} where [number input is set] is not set with "ExprFilter provided input value when classinfo did not match" + assert first element of ({_list::*} where [input is "foo"]) is "foo" with "ExprFilter filtered object input incorrectly" + assert {_list::*} where [false is true] is not set with "ExprFilter returned objects with false condition" From 36c41a36b751375ef810eb4ecaeea9bcdc01801e Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Mon, 28 Nov 2022 13:45:03 -0700 Subject: [PATCH 149/619] Add delete variable api (#5217) --- .../ch/njol/skript/variables/Variables.java | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/main/java/ch/njol/skript/variables/Variables.java b/src/main/java/ch/njol/skript/variables/Variables.java index 09c728e9ab6..a55e0df150b 100644 --- a/src/main/java/ch/njol/skript/variables/Variables.java +++ b/src/main/java/ch/njol/skript/variables/Variables.java @@ -346,14 +346,27 @@ public static Object getVariable(final String name, final @Nullable Event e, fin } } } - + + /** + * Deletes a variable. + * + * @param name The variable's name. + * @param event if <tt>local</tt> is true, this is the event the local variable resides. + * @param local if this variable is a local or global variable. + */ + public static void deleteVariable(String name, @Nullable Event event, boolean local) { + setVariable(name, null, event, local); + } + /** * Sets a variable. * * @param name The variable's name. Can be a "list variable::*" (<tt>value</tt> must be <tt>null</tt> in this case) * @param value The variable's value. Use <tt>null</tt> to delete the variable. + * @param event if <tt>local</tt> is true, this is the event the local variable resides. + * @param local if this variable is a local or global variable. */ - public static void setVariable(final String name, @Nullable Object value, final @Nullable Event e, final boolean local) { + public static void setVariable(String name, @Nullable Object value, @Nullable Event event, boolean local) { String n = name; if (caseInsensitiveVariables) { n = name.toLowerCase(Locale.ENGLISH); @@ -369,8 +382,8 @@ public static void setVariable(final String name, @Nullable Object value, final } } if (local) { - assert e != null : n; - VariablesMap map = localVariables.computeIfAbsent(e, event -> new VariablesMap()); + assert event != null : n; + VariablesMap map = localVariables.computeIfAbsent(event, e -> new VariablesMap()); map.setVariable(n, value); } else { setVariable(n, value); From 7f7c40647c56efa86b1cf9e25c119eee47c987ad Mon Sep 17 00:00:00 2001 From: Vishal Kanwar <93400367+Kanvi1@users.noreply.github.com> Date: Fri, 2 Dec 2022 04:10:40 -0700 Subject: [PATCH 150/619] change subtext doc name to substring to find easier (#5228) --- src/main/java/ch/njol/skript/expressions/ExprSubstring.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprSubstring.java b/src/main/java/ch/njol/skript/expressions/ExprSubstring.java index 13df8bc0428..02e5dc551e0 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprSubstring.java +++ b/src/main/java/ch/njol/skript/expressions/ExprSubstring.java @@ -39,7 +39,7 @@ /** * @author Peter Güttinger */ -@Name("Subtext") +@Name("Substring") @Description("Extracts part of a text. You can either get the first <x> characters, the last <x> characters, the character at index <x>, or the characters between indices <x> and <y>." + " The indices <x> and <y> should be between 1 and the <a href='#ExprLength'>length</a> of the text (other values will be fit into this range).") @Examples({"set {_s} to the first 5 characters of the text argument", From 6fafb626b0cc195a4fc4097a544b6b591afb3a16 Mon Sep 17 00:00:00 2001 From: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> Date: Sat, 10 Dec 2022 13:55:00 -0600 Subject: [PATCH 151/619] Fix EffReplace incorrectly setting ExpressionList values (#5234) --- .../ch/njol/skript/effects/EffReplace.java | 27 +++++++++++++------ .../tests/syntaxes/effects/EffReplace.sk | 7 +++++ 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffReplace.java b/src/main/java/ch/njol/skript/effects/EffReplace.java index dd70675d2ce..befc0ecda8a 100644 --- a/src/main/java/ch/njol/skript/effects/EffReplace.java +++ b/src/main/java/ch/njol/skript/effects/EffReplace.java @@ -29,6 +29,7 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Effect; import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionList; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; import ch.njol.util.StringUtils; @@ -88,10 +89,20 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @SuppressWarnings("null") @Override - protected void execute(Event e) { - Object[] haystack = this.haystack.getAll(e); - Object[] needles = this.needles.getAll(e); - Object replacement = this.replacement.getSingle(e); + protected void execute(Event event) { + Object[] needles = this.needles.getAll(event); + if (haystack instanceof ExpressionList) { + for (Expression<?> haystackExpr : ((ExpressionList<?>) haystack).getExpressions()) { + replace(event, needles, haystackExpr); + } + } else { + replace(event, needles, haystack); + } + } + + private void replace(Event event, Object[] needles, Expression<?> haystackExpr) { + Object[] haystack = haystackExpr.getAll(event); + Object replacement = this.replacement.getSingle(event); if (replacement == null || haystack == null || haystack.length == 0 || needles == null || needles.length == 0) return; if (replaceString) { @@ -108,7 +119,7 @@ protected void execute(Event e) { haystack[x] = StringUtils.replace((String) haystack[x], (String) n, (String) replacement, caseSensitive); } } - this.haystack.change(e, haystack, ChangeMode.SET); + haystackExpr.change(event, haystack, ChangeMode.SET); } else { for (Inventory inv : (Inventory[]) haystack) for (ItemType needle : (ItemType[]) needles) @@ -127,11 +138,11 @@ protected void execute(Event e) { } @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { if (replaceFirst) - return "replace first " + needles.toString(e, debug) + " in " + haystack.toString(e, debug) + " with " + replacement.toString(e, debug) + return "replace first " + needles.toString(event, debug) + " in " + haystack.toString(event, debug) + " with " + replacement.toString(event, debug) + "(case sensitive: " + caseSensitive + ")"; - return "replace " + needles.toString(e, debug) + " in " + haystack.toString(e, debug) + " with " + replacement.toString(e, debug) + return "replace " + needles.toString(event, debug) + " in " + haystack.toString(event, debug) + " with " + replacement.toString(event, debug) + "(case sensitive: " + caseSensitive + ")"; } diff --git a/src/test/skript/tests/syntaxes/effects/EffReplace.sk b/src/test/skript/tests/syntaxes/effects/EffReplace.sk index fbc91c963c9..bb40dc3c6d3 100644 --- a/src/test/skript/tests/syntaxes/effects/EffReplace.sk +++ b/src/test/skript/tests/syntaxes/effects/EffReplace.sk @@ -41,3 +41,10 @@ test "replace strings": assert {_text::6} is "My name is bob and this is my friend bob!" with "replace all pattern 1 (case sensitivity enabled) (%{_text::5}%) failed" assert {_text::7} is "My name is BOBBY and this is my friend bob!" with "replace first pattern 2 (case sensitivity enabled) (%{_text::8}%) failed" assert {_text::8} is "My name is bob and this is my friend bob!" with "replace first pattern 1 (case sensitivity enabled) (%{_text::7}%) failed" + + # Replacing in an ExpressionList + set {_list::*} to "1", "2", "12" + replace all "1" in {_list::1}, {_list::2} and {_list::3} with "(replaced)" + assert {_list::1} is "(replaced)" with "Incorrect ExpressionList replacement value (1)" + assert {_list::2} is "2" with "Incorrect ExpressionList replacement value (2)" + assert {_list::3} is "(replaced)2" with "Incorrect ExpressionList replacement value (3)" From 291b4d6f1ddd92200a86aa6bbf70d294dc41cfae Mon Sep 17 00:00:00 2001 From: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> Date: Sat, 10 Dec 2022 13:58:35 -0600 Subject: [PATCH 152/619] Fix "target falling block" not working (#5233) --- src/main/java/ch/njol/skript/entity/FallingBlockData.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/ch/njol/skript/entity/FallingBlockData.java b/src/main/java/ch/njol/skript/entity/FallingBlockData.java index f911133d415..46c8b18e5a5 100644 --- a/src/main/java/ch/njol/skript/entity/FallingBlockData.java +++ b/src/main/java/ch/njol/skript/entity/FallingBlockData.java @@ -88,8 +88,6 @@ public ItemType convert(ItemType t) { Skript.error(m_not_a_block_error.toString()); return false; } - } else { - types = new ItemType[] {new ItemType(Material.STONE)}; } return true; } @@ -116,7 +114,7 @@ protected boolean match(final FallingBlock entity) { @Override @Nullable public FallingBlock spawn(Location loc, @Nullable Consumer<FallingBlock> consumer) { - ItemType t = CollectionUtils.getRandom(types); + ItemType t = types == null ? new ItemType(Material.STONE) : CollectionUtils.getRandom(types); assert t != null; Material material = t.getMaterial(); From cb270b6a3b1ec4aef26017058ca973154b75f80c Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Sat, 10 Dec 2022 12:58:49 -0700 Subject: [PATCH 153/619] Readme fix example not being latest (#5226) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3e5049eba42..79036e30784 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Skript requires **Spigot** to work. You heard it right, Bukkit does *not* work. parts of Skript to be available. Skript supports only the **latest** patch versions of Minecraft 1.9+. -For example, this means that 1.16.4 is supported, but 1.16.3 is *not*. +For example, this means that 1.16.5 is supported, but 1.16.4 is *not*. Testing with all old patch versions is not feasible for us. Minecraft 1.8 and earlier are not, and will not be supported. New Minecraft From 95e2db6f1fb9c0ff8f6c8ee9a4f1b20fde8bee19 Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Sun, 11 Dec 2022 00:54:56 +0300 Subject: [PATCH 154/619] Change Maven dependency (#5240) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 79036e30784..1e8540dccd6 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,7 @@ Or, if you use Maven: <groupId>com.github.SkriptLang</groupId> <artifactId>Skript</artifactId> <version>[versionTag]</version> + <scope>provided</scope> </dependency> ``` From 006895d335d1b618a89e35264edb43a3132088d9 Mon Sep 17 00:00:00 2001 From: sovdee <ethansovde@gmail.com> Date: Wed, 14 Dec 2022 00:55:58 -0500 Subject: [PATCH 155/619] Make BlockPlaceEvent's event-item value use getItemInHand() (#5137) --- .../classes/data/BukkitEventValues.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) 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 3ae4e8f8c7f..b407992ec16 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -46,6 +46,7 @@ import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; +import org.bukkit.GameMode; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.block.BlockState; @@ -238,6 +239,30 @@ public Player get(final BlockPlaceEvent e) { return e.getPlayer(); } }, 0); + EventValues.registerEventValue(BlockPlaceEvent.class, ItemStack.class, new Getter<ItemStack, BlockPlaceEvent>() { + @Override + @Nullable + public ItemStack get(BlockPlaceEvent event) { + return event.getItemInHand(); + } + }, EventValues.TIME_PAST); + EventValues.registerEventValue(BlockPlaceEvent.class, ItemStack.class, new Getter<ItemStack, BlockPlaceEvent>() { + @Override + @Nullable + public ItemStack get(BlockPlaceEvent event) { + return event.getItemInHand(); + } + }, EventValues.TIME_NOW); + EventValues.registerEventValue(BlockPlaceEvent.class, ItemStack.class, new Getter<ItemStack, BlockPlaceEvent>() { + @Override + @Nullable + public ItemStack get(BlockPlaceEvent event) { + ItemStack item = event.getItemInHand().clone(); + if (event.getPlayer().getGameMode() != GameMode.CREATIVE) + item.setAmount(item.getAmount() - 1); + return item; + } + }, EventValues.TIME_FUTURE); EventValues.registerEventValue(BlockPlaceEvent.class, Block.class, new Getter<Block, BlockPlaceEvent>() { @Override public Block get(final BlockPlaceEvent e) { From ca3342666db52c13ad92153de0f0c402f72e11fa Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Fri, 16 Dec 2022 03:01:07 +0300 Subject: [PATCH 156/619] Add Block Data support to ExprTypeOf (#5172) --- .../njol/skript/expressions/ExprTypeOf.java | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprTypeOf.java b/src/main/java/ch/njol/skript/expressions/ExprTypeOf.java index 7a83a6b88e9..4f901e90407 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTypeOf.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTypeOf.java @@ -27,24 +27,27 @@ import ch.njol.skript.expressions.base.SimplePropertyExpression; import ch.njol.skript.lang.util.ConvertedExpression; import ch.njol.skript.registrations.Converters; +import org.bukkit.block.data.BlockData; import org.bukkit.inventory.Inventory; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import org.eclipse.jdt.annotation.Nullable; @Name("Type of") -@Description({"Type of a block, item, entity, inventory or potion effect.", - "Types of items and blocks are item types similar to them but have amounts", +@Description({ + "Type of a block, item, entity, inventory or potion effect.", + "Types of items, blocks and block datas are item types similar to them but have amounts", "of one, no display names and, on Minecraft 1.13 and newer versions, are undamaged.", "Types of entities and inventories are entity types and inventory types known to Skript.", - "Types of potion effects are potion effect types."}) + "Types of potion effects are potion effect types." +}) @Examples({"on rightclick on an entity:", "\tmessage \"This is a %type of clicked entity%!\""}) -@Since("1.4, 2.5.2 (potion effect)") +@Since("1.4, 2.5.2 (potion effect), INSERT VERSION (block datas)") public class ExprTypeOf extends SimplePropertyExpression<Object, Object> { static { - register(ExprTypeOf.class, Object.class, "type", "entitydatas/itemtypes/inventories/potioneffects"); + register(ExprTypeOf.class, Object.class, "type", "entitydatas/itemtypes/inventories/potioneffects/blockdatas"); } @Override @@ -54,7 +57,7 @@ protected String getPropertyName() { @Override @Nullable - public Object convert(final Object o) { + public Object convert(Object o) { if (o instanceof EntityData) { return ((EntityData<?>) o).getSuperType(); } else if (o instanceof ItemType) { @@ -63,6 +66,8 @@ public Object convert(final Object o) { return ((Inventory) o).getType(); } else if (o instanceof PotionEffect) { return ((PotionEffect) o).getType(); + } else if (o instanceof BlockData) { + return new ItemType(((BlockData) o).getMaterial()); } assert false; return null; @@ -73,7 +78,8 @@ public Class<?> getReturnType() { Class<?> returnType = getExpr().getReturnType(); return EntityData.class.isAssignableFrom(returnType) ? EntityData.class : ItemType.class.isAssignableFrom(returnType) ? ItemType.class - : PotionEffectType.class.isAssignableFrom(returnType) ? PotionEffectType.class : Object.class; + : PotionEffectType.class.isAssignableFrom(returnType) ? PotionEffectType.class + : BlockData.class.isAssignableFrom(returnType) ? ItemType.class : Object.class; } @Override From 04d50493b4dfe9c70a666a4fff6ddd493fd0093f Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <alali_ayham@yahoo.com> Date: Fri, 16 Dec 2022 04:24:11 +0300 Subject: [PATCH 157/619] =?UTF-8?q?=F0=9F=9A=80=20Add=20anvil=20input=20te?= =?UTF-8?q?xt=20expression=20(#4618)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: APickledWalrus <apickledwalrus@gmail.com> --- .../skript/expressions/ExprAnvilText.java | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprAnvilText.java diff --git a/src/main/java/ch/njol/skript/expressions/ExprAnvilText.java b/src/main/java/ch/njol/skript/expressions/ExprAnvilText.java new file mode 100644 index 00000000000..d2f9f36df45 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprAnvilText.java @@ -0,0 +1,71 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.inventory.AnvilInventory; +import org.bukkit.inventory.Inventory; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Anvil Text Input") +@Description("An expression to get the name to be applied to an item in an anvil inventory.") +@Examples({ + "on inventory click:", + "\ttype of event-inventory is anvil inventory", + "\tif the anvil input text of the event-inventory is \"FREE OP\":", + "\t\tban player" +}) +@Since("INSERT VERSION") +public class ExprAnvilText extends SimplePropertyExpression<Inventory, String> { + + static { + register(ExprAnvilText.class, String.class, "anvil [inventory] (rename|text) input", "inventories"); + } + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + return true; + } + + @Override + @Nullable + public String convert(Inventory inv) { + if (!(inv instanceof AnvilInventory)) + return null; + return ((AnvilInventory) inv).getRenameText(); + } + + @Override + public Class<? extends String> getReturnType() { + return String.class; + } + + @Override + public String getPropertyName() { + return "anvil text input"; + } + +} From 67497ac224ce0845c0e6bd60245e908773d4a611 Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Fri, 16 Dec 2022 06:36:44 +0300 Subject: [PATCH 158/619] set block to player skull (#5093) --- .../java/ch/njol/skript/aliases/ItemType.java | 15 +++++++- .../skript/classes/data/DefaultChangers.java | 34 +++++++++---------- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/src/main/java/ch/njol/skript/aliases/ItemType.java b/src/main/java/ch/njol/skript/aliases/ItemType.java index 99da8dac535..6b42a628622 100644 --- a/src/main/java/ch/njol/skript/aliases/ItemType.java +++ b/src/main/java/ch/njol/skript/aliases/ItemType.java @@ -37,14 +37,17 @@ import ch.njol.skript.classes.Comparator.Relation; import org.bukkit.Location; import org.bukkit.Material; +import org.bukkit.OfflinePlayer; import org.bukkit.block.Block; import org.bukkit.block.BlockState; +import org.bukkit.block.Skull; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.Player; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.SkullMeta; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.aliases.ItemData.OldItemData; @@ -357,8 +360,18 @@ public boolean setBlock(Block block, boolean applyPhysics) { Material blockType = ItemUtils.asBlock(d.type); if (blockType == null) // Ignore items which cannot be placed continue; - if (BlockUtils.set(block, blockType, d.getBlockValues(), applyPhysics)) + if (BlockUtils.set(block, blockType, d.getBlockValues(), applyPhysics)) { + ItemMeta itemMeta = getItemMeta(); + if (itemMeta instanceof SkullMeta) { + OfflinePlayer offlinePlayer = ((SkullMeta) itemMeta).getOwningPlayer(); + if (offlinePlayer == null) + continue; + Skull skull = (Skull) block.getState(); + skull.setOwningPlayer(offlinePlayer); + skull.update(false, applyPhysics); + } return true; + } } return false; } diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultChangers.java b/src/main/java/ch/njol/skript/classes/data/DefaultChangers.java index 4a3d64fb72a..de48c85685a 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultChangers.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultChangers.java @@ -292,16 +292,16 @@ public Class<?>[] acceptChange(final ChangeMode mode) { @Override public void change(final Block[] blocks, final @Nullable Object[] delta, final ChangeMode mode) { - for (final Block block : blocks) { + for (Block block : blocks) { assert block != null; switch (mode) { case SET: assert delta != null; - Object o = delta[0]; - if (o instanceof ItemType) { - ((ItemType) delta[0]).getBlock().setBlock(block, true); - } else if (o instanceof BlockData) { - block.setBlockData(((BlockData) o)); + Object object = delta[0]; + if (object instanceof ItemType) { + ((ItemType) object).getBlock().setBlock(block, true); + } else if (object instanceof BlockData) { + block.setBlockData(((BlockData) object)); } break; case DELETE: @@ -311,30 +311,30 @@ public void change(final Block[] blocks, final @Nullable Object[] delta, final C case REMOVE: case REMOVE_ALL: assert delta != null; - final BlockState state = block.getState(); + BlockState state = block.getState(); if (!(state instanceof InventoryHolder)) break; - final Inventory invi = ((InventoryHolder) state).getInventory(); + Inventory invi = ((InventoryHolder) state).getInventory(); if (mode == ChangeMode.ADD) { - for (final Object d : delta) { - if (d instanceof Inventory) { - for (final ItemStack i : (Inventory) d) { + for (Object obj : delta) { + if (obj instanceof Inventory) { + for (ItemStack i : (Inventory) obj) { if (i != null) invi.addItem(i); } } else { - ((ItemType) d).addTo(invi); + ((ItemType) obj).addTo(invi); } } } else { - for (final Object d : delta) { - if (d instanceof Inventory) { - invi.removeItem(((Inventory) d).getContents()); + for (Object obj : delta) { + if (obj instanceof Inventory) { + invi.removeItem(((Inventory) obj).getContents()); } else { if (mode == ChangeMode.REMOVE) - ((ItemType) d).removeFrom(invi); + ((ItemType) obj).removeFrom(invi); else - ((ItemType) d).removeAll(invi); + ((ItemType) obj).removeAll(invi); } } } From 6aed3b7f0479f82e8888e6626291f1f12b99e4a6 Mon Sep 17 00:00:00 2001 From: Matthew <98179506+nylhus@users.noreply.github.com> Date: Fri, 16 Dec 2022 00:58:25 -0400 Subject: [PATCH 159/619] Removed Op EntityData. (#5051) Co-authored-by: Ayham Al Ali <alali_ayham@yahoo.com> --- .../ch/njol/skript/conditions/CondIsOp.java | 48 +++++++ .../ch/njol/skript/entity/EntityData.java | 16 +-- .../ch/njol/skript/entity/PlayerData.java | 110 --------------- .../njol/skript/entity/SimpleEntityData.java | 2 + .../ch/njol/skript/expressions/ExprOps.java | 132 ++++++++++++++++++ src/main/resources/lang/default.lang | 6 - 6 files changed, 187 insertions(+), 127 deletions(-) create mode 100644 src/main/java/ch/njol/skript/conditions/CondIsOp.java delete mode 100644 src/main/java/ch/njol/skript/entity/PlayerData.java create mode 100644 src/main/java/ch/njol/skript/expressions/ExprOps.java diff --git a/src/main/java/ch/njol/skript/conditions/CondIsOp.java b/src/main/java/ch/njol/skript/conditions/CondIsOp.java new file mode 100644 index 00000000000..fd90a64942d --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondIsOp.java @@ -0,0 +1,48 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.conditions; + +import ch.njol.skript.conditions.base.PropertyCondition; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import org.bukkit.OfflinePlayer; + +@Name("Is Operator") +@Description("Checks whether a player is a server operator.") +@Examples("player is an operator") +@Since("INSERT VERSION") +public class CondIsOp extends PropertyCondition<OfflinePlayer> { + + static { + register(CondIsOp.class, PropertyType.BE, "[[a] server|an] op[erator][s]", "offlineplayers"); + } + + @Override + public boolean check(OfflinePlayer player) { + return player.isOp(); + } + + @Override + protected String getPropertyName() { + return "op"; + } + +} diff --git a/src/main/java/ch/njol/skript/entity/EntityData.java b/src/main/java/ch/njol/skript/entity/EntityData.java index b8261e26d04..f797c61d7f8 100644 --- a/src/main/java/ch/njol/skript/entity/EntityData.java +++ b/src/main/java/ch/njol/skript/entity/EntityData.java @@ -462,18 +462,12 @@ public E[] getAll(final World... worlds) { public static <E extends Entity> E[] getAll(final EntityData<?>[] types, final Class<E> type, @Nullable World[] worlds) { assert types.length > 0; if (type == Player.class) { - if (worlds == null && types.length == 1 && types[0] instanceof PlayerData && ((PlayerData) types[0]).op == 0) + if (worlds == null) return (E[]) Bukkit.getOnlinePlayers().toArray(new Player[0]); - final List<Player> list = new ArrayList<>(); - for (final Player p : Bukkit.getOnlinePlayers()) { - if (worlds != null && !CollectionUtils.contains(worlds, p.getWorld())) - continue; - for (final EntityData<?> t : types) { - if (t.isInstance(p)) { - list.add(p); - break; - } - } + List<Player> list = new ArrayList<>(); + for (Player p : Bukkit.getOnlinePlayers()) { + if (CollectionUtils.contains(worlds, p.getWorld())) + list.add(p); } return (E[]) list.toArray(new Player[list.size()]); } diff --git a/src/main/java/ch/njol/skript/entity/PlayerData.java b/src/main/java/ch/njol/skript/entity/PlayerData.java deleted file mode 100644 index 195e1347cc9..00000000000 --- a/src/main/java/ch/njol/skript/entity/PlayerData.java +++ /dev/null @@ -1,110 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.entity; - -import org.bukkit.Location; -import org.bukkit.entity.Player; -import org.bukkit.util.Consumer; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.lang.Literal; -import ch.njol.skript.lang.SkriptParser.ParseResult; - -/** - * @author Peter Güttinger - */ -public class PlayerData extends EntityData<Player> { - static { - EntityData.register(PlayerData.class, "player", Player.class, 1, "non-op", "player", "op"); - } - - // used by EntityData.getAll to efficiently get all players - int op = 0; - - @Override - protected boolean init(final Literal<?>[] exprs, final int matchedPattern, final ParseResult parseResult) { - op = matchedPattern - 1; - return true; - } - - @Override - protected boolean init(final @Nullable Class<? extends Player> c, final @Nullable Player e) { - op = e == null ? 0 : e.isOp() ? 1 : -1; - return true; - } - - @Override - public void set(final Player p) { - if (op != 0) - p.setOp(op == 1); - } - - @Override - protected boolean match(final Player p) { - return op == 0 || p.isOp() == (op == 1); - } - - @Override - public Class<? extends Player> getType() { - return Player.class; - } - - @Override - @Nullable - public Player spawn(Location loc, @Nullable Consumer<Player> consumer) { - return null; - } - - @Override - protected int hashCode_i() { - return op; - } - - @Override - protected boolean equals_i(final EntityData<?> obj) { - if (!(obj instanceof PlayerData)) - return false; - final PlayerData other = (PlayerData) obj; - return op == other.op; - } - -// return "" + op; - @Override - protected boolean deserialize(final String s) { - try { - op = Integer.parseInt(s); - return true; - } catch (final NumberFormatException e) { - return false; - } - } - - @Override - public boolean isSupertypeOf(final EntityData<?> e) { - if (e instanceof PlayerData) - return op == 0 || ((PlayerData) e).op == op; - return false; - } - - @Override - public EntityData getSuperType() { - return new PlayerData(); - } - -} diff --git a/src/main/java/ch/njol/skript/entity/SimpleEntityData.java b/src/main/java/ch/njol/skript/entity/SimpleEntityData.java index 7fb1fffaa20..f0b44810992 100644 --- a/src/main/java/ch/njol/skript/entity/SimpleEntityData.java +++ b/src/main/java/ch/njol/skript/entity/SimpleEntityData.java @@ -86,6 +86,7 @@ import org.bukkit.entity.Piglin; import org.bukkit.entity.PiglinBrute; import org.bukkit.entity.Pillager; +import org.bukkit.entity.Player; import org.bukkit.entity.PolarBear; import org.bukkit.entity.Projectile; import org.bukkit.entity.PufferFish; @@ -209,6 +210,7 @@ private static void addSuperEntity(String codeName, Class<? extends Entity> enti addSimpleEntity("magma cube", MagmaCube.class); addSimpleEntity("slime", Slime.class); addSimpleEntity("painting", Painting.class); + addSimpleEntity("player", Player.class); addSimpleEntity("zombie pigman", PigZombie.class); addSimpleEntity("silverfish", Silverfish.class); addSimpleEntity("snowball", Snowball.class); diff --git a/src/main/java/ch/njol/skript/expressions/ExprOps.java b/src/main/java/ch/njol/skript/expressions/ExprOps.java new file mode 100644 index 00000000000..8fa9ae65eb7 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprOps.java @@ -0,0 +1,132 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.ArrayList; +import java.util.List; + +@Name("All Operators") +@Description("The list of operators on the server.") +@Examples("set {_ops::*} to all operators") +@Since("INSERT VERSION") +public class ExprOps extends SimpleExpression<OfflinePlayer> { + + private boolean nonOps; + + static { + Skript.registerExpression(ExprOps.class, OfflinePlayer.class, ExpressionType.SIMPLE, "[all [[of] the]|the] [server] [:non(-| )]op[erator]s"); + } + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + nonOps = parseResult.hasTag("non"); + return true; + } + + @Override + protected OfflinePlayer[] get(Event event) { + if (nonOps) { + List<Player> nonOpsList = new ArrayList<>(); + for (Player player : Bukkit.getOnlinePlayers()) { + if (!player.isOp()) + nonOpsList.add(player); + } + return nonOpsList.toArray(new Player[0]); + } + return Bukkit.getOperators().toArray(new OfflinePlayer[0]); + } + + @Nullable + @Override + public Class<?>[] acceptChange(ChangeMode mode) { + if (nonOps) + return null; + switch (mode) { + case ADD: + case SET: + case REMOVE: + case RESET: + case DELETE: + return CollectionUtils.array(OfflinePlayer[].class); + } + return null; + } + + @Override + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + if (delta == null && mode != ChangeMode.RESET && mode != ChangeMode.DELETE) + return; + switch (mode) { + case SET: + for (OfflinePlayer player : Bukkit.getOperators()) + player.setOp(false); + case ADD: + for (Object player : delta) + ((OfflinePlayer) player).setOp(true); + break; + case REMOVE: + for (Object player : delta) + ((OfflinePlayer) player).setOp(false); + break; + case DELETE: + case RESET: + for (OfflinePlayer player : Bukkit.getOperators()) + player.setOp(false); + break; + default: + assert false; + } + } + + @Override + public boolean isSingle() { + return false; + } + + @Override + public Class<? extends OfflinePlayer> getReturnType() { + return OfflinePlayer.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + if (nonOps) + return "all non-operators"; + return "all operators"; + } + +} diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 58bac190ed4..237a6857ff9 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -655,12 +655,6 @@ entities: player: name: player¦s pattern: player(|1¦s) - op: - name: op¦s @an - pattern: op(|1¦s) - non-op: - name: non-op¦s - pattern: non(-| |)op(|1¦s) zombie pigman: name: zombie pig¦man¦men|zombified piglin pattern: <age> (zombie pigm(an|1¦en)|zombified piglin(|1¦s))|(4¦)(zombie pigletboy(|1¦s)|zombified piglin (kid(|1¦s)|child(|1¦ren))) From a761bbb0e217617d61346de8aa512517b5bdbd43 Mon Sep 17 00:00:00 2001 From: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> Date: Fri, 16 Dec 2022 14:27:04 -0600 Subject: [PATCH 160/619] Implement and/or condition sections (#5165) * Implement any/and conditions * Some minor refactoring * Address reviews and add support for else if any/all sections * Fix getNextNode changing current node and check structure before parsing conditions * Address reviews * Address reviews --- .../njol/skript/sections/SecConditional.java | 195 ++++++++++++++---- .../tests/syntaxes/sections/SecConditional.sk | 113 ++++++++++ 2 files changed, 270 insertions(+), 38 deletions(-) diff --git a/src/main/java/ch/njol/skript/sections/SecConditional.java b/src/main/java/ch/njol/skript/sections/SecConditional.java index b841dbe7dd9..8b983b35f24 100644 --- a/src/main/java/ch/njol/skript/sections/SecConditional.java +++ b/src/main/java/ch/njol/skript/sections/SecConditional.java @@ -18,7 +18,9 @@ */ package ch.njol.skript.sections; +import ch.njol.skript.ScriptLoader; import ch.njol.skript.Skript; +import ch.njol.skript.config.Node; import ch.njol.skript.config.SectionNode; import ch.njol.skript.events.bukkit.SkriptParseEvent; import ch.njol.skript.lang.Condition; @@ -27,32 +29,49 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.TriggerItem; import ch.njol.skript.lang.parser.ParserInstance; +import ch.njol.skript.patterns.PatternCompiler; +import ch.njol.skript.patterns.SkriptPattern; +import ch.njol.skript.util.Patterns; import ch.njol.util.Kleenean; +import com.google.common.collect.Iterables; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; import org.skriptlang.skript.lang.structure.Structure; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; @SuppressWarnings("NotNullFieldNotInitialized") public class SecConditional extends Section { + private static final SkriptPattern THEN_PATTERN = PatternCompiler.compile("then [run]"); + private static final Patterns<ConditionalType> CONDITIONAL_PATTERNS = new Patterns<>(new Object[][] { + {"else", ConditionalType.ELSE}, + {"else [:parse] if <.+>", ConditionalType.ELSE_IF}, + {"else [:parse] if (:any|any:at least one [of])", ConditionalType.ELSE_IF}, + {"else [:parse] if [all]", ConditionalType.ELSE_IF}, + {"[:parse] if (:any|any:at least one [of])", ConditionalType.IF}, + {"[:parse] if [all]", ConditionalType.IF}, + {"[:parse] if <.+>", ConditionalType.IF}, + {THEN_PATTERN.toString(), ConditionalType.THEN}, + {"implicit:<.+>", ConditionalType.IF} + }); + static { - Skript.registerSection(SecConditional.class, - "else", - "else [(1¦parse)] if <.+>", - "[(1¦parse if|2¦if)] <.+>"); + Skript.registerSection(SecConditional.class, CONDITIONAL_PATTERNS.getPatterns()); } private enum ConditionalType { - ELSE, ELSE_IF, IF + ELSE, ELSE_IF, IF, THEN } private ConditionalType type; - private Condition condition; + private List<Condition> conditions = new ArrayList<>(); + private boolean ifAny; private boolean parseIf; private boolean parseIfPassed; + private boolean multiline; private Kleenean hasDelayAfter; @@ -63,10 +82,49 @@ public boolean init(Expression<?>[] exprs, ParseResult parseResult, SectionNode sectionNode, List<TriggerItem> triggerItems) { - type = ConditionalType.values()[matchedPattern]; - parseIf = parseResult.mark == 1; + type = CONDITIONAL_PATTERNS.getInfo(matchedPattern); + ifAny = parseResult.hasTag("any"); + parseIf = parseResult.hasTag("parse"); + multiline = parseResult.regexes.size() == 0 && type != ConditionalType.ELSE; + + // ensure this conditional is chained correctly (e.g. an else must have an if) + SecConditional lastIf; + if (type != ConditionalType.IF) { + lastIf = getClosestIf(triggerItems); + if (lastIf == null) { + if (type == ConditionalType.ELSE_IF) { + Skript.error("'else if' has to be placed just after another 'if' or 'else if' section"); + } else if (type == ConditionalType.ELSE) { + Skript.error("'else' has to be placed just after another 'if' or 'else if' section"); + } else if (type == ConditionalType.THEN) { + Skript.error("'then' has to placed just after a multiline 'if' or 'else if' section"); + } + return false; + } else if (!lastIf.multiline && type == ConditionalType.THEN) { + Skript.error("'then' has to placed just after a multiline 'if' or 'else if' section"); + return false; + } + } else { + // if this is a multiline if, we need to check if there is a "then" section after this + if (multiline) { + Node nextNode = getNextNode(sectionNode, getParser()); + String error = (ifAny ? "'if any'" : "'if all'") + " has to be placed just before a 'then' section"; + if (nextNode instanceof SectionNode && nextNode.getKey() != null) { + String nextNodeKey = ScriptLoader.replaceOptions(nextNode.getKey()); + if (THEN_PATTERN.match(nextNodeKey) == null) { + Skript.error(error); + return false; + } + } else { + Skript.error(error); + return false; + } + } + lastIf = null; + } + + // if this an "if" or "else if", let's try to parse the conditions right away if (type == ConditionalType.IF || type == ConditionalType.ELSE_IF) { - String expr = parseResult.regexes.get(0).group(); ParserInstance parser = getParser(); Class<? extends Event>[] currentEvents = parser.getCurrentEvents(); String currentEventName = parser.getCurrentEventName(); @@ -79,8 +137,40 @@ public boolean init(Expression<?>[] exprs, parser.setCurrentEventName("parse"); parser.setCurrentStructure(null); } - // Don't print a default error if 'if' keyword wasn't provided - condition = Condition.parse(expr, parseResult.mark != 0 ? "Can't understand this condition: '" + expr + "'" : null); + + // if this is a multiline "if", we have to parse each line as its own condition + if (multiline) { + // we have to get the size of the iterator here as SectionNode#size includes empty/void nodes + int nonEmptyNodeCount = Iterables.size(sectionNode); + if (nonEmptyNodeCount < 2) { + Skript.error((ifAny ? "'if any'" : "'if all'") + " sections must contain at least two conditions"); + return false; + } + for (Node childNode : sectionNode) { + if (childNode instanceof SectionNode) { + Skript.error((ifAny ? "'if any'" : "'if all'") + " sections may not contain other sections"); + return false; + } + String childKey = childNode.getKey(); + if (childKey != null) { + childKey = ScriptLoader.replaceOptions(childKey); + parser.setNode(childNode); + Condition condition = Condition.parse(childKey, "Can't understand this condition: '" + childKey + "'"); + // if this condition was invalid, don't bother parsing the rest + if (condition == null) + return false; + conditions.add(condition); + } + } + parser.setNode(sectionNode); + } else { + // otherwise, this is just a simple single line "if", with the condition on the same line + String expr = parseResult.regexes.get(0).group(); + // Don't print a default error if 'if' keyword wasn't provided + Condition condition = Condition.parse(expr, parseResult.hasTag("implicit") ? null : "Can't understand this condition: '" + expr + "'"); + if (condition != null) + conditions.add(condition); + } if (parseIf) { parser.setCurrentEvents(currentEvents); @@ -88,34 +178,21 @@ public boolean init(Expression<?>[] exprs, parser.setCurrentStructure(currentStructure); } - if (condition == null) + if (conditions.isEmpty()) return false; } - SecConditional lastIf; - if (type != ConditionalType.IF) { - lastIf = getIf(triggerItems); - if (lastIf == null) { - if (type == ConditionalType.ELSE_IF) - Skript.error("'else if' has to be placed just after another 'if' or 'else if' section"); - else - Skript.error("'else' has to be placed just after another 'if' or 'else if' section"); - return false; - } - } else { - lastIf = null; - } - // ([else] parse if) If condition is valid and false, do not parse the section if (parseIf) { - if (!condition.check(new SkriptParseEvent())) { + if (!checkConditions(new SkriptParseEvent())) { return true; } parseIfPassed = true; } Kleenean hadDelayBefore = getParser().getHasDelayBefore(); - loadCode(sectionNode); + if (!multiline || type == ConditionalType.THEN) + loadCode(sectionNode); hasDelayAfter = getParser().getHasDelayBefore(); // If the code definitely has a delay before this section, or if the section did not alter the delayed Kleenean, @@ -159,14 +236,16 @@ public TriggerItem getNormalNext() { @Nullable @Override - protected TriggerItem walk(Event e) { - if (parseIf && !parseIfPassed) { + protected TriggerItem walk(Event event) { + if (type == ConditionalType.THEN || (parseIf && !parseIfPassed)) { return getNormalNext(); - } else if (type == ConditionalType.ELSE || parseIf || condition.check(e)) { + } else if (parseIf || checkConditions(event)) { + // if this is a multiline if, we need to run the "then" section instead + SecConditional sectionToRun = multiline ? (SecConditional) getNormalNext() : this; TriggerItem skippedNext = getSkippedNext(); - if (last != null) - last.setNext(skippedNext); - return first != null ? first : skippedNext; + if (sectionToRun.last != null) + sectionToRun.last.setNext(skippedNext); + return sectionToRun.first != null ? sectionToRun.first : skippedNext; } else { return getNormalNext(); } @@ -181,15 +260,21 @@ private TriggerItem getSkippedNext() { } @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { String parseIf = this.parseIf ? "parse " : ""; switch (type) { case IF: - return parseIf + "if " + condition.toString(e, debug); + if (multiline) + return parseIf + "if " + (ifAny ? "any" : "all"); + return parseIf + "if " + conditions.get(0).toString(event, debug); case ELSE_IF: - return "else " + parseIf + "if " + condition.toString(e, debug); + if (multiline) + return "else " + parseIf + "if " + (ifAny ? "any" : "all"); + return "else " + parseIf + "if " + conditions.get(0).toString(event, debug); case ELSE: return "else"; + case THEN: + return "then"; default: throw new IllegalStateException(); } @@ -200,15 +285,19 @@ private Kleenean getHasDelayAfter() { } @Nullable - private static SecConditional getIf(List<TriggerItem> triggerItems) { + private static SecConditional getClosestIf(List<TriggerItem> triggerItems) { + // loop through the triggerItems in reverse order so that we find the most recent items first for (int i = triggerItems.size() - 1; i >= 0; i--) { TriggerItem triggerItem = triggerItems.get(i); if (triggerItem instanceof SecConditional) { SecConditional secConditional = (SecConditional) triggerItem; if (secConditional.type == ConditionalType.IF) + // if the condition is an if, we found our most recent preceding "if" return secConditional; else if (secConditional.type == ConditionalType.ELSE) + // if the conditional is an else, return null because it belongs to a different condition and ends + // this one return null; } else { return null; @@ -235,4 +324,34 @@ private static List<SecConditional> getElseIfs(List<TriggerItem> triggerItems) { return list; } + private boolean checkConditions(Event event) { + if (conditions.isEmpty()) { // else and then + return true; + } else if (ifAny) { + return conditions.stream().anyMatch(c -> c.check(event)); + } else { + return conditions.stream().allMatch(c -> c.check(event)); + } + } + + @Nullable + private Node getNextNode(Node precedingNode, ParserInstance parser) { + // iterating over the parent node causes the current node to change, so we need to store it to reset it later + Node originalCurrentNode = parser.getNode(); + SectionNode parentNode = precedingNode.getParent(); + if (parentNode == null) + return null; + Iterator<Node> parentIterator = parentNode.iterator(); + while (parentIterator.hasNext()) { + Node current = parentIterator.next(); + if (current == precedingNode) { + Node nextNode = parentIterator.hasNext() ? parentIterator.next() : null; + parser.setNode(originalCurrentNode); + return nextNode; + } + } + parser.setNode(originalCurrentNode); + return null; + } + } diff --git a/src/test/skript/tests/syntaxes/sections/SecConditional.sk b/src/test/skript/tests/syntaxes/sections/SecConditional.sk index c9b0c6426f9..98fb633745d 100644 --- a/src/test/skript/tests/syntaxes/sections/SecConditional.sk +++ b/src/test/skript/tests/syntaxes/sections/SecConditional.sk @@ -30,3 +30,116 @@ test "SecConditional": exit 1 section else: assert 1 = 2 with "conditional failed ##5" + +test "SecConditional - if all true": + if: + 1 is 1 + 2 is 2 + then: + set {_a} to true + assert {_a} is set with "if all did not run" + +test "SecConditional - if all false": + if: + 1 is 1 + 2 is 1 + then: + set {_a} to true + assert {_a} is not set with "if all ran when conditions were false" + +test "SecConditional - if any true": + if any: + 1 is 3 + 2 is 2 + then: + set {_a} to true + assert {_a} is set with "if any did not run when at least one condition was true" + +test "SecConditional - if any false": + if any: + 1 is 3 + 2 is 7 + then: + set {_a} to true + assert {_a} is not set with "if any ran when no condition was true" + + +test "SecConditional - if any else": + if any: + 1 is 3 + 2 is 7 + then: + set {_a} to true + else: + set {_a} to false + assert {_a} is false with "if any did not run else when no condition was true" + +test "SecConditional - if all else": + if all: + 1 is 3 + 2 is 7 + then: + set {_a} to true + else: + set {_a} to false + assert {_a} is false with "if all did not run else when at least one condition was false" + +test "SecConditional - if all by default": + if: + 1 is 2 + 2 is 7 + then: + set {_a} to true + assert {_a} is not set with "bare 'if' did not require all conditions to be true" + +test "SecConditional - else if all true": + if: + 1 is 2 + 2 is 7 + then: + set {_a} to true + else if: + 1 is 1 + 2 is 2 + then: + set {_a} to false + assert {_a} is false with "'else if all' did not run when preceding 'if' was false and conditions were true" + +test "SecConditional - else if any true": + if: + 1 is 2 + 2 is 7 + then: + set {_a} to true + else if any: + 1 is 1 + 2 is 7 + then: + set {_a} to false + assert {_a} is false with "'else if any' did not run when preceding 'if' was false and at least one condition was true" + +test "SecConditional - else if any false": + if: + 1 is 2 + 2 is 7 + then: + set {_b} to true + else if any: + 1 is 8 + 2 is 7 + then: + set {_a} to false + assert {_a} is not set with "'else if any' ran even though all conditions were false" + +test "SecConditional - else if all false": + if: + 1 is 2 + 2 is 7 + then: + set {_b} to true + else if: + 1 is 8 + 2 is 7 + then: + set {_a} to false + assert {_a} is not set with "'else if all' ran even though all conditions were false" From e9b1e42f4f69eb66eaf2e7ee5de0eb723c212eff Mon Sep 17 00:00:00 2001 From: Shane Bee <shanebolenback@me.com> Date: Sat, 17 Dec 2022 17:03:42 -0800 Subject: [PATCH 161/619] DefaultComparators - add comparator for BlockData - BlockData (#5252) --- .../skript/classes/data/DefaultComparators.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java b/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java index d949ae2d5ff..0c95831e6a8 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java @@ -219,6 +219,19 @@ public boolean supportsOrdering() { } }); + // BlockData - BlockData + Comparators.registerComparator(BlockData.class, BlockData.class, new Comparator<BlockData, BlockData>() { + @Override + public Relation compare(BlockData data1, BlockData data2) { + return Relation.get(data1.matches(data2)); + } + + @Override + public boolean supportsOrdering() { + return false; + } + }); + // ItemType - ItemType Comparators.registerComparator(ItemType.class, ItemType.class, new Comparator<ItemType, ItemType>() { @Override From e28aacd52f4551b595f5040741386bd7d3d24557 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Wed, 21 Dec 2022 04:35:25 -0700 Subject: [PATCH 162/619] Add setTime to the code-conventions (#5248) Co-authored-by: Kenzie <admin@moderocky.com> --- code-conventions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code-conventions.md b/code-conventions.md index 1e522e78082..37ac182cd62 100644 --- a/code-conventions.md +++ b/code-conventions.md @@ -111,8 +111,8 @@ If we need to remove or alter contributed code due to a licensing issue we will * When extending one of following classes: SimpleExpression, SimplePropertyExpression, Effect, Condition... - Put overridden methods in order - - SimpleExpression: init -> get (getAll) -> (acceptChange) -> (change) -> isSingle -> getReturnType -> toString - - SimplePropertyExpression: -> (init) -> convert -> (acceptChange) -> (change) -> getReturnType -> getPropertyName + - SimpleExpression: init -> get/getAll -> acceptChange -> change -> setTime -> getTime -> isSingle -> getReturnType -> toString + - SimplePropertyExpression: -> init -> convert -> acceptChange -> change -> setTime -> getTime -> getReturnType -> getPropertyName - Effect: init -> execute -> toString - Condition: init -> check -> toString - PropertyCondition: (init) -> check -> (getPropertyType) -> getPropertyName From e41e2ba811210a8a817a694776d2e929c97fe9ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Dec 2022 20:41:40 -0700 Subject: [PATCH 163/619] Bump paper-api from 1.19.2-R0.1-SNAPSHOT to 1.19.3-R0.1-SNAPSHOT (#5241) --- build.gradle | 2 +- gradle.properties | 2 +- .../ch/njol/skript/util/BlockStateBlock.java | 11 ++ .../java/ch/njol/skript/util/BlockUtils.java | 2 +- .../njol/skript/util/DelayedChangeBlock.java | 153 ++++++++++-------- src/main/resources/lang/default.lang | 1 + .../{paper-1.19.2.json => paper-1.19.3.json} | 4 +- 7 files changed, 99 insertions(+), 76 deletions(-) rename src/test/skript/environments/java17/{paper-1.19.2.json => paper-1.19.3.json} (85%) diff --git a/build.gradle b/build.gradle index bf9c0b4c9a7..62f67dbf31f 100644 --- a/build.gradle +++ b/build.gradle @@ -24,7 +24,7 @@ allprojects { dependencies { shadow group: 'io.papermc', name: 'paperlib', version: '1.0.7' shadow group: 'org.bstats', name: 'bstats-bukkit', version: '3.0.0' - implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.19.2-R0.1-SNAPSHOT' + implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.19.3-R0.1-SNAPSHOT' implementation group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.700' implementation group: 'com.google.code.findbugs', name: 'findbugs', version: '3.0.1' implementation group: 'com.sk89q.worldguard', name: 'worldguard-legacy', version: '7.0.0-SNAPSHOT' diff --git a/gradle.properties b/gradle.properties index 6f3c474cb5c..4dc2e502499 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,5 +2,5 @@ groupid=ch.njol name=skript version=2.6.4 jarName=Skript.jar -testEnv=java17/paper-1.19.2 +testEnv=java17/paper-1.19.3 testEnvJavaVersion=17 diff --git a/src/main/java/ch/njol/skript/util/BlockStateBlock.java b/src/main/java/ch/njol/skript/util/BlockStateBlock.java index 91d37ef43a0..e61014acea0 100644 --- a/src/main/java/ch/njol/skript/util/BlockStateBlock.java +++ b/src/main/java/ch/njol/skript/util/BlockStateBlock.java @@ -523,4 +523,15 @@ public float getBreakSpeed(@NotNull Player player) { public @NotNull String translationKey() { return state.getBlock().getTranslationKey(); } + + @Override + public boolean breakNaturally(boolean triggerEffect, boolean dropExperience) { + return state.getBlock().breakNaturally(triggerEffect, dropExperience); + } + + @Override + public boolean breakNaturally(@NotNull ItemStack tool, boolean triggerEffect, boolean dropExperience) { + return state.getBlock().breakNaturally(tool, triggerEffect, dropExperience); + } + } diff --git a/src/main/java/ch/njol/skript/util/BlockUtils.java b/src/main/java/ch/njol/skript/util/BlockUtils.java index 6ae57133a76..92a7675951a 100644 --- a/src/main/java/ch/njol/skript/util/BlockUtils.java +++ b/src/main/java/ch/njol/skript/util/BlockUtils.java @@ -141,7 +141,7 @@ public static String blockToString(Block block, int flags) { * @return the actual CB block from the given argument */ public static Block extractBlock(Block block) { - return block instanceof DelayedChangeBlock ? ((DelayedChangeBlock) block).b : block; + return block instanceof DelayedChangeBlock ? ((DelayedChangeBlock) block).block : block; } } diff --git a/src/main/java/ch/njol/skript/util/DelayedChangeBlock.java b/src/main/java/ch/njol/skript/util/DelayedChangeBlock.java index 72dba53423a..44b30810018 100644 --- a/src/main/java/ch/njol/skript/util/DelayedChangeBlock.java +++ b/src/main/java/ch/njol/skript/util/DelayedChangeBlock.java @@ -58,18 +58,18 @@ public class DelayedChangeBlock implements Block { private static final boolean ISPASSABLE_METHOD_EXISTS = Skript.methodExists(Block.class, "isPassable"); - final Block b; + final Block block; @Nullable private final BlockState newState; private final boolean isPassable; - public DelayedChangeBlock(Block b) { - this(b, null); + public DelayedChangeBlock(Block block) { + this(block, null); } - public DelayedChangeBlock(Block b, @Nullable BlockState newState) { - assert b != null; - this.b = b; + public DelayedChangeBlock(Block block, @Nullable BlockState newState) { + assert block != null; + this.block = block; this.newState = newState; if (ISPASSABLE_METHOD_EXISTS && newState != null) this.isPassable = newState.getBlock().isPassable(); @@ -79,93 +79,93 @@ public DelayedChangeBlock(Block b, @Nullable BlockState newState) { @Override public void setMetadata(String metadataKey, MetadataValue newMetadataValue) { - b.setMetadata(metadataKey, newMetadataValue); + block.setMetadata(metadataKey, newMetadataValue); } @Override public List<MetadataValue> getMetadata(String metadataKey) { - return b.getMetadata(metadataKey); + return block.getMetadata(metadataKey); } @Override public boolean hasMetadata(String metadataKey) { - return b.hasMetadata(metadataKey); + return block.hasMetadata(metadataKey); } @Override public void removeMetadata(String metadataKey, Plugin owningPlugin) { - b.removeMetadata(metadataKey, owningPlugin); + block.removeMetadata(metadataKey, owningPlugin); } @SuppressWarnings("deprecation") @Override public byte getData() { - return b.getData(); + return block.getData(); } @Override public Block getRelative(int modX, int modY, int modZ) { - return b.getRelative(modX, modY, modZ); + return block.getRelative(modX, modY, modZ); } @Override public Block getRelative(BlockFace face) { - return b.getRelative(face); + return block.getRelative(face); } @Override public Block getRelative(BlockFace face, int distance) { - return b.getRelative(face, distance); + return block.getRelative(face, distance); } @Override public Material getType() { - return b.getType(); + return block.getType(); } @Override public byte getLightLevel() { - return b.getLightLevel(); + return block.getLightLevel(); } @Override public byte getLightFromSky() { - return b.getLightFromSky(); + return block.getLightFromSky(); } @Override public byte getLightFromBlocks() { - return b.getLightFromBlocks(); + return block.getLightFromBlocks(); } @Override public World getWorld() { - return b.getWorld(); + return block.getWorld(); } @Override public int getX() { - return b.getX(); + return block.getX(); } @Override public int getY() { - return b.getY(); + return block.getY(); } @Override public int getZ() { - return b.getZ(); + return block.getZ(); } @Override public Location getLocation() { - return b.getLocation(); + return block.getLocation(); } @Override public Chunk getChunk() { - return b.getChunk(); + return block.getChunk(); } @Override @@ -176,7 +176,7 @@ public void setType(Material type) { Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.getInstance(), new Runnable() { @Override public void run() { - b.setType(type); + block.setType(type); } }); } @@ -185,62 +185,62 @@ public void run() { @Nullable @Override public BlockFace getFace(Block block) { - return b.getFace(block); + return block.getFace(block); } @Override public BlockState getState() { - return b.getState(); + return block.getState(); } @Override public BlockState getState(boolean useSnapshot) { - return b.getState(useSnapshot); + return block.getState(useSnapshot); } @Override public Biome getBiome() { - return b.getBiome(); + return block.getBiome(); } @Override public @NotNull Biome getComputedBiome() { - return b.getComputedBiome(); + return block.getComputedBiome(); } @Override public void setBiome(Biome bio) { - b.setBiome(bio); + block.setBiome(bio); } @Override public boolean isBlockPowered() { - return b.isBlockPowered(); + return block.isBlockPowered(); } @Override public boolean isBlockIndirectlyPowered() { - return b.isBlockIndirectlyPowered(); + return block.isBlockIndirectlyPowered(); } @Override public boolean isBlockFacePowered(BlockFace face) { - return b.isBlockFacePowered(face); + return block.isBlockFacePowered(face); } @Override public boolean isBlockFaceIndirectlyPowered(BlockFace face) { - return b.isBlockFaceIndirectlyPowered(face); + return block.isBlockFaceIndirectlyPowered(face); } @Override public int getBlockPower(BlockFace face) { - return b.getBlockPower(face); + return block.getBlockPower(face); } @Override public int getBlockPower() { - return b.getBlockPower(); + return block.getBlockPower(); } @Override @@ -259,42 +259,42 @@ public boolean isLiquid() { @Override public boolean isBuildable() { - return b.isBuildable(); + return block.isBuildable(); } @Override public boolean isBurnable() { - return b.isBurnable(); + return block.isBurnable(); } @Override public boolean isReplaceable() { - return b.isReplaceable(); + return block.isReplaceable(); } @Override public boolean isSolid() { - return b.isSolid(); + return block.isSolid(); } @Override public boolean isCollidable() { - return b.isCollidable(); + return block.isCollidable(); } @Override public double getTemperature() { - return b.getTemperature(); + return block.getTemperature(); } @Override public double getHumidity() { - return b.getHumidity(); + return block.getHumidity(); } @Override public PistonMoveReaction getPistonMoveReaction() { - return b.getPistonMoveReaction(); + return block.getPistonMoveReaction(); } @Override @@ -305,7 +305,7 @@ public boolean breakNaturally() { Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.getInstance(), new Runnable() { @Override public void run() { - b.breakNaturally(); + block.breakNaturally(); } }); return true; @@ -320,7 +320,7 @@ public boolean breakNaturally(@Nullable ItemStack tool) { Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.getInstance(), new Runnable() { @Override public void run() { - b.breakNaturally(tool); + block.breakNaturally(tool); } }); return true; @@ -335,7 +335,7 @@ public boolean breakNaturally(boolean triggerEffect) { Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.getInstance(), new Runnable() { @Override public void run() { - b.breakNaturally(triggerEffect); + block.breakNaturally(triggerEffect); } }); return true; @@ -350,7 +350,7 @@ public boolean breakNaturally(ItemStack tool, boolean triggerEffect) { Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.getInstance(), new Runnable() { @Override public void run() { - b.breakNaturally(tool, triggerEffect); + block.breakNaturally(tool, triggerEffect); } }); return true; @@ -359,32 +359,32 @@ public void run() { @Override public void tick() { - b.tick(); + block.tick(); } @Override public void randomTick() { - b.randomTick(); + block.randomTick(); } @Override public boolean applyBoneMeal(BlockFace blockFace) { - return b.applyBoneMeal(blockFace); + return block.applyBoneMeal(blockFace); } @Override public Collection<ItemStack> getDrops() { - return b.getDrops(); + return block.getDrops(); } @Override public Collection<ItemStack> getDrops(@Nullable ItemStack tool) { - return b.getDrops(tool); + return block.getDrops(tool); } @Override public Collection<ItemStack> getDrops(ItemStack tool, @Nullable Entity entity) { - return b.getDrops(tool, entity); + return block.getDrops(tool, entity); } @Nullable @@ -409,7 +409,7 @@ public void setType(Material type, boolean applyPhysics) { Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.getInstance(), new Runnable() { @Override public void run() { - b.setType(type, applyPhysics); + block.setType(type, applyPhysics); } }); } @@ -417,7 +417,7 @@ public void run() { @Override public BlockData getBlockData() { - return b.getBlockData(); + return block.getBlockData(); } @Override @@ -430,14 +430,14 @@ public void setBlockData(BlockData data, boolean applyPhysics) { if (newState != null) { newState.setBlockData(data); } else { - Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.getInstance(), () -> b.setBlockData(data, applyPhysics)); + Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.getInstance(), () -> block.setBlockData(data, applyPhysics)); } } @Nullable @Override public RayTraceResult rayTrace(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode) { - return b.rayTrace(start, direction, maxDistance, fluidCollisionMode); + return block.rayTrace(start, direction, maxDistance, fluidCollisionMode); } @Override @@ -447,62 +447,73 @@ public boolean isPassable() { @Override public BoundingBox getBoundingBox() { - return b.getBoundingBox(); + return block.getBoundingBox(); } @Override public BlockSoundGroup getSoundGroup() { - return b.getSoundGroup(); + return block.getSoundGroup(); } @Override public @NotNull SoundGroup getBlockSoundGroup() { - return b.getBlockSoundGroup(); + return block.getBlockSoundGroup(); } @Override public String getTranslationKey() { - return b.getTranslationKey(); + return block.getTranslationKey(); } @Override public float getDestroySpeed(ItemStack itemStack) { - return b.getDestroySpeed(itemStack); + return block.getDestroySpeed(itemStack); } @Override public boolean isPreferredTool(@NotNull ItemStack tool) { - return b.isPreferredTool(tool); + return block.isPreferredTool(tool); } @Override public boolean isValidTool(@NotNull ItemStack itemStack) { - return b.isValidTool(itemStack); + return block.isValidTool(itemStack); } @Override public float getDestroySpeed(@NotNull ItemStack itemStack, boolean considerEnchants) { - return b.getDestroySpeed(itemStack, considerEnchants); + return block.getDestroySpeed(itemStack, considerEnchants); } @Override @NotNull public VoxelShape getCollisionShape() { - return b.getCollisionShape(); + return block.getCollisionShape(); } @Override public boolean canPlace(@NotNull BlockData data) { - return b.canPlace(data); + return block.canPlace(data); } @Override public float getBreakSpeed(@NotNull Player player) { - return b.getBreakSpeed(player); + return block.getBreakSpeed(player); } @Override public @NotNull String translationKey() { - return b.getTranslationKey(); + return block.getTranslationKey(); } + + @Override + public boolean breakNaturally(boolean triggerEffect, boolean dropExperience) { + return block.breakNaturally(triggerEffect, dropExperience); + } + + @Override + public boolean breakNaturally(@NotNull ItemStack tool, boolean triggerEffect, boolean dropExperience) { + return block.breakNaturally(tool, triggerEffect, dropExperience); + } + } diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 237a6857ff9..56ca96c866f 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -1746,6 +1746,7 @@ inventory types: stonecutter: stonecutter inventory smithing: smithing inventory composter: composter inventory + chiseled_bookshelf: chiseled bookshelf, bookshelf # -- Spawn Reasons -- spawn reasons: diff --git a/src/test/skript/environments/java17/paper-1.19.2.json b/src/test/skript/environments/java17/paper-1.19.3.json similarity index 85% rename from src/test/skript/environments/java17/paper-1.19.2.json rename to src/test/skript/environments/java17/paper-1.19.3.json index d3502b29da9..731f032034e 100644 --- a/src/test/skript/environments/java17/paper-1.19.2.json +++ b/src/test/skript/environments/java17/paper-1.19.3.json @@ -1,11 +1,11 @@ { - "name": "paper-1.19.2", + "name": "paper-1.19.3", "resources": [ {"source": "server.properties.generic", "target": "server.properties"} ], "paperDownloads": [ { - "version": "1.19.2", + "version": "1.19.3", "target": "paperclip.jar" } ], From 8ea17faddc4fa0f4de0c82d8dc7deedf2756438f Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Sat, 24 Dec 2022 03:24:45 -0700 Subject: [PATCH 164/619] Add missing lang nodes (#5221) * Add missing lang nodes * Remove extra line space * Add a missing teleport cause and change quickTests to 1.19.3 * Organize the default.lang file --- build.gradle | 2 +- src/main/resources/lang/default.lang | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index 62f67dbf31f..07b5d5fb6f3 100644 --- a/build.gradle +++ b/build.gradle @@ -186,7 +186,7 @@ void createTestTask(String name, String environments, boolean devMode, int javaV } } -def latestEnv = 'java17/paper-1.19.2.json' +def latestEnv = 'java17/paper-1.19.3.json' def latestJava = 17 def oldestJava = 8 diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 56ca96c866f..8f1631e5a32 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -1266,6 +1266,7 @@ teleport causes: plugin: plugin spectate: spectate, spectator unknown: unknown + dismount: dismount, dismounted # -- Game Modes -- game modes: @@ -1763,6 +1764,7 @@ spawn reasons: default: default dispense_egg: dispense egg, dispensing egg drowned: drowned + duplication: duplication egg: egg ender_pearl: ender pearl explosion: explosion @@ -1931,7 +1933,12 @@ types: cattype: cat type¦s @a gamerule: gamerule¦s @a attributetype: attribute type¦s @a - enchantmentoffer: enchantment offer¦s @a + enchantmentoffer: enchantment offer¦s @an + environment: environment¦s @an + moonphase: moonphase¦s @a + resourcepackstate: resource pack state¦s @a + gene: panda gene¦s @a + gamerulevalue: gamerule value¦s @a # Skript weathertype: weather type¦s @a @@ -1951,9 +1958,6 @@ types: experience.pattern: (e?xp|experience( points?)?) classinfo: type¦s @a visualeffect: visual effect¦s @a - resourcepackstate: resource pack state¦s @a - gene: panda gene¦s @a - gamerulevalue: gamerule value¦s @a # Hooks money: money From 9e0cfe5578dfe6d344e3124e40ba811439a17c6b Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Mon, 26 Dec 2022 15:33:12 -0700 Subject: [PATCH 165/619] Adds lookAt for entities and players (#4985) --- .../njol/skript/bukkitutil/EntityUtils.java | 35 ++-- .../skript/bukkitutil/PaperEntityUtils.java | 178 ++++++++++++++++++ .../java/ch/njol/skript/effects/EffLook.java | 123 ++++++++++++ 3 files changed, 322 insertions(+), 14 deletions(-) create mode 100644 src/main/java/ch/njol/skript/bukkitutil/PaperEntityUtils.java create mode 100644 src/main/java/ch/njol/skript/effects/EffLook.java diff --git a/src/main/java/ch/njol/skript/bukkitutil/EntityUtils.java b/src/main/java/ch/njol/skript/bukkitutil/EntityUtils.java index 4173f0331f9..0da7aee9a66 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/EntityUtils.java +++ b/src/main/java/ch/njol/skript/bukkitutil/EntityUtils.java @@ -18,24 +18,31 @@ */ package ch.njol.skript.bukkitutil; -import ch.njol.skript.Skript; -import ch.njol.skript.entity.EntityData; +import org.bukkit.Location; +import org.bukkit.entity.Ageable; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Piglin; +import org.bukkit.entity.Zoglin; +import org.bukkit.entity.Zombie; + import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; -import org.bukkit.Location; -import org.bukkit.entity.*; + +import ch.njol.skript.Skript; +import ch.njol.skript.entity.EntityData; /** * Utility class for quick {@link Entity} methods */ public class EntityUtils { - + private static final boolean HAS_PIGLINS = Skript.classExists("org.bukkit.entity.Piglin"); /** * Cache Skript EntityData -> Bukkit EntityType */ - private static final BiMap<EntityData, EntityType> SPAWNER_TYPES = HashBiMap.create(); + private static final BiMap<EntityData<?>, EntityType> SPAWNER_TYPES = HashBiMap.create(); static { for (EntityType e : EntityType.values()) { @@ -44,7 +51,7 @@ public class EntityUtils { SPAWNER_TYPES.put(EntityData.fromClass(c), e); } } - + /** * Check if an entity is ageable. * Some entities, such as zombies, do not have an age but can be a baby/adult. @@ -57,7 +64,7 @@ public static boolean isAgeable(Entity entity) { return true; return HAS_PIGLINS && (entity instanceof Piglin || entity instanceof Zoglin); } - + /** * Get the age of an ageable entity. * Entities such as zombies do not have an age, this will return -1 if baby, 0 if adult. @@ -78,7 +85,7 @@ else if (entity instanceof Zoglin) } return 0; } - + /** * Set the age of an entity. * Entities such as zombies do not have an age, setting below 0 will make them a baby otherwise adult. @@ -98,7 +105,7 @@ else if (entity instanceof Zoglin) ((Zoglin) entity).setBaby(age < 0); } } - + /** * Quick method for making an entity a baby. * Ageable entities (such as sheep or pigs) will set their default baby age to -24000. @@ -108,7 +115,7 @@ else if (entity instanceof Zoglin) public static void setBaby(Entity entity) { setAge(entity, -24000); } - + /** * Quick method for making an entity an adult. * @@ -117,7 +124,7 @@ public static void setBaby(Entity entity) { public static void setAdult(Entity entity) { setAge(entity, 0); } - + /** * Quick method to check if entity is an adult. * @@ -133,7 +140,7 @@ public static boolean isAdult(Entity entity) { * @param e Skript's EntityData * @return Bukkit's EntityType */ - public static EntityType toBukkitEntityType(EntityData e) { + public static EntityType toBukkitEntityType(EntityData<?> e) { return SPAWNER_TYPES.get(EntityData.fromClass(e.getType())); // Fix Comparison Issues } @@ -142,7 +149,7 @@ public static EntityType toBukkitEntityType(EntityData e) { * @param e Bukkit's EntityType * @return Skript's EntityData */ - public static EntityData toSkriptEntityData(EntityType e) { + public static EntityData<?> toSkriptEntityData(EntityType e) { return SPAWNER_TYPES.inverse().get(e); } diff --git a/src/main/java/ch/njol/skript/bukkitutil/PaperEntityUtils.java b/src/main/java/ch/njol/skript/bukkitutil/PaperEntityUtils.java new file mode 100644 index 00000000000..b20a2027a59 --- /dev/null +++ b/src/main/java/ch/njol/skript/bukkitutil/PaperEntityUtils.java @@ -0,0 +1,178 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.bukkitutil; + +import java.util.EnumSet; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.NamespacedKey; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Mob; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import org.eclipse.jdt.annotation.Nullable; + +import com.destroystokyo.paper.entity.ai.Goal; +import com.destroystokyo.paper.entity.ai.GoalKey; +import com.destroystokyo.paper.entity.ai.GoalType; + +import ch.njol.skript.Skript; +import io.papermc.paper.entity.LookAnchor; + +public class PaperEntityUtils { + + private static final boolean LOOK_ANCHORS = Skript.classExists("io.papermc.paper.entity.LookAnchor"); + private static final boolean LOOK_AT = Skript.methodExists(Mob.class, "lookAt", Entity.class); + + /** + * Utility method for usage only from this class. + */ + private static void mobLookAt(Object target, @Nullable Float headRotationSpeed, @Nullable Float maxHeadPitch, Mob mob) { + Bukkit.getMobGoals().getRunningGoals(mob, GoalType.LOOK).forEach(goal -> Bukkit.getMobGoals().removeGoal(mob, goal)); + float speed = headRotationSpeed != null ? headRotationSpeed : mob.getHeadRotationSpeed(); + float maxPitch = maxHeadPitch != null ? maxHeadPitch : mob.getMaxHeadPitch(); + Bukkit.getMobGoals().addGoal(mob, 0, new LookGoal(target, mob, speed, maxPitch)); + } + + /** + * Instruct a Mob (1.17+) to look at a specific vector/location/entity. + * Object can be a {@link org.bukkit.util.Vector}, {@link org.bukkit.Location} or {@link org.bukkit.entity.Entity} + * + * @param target The vector/location/entity to make the livingentity look at. + * @param entities The living entities to make look at something. + */ + public static void lookAt(Object target, LivingEntity... entities) { + lookAt(target, null, null, entities); + } + + /** + * Instruct a Mob (1.17+) to look at a specific vector/location/entity. + * Object can be a {@link org.bukkit.util.Vector}, {@link org.bukkit.Location} or {@link org.bukkit.entity.Entity} + * + * @param target The vector/location/entity to make the livingentity look at. + * @param headRotationSpeed The rotation speed at which the living entities will rotate their head to the target. Vanilla default values range from 10-50. Doesn't apply to players. + * @param maxHeadPitch The maximum pitch at which the eyes/feet can go to. Doesn't apply to players. + * @param entities The living entities to make look at something. + */ + public static void lookAt(Object target, @Nullable Float headRotationSpeed, @Nullable Float maxHeadPitch, LivingEntity... entities) { + if (target == null || !LOOK_AT) + return; + // Use support for players if using Paper 1.19.1+ + if (LOOK_ANCHORS) { + lookAt(LookAnchor.EYES, headRotationSpeed, maxHeadPitch, entities); + return; + } + for (LivingEntity entity : entities) { + if (!(entity instanceof Mob)) + continue; + mobLookAt(target, headRotationSpeed, maxHeadPitch, (Mob) entity); + } + } + + /** + * Instruct a Mob (1.17+) or Players (1.19.1+) to look at a specific vector/location/entity. + * Object can be a {@link org.bukkit.util.Vector}, {@link org.bukkit.Location} or {@link org.bukkit.entity.Entity} + * THIS METHOD IS FOR 1.19.1+ ONLY. Use {@link lookAt(Object, Float, Float, LivingEntity...)} otherwise. + * + * @param entityAnchor What part of the entity the player should face assuming the LivingEntity argument contains a player. Only for players. + * @param target The vector/location/entity to make the livingentity or player look at. + * @param headRotationSpeed The rotation speed at which the living entities will rotate their head to the target. Vanilla default values range from 10-50. Doesn't apply to players. + * @param maxHeadPitch The maximum pitch at which the eyes/feet can go to. Doesn't apply to players. + * @param entities The living entities to make look at something. Players can be involved in 1.19.1+ + */ + public static void lookAt(LookAnchor entityAnchor, Object target, @Nullable Float headRotationSpeed, @Nullable Float maxHeadPitch, LivingEntity... entities) { + if (target == null || !LOOK_AT || !LOOK_ANCHORS) + return; + for (LivingEntity entity : entities) { + if (entity instanceof Player) { + Player player = (Player) entity; + if (target instanceof Vector) { + Vector vector = (Vector) target; + player.lookAt(vector.getX(), vector.getY(), vector.getZ(), LookAnchor.EYES); + player.lookAt(vector.getX(), vector.getY(), vector.getZ(), LookAnchor.FEET); + } else if (target instanceof Location) { + player.lookAt((Location) target, LookAnchor.EYES); + player.lookAt((Location) target, LookAnchor.FEET); + } else if (target instanceof Entity) { + player.lookAt((Entity) target, LookAnchor.EYES, entityAnchor); + player.lookAt((Entity) target, LookAnchor.FEET, entityAnchor); + } + } else if (entity instanceof Mob) { + mobLookAt(target, headRotationSpeed, maxHeadPitch, (Mob) entity); + } + } + } + + public static class LookGoal implements Goal<Mob> { + + private static final GoalKey<Mob> SKRIPT_LOOK_KEY = GoalKey.of(Mob.class, new NamespacedKey(Skript.getInstance(), "skript_entity_look")); + private static final EnumSet<GoalType> LOOK_GOAL = EnumSet.of(GoalType.LOOK); + + private static final int VECTOR = 0, LOCATION = 1, ENTITY = 2; + + private final float speed, maxPitch; + private final Object target; + private int ticks = 0, type; + private final Mob mob; + + LookGoal(Object target, Mob mob, float speed, float maxPitch) { + this.type = target instanceof Vector ? 0 : target instanceof Location ? 1 : 2; + this.maxPitch = maxPitch; + this.target = target; + this.speed = speed; + this.mob = mob; + } + + @Override + public boolean shouldActivate() { + return ticks < 50; + } + + @Override + public void tick() { + switch (type) { + case VECTOR: + Vector vector = ((Vector)target); + mob.lookAt(vector.getX(), vector.getY(), vector.getZ(), speed, maxPitch); + break; + case LOCATION: + mob.lookAt((Location) target, speed, maxPitch); + break; + case ENTITY: + mob.lookAt((Entity) target, speed, maxPitch); + break; + } + ticks++; + } + + @Override + public GoalKey<Mob> getKey() { + return SKRIPT_LOOK_KEY; + } + + @Override + public EnumSet<GoalType> getTypes() { + return LOOK_GOAL; + } + + } + +} diff --git a/src/main/java/ch/njol/skript/effects/EffLook.java b/src/main/java/ch/njol/skript/effects/EffLook.java new file mode 100644 index 00000000000..6adb1ee96f3 --- /dev/null +++ b/src/main/java/ch/njol/skript/effects/EffLook.java @@ -0,0 +1,123 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.effects; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Mob; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import ch.njol.skript.Skript; +import ch.njol.skript.bukkitutil.PaperEntityUtils; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import io.papermc.paper.entity.LookAnchor; + +@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", + "", + "on entity explosion:", + "\tset {_player} to the nearest player", + "\t{_player} is set", + "\tdistance between {_player} and the event-location is less than 15", + "\tmake {_player} look towards vector from the {_player} to location of the event-entity", + "", + "force {_enderman} to face the block 3 meters above {_location} at head rotation speed 100.5 and max head pitch -40" +}) +@Since("INSERT VERSION") +@RequiredPlugins("Paper 1.17+, Paper 1.19.1+ (Players & Look Anchors)") +public class EffLook extends Effect { + + private static final boolean LOOK_ANCHORS = Skript.classExists("io.papermc.paper.entity.LookAnchor"); + + static { + if (Skript.methodExists(Mob.class, "lookAt", Entity.class)) { + if (LOOK_ANCHORS) { + Skript.registerEffect(EffLook.class, "(force|make) %livingentities% [to] (face [towards]|look [(at|towards)]) " + + "(%entity%['s (feet:feet|eyes)]|of:(feet:feet|eyes) of %entity%) " + + "[at [head] [rotation] speed %-number%] [[and] max[imum] [head] pitch %-number%]", + + "(force|make) %livingentities% [to] (face [towards]|look [(at|towards)]) %vector/location% " + + "[at [head] [rotation] speed %-number%] [[and] max[imum] [head] pitch %-number%]"); + } else { + Skript.registerEffect(EffLook.class, "(force|make) %livingentities% [to] (face [towards]|look [(at|towards)]) %vector/location/entity% " + + "[at [head] [rotation] speed %-number%] [[and] max[imum] [head] pitch %-number%]"); + } + } + } + + private LookAnchor anchor = LookAnchor.EYES; + private Expression<LivingEntity> entities; + + @Nullable + private Expression<Number> speed, maxPitch; + + /** + * Can be Vector, Location or an Entity. + */ + private Expression<?> target; + + @SuppressWarnings("unchecked") + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + entities = (Expression<LivingEntity>) exprs[0]; + if (LOOK_ANCHORS && matchedPattern == 0) { + target = exprs[parseResult.hasTag("of") ? 2 : 1]; + speed = (Expression<Number>) exprs[3]; + maxPitch = (Expression<Number>) exprs[4]; + if (parseResult.hasTag("feet")) + anchor = LookAnchor.FEET; + } else { + target = exprs[1]; + speed = (Expression<Number>) exprs[2]; + maxPitch = (Expression<Number>) exprs[3]; + } + return true; + } + + @Override + protected void execute(Event event) { + Object object = target.getSingle(event); + if (object == null) + return; + Float speed = this.speed == null ? null : this.speed.getSingle(event).floatValue(); + Float maxPitch = this.maxPitch == null ? null : this.maxPitch.getSingle(event).floatValue(); + if (LOOK_ANCHORS) { + PaperEntityUtils.lookAt(anchor, object, speed, maxPitch, entities.getArray(event)); + return; + } + PaperEntityUtils.lookAt(object, speed, maxPitch, entities.getArray(event)); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "force " + entities.toString(event, debug) + " to look at " + target.toString(event, debug); + } + +} From 5e1626580e1eaf81293f4b94fb6c911d39d4c4d0 Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Tue, 27 Dec 2022 00:31:52 +0100 Subject: [PATCH 166/619] Fix ExprParse wrong return type (#5274) --- src/main/java/ch/njol/skript/expressions/ExprParse.java | 2 +- .../tests/regressions/5262-exprparse return type array.sk | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 src/test/skript/tests/regressions/5262-exprparse return type array.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprParse.java b/src/main/java/ch/njol/skript/expressions/ExprParse.java index 72516bcf0e2..71348f6b716 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprParse.java +++ b/src/main/java/ch/njol/skript/expressions/ExprParse.java @@ -204,7 +204,7 @@ public boolean isSingle() { @Override public Class<?> getReturnType() { - return classInfo != null ? classInfo.getC() : Object[].class; + return classInfo != null ? classInfo.getC() : Object.class; } @Override diff --git a/src/test/skript/tests/regressions/5262-exprparse return type array.sk b/src/test/skript/tests/regressions/5262-exprparse return type array.sk new file mode 100644 index 00000000000..624e883e2a6 --- /dev/null +++ b/src/test/skript/tests/regressions/5262-exprparse return type array.sk @@ -0,0 +1,6 @@ +test "ExprParse return type array": + set {_i} to stone named "textHello" + + set {_d} to first element of ((uncolored name of {_i}) parsed as "text%string%") + + assert {_d} is "Hello" with "parsed as pattern expression did not work correctly" From beeedd14a498b68f9b9b3bbbd879ba7c2715e37b Mon Sep 17 00:00:00 2001 From: Kiip <25848425+kiip1@users.noreply.github.com> Date: Tue, 27 Dec 2022 06:05:30 +0100 Subject: [PATCH 167/619] Allow modification of real player cap (#5227) --- .../skript/expressions/ExprMaxPlayers.java | 128 +++++++++++------- .../syntaxes/expressions/ExprMaxPlayers.sk | 6 + 2 files changed, 85 insertions(+), 49 deletions(-) create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprMaxPlayers.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprMaxPlayers.java b/src/main/java/ch/njol/skript/expressions/ExprMaxPlayers.java index 2ecdea9346c..36f5cf417fe 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprMaxPlayers.java +++ b/src/main/java/ch/njol/skript/expressions/ExprMaxPlayers.java @@ -18,17 +18,12 @@ */ package ch.njol.skript.expressions; -import org.bukkit.Bukkit; -import org.bukkit.event.Event; -import org.bukkit.event.server.ServerListPingEvent; -import org.eclipse.jdt.annotation.Nullable; - -import com.destroystokyo.paper.event.server.PaperServerListPingEvent; import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; @@ -36,22 +31,31 @@ 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 org.bukkit.Bukkit; +import org.bukkit.Server; +import org.bukkit.event.Event; +import org.bukkit.event.server.ServerListPingEvent; +import org.eclipse.jdt.annotation.Nullable; @Name("Max Players") @Description({"The count of max players. This can be changed in a <a href='events.html#server_list_ping'>server list ping</a> event only.", - "'real max players' returns the real count of max players of the server always and can't be changed."}) + "'real max players' returns the real count of max players of the server and can be modified on Paper 1.16 or later."}) @Examples({"on server list ping:", " set the max players count to (online players count + 1)"}) -@Since("2.3") -public class ExprMaxPlayers extends SimpleExpression<Long> { +@RequiredPlugins("Paper 1.16+ (modify max real players)") +@Since("2.3, INSERT VERSION (modify max real players)") +public class ExprMaxPlayers extends SimpleExpression<Integer> { static { - Skript.registerExpression(ExprMaxPlayers.class, Long.class, ExpressionType.PROPERTY, - "[the] [(1¦(real|default)|2¦(fake|shown|displayed))] max[imum] player[s] [(count|amount|number|size)]", - "[the] [(1¦(real|default)|2¦(fake|shown|displayed))] max[imum] (count|amount|number|size) of players"); + Skript.registerExpression(ExprMaxPlayers.class, Integer.class, ExpressionType.PROPERTY, + "[the] [1:(real|default)|2:(fake|shown|displayed)] max[imum] player[s] [count|amount|number|size]", + "[the] [1:(real|default)|2:(fake|shown|displayed)] max[imum] (count|amount|number|size) of players" + ); } private static final boolean PAPER_EVENT_EXISTS = Skript.classExists("com.destroystokyo.paper.event.server.PaperServerListPingEvent"); + private static final boolean SET_MAX_PLAYERS_EXISTS = Skript.methodExists(Server.class, "setMaxPlayers", int.class); private boolean isReal; @@ -59,66 +63,92 @@ public class ExprMaxPlayers extends SimpleExpression<Long> { public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { boolean isServerPingEvent = getParser().isCurrentEvent(ServerListPingEvent.class) || (PAPER_EVENT_EXISTS && getParser().isCurrentEvent(PaperServerListPingEvent.class)); + if (parseResult.mark == 2 && !isServerPingEvent) { Skript.error("The 'shown' max players count expression can't be used outside of a server list ping event"); return false; } + isReal = (parseResult.mark == 0 && !isServerPingEvent) || parseResult.mark == 1; return true; } @Override @Nullable - public Long[] get(Event e) { - if (!isReal && !(e instanceof ServerListPingEvent)) + public Integer[] get(Event event) { + if (!isReal && !(event instanceof ServerListPingEvent)) return null; - if (isReal) - return CollectionUtils.array((long) Bukkit.getMaxPlayers()); - else - return CollectionUtils.array((long) ((ServerListPingEvent) e).getMaxPlayers()); + if (isReal) { + return CollectionUtils.array(Bukkit.getMaxPlayers()); + } else { + return CollectionUtils.array(((ServerListPingEvent) event).getMaxPlayers()); + } } @Override @Nullable public Class<?>[] acceptChange(ChangeMode mode) { - if (!isReal) { - if (getParser().getHasDelayBefore().isTrue()) { - Skript.error("Can't change the fake max players count anymore after the server list ping event has already passed"); - return null; - } - switch (mode) { - case SET: - case ADD: - case REMOVE: - case DELETE: - case RESET: + if (!isReal && getParser().getHasDelayBefore().isTrue()) { + Skript.error("Can't change the fake max players count anymore after the server list ping event has already passed"); + return null; + } + + if (isReal && !SET_MAX_PLAYERS_EXISTS) { + Skript.error("Modifying the 'real max player count' is only supported on Paper 1.16 and newer"); + return null; + } + + switch (mode) { + case SET: + case ADD: + case REMOVE: + return CollectionUtils.array(Number.class); + case RESET: + case DELETE: + if (!isReal) return CollectionUtils.array(Number.class); - } } + return null; } @SuppressWarnings("null") @Override - public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { - if (!(e instanceof ServerListPingEvent)) + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + int amount = delta == null ? 0 : ((Number) delta[0]).intValue(); + + if (!isReal && !(event instanceof ServerListPingEvent)) return; - ServerListPingEvent event = (ServerListPingEvent) e; - switch (mode) { - case SET: - event.setMaxPlayers(((Number) delta[0]).intValue()); - break; - case ADD: - event.setMaxPlayers(event.getMaxPlayers() + ((Number) delta[0]).intValue()); - break; - case REMOVE: - event.setMaxPlayers(event.getMaxPlayers() - ((Number) delta[0]).intValue()); - break; - case DELETE: - case RESET: - event.setMaxPlayers(Bukkit.getMaxPlayers()); + if (isReal) { + switch (mode) { + case SET: + Bukkit.setMaxPlayers(amount); + break; + case ADD: + Bukkit.setMaxPlayers(Bukkit.getMaxPlayers() + amount); + break; + case REMOVE: + Bukkit.setMaxPlayers(Bukkit.getMaxPlayers() - amount); + break; + } + } else { + ServerListPingEvent pingEvent = (ServerListPingEvent) event; + switch (mode) { + case SET: + pingEvent.setMaxPlayers(amount); + break; + case ADD: + pingEvent.setMaxPlayers(pingEvent.getMaxPlayers() + amount); + break; + case REMOVE: + pingEvent.setMaxPlayers(pingEvent.getMaxPlayers() - amount); + break; + case RESET: + case DELETE: + pingEvent.setMaxPlayers(Bukkit.getMaxPlayers()); + } } } @@ -128,12 +158,12 @@ public boolean isSingle() { } @Override - public Class<? extends Long> getReturnType() { - return Long.class; + public Class<? extends Integer> getReturnType() { + return Integer.class; } @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { return "the count of " + (isReal ? "real max players" : "max players"); } diff --git a/src/test/skript/tests/syntaxes/expressions/ExprMaxPlayers.sk b/src/test/skript/tests/syntaxes/expressions/ExprMaxPlayers.sk new file mode 100644 index 00000000000..d019eeccdd9 --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprMaxPlayers.sk @@ -0,0 +1,6 @@ +test "max players" when running minecraft "1.16.5": + set real max players count to 5 + assert real max players count is 5 with "setting max players failed" + add 3 to real max players + assert real max players count is 8 with "adding max players failed" + assert reset real max players count to fail with "resetting max players count unavailable" From b14a3cd2eeccc7e432820ce352a990dc2b11b29b Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Tue, 27 Dec 2022 02:53:38 -0700 Subject: [PATCH 168/619] Set the string flavour tag of the jar in the actions artifacts to nightly (#4994) --- .github/workflows/java-17-builds.yml | 4 +--- .github/workflows/java-8-builds.yml | 4 +--- .github/workflows/repo.yml | 4 +++- build.gradle | 4 ++-- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/.github/workflows/java-17-builds.yml b/.github/workflows/java-17-builds.yml index 8e435547f11..67007d76d90 100644 --- a/.github/workflows/java-17-builds.yml +++ b/.github/workflows/java-17-builds.yml @@ -23,9 +23,7 @@ jobs: cache: gradle - name: Grant execute permission for gradlew run: chmod +x gradlew - - name: Build Skript - run: ./gradlew nightlyRelease - - name: Run test scripts + - name: Build Skript and run test scripts run: ./gradlew clean skriptTestJava17 - name: Upload Nightly Build uses: actions/upload-artifact@v3 diff --git a/.github/workflows/java-8-builds.yml b/.github/workflows/java-8-builds.yml index 6257a3ebccd..adcd71fb09b 100644 --- a/.github/workflows/java-8-builds.yml +++ b/.github/workflows/java-8-builds.yml @@ -23,9 +23,7 @@ jobs: cache: gradle - name: Grant execute permission for gradlew run: chmod +x gradlew - - name: Build Skript - run: ./gradlew nightlyRelease - - name: Run test scripts + - name: Build Skript and run test scripts run: ./gradlew clean skriptTestJava8 - name: Upload Nightly Build uses: actions/upload-artifact@v3 diff --git a/.github/workflows/repo.yml b/.github/workflows/repo.yml index 1518620c7e7..d7639b98a94 100644 --- a/.github/workflows/repo.yml +++ b/.github/workflows/repo.yml @@ -14,7 +14,9 @@ jobs: - name: Set up JDK 17 uses: actions/setup-java@v3 with: - java-version: 17 + java-version: '17' + distribution: 'adopt' + cache: gradle - name: Publish Skript run: ./gradlew publish env: diff --git a/build.gradle b/build.gradle index 07b5d5fb6f3..5c7e45d22bc 100644 --- a/build.gradle +++ b/build.gradle @@ -161,7 +161,7 @@ tasks.register('testNaming') { // Create a test task with given name, environments dir/file, dev mode and java version. void createTestTask(String name, String environments, boolean devMode, int javaVersion, boolean genDocs) { tasks.register(name, JavaExec) { - dependsOn jar, testNaming + dependsOn nightlyRelease, testNaming javaLauncher = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(javaVersion) } @@ -170,7 +170,7 @@ void createTestTask(String name, String environments, boolean devMode, int javaV } group = 'execution' classpath = files([ - 'build' + File.separator + 'libs' + File.separator + 'Skript.jar', + 'build' + File.separator + 'libs' + File.separator + 'Skript-nightly.jar', project.configurations.runtimeClasspath.find { it.name.startsWith('gson') }, sourceSets.main.runtimeClasspath ]) From 6389bc232c9b120c9603930fc4a793e783c981b9 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Tue, 27 Dec 2022 04:29:58 -0700 Subject: [PATCH 169/619] Adds spectator start/stop/swap Paper events and spectator target event value expression (#4999) --- .../ch/njol/skript/events/EvtSpectate.java | 104 ++++++++++++ .../expressions/ExprSpectatorTarget.java | 152 ++++++++++++++---- .../skript/lang/util/SimpleExpression.java | 47 ++++-- 3 files changed, 258 insertions(+), 45 deletions(-) create mode 100644 src/main/java/ch/njol/skript/events/EvtSpectate.java diff --git a/src/main/java/ch/njol/skript/events/EvtSpectate.java b/src/main/java/ch/njol/skript/events/EvtSpectate.java new file mode 100644 index 00000000000..33e5cf4a733 --- /dev/null +++ b/src/main/java/ch/njol/skript/events/EvtSpectate.java @@ -0,0 +1,104 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.events; + +import org.bukkit.entity.Entity; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import com.destroystokyo.paper.event.player.PlayerStartSpectatingEntityEvent; +import com.destroystokyo.paper.event.player.PlayerStopSpectatingEntityEvent; + +import ch.njol.skript.Skript; +import ch.njol.skript.entity.EntityData; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptEvent; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.coll.CollectionUtils; + +public class EvtSpectate extends SkriptEvent { + + static { + if (Skript.classExists("com.destroystokyo.paper.event.player.PlayerStartSpectatingEntityEvent")) + Skript.registerEvent("Spectate", EvtSpectate.class, CollectionUtils.array(PlayerStartSpectatingEntityEvent.class, PlayerStopSpectatingEntityEvent.class), + "[player] stop spectating [(of|from) %-*entitydatas%]", + "[player] (swap|switch) spectating [(of|from) %-*entitydatas%]", + "[player] start spectating [of %-*entitydatas%]") + .description("Called with a player starts, stops or swaps spectating an entity.") + .examples("on player start spectating of a zombie:") + .requiredPlugins("Paper") + .since("INSERT VERSION"); + } + + private Literal<EntityData<?>> datas; + + private static final int STOP = -1, SWAP = 0, START = 1; + + /** + * 1 = swap. When the player did have a past spectating target. + * 0 = start. When the player starts spectating a new target. + * -1 = stop. When the player stops spectating a target. + */ + private int pattern; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult) { + pattern = matchedPattern - 1; + datas = (Literal<EntityData<?>>) args[0]; + return true; + } + + @Override + public boolean check(Event event) { + boolean swap = false; + Entity entity; + // Start or swap event, and must be PlayerStartSpectatingEntityEvent. + if (pattern != STOP && event instanceof PlayerStartSpectatingEntityEvent) { + PlayerStartSpectatingEntityEvent spectating = (PlayerStartSpectatingEntityEvent) event; + entity = spectating.getNewSpectatorTarget(); + + // If it's a swap event, we're checking for past target on entity data and no null targets in the event. + if (swap = pattern == SWAP && entity != null && spectating.getCurrentSpectatorTarget() != null) + entity = spectating.getCurrentSpectatorTarget(); + } else if (event instanceof PlayerStopSpectatingEntityEvent) { + entity = ((PlayerStopSpectatingEntityEvent) event).getSpectatorTarget(); + } else { + // Swap event cannot be a stop spectating event. + return false; + } + // Wasn't a swap event. + if (pattern == SWAP && !swap) + return false; + if (datas == null) + return true; + for (EntityData<?> data : this.datas.getAll(event)) { + if (data.isInstance(entity)) + return true; + } + return false; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return (pattern == START ? "start" : pattern == SWAP ? "swap" : "stop") + " spectating" + + (datas != null ? "of " + datas.toString(event, debug) : ""); + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprSpectatorTarget.java b/src/main/java/ch/njol/skript/expressions/ExprSpectatorTarget.java index 540d3fba989..fb22281d74d 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprSpectatorTarget.java +++ b/src/main/java/ch/njol/skript/expressions/ExprSpectatorTarget.java @@ -24,60 +24,150 @@ import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.classes.Changer; -import ch.njol.skript.doc.NoDoc; -import ch.njol.skript.expressions.base.SimplePropertyExpression; +import com.destroystokyo.paper.event.player.PlayerStartSpectatingEntityEvent; +import com.destroystokyo.paper.event.player.PlayerStopSpectatingEntityEvent; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.classes.data.DefaultChangers; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.effects.Delay; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.skript.registrations.EventValues; +import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -@NoDoc -public class ExprSpectatorTarget extends SimplePropertyExpression<Player, Entity> { +@Name("Spectator Target") +@Description("Grabs the spectator target entity of the players.") +@Examples({ + "on player start spectating of player:", + "\tmessage \"&c%spectator target% currently has %{game::kills::%spectator target%}% kills!\" to the player", + "", + "on player stop spectating:", + "\tpast spectator target was a zombie", + "\tset spectator target to the nearest skeleton" +}) +@RequiredPlugins("Paper") +@Since("2.4-alpha4, INSERT VERSION (Paper Spectator Event)") +public class ExprSpectatorTarget extends SimpleExpression<Entity> { + + private static final boolean EVENT_SUPPORT = Skript.classExists("com.destroystokyo.paper.event.player.PlayerStartSpectatingEntityEvent"); static { - register(ExprSpectatorTarget.class, Entity.class, "spectator target", "players"); + Skript.registerExpression(ExprSpectatorTarget.class, Entity.class, ExpressionType.PROPERTY, + "spectator target [of %-players%]", + "%players%'[s] spectator target" + ); } - @Override - protected String getPropertyName() { - return "spectator target"; - } + private Expression<Player> players; - @Nullable @Override - public Entity convert(Player player) { - return player.getSpectatorTarget(); + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + players = (Expression<Player>) expressions[0]; + if (players == null && !EVENT_SUPPORT) { + Skript.error("Your server platform does not support using 'spectator target' without players defined." + + "'spectator target of event-player'"); + return false; + } else if (players == null && !getParser().isCurrentEvent(PlayerStartSpectatingEntityEvent.class, PlayerStopSpectatingEntityEvent.class)) { + Skript.error("The expression 'spectator target' may only be used in a start/stop/swap spectating target event"); + return false; + } + return true; } @Override - public Class<? extends Entity> getReturnType() { - return Entity.class; + @Nullable + protected Entity[] get(Event event) { + if (EVENT_SUPPORT && players == null && !Delay.isDelayed(event)) { + if (event instanceof PlayerStartSpectatingEntityEvent) { + // Past state. + if (getTime() == EventValues.TIME_PAST) + return CollectionUtils.array(((PlayerStartSpectatingEntityEvent) event).getCurrentSpectatorTarget()); + return CollectionUtils.array(((PlayerStartSpectatingEntityEvent) event).getNewSpectatorTarget()); + } else if (event instanceof PlayerStopSpectatingEntityEvent) { + // There isn't going to be a future state in a stop spectating event. + if (getTime() == EventValues.TIME_FUTURE) + return new Entity[0]; + return CollectionUtils.array(((PlayerStopSpectatingEntityEvent) event).getSpectatorTarget()); + } + } + if (players == null) + return new Entity[0]; + return players.stream(event).map(Player::getSpectatorTarget).toArray(Entity[]::new); } - @Nullable @Override - public Class<?>[] acceptChange(Changer.ChangeMode mode) { - if (mode == Changer.ChangeMode.SET - || mode == Changer.ChangeMode.RESET - || mode == Changer.ChangeMode.DELETE) { + @Nullable + public Class<?>[] acceptChange(ChangeMode mode) { + // Make 'spectator target' act as an entity changer. Will error in init for unsupported server platform. + if (players == null) + return DefaultChangers.entityChanger.acceptChange(mode); + if (mode == ChangeMode.SET || mode == ChangeMode.RESET || mode == ChangeMode.DELETE) return CollectionUtils.array(Entity.class); - } return null; } @Override - public void change(Event e, @Nullable Object[] delta, Changer.ChangeMode mode) { - for (Player player : getExpr().getArray(e)) { - if (player.getGameMode() == GameMode.SPECTATOR) { - switch (mode) { - case SET: - assert delta != null; + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + // Make 'spectator target' act as an entity changer. Will error in init for unsupported server platform. + if (players == null) { + Entity[] entities = get(event); + if (entities.length == 0) + return; + DefaultChangers.entityChanger.change(entities, delta, mode); + return; + } + switch (mode) { + case SET: + assert delta != null; + for (Player player : players.getArray(event)) { + if (player.getGameMode() == GameMode.SPECTATOR) player.setSpectatorTarget((Entity) delta[0]); - break; - case RESET: - case DELETE: + } + break; + case RESET: + case DELETE: + for (Player player : players.getArray(event)) { + if (player.getGameMode() == GameMode.SPECTATOR) player.setSpectatorTarget(null); } - } + break; + default: + break; } } + @Override + public boolean setTime(int time) { + if (!EVENT_SUPPORT) + return false; + if (players == null) + return super.setTime(time, PlayerStartSpectatingEntityEvent.class, PlayerStopSpectatingEntityEvent.class); + return super.setTime(time, players, PlayerStartSpectatingEntityEvent.class, PlayerStopSpectatingEntityEvent.class); + } + + @Override + public boolean isSingle() { + return players == null || players.isSingle(); + } + + @Override + public Class<? extends Entity> getReturnType() { + return Entity.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "spectator target" + (players != null ? " of " + players.toString(event, debug) : ""); + } + } diff --git a/src/main/java/ch/njol/skript/lang/util/SimpleExpression.java b/src/main/java/ch/njol/skript/lang/util/SimpleExpression.java index 5aba0270fb1..98bda113eac 100644 --- a/src/main/java/ch/njol/skript/lang/util/SimpleExpression.java +++ b/src/main/java/ch/njol/skript/lang/util/SimpleExpression.java @@ -22,6 +22,7 @@ import java.util.Iterator; import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; @@ -239,7 +240,7 @@ public void change(final Event e, final @Nullable Object[] delta, final ChangeMo throw new UnsupportedOperationException(); ((Changer<T>) c).change(getArray(e), delta, mode); } - + /** * {@inheritDoc} * <p> @@ -249,18 +250,18 @@ public void change(final Event e, final @Nullable Object[] delta, final ChangeMo * @see #setTime(int, Expression, Class...) */ @Override - public boolean setTime(final int time) { + public boolean setTime(int time) { if (getParser().getHasDelayBefore() == Kleenean.TRUE && time != 0) { - Skript.error("Can't use time states after the event has already passed"); + Skript.error("Can't use time states after the event has already passed."); return false; } this.time = time; return false; } - - protected final boolean setTime(final int time, final Class<? extends Event> applicableEvent) { + + protected final boolean setTime(int time, Class<? extends Event> applicableEvent) { if (getParser().getHasDelayBefore() == Kleenean.TRUE && time != 0) { - Skript.error("Can't use time states after the event has already passed"); + Skript.error("Can't use time states after the event has already passed."); return false; } if (!getParser().isCurrentEvent(applicableEvent)) @@ -268,15 +269,27 @@ protected final boolean setTime(final int time, final Class<? extends Event> app this.time = time; return true; } - - protected final boolean setTime(final int time, final Class<? extends Event> applicableEvent, final Expression<?>... mustbeDefaultVars) { + + @SafeVarargs + protected final boolean setTime(int time, Class<? extends Event>... applicableEvents) { + if (getParser().getHasDelayBefore() == Kleenean.TRUE && time != 0) { + Skript.error("Can't use time states after the event has already passed."); + return false; + } + if (!getParser().isCurrentEvent(applicableEvents)) + return false; + this.time = time; + return true; + } + + protected final boolean setTime(int time, Class<? extends Event> applicableEvent, @NonNull Expression<?>... mustbeDefaultVars) { if (getParser().getHasDelayBefore() == Kleenean.TRUE && time != 0) { - Skript.error("Can't use time states after the event has already passed"); + Skript.error("Can't use time states after the event has already passed."); return false; } if (!getParser().isCurrentEvent(applicableEvent)) return false; - for (final Expression<?> var : mustbeDefaultVars) { + for (Expression<?> var : mustbeDefaultVars) { if (!var.isDefault()) { return false; } @@ -284,10 +297,16 @@ protected final boolean setTime(final int time, final Class<? extends Event> app this.time = time; return true; } - - protected final boolean setTime(final int time, final Expression<?> mustbeDefaultVar, final Class<? extends Event>... applicableEvents) { + + @SafeVarargs + protected final boolean setTime(int time, Expression<?> mustbeDefaultVar, Class<? extends Event>... applicableEvents) { if (getParser().getHasDelayBefore() == Kleenean.TRUE && time != 0) { - Skript.error("Can't use time states after the event has already passed"); + Skript.error("Can't use time states after the event has already passed."); + return false; + } + if (mustbeDefaultVar == null) { + Skript.exception(new SkriptAPIException("Default expression was null. If the default expression can be null, don't be using" + + " 'SimpleExpression#setTime(int, Expression<?>, Class<? extends Event>...)' instead use the setTime without an expression if null.")); return false; } if (!mustbeDefaultVar.isDefault()) @@ -298,7 +317,7 @@ protected final boolean setTime(final int time, final Expression<?> mustbeDefaul } return false; } - + @Override public int getTime() { return time; From ce0bb43cb059201d24caa68f3a81f9a9c5309d80 Mon Sep 17 00:00:00 2001 From: Kenzie <admin@moderocky.com> Date: Tue, 27 Dec 2022 17:42:29 +0000 Subject: [PATCH 170/619] Rewrite the Default Examples (#4605) * Replace examples. * Add example for options and aliases. * Update src/main/resources/scripts/-examples/commands.sk Co-authored-by: Jake Ben-Tovim <jacobbentovim@gmail.com> * Update src/main/resources/scripts/-examples/events.sk Co-authored-by: Jake Ben-Tovim <jacobbentovim@gmail.com> * Update src/main/resources/scripts/-examples/chest menus.sk Co-authored-by: Jake Ben-Tovim <jacobbentovim@gmail.com> * Update src/main/resources/scripts/-examples/chest menus.sk * Apply suggestions from code review * Implement some suggested changes * Correct some formatting and add better inventory example. * Add text formatting example (sorbonbon) * Fix small issue with destroy ore command * Fix argument errors Co-authored-by: Jake Ben-Tovim <jacobbentovim@gmail.com> Co-authored-by: APickledWalrus <apickledwalrus@gmail.com> --- .../scripts/-examples/chest menus.sk | 62 +++++++++++++++ .../resources/scripts/-examples/commands.sk | 71 ++++++++++++++++++ .../resources/scripts/-examples/events.sk | 44 +++++++++++ .../resources/scripts/-examples/functions.sk | 55 ++++++++++++++ src/main/resources/scripts/-examples/loops.sk | 75 +++++++++++++++++++ .../scripts/-examples/options and meta.sk | 44 +++++++++++ .../scripts/-examples/text formatting.sk | 38 ++++++++++ .../resources/scripts/-examples/timings.sk | 36 +++++++++ .../resources/scripts/-examples/variables.sk | 53 +++++++++++++ .../scripts/command with cooldown.sk | 23 ------ src/main/resources/scripts/custom helmet.sk | 22 ------ src/main/resources/scripts/disable weather.sk | 6 -- src/main/resources/scripts/eternal day.sk | 6 -- .../scripts/furnace automatisation.sk | 38 ---------- src/main/resources/scripts/homes.sk | 30 -------- src/main/resources/scripts/item command.sk | 64 ---------------- src/main/resources/scripts/kill counter.sk | 22 ------ src/main/resources/scripts/nerf endermen.sk | 11 --- src/main/resources/scripts/realistic drops.sk | 14 ---- src/main/resources/scripts/simple god mode.sk | 8 -- .../scripts/simple join and leave message.sk | 27 ------- src/main/resources/scripts/simple motd.sk | 23 ------ .../scripts/teleport with compass.sk | 9 --- src/main/resources/scripts/vanilla gui.sk | 16 ---- 24 files changed, 478 insertions(+), 319 deletions(-) create mode 100644 src/main/resources/scripts/-examples/chest menus.sk create mode 100644 src/main/resources/scripts/-examples/commands.sk create mode 100644 src/main/resources/scripts/-examples/events.sk create mode 100644 src/main/resources/scripts/-examples/functions.sk create mode 100644 src/main/resources/scripts/-examples/loops.sk create mode 100644 src/main/resources/scripts/-examples/options and meta.sk create mode 100644 src/main/resources/scripts/-examples/text formatting.sk create mode 100644 src/main/resources/scripts/-examples/timings.sk create mode 100644 src/main/resources/scripts/-examples/variables.sk delete mode 100644 src/main/resources/scripts/command with cooldown.sk delete mode 100644 src/main/resources/scripts/custom helmet.sk delete mode 100644 src/main/resources/scripts/disable weather.sk delete mode 100644 src/main/resources/scripts/eternal day.sk delete mode 100644 src/main/resources/scripts/furnace automatisation.sk delete mode 100644 src/main/resources/scripts/homes.sk delete mode 100644 src/main/resources/scripts/item command.sk delete mode 100644 src/main/resources/scripts/kill counter.sk delete mode 100644 src/main/resources/scripts/nerf endermen.sk delete mode 100644 src/main/resources/scripts/realistic drops.sk delete mode 100644 src/main/resources/scripts/simple god mode.sk delete mode 100644 src/main/resources/scripts/simple join and leave message.sk delete mode 100644 src/main/resources/scripts/simple motd.sk delete mode 100644 src/main/resources/scripts/teleport with compass.sk delete mode 100644 src/main/resources/scripts/vanilla gui.sk diff --git a/src/main/resources/scripts/-examples/chest menus.sk b/src/main/resources/scripts/-examples/chest menus.sk new file mode 100644 index 00000000000..42de70edfe3 --- /dev/null +++ b/src/main/resources/scripts/-examples/chest menus.sk @@ -0,0 +1,62 @@ + +# +# An example of opening a chest inventory with an item. +# Players will be able to take the item out. +# + +command /simplechest: + permission: skript.example.chest + trigger: + set {_chest} to a new chest inventory named "Simple Chest" + set slot 0 of {_chest} to apple # Slots are numbered 0, 1, 2... + open {_chest} to player + +# +# An example of listening for click events in a chest inventory. +# This can be used to create fancy button-style interfaces for users. +# + +command /chestmenu: + permission: skript.example.menu + trigger: + set {_menu} to a new chest inventory with 1 row named "Simple Menu" + set slot 4 of {_menu} to stone named "Button" # Slots are numbered 0, 1, 2... + open {_menu} to player + +on inventory click: # Listen for players clicking in an inventory. + name of event-inventory is "Simple Menu" # Make sure it's our menu. + cancel event + if index of event-slot is 4: # The button slot. + send "You clicked the button." + else: + send "You didn't click the button." + +# +# An example of making and filling a fancy inventory with a function. +# This demonstrates another way you can use chest inventories, and a safe way of listening to specific ones. +# + +aliases: + menu items = TNT, lava bucket, string, coal, oak planks + +function makeMenu(name: text, rows: number) :: inventory: + set {_gui} to a new chest inventory with {_rows} rows with name {_name} + loop {_rows} * 9 times: # Fill the inventory with random items. + set slot loop-number - 1 of {_gui} to random item out of menu items + return {_gui} + +command /fancymenu: + permission: skript.example.menu + trigger: + set {_menu} to makeMenu("hello", 4) + add {_menu} to {my inventories::*} # Prepare to listen to this inventory. + open {_menu} to player + +on inventory click: # Listen for players clicking in any inventory. + if {my inventories::*} contains event-inventory: # Make sure it's our menu. + cancel event + send "You clicked slot %index of event-slot%!" + +on inventory close: # No longer need to listen to this inventory. + {my inventories::*} contains event-inventory + remove event-inventory from {my inventories::*} diff --git a/src/main/resources/scripts/-examples/commands.sk b/src/main/resources/scripts/-examples/commands.sk new file mode 100644 index 00000000000..303eb2ee70b --- /dev/null +++ b/src/main/resources/scripts/-examples/commands.sk @@ -0,0 +1,71 @@ + +# +# A very simple `broadcast` command for broadcasting the text argument. +# This is accessible only to users with the `skript.example.broadcast` permission. +# + +command /broadcast <text>: + permission: skript.example.broadcast + description: Broadcasts a message to everybody. + trigger: + broadcast arg-text + +# +# A simple /home command that allows players to set, remove and travel to homes. +# This command is executable only by players, and has a `correct usage` message. +# The first argument is required, whereas the second is optional. +# + +command /home <text> [<text>]: + description: Set, delete or travel to your home. + usage: /home set/remove <name>, /home <name> + permission: skript.example.home + executable by: players + trigger: + if arg-1 is "set": + if arg-2 is set: + set {homes::%uuid of player%::%arg-2%} to player's location + send "Set your home <green>%arg-2%<reset> to <grey>%location of player%<reset>" to player + else: + send "You must specify a name for this home." to player + else if arg-1 is "remove": + if arg-2 is set: + delete {homes::%uuid of player%::%arg-2%} + send "Deleted your home <green>%arg-2%<reset>" to player + else: + send "You must specify the name of this home." to player + else if arg-2 is set: + send "Correct usage: /home set/remove <name>" to player + else if {homes::%uuid of player%::%arg-1%} is set: + teleport player to {homes::%uuid of player%::%arg-1%} + else: + send "You have no home named <green>%arg-1%<reset>." to player + +# +# An /item command that accepts Skript item aliases. +# E.g. `/item birch plank, 5 red wool and 17 iron ore` +# This command has aliases - alternative versions of the command that can be used. +# + +aliases: + # Creates an alias `blacklisted` for this list of items. + blacklisted = TNT, bedrock, obsidian, mob spawner, lava, lava bucket + +command /item <items>: + description: Give yourself some items. + usage: /item <items...> + aliases: /i + executable by: players + permission: skript.example.item + cooldown: 30 seconds + cooldown message: You need to wait %remaining time% to use this command again. + cooldown bypass: skript.example.cooldown + trigger: + if player has permission "skript.example.item.all": + give argument to player + else: + loop argument: + if loop-item is not blacklisted: + give loop-item to player + else: + send "<red>%loop-item%<reset> is blacklisted and cannot be spawned." to player diff --git a/src/main/resources/scripts/-examples/events.sk b/src/main/resources/scripts/-examples/events.sk new file mode 100644 index 00000000000..45fbfb2a7d5 --- /dev/null +++ b/src/main/resources/scripts/-examples/events.sk @@ -0,0 +1,44 @@ + +# +# This example listens for players joining and leaving. +# Alters the default message when they do. +# + +on join: + set the join message to "Oh look, %player% joined! :)" + +on quit: + set the quit message to "Oh no, %player% left! :(" + +# +# This example cancels damage for players if they have a specific permission. +# If they don't, tell them how much damage they took. +# + +on damage: + victim is a player + if the victim has permission "skript.example.damage": + cancel the event # Stops the default behaviour - the victim taking damage. + else: + send "Ouch! You took %damage% damage." to the victim + add damage to {damage::%uuid of victim%::taken} + if the attacker is a player: + add damage to {damage::%uuid of attacker%::dealt} + +# +# This example allows players to wear specified blocks as hats. +# Listens for the clicking in the head slot and, if the player has permission, puts the item on their head. +# + +aliases: # An alias for our allowed hat items. + custom helmets = iron block, gold block, diamond block + +on inventory click: + event-slot is the helmet slot of player # Check that player clicked their head slot. + inventory action is place all or nothing + player has permission "skript.example.helmet" + cursor slot of player is custom helmets # Check if the item is in our custom alias. + cancel the event + set {_old helmet} to the helmet of player + set the helmet of player to the cursor slot of player + set the cursor slot of player to {_old helmet} diff --git a/src/main/resources/scripts/-examples/functions.sk b/src/main/resources/scripts/-examples/functions.sk new file mode 100644 index 00000000000..bb743a075dd --- /dev/null +++ b/src/main/resources/scripts/-examples/functions.sk @@ -0,0 +1,55 @@ + +# +# A simple broadcasting function example. +# This demonstrates how to declare and run a simple function. +# + +function sayMessage(message: text): + broadcast {_message} # our message argument is available in `{_message}`. + +on first join: + wait 1 second + sayMessage("Welcome, %player%!") # Runs the `sayMessage` function. + +# +# An example of a function with multiple parameters and a return type. +# This demonstrates how to return a value and use it. +# + +function giveApple(name: text, amount: number) :: item: + set {_item} to an apple + set the name of {_item} to {_name} + set the item amount of {_item} to {_amount} + return {_item} # Gives this value to the code that called the function. + +command /appleexample: + permission: skript.example.apple + trigger: + send "Giving you an apple!" + set {_item} to giveApple("Banana", 4) + give player {_item} + +# +# An example of a recursive (self-calling) function that is used to repeat a complex task. +# Please note that self-calling functions can loop infinitely, so use with caution. +# + +function destroyOre(source: block) :: blocks: + add {_source} to {_found::*} + break {_source} naturally using an iron pickaxe + loop blocks in radius 1 of {_source}: + loop-block is any ore + break loop-block naturally using an iron pickaxe + if {_found::*} does not contain loop-block: + add destroyOre(loop-block) to {_found::*} + return {_found::*} + +command /oreexample: + permission: skript.example.ore + trigger: + if player's target block is any ore: + send "Destroying all connected ore." + set {_found::*} to destroyOre(player's target block) + send "Destroyed %size of {_found::*}% connected ores!" + else: + send "<red>You must be looking at an ore block!" diff --git a/src/main/resources/scripts/-examples/loops.sk b/src/main/resources/scripts/-examples/loops.sk new file mode 100644 index 00000000000..73765868c2c --- /dev/null +++ b/src/main/resources/scripts/-examples/loops.sk @@ -0,0 +1,75 @@ + +# +# Examples for two basic loops: one will run a set number of times, the other will run for all elements in a list. +# Multi-value expressions like `all players` can also be looped. +# + +command /loopexample: + permission: skript.example.loop + trigger: + set {_number} to 5 + loop {_number} times: # Runs `{_number}` times. + send "The number is %loop-number%." + + set {_list::*} to "apple", "banana" and "orange" + loop {_list::*}: # Runs for each value in the list. + send "The word is: %loop-value%" + +# +# Examples for while-loops, which run as long as the condition is true. +# A while-loop can run indefinitely and freeze the server, so make sure to add a delay or an exit condition. +# + +command /whileexample: + permission: skript.example.while + trigger: + set {_number} to 5 + while {_number} is greater than 0: + send "The number is %{_number}%" + remove a random number between 0 and 2 from {_number} + send "Finished counting down." + + while true is true: # this will run forever + add "banana" to {_list::*} + if size of {_list::*} is 10: + exit loop + send "The list has %size of {_list::*}% bananas." + +command /dowhileexample: + permission: skript.example.dowhile + trigger: + set {_number} to a random integer between 0 and 6 # The player will get 1 to 3 apples. + do while {_number} is greater than 3: # This will always run at least once, even if `{_number} is less than or equal to 3`. + give the player an apple + remove 1 from {_number} + send "Finished giving out apples!" + + do while true is false: # This will run once - the condition is checked AFTER the code is executed. + send "I will run only once!" + +# +# Examples for looping collections of specific types, such as players, blocks and items. +# This shows how loops can be used to simplify more complex actions. +# + +command /anotherloopexample: + permission: skript.example.loop + trigger: + send "Listing all players:" + loop all players: # Remember - player is the command sender, loop-player is the loop value. + send " - %loop-player%" + if loop-player has permission "skript.example.apple": + give loop-player an apple named "Potato" + + set {_items::*} to stone, oak planks and an apple + loop {_items::*}: + send "%loop-index%. %loop-value%" + give loop-value to player + + loop blocks in radius 2 of player: + loop-block is a chest + loop items of types ore and log: # Loop-block comes from the first loop, loop-item from the second. + inventory of loop-block contains loop-item + remove loop-item from the inventory of loop-block + send "Destroyed a %loop-item%!" + exit loop # Exits the item loop. diff --git a/src/main/resources/scripts/-examples/options and meta.sk b/src/main/resources/scripts/-examples/options and meta.sk new file mode 100644 index 00000000000..8d0e1c35955 --- /dev/null +++ b/src/main/resources/scripts/-examples/options and meta.sk @@ -0,0 +1,44 @@ + +# +# A simple example of using an option to make a common value accessible through the file. +# + +options: + my server name: Server Name + condition: player is alive + nice message: "You're alive!" + +on join: + send "Welcome to {@my server name}" + # Options don't need `%...%` since they are raw inputs. + if {@condition}: # The raw `player is alive` is copied here during parsing. + send {@nice message} + +# +# An example of custom aliases for groups of items. +# This can be used as a shorthand in code. +# + +aliases: + pretty items = iron ingot, gold ingot, diamond + +on join: + player has permission "skript.example.aliases" + give player random item out of pretty items # A random item from our alias. + +# +# An example showing how default variables can be used. +# These are seen the first time they are loaded, but not overwritten if you change the file copy. +# They act like default variable values. +# + +variables: + score::%player% = 100 + some variable = "Hello" + +command /variabletest: + permission: skript.example.variables + trigger: + add 1 to {score::%player%} + send "Your score is now %{score::%player%}%." + send {some variable} diff --git a/src/main/resources/scripts/-examples/text formatting.sk b/src/main/resources/scripts/-examples/text formatting.sk new file mode 100644 index 00000000000..8a5a1ab277a --- /dev/null +++ b/src/main/resources/scripts/-examples/text formatting.sk @@ -0,0 +1,38 @@ +# +# This example is for colours and formatting in Skript. +# Skript allows the old-style Minecraft codes using `&`, like &0 for black and &l for bold. +# You can also use <> for colours and formats, like `<red>` for red and `<bold>` for bold +# +# In Minecraft 1.16, support was added for 6-digit hexadecimal colors to specify custom colors other than the 16 default color codes. +# The tag for these colors looks like this: <##hex code> e.g. `<##123456>` +# + +command /color: + permission: skript.example.color + trigger: + send "&6This message is golden." + send "<light red><bold>This message is light red and bold." + send "<##FF0000>This message is red." + +# +# Other formatting options are also available. +# You can create clickable text, which opens a website or execute a command, or even show a tool tip when hovering over the text. +# + +command /forum: + permission: skript.example.link + trigger: + send "To visit the website, [<link:https://google.com><tooltip:click here>click here<reset>]" + +command /copy <text>: + permission: skript.example.copy + trigger: + # Insertion: when the player shift clicks on the message, it will add the text to their text box. + # To use variables and other expressions with these tags, you have to send the text as `formatted`. + send formatted "<insertion:%arg-1%>%arg-1%" + +command /suggest: + permission: skript.example.suggest + trigger: + send "<cmd:/say hi>Click here to run the command /say hi" + send "<sgt:/say hi>Click here to suggest the command /say hi" diff --git a/src/main/resources/scripts/-examples/timings.sk b/src/main/resources/scripts/-examples/timings.sk new file mode 100644 index 00000000000..18073095e5e --- /dev/null +++ b/src/main/resources/scripts/-examples/timings.sk @@ -0,0 +1,36 @@ +# +# This example schedules a repeating action to be run at a time in the *minecraft* world. +# Every minecraft day at 6 PM, the time is reset to 7 AM, making it always day. +# + +at 18:00: + set the time to 7:00 + +# +# This example schedules a repeating action. Each time the delay elapses, the trigger will be run. +# The delay is in real-world time. +# + +every 5 minutes: + broadcast "Did you know that five minutes have passed?" + loop all players: + if loop-player has permission "skript.example.apple": + give loop-player an apple + +# +# This example shows how the `wait` effect can be used to delay code being run. +# + +command /waitexample: + permission: skript.example.wait + trigger: + send "Waiting for two seconds..." + wait 2 seconds + send "Finished waiting!" + + send "Counting to five." + set {_count} to 0 + while {_count} is less than 5: + wait 3 ticks # Minecraft game ticks: 20 ticks = 1 second. + add 0.5 to {_count} + send "Finished counting!" diff --git a/src/main/resources/scripts/-examples/variables.sk b/src/main/resources/scripts/-examples/variables.sk new file mode 100644 index 00000000000..a39061e1230 --- /dev/null +++ b/src/main/resources/scripts/-examples/variables.sk @@ -0,0 +1,53 @@ + +# +# This example stores a time-stamp in the global `{timer}` variable. +# This variable is accessible everywhere, and will be saved when the server stops. +# +# The `{_difference}` variable is local and is different for each use of the command. +# + +command /timer: + permission: skript.example.timer + trigger: + if {timer} is set: + send "This command was last run %time since {timer}% ago." + else: + send "This command has never been run." + set {timer} to now + +# +# This example stores two items in a global list variable `{items::%uuid of player%::*}`. +# These items can then be recalled with the example `/outfit` command, which then clears the list. +# + +on join: + set {items::%uuid of player%::helmet} to player's helmet + set {items::%uuid of player%::boots} to player's boots + send "Stored your helmet and boots." + +command /outfit: + executable by: players + permission: skript.example.outfit + trigger: + give player {items::%uuid of player%::*} # gives the contents of the list + clear {items::%uuid of player%::*} # clears this list + send "Gave you the helmet and boots you joined with." + +# +# An example of adding, looping and removing the contents of a list variable. +# This list variable is local, and so will not be kept between uses of the command. +# + +command /shoppinglist: + permission: skript.example.list + trigger: + add "bacon" to {_shopping list::*} + add "eggs" to {_shopping list::*} + add "oats" and "sugar" to {_shopping list::*} + send "You have %size of {_shopping list::*}% things in your shopping list:" + loop {_shopping list::*}: + send "%loop-index%. %loop-value%" + send "You bought some %{_shopping list::1}%!" + remove "bacon" from {_shopping list::*} + send "Removing bacon from your list." + send "You now have %size of {_shopping list::*}% things in your shopping list." diff --git a/src/main/resources/scripts/command with cooldown.sk b/src/main/resources/scripts/command with cooldown.sk deleted file mode 100644 index bdd38c7e255..00000000000 --- a/src/main/resources/scripts/command with cooldown.sk +++ /dev/null @@ -1,23 +0,0 @@ -#This command has a cooldown of 1 minute, -#meaning a player can only use it each minute. -#If a player tries to use it more than once in a minute, -#the cooldown message is shown - -command /cake: - description: Give the command sender some cake! - cooldown: 1 minute - cooldown message: You need to wait &l%remaining time% &rto use this command again! - cooldown bypass: cake.nocooldown - - #See https://skriptlang.github.io/Skript/expressions.html#ExprCmdCooldownInfo for more info - #on the expressions you can use in a cooldown message. - - trigger: - if the player's inventory doesn't have space for cake: - send "&cYou don't have space for cake in your inventory! :(" - cancel command cooldown - #This is used so the player doesn't have to wait 1 minute before - #using the command again if they didn't have space for cake. - else: - give cake to the player - send "Here you go!" diff --git a/src/main/resources/scripts/custom helmet.sk b/src/main/resources/scripts/custom helmet.sk deleted file mode 100644 index c65488e4914..00000000000 --- a/src/main/resources/scripts/custom helmet.sk +++ /dev/null @@ -1,22 +0,0 @@ -# -# Allows specified items to be used as helmets for survival players (Minecraft 1.9 recommended). -# - -aliases: - custom helmets = iron block, gold block, diamond block # Change this to anything you'd like - -on inventory click: - inventory action is place all or nothing - cursor slot of player is custom helmets # Check if item is allowed as helmet - event-slot is helmet slot of event-player # Check that player clicked the helmet - cancel the event - set {_old helmet} to helmet of player # Store old helmet - set helmet of player to cursor slot of player # Set new helmet to item in player's cursor - set cursor slot of player to {_old helmet} # Set player's cursor to old helmet, possibly air - -on right click: - tool of player is custom helmets - helmet of player is air - cancel the event - set helmet of player to tool of player - set tool of player to air diff --git a/src/main/resources/scripts/disable weather.sk b/src/main/resources/scripts/disable weather.sk deleted file mode 100644 index ad841ef859e..00000000000 --- a/src/main/resources/scripts/disable weather.sk +++ /dev/null @@ -1,6 +0,0 @@ -# -# Disables rain & thunder in all worlds -# - -on weather change to rain or thunder: - cancel event diff --git a/src/main/resources/scripts/eternal day.sk b/src/main/resources/scripts/eternal day.sk deleted file mode 100644 index a71d3b4eb9c..00000000000 --- a/src/main/resources/scripts/eternal day.sk +++ /dev/null @@ -1,6 +0,0 @@ -# -# Makes eternal day in all worlds by setting each world's time to morning in the evening. -# - -at 18:00: - set time to 7:00 diff --git a/src/main/resources/scripts/furnace automatisation.sk b/src/main/resources/scripts/furnace automatisation.sk deleted file mode 100644 index 9643da934a0..00000000000 --- a/src/main/resources/scripts/furnace automatisation.sk +++ /dev/null @@ -1,38 +0,0 @@ -# -# This script automates furnaces. -# They store smelted items and restock/refuel automatically from surrounding chests -# But only as long as they have fuel and something to smelt, and there's storage space for the smelted items, -# I.e. if a furnace stops burning it won't resume automatically, but has to be lit manually again. -# - -options: - # radius: in which radius chest should be searched. - # Putting a small radius is recommended as it increases performance and prevents furnaces from taking items out of chests behind walls or otherwise hidden chests. - # Some recommended values: - # 1: only chests direcly next to the furnace are affected - # 1.5: chests diaginally adjacent (i.e. which touch the furnace with one edge) are affected as well - # 1.75: all surrounding chests in a 3x3x3 cube are affected - radius: 1 - -aliases: - fuel = coal, coal ore, coal block, any wooden tool - -on smelt: - loop blocks in radius {@radius}: - loop-block is chest - if ore slot of block is empty: - loop items of types ore and log: - inventory of loop-block contains loop-item - remove loop-item from inventory of loop-block - set ore of event-block to loop-item - exit loop - if fuel slot of block is empty: - loop items in inventory of loop-block: - loop-item is fuel - remove 1 of loop-item from inventory of loop-block - set fuel of event-block to 1 of loop-item - exit loop - if result slot of block is not empty: - loop-block can hold result of event-block - add result of event-block to loop-block - clear result of event-block \ No newline at end of file diff --git a/src/main/resources/scripts/homes.sk b/src/main/resources/scripts/homes.sk deleted file mode 100644 index cd1bc99a796..00000000000 --- a/src/main/resources/scripts/homes.sk +++ /dev/null @@ -1,30 +0,0 @@ -# -# A simple home script which allows players with the permission 'skript.home' to -# define a home location with /sethome, delete a home with /delhome and teleport to that location whenever they want to with /home. -# - -command /sethome <string>: - description: Sets your home - permission: skript.home - executable by: players - trigger: - set {homes::%uuid of player%::%arg-1%} to location of player - message "Set your home <green>%arg-1%<reset> to <grey>%location of player%<reset>" - -command /delhome <string>: - description: Deletes your home - permission: skript.home - executable by: players - trigger: - clear {homes::%uuid of player%::%arg-1%} - message "Deleted your home <green>%arg-1%<reset>" - -command /home <string>: - description: Teleports yourself to your home - permission: skript.home - executable by: players - trigger: - if {homes::%uuid of player%::%arg-1%} is not set: - message "You have not set your home <green>%arg-1%<reset> yet!" - else: - teleport player to {homes::%uuid of player%::%arg-1%} \ No newline at end of file diff --git a/src/main/resources/scripts/item command.sk b/src/main/resources/scripts/item command.sk deleted file mode 100644 index 94ecb74e6de..00000000000 --- a/src/main/resources/scripts/item command.sk +++ /dev/null @@ -1,64 +0,0 @@ -# -# Two /item commands that accept aliases as arguments and are thus extremely versatile. -# E.g. you can use the command like "/item birch plank, 5 red wool and 17 iron ore" and you will receive all 3. -# If you add translated aliases to your aliases.sk your players will be able to use those aliases as well. -# -# Please note that an <items> argument accepts enchantments as well, and only the second example command -# in this script restricts spawing of enchanted items. -# - -aliases: - # Items that the user cannot give himself - blacklisted = TNT, bedrock, obsidian, mob spawner, lava, lava bucket - -# This version of the command requires that the player has the permission skript.give, -# and blacklisted items are only allowed if the player has the permission skript.give.bypassblacklist -command /item <items>: - description: Give yourself some items - usage: /item <item(s)> - aliases: i - executable by: players - permission: skript.give - trigger: - if player has permission "skript.give.bypassblacklist": - give arguments to player - else: - loop arguments: - if loop-item is not blacklisted: - give loop-item to player - else: - message "<red>%loop-item%<reset> is blacklisted and thus cannot be spawned" - -# This version of the command checks whether the player has permission to spawn the desired item. -# A player needs the "skript.give.<type of item>" permission to spawn an item of the desired type -# and the permission "skript.give.enchanted" to spawn enchanted items. -command /item2 <items>: - description: Give yourself some items - usage: /item2 <item(s)> - aliases: i2 - executable by: players - permission: skript.give - trigger: - loop arguments: - if loop-item is enchanted: - if player does not have the permission "skript.give.enchanted": - message "You don't have permission to spawn enchanted items!" - stop - if player has permission "skript.give.%type of loop-item%": - give loop-item to player - else: - message "You don't have permission to spawn <red>%loop-item%<reset>! (skript.give.%type of loop-item%)" - - -command /give <item types> to <player>: - description: Give someone else some items - usage: /give <item(s)> to <player> - permission: skript.give - trigger: - send "Giving %argument 1% to %argument 2%" to player - loop argument 1: - if player has permission "skript.give.%type of loop-item%": - give loop-item to argument 2 - send "You recieved %loop-item% from %player%" to argument 2 - else: - message "<red>You don't have permission to give away free <red>%loop-item%<reset>! (skript.give.%type of loop-item%)" diff --git a/src/main/resources/scripts/kill counter.sk b/src/main/resources/scripts/kill counter.sk deleted file mode 100644 index 20edb85de9e..00000000000 --- a/src/main/resources/scripts/kill counter.sk +++ /dev/null @@ -1,22 +0,0 @@ -# -# Counts kills per life, in total, and the highest kill streak. -# - -# Defaults are useful here, as a player's kills would be '<none>' if he hasn't killed anything yet, -# but with defaults defined those will be used in that case. - -on death: - attacker is a player - add 1 to {kill counter::%uuid of attacker%::kills_total} - add 1 to {kill counter::%uuid of attacker%::kills_session} - if {kill counter::%uuid of attacker%::kills_session} > {kill counter::%uuid of attacker%::kills_session_max}: - set {kill counter::%uuid of attacker%::kills_session_max} to {kill counter::%uuid of attacker%::kills_session} - -on death of player: - set {kill counter::%uuid of player%::kills_session} to 0 - -command /kills: - executable by: players - trigger: - message "You have killed %{kill counter::%uuid of player%::kills_session}% mobs and players in this life out of %{kill counter::%uuid of player%::kills_total}% kills in total." - message "Your maximum kill streak is %{kill counter::%uuid of player%::kills_session_max}% kills in one life." diff --git a/src/main/resources/scripts/nerf endermen.sk b/src/main/resources/scripts/nerf endermen.sk deleted file mode 100644 index 1c35f4b31b6..00000000000 --- a/src/main/resources/scripts/nerf endermen.sk +++ /dev/null @@ -1,11 +0,0 @@ -# -# this simple file prevents endermen from modifying the landscape. -# You could also only restrict what they can/cannot take/place by adding a condition. -# - -on enderman pickup: - cancel event - -on enderman place: - cancel event - kill the enderman # kills endermen which were still carrying something when this trigger file was activated \ No newline at end of file diff --git a/src/main/resources/scripts/realistic drops.sk b/src/main/resources/scripts/realistic drops.sk deleted file mode 100644 index 88ba7ba2332..00000000000 --- a/src/main/resources/scripts/realistic drops.sk +++ /dev/null @@ -1,14 +0,0 @@ -# -# These triggers fix some drops. -# - -on break of glass: - drop glass - -on break of glass pane: - drop glass pane - -on break of bookshelf: - cancel event - set block to air - drop bookshelf diff --git a/src/main/resources/scripts/simple god mode.sk b/src/main/resources/scripts/simple god mode.sk deleted file mode 100644 index 48b23fab63b..00000000000 --- a/src/main/resources/scripts/simple god mode.sk +++ /dev/null @@ -1,8 +0,0 @@ -# -# Makes a player invulnerable if he has the permission 'skript.god' -# - -on damage: - victim is a player - victim has permission "skript.god" - cancel event diff --git a/src/main/resources/scripts/simple join and leave message.sk b/src/main/resources/scripts/simple join and leave message.sk deleted file mode 100644 index 7bd3a7f1d58..00000000000 --- a/src/main/resources/scripts/simple join and leave message.sk +++ /dev/null @@ -1,27 +0,0 @@ -# -# A join and leave message. -# - -command /set <string> message <string>: - permission: skript.set.join_message - description: Sets message when player joins - trigger: - if argument-1 is "join" or "leave": - set {custom messages::%argument-1%} to argument-2 - message "Set the %argument-1% message to '%argument-2%<reset>'" - else: - message "Only 'join' and 'leave' messages are available here." - -command /show <string> message: - description: Displays the message of the day - trigger: - if {custom messages::%argument%} is set: - message {custom messages::%argument%} - else: - message "Only 'join' and 'leave' messages are available here." - -on join: - set join message to {custom messages::join} - -on quit: - set leave message to {custom messages::leave} \ No newline at end of file diff --git a/src/main/resources/scripts/simple motd.sk b/src/main/resources/scripts/simple motd.sk deleted file mode 100644 index 5f45b185f19..00000000000 --- a/src/main/resources/scripts/simple motd.sk +++ /dev/null @@ -1,23 +0,0 @@ -# -# A simple message of the day script. -# The MOTD can be set with the /setmotd command, -# and it will be sent to each player when they log in -# and when they use the /motd command. -# - -command /setmotd <text>: - permission: skript.setmotd - description: Sets the message of the day - trigger: - message "Set the MOTD to '%argument%<reset>'" - set {motd} to argument - -command /showmotd: - description: Displays the message of the day - trigger: - message {motd} - -on join: - # uncomment the following line to make the MOTD appear after other messages - # wait a tick - message {motd} \ No newline at end of file diff --git a/src/main/resources/scripts/teleport with compass.sk b/src/main/resources/scripts/teleport with compass.sk deleted file mode 100644 index 0f23a8a12ff..00000000000 --- a/src/main/resources/scripts/teleport with compass.sk +++ /dev/null @@ -1,9 +0,0 @@ -# -# Allows players with the 'skript.teleport' permission to be able to teleport -# with a compass like in WorldEdit. -# - -on rightclick with compass: - loop blocks above targeted block: - teleport player to loop-block - stop trigger diff --git a/src/main/resources/scripts/vanilla gui.sk b/src/main/resources/scripts/vanilla gui.sk deleted file mode 100644 index 3868a32f8f1..00000000000 --- a/src/main/resources/scripts/vanilla gui.sk +++ /dev/null @@ -1,16 +0,0 @@ -# -# A vanilla example of a gui creation using Skript only -# Requires Skript 2.3+ -# - -command /gui: - trigger: - set {_gui} to a new chest inventory with 6 row with name "Gui Test" - set slot 0 of {_gui} to stone - open {_gui} to player - -on inventory click: - if name of event-inventory is "Gui Test": - if index of event-slot = 0: - cancel event - send "You clicked on the slot 0 !" \ No newline at end of file From 54b9ea36dff4b948ea267275f3893576d60b90f8 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Tue, 27 Dec 2022 21:11:25 -0700 Subject: [PATCH 171/619] Clean up ExprBlockData.sk test after running (#5283) --- src/test/skript/tests/syntaxes/expressions/ExprBlockData.sk | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/skript/tests/syntaxes/expressions/ExprBlockData.sk b/src/test/skript/tests/syntaxes/expressions/ExprBlockData.sk index e31d411600e..1916c370f95 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprBlockData.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprBlockData.sk @@ -10,3 +10,5 @@ test "block data" when running minecraft "1.15.2": set {_data} to block data of block at {_b} assert "%{_data}%" contains "campfire", "lit=false" and "waterlogged=true" with "block data for campfire did not match" + + set block at {_b} to air From 33b2e7b8cd5b68dc39204f67e37dfe3439956374 Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Wed, 28 Dec 2022 07:34:23 +0300 Subject: [PATCH 172/619] Add changers to ExprBookPages (#5025) --- .../skript/expressions/ExprBookPages.java | 202 +++++++++++++----- 1 file changed, 146 insertions(+), 56 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprBookPages.java b/src/main/java/ch/njol/skript/expressions/ExprBookPages.java index 53ebef188c6..ec5df08f3f6 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprBookPages.java +++ b/src/main/java/ch/njol/skript/expressions/ExprBookPages.java @@ -19,99 +19,189 @@ package ch.njol.skript.expressions; import ch.njol.skript.Skript; -import ch.njol.skript.aliases.Aliases; import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; -import ch.njol.skript.lang.SkriptParser; +import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; import org.bukkit.event.Event; -import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.BookMeta; import org.eclipse.jdt.annotation.Nullable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.List; @Name("Book Pages") @Description("The pages of a book.") -@Examples({"on book sign:", - "\tmessage \"Book Pages: %pages of event-item%\"", - "\tmessage \"Book Page 1: %page 1 of event-item%\""}) -@Since("2.2-dev31") +@Examples({ + "on book sign:", + "\tmessage \"Book Pages: %pages of event-item%\"", + "\tmessage \"Book Page 1: %page 1 of event-item%\"", + "set page 1 of player's held item to \"Book writing\"" +}) +@Since("2.2-dev31, INSERT VERSION (changers)") public class ExprBookPages extends SimpleExpression<String> { - + static { Skript.registerExpression(ExprBookPages.class, String.class, ExpressionType.PROPERTY, - "[all] [the] [book] (pages|content) of %itemtypes%", - "%itemtypes%'s [book] (pages|content)", - "[book] page %number% of %itemtypes%", - "%itemtypes%'s [book] page %number%"); + "[all [[of] the]|the] [book] (pages|content) of %itemtypes%", + "%itemtypes%'[s] [book] (pages|content)", + "[book] page %number% of %itemtypes%", + "%itemtypes%'[s] [book] page %number%" + ); } - - private static final ItemType bookItem = Aliases.javaItemType("book with text"); - - @SuppressWarnings("null") - private Expression<ItemType> book; + + private Expression<ItemType> items; @Nullable private Expression<Number> page; - - @SuppressWarnings("null") + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (matchedPattern == 0 || matchedPattern == 1) { + items = (Expression<ItemType>) exprs[0]; + } else if (matchedPattern == 2) { + page = (Expression<Number>) exprs[0]; + items = (Expression<ItemType>) exprs[1]; + } else { + items = (Expression<ItemType>) exprs[0]; + page = (Expression<Number>) exprs[1]; + } + return true; + } + + @Override @Nullable + protected String[] get(Event event) { + List<String> pages = new ArrayList<>(); + for (ItemType itemType : items.getArray(event)) { + if (!(itemType.getItemMeta() instanceof BookMeta)) + continue; + BookMeta bookMeta = (BookMeta) itemType.getItemMeta(); + if (isAllPages()) { + pages.addAll(bookMeta.getPages()); + } else { + Number pageNumber = page.getSingle(event); + if (pageNumber == null) + continue; + int page = pageNumber.intValue(); + if (page <= 0 || page > bookMeta.getPageCount()) + continue; + pages.add(bookMeta.getPage(page)); + } + } + return pages.toArray(new String[0]); + } + @Override - protected String[] get(Event e) { - ItemStack itemStack = book.getSingle(e).getRandom(); - if (itemStack == null || !bookItem.isOfType(itemStack)) - return null; - List<String> pages = ((BookMeta) itemStack.getItemMeta()).getPages(); - if (page != null){ - Number pageNumber = page.getSingle(e); - if (pageNumber == null){ + @Nullable + public Class<?>[] acceptChange(ChangeMode mode) { + switch (mode) { + case RESET: + case DELETE: + return CollectionUtils.array(); + case SET: + return CollectionUtils.array(isAllPages() ? String[].class : String.class); + case ADD: + return isAllPages() ? CollectionUtils.array(String[].class) : null; + default: return null; + } + } + + @Override + @SuppressWarnings("ConstantConditions") + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + if (delta == null && (mode == ChangeMode.SET || mode == ChangeMode.ADD)) + return; + ItemType[] itemTypes = items.getArray(event); + int page = !isAllPages() ? this.page.getOptionalSingle(event).orElse(-1).intValue() : -1; + String[] newPages = delta == null ? null : new String[delta.length]; + + if (newPages != null) { + for (int i = 0; i < delta.length; i++) + newPages[i] = delta[i] + ""; + } + + for (ItemType itemType : itemTypes) { + if (!(itemType.getItemMeta() instanceof BookMeta)) + continue; + + BookMeta bookMeta = (BookMeta) itemType.getItemMeta(); + List<String> pages = null; + if (isAllPages()) { + switch (mode) { + case DELETE: + case RESET: + pages = Collections.singletonList(""); + break; + case SET: + pages = Arrays.asList(newPages); + break; + default: + assert false; + } + } else { + pages = new ArrayList<>(bookMeta.getPages()); } - int page = pageNumber.intValue(); - if ((page) > pages.size() || page < 1){ - return null; + int pageCount = bookMeta.getPageCount(); + + switch (mode) { + case DELETE: + case RESET: + if (!isAllPages()) { + if (page <= 0 || page > pageCount) + continue; + pages.remove(page - 1); + } + break; + case SET: + if (newPages.length == 0) + continue; + if (!isAllPages()) { + if (page <= 0) + continue; + while (pages.size() < page) + pages.add(""); + pages.set(page - 1, newPages[0]); + } + break; + case ADD: + pages.addAll(Arrays.asList(newPages)); + break; } - return new String[]{pages.get(page - 1)}; - }else{ - return pages.toArray(new String[pages.size()]); + + bookMeta.setPages(pages); + itemType.setItemMeta(bookMeta); } } - + + private boolean isAllPages() { + return page == null; + } + @Override public boolean isSingle() { - return page != null; + return items.isSingle() && !isAllPages(); } - + @Override public Class<? extends String> getReturnType() { return String.class; } - + @Override - public String toString(@Nullable Event e, boolean debug) { - return "book pages of " + book.toString(e, debug); - } - - @SuppressWarnings({"unchecked", "null"}) - @Override - public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { - if (matchedPattern == 0 || matchedPattern == 1){ - book = (Expression<ItemType>) exprs[0]; - }else{ - if (matchedPattern == 2){ - page =(Expression<Number>) exprs[0]; - book = (Expression<ItemType>) exprs[1]; - }else{ - book = (Expression<ItemType>) exprs[0]; - page = (Expression<Number>) exprs[1]; - } - } - return true; + public String toString(@Nullable Event event, boolean debug) { + return (page != null ? "page " + page.toString(event, debug) : "the book pages") + " of " + items.toString(event, debug); } + } From 928b97b0f7419e382d1a08d2043ae8da26904d45 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Wed, 28 Dec 2022 02:38:49 -0500 Subject: [PATCH 173/619] Improve book edit event `event values` (#5282) --- .../skript/classes/data/BukkitEventValues.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) 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 b407992ec16..1ef3c3ebfed 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -1210,12 +1210,20 @@ public Entity get(final PortalCreateEvent e) { //PlayerEditBookEvent EventValues.registerEventValue(PlayerEditBookEvent.class, ItemStack.class, new Getter<ItemStack, PlayerEditBookEvent>() { @Override - public ItemStack get(PlayerEditBookEvent e) { - ItemStack book = new ItemStack(e.getPlayer().getItemInHand().getType()); - book.setItemMeta(e.getNewBookMeta()); - return book; // TODO: Find better way to derive this event value + public ItemStack get(PlayerEditBookEvent event) { + ItemStack book = new ItemStack(Material.WRITABLE_BOOK); + book.setItemMeta(event.getPreviousBookMeta()); + return book; } - }, 0); + }, EventValues.TIME_PAST); + EventValues.registerEventValue(PlayerEditBookEvent.class, ItemStack.class, new Getter<ItemStack, PlayerEditBookEvent>() { + @Override + public ItemStack get(PlayerEditBookEvent event) { + ItemStack book = new ItemStack(Material.WRITABLE_BOOK); + book.setItemMeta(event.getNewBookMeta()); + return book; + } + }, EventValues.TIME_FUTURE); //ItemDespawnEvent EventValues.registerEventValue(ItemDespawnEvent.class, Item.class, new Getter<Item, ItemDespawnEvent>() { @Override From 7e7faeae3233993d227bb0caaa9812838b3f1a6a Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Wed, 28 Dec 2022 02:44:53 -0500 Subject: [PATCH 174/619] Add `a/an` support for some type enums (#5280) --- src/main/resources/lang/default.lang | 54 ++++++++++++++-------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 8f1631e5a32..a6be84db554 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -1721,33 +1721,33 @@ click types: # -- Inventory Types -- inventory types: - chest: chest inventory - dispenser: dispenser inventory - dropper: dropper inventory - furnace: furnace inventory - workbench: workbench inventory - crafting: crafting table inventory - enchanting: enchanting table inventory - brewing: brewing stand inventory - player: player inventory - creative: creative inventory - merchant: merchant inventory, villager inventory - ender_chest: ender chest inventory - anvil: anvil inventory - beacon: beacon inventory - hopper: hopper inventory - shulker_box: shulker box inventory - barrel: barrel inventory - blast_furnace: blast furnace inventory - lectern: lectern inventory - smoker: smoker inventory - loom: loom inventory - cartography: cartography table inventory - grindstone: grindstone inventory - stonecutter: stonecutter inventory - smithing: smithing inventory - composter: composter inventory - chiseled_bookshelf: chiseled bookshelf, bookshelf + chest: chest inventory @a + dispenser: dispenser inventory @a + dropper: dropper inventory @a + furnace: furnace inventory @a + workbench: workbench inventory @a + crafting: crafting table inventory @a + enchanting: enchanting table inventory @an + brewing: brewing stand inventory @a + player: player inventory @a + creative: creative inventory @a + merchant: merchant inventory @a, villager inventory @a + ender_chest: ender chest inventory @an + anvil: anvil inventory @an + beacon: beacon inventory @a + hopper: hopper inventory @a + shulker_box: shulker box inventory @a + barrel: barrel inventory @a + blast_furnace: blast furnace inventory @a + lectern: lectern inventory @a + smoker: smoker inventory @a + loom: loom inventory @a + cartography: cartography table inventory @a + grindstone: grindstone inventory @a + stonecutter: stonecutter inventory @a + smithing: smithing inventory @a + composter: composter inventory @a + chiseled_bookshelf: chiseled bookshelf @a, bookshelf @a # -- Spawn Reasons -- spawn reasons: From eb645b6db0c037d5d2e2a2fc4e9d191659324914 Mon Sep 17 00:00:00 2001 From: hotpocket184 <80357042+hotpocket184@users.noreply.github.com> Date: Wed, 28 Dec 2022 15:05:21 -0500 Subject: [PATCH 175/619] Respawn Anchor Support (#4855) Co-authored-by: Patrick Miller <apickledwalrus@gmail.com> --- .../skript/conditions/CondAnchorWorks.java | 68 ++++++++++ .../conditions/CondRespawnLocation.java | 80 ++++++++++++ .../njol/skript/expressions/ExprCharges.java | 122 ++++++++++++++++++ 3 files changed, 270 insertions(+) create mode 100644 src/main/java/ch/njol/skript/conditions/CondAnchorWorks.java create mode 100644 src/main/java/ch/njol/skript/conditions/CondRespawnLocation.java create mode 100644 src/main/java/ch/njol/skript/expressions/ExprCharges.java diff --git a/src/main/java/ch/njol/skript/conditions/CondAnchorWorks.java b/src/main/java/ch/njol/skript/conditions/CondAnchorWorks.java new file mode 100644 index 00000000000..48da63a0fc8 --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondAnchorWorks.java @@ -0,0 +1,68 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.conditions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Condition; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.World; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Do Respawn Anchors Work") +@Description("Checks whether or not respawn anchors work in a world.") +@Examples("respawn anchors work in world \"world_nether\"") +@RequiredPlugins("Minecraft 1.16+") +@Since("INSERT VERSION") +public class CondAnchorWorks extends Condition { + + static { + if (Skript.classExists("org.bukkit.block.data.type.RespawnAnchor")) + Skript.registerCondition(CondAnchorWorks.class, "respawn anchors [do[1:(n't| not)]] work in %worlds%"); + } + + @SuppressWarnings("NotNullFieldNotInitialized") + private Expression<World> worlds; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + worlds = (Expression<World>) exprs[0]; + setNegated(parseResult.mark == 1); + return true; + } + + @Override + public boolean check(Event event) { + return worlds.check(event, World::isRespawnAnchorWorks, isNegated()); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "respawn anchors " + (isNegated() ? " do" : " don't") + " work in " + worlds.toString(event, debug); + } + +} diff --git a/src/main/java/ch/njol/skript/conditions/CondRespawnLocation.java b/src/main/java/ch/njol/skript/conditions/CondRespawnLocation.java new file mode 100644 index 00000000000..73965a6df21 --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondRespawnLocation.java @@ -0,0 +1,80 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.conditions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Events; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Condition; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.bukkit.event.player.PlayerRespawnEvent; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Is Bed/Anchor Spawn") +@Description("Checks what the respawn location of a player in the respawn event is.") +@Examples({ + "on respawn:", + "\tthe respawn location is a bed", + "\tbroadcast \"%player% is respawning in their bed! So cozy!\"" +}) +@RequiredPlugins("Minecraft 1.16+") +@Since("INSERT VERSION") +@Events("respawn") +public class CondRespawnLocation extends Condition { + + static { + if (Skript.classExists("org.bukkit.block.data.type.RespawnAnchor")) + Skript.registerCondition(CondRespawnLocation.class, "[the] respawn location (was|is)[1:(n'| no)t] [a] (:bed|respawn anchor)"); + } + + private boolean bedSpawn; + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(PlayerRespawnEvent.class)) { + Skript.error("The 'respawn location' condition may only be used in a respawn event"); + return false; + } + setNegated(parseResult.mark == 1); + bedSpawn = parseResult.hasTag("bed"); + return true; + } + + @Override + public boolean check(Event event) { + if (event instanceof PlayerRespawnEvent) { + PlayerRespawnEvent respawnEvent = (PlayerRespawnEvent) event; + return (bedSpawn ? respawnEvent.isBedSpawn() : respawnEvent.isAnchorSpawn()) != isNegated(); + } + return false; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the respawn location " + (isNegated() ? "isn't" : "is") + " a " + (bedSpawn ? "bed spawn" : "respawn anchor"); + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprCharges.java b/src/main/java/ch/njol/skript/expressions/ExprCharges.java new file mode 100644 index 00000000000..09796d77378 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprCharges.java @@ -0,0 +1,122 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.type.RespawnAnchor; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import static java.lang.Math.max; +import static java.lang.Math.min; + +@Name("Respawn Anchor Charges") +@Description("The charges of a respawn anchor.") +@Examples({"set the charges of event-block to 3"}) +@RequiredPlugins("Minecraft 1.16+") +@Since("INSERT VERSION") +public class ExprCharges extends SimplePropertyExpression<Block, Integer> { + + static { + if (Skript.classExists("org.bukkit.block.data.type.RespawnAnchor")) + register(ExprCharges.class, Integer.class, "[:max[imum]] charge[s]", "blocks"); + } + + private boolean maxCharges; + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + maxCharges = parseResult.hasTag("max"); + return super.init(exprs, matchedPattern, isDelayed, parseResult); + } + + @Nullable + @Override + public Integer convert(Block block) { + BlockData blockData = block.getBlockData(); + if (blockData instanceof RespawnAnchor) { + if (maxCharges) + return ((RespawnAnchor) blockData).getMaximumCharges(); + return ((RespawnAnchor) blockData).getCharges(); + } + return null; + } + + @Nullable + @Override + public Class<?>[] acceptChange(ChangeMode mode) { + switch (mode) { + case REMOVE: + case ADD: + case SET: + case RESET: + case DELETE: + return CollectionUtils.array(Number.class); + } + return null; + } + + @Override + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + int charge = 0; + int charges = delta != null ? ((Number) delta[0]).intValue() : 0; + + for (Block block : getExpr().getArray(event)) { + if (block.getBlockData() instanceof RespawnAnchor) { + RespawnAnchor anchor = (RespawnAnchor) block.getBlockData(); + switch (mode) { + case REMOVE: + charge = anchor.getCharges() - charges; + break; + case ADD: + charge = anchor.getCharges() + charges; + break; + case SET: + charge = charges; + } + anchor.setCharges(min(max(charge, 0), 4)); + block.setBlockData(anchor); + } + } + } + + @Override + public Class<? extends Integer> getReturnType() { + return Integer.class; + } + + @Override + protected String getPropertyName() { + return "charges"; + } + +} From 10e5c76fb0ea29936138cfbff729f7a93f9e073c Mon Sep 17 00:00:00 2001 From: Kiip <25848425+kiip1@users.noreply.github.com> Date: Thu, 29 Dec 2022 05:03:31 +0100 Subject: [PATCH 176/619] Add pi literal (#5289) --- .../ch/njol/skript/expressions/LitPi.java | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 src/main/java/ch/njol/skript/expressions/LitPi.java diff --git a/src/main/java/ch/njol/skript/expressions/LitPi.java b/src/main/java/ch/njol/skript/expressions/LitPi.java new file mode 100644 index 00000000000..2fe393c3fb4 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/LitPi.java @@ -0,0 +1,59 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.SkriptParser; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleLiteral; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; + +@Name("Pi") +@Description("Returns the mathematical constant pi. (approx. 3.1415926535)") +@Examples("set {_tau} to pi * 2") +@Since("INSERT VERSION") +public class LitPi extends SimpleLiteral<Double> { + + static { + Skript.registerExpression(LitPi.class, Double.class, ExpressionType.SIMPLE, "(pi|π)"); + } + + public LitPi() { + super(Math.PI, false); + } + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + return true; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "pi"; + } + +} From 1e80bebc0b5b2e66c2ab6e1f601d6d47dacbd008 Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Thu, 29 Dec 2022 09:28:28 +0300 Subject: [PATCH 177/619] command prefix entry (#5143) --- .../ch/njol/skript/command/ScriptCommand.java | 38 ++++++++++++++++--- .../njol/skript/structures/StructCommand.java | 6 ++- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/main/java/ch/njol/skript/command/ScriptCommand.java b/src/main/java/ch/njol/skript/command/ScriptCommand.java index cd366e7fc08..93e8cad25b0 100644 --- a/src/main/java/ch/njol/skript/command/ScriptCommand.java +++ b/src/main/java/ch/njol/skript/command/ScriptCommand.java @@ -85,6 +85,7 @@ public class ScriptCommand implements TabExecutor { public final static Message m_executable_by_players = new Message("commands.executable by players"); public final static Message m_executable_by_console = new Message("commands.executable by console"); + private static final String DEFAULT_PREFIX = "skript"; final String name; private final String label; @@ -93,6 +94,7 @@ public class ScriptCommand implements TabExecutor { private String permission; private final VariableString permissionMessage; private final String description; + private final String prefix; @Nullable private final Timespan cooldown; private final Expression<String> cooldownMessage; @@ -120,6 +122,7 @@ public class ScriptCommand implements TabExecutor { * @param pattern * @param arguments the list of Arguments this command takes * @param description description to display in /help + * @param prefix the prefix of the command * @param usage message to display if the command was used incorrectly * @param aliases /alias1, /alias2, ... * @param permission permission or null if none @@ -128,7 +131,7 @@ public class ScriptCommand implements TabExecutor { */ public ScriptCommand( Script script, String name, String pattern, List<Argument<?>> arguments, - String description, String usage, List<String> aliases, + String description, @Nullable String prefix, String usage, List<String> aliases, String permission, @Nullable VariableString permissionMessage, @Nullable Timespan cooldown, @Nullable VariableString cooldownMessage, String cooldownBypass, @Nullable VariableString cooldownStorage, int executableBy, SectionNode node @@ -145,6 +148,25 @@ public ScriptCommand( this.permissionMessage = permissionMessage; } + if (prefix != null) { + for (char c : prefix.toCharArray()) { + if (Character.isWhitespace(c)) { + Skript.warning("command /" + name + " has a whitespace in its prefix. Defaulting to '" + ScriptCommand.DEFAULT_PREFIX + "'."); + prefix = ScriptCommand.DEFAULT_PREFIX; + break; + } + // char 167 is § + if (c == 167) { + Skript.warning("command /" + name + " has a section character in its prefix. Defaulting to '" + ScriptCommand.DEFAULT_PREFIX + "'."); + prefix = ScriptCommand.DEFAULT_PREFIX; + break; + } + } + } else { + prefix = DEFAULT_PREFIX; + } + this.prefix = prefix; + this.cooldown = cooldown; this.cooldownMessage = cooldownMessage == null ? new SimpleLiteral<>(Language.get("commands.cooldown message"),false) @@ -318,7 +340,7 @@ public String getPattern() { private transient Command overridden = null; private transient Map<String, Command> overriddenAliases = new HashMap<>(); - public void register(final SimpleCommandMap commandMap, final Map<String, Command> knownCommands, final @Nullable Set<String> aliases) { + public void register(SimpleCommandMap commandMap, Map<String, Command> knownCommands, @Nullable Set<String> aliases) { synchronized (commandMap) { overriddenAliases.clear(); overridden = knownCommands.put(label, bukkitCommand); @@ -336,19 +358,19 @@ public void register(final SimpleCommandMap commandMap, final Map<String, Comman aliases.add(lowerAlias); } bukkitCommand.setAliases(activeAliases); - commandMap.register("skript", bukkitCommand); + commandMap.register(prefix, bukkitCommand); } } - public void unregister(final SimpleCommandMap commandMap, final Map<String, Command> knownCommands, final @Nullable Set<String> aliases) { + public void unregister(SimpleCommandMap commandMap, Map<String, Command> knownCommands, @Nullable Set<String> aliases) { synchronized (commandMap) { knownCommands.remove(label); - knownCommands.remove("skript:" + label); + knownCommands.remove(prefix + ":" + label); if (aliases != null) aliases.removeAll(activeAliases); for (final String alias : activeAliases) { knownCommands.remove(alias); - knownCommands.remove("skript:" + alias); + knownCommands.remove(prefix + ":" + alias); } activeAliases = new ArrayList<>(this.aliases); bukkitCommand.unregister(commandMap); @@ -419,6 +441,10 @@ public String getName() { return name; } + public String getPrefix() { + return prefix; + } + public String getLabel() { return label; } diff --git a/src/main/java/ch/njol/skript/structures/StructCommand.java b/src/main/java/ch/njol/skript/structures/StructCommand.java index 9a7c5a8f47d..bee10ce76f7 100644 --- a/src/main/java/ch/njol/skript/structures/StructCommand.java +++ b/src/main/java/ch/njol/skript/structures/StructCommand.java @@ -95,6 +95,7 @@ public class StructCommand extends Structure { EntryValidator.builder() .addEntry("usage", null, true) .addEntry("description", "", true) + .addEntry("prefix", null, true) .addEntry("permission", "", true) .addEntryData(new VariableStringEntryData("permission message", null, true, CommandEvent.class)) .addEntryData(new KeyValueEntryData<List<String>>("aliases", new ArrayList<>(), true) { @@ -259,6 +260,7 @@ public boolean load() { } String description = entryContainer.get("description", String.class, true); + String prefix = entryContainer.getOptional("prefix", String.class, false); String permission = entryContainer.get("permission", String.class, true); VariableString permissionMessage = entryContainer.getOptional("permission message", VariableString.class, false); @@ -289,8 +291,8 @@ public boolean load() { Commands.currentArguments = currentArguments; try { - scriptCommand = new ScriptCommand(getParser().getCurrentScript(), command, pattern.toString(), currentArguments, description, usage, - aliases, permission, permissionMessage, cooldown, cooldownMessage, cooldownBypass, cooldownStorage, + scriptCommand = new ScriptCommand(getParser().getCurrentScript(), command, pattern.toString(), currentArguments, description, prefix, + usage, aliases, permission, permissionMessage, cooldown, cooldownMessage, cooldownBypass, cooldownStorage, executableBy, entryContainer.get("trigger", SectionNode.class, false)); } finally { Commands.currentArguments = null; From ee3503339e2712f1425d87167875f7dec56ed98e Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Thu, 29 Dec 2022 08:38:16 +0100 Subject: [PATCH 178/619] Fix rounding accuracy (#4241) --- src/main/java/ch/njol/util/Math2.java | 324 +++--------------- ...loating point errors rounding functions.sk | 12 + 2 files changed, 58 insertions(+), 278 deletions(-) create mode 100644 src/test/skript/tests/regressions/4235-floating point errors rounding functions.sk diff --git a/src/main/java/ch/njol/util/Math2.java b/src/main/java/ch/njol/util/Math2.java index 82923af7ba2..f7e1b110846 100644 --- a/src/main/java/ch/njol/util/Math2.java +++ b/src/main/java/ch/njol/util/Math2.java @@ -18,16 +18,15 @@ */ package ch.njol.util; -/** - * @author Peter Güttinger - */ +import ch.njol.skript.Skript; + public abstract class Math2 { - public static int min(final int a, final int b, final int c) { + public static int min(int a, int b, int c) { return a <= b ? (a <= c ? a : c) : (b <= c ? b : c); } - public static int min(final int... nums) { + public static int min(int... nums) { if (nums == null || nums.length == 0) { assert false; return 0; @@ -40,11 +39,11 @@ public static int min(final int... nums) { return min; } - public static int max(final int a, final int b, final int c) { + public static int max(int a, int b, int c) { return a >= b ? (a >= c ? a : c) : (b >= c ? b : c); } - public static int max(final int... nums) { + public static int max(int... nums) { if (nums == null || nums.length == 0) { assert false; return 0; @@ -57,11 +56,11 @@ public static int max(final int... nums) { return max; } - public static double min(final double a, final double b, final double c) { + public static double min(double a, double b, double c) { return a <= b ? (a <= c ? a : c) : (b <= c ? b : c); } - public static double min(final double... nums) { + public static double min(double... nums) { if (nums == null || nums.length == 0) { assert false; return Double.NaN; @@ -74,11 +73,11 @@ public static double min(final double... nums) { return min; } - public static double max(final double a, final double b, final double c) { + public static double max(double a, double b, double c) { return a >= b ? (a >= c ? a : c) : (b >= c ? b : c); } - public static double max(final double... nums) { + public static double max(double... nums) { if (nums == null || nums.length == 0) { assert false; return Double.NaN; @@ -91,59 +90,22 @@ public static double max(final double... nums) { return max; } - /** - * finds the smallest positive number (≥0) in the sequence - * - * @param nums - * @return smallest positive number in the sequence or -1 if no number is positive - */ - public static int minPositive(final int... nums) { - int max = -1; - if (nums != null) { - for (final int num : nums) { - if (num >= 0 && (num < max || max == -1)) - max = num; - } - } - return max; - } - - /** - * Fits a number into the given interval. The method's behaviour when min > max is unspecified. - * - * @return <tt>x <= min ? min : x >= max ? max : x</tt> - */ - public static int fit(final int min, final int x, final int max) { - assert min <= max : min + "," + x + "," + max; - return x <= min ? min : x >= max ? max : x; - } - - /** - * Fits a number into the given interval. The method's behaviour when min > max is unspecified. - * - * @return <tt>x <= min ? min : x >= max ? max : x</tt> - */ - public static short fit(final short min, final short x, final short max) { - assert min <= max : min + "," + x + "," + max; - return x <= min ? min : x >= max ? max : x; - } - /** * Fits a number into the given interval. The method's behaviour when min > max is unspecified. * * @return <tt>x <= min ? min : x >= max ? max : x</tt> */ - public static long fit(final long min, final long x, final long max) { + public static int fit(int min, int x, int max) { assert min <= max : min + "," + x + "," + max; return x <= min ? min : x >= max ? max : x; } - + /** * Fits a number into the given interval. The method's behaviour when min > max is unspecified. * * @return <tt>x <= min ? min : x >= max ? max : x</tt> */ - public static float fit(final float min, final float x, final float max) { + public static float fit(float min, float x, float max) { assert min <= max : min + "," + x + "," + max; return x <= min ? min : x >= max ? max : x; } @@ -153,56 +115,48 @@ public static float fit(final float min, final float x, final float max) { * * @return <tt>x <= min ? min : x >= max ? max : x</tt> */ - public static double fit(final double min, final double x, final double max) { + public static double fit(double min, double x, double max) { assert min <= max : min + "," + x + "," + max; return x <= min ? min : x >= max ? max : x; } /** * Modulo that returns positive values even for negative arguments. - * - * @param d - * @param m + * * @return <tt>d%m < 0 ? d%m + m : d%m</tt> */ - public static double mod(final double d, final double m) { - final double r = d % m; + public static double mod(double d, double m) { + double r = d % m; return r < 0 ? r + m : r; } /** * Modulo that returns positive values even for negative arguments. - * - * @param d - * @param m + * * @return <tt>d%m < 0 ? d%m + m : d%m</tt> */ - public static float mod(final float d, final float m) { - final float r = d % m; + public static float mod(float d, float m) { + float r = d % m; return r < 0 ? r + m : r; } /** * Modulo that returns positive values even for negative arguments. - * - * @param d - * @param m + * * @return <tt>d%m < 0 ? d%m + m : d%m</tt> */ - public static int mod(final int d, final int m) { - final int r = d % m; + public static int mod(int d, int m) { + int r = d % m; return r < 0 ? r + m : r % m; } /** * Modulo that returns positive values even for negative arguments. - * - * @param d - * @param m + * * @return <tt>d%m < 0 ? d%m + m : d%m</tt> */ - public static long mod(final long d, final long m) { - final long r = d % m; + public static long mod(long d, long m) { + long r = d % m; return r < 0 ? r + m : r % m; } @@ -211,8 +165,9 @@ public static long mod(final long d, final long m) { * <p> * This method can be up to 20 times faster than the default {@link Math#floor(double)} (both with and without casting to long). */ - public static long floor(final double d) { - final long l = (long) d; + public static long floor(double d) { + d += Skript.EPSILON; + long l = (long) d; if (!(d < 0)) // d >= 0 || d == NaN return l; if (l == Long.MIN_VALUE) @@ -225,8 +180,9 @@ public static long floor(final double d) { * <p> * This method can be up to 20 times faster than the default {@link Math#ceil(double)} (both with and without casting to long). */ - public static long ceil(final double d) { - final long l = (long) d; + public static long ceil(double d) { + d -= Skript.EPSILON; + long l = (long) d; if (!(d > 0)) // d <= 0 || d == NaN return l; if (l == Long.MAX_VALUE) @@ -239,16 +195,16 @@ public static long ceil(final double d) { * <p> * This method is more exact and faster than {@link Math#round(double)} of Java 7 and older. */ - public static long round(final double d) { - if (d == 0x1.fffffffffffffp-2) // greatest double value less than 0.5 - return 0; + public static long round(double d) { + d += Skript.EPSILON; if (Math.getExponent(d) >= 52) return (long) d; return floor(d + 0.5); } - public static int floorI(final double d) { - final int i = (int) d; + public static int floorI(double d) { + d += Skript.EPSILON; + int i = (int) d; if (!(d < 0)) // d >= 0 || d == NaN return i; if (i == Integer.MIN_VALUE) @@ -256,8 +212,9 @@ public static int floorI(final double d) { return d == i ? i : i - 1; } - public static int ceilI(final double d) { - final int i = (int) d; + public static int ceilI(double d) { + d -= Skript.EPSILON; + int i = (int) d; if (!(d > 0)) // d <= 0 || d == NaN return i; if (i == Integer.MAX_VALUE) @@ -265,196 +222,20 @@ public static int ceilI(final double d) { return d == i ? i : i + 1; } - public static int roundI(final double d) { - if (d == 0x1.fffffffffffffp-2) // greatest double value less than 0.5 - return 0; - if (Math.getExponent(d) >= 52) - return (int) d; - return floorI(d + 0.5); - } - - public static long floor(final float f) { - final long l = (long) f; + public static long floor(float f) { + f += Skript.EPSILON; + long l = (long) f; if (!(f < 0)) // f >= 0 || f == NaN return l; if (l == Long.MIN_VALUE) return Long.MIN_VALUE; return f == l ? l : l - 1; } - - public static long ceil(final float f) { - final long l = (long) f; - if (!(f > 0)) // f <= 0 || f == NaN - return l; - if (l == Long.MAX_VALUE) - return Long.MAX_VALUE; - return f == l ? l : l + 1; - } - - /** - * Rounds the given float (where .5 is rounded up) and returns the result as a long. - * <p> - * This method is more exact and faster than {@link Math#round(float)} of Java 7 and older. - */ - public static long round(final float f) { - if (f == 0x1.fffffep-2f) // greatest float value less than 0.5 - return 0; - if (Math.getExponent(f) >= 23) - return (long) f; - return floor(f + 0.5f); - } - - public static int floorI(final float f) { - final int i = (int) f; - if (!(f < 0)) // f >= 0 || f == NaN - return i; - if (i == Integer.MIN_VALUE) - return Integer.MIN_VALUE; - return f == i ? i : i - 1; - } - - public static int ceilI(final float f) { - final int i = (int) f; - if (!(f > 0)) // f <= 0 || f == NaN - return i; - if (i == Integer.MAX_VALUE) - return Integer.MAX_VALUE; - return f == i ? i : i + 1; - } - - /** - * Rounds the given float (where .5 is rounded up) and returns the result as an int. - * <p> - * This method is more exact and faster than {@link Math#round(float)} of Java 7 and older. - */ - public static int roundI(final float f) { - if (f == 0x1.fffffep-2f) // greatest float value less than 0.5 - return 0; - if (Math.getExponent(f) >= 23) - return (int) f; - return floorI(f + 0.5f); - } - - /** - * Gets the smallest power of two ≥n. Returns {@link Integer#MIN_VALUE} if <tt>n > 2<sup>30</sup></tt>. - */ - public static int nextPowerOfTwo(final int n) { - if (n < 0) { - int h = ~n; - h |= (h >> 1); - h |= (h >> 2); - h |= (h >> 4); - h |= (h >> 8); - h |= (h >> 16); - h = ~h; - return n == h ? n : h >> 1; - } else { - final int h = Integer.highestOneBit(n); - return n == h ? n : h << 1; - } - } - - /** - * Gets the smallest power of two ≥n. Returns {@link Long#MIN_VALUE} if <tt>n > 2<sup>62</sup></tt>. - */ - public static long nextPowerOfTwo(final long n) { - if (n < 0) { - long h = ~n; - h |= (h >> 1); - h |= (h >> 2); - h |= (h >> 4); - h |= (h >> 8); - h |= (h >> 16); - h |= (h >> 32); - h = ~h; - return n == h ? n : h >> 1; - } else { - final long h = Long.highestOneBit(n); - return n == h ? n : h << 1; - } - } - - /** - * @return The floating point part of d in the range [0, 1) - */ - public static double frac(final double d) { - final double r = mod(d, 1); - return r == 1 ? 0 : r; - } - - /** - * @return The floating point part of f in the range [0, 1) - */ - public static float frac(final float f) { - final float r = mod(f, 1); - return r == 1 ? 0 : r; - } - - /** - * @return -1 if i is negative, 0 if i is 0, or 1 if i is positive - */ - public static int sign(final byte i) { - return (i >> 7) | (-i >>> 7); - } - - /** - * @return -1 if i is negative, 0 if i is 0, or 1 if i is positive - */ - public static int sign(final short i) { - return (i >> 15) | (-i >>> 15); - } - - /** - * @return -1 if i is negative, 0 if i is 0, or 1 if i is positive - */ - public static int sign(final int i) { - return (i >> 31) | (-i >>> 31); - } - - /** - * @return -1 if i is negative, 0 if i is 0, or 1 if i is positive - */ - public static int sign(final long i) { - return (int) (i >> 63) | (int) (-i >>> 63); - } - - /** - * @return -1 if f is negative, 0 if f is +0, -0 or NaN, or 1 if f is positive - */ - public static int sign(final float f) { - return f > 0 ? 1 : f < 0 ? -1 : 0; - } - - /** - * @return -1 if d is negative, 0 if d is +0, -0 or NaN, or 1 if d is positive - */ - public static int sign(final double d) { - return d > 0 ? 1 : d < 0 ? -1 : 0; - } - - /** - * Performs a hermite interpolation between the given values, or returns 0 or 1 respectively if the value is out of range. - * <p> - * Specifically this method returns <tt>d * d * (3 - 2 * d)</tt>, where <tt>d = {@link #fit(double, double, double) fit}(0, (x - x1) / (x2 - x1), 1)</tt>. This is very similar - * to <tt>0.5 - 0.5 * cos(PI * d)</tt>. - * <p> - * This function is essentially equal to GLSL's smoothstep, but with a different argument order. - * - * @param x The value to get the step at - * @param x1 The lower end of the step - * @param x2 The upper end of the step - * @return The step's value at <tt>x</tt> - */ - public static double smoothStep(final double x, final double x1, final double x2) { - final double d = fit(0, (x - x1) / (x2 - x1), 1); - return d * d * (3 - 2 * d); - } - + /** * Guarantees a float is neither NaN nor INF. * Useful for situations when safe floats are required. - * - * @param f + * * @return 0 if f is NaN or INF, otherwise f */ public static float safe(float f) { @@ -462,18 +243,5 @@ public static float safe(float f) { return 0; return f; } - - /** - * Guarantees a double is neither NaN nor INF. - * Useful for situations when safe doubles are required. - * - * @param d - * @return 0 if d is NaN or INF, otherwise d - */ - public static double safe(double d) { - if (d != d || Double.isInfinite(d)) //NaN or INF - return 0; - return d; - } - + } diff --git a/src/test/skript/tests/regressions/4235-floating point errors rounding functions.sk b/src/test/skript/tests/regressions/4235-floating point errors rounding functions.sk new file mode 100644 index 00000000000..ad8ecff83d5 --- /dev/null +++ b/src/test/skript/tests/regressions/4235-floating point errors rounding functions.sk @@ -0,0 +1,12 @@ +test "floating point errors in rounding functions": + #assert ceil(100*0.07) is 7 with "ceil function doesn't adjust for floating point errors ##1" + #assert ceil(100*0.033 - 0.3) is 3 with "ceil function doesn't adjust for floating point errors ##2" + + #assert rounded up 100*0.07 is 7 with "ceil expression doesn't adjust for floating point errors ##1" + #assert rounded up 100*0.033 - 0.3 is 3 with "ceil expression doesn't adjust for floating point errors ##2" + + set {_sum} to 0 + loop 100 times: + add 0.1 to {_sum} + assert floor({_sum}) is 10 with "floor function doesn't adjust for floating point errors ##1" + assert rounded down {_sum} is 10 with "floor expression doesn't adjust for floating point errors ##1" From 99ead398c74d8494bd8cc7974d51123d0881a005 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Thu, 29 Dec 2022 03:07:10 -0500 Subject: [PATCH 179/619] Structure API Improvements and Fixes (#5144) --- .../java/ch/njol/skript/ScriptLoader.java | 5 +- src/main/java/ch/njol/skript/Skript.java | 2 +- .../ch/njol/skript/SkriptEventHandler.java | 22 +++- .../java/ch/njol/skript/command/Commands.java | 2 +- .../skript/conditions/CondScriptLoaded.java | 49 +++++---- .../ch/njol/skript/effects/EffChange.java | 2 +- .../skript/effects/EffSuppressWarnings.java | 9 +- .../njol/skript/structures/StructCommand.java | 20 ++-- .../skript/structures/StructFunction.java | 10 +- .../skript/lang/entry/EntryContainer.java | 8 +- .../skript/lang/entry/EntryValidator.java | 104 +++++++++++------- 11 files changed, 144 insertions(+), 89 deletions(-) diff --git a/src/main/java/ch/njol/skript/ScriptLoader.java b/src/main/java/ch/njol/skript/ScriptLoader.java index b054ad219f7..fdca5b339ce 100644 --- a/src/main/java/ch/njol/skript/ScriptLoader.java +++ b/src/main/java/ch/njol/skript/ScriptLoader.java @@ -480,7 +480,9 @@ public static CompletableFuture<ScriptInfo> loadScripts(Collection<File> files, * * @param configs Configs representing scripts. * @param openCloseable An {@link OpenCloseable} that will be called before and after - * each individual script load (see {@link #makeFuture(Supplier, OpenCloseable)}). + * each individual script load (see {@link #makeFuture(Supplier, OpenCloseable)}). + * Note that this is also opened before the {@link Structure#preLoad()} stage + * and closed after the {@link Structure#postLoad()} stage. * @return Info on the loaded scripts. */ private static CompletableFuture<ScriptInfo> loadScripts(List<Config> configs, OpenCloseable openCloseable) { @@ -530,6 +532,7 @@ private static CompletableFuture<ScriptInfo> loadScripts(List<Config> configs, O parser.setActive(script); parser.setCurrentStructure(structure); + parser.setNode(structure.getEntryContainer().getSource()); try { if (!structure.preLoad()) diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index a147a0e358e..f40a10a1990 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -760,7 +760,7 @@ protected void afterErrors() { File scriptsFolder = getScriptsFolder(); ScriptLoader.updateDisabledScripts(scriptsFolder.toPath()); - ScriptLoader.loadScripts(scriptsFolder, OpenCloseable.EMPTY) + ScriptLoader.loadScripts(scriptsFolder, logHandler) .thenAccept(scriptInfo -> { try { if (logHandler.getCount() == 0) diff --git a/src/main/java/ch/njol/skript/SkriptEventHandler.java b/src/main/java/ch/njol/skript/SkriptEventHandler.java index 660556ba035..33ff353579b 100644 --- a/src/main/java/ch/njol/skript/SkriptEventHandler.java +++ b/src/main/java/ch/njol/skript/SkriptEventHandler.java @@ -34,6 +34,7 @@ import org.bukkit.plugin.RegisteredListener; import org.eclipse.jdt.annotation.Nullable; +import java.lang.ref.WeakReference; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; @@ -287,18 +288,31 @@ public static void unregisterBukkitEvents(Trigger trigger) { public static final Set<Class<? extends Event>> listenCancelled = new HashSet<>(); /** - * A cache for the getHandlerList methods of Event classes + * A cache for the getHandlerList methods of Event classes. */ private static final Map<Class<? extends Event>, Method> handlerListMethods = new HashMap<>(); + /** + * A cache for obtained HandlerLists. + */ + private static final Map<Method, WeakReference<HandlerList>> handlerListCache = new HashMap<>(); + @Nullable - @SuppressWarnings("ThrowableNotThrown") private static HandlerList getHandlerList(Class<? extends Event> eventClass) { try { Method method = getHandlerListMethod(eventClass); - method.setAccessible(true); - return (HandlerList) method.invoke(null); + + WeakReference<HandlerList> handlerListReference = handlerListCache.get(method); + HandlerList handlerList = handlerListReference != null ? handlerListReference.get() : null; + if (handlerList == null) { + method.setAccessible(true); + handlerList = (HandlerList) method.invoke(null); + handlerListCache.put(method, new WeakReference<>(handlerList)); + } + + return handlerList; } catch (Exception ex) { + //noinspection ThrowableNotThrown Skript.exception(ex, "Failed to get HandlerList for event " + eventClass.getName()); return null; } diff --git a/src/main/java/ch/njol/skript/command/Commands.java b/src/main/java/ch/njol/skript/command/Commands.java index bd28ed59288..d55b427d264 100644 --- a/src/main/java/ch/njol/skript/command/Commands.java +++ b/src/main/java/ch/njol/skript/command/Commands.java @@ -287,7 +287,7 @@ public static void unregisterCommand(ScriptCommand scriptCommand) { assert cmKnownCommands != null;// && cmAliases != null; scriptCommand.unregister(commandMap, cmKnownCommands, cmAliases); } - commands.values().remove(scriptCommand); + commands.values().removeIf(command -> command == scriptCommand); } private static boolean registeredListeners = false; diff --git a/src/main/java/ch/njol/skript/conditions/CondScriptLoaded.java b/src/main/java/ch/njol/skript/conditions/CondScriptLoaded.java index 2590c71eb3c..e667cab90ef 100644 --- a/src/main/java/ch/njol/skript/conditions/CondScriptLoaded.java +++ b/src/main/java/ch/njol/skript/conditions/CondScriptLoaded.java @@ -27,12 +27,15 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Condition; import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.parser.ParserInstance; import org.skriptlang.skript.lang.script.Script; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; +import java.io.File; + @Name("Is Script Loaded") @Description("Check if the current script, or another script, is currently loaded.") @Examples({ @@ -45,7 +48,8 @@ public class CondScriptLoaded extends Condition { static { Skript.registerCondition(CondScriptLoaded.class, "script[s] [%-strings%] (is|are) loaded", - "script[s] [%-strings%] (isn't|is not|aren't|are not) loaded"); + "script[s] [%-strings%] (isn't|is not|aren't|are not) loaded" + ); } @Nullable @@ -54,42 +58,41 @@ public class CondScriptLoaded extends Condition { private Script currentScript; @Override - @SuppressWarnings({"unchecked"}) + @SuppressWarnings("unchecked") public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { scripts = (Expression<String>) exprs[0]; - currentScript = getParser().getCurrentScript(); + + ParserInstance parser = getParser(); if (scripts == null) { - Skript.error("The condition 'script loaded' requires a script name argument when used outside of script files"); - return false; + if (parser.isActive()) { // no scripts provided means use current script + currentScript = parser.getCurrentScript(); + } else { // parser is inactive but no scripts were provided + Skript.error("The condition 'script loaded' requires a script name argument when used outside of script files"); + return false; + } } + setNegated(matchedPattern == 1); return true; } @Override - public boolean check(Event e) { - if (scripts == null) { - if (currentScript == null) - return isNegated(); + public boolean check(Event event) { + if (scripts == null) return ScriptLoader.getLoadedScripts().contains(currentScript) ^ isNegated(); - } - return scripts.check(e, - scriptName -> ScriptLoader.getLoadedScripts().contains(ScriptLoader.getScript(SkriptCommand.getScriptFromName(scriptName))), - isNegated()); + return scripts.check(event, scriptName -> { + File scriptFile = SkriptCommand.getScriptFromName(scriptName); + return scriptFile != null && ScriptLoader.getLoadedScripts().contains(ScriptLoader.getScript(scriptFile)); + }, isNegated()); } @Override - public String toString(@Nullable Event e, boolean debug) { - String scriptName; - if (scripts == null) - scriptName = "script"; - else - scriptName = (scripts.isSingle() ? "script" : "scripts" + " " + scripts.toString(e, debug)); - boolean isSingle = scripts == null || scripts.isSingle(); - if (isSingle) + public String toString(@Nullable Event event, boolean debug) { + String scriptName = scripts == null ? + "script" : (scripts.isSingle() ? "script" : "scripts" + " " + scripts.toString(event, debug)); + if (scripts == null || scripts.isSingle()) return scriptName + (isNegated() ? " isn't" : " is") + " loaded"; - else - return scriptName + (isNegated() ? " aren't" : " are") + " loaded"; + return scriptName + (isNegated() ? " aren't" : " are") + " loaded"; } } diff --git a/src/main/java/ch/njol/skript/effects/EffChange.java b/src/main/java/ch/njol/skript/effects/EffChange.java index 1af18c8f36f..12d46e56b4c 100644 --- a/src/main/java/ch/njol/skript/effects/EffChange.java +++ b/src/main/java/ch/njol/skript/effects/EffChange.java @@ -258,7 +258,7 @@ else if (mode == ChangeMode.SET) if (changed instanceof Variable && !((Variable<?>) changed).isLocal() && (mode == ChangeMode.SET || ((Variable<?>) changed).isList() && mode == ChangeMode.ADD)) { final ClassInfo<?> ci = Classes.getSuperClassInfo(ch.getReturnType()); if (ci.getC() != Object.class && ci.getSerializer() == null && ci.getSerializeAs() == null && !SkriptConfig.disableObjectCannotBeSavedWarnings.value()) { - if (!getParser().getCurrentScript().suppressesWarning(ScriptWarning.VARIABLE_SAVE)) { + if (getParser().isActive() && !getParser().getCurrentScript().suppressesWarning(ScriptWarning.VARIABLE_SAVE)) { Skript.warning(ci.getName().withIndefiniteArticle() + " cannot be saved, i.e. the contents of the variable " + changed + " will be lost when the server stops."); } } diff --git a/src/main/java/ch/njol/skript/effects/EffSuppressWarnings.java b/src/main/java/ch/njol/skript/effects/EffSuppressWarnings.java index 9137bafee06..9d422f36137 100644 --- a/src/main/java/ch/njol/skript/effects/EffSuppressWarnings.java +++ b/src/main/java/ch/njol/skript/effects/EffSuppressWarnings.java @@ -51,6 +51,11 @@ public class EffSuppressWarnings extends Effect { @Override public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { + if (!getParser().isActive()) { + Skript.error("You can't suppress warnings outside of a script!"); + return false; + } + mark = parseResult.mark; if (mark == 1) { Skript.warning("Variable conflict warnings no longer need suppression, as they have been removed altogether"); @@ -61,10 +66,10 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye } @Override - protected void execute(Event e) { } + protected void execute(Event event) { } @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { String word; switch (mark) { case CONFLICT: diff --git a/src/main/java/ch/njol/skript/structures/StructCommand.java b/src/main/java/ch/njol/skript/structures/StructCommand.java index bee10ce76f7..22ea200ca08 100644 --- a/src/main/java/ch/njol/skript/structures/StructCommand.java +++ b/src/main/java/ch/njol/skript/structures/StructCommand.java @@ -87,7 +87,7 @@ public class StructCommand extends Structure { ARGUMENT_PATTERN = Pattern.compile("<\\s*(?:([^>]+?)\\s*:\\s*)?(.+?)\\s*(?:=\\s*(" + SkriptParser.wildcard + "))?\\s*>"), DESCRIPTION_PATTERN = Pattern.compile("(?<!\\\\)%-?(.+?)%"); - private static final AtomicBoolean syncCommands = new AtomicBoolean(); + private static final AtomicBoolean SYNC_COMMANDS = new AtomicBoolean(); static { Skript.registerStructure( @@ -133,9 +133,12 @@ protected Integer getValue(String value) { }) .addEntryData(new LiteralEntryData<>("cooldown", null, true, Timespan.class)) .addEntryData(new VariableStringEntryData("cooldown message", null, true, CommandEvent.class)) - .addEntry("cooldown bypass", null,true) + .addEntry("cooldown bypass", null, true) .addEntryData(new VariableStringEntryData("cooldown storage", null, true, StringMode.VARIABLE_NAME, CommandEvent.class)) .addSection("trigger", false) + .unexpectedEntryMessage(key -> + "Unexpected entry '" + key + "'. Check that it's spelled correctly, and ensure that you have put all code into a trigger." + ) .build(), "command <.+>" ); @@ -304,7 +307,7 @@ public boolean load() { getParser().deleteCurrentEvent(); Commands.registerCommand(scriptCommand); - syncCommands.set(true); + SYNC_COMMANDS.set(true); return true; } @@ -317,10 +320,9 @@ public boolean postLoad() { @Override public void unload() { - if (scriptCommand != null) { - Commands.unregisterCommand(scriptCommand); - syncCommands.set(true); - } + assert scriptCommand != null; // This method should never be called if one of the loading methods fail + Commands.unregisterCommand(scriptCommand); + SYNC_COMMANDS.set(true); } @Override @@ -329,8 +331,8 @@ public void postUnload() { } private void attemptCommandSync() { - if (syncCommands.get()) { - syncCommands.set(false); + if (SYNC_COMMANDS.get()) { + SYNC_COMMANDS.set(false); if (CommandReloader.syncCommands(Bukkit.getServer())) { Skript.debug("Commands synced to clients"); } else { diff --git a/src/main/java/ch/njol/skript/structures/StructFunction.java b/src/main/java/ch/njol/skript/structures/StructFunction.java index 4641d7bbfc7..eab8583aed8 100644 --- a/src/main/java/ch/njol/skript/structures/StructFunction.java +++ b/src/main/java/ch/njol/skript/structures/StructFunction.java @@ -54,7 +54,7 @@ public class StructFunction extends Structure { public static final Priority PRIORITY = new Priority(400); - private static final AtomicBoolean validateFunctions = new AtomicBoolean(); + private static final AtomicBoolean VALIDATE_FUNCTIONS = new AtomicBoolean(); static { Skript.registerStructure(StructFunction.class, @@ -97,15 +97,15 @@ public boolean load() { parser.deleteCurrentEvent(); - validateFunctions.set(true); + VALIDATE_FUNCTIONS.set(true); return true; } @Override public boolean postLoad() { - if (validateFunctions.get()) { - validateFunctions.set(false); + if (VALIDATE_FUNCTIONS.get()) { + VALIDATE_FUNCTIONS.set(false); Functions.validateFunctions(); } return true; @@ -114,7 +114,7 @@ public boolean postLoad() { @Override public void unload() { Functions.unregisterFunction(signature); - validateFunctions.set(true); + VALIDATE_FUNCTIONS.set(true); } @Override diff --git a/src/main/java/org/skriptlang/skript/lang/entry/EntryContainer.java b/src/main/java/org/skriptlang/skript/lang/entry/EntryContainer.java index 77856618d05..bc277094db6 100644 --- a/src/main/java/org/skriptlang/skript/lang/entry/EntryContainer.java +++ b/src/main/java/org/skriptlang/skript/lang/entry/EntryContainer.java @@ -68,10 +68,10 @@ public SectionNode getSource() { } /** - * @return Any nodes unhandled by the EntryValidator. - * {@link EntryValidator#allowsUnknownEntries()} or {@link EntryValidator#allowsUnknownSections()} must be true - * for this list to contain any values. The 'unhandled node' would represent any entry provided by the user that the validator - * is not explicitly aware of. + * @return Any nodes unhandled by the {@link EntryValidator}. + * The validator must have a node testing predicate for this list to contain any values. + * The 'unhandled node' would represent any entry provided by the user that the validator + * is not explicitly aware of. */ public List<Node> getUnhandledNodes() { return unhandledNodes; diff --git a/src/main/java/org/skriptlang/skript/lang/entry/EntryValidator.java b/src/main/java/org/skriptlang/skript/lang/entry/EntryValidator.java index cfb5a99704c..942b8e439de 100644 --- a/src/main/java/org/skriptlang/skript/lang/entry/EntryValidator.java +++ b/src/main/java/org/skriptlang/skript/lang/entry/EntryValidator.java @@ -18,17 +18,20 @@ */ package org.skriptlang.skript.lang.entry; +import ch.njol.skript.ScriptLoader; import ch.njol.skript.Skript; import ch.njol.skript.config.Node; import ch.njol.skript.config.SectionNode; -import ch.njol.skript.config.SimpleNode; import org.eclipse.jdt.annotation.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.function.Function; +import java.util.function.Predicate; /** * A validator for storing {@link EntryData}. @@ -45,13 +48,31 @@ public static EntryValidatorBuilder builder() { return new EntryValidatorBuilder(); } + private static final Function<String, String> + DEFAULT_UNEXPECTED_ENTRY_MESSAGE = + key -> "Unexpected entry '" + key + "'. Check whether it's spelled correctly or remove it", + DEFAULT_MISSING_REQUIRED_ENTRY_MESSAGE = + key -> "Required entry '" + key + "' is missing"; + private final List<EntryData<?>> entryData; - private final boolean allowUnknownEntries, allowUnknownSections; - private EntryValidator(List<EntryData<?>> entryData, boolean allowUnknownEntries, boolean allowUnknownSections) { + @Nullable + private final Predicate<Node> unexpectedNodeTester; + + private final Function<String, String> unexpectedEntryMessage, missingRequiredEntryMessage; + + private EntryValidator( + List<EntryData<?>> entryData, + @Nullable Predicate<Node> unexpectedNodeTester, + @Nullable Function<String, String> unexpectedEntryMessage, + @Nullable Function<String, String> missingRequiredEntryMessage + ) { this.entryData = entryData; - this.allowUnknownEntries = allowUnknownEntries; - this.allowUnknownSections = allowUnknownSections; + this.unexpectedNodeTester = unexpectedNodeTester; + this.unexpectedEntryMessage = + unexpectedEntryMessage != null ? unexpectedEntryMessage : DEFAULT_UNEXPECTED_ENTRY_MESSAGE; + this.missingRequiredEntryMessage = + missingRequiredEntryMessage != null ? missingRequiredEntryMessage : DEFAULT_MISSING_REQUIRED_ENTRY_MESSAGE; } /** @@ -61,20 +82,6 @@ public List<EntryData<?>> getEntryData() { return Collections.unmodifiableList(entryData); } - /** - * @return Whether this validator allows {@link SimpleNode}-based entries not declared in the entry data map. - */ - public boolean allowsUnknownEntries() { - return allowUnknownEntries; - } - - /** - * @return Whether this validator allows {@link SectionNode}-based entries not declared in the entry data map. - */ - public boolean allowsUnknownSections() { - return allowUnknownSections; - } - /** * Validates a node using this entry validator. * @param sectionNode The node to validate. @@ -94,33 +101,29 @@ public EntryContainer validate(SectionNode sectionNode) { continue; // The first step is to determine if the node is present in the entry data list - - for (EntryData<?> data : entryData) { + Iterator<EntryData<?>> iterator = entries.iterator(); + while (iterator.hasNext()) { + EntryData<?> data = iterator.next(); if (data.canCreateWith(node)) { // Determine if it's a match handledNodes.put(data.getKey(), node); // This is a known node, mark it as such - entries.remove(data); + iterator.remove(); continue nodeLoop; } } // We found no matching entry data - - if ( - (!allowUnknownEntries && node instanceof SimpleNode) - || (!allowUnknownSections && node instanceof SectionNode) - ) { + if (unexpectedNodeTester == null || unexpectedNodeTester.test(node)) { ok = false; // Instead of terminating here, we should try and print all errors possible - Skript.error("Unexpected entry '" + node.getKey() + "'. Check whether it's spelled correctly or remove it"); + Skript.error(unexpectedEntryMessage.apply(ScriptLoader.replaceOptions(node.getKey()))); } else { // This validator allows this type of node to be unhandled unhandledNodes.add(node); } } // Now we're going to check for missing entries that are *required* - for (EntryData<?> entryData : entries) { if (!entryData.isOptional()) { - Skript.error("Required entry '" + entryData.getKey() + "' is missing"); + Skript.error(missingRequiredEntryMessage.apply(entryData.getKey())); ok = false; } } @@ -146,7 +149,12 @@ private EntryValidatorBuilder() { } private final List<EntryData<?>> entryData = new ArrayList<>(); private String entrySeparator = DEFAULT_ENTRY_SEPARATOR; - private boolean allowUnknownEntries, allowUnknownSections; + + @Nullable + private Predicate<Node> unexpectedNodeTester; + + @Nullable + private Function<String, String> unexpectedEntryMessage, missingRequiredEntryMessage; /** * Updates the separator to be used when creating KeyValue entries. Please note @@ -160,20 +168,38 @@ public EntryValidatorBuilder entrySeparator(String separator) { } /** - * Sets that the validator should accept {@link SimpleNode}-based entries not declared in the entry data map. + * A predicate to be supplied for checking whether a Node should be allowed + * even as an entry not declared in the entry data map. + * The default behavior is that the predicate returns true for every Node tested. + * @param unexpectedNodeTester The predicate to use. + * @return The builder instance. + */ + public EntryValidatorBuilder unexpectedNodeTester(Predicate<Node> unexpectedNodeTester) { + this.unexpectedNodeTester = unexpectedNodeTester; + return this; + } + + /** + * A function to be applied when an unexpected Node is encountered during validation. + * A String representing the user input (the Node's key) goes in, + * and an error message to output comes out. + * @param unexpectedEntryMessage The function to use. * @return The builder instance. */ - public EntryValidatorBuilder allowUnknownEntries() { - allowUnknownEntries = true; + public EntryValidatorBuilder unexpectedEntryMessage(Function<String, String> unexpectedEntryMessage) { + this.unexpectedEntryMessage = unexpectedEntryMessage; return this; } /** - * Sets that the validator should accept {@link SectionNode}-based entries not declared in the entry data map. + * A function to be applied when a required Node is missing during validation. + * A String representing the key of the missing entry goes in, + * and an error message to output comes out. + * @param missingRequiredEntryMessage The function to use. * @return The builder instance. */ - public EntryValidatorBuilder allowUnknownSections() { - allowUnknownSections = true; + public EntryValidatorBuilder missingRequiredEntryMessage(Function<String, String> missingRequiredEntryMessage) { + this.missingRequiredEntryMessage = missingRequiredEntryMessage; return this; } @@ -227,7 +253,9 @@ public EntryValidatorBuilder addEntryData(EntryData<?> entryData) { * @return The final, built entry validator. */ public EntryValidator build() { - return new EntryValidator(entryData, allowUnknownEntries, allowUnknownSections); + return new EntryValidator( + entryData, unexpectedNodeTester, unexpectedEntryMessage, missingRequiredEntryMessage + ); } } From 1610b779334912836a53c114ef2c2348893883c9 Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Thu, 29 Dec 2022 11:16:55 +0100 Subject: [PATCH 180/619] Add MarkedForRemoval annotation (#5278) --- .../ch/njol/skript/util/MarkedForRemoval.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/main/java/ch/njol/skript/util/MarkedForRemoval.java diff --git a/src/main/java/ch/njol/skript/util/MarkedForRemoval.java b/src/main/java/ch/njol/skript/util/MarkedForRemoval.java new file mode 100644 index 00000000000..87739c1a333 --- /dev/null +++ b/src/main/java/ch/njol/skript/util/MarkedForRemoval.java @@ -0,0 +1,43 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.util; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Marks the annotated element as being subject to removal in the future. + * <p> + * The annotated element should also be annotated with {@link Deprecated}. + * <p> + * It is recommended to provide when the annotated element will be removed, + * using the {@code version} element. + */ +@Retention(RetentionPolicy.SOURCE) +public @interface MarkedForRemoval { + + /** + * When the annotated element is expected to be removed. + * <p> + * For example, this could be {@code after "2.6.4"}, + * {@code "starting from 2.7"} or simply {@code "2.7"}. + */ + String version() default ""; + +} From 7f62d7681b0f93421ad6a809012acd9750608314 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Fri, 30 Dec 2022 11:57:40 -0700 Subject: [PATCH 181/619] Update EffActionBar.java (#5294) --- src/main/java/ch/njol/skript/effects/EffActionBar.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffActionBar.java b/src/main/java/ch/njol/skript/effects/EffActionBar.java index 980e7d7974c..09d62e9044c 100644 --- a/src/main/java/ch/njol/skript/effects/EffActionBar.java +++ b/src/main/java/ch/njol/skript/effects/EffActionBar.java @@ -43,7 +43,7 @@ public class EffActionBar extends Effect { static { - Skript.registerEffect(EffActionBar.class, "send [the] action bar [with text] %string% to %players%"); + Skript.registerEffect(EffActionBar.class, "send [the] action[ ]bar [with text] %string% to %players%"); } @SuppressWarnings("null") @@ -75,4 +75,4 @@ public String toString(final @Nullable Event e, final boolean debug) { return "send action bar " + message.toString(e, debug) + " to " + recipients.toString(e, debug); } -} \ No newline at end of file +} From 63978505ae61dd3dcfaf4326b76dca43e9da93ec Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Mon, 2 Jan 2023 04:01:02 -0700 Subject: [PATCH 182/619] Fix examples and lang files not generating (#5300) --- src/main/java/ch/njol/skript/Skript.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index f40a10a1990..1399946e69b 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -393,7 +393,7 @@ public void onEnable() { if (!getDataFolder().isDirectory()) getDataFolder().mkdirs(); - scriptsFolder = new File(getDataFolder(), SCRIPTSFOLDER + File.separator); + scriptsFolder = new File(getDataFolder(), SCRIPTSFOLDER); File config = new File(getDataFolder(), "config.sk"); File features = new File(getDataFolder(), "features.sk"); File lang = new File(getDataFolder(), "lang"); @@ -419,16 +419,16 @@ public void onEnable() { if (e.isDirectory()) continue; File saveTo = null; - if (populateExamples && e.getName().startsWith(SCRIPTSFOLDER + File.separator)) { + if (populateExamples && e.getName().startsWith(SCRIPTSFOLDER + "/")) { String fileName = e.getName().substring(e.getName().lastIndexOf(File.separatorChar) + 1); if (fileName.startsWith(ScriptLoader.DISABLED_SCRIPT_PREFIX)) fileName = ScriptLoader.DISABLED_SCRIPT_PREFIX + fileName; saveTo = new File(scriptsFolder, fileName); } else if (populateLanguageFiles - && e.getName().startsWith("lang" + File.separator) + && e.getName().startsWith("lang/") && e.getName().endsWith(".lang") - && !e.getName().endsWith(File.separator + "default.lang")) { - String fileName = e.getName().substring(e.getName().lastIndexOf(File.separatorChar) + 1); + && !e.getName().endsWith("/default.lang")) { + String fileName = e.getName().substring(e.getName().lastIndexOf("/") + 1); saveTo = new File(lang, fileName); } else if (e.getName().equals("config.sk")) { if (!config.exists()) From 3057a4073ea086ccbc2508fe924542be108908f5 Mon Sep 17 00:00:00 2001 From: DelayedGaming <72163224+DelayedGaming@users.noreply.github.com> Date: Mon, 2 Jan 2023 21:28:44 +0800 Subject: [PATCH 183/619] Support Worlds into ExprName.java (#5290) --- .../ch/njol/skript/expressions/ExprName.java | 124 ++++++++++-------- 1 file changed, 72 insertions(+), 52 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprName.java b/src/main/java/ch/njol/skript/expressions/ExprName.java index c0c26f40e8c..2b22278a768 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprName.java +++ b/src/main/java/ch/njol/skript/expressions/ExprName.java @@ -39,6 +39,7 @@ import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.World; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; @@ -58,47 +59,56 @@ import net.md_5.bungee.api.ChatColor; @Name("Name / Display Name / Tab List Name") -@Description({"Represents the Minecraft account, display or tab list name of a player, or the custom name of an item, entity, block, inventory, or gamerule.", - "", - "<ul>", - "\t<li><strong>Players</strong>", - "\t\t<ul>", - "\t\t\t<li><strong>Name:</strong> The Minecraft account name of the player. Can't be changed, but 'display name' can be changed.</li>", - "\t\t\t<li><strong>Display Name:</strong> The name of the player that is displayed in messages. " + - "This name can be changed freely and can include colour codes, and is shared among all plugins (e.g. chat plugins will use the display name).</li>", - "\t\t</ul>", - "\t</li>", - "\t<li><strong>Entities</strong>", - "\t\t<ul>", - "\t\t\t<li><strong>Name:</strong> The custom name of the entity. Can be changed. But for living entities, " + - "the players will have to target the entity to see its name tag. For non-living entities, the name will not be visible at all. To prevent this, use 'display name'.</li>", - "\t\t\t<li><strong>Display Name:</strong> The custom name of the entity. Can be changed, " + - "which will also enable <em>custom name visibility</em> of the entity so name tag of the entity will be visible always.</li>", - "\t\t</ul>", - "\t</li>", - "\t<li><strong>Items</strong>", - "\t\t<ul>", - "\t\t\t<li><strong>Name and Display Name:</strong> The <em>custom</em> name of the item (not the Minecraft locale name). Can be changed.</li>", - "\t\t</ul>", - "\t</li>", - "\t<li><strong>Inventories</strong>", - "\t\t<ul>", - "\t\t\t<li><strong>Name and Display Name:</strong> The name/title of the inventory. " + - "Changing name of an inventory means opening the same inventory with the same contents but with a different name to its current viewers.</li>", - "\t\t</ul>", - "\t</li>", - "\t<li><strong>Gamerules (1.13+)</strong>", - "\t\t<ul>", - "\t\t\t<li><strong>Name:</strong> The name of the gamerule. Cannot be changed.</li>", - "\t\t</ul>", - "\t</li>", - "</ul>"}) -@Examples({"on join:", - " player has permission \"name.red\"", - " set the player's display name to \"<red>[admin] <gold>%name of player%\"", - " set the player's tab list name to \"<green>%player's name%\"", - "set the name of the player's tool to \"Legendary Sword of Awesomeness\""}) -@Since("before 2.1, 2.2-dev20 (inventory name), 2.4 (non-living entity support, changeable inventory name)") +@Description({ + "Represents the Minecraft account, display or tab list name of a player, or the custom name of an item, entity, block, inventory, gamerule or world.", + "", + "<ul>", + "\t<li><strong>Players</strong>", + "\t\t<ul>", + "\t\t\t<li><strong>Name:</strong> The Minecraft account name of the player. Can't be changed, but 'display name' can be changed.</li>", + "\t\t\t<li><strong>Display Name:</strong> The name of the player that is displayed in messages. " + + "This name can be changed freely and can include colour codes, and is shared among all plugins (e.g. chat plugins will use the display name).</li>", + "\t\t</ul>", + "\t</li>", + "\t<li><strong>Entities</strong>", + "\t\t<ul>", + "\t\t\t<li><strong>Name:</strong> The custom name of the entity. Can be changed. But for living entities, " + + "the players will have to target the entity to see its name tag. For non-living entities, the name will not be visible at all. To prevent this, use 'display name'.</li>", + "\t\t\t<li><strong>Display Name:</strong> The custom name of the entity. Can be changed, " + + "which will also enable <em>custom name visibility</em> of the entity so name tag of the entity will be visible always.</li>", + "\t\t</ul>", + "\t</li>", + "\t<li><strong>Items</strong>", + "\t\t<ul>", + "\t\t\t<li><strong>Name and Display Name:</strong> The <em>custom</em> name of the item (not the Minecraft locale name). Can be changed.</li>", + "\t\t</ul>", + "\t</li>", + "\t<li><strong>Inventories</strong>", + "\t\t<ul>", + "\t\t\t<li><strong>Name and Display Name:</strong> The name/title of the inventory. " + + "Changing name of an inventory means opening the same inventory with the same contents but with a different name to its current viewers.</li>", + "\t\t</ul>", + "\t</li>", + "\t<li><strong>Gamerules (1.13+)</strong>", + "\t\t<ul>", + "\t\t\t<li><strong>Name:</strong> The name of the gamerule. Cannot be changed.</li>", + "\t\t</ul>", + "\t</li>", + "\t<li><strong>Worlds</strong>", + "\t\t<ul>", + "\t\t\t<li><strong>Name:</strong> The name of the world. Cannot be changed.</li>", + "\t\t</ul>", + "\t</li>", + "</ul>" +}) +@Examples({ + "on join:", + "\tplayer has permission \"name.red\"", + "\tset the player's display name to \"<red>[admin] <gold>%name of player%\"", + "\tset the player's tab list name to \"<green>%player's name%\"", + "set the name of the player's tool to \"Legendary Sword of Awesomeness\"" +}) +@Since("before 2.1, 2.2-dev20 (inventory name), 2.4 (non-living entity support, changeable inventory name), INSERT VERSION (worlds)") public class ExprName extends SimplePropertyExpression<Object, String> { @Nullable @@ -107,8 +117,8 @@ public class ExprName extends SimplePropertyExpression<Object, String> { static { HAS_GAMERULES = Skript.classExists("org.bukkit.GameRule"); - register(ExprName.class, String.class, "(1¦name[s]|2¦(display|nick|chat|custom)[ ]name[s])", "offlineplayers/entities/blocks/itemtypes/inventories/slots" - + (HAS_GAMERULES ? "/gamerules" : "")); + register(ExprName.class, String.class, "(1¦name[s]|2¦(display|nick|chat|custom)[ ]name[s])", "offlineplayers/entities/blocks/itemtypes/inventories/slots/worlds" + + (HAS_GAMERULES ? "/gamerules" : "")); register(ExprName.class, String.class, "(3¦(player|tab)[ ]list name[s])", "players"); // Get the old method for getting the name of an inventory. @@ -132,6 +142,10 @@ public class ExprName extends SimplePropertyExpression<Object, String> { public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { mark = parseResult.mark; setExpr(exprs[0]); + if (mark != 1 && World.class.isAssignableFrom(getExpr().getReturnType())) { + Skript.error("Can't use 'display name' with worlds. Use 'name' instead."); + return false; + } return true; } @@ -165,8 +179,8 @@ public String convert(Object o) { if (TITLE_METHOD != null) { try { return (String) TITLE_METHOD.invoke(o); - } catch (Throwable e) { - Skript.exception(e); + } catch (Throwable ex) { + Skript.exception(ex); return null; } } else { @@ -180,9 +194,11 @@ public String convert(Object o) { ItemMeta m = is.getItemMeta(); return m.hasDisplayName() ? m.getDisplayName() : null; } + } else if (o instanceof World) { + return ((World) o).getName(); } else if (HAS_GAMERULES && o instanceof GameRule) { - return ((GameRule) o).getName(); - } + return ((GameRule) o).getName(); + } return null; } @@ -190,13 +206,17 @@ public String convert(Object o) { @Nullable public Class<?>[] acceptChange(ChangeMode mode) { if (mode == ChangeMode.SET || mode == ChangeMode.RESET) { - if (mark == 1 && Player.class.isAssignableFrom(getExpr().getReturnType())) { - Skript.error("Can't change the Minecraft name of a player. Change the 'display name' or 'tab list name' instead."); - return null; + if (mark == 1) { + if (Player.class.isAssignableFrom(getExpr().getReturnType())) { + Skript.error("Can't change the Minecraft name of a player. Change the 'display name' or 'tab list name' instead."); + return null; + } else if (World.class.isAssignableFrom(getExpr().getReturnType())) { + return null; + } } return CollectionUtils.array(String.class); } - return null; + return null; } @Override @@ -205,7 +225,7 @@ public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { for (Object o : getExpr().getArray(e)) { if (o instanceof Player) { switch (mark) { - case 2: + case 2: ((Player) o).setDisplayName(name != null ? name + ChatColor.RESET : ((Player) o).getName()); break; case 3: // Null check not necessary. This method will use the player's name if 'name' is null. From bb6ddc5dc1bf3708a5fe6b08a7029aa62008d167 Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Wed, 4 Jan 2023 04:19:04 +0100 Subject: [PATCH 184/619] ExprParse improvements (#5275) --- .../ch/njol/skript/expressions/ExprParse.java | 161 ++++++++++++------ .../ch/njol/skript/patterns/MatchResult.java | 20 +++ .../njol/skript/patterns/PatternCompiler.java | 4 +- .../njol/skript/patterns/SkriptPattern.java | 61 +++++++ 4 files changed, 189 insertions(+), 57 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprParse.java b/src/main/java/ch/njol/skript/expressions/ExprParse.java index 71348f6b716..d849688ef7f 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprParse.java +++ b/src/main/java/ch/njol/skript/expressions/ExprParse.java @@ -18,12 +18,6 @@ */ package ch.njol.skript.expressions; -import java.lang.reflect.Array; - -import org.bukkit.ChatColor; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.classes.Parser; @@ -43,8 +37,17 @@ import ch.njol.skript.log.LogEntry; import ch.njol.skript.log.ParseLogHandler; import ch.njol.skript.log.SkriptLogger; +import ch.njol.skript.patterns.MalformedPatternException; +import ch.njol.skript.patterns.MatchResult; +import ch.njol.skript.patterns.PatternCompiler; +import ch.njol.skript.patterns.SkriptPattern; import ch.njol.util.Kleenean; import ch.njol.util.NonNullPair; +import org.bukkit.ChatColor; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.lang.reflect.Array; @Name("Parse") @Description({"Parses text as a given type, or as a given pattern.", @@ -83,9 +86,10 @@ public class ExprParse extends SimpleExpression<Object> { private Expression<String> text; @Nullable - private String pattern; + private SkriptPattern pattern; @Nullable - private boolean[] plurals; + private boolean[] patternExpressionPlurals; + private boolean patternHasSingleExpression = false; @Nullable private ClassInfo<?> classInfo; @@ -96,43 +100,47 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye text = (Expression<String>) exprs[0]; if (exprs[1] == null) { String pattern = ChatColor.translateAlternateColorCodes('&', parseResult.regexes.get(0).group()); + if (!VariableString.isQuotedCorrectly(pattern, false)) { Skript.error("Invalid amount and/or placement of double quotes in '" + pattern + "'"); return false; } NonNullPair<String, boolean[]> p = SkriptParser.validatePattern(pattern); - if (p == null) + if (p == null) { + // Errored in validatePattern already return false; + } + + // Make all types in the pattern plural pattern = p.getFirst(); + patternExpressionPlurals = p.getSecond(); // Escape '¦' and ':' (used for parser tags/marks) - StringBuilder b = new StringBuilder(pattern.length()); - for (int i = 0; i < pattern.length(); i++) { - char c = pattern.charAt(i); - if (c == '\\') { - b.append(c); - b.append(pattern.charAt(i + 1)); - i++; - } else if (c == '¦' || c == ':') { - b.append("\\"); - b.append(c); - } else { - b.append(c); - } + pattern = escapeParseTags(pattern); + + // Compile the SkriptPattern + try { + this.pattern = PatternCompiler.compile(pattern); + } catch (MalformedPatternException exception) { + // Some checks already done by validatePattern above, but just making sure + Skript.error("Malformed pattern: " + exception.getMessage()); + return false; } - pattern = b.toString(); - this.pattern = pattern; - plurals = p.getSecond(); + // If the pattern contains at most 1 type, this expression is single + this.patternHasSingleExpression = this.pattern.countNonNullTypes() <= 1; } else { classInfo = ((Literal<ClassInfo<?>>) exprs[1]).getSingle(); + if (classInfo.getC() == String.class) { Skript.error("Parsing as text is useless as only things that are already text may be parsed"); return false; } - Parser<?> p = classInfo.getParser(); - if (p == null || !p.canParse(ParseContext.COMMAND)) { // TODO special parse context? + + // Make sure the ClassInfo has a parser + Parser<?> parser = classInfo.getParser(); + if (parser == null || !parser.canParse(ParseContext.COMMAND)) { // TODO special parse context? Skript.error("Text cannot be parsed as " + classInfo.getName().withIndefiniteArticle()); return false; } @@ -144,62 +152,76 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Nullable @SuppressWarnings("null") protected Object[] get(Event event) { - String t = text.getSingle(event); - if (t == null) + String text = this.text.getSingle(event); + if (text == null) return null; - ParseLogHandler h = SkriptLogger.startParseLogHandler(); + + ParseLogHandler parseLogHandler = SkriptLogger.startParseLogHandler(); try { lastError = null; if (classInfo != null) { Parser<?> parser = classInfo.getParser(); assert parser != null; // checked in init() - Object o = parser.parse(t, ParseContext.COMMAND); - if (o != null) { - Object[] one = (Object[]) Array.newInstance(classInfo.getC(), 1); - one[0] = o; - return one; + + // Parse and return value + Object value = parser.parse(text, ParseContext.COMMAND); + if (value != null) { + Object[] valueArray = (Object[]) Array.newInstance(classInfo.getC(), 1); + valueArray[0] = value; + return valueArray; } } else { - assert pattern != null && plurals != null; - ParseResult r = SkriptParser.parse(t, pattern); - if (r != null) { - assert plurals.length == r.exprs.length; - int resultCount = 0; - for (int i = 0; i < r.exprs.length; i++) { - if (r.exprs[i] != null) // Ignore missing optional parts - resultCount++; + assert pattern != null && patternExpressionPlurals != null; + + MatchResult matchResult = pattern.match(text, SkriptParser.PARSE_LITERALS, ParseContext.COMMAND); + + if (matchResult != null) { + Expression<?>[] exprs = matchResult.getExpressions(); + + assert patternExpressionPlurals.length == exprs.length; + int nonNullExprCount = 0; + for (Expression<?> expr : exprs) { + if (expr != null) // Ignore missing optional parts + nonNullExprCount++; } - Object[] os = new Object[resultCount]; - for (int i = 0, slot = 0; i < r.exprs.length; i++) { - if (r.exprs[i] != null) - os[slot++] = plurals[i] ? r.exprs[i].getArray(null) : r.exprs[i].getSingle(null); + Object[] values = new Object[nonNullExprCount]; + + int valueIndex = 0; + for (int i = 0; i < exprs.length; i++) { + if (exprs[i] != null) { + //noinspection DataFlowIssue + values[valueIndex] = patternExpressionPlurals[i] ? exprs[i].getArray(null) : exprs[i].getSingle(null); + + valueIndex++; + } } - return os; + return values; } } - LogEntry err = h.getError(); - if (err != null) { - lastError = err.toString(); + + LogEntry error = parseLogHandler.getError(); + if (error != null) { + lastError = error.toString(); } else { if (classInfo != null) { - lastError = t + " could not be parsed as " + classInfo.getName().withIndefiniteArticle(); + lastError = text + " could not be parsed as " + classInfo.getName().withIndefiniteArticle(); } else { - lastError = t + " could not be parsed as \"" + pattern + "\""; + lastError = text + " could not be parsed as \"" + pattern + "\""; } } return null; } finally { - h.clear(); - h.printLog(); + parseLogHandler.clear(); + parseLogHandler.printLog(); } } @Override public boolean isSingle() { - return pattern == null; + return pattern == null || patternHasSingleExpression; } @Override @@ -212,4 +234,31 @@ public String toString(@Nullable Event e, boolean debug) { return text.toString(e, debug) + " parsed as " + (classInfo != null ? classInfo.toString(Language.F_INDEFINITE_ARTICLE) : pattern); } + /** + * Escapes parse tags & parse mark characters in the given pattern. + */ + private static String escapeParseTags(String pattern) { + StringBuilder b = new StringBuilder(pattern.length()); + + for (int i = 0; i < pattern.length(); i++) { + char c = pattern.charAt(i); + + if (c == '\\') { + // Escape character, add it and directly add the next character + b.append(c); + b.append(pattern.charAt(i + 1)); + i++; + } else if (c == '¦' || c == ':') { + // Escape parse tags & marks + b.append("\\"); + b.append(c); + } else { + // Add regular characters + b.append(c); + } + } + + return b.toString(); + } + } diff --git a/src/main/java/ch/njol/skript/patterns/MatchResult.java b/src/main/java/ch/njol/skript/patterns/MatchResult.java index 054dccfa6cc..e0350e2b921 100644 --- a/src/main/java/ch/njol/skript/patterns/MatchResult.java +++ b/src/main/java/ch/njol/skript/patterns/MatchResult.java @@ -64,6 +64,26 @@ public ParseResult toParseResult() { return parseResult; } + public Expression<?>[] getExpressions() { + return expressions; + } + + public String getExpr() { + return expr; + } + + public int getMark() { + return mark; + } + + public List<String> getTags() { + return tags; + } + + public List<java.util.regex.MatchResult> getRegexResults() { + return regexResults; + } + @Override public String toString() { return "MatchResult{" + diff --git a/src/main/java/ch/njol/skript/patterns/PatternCompiler.java b/src/main/java/ch/njol/skript/patterns/PatternCompiler.java index 542046227aa..36323cf1dc8 100644 --- a/src/main/java/ch/njol/skript/patterns/PatternCompiler.java +++ b/src/main/java/ch/njol/skript/patterns/PatternCompiler.java @@ -42,8 +42,10 @@ private static PatternElement getEmpty() { /** * Parses a pattern String into a {@link SkriptPattern}. + * + * @throws MalformedPatternException when the given pattern is malformed. */ - public static SkriptPattern compile(String pattern) { + public static SkriptPattern compile(String pattern) throws MalformedPatternException { AtomicInteger atomicInteger = new AtomicInteger(0); try { PatternElement first = compile(pattern, atomicInteger); diff --git a/src/main/java/ch/njol/skript/patterns/SkriptPattern.java b/src/main/java/ch/njol/skript/patterns/SkriptPattern.java index 5a3d65a8083..f43c521b59b 100644 --- a/src/main/java/ch/njol/skript/patterns/SkriptPattern.java +++ b/src/main/java/ch/njol/skript/patterns/SkriptPattern.java @@ -86,4 +86,65 @@ public static String[] getKeywords(PatternElement first) { return keywords.toArray(new String[0]); } + /** + * @return the size of the {@link MatchResult#expressions} array + * from a match. + */ + public int countTypes() { + return expressionAmount; + } + + /** + * Count the maximum amount of non-null types in this pattern, + * i.e. the maximum amount of non-null values in the {@link MatchResult#expressions} + * array from a match. + * + * @see #countTypes() for the amount of nullable values + * in the expressions array from a match. + */ + public int countNonNullTypes() { + return countNonNullTypes(first); + } + + /** + * Count the maximum amount of non-null types in the given pattern, + * i.e. the maximum amount of non-null values in the {@link MatchResult#expressions} + * array from a match. + */ + private static int countNonNullTypes(PatternElement patternElement) { + int count = 0; + + // Iterate over all consequent pattern elements + while (patternElement != null) { + if (patternElement instanceof ChoicePatternElement) { + // Keep track of the max type count of each component + int max = 0; + + for (PatternElement component : ((ChoicePatternElement) patternElement).getPatternElements()) { + int componentCount = countNonNullTypes(component); + if (componentCount > max) { + max = componentCount; + } + } + + // Only one of the components will be used, the rest will be non-null + // So we only need to add the max + count += max; + } else if (patternElement instanceof GroupPatternElement) { + // For groups and optionals, simply recurse + count += countNonNullTypes(((GroupPatternElement) patternElement).getPatternElement()); + } else if (patternElement instanceof OptionalPatternElement) { + count += countNonNullTypes(((OptionalPatternElement) patternElement).getPatternElement()); + } else if (patternElement instanceof TypePatternElement) { + // Increment when seeing a type + count++; + } + + // Move on to the next pattern element + patternElement = patternElement.originalNext; + } + + return count; + } + } From b8ffe8f5a0945a60efe50a69ca5f9a08bf2ffa80 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Thu, 5 Jan 2023 18:24:07 -0700 Subject: [PATCH 185/619] Add isValid for entities (#5313) --- .../njol/skript/conditions/CondIsValid.java | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/main/java/ch/njol/skript/conditions/CondIsValid.java diff --git a/src/main/java/ch/njol/skript/conditions/CondIsValid.java b/src/main/java/ch/njol/skript/conditions/CondIsValid.java new file mode 100644 index 00000000000..b2735c42724 --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondIsValid.java @@ -0,0 +1,49 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.conditions; + +import org.bukkit.entity.Entity; + +import ch.njol.skript.conditions.base.PropertyCondition; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; + +@Name("Is Valid") +@Description("Checks whether an entity has died or been despawned for some other reason.") +@Examples("if event-entity is valid") +@Since("INSERT VERSION") +public class CondIsValid extends PropertyCondition<Entity> { + + static { + register(CondIsValid.class, "valid", "entities"); + } + + @Override + public boolean check(Entity entity) { + return entity.isValid(); + } + + @Override + protected String getPropertyName() { + return "valid"; + } + +} From e486a52dee142cb6683ce571187317e12aed258c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Jan 2023 17:06:33 -0700 Subject: [PATCH 186/619] Bump paperlib from 1.0.7 to 1.0.8 (#5314) Bumps [paperlib](https://github.com/PaperMC/PaperLib) from 1.0.7 to 1.0.8. - [Release notes](https://github.com/PaperMC/PaperLib/releases) - [Changelog](https://github.com/PaperMC/PaperLib/blob/master/CHANGELOG.md) - [Commits](https://github.com/PaperMC/PaperLib/compare/v1.0.7...v1.0.8) --- updated-dependencies: - dependency-name: io.papermc:paperlib dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5c7e45d22bc..c1bbdbd2945 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ allprojects { } dependencies { - shadow group: 'io.papermc', name: 'paperlib', version: '1.0.7' + shadow group: 'io.papermc', name: 'paperlib', version: '1.0.8' shadow group: 'org.bstats', name: 'bstats-bukkit', version: '3.0.0' implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.19.3-R0.1-SNAPSHOT' implementation group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.700' From c56cc71fd36478c6d18654cf779663781db335b8 Mon Sep 17 00:00:00 2001 From: Crafter_Y <71288761+Crafter-Y@users.noreply.github.com> Date: Sat, 7 Jan 2023 01:11:48 +0100 Subject: [PATCH 187/619] Fix inconsistency in README.md (#5316) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1e8540dccd6..dcbd171e4b0 100644 --- a/README.md +++ b/README.md @@ -162,7 +162,7 @@ Or, if you use Maven: <groupId>com.github.SkriptLang</groupId> <artifactId>Skript</artifactId> <version>[versionTag]</version> - <scope>provided</scope> + <scope>provided</scope> </dependency> ``` From 2d4fa9af31e5fd1ec15650680532a995636584c4 Mon Sep 17 00:00:00 2001 From: Shane Bee <shanebolenback@me.com> Date: Tue, 10 Jan 2023 07:47:50 -0800 Subject: [PATCH 188/619] EvtItem - add support for EntityDropItemEvent (#5335) - code cleanup as well --- .../java/ch/njol/skript/events/EvtItem.java | 75 +++++++++++-------- 1 file changed, 43 insertions(+), 32 deletions(-) diff --git a/src/main/java/ch/njol/skript/events/EvtItem.java b/src/main/java/ch/njol/skript/events/EvtItem.java index c0cfd16cae6..cc9e7336c0a 100644 --- a/src/main/java/ch/njol/skript/events/EvtItem.java +++ b/src/main/java/ch/njol/skript/events/EvtItem.java @@ -21,6 +21,7 @@ import ch.njol.skript.sections.EffSecSpawn; import org.bukkit.event.Event; import org.bukkit.event.block.BlockDispenseEvent; +import org.bukkit.event.entity.EntityDropItemEvent; import org.bukkit.event.entity.EntityPickupItemEvent; import org.bukkit.event.entity.ItemDespawnEvent; import org.bukkit.event.entity.ItemMergeEvent; @@ -61,10 +62,17 @@ public class EvtItem extends SkriptEvent { .examples("on item spawn of iron sword:", "\tbroadcast \"Someone dropped an iron sword!\"") .since("<i>unknown</i> (before 2.1)"); - Skript.registerEvent("Drop", EvtItem.class, PlayerDropItemEvent.class, "[player] drop[ing] [[of] %-itemtypes%]") - .description("Called when a player drops an item from their inventory.") - .examples("on drop:") - .since("<i>unknown</i> (before 2.1)"); + Skript.registerEvent("Drop", EvtItem.class, CollectionUtils.array(PlayerDropItemEvent.class, EntityDropItemEvent.class), + "[player|1:entity] drop[ping] [[of] %-itemtypes%]") + .description("Called when a player drops an item from their inventory, or an entity drops an item, such as a chicken laying an egg.") + .examples("on drop:", + "\tif event-item is compass:", + "\t\tcancel event", + "", + "on entity drop of an egg:", + "\tif event-entity is a chicken:", + "\t\tset item of event-dropped item to a diamond") + .since("<i>unknown</i> (before 2.1), INSERT VERSION (entity)"); if (hasPrepareCraftEvent) { // Must be loaded before CraftItemEvent Skript.registerEvent("Prepare Craft", EvtItem.class, PrepareItemCraftEvent.class, "[player] (preparing|beginning) craft[ing] [[of] %-itemtypes%]") .description("Called just before displaying crafting result to player. Note that setting the result item might or might not work due to Bukkit bugs.") @@ -135,44 +143,47 @@ public boolean init(final Literal<?>[] args, final int matchedPattern, final Par @SuppressWarnings("null") @Override - public boolean check(final Event e) { - if (e instanceof ItemSpawnEvent) // To make 'last dropped item' possible. - EffSecSpawn.lastSpawned = ((ItemSpawnEvent) e).getEntity(); - if (hasEntityPickupItemEvent && ((!entity && e instanceof EntityPickupItemEvent) || (entity && e instanceof PlayerPickupItemEvent))) + public boolean check(final Event event) { + if (event instanceof ItemSpawnEvent) // To make 'last dropped item' possible. + EffSecSpawn.lastSpawned = ((ItemSpawnEvent) event).getEntity(); + if (hasEntityPickupItemEvent && ((!entity && event instanceof EntityPickupItemEvent) || (entity && event instanceof PlayerPickupItemEvent))) + return false; + if ((!entity && event instanceof EntityDropItemEvent) || (entity && event instanceof PlayerDropItemEvent)) return false; if (types == null) return true; final ItemStack is; - if (e instanceof BlockDispenseEvent) { - is = ((BlockDispenseEvent) e).getItem(); - } else if (e instanceof ItemSpawnEvent) { - is = ((ItemSpawnEvent) e).getEntity().getItemStack(); - } else if (e instanceof PlayerDropItemEvent) { - is = ((PlayerDropItemEvent) e).getItemDrop().getItemStack(); - } else if (e instanceof CraftItemEvent) { - is = ((CraftItemEvent) e).getRecipe().getResult(); - } else if (hasPrepareCraftEvent && e instanceof PrepareItemCraftEvent) { - PrepareItemCraftEvent event = (PrepareItemCraftEvent) e; - Recipe recipe = event.getRecipe(); + if (event instanceof BlockDispenseEvent) { + is = ((BlockDispenseEvent) event).getItem(); + } else if (event instanceof ItemSpawnEvent) { + is = ((ItemSpawnEvent) event).getEntity().getItemStack(); + } else if (event instanceof PlayerDropItemEvent) { + is = ((PlayerDropItemEvent) event).getItemDrop().getItemStack(); + } else if (event instanceof EntityDropItemEvent) { + is = ((EntityDropItemEvent) event).getItemDrop().getItemStack(); + } else if (event instanceof CraftItemEvent) { + is = ((CraftItemEvent) event).getRecipe().getResult(); + } else if (hasPrepareCraftEvent && event instanceof PrepareItemCraftEvent) { + Recipe recipe = ((PrepareItemCraftEvent) event).getRecipe(); if (recipe != null) { is = recipe.getResult(); } else { return false; } - } else if (e instanceof EntityPickupItemEvent) { - is = ((EntityPickupItemEvent) e).getItem().getItemStack(); - } else if (e instanceof PlayerPickupItemEvent) { - is = ((PlayerPickupItemEvent) e).getItem().getItemStack(); - } else if (hasConsumeEvent && e instanceof PlayerItemConsumeEvent) { - is = ((PlayerItemConsumeEvent) e).getItem(); + } else if (event instanceof EntityPickupItemEvent) { + is = ((EntityPickupItemEvent) event).getItem().getItemStack(); + } else if (event instanceof PlayerPickupItemEvent) { + is = ((PlayerPickupItemEvent) event).getItem().getItemStack(); + } else if (hasConsumeEvent && event instanceof PlayerItemConsumeEvent) { + is = ((PlayerItemConsumeEvent) event).getItem(); // } else if (e instanceof BrewEvent) // is = ((BrewEvent) e).getContents().getContents() - } else if (e instanceof InventoryClickEvent) { - is = ((InventoryClickEvent) e).getCurrentItem(); - } else if (e instanceof ItemDespawnEvent) { - is = ((ItemDespawnEvent) e).getEntity().getItemStack(); - } else if (e instanceof ItemMergeEvent) { - is = ((ItemMergeEvent) e).getTarget().getItemStack(); + } else if (event instanceof InventoryClickEvent) { + is = ((InventoryClickEvent) event).getCurrentItem(); + } else if (event instanceof ItemDespawnEvent) { + is = ((ItemDespawnEvent) event).getEntity().getItemStack(); + } else if (event instanceof ItemMergeEvent) { + is = ((ItemMergeEvent) event).getTarget().getItemStack(); } else { assert false; return false; @@ -181,7 +192,7 @@ public boolean check(final Event e) { if (is == null) return false; - return types.check(e, new Checker<ItemType>() { + return types.check(event, new Checker<ItemType>() { @Override public boolean check(final ItemType t) { return t.isOfType(is); From 8952a00c885743de45395f8ecdd9dbd724a96d5c Mon Sep 17 00:00:00 2001 From: Shane Bee <shanebolenback@me.com> Date: Wed, 11 Jan 2023 14:05:22 -0800 Subject: [PATCH 189/619] SimpleEntityData - add Enemy/Camel entities (#5310) - add a couple other missing entities --- .../ch/njol/skript/entity/SimpleEntityData.java | 11 +++++++++++ src/main/resources/lang/default.lang | 13 +++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/main/java/ch/njol/skript/entity/SimpleEntityData.java b/src/main/java/ch/njol/skript/entity/SimpleEntityData.java index f0b44810992..eef1aa16029 100644 --- a/src/main/java/ch/njol/skript/entity/SimpleEntityData.java +++ b/src/main/java/ch/njol/skript/entity/SimpleEntityData.java @@ -31,6 +31,7 @@ import org.bukkit.entity.Arrow; import org.bukkit.entity.Bat; import org.bukkit.entity.Blaze; +import org.bukkit.entity.Camel; import org.bukkit.entity.CaveSpider; import org.bukkit.entity.ChestedHorse; import org.bukkit.entity.Chicken; @@ -47,7 +48,9 @@ import org.bukkit.entity.EnderCrystal; import org.bukkit.entity.EnderDragon; import org.bukkit.entity.EnderPearl; +import org.bukkit.entity.EnderSignal; import org.bukkit.entity.Endermite; +import org.bukkit.entity.Enemy; import org.bukkit.entity.Entity; import org.bukkit.entity.Evoker; import org.bukkit.entity.EvokerFangs; @@ -71,6 +74,7 @@ import org.bukkit.entity.ItemFrame; import org.bukkit.entity.LargeFireball; import org.bukkit.entity.LeashHitch; +import org.bukkit.entity.LightningStrike; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Llama; import org.bukkit.entity.LlamaSpit; @@ -200,6 +204,7 @@ private static void addSuperEntity(String codeName, Class<? extends Entity> enti addSimpleEntity("ender crystal", EnderCrystal.class); addSimpleEntity("ender dragon", EnderDragon.class); addSimpleEntity("ender pearl", EnderPearl.class); + addSimpleEntity("ender eye", EnderSignal.class); addSimpleEntity("small fireball", SmallFireball.class); addSimpleEntity("large fireball", LargeFireball.class); addSimpleEntity("fireball", Fireball.class); @@ -207,6 +212,7 @@ private static void addSuperEntity(String codeName, Class<? extends Entity> enti addSimpleEntity("ghast", Ghast.class); addSimpleEntity("giant", Giant.class); addSimpleEntity("iron golem", IronGolem.class); + addSimpleEntity("lightning bolt", LightningStrike.class); addSimpleEntity("magma cube", MagmaCube.class); addSimpleEntity("slime", Slime.class); addSimpleEntity("painting", Painting.class); @@ -287,6 +293,9 @@ private static void addSuperEntity(String codeName, Class<? extends Entity> enti addSimpleEntity("tadpole", Tadpole.class); addSimpleEntity("warden", Warden.class); } + + if (Skript.isRunningMinecraft(1,19,3)) + addSimpleEntity("camel", Camel.class); // Register zombie after Husk and Drowned to make sure both work addSimpleEntity("zombie", Zombie.class); @@ -314,6 +323,8 @@ private static void addSuperEntity(String codeName, Class<? extends Entity> enti addSuperEntity("spellcaster", Spellcaster.class); if (Skript.classExists("org.bukkit.entity.Raider")) // Introduced in Spigot 1.14 addSuperEntity("raider", Raider.class); + if (Skript.classExists("org.bukkit.entity.Enemy")) // Introduced in Spigot 1.19.3 + addSuperEntity("enemy", Enemy.class); } static { diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index a6be84db554..915fe646839 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -526,6 +526,9 @@ entities: ender pearl: name: ender pearl¦s @an pattern: ender[ ]pearl(|1¦s) + ender eye: + name: ender eye¦s @an + pattern: ender eye(|1¦s)|eye(|1¦s) of ender fireball: name: fireball¦s pattern: [(ghast|big)] fire[ ]ball(|1¦s) @@ -592,6 +595,9 @@ entities: iron golem: name: iron golem¦s @an pattern: iron golem(|1¦s) + lightning bolt: + name: lightning bolt¦s + pattern: lightning bolt(|1¦s) item frame: name: item frame¦s @an pattern: item[ ]frame(|1¦s) @@ -1195,6 +1201,13 @@ entities: warden: name: warden¦s pattern: warden(|1¦s) + # 1.19.3 Entities + enemy: + name: enem¦y¦ies @an + pattern: enem(y|1¦ies) + camel: + name: camel¦s + pattern: camel(|1¦s) # -- Heal Reasons -- heal reasons: From 1435a12be4c215c7861cf75e4e362cd9c698dfcf Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Thu, 12 Jan 2023 06:53:52 -0700 Subject: [PATCH 190/619] Fix scripts example folder generation (#5320) Co-authored-by: Kenzie <admin@moderocky.com> --- src/main/java/ch/njol/skript/Skript.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 1399946e69b..ed41d575335 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -420,8 +420,9 @@ public void onEnable() { continue; File saveTo = null; if (populateExamples && e.getName().startsWith(SCRIPTSFOLDER + "/")) { - String fileName = e.getName().substring(e.getName().lastIndexOf(File.separatorChar) + 1); - if (fileName.startsWith(ScriptLoader.DISABLED_SCRIPT_PREFIX)) + String fileName = e.getName().substring(e.getName().indexOf("/") + 1); + // All example scripts must be disabled for jar security. + if (!fileName.startsWith(ScriptLoader.DISABLED_SCRIPT_PREFIX)) fileName = ScriptLoader.DISABLED_SCRIPT_PREFIX + fileName; saveTo = new File(scriptsFolder, fileName); } else if (populateLanguageFiles From ef8791738e9fa6575885cfe79bc3bf1443a435b7 Mon Sep 17 00:00:00 2001 From: Shane Bee <shanebolenback@me.com> Date: Fri, 13 Jan 2023 15:07:09 -0800 Subject: [PATCH 191/619] BukkitEventValues - current/past event-block both return the same for BlockSpreadEvent (#5343) --- .../java/ch/njol/skript/classes/data/BukkitEventValues.java | 3 --- 1 file changed, 3 deletions(-) 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 1ef3c3ebfed..e1a63fd04c2 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -73,7 +73,6 @@ import org.bukkit.event.block.BlockGrowEvent; import org.bukkit.event.block.BlockIgniteEvent; import org.bukkit.event.block.BlockPlaceEvent; -import org.bukkit.event.block.BlockSpreadEvent; import org.bukkit.event.block.SignChangeEvent; import org.bukkit.event.enchantment.EnchantItemEvent; import org.bukkit.event.enchantment.PrepareItemEnchantEvent; @@ -305,8 +304,6 @@ public Block get(final BlockFadeEvent e) { @Override @Nullable public Block get(final BlockGrowEvent e) { - if (e instanceof BlockSpreadEvent) - return e.getBlock(); return new BlockStateBlock(e.getNewState()); } }, 0); From 64fdb351f72da251757553c01e9e8b127e566f00 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Fri, 13 Jan 2023 18:28:52 -0500 Subject: [PATCH 192/619] Rewrite Comparator System (#4931) --- src/main/java/ch/njol/skript/Skript.java | 4 +- .../java/ch/njol/skript/aliases/ItemType.java | 1 - .../ch/njol/skript/classes/Comparator.java | 69 +++-- .../skript/classes/InverseComparator.java | 14 +- .../classes/data/DefaultComparators.java | 20 +- .../njol/skript/conditions/CondCompare.java | 18 +- .../njol/skript/conditions/CondContains.java | 4 +- .../njol/skript/conditions/CondIsOfType.java | 4 +- .../skript/conditions/CondItemInHand.java | 6 +- .../java/ch/njol/skript/events/EvtBlock.java | 4 +- .../java/ch/njol/skript/events/EvtClick.java | 4 +- .../njol/skript/expressions/ExprIndices.java | 2 +- .../skript/hooks/economy/classes/Money.java | 5 +- .../java/ch/njol/skript/lang/Variable.java | 8 +- .../skript/registrations/Comparators.java | 224 +++++---------- .../skript/lang/comparator/Comparator.java | 53 ++++ .../lang/comparator/ComparatorInfo.java | 65 +++++ .../skript/lang/comparator/Comparators.java | 264 ++++++++++++++++++ .../lang/comparator/ConvertedComparator.java | 81 ++++++ .../lang/comparator/InverseComparator.java | 56 ++++ .../skript/lang/comparator/Relation.java | 181 ++++++++++++ .../skript/lang/comparator/package-info.java | 23 ++ 22 files changed, 872 insertions(+), 238 deletions(-) create mode 100644 src/main/java/org/skriptlang/skript/lang/comparator/Comparator.java create mode 100644 src/main/java/org/skriptlang/skript/lang/comparator/ComparatorInfo.java create mode 100644 src/main/java/org/skriptlang/skript/lang/comparator/Comparators.java create mode 100644 src/main/java/org/skriptlang/skript/lang/comparator/ConvertedComparator.java create mode 100644 src/main/java/org/skriptlang/skript/lang/comparator/InverseComparator.java create mode 100644 src/main/java/org/skriptlang/skript/lang/comparator/Relation.java create mode 100644 src/main/java/org/skriptlang/skript/lang/comparator/package-info.java diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index ed41d575335..f3e0428a2a2 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -21,7 +21,7 @@ import ch.njol.skript.aliases.Aliases; import ch.njol.skript.bukkitutil.BurgerHelper; import ch.njol.skript.classes.ClassInfo; -import ch.njol.skript.classes.Comparator; +import org.skriptlang.skript.lang.comparator.Comparator; import ch.njol.skript.classes.Converter; import ch.njol.skript.classes.data.BukkitClasses; import ch.njol.skript.classes.data.BukkitEventValues; @@ -59,7 +59,7 @@ import ch.njol.skript.log.SkriptLogger; import ch.njol.skript.log.Verbosity; import ch.njol.skript.registrations.Classes; -import ch.njol.skript.registrations.Comparators; +import org.skriptlang.skript.lang.comparator.Comparators; import ch.njol.skript.registrations.Converters; import ch.njol.skript.registrations.EventValues; import ch.njol.skript.tests.runner.SkriptTestEvent; diff --git a/src/main/java/ch/njol/skript/aliases/ItemType.java b/src/main/java/ch/njol/skript/aliases/ItemType.java index 6b42a628622..bc83592a596 100644 --- a/src/main/java/ch/njol/skript/aliases/ItemType.java +++ b/src/main/java/ch/njol/skript/aliases/ItemType.java @@ -34,7 +34,6 @@ import java.util.RandomAccess; import java.util.Set; -import ch.njol.skript.classes.Comparator.Relation; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.OfflinePlayer; diff --git a/src/main/java/ch/njol/skript/classes/Comparator.java b/src/main/java/ch/njol/skript/classes/Comparator.java index 48fd59649af..ae71dd3c8de 100644 --- a/src/main/java/ch/njol/skript/classes/Comparator.java +++ b/src/main/java/ch/njol/skript/classes/Comparator.java @@ -18,60 +18,54 @@ */ package ch.njol.skript.classes; -import ch.njol.skript.classes.data.DefaultComparators; -import ch.njol.skript.registrations.Comparators; - /** * Used to compare two objects of a different or the same type. - * - * @author Peter Güttinger - * @param <T1> , - * @param <T2> the types to compare - * @see Comparators#registerComparator(Class, Class, Comparator) - * @see DefaultComparators + * @deprecated Use {@link org.skriptlang.skript.lang.comparator.Comparators} */ +@Deprecated public interface Comparator<T1, T2> { - + /** * represents a relation between two objects. */ + @Deprecated public static enum Relation { EQUAL, NOT_EQUAL, GREATER, GREATER_OR_EQUAL, SMALLER, SMALLER_OR_EQUAL; - + /** * Returns EQUAL for true or NOT_EQUAL for false - * + * * @param b * @return <tt>b ? Relation.EQUAL : Relation.NOT_EQUAL</tt> */ public static Relation get(final boolean b) { return b ? Relation.EQUAL : Relation.NOT_EQUAL; } - + /** * Gets a Relation from a difference: If i is 0, EQUAL is returned, if i is greater than 0, GREATER is returned, otherwise SMALLER. - * + * * @param i * @return <tt>i == 0 ? Relation.EQUAL : i > 0 ? Relation.GREATER : Relation.SMALLER</tt> */ public static Relation get(final int i) { return i == 0 ? Relation.EQUAL : i > 0 ? Relation.GREATER : Relation.SMALLER; } - + /** * Gets a Relation from a difference: If d is 0, EQUAL is returned, if d is greater than 0, GREATER is returned, otherwise SMALLER. - * + * * @param d * @return <tt>d == 0 ? Relation.EQUAL : d > 0 ? Relation.GREATER : Relation.SMALLER</tt> */ public static Relation get(final double d) { return d == 0 ? Relation.EQUAL : d > 0 ? Relation.GREATER : Relation.SMALLER; } - + /** * Test whether this relation is fulfilled if another is, i.e. if the parameter relation fulfils <code>X rel Y</code>, then this relation fulfils <code>X rel Y</code> as * well. - * + * * @param other * @return Whether this relation is part of the given relation, e.g. <code>GREATER_OR_EQUAL.is(EQUAL)</code> returns true. */ @@ -95,7 +89,7 @@ public boolean is(final Relation other) { assert false; return false; } - + /** * Returns this relation's string representation, which is similar to "equal to" or "greater than". */ @@ -118,10 +112,10 @@ public String toString() { assert false; return ""; } - + /** * Gets the inverse of this relation, i.e if this relation fulfils <code>X rel Y</code>, then the returned relation fulfils <code>!(X rel Y)</code>. - * + * * @return !this */ public Relation getInverse() { @@ -142,10 +136,10 @@ public Relation getInverse() { assert false; return NOT_EQUAL; } - + /** * Gets the relation which has switched arguments, i.e. if this relation fulfils <code>X rel Y</code>, then the returned relation fulfils <code>Y rel X</code>. - * + * * @return siht */ public Relation getSwitched() { @@ -166,11 +160,11 @@ public Relation getSwitched() { assert false; return NOT_EQUAL; } - + public boolean isEqualOrInverse() { return this == Relation.EQUAL || this == Relation.NOT_EQUAL; } - + public int getRelation() { switch (this) { case EQUAL: @@ -187,55 +181,56 @@ public int getRelation() { return 0; } } - + /** * holds information about a comparator. - * + * * @param <T1> see {@link Comparator} * @param <T2> dito */ + @Deprecated public static class ComparatorInfo<T1, T2> { - + public Class<T1> c1; public Class<T2> c2; public Comparator<T1, T2> c; - + public ComparatorInfo(final Class<T1> c1, final Class<T2> c2, final Comparator<T1, T2> c) { this.c1 = c1; this.c2 = c2; this.c = c; } - + public Class<?> getType(final boolean first) { return first ? c1 : c2; } - + } - + Comparator<?, ?> equalsComparator = new Comparator<Object, Object>() { @Override public Relation compare(final Object o1, final Object o2) { return Relation.get(o1.equals(o2)); } - + @Override public boolean supportsOrdering() { return false; } }; - + /** * Compares the given objects which may not be null. Returning GREATER/SMALLER means that the first parameter is greater/smaller. - * + * * @param o1 Non-null object * @param o2 Non-null object * @return the relation of the objects. Should neither return GREATER_OR_EQUAL nor SMALLER_OR_EQUAL. */ public Relation compare(T1 o1, T2 o2); - + /** * @return whether this comparator supports ordering of elements or not. */ public boolean supportsOrdering(); - + } diff --git a/src/main/java/ch/njol/skript/classes/InverseComparator.java b/src/main/java/ch/njol/skript/classes/InverseComparator.java index 16ef5d324f4..dc9ccf5f003 100644 --- a/src/main/java/ch/njol/skript/classes/InverseComparator.java +++ b/src/main/java/ch/njol/skript/classes/InverseComparator.java @@ -20,28 +20,30 @@ /** * @author Peter Güttinger + * @deprecated This class is no longer exposed in newer versions. It should not be used or referenced. */ +@Deprecated public class InverseComparator<T1, T2> implements Comparator<T1, T2> { - + private final Comparator<? super T2, ? super T1> comp; - + public InverseComparator(final Comparator<? super T2, ? super T1> c) { comp = c; } - + @Override public Relation compare(final T1 o1, final T2 o2) { return comp.compare(o2, o1).getSwitched(); } - + @Override public boolean supportsOrdering() { return comp.supportsOrdering(); } - + @Override public String toString() { return "InverseComparator(" + comp + ")"; } - + } diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java b/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java index 0c95831e6a8..7f96bab14d8 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java @@ -24,12 +24,12 @@ import ch.njol.skript.aliases.ItemData; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.classes.ClassInfo; -import ch.njol.skript.classes.Comparator; +import org.skriptlang.skript.lang.comparator.Comparator; import ch.njol.skript.entity.BoatChestData; import ch.njol.skript.entity.BoatData; import ch.njol.skript.entity.EntityData; import ch.njol.skript.entity.RabbitData; -import ch.njol.skript.registrations.Comparators; +import org.skriptlang.skript.lang.comparator.Comparators; import ch.njol.skript.util.BlockUtils; import ch.njol.skript.util.Date; import ch.njol.skript.util.EnchantmentType; @@ -52,7 +52,6 @@ import org.bukkit.block.data.BlockData; import org.bukkit.command.CommandSender; import org.bukkit.enchantments.EnchantmentOffer; -import org.bukkit.entity.ChestBoat; import org.bukkit.entity.Entity; import org.bukkit.entity.FallingBlock; import org.bukkit.entity.Item; @@ -62,6 +61,7 @@ import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; +import org.skriptlang.skript.lang.comparator.Relation; import java.util.Objects; @@ -146,7 +146,7 @@ public Relation compare(Slot slot, ItemType item) { } @Override - public boolean supportsOrdering() { + public boolean supportsInversion() { return false; } }); @@ -162,11 +162,11 @@ public Relation compare(ItemType item, Slot slot) { } @Override - public boolean supportsOrdering() { + public boolean supportsInversion() { return false; } }); - + // ItemStack - ItemType Comparators.registerComparator(ItemStack.class, ItemType.class, new Comparator<ItemStack, ItemType>() { @Override @@ -175,7 +175,7 @@ public Relation compare(ItemStack is, ItemType it) { } @Override - public boolean supportsOrdering() { + public boolean supportsInversion() { return false; } }); @@ -188,7 +188,7 @@ public Relation compare(ItemType it, ItemStack is) { } @Override - public boolean supportsOrdering() { + public boolean supportsInversion() { return false; } }); @@ -201,7 +201,7 @@ public Relation compare(Block b, ItemType it) { } @Override - public boolean supportsOrdering() { + public boolean supportsInversion() { return false; } }); @@ -250,7 +250,7 @@ public Relation compare(ItemType i1, ItemType i2) { } @Override - public boolean supportsOrdering() { + public boolean supportsInversion() { return false; } }); diff --git a/src/main/java/ch/njol/skript/conditions/CondCompare.java b/src/main/java/ch/njol/skript/conditions/CondCompare.java index 3137a6fb27a..dbcb583436b 100644 --- a/src/main/java/ch/njol/skript/conditions/CondCompare.java +++ b/src/main/java/ch/njol/skript/conditions/CondCompare.java @@ -22,8 +22,8 @@ import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; -import ch.njol.skript.classes.Comparator; -import ch.njol.skript.classes.Comparator.Relation; +import org.skriptlang.skript.lang.comparator.Comparator; +import org.skriptlang.skript.lang.comparator.Relation; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; @@ -39,7 +39,7 @@ import ch.njol.skript.log.RetainingLogHandler; import ch.njol.skript.log.SkriptLogger; import ch.njol.skript.registrations.Classes; -import ch.njol.skript.registrations.Comparators; +import org.skriptlang.skript.lang.comparator.Comparators; import ch.njol.skript.util.Patterns; import ch.njol.skript.util.Utils; import ch.njol.util.Checker; @@ -137,7 +137,7 @@ public boolean init(final Expression<?>[] vars, final int matchedPattern, final final Comparator comp = this.comp; if (comp != null) { if (third == null) { - if (!relation.isEqualOrInverse() && !comp.supportsOrdering()) { + if (!relation.isImpliedBy(Relation.EQUAL, Relation.NOT_EQUAL) && !comp.supportsOrdering()) { Skript.error("Can't test " + f(first) + " for being '" + relation + "' " + f(second), ErrorQuality.NOT_AN_EXPRESSION); return false; } @@ -308,19 +308,19 @@ public boolean check(final Event e) { return first.check(e, (Checker<Object>) o1 -> second.check(e, (Checker<Object>) o2 -> { if (third == null) - return relation.is(comp != null ? comp.compare(o1, o2) : Comparators.compare(o1, o2)); + return relation.isImpliedBy(comp != null ? comp.compare(o1, o2) : Comparators.compare(o1, o2)); return third.check(e, (Checker<Object>) o3 -> { boolean isBetween; if (comp != null) { isBetween = - (Relation.GREATER_OR_EQUAL.is(comp.compare(o1, o2)) && Relation.SMALLER_OR_EQUAL.is(comp.compare(o1, o3))) + (Relation.GREATER_OR_EQUAL.isImpliedBy(comp.compare(o1, o2)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(comp.compare(o1, o3))) // Check OPPOSITE (switching o2 / o3) - || (Relation.GREATER_OR_EQUAL.is(comp.compare(o1, o3)) && Relation.SMALLER_OR_EQUAL.is(comp.compare(o1, o2))); + || (Relation.GREATER_OR_EQUAL.isImpliedBy(comp.compare(o1, o3)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(comp.compare(o1, o2))); } else { isBetween = - (Relation.GREATER_OR_EQUAL.is(Comparators.compare(o1, o2)) && Relation.SMALLER_OR_EQUAL.is(Comparators.compare(o1, o3))) + (Relation.GREATER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o2)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o3))) // Check OPPOSITE (switching o2 / o3) - || (Relation.GREATER_OR_EQUAL.is(Comparators.compare(o1, o3)) && Relation.SMALLER_OR_EQUAL.is(Comparators.compare(o1, o2))); + || (Relation.GREATER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o3)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o2))); } return relation == Relation.NOT_EQUAL ^ isBetween; }); diff --git a/src/main/java/ch/njol/skript/conditions/CondContains.java b/src/main/java/ch/njol/skript/conditions/CondContains.java index 6c82faec371..4719a9c01b2 100644 --- a/src/main/java/ch/njol/skript/conditions/CondContains.java +++ b/src/main/java/ch/njol/skript/conditions/CondContains.java @@ -21,7 +21,7 @@ import ch.njol.skript.Skript; import ch.njol.skript.SkriptConfig; import ch.njol.skript.aliases.ItemType; -import ch.njol.skript.classes.Comparator.Relation; +import org.skriptlang.skript.lang.comparator.Relation; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; @@ -30,7 +30,7 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; -import ch.njol.skript.registrations.Comparators; +import org.skriptlang.skript.lang.comparator.Comparators; import ch.njol.util.Kleenean; import ch.njol.util.StringUtils; import org.bukkit.event.Event; diff --git a/src/main/java/ch/njol/skript/conditions/CondIsOfType.java b/src/main/java/ch/njol/skript/conditions/CondIsOfType.java index 8fd0fa2a831..5ca721081bc 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsOfType.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsOfType.java @@ -23,7 +23,7 @@ import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.aliases.ItemType; -import ch.njol.skript.classes.Comparator.Relation; +import org.skriptlang.skript.lang.comparator.Relation; import ch.njol.skript.classes.data.DefaultComparators; import ch.njol.skript.conditions.base.PropertyCondition; import ch.njol.skript.conditions.base.PropertyCondition.PropertyType; @@ -77,7 +77,7 @@ public boolean check(final Event e) { } else if (o2 instanceof EntityData && o1 instanceof Entity) { return ((EntityData<?>) o2).isInstance((Entity) o1); } else if (o2 instanceof ItemType && o1 instanceof Entity) { - return Relation.EQUAL.is(DefaultComparators.entityItemComparator.compare(EntityData.fromEntity((Entity) o1), (ItemType) o2)); + return Relation.EQUAL.isImpliedBy(DefaultComparators.entityItemComparator.compare(EntityData.fromEntity((Entity) o1), (ItemType) o2)); } else { return false; } diff --git a/src/main/java/ch/njol/skript/conditions/CondItemInHand.java b/src/main/java/ch/njol/skript/conditions/CondItemInHand.java index 2177736bc18..98af683b1e1 100644 --- a/src/main/java/ch/njol/skript/conditions/CondItemInHand.java +++ b/src/main/java/ch/njol/skript/conditions/CondItemInHand.java @@ -20,7 +20,7 @@ import ch.njol.skript.Skript; import ch.njol.skript.aliases.ItemType; -import ch.njol.skript.classes.Comparator.Relation; +import org.skriptlang.skript.lang.comparator.Relation; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; @@ -28,7 +28,7 @@ import ch.njol.skript.lang.Condition; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.registrations.Comparators; +import org.skriptlang.skript.lang.comparator.Comparators; import ch.njol.util.Kleenean; import org.bukkit.entity.LivingEntity; import org.bukkit.event.Event; @@ -83,7 +83,7 @@ public boolean check(Event e) { if (equipment == null) return false; // No equipment -> no item in hand ItemType handItem = new ItemType(offTool ? equipment.getItemInOffHand() : equipment.getItemInMainHand()); - return Comparators.compare(handItem, itemType).is(Relation.EQUAL); + return Comparators.compare(handItem, itemType).isImpliedBy(Relation.EQUAL); }), isNegated()); } diff --git a/src/main/java/ch/njol/skript/events/EvtBlock.java b/src/main/java/ch/njol/skript/events/EvtBlock.java index 6c9ee44c102..98b94d7ee0f 100644 --- a/src/main/java/ch/njol/skript/events/EvtBlock.java +++ b/src/main/java/ch/njol/skript/events/EvtBlock.java @@ -37,7 +37,7 @@ import ch.njol.skript.Skript; import ch.njol.skript.aliases.ItemType; -import ch.njol.skript.classes.Comparator.Relation; +import org.skriptlang.skript.lang.comparator.Relation; import ch.njol.skript.classes.data.DefaultComparators; import ch.njol.skript.entity.EntityData; import ch.njol.skript.lang.Literal; @@ -123,7 +123,7 @@ public boolean check(final Event e) { final EntityData<?> d = EntityData.fromEntity(((HangingEvent) e).getEntity()); return types.check(e, o -> { if (o instanceof ItemType) - return Relation.EQUAL.is(DefaultComparators.entityItemComparator.compare(d, ((ItemType) o))); + return Relation.EQUAL.isImpliedBy(DefaultComparators.entityItemComparator.compare(d, ((ItemType) o))); return false; }); } else { diff --git a/src/main/java/ch/njol/skript/events/EvtClick.java b/src/main/java/ch/njol/skript/events/EvtClick.java index c41de507c30..eec7125113c 100644 --- a/src/main/java/ch/njol/skript/events/EvtClick.java +++ b/src/main/java/ch/njol/skript/events/EvtClick.java @@ -35,7 +35,7 @@ import ch.njol.skript.Skript; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.bukkitutil.ClickEventTracker; -import ch.njol.skript.classes.Comparator.Relation; +import org.skriptlang.skript.lang.comparator.Relation; import ch.njol.skript.classes.data.DefaultComparators; import ch.njol.skript.entity.EntityData; import ch.njol.skript.lang.Literal; @@ -199,7 +199,7 @@ public boolean check(final ItemType t) { @Override public boolean check(final Object o) { if (entity != null) { - return o instanceof EntityData ? ((EntityData<?>) o).isInstance(entity) : Relation.EQUAL.is(DefaultComparators.entityItemComparator.compare(EntityData.fromEntity(entity), (ItemType) o)); + return o instanceof EntityData ? ((EntityData<?>) o).isInstance(entity) : Relation.EQUAL.isImpliedBy(DefaultComparators.entityItemComparator.compare(EntityData.fromEntity(entity), (ItemType) o)); } else { return o instanceof EntityData ? false : ((ItemType) o).isOfType(block); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprIndices.java b/src/main/java/ch/njol/skript/expressions/ExprIndices.java index ceae144ab35..1f4439273b6 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprIndices.java +++ b/src/main/java/ch/njol/skript/expressions/ExprIndices.java @@ -28,7 +28,7 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.Variable; import ch.njol.skript.lang.util.SimpleExpression; -import ch.njol.skript.registrations.Comparators; +import org.skriptlang.skript.lang.comparator.Comparators; import ch.njol.skript.util.LiteralUtils; import ch.njol.util.Kleenean; import org.bukkit.event.Event; diff --git a/src/main/java/ch/njol/skript/hooks/economy/classes/Money.java b/src/main/java/ch/njol/skript/hooks/economy/classes/Money.java index 9e0ec108cb7..7994c3c0113 100644 --- a/src/main/java/ch/njol/skript/hooks/economy/classes/Money.java +++ b/src/main/java/ch/njol/skript/hooks/economy/classes/Money.java @@ -24,15 +24,16 @@ import ch.njol.skript.Skript; import ch.njol.skript.classes.Arithmetic; import ch.njol.skript.classes.ClassInfo; -import ch.njol.skript.classes.Comparator; +import org.skriptlang.skript.lang.comparator.Comparator; import ch.njol.skript.classes.Converter; import ch.njol.skript.classes.Parser; import ch.njol.skript.hooks.VaultHook; import ch.njol.skript.lang.ParseContext; import ch.njol.skript.registrations.Classes; -import ch.njol.skript.registrations.Comparators; +import org.skriptlang.skript.lang.comparator.Comparators; import ch.njol.skript.registrations.Converters; import ch.njol.util.StringUtils; +import org.skriptlang.skript.lang.comparator.Relation; /** * @author Peter Güttinger diff --git a/src/main/java/ch/njol/skript/lang/Variable.java b/src/main/java/ch/njol/skript/lang/Variable.java index bf4fbbae19c..ad319e8bacc 100644 --- a/src/main/java/ch/njol/skript/lang/Variable.java +++ b/src/main/java/ch/njol/skript/lang/Variable.java @@ -26,14 +26,14 @@ import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.classes.Changer.ChangerUtils; import ch.njol.skript.classes.ClassInfo; -import ch.njol.skript.classes.Comparator.Relation; +import org.skriptlang.skript.lang.comparator.Relation; import org.skriptlang.skript.lang.script.Script; import org.skriptlang.skript.lang.script.ScriptWarning; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.skript.registrations.Classes; -import ch.njol.skript.registrations.Comparators; +import org.skriptlang.skript.lang.comparator.Comparators; import ch.njol.skript.registrations.Converters; import ch.njol.skript.util.StringMode; import ch.njol.skript.util.Utils; @@ -549,7 +549,7 @@ public void change(Event e, @Nullable Object[] delta, ChangeMode mode) throws Un ArrayList<String> rem = new ArrayList<>(); // prevents CMEs for (Object d : delta) { for (Entry<String, Object> i : o.entrySet()) { - if (Relation.EQUAL.is(Comparators.compare(i.getValue(), d))) { + if (Relation.EQUAL.isImpliedBy(Comparators.compare(i.getValue(), d))) { String key = i.getKey(); if (key == null) continue; // This is NOT a part of list variable @@ -570,7 +570,7 @@ public void change(Event e, @Nullable Object[] delta, ChangeMode mode) throws Un ArrayList<String> rem = new ArrayList<>(); // prevents CMEs for (Entry<String, Object> i : o.entrySet()) { for (Object d : delta) { - if (Relation.EQUAL.is(Comparators.compare(i.getValue(), d))) + if (Relation.EQUAL.isImpliedBy(Comparators.compare(i.getValue(), d))) rem.add(i.getKey()); } } diff --git a/src/main/java/ch/njol/skript/registrations/Comparators.java b/src/main/java/ch/njol/skript/registrations/Comparators.java index 91f169481a8..3e6a3a1f8b5 100644 --- a/src/main/java/ch/njol/skript/registrations/Comparators.java +++ b/src/main/java/ch/njol/skript/registrations/Comparators.java @@ -18,189 +18,103 @@ */ package ch.njol.skript.registrations; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.Skript; import ch.njol.skript.classes.Comparator; -import ch.njol.skript.classes.Comparator.ComparatorInfo; import ch.njol.skript.classes.Comparator.Relation; -import ch.njol.skript.classes.Converter; -import ch.njol.skript.classes.InverseComparator; -import ch.njol.util.Pair; +import org.eclipse.jdt.annotation.Nullable; /** - * @author Peter Güttinger + * @deprecated Use {@link org.skriptlang.skript.lang.comparator.Comparators} */ +@Deprecated public class Comparators { - + private Comparators() {} - - public final static Collection<ComparatorInfo<?, ?>> comparators = new ArrayList<>(); - + /** * Registers a {@link Comparator}. - * + * * @param t1 * @param t2 * @param c * @throws IllegalArgumentException if any given class is equal to <code>Object.class</code> */ public static <T1, T2> void registerComparator(final Class<T1> t1, final Class<T2> t2, final Comparator<T1, T2> c) { - Skript.checkAcceptRegistrations(); - if (t1 == Object.class && t2 == Object.class) - throw new IllegalArgumentException("You must not add a comparator for Objects"); - comparators.add(new ComparatorInfo<>(t1, t2, c)); + org.skriptlang.skript.lang.comparator.Comparators.registerComparator(t1, t2, new org.skriptlang.skript.lang.comparator.Comparator<T1, T2>() { + @Override + public org.skriptlang.skript.lang.comparator.Relation compare(T1 o1, T2 o2) { + return getFromOld(c.compare(o1, o2)); + } + + @Override + public boolean supportsOrdering() { + return c.supportsOrdering(); + } + }); } - - @SuppressWarnings({"rawtypes", "unchecked"}) + public static Relation compare(final @Nullable Object o1, final @Nullable Object o2) { - if (o1 == null || o2 == null) - return Relation.NOT_EQUAL; - final Comparator c = getComparator(o1.getClass(), o2.getClass()); - - if (c == null) - return Relation.NOT_EQUAL; - return c.compare(o1, o2); + return getFromNew(org.skriptlang.skript.lang.comparator.Comparators.compare(o1, o2)); } - - private final static java.util.Comparator<Object> javaComparator = new java.util.Comparator<Object>() { - @Override - public int compare(final @Nullable Object o1, final @Nullable Object o2) { - return Comparators.compare(o1, o2).getRelation(); - } - }; - + public static java.util.Comparator<Object> getJavaComparator() { - return javaComparator; + return org.skriptlang.skript.lang.comparator.Comparators.JAVA_COMPARATOR; } - - private final static Map<Pair<Class<?>, Class<?>>, Comparator<?, ?>> comparatorsQuickAccess = new HashMap<>(); - - @SuppressWarnings("unchecked") + @Nullable public static <F, S> Comparator<? super F, ? super S> getComparator(final Class<F> f, final Class<S> s) { - final Pair<Class<?>, Class<?>> p = new Pair<>(f, s); - if (comparatorsQuickAccess.containsKey(p)) - return (Comparator<? super F, ? super S>) comparatorsQuickAccess.get(p); - final Comparator<?, ?> comp = getComparator_i(f, s); - comparatorsQuickAccess.put(p, comp); - return (Comparator<? super F, ? super S>) comp; - } - - @SuppressWarnings("unchecked") - @Nullable - private static <F, S> Comparator<?, ?> getComparator_i(Class<F> f, Class<S> s) { - - // perfect match - for (ComparatorInfo<?, ?> info : comparators) { - if (info.c1.isAssignableFrom(f) && info.c2.isAssignableFrom(s)) { - return info.c; + org.skriptlang.skript.lang.comparator.Comparator<F, S> newComp = + org.skriptlang.skript.lang.comparator.Comparators.getComparator(f, s); + if (newComp == null) + return null; + return new Comparator<F, S>() { + @Override + public Relation compare(F f, S s) { + return getFromNew(newComp.compare(f, s)); } - } - // try to match to create an InverseComparator - for (ComparatorInfo<?, ?> info : comparators) { - if (info.c1.isAssignableFrom(s) && info.c2.isAssignableFrom(f)) { - return new InverseComparator<F, S>((Comparator<? super S, ? super F>) info.c); + @Override + public boolean supportsOrdering() { + return newComp.supportsOrdering(); } - } - - // same class but no comparator - if (s == f && f != Object.class && s != Object.class) { - return Comparator.equalsComparator; - } - - final boolean[] trueFalse = {true, false}; - Converter<? super F, ?> c1; - Converter<? super S, ?> c2; - - // single conversion - for (ComparatorInfo<?, ?> info : comparators) { - for (boolean first : trueFalse) { - if (info.getType(first).isAssignableFrom(f)) { - c2 = Converters.getConverter(s, info.getType(!first)); - if (c2 != null) { - return first ? new ConvertedComparator<F, S>(info.c, c2) : new InverseComparator<>(new ConvertedComparator<S, F>(c2, info.c)); - } - } - if (info.getType(first).isAssignableFrom(s)) { - c1 = Converters.getConverter(f, info.getType(!first)); - if (c1 != null) { - return !first ? new ConvertedComparator<F, S>(c1, info.c) : new InverseComparator<>(new ConvertedComparator<S, F>(info.c, c1)); - } - } - } - } - - // double conversion - for (ComparatorInfo<?, ?> info : comparators) { - for (boolean first : trueFalse) { - c1 = Converters.getConverter(f, info.getType(first)); - c2 = Converters.getConverter(s, info.getType(!first)); - if (c1 != null && c2 != null) { - return first ? new ConvertedComparator<F, S>(c1, info.c, c2) : new InverseComparator<>(new ConvertedComparator<S, F>(c2, info.c, c1)); - } - } - } - - return null; + }; } - - private final static class ConvertedComparator<T1, T2> implements Comparator<T1, T2> { - - @SuppressWarnings("rawtypes") - private final Comparator c; - @SuppressWarnings("rawtypes") - @Nullable - private final Converter c1, c2; - - public ConvertedComparator(final Converter<? super T1, ?> c1, final Comparator<?, ?> c) { - this.c1 = c1; - this.c = c; - this.c2 = null; - } - - public ConvertedComparator(final Comparator<?, ?> c, final Converter<? super T2, ?> c2) { - this.c1 = null; - this.c = c; - this.c2 = c2; - } - - public ConvertedComparator(final Converter<? super T1, ?> c1, final Comparator<?, ?> c, final Converter<? super T2, ?> c2) { - this.c1 = c1; - this.c = c; - this.c2 = c2; - } - - @SuppressWarnings({"rawtypes", "unchecked"}) - @Override - public Relation compare(final T1 o1, final T2 o2) { - final Converter c1 = this.c1; - final Object t1 = c1 == null ? o1 : c1.convert(o1); - if (t1 == null) - return Relation.NOT_EQUAL; - final Converter c2 = this.c2; - final Object t2 = c2 == null ? o2 : c2.convert(o2); - if (t2 == null) + + private static Relation getFromNew(org.skriptlang.skript.lang.comparator.Relation newRelation) { + switch (newRelation) { + case EQUAL: + return Relation.EQUAL; + case NOT_EQUAL: return Relation.NOT_EQUAL; - return c.compare(t1, t2); - } - - @Override - public boolean supportsOrdering() { - return c.supportsOrdering(); + case SMALLER: + return Relation.SMALLER; + case SMALLER_OR_EQUAL: + return Relation.SMALLER_OR_EQUAL; + case GREATER: + return Relation.GREATER; + case GREATER_OR_EQUAL: + return Relation.GREATER_OR_EQUAL; + default: + throw new IllegalArgumentException("Unexpected value: " + newRelation); } - - @Override - public String toString() { - return "ConvertedComparator(" + c1 + "," + c + "," + c2 + ")"; + } + + private static org.skriptlang.skript.lang.comparator.Relation getFromOld(Relation oldRelation) { + switch (oldRelation) { + case EQUAL: + return org.skriptlang.skript.lang.comparator.Relation.EQUAL; + case NOT_EQUAL: + return org.skriptlang.skript.lang.comparator.Relation.NOT_EQUAL; + case SMALLER: + return org.skriptlang.skript.lang.comparator.Relation.SMALLER; + case SMALLER_OR_EQUAL: + return org.skriptlang.skript.lang.comparator.Relation.SMALLER_OR_EQUAL; + case GREATER: + return org.skriptlang.skript.lang.comparator.Relation.GREATER; + case GREATER_OR_EQUAL: + return org.skriptlang.skript.lang.comparator.Relation.GREATER_OR_EQUAL; + default: + throw new IllegalArgumentException("Unexpected value: " + oldRelation); } - } - + } diff --git a/src/main/java/org/skriptlang/skript/lang/comparator/Comparator.java b/src/main/java/org/skriptlang/skript/lang/comparator/Comparator.java new file mode 100644 index 00000000000..d06f508a735 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/comparator/Comparator.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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.comparator; + +/** + * Used to compare two objects of a different or the same type. + * + * @param <T1> The first type for comparison. + * @param <T2> The second type for comparison. + * @see Comparators#registerComparator(Class, Class, Comparator) + */ +@FunctionalInterface +public interface Comparator<T1, T2> { + + /** + * The main method for this Comparator to determine the Relation between two objects. + * @param o1 The first object for comparison. + * @param o2 The second object for comparison. + * @return The Relation between the two provided objects. + */ + Relation compare(T1 o1, T2 o2); + + /** + * @return Whether this comparator supports ordering of elements or not. + */ + default boolean supportsOrdering() { + return false; + } + + /** + * @return Whether this comparator supports argument inversion through {@link InverseComparator}. + */ + default boolean supportsInversion() { + return true; + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/comparator/ComparatorInfo.java b/src/main/java/org/skriptlang/skript/lang/comparator/ComparatorInfo.java new file mode 100644 index 00000000000..3f63bab9cbc --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/comparator/ComparatorInfo.java @@ -0,0 +1,65 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.comparator; + +/** + * Holds information about a Comparator. + * + * @param <T1> The first type for comparison. + * @param <T2> The second type for comparison. + */ +public final class ComparatorInfo<T1, T2> { + + final Class<T1> firstType; + final Class<T2> secondType; + final Comparator<T1, T2> comparator; + + ComparatorInfo(Class<T1> firstType, Class<T2> secondType, Comparator<T1, T2> comparator) { + this.firstType = firstType; + this.secondType = secondType; + this.comparator = comparator; + } + + /** + * @return The first type for comparison for this Comparator. + */ + public Class<T1> getFirstType() { + return firstType; + } + + /** + * @return The second type for comparison for this Comparator. + */ + public Class<T2> getSecondType() { + return secondType; + } + + /** + * @return The Comparator this information is in reference to. + */ + public Comparator<T1, T2> getComparator() { + return comparator; + } + + @Override + public String toString() { + return "ComparatorInfo{first=" + firstType + ",second=" + secondType + ",comparator=" + comparator + "}"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/comparator/Comparators.java b/src/main/java/org/skriptlang/skript/lang/comparator/Comparators.java new file mode 100644 index 00000000000..5540cb613d4 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/comparator/Comparators.java @@ -0,0 +1,264 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.comparator; + +import ch.njol.skript.Skript; +import ch.njol.skript.SkriptAPIException; +import ch.njol.skript.classes.Converter; +import ch.njol.skript.registrations.Converters; +import ch.njol.util.Pair; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Comparators are used to provide Skript with specific instructions for comparing two objects. + * By integrating with the {@link Converter} system, comparators can be used to compare two objects. + * that seemingly have no {@link Relation}. + * @see #registerComparator(Class, Class, Comparator) + */ +public final class Comparators { + + private Comparators() {} + + /** + * A default comparator to compare two objects using {@link Object#equals(Object)}. + */ + public static final Comparator<Object, Object> EQUALS_COMPARATOR = (o1, o2) -> Relation.get(o1.equals(o2)); + + /** + * A Java {@link java.util.Comparator} for comparing two objects using {@link #compare(Object, Object)}. + */ + public static final java.util.Comparator<Object> JAVA_COMPARATOR = (o1, o2) -> compare(o1, o2).getRelation(); + + /** + * A List containing information for all registered comparators. + */ + private static final List<ComparatorInfo<?, ?>> COMPARATORS = Collections.synchronizedList(new ArrayList<>()); + + /** + * @return An unmodifiable, synchronized list containing all registered {@link ComparatorInfo}s. + * When traversing this list, please refer to {@link Collections#synchronizedList(List)} to ensure that + * the list is properly traversed due to its synchronized status. + * Please note that this does not include any special Comparators resolved by Skript during runtime. + * This method ONLY returns Comparators explicitly registered during registration. + * Thus, it is recommended to use {@link #getComparator(Class, Class)} if possible. + */ + public static List<ComparatorInfo<?, ?>> getComparatorInfos() { + return Collections.unmodifiableList(COMPARATORS); + } + + /** + * A map for quickly accessing comparators that have already been resolved. + * This is useful for skipping complex lookups that may require conversion and inversion. + */ + private static final Map<Pair<Class<?>, Class<?>>, Comparator<?, ?>> QUICK_ACCESS_COMPARATORS = Collections.synchronizedMap(new HashMap<>()); + + /** + * Registers a new Comparator with Skript's collection of Comparators. + * @param firstType The first type for comparison. + * @param secondType The second type for comparison. + * @param comparator A Comparator for comparing objects of 'firstType' and 'secondType'. + */ + public static <T1, T2> void registerComparator( + Class<T1> firstType, + Class<T2> secondType, + Comparator<T1, T2> comparator + ) { + Skript.checkAcceptRegistrations(); + + if (firstType == Object.class && secondType == Object.class) + throw new IllegalArgumentException("It is not possible to add a comparator between objects"); + + for (ComparatorInfo<?, ?> info : COMPARATORS) { + if (info.firstType == firstType && info.secondType == secondType) { + throw new SkriptAPIException( + "A Comparator comparing '" + firstType + "' and '" + secondType + " already exists!" + ); + } + } + + COMPARATORS.add(new ComparatorInfo<>(firstType, secondType, comparator)); + } + + /** + * Compares two objects to see if a Relation exists between them. + * @param first The first object for comparison. + * @param second The second object for comparison. + * @return The Relation between the two provided objects. + */ + @SuppressWarnings("unchecked") + public static <T1, T2> Relation compare(@Nullable T1 first, @Nullable T2 second) { + if (first == null || second == null) + return Relation.NOT_EQUAL; + + Comparator<T1, T2> comparator = getComparator((Class<T1>) first.getClass(), (Class<T2>) second.getClass()); + if (comparator == null) + return Relation.NOT_EQUAL; + + return comparator.compare(first, second); + } + + /** + * A method for obtaining a comparator that can compare two objects of 'firstType' and 'secondType'. + * Please note that comparators may convert objects if necessary for comparisons. + * @param firstType The first type for comparison. + * @param secondType The second type for comparison. + * @return A comparator capable of determine the {@link Relation} between two objects of 'firstType' and 'secondType'. + * Will be null if no comparator capable of comparing two objects of 'firstType' and 'secondType' was found. + */ + @Nullable + @SuppressWarnings("unchecked") + public static <T1, T2> Comparator<T1, T2> getComparator(Class<T1> firstType, Class<T2> secondType) { + if (Skript.isAcceptRegistrations()) + throw new SkriptAPIException("Comparators cannot be retrieved until Skript has finished registrations."); + + Pair<Class<?>, Class<?>> pair = new Pair<>(firstType, secondType); + Comparator<T1, T2> comparator; + + if (QUICK_ACCESS_COMPARATORS.containsKey(pair)) { + comparator = (Comparator<T1, T2>) QUICK_ACCESS_COMPARATORS.get(pair); + } else { // Compute QUICK_ACCESS for provided types + comparator = getComparator_i(firstType, secondType); + QUICK_ACCESS_COMPARATORS.put(pair, comparator); + } + + return comparator; + } + + /** + * The internal method for obtaining a comparator that can compare two objects of 'firstType' and 'secondType'. + * This method handles regular {@link Comparator}s, {@link ConvertedComparator}s, and {@link InverseComparator}s. + * @param firstType The first type for comparison. + * @param secondType The second type for comparison. + * @return A comparator capable of determine the {@link Relation} between two objects of 'firstType' and 'secondType'. + * Will be null if no comparator capable of comparing two objects of 'firstType' and 'secondType' was found. + * @param <T1> The first type for comparison. + * @param <T2> The second type for comparison. + * @param <C1> The first type for any {@link ComparatorInfo}. + * This is also used in organizing the conversion process of arguments (ex: 'T1' to 'C1' converter). + * @param <C2> The second type for any {@link ComparatorInfo}. + * This is also used in organizing the conversion process of arguments (ex: 'T2' to 'C2' converter). + */ + @Nullable + @SuppressWarnings("unchecked") + private static <T1, T2, C1, C2> Comparator<T1, T2> getComparator_i( + Class<T1> firstType, + Class<T2> secondType + ) { + + // Look for an exact match + for (ComparatorInfo<?, ?> info : COMPARATORS) { + if (info.firstType == firstType && info.secondType == secondType) { + return (Comparator<T1, T2>) info.comparator; + } + } + + // Look for a basically perfect match + for (ComparatorInfo<?, ?> info : COMPARATORS) { + if (info.firstType.isAssignableFrom(firstType) && info.secondType.isAssignableFrom(secondType)) { + return (Comparator<T1, T2>) info.comparator; + } + } + + // Try to match and create an InverseComparator + for (ComparatorInfo<?, ?> info : COMPARATORS) { + if (info.comparator.supportsInversion() && info.firstType.isAssignableFrom(secondType) && info.secondType.isAssignableFrom(firstType)) { + return new InverseComparator<>((Comparator<T2, T1>) info.comparator); + } + } + + // Attempt converting one parameter + for (ComparatorInfo<?, ?> unknownInfo : COMPARATORS) { + ComparatorInfo<C1, C2> info = (ComparatorInfo<C1, C2>) unknownInfo; + + if (info.firstType.isAssignableFrom(firstType)) { // Attempt to convert the second argument to the second comparator type + Converter<T2, C2> sc2 = (Converter<T2, C2>) Converters.getConverter(secondType, info.secondType); + if (sc2 != null) + return new ConvertedComparator<>(null, info.comparator, sc2); + } + + if (info.secondType.isAssignableFrom(secondType)) { // Attempt to convert the first argument to the first comparator type + Converter<T1, C1> fc1 = (Converter<T1, C1>) Converters.getConverter(firstType, info.firstType); + if (fc1 != null) + return new ConvertedComparator<>(fc1, info.comparator, null); + } + + } + + // Attempt converting one parameter but with reversed types + for (ComparatorInfo<?, ?> unknownInfo : COMPARATORS) { + if (!unknownInfo.comparator.supportsInversion()) // Unsupported for reversing types + continue; + + ComparatorInfo<C1, C2> info = (ComparatorInfo<C1, C2>) unknownInfo; + + if (info.secondType.isAssignableFrom(firstType)) { // Attempt to convert the second argument to the first comparator type + Converter<T2, C1> sc1 = (Converter<T2, C1>) Converters.getConverter(secondType, info.firstType); + if (sc1 != null) + return new InverseComparator<>(new ConvertedComparator<>(sc1, info.comparator, null)); + } + + if (info.firstType.isAssignableFrom(secondType)) { // Attempt to convert the first argument to the second comparator type + Converter<T1, C2> fc2 = (Converter<T1, C2>) Converters.getConverter(firstType, info.secondType); + if (fc2 != null) + new InverseComparator<>(new ConvertedComparator<>(null, info.comparator, fc2)); + } + + } + + // Attempt converting both parameters + for (ComparatorInfo<?, ?> unknownInfo : COMPARATORS) { + ComparatorInfo<C1, C2> info = (ComparatorInfo<C1, C2>) unknownInfo; + + Converter<T1, C1> c1 = (Converter<T1, C1>) Converters.getConverter(firstType, info.firstType); + Converter<T2, C2> c2 = (Converter<T2, C2>) Converters.getConverter(secondType, info.secondType); + if (c1 != null && c2 != null) + return new ConvertedComparator<>(c1, info.comparator, c2); + + } + + // Attempt converting both parameters but with reversed types + for (ComparatorInfo<?, ?> unknownInfo : COMPARATORS) { + if (!unknownInfo.comparator.supportsInversion()) // Unsupported for reversing types + continue; + + ComparatorInfo<C1, C2> info = (ComparatorInfo<C1, C2>) unknownInfo; + + Converter<T1, C2> c1 = (Converter<T1, C2>) Converters.getConverter(firstType, info.secondType); + Converter<T2, C1> c2 = (Converter<T2, C1>) Converters.getConverter(secondType, info.firstType); + if (c1 != null && c2 != null) + return new InverseComparator<>(new ConvertedComparator<>(c2, info.comparator, c1)); + + } + + // Same class but no comparator + if (firstType != Object.class && secondType == firstType) { + return (Comparator<T1, T2>) EQUALS_COMPARATOR; + } + + // Well, we tried! + return null; + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/comparator/ConvertedComparator.java b/src/main/java/org/skriptlang/skript/lang/comparator/ConvertedComparator.java new file mode 100644 index 00000000000..d38a84521ba --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/comparator/ConvertedComparator.java @@ -0,0 +1,81 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.comparator; + +import ch.njol.skript.classes.Converter; +import org.eclipse.jdt.annotation.Nullable; + +/** + * A ConvertedComparator is a comparator that converts its parameters so that they may be used + * within a different comparator that requires different parameter types. + * + * @param <T1> The first type for comparison. + * @param <T2> The second type for comparison. + * @param <C1> The type of the conversion result for T1. + * If no 'firstConverter' is provided, then this is the same as T1. + * @param <C2> The type of the conversion result for T2. + * If no 'secondConverter' is provided, then this is the same as T2. + */ +final class ConvertedComparator<T1, T2, C1, C2> implements Comparator<T1, T2> { + + private final Comparator<C1, C2> comparator; + @Nullable + private final Converter<T1, C1> firstConverter; + @Nullable + private final Converter<T2, C2> secondConverter; + + ConvertedComparator( + @Nullable Converter<T1, C1> firstConverter, + Comparator<C1, C2> c, + @Nullable Converter<T2, C2> secondConverter + ) { + if (firstConverter == null && secondConverter == null) + throw new IllegalArgumentException("firstConverter and secondConverter must not BOTH be null!"); + this.firstConverter = firstConverter; + this.comparator = c; + this.secondConverter = secondConverter; + } + + @Override + @SuppressWarnings("unchecked") + public Relation compare(T1 o1, T2 o2) { + // null converter means 'comparator' is actually Comparator<T1, C2> + C1 t1 = firstConverter == null ? (C1) o1 : firstConverter.convert(o1); + if (t1 == null) + return Relation.NOT_EQUAL; + + // null converter means 'comparator' is actually Comparator<C1, T2> + C2 t2 = secondConverter == null ? (C2) o2 : secondConverter.convert(o2); + if (t2 == null) + return Relation.NOT_EQUAL; + + return comparator.compare(t1, t2); + } + + @Override + public boolean supportsOrdering() { + return comparator.supportsOrdering(); + } + + @Override + public String toString() { + return "ConvertedComparator(" + firstConverter + "," + comparator + "," + secondConverter + ")"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/comparator/InverseComparator.java b/src/main/java/org/skriptlang/skript/lang/comparator/InverseComparator.java new file mode 100644 index 00000000000..b5e22c17d8d --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/comparator/InverseComparator.java @@ -0,0 +1,56 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.comparator; + +/** + * Similar to {@link Comparator}, but {@link Comparator#compare(Object, Object)} arguments are switched. + * If necessary, the resulting {@link Relation} is switched. + * + * @param <T1> The first type for comparison. + * @param <T2> The second type for comparison. + */ +final class InverseComparator<T1, T2> implements Comparator<T1, T2> { + + private final Comparator<T2, T1> comparator; + + InverseComparator(Comparator<T2, T1> comparator) { + this.comparator = comparator; + } + + @Override + public Relation compare(T1 o1, T2 o2) { + return comparator.compare(o2, o1).getSwitched(); + } + + @Override + public boolean supportsOrdering() { + return comparator.supportsOrdering(); + } + + @Override + public boolean supportsInversion() { + return false; + } + + @Override + public String toString() { + return "InverseComparator{" + comparator + "}"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/comparator/Relation.java b/src/main/java/org/skriptlang/skript/lang/comparator/Relation.java new file mode 100644 index 00000000000..fcba1245c6c --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/comparator/Relation.java @@ -0,0 +1,181 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.comparator; + +/** + * Represents a relation between two objects. + */ +public enum Relation { + + EQUAL("equal to"), + NOT_EQUAL("not equal to"), + GREATER("greater than"), + GREATER_OR_EQUAL("greater than or equal to"), + SMALLER("smaller than"), + SMALLER_OR_EQUAL("smaller than or equal to"); + + private final String toString; + + Relation(String toString) { + this.toString = toString; + } + + /** + * @param b The boolean to get a Relation from. + * @return {@link #EQUAL} if 'b' is true else {@link #NOT_EQUAL} + */ + public static Relation get(boolean b) { + return b ? Relation.EQUAL : Relation.NOT_EQUAL; + } + + /** + * @param i The int to get a Relation from. + * @return {@link #EQUAL} if 'i' is equal to 0, + * {@link #GREATER} if 'i' is greater than 0, + * {@link #SMALLER} if 'i' is less than 0 + */ + public static Relation get(int i) { + return i == 0 ? Relation.EQUAL : i > 0 ? Relation.GREATER : Relation.SMALLER; + } + + /** + * @param d The double to get a Relation from. + * @return {@link #EQUAL} if 'd' is equal to 0, + * {@link #GREATER} if 'd' is greater than 0, + * {@link #SMALLER} if 'd' is less than 0 + */ + public static Relation get(double d) { + return d == 0 ? Relation.EQUAL : d > 0 ? Relation.GREATER : Relation.SMALLER; + } + + /** + * Test whether this Relation is fulfilled if another is, i.e. if the parameter 'other' fulfils <code>X rel Y</code>, + * then this Relation fulfils <code>X rel Y</code> as well. + * + * @param other The Relation to compare with. + * @return Whether this Relation is part of the given Relation, e.g. <code>GREATER_OR_EQUAL.isImpliedBy(EQUAL)</code> returns true. + */ + public boolean isImpliedBy(Relation other) { + if (other == this) + return true; + switch (this) { + case EQUAL: + case GREATER: + case SMALLER: + return false; + case NOT_EQUAL: + return other == SMALLER || other == GREATER; + case GREATER_OR_EQUAL: + return other == GREATER || other == EQUAL; + case SMALLER_OR_EQUAL: + return other == SMALLER || other == EQUAL; + default: + throw new IllegalStateException("Unexpected value: " + this); + } + } + + /** + * @param others The Relations to compare with. + * @return True if {@link #isImpliedBy(Relation)} is true for any of the provided Relations. + */ + public boolean isImpliedBy(Relation... others) { + for (Relation other : others) { + if (isImpliedBy(other)) + return true; + } + return false; + } + + /** + * Returns this Relation's string representation, which is similar to "equal to" or "greater than". + */ + @Override + public String toString() { + return toString; + } + + /** + * @return The inverse of this Relation, i.e if this Relation fulfils <code>X rel Y</code>, + * then the returned Relation fulfils <code>!(X rel Y)</code>. + */ + public Relation getInverse() { + switch (this) { + case EQUAL: + return NOT_EQUAL; + case NOT_EQUAL: + return EQUAL; + case GREATER: + return SMALLER_OR_EQUAL; + case GREATER_OR_EQUAL: + return SMALLER; + case SMALLER: + return GREATER_OR_EQUAL; + case SMALLER_OR_EQUAL: + return GREATER; + default: + throw new IllegalStateException("Unexpected value: " + this); + } + } + + /** + * @return The Relation which has switched arguments, i.e. if this Relation fulfils <code>X rel Y</code>, + * then the returned Relation fulfils <code>Y rel X</code>. + */ + public Relation getSwitched() { + switch (this) { + case EQUAL: + return EQUAL; + case NOT_EQUAL: + return NOT_EQUAL; + case GREATER: + return SMALLER; + case GREATER_OR_EQUAL: + return SMALLER_OR_EQUAL; + case SMALLER: + return GREATER; + case SMALLER_OR_EQUAL: + return GREATER_OR_EQUAL; + default: + throw new IllegalStateException("Unexpected value: " + this); + } + } + + /** + * @return An int relating to the value of this Relation. + * <br>0 if {@link #EQUAL} or {@link #NOT_EQUAL} + * <br>1 if {@link #GREATER} or {@link #GREATER_OR_EQUAL} + * <br>-1 if {@link #SMALLER} or {@link #SMALLER_OR_EQUAL} + */ + public int getRelation() { + switch (this) { + case EQUAL: + case NOT_EQUAL: + return 0; + case GREATER: + case GREATER_OR_EQUAL: + return 1; + case SMALLER: + case SMALLER_OR_EQUAL: + return -1; + default: + throw new IllegalStateException("Unexpected value: " + this); + } + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/comparator/package-info.java b/src/main/java/org/skriptlang/skript/lang/comparator/package-info.java new file mode 100644 index 00000000000..85014fb6a01 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/comparator/package-info.java @@ -0,0 +1,23 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +@NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) +package org.skriptlang.skript.lang.comparator; + +import org.eclipse.jdt.annotation.DefaultLocation; +import org.eclipse.jdt.annotation.NonNullByDefault; From eca99fef6bd5f3e504638454236ad54a1a67dd26 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Sat, 14 Jan 2023 01:57:18 -0700 Subject: [PATCH 193/619] Bump to 2.7 developer version tag (#5273) Co-authored-by: APickledWalrus <apickledwalrus@gmail.com> --- gradle.properties | 2 +- src/main/java/ch/njol/skript/Skript.java | 5 +- .../java/ch/njol/skript/SkriptConfig.java | 6 +- .../ch/njol/skript/localization/Language.java | 3 +- .../java/ch/njol/skript/util/Version.java | 62 ++++++++++--------- src/main/resources/config.sk | 2 +- 6 files changed, 45 insertions(+), 35 deletions(-) diff --git a/gradle.properties b/gradle.properties index 4dc2e502499..bfe06bfd9d6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ groupid=ch.njol name=skript -version=2.6.4 +version=2.7.0-dev jarName=Skript.jar testEnv=java17/paper-1.19.3 testEnvJavaVersion=17 diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index f3e0428a2a2..b9a8b82c5af 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -380,8 +380,6 @@ public void onEnable() { version = new Version("" + getDescription().getVersion()); // Skript version - getAddonInstance(); - // Start the updater // Note: if config prohibits update checks, it will NOT do network connections try { @@ -463,6 +461,9 @@ public void onEnable() { } } } + + // initialize the Skript addon instance + getAddonInstance(); // Load classes which are always safe to use new JavaClasses(); // These may be needed in configuration diff --git a/src/main/java/ch/njol/skript/SkriptConfig.java b/src/main/java/ch/njol/skript/SkriptConfig.java index c1661ce5877..bdf7144cb36 100644 --- a/src/main/java/ch/njol/skript/SkriptConfig.java +++ b/src/main/java/ch/njol/skript/SkriptConfig.java @@ -37,6 +37,7 @@ import ch.njol.skript.update.ReleaseChannel; import ch.njol.skript.util.FileUtils; import ch.njol.skript.util.Timespan; +import ch.njol.skript.util.Version; import ch.njol.skript.util.chat.ChatMessages; import ch.njol.skript.util.chat.LinkParseMode; import ch.njol.skript.variables.Variables; @@ -363,8 +364,9 @@ static boolean load() { return false; } mainConfig = mc; - - if (!Skript.getVersion().toString().equals(mc.get(version.key))) { + + String configVersion = mc.get(version.key); + if (configVersion == null || Skript.getVersion().compareTo(new Version(configVersion)) != 0) { try { final InputStream in = Skript.getInstance().getResource("config.sk"); if (in == null) { diff --git a/src/main/java/ch/njol/skript/localization/Language.java b/src/main/java/ch/njol/skript/localization/Language.java index 3f32698071a..243e77e2dd2 100644 --- a/src/main/java/ch/njol/skript/localization/Language.java +++ b/src/main/java/ch/njol/skript/localization/Language.java @@ -297,7 +297,8 @@ private static HashMap<String, String> load(@Nullable InputStream in, String nam try { Config langConfig = new Config(in, name + ".lang", false, false, ":"); - if (tryUpdate && !Skript.getVersion().toString().equals(langConfig.get("version"))) { + String langVersion = langConfig.get("version"); + if (tryUpdate && (langVersion == null || Skript.getVersion().compareTo(new Version(langVersion)) != 0)) { String langFileName = "lang/" + name + ".lang"; InputStream newConfigIn = Skript.getInstance().getResource(langFileName); diff --git a/src/main/java/ch/njol/skript/util/Version.java b/src/main/java/ch/njol/skript/util/Version.java index 30388517af4..62a602eef39 100644 --- a/src/main/java/ch/njol/skript/util/Version.java +++ b/src/main/java/ch/njol/skript/util/Version.java @@ -31,31 +31,30 @@ public class Version implements Serializable, Comparable<Version> { private final static long serialVersionUID = 8687040355286333293L; - private final int[] version = new int[3]; + private final Integer[] version = new Integer[3]; /** * Everything after the version, e.g. "alpha", "b", "rc 1", "build 2314", "-SNAPSHOT" etc. or null if nothing. */ @Nullable private final String postfix; - - public Version(final int... version) { + + public Version(int... version) { if (version.length < 1 || version.length > 3) throw new IllegalArgumentException("Versions must have a minimum of 2 and a maximum of 3 numbers (" + version.length + " numbers given)"); for (int i = 0; i < version.length; i++) this.version[i] = version[i]; postfix = null; } - - public Version(final int major, final int minor, final @Nullable String postfix) { + + public Version(int major, int minor, @Nullable String postfix) { version[0] = major; version[1] = minor; this.postfix = postfix == null || postfix.isEmpty() ? null : postfix; } - - @SuppressWarnings("null") - public final static Pattern versionPattern = Pattern.compile("(\\d+)\\.(\\d+)(?:\\.(\\d+))?\\s*(.*)"); - - public Version(final String version) { + + public final static Pattern versionPattern = Pattern.compile("(\\d+)\\.(\\d+)(?:\\.(\\d+))?(?:-(.*))?"); + + public Version(String version) { final Matcher m = versionPattern.matcher(version.trim()); if (!m.matches()) throw new IllegalArgumentException("'" + version + "' is not a valid version string"); @@ -63,9 +62,9 @@ public Version(final String version) { if (m.group(i + 1) != null) this.version[i] = Utils.parseInt("" + m.group(i + 1)); } - postfix = m.group(m.groupCount()).isEmpty() ? null : m.group(m.groupCount()); + postfix = m.group(4); } - + @Override public boolean equals(final @Nullable Object obj) { if (this == obj) @@ -77,37 +76,45 @@ public boolean equals(final @Nullable Object obj) { @Override public int hashCode() { - final String pf = postfix; - return Arrays.hashCode(version) * 31 + (pf == null ? 0 : pf.hashCode()); + return Arrays.hashCode(version) * 31 + (postfix == null ? 0 : postfix.hashCode()); } @Override - public int compareTo(final @Nullable Version other) { + public int compareTo(@Nullable Version other) { if (other == null) return 1; + for (int i = 0; i < version.length; i++) { - if (version[i] > other.version[i]) + if (get(i) > other.get(i)) return 1; - if (version[i] < other.version[i]) + if (get(i) < other.get(i)) return -1; } - final String pf = postfix; - if (pf == null) + + if (postfix == null) return other.postfix == null ? 0 : 1; - else - return other.postfix == null ? -1 : pf.compareTo(other.postfix); + return other.postfix == null ? -1 : postfix.compareTo(other.postfix); } - - public int compareTo(final int... other) { + + /** + * @param other An array containing the major, minor, and revision (ex: 1,19,3) + * @return a negative integer, zero, or a positive integer as this object is + * less than, equal to, or greater than the specified object. + */ + public int compareTo(int... other) { assert other.length >= 2 && other.length <= 3; for (int i = 0; i < version.length; i++) { - if (version[i] > (i >= other.length ? 0 : other[i])) + if (get(i) > (i >= other.length ? 0 : other[i])) return 1; - if (version[i] < (i >= other.length ? 0 : other[i])) + if (get(i) < (i >= other.length ? 0 : other[i])) return -1; } return 0; } + + private int get(int i) { + return version[i] == null ? 0 : version[i]; + } public boolean isSmallerThan(final Version other) { return compareTo(other) < 0; @@ -133,13 +140,12 @@ public int getMinor() { } public int getRevision() { - return version.length == 2 ? 0 : version[2]; + return version[2] == null ? 0 : version[2]; } @Override public String toString() { - final String pf = postfix; - return version[0] + "." + version[1] + (version[2] == 0 ? "" : "." + version[2]) + (pf == null ? "" : pf.startsWith("-") ? pf : " " + pf); + return version[0] + "." + version[1] + (version[2] == null ? "" : "." + version[2]) + (postfix == null ? "" : "-" + postfix); } public static int compare(final String v1, final String v2) { diff --git a/src/main/resources/config.sk b/src/main/resources/config.sk index 28825f8e331..3e0ad76479c 100644 --- a/src/main/resources/config.sk +++ b/src/main/resources/config.sk @@ -325,7 +325,7 @@ databases: # ==== Settings that should not be changed ==== -version: ${project.version} +version: @version@ # DO NOT CHANGE THIS VALUE MANUALLY! # This saves for which version of Skript this configuration was written for. # If it does not match the version of the .jar file then the config will be updated automatically. From 5d34a2736530af85940fd04478d961c4504b2ecd Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <alali_ayham@yahoo.com> Date: Fri, 20 Jan 2023 03:06:34 +0300 Subject: [PATCH 194/619] =?UTF-8?q?=F0=9F=9B=A0=20Removes=201.13=20code=20?= =?UTF-8?q?from=20ItemUtils.isAir=20(#5368)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java b/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java index f4f9ae4f79a..1fd542ed4db 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java +++ b/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java @@ -99,17 +99,11 @@ public static boolean itemStacksEqual(final @Nullable ItemStack is1, final @Null // Only 1.15 and versions after have Material#isAir method private static final boolean IS_AIR_EXISTS = Skript.methodExists(Material.class, "isAir"); - // Version 1.14 have multiple air types but no Material#isAir method - private static final boolean OTHER_AIR_EXISTS = Skript.isRunningMinecraft(1, 14); public static boolean isAir(Material type) { - if (IS_AIR_EXISTS) { + if (IS_AIR_EXISTS) return type.isAir(); - } else if (OTHER_AIR_EXISTS) { - return type == Material.AIR || type == Material.CAVE_AIR || type == Material.VOID_AIR; - } - // All versions prior to 1.14 only have 1 air type - return type == Material.AIR; + return type == Material.AIR || type == Material.CAVE_AIR || type == Material.VOID_AIR; } } From 523bd1e37f193fd35c589bb67fba757e4db1e496 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <alali_ayham@yahoo.com> Date: Fri, 20 Jan 2023 22:45:11 +0300 Subject: [PATCH 195/619] =?UTF-8?q?=F0=9F=9A=80=20Docs=20Site=20Improvemen?= =?UTF-8?q?ts=20(#4319)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Make `parsed` optional in `parsed as` * First Improvements Phase + Added DarkMode (Switcher, AutoLoad from cookies) + Added support for lower versions in search filter (v:2.5-) + Added Search results counter + Added support for branched version search (v:2.2-dev36) * Removed the SH for now until I implement the new one. * Improved the overall style to be much easier to read and understand. * Shorten the tabs list by using sub-tabs + Version number will now go to the GitHub changelog link of that version when clicked * Fixes - Fixed found results colors - Fixed found results showing in some cases when should not * 🛠 Sections + Improvements + Added sections page - Improved page inital loading for chromioum based browsers (up to 70%) by using content-visibility css property - Fixed searching filters regex - Improved submenu active state * Forgot search fixes * 🚀 Another Update - Merged PR #4063 - Added more info about strings (single quotes support after v2.6) - Fixed all broken docs links in examples and descriptions - Improved the docs patterns to temove unnecessary parentheses such as [(script)] ex. ExprAllCommands - Unified the word "coluor" to be "color" for better search results and unified usage * Sections update & much more fixes - Update sections description & examples - Revert SecConditional syntax ordering changes - Fixed more broken docs links using ../ instead of ./ - Fixed hooks docs not generating because the plugins are not installed, now they will generate if the user have /docs-template/ folder in Skript's folder OR the plugins installed - Updated docs info of hooks (required-plugins section) - Fixed classes and events requiredPlugins section joining values using newline instead of ", " - Unified left-over "Colour" words in ExprColorOf * 🛠 SyntaxHighlighting + Improvements - Added custom Skript SyntaxHighlighting (Much faster, made for Skript, Built-in) - Theme Improvements - Fixed a padding bug causes recently in this PR - Fixed a bug in theme switcher button in home page - Added stable version and more placeholders to be used later on - Anchor elements Improvements * 🚀 'New' Tab and much more This is probably the last update in this PR - Added 'New' Tab that show all the registered syntaxes with a version filter to see the new additions since specific version - Added new search filters (is:new type:cond type:expr etc.) - Added syntax type on the right side - Syntax left border will now depends on the type - Added copy search link to allow people to paste links with custom search parameter - Added 'new' badge beside new added/updated syntaxes - Allow generating hooks docs if in testing mode and doc-templates folders exists (to make it easier for debugging and new releases) - Some main.js improvements and cleaning up - Search bar and search icon now have a tooltip to show some information - Updated the light bulb icon to match the feel of the website - Added 'How to generate the docs' section to README - Dimmed the white text a bit in dark mode * 🛠 Mobile CSS fixes - Fixed new CSS not working well on mobile - Improved CSS for mobile * 🛠 Little CSS fixes * 🛠 Item type text color * 🛠 Quick improvements and fixes - Improved new.html page versions loading (by caching versions) - Fixed duplicated IDs check and new.html sidebar items filtering - Fixed search link parameter decoding '+' as a space - Fixed new search filters not showing results count - White theme improvements * Improvements - Fix dark mode not selected by default - Improve hash link scroll handling - Fix new.html versions showing empty option sometimes - Merged #4348 Thanks to (@oskarkk) - Readded (font) chat component to text.html after removing it by accident - Fixed unnamed classes warning in docs.json - Fixed typo in EvtBlock - Added missing docs info for couple classes - Improved light off icon - Improved white theme background color * Add Javadocs * Revert "Add Javadocs" This reverts commit 56d2cf6e24b3e29fa75fa975f19708e16d053116. * Add javadocs navbar item * A quick checkpoint - Made examples collapsible - Added EffectSection support - Added a template for a yet-to-come docs feature (By Addon) cell which tells which addon created this element (this is still under progress so I left it commented to finish later) - Solved #4508 * 🚀 Should be ready now - Fixed few css and js bugs * Fixes - Fixed last element cutting off on mobile (when sidebar disaapears) - Improved empty space below examples not equal to above example box * Quick fix for example spaces * Improvements - Fix new.html page loading issues - Improve overall js loading using 'defer' - Added theme-color meta tag * Improve new.html again & fix jQuery loading order * Fix chrome scrolling issue that caused more issues * Imrpovements - Improved white theme once again (and hopefully last time) - Improved scrollbar styling - Improved link color on white theme - Fixed search result counter shifted 1 pixel down - Added a new header tab 'Dev Tools' for anything related to addon developement in general such as Javadocs and later on addon development tutorials maybe * Fix black theme flicker when on white theme on load * Fix white theme flickering (again) * JS Improvements * Improve auto hash scroll * Quick commit * ⚒️ Improvements + Addition - Added `return type` for expressions and functions closes #4609 - Replaced new.html with docs.html which contains all the elements and `new` tab is a special case of docs.html with a special search filter - Improved red box font color from black to background color (gray) - Renamed `getNullOrEmptyDefault` method to `getDefaultIfNullOrEmpty` to make it clear - Removed redundant assertions of `desc` and replaced them with one assertion in `handleIf` method as they should - Added `Expression#getAcceptedChangeModes` to be used later for docs * 🚀 Link return type section to their classes if found - Known issues: Some return types such Long and Float does have a classinfo but not found in docs which leads to unknown links (might find a good solution later) * 🚀 Change `force generate hooks` requirement * Last touches - Added search by description for some cases like `absorbed blocks` element has the word sponge only in its description - Changed hash links scrolling to search filter (#ElementID would only match element of that ID), some browsers like chrome had bugs with scroll for some reason (probably lazy page loading) - Little code cleanup - Fixed minor bugs with search link handling - Fixed element copy link including search params - Fixed version filter wasn't handling the equal case properly * Forgotten commits :) - Fixed not named elements not checking for NoDoc annotation - Fixed return type not checking for NO_DOC * Fix #4429 * Fix searching by id for events * Improve hash link handling * Fix regex patterns for unsupported browsers This error was causing most features from main.js not to work in some browsers such as Safari * Apply suggestions from code review Co-authored-by: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> * Fix compilation * Parts of the requested changes - Other than most "colour" changes to "color" (only in visible places) the class ExprColoured has been changed to ExprColored since in the docs you can search for #ExprColored as an ID so it must match the others * Docs: fix old info on dates - add new ways to get them * Docs: fix old info on dates - more precisely Co-authored-by: Ayham Al Ali <alali_ayham@yahoo.com> * Requested Changes * Update and rename ExprFormatTime.java to ExprFormatDate.java - Minor correction to match objective of this expression * Update main.js * Tests bump * Update src/main/java/ch/njol/skript/lang/function/Signature.java Co-authored-by: APickledWalrus <apickledwalrus@gmail.com> * Update docs/text.html Co-authored-by: APickledWalrus <apickledwalrus@gmail.com> * RC * Build fix * Discord RC - Part 1 * Improvements - Delay examples click events to speed up page loading - Remove bold style from side bar hovering - Removed scrolling to element on link copy - Filter new parse tags - Improve output of returnType - Cleaned up HTMLGenerator class a little * Improvements 2 - Prevent scrolling on link copy (last try didn't work well) - Added hash search support (if you have a hash it will be searched using search bar instead of scrolling to the element) * Improvements 3 - Make description/pattern links open in the same tab rather than scrolling (because scrolling doesn't work anymore due to hash link search feature) - Fix returnType if blocks not removed * Fix copy link not working as expected * Improvements 4 - Fix new parse tags syntax filter not working * Improve inline code BG on white theme * Stop cleaning patterns of classinfos * RC 1 - Rename ExprColored back to ExprColoured (for safety because it's used in other places and addons might have been using it) * Mark pages as English language * Make Signature#originClassPath nullable in more places * Filter Sections that are effect sections if isDocsPage * Create fields and getters for docs and template directory * Removed commented line * Update Icons, favicon and logos * Update src/main/java/ch/njol/skript/doc/Documentation.java Co-authored-by: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> * Update src/main/java/ch/njol/skript/doc/Documentation.java Co-authored-by: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> * Improvements - Add ClassInfo#hasDocs method - Add javadocs to sortedAnnotatedIterator - Improve static field naming - Remove some unneeded methods due to improvements - Changed EffectSection section doc type from "Section" to "EffectSection" * Move annotatedComparator field up * Requested Changes * Fix localStorage expiration date * Fix build * Add Cookies Accept Bottom Bar - Cleaned up functions to be in their own file to remove duplicated code in theme-switcher.js and main.js * Fix a quick error in cookies bar * Fix cookies bar off-screen * Improve styling of cookies bar * Add keywords functionality * Remove wildcard import * Go in to manually fix conflicts * Fix couple bugs ([on] & classes) - Added arrow to tutorials navbar Co-authored-by: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Co-authored-by: oskarkk <16339637+oskarkk@users.noreply.github.com> Co-authored-by: DelayedGaming <72163224+DelayedGaming@users.noreply.github.com> Co-authored-by: APickledWalrus <apickledwalrus@gmail.com> Co-authored-by: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Co-authored-by: TheLimeGlass <seantgrover@gmail.com> Co-authored-by: Moderocky <admin@moderocky.com> --- docs/README.md | 13 +- docs/assets/Logo/Skript Logo Circle 2.png | Bin 18900 -> 0 bytes docs/assets/Logo/Skript Logo Circle.png | Bin 22859 -> 0 bytes docs/assets/Logo/Skript Logo Circular.png | Bin 0 -> 19415 bytes docs/assets/Logo/Skript Logo Trans.png | Bin 13278 -> 0 bytes docs/assets/Logo/Skript Logo.jpg | Bin 0 -> 30413 bytes docs/assets/Logo/Skript Logo.png | Bin 14704 -> 10738 bytes docs/assets/icon.png | Bin 14704 -> 19415 bytes docs/assets/light-off.svg | 70 + docs/assets/light-on.svg | 114 ++ docs/assets/search.svg | 173 +++ docs/css/highlightjs.min.css | 2 - docs/css/styles.css | 806 ++++++++-- docs/docs.html | 5 + docs/index.html | 16 +- docs/js/functions.js | 353 +++++ docs/js/highlight.js | 1359 ----------------- docs/js/highlight.js.LICENSE | 11 - docs/js/main.js | 505 +++--- docs/js/theme-switcher.js | 34 + docs/sections.html | 5 + docs/template.html | 23 +- docs/templates/desc_full.html | 18 +- docs/templates/navbar.html | 40 +- docs/text.html | 9 +- src/main/java/ch/njol/skript/PatcherTool.java | 2 +- src/main/java/ch/njol/skript/Skript.java | 2 +- .../java/ch/njol/skript/SkriptCommand.java | 14 +- .../ch/njol/skript/classes/ClassInfo.java | 9 + .../skript/classes/data/BukkitClasses.java | 4 +- .../skript/classes/data/DefaultFunctions.java | 4 +- .../njol/skript/classes/data/JavaClasses.java | 2 +- .../skript/classes/data/SkriptClasses.java | 20 +- .../ch/njol/skript/doc/Documentation.java | 50 +- .../ch/njol/skript/doc/HTMLGenerator.java | 497 ++++-- .../java/ch/njol/skript/doc/Keywords.java | 36 + .../ch/njol/skript/effects/EffChange.java | 4 +- .../ch/njol/skript/effects/EffColorItems.java | 10 +- .../skript/effects/EffFireworkLaunch.java | 2 +- .../ch/njol/skript/effects/EffMessage.java | 2 +- .../ch/njol/skript/effects/EffReplace.java | 2 +- .../java/ch/njol/skript/events/EvtAtTime.java | 4 +- .../java/ch/njol/skript/events/EvtChat.java | 2 +- .../java/ch/njol/skript/events/EvtClick.java | 3 +- .../ch/njol/skript/events/EvtFirework.java | 2 +- .../ch/njol/skript/events/EvtGameMode.java | 4 +- .../skript/expressions/ExprAmountOfItems.java | 2 +- .../ch/njol/skript/expressions/ExprChunk.java | 2 +- .../njol/skript/expressions/ExprColorOf.java | 14 +- .../njol/skript/expressions/ExprColoured.java | 12 +- .../skript/expressions/ExprDamageCause.java | 2 +- .../skript/expressions/ExprDifference.java | 2 +- .../skript/expressions/ExprDurability.java | 2 +- .../expressions/ExprEnchantmentOfferCost.java | 12 +- .../expressions/ExprFireworkEffect.java | 4 +- .../njol/skript/expressions/ExprHanging.java | 2 +- .../skript/expressions/ExprHealReason.java | 2 +- .../skript/expressions/ExprHotbarSlot.java | 5 +- .../expressions/ExprInventoryAction.java | 2 +- .../skript/expressions/ExprInventoryInfo.java | 6 +- .../skript/expressions/ExprLocationAt.java | 2 +- .../skript/expressions/ExprLoopValue.java | 2 +- .../ch/njol/skript/expressions/ExprName.java | 2 +- .../ch/njol/skript/expressions/ExprParse.java | 2 +- .../njol/skript/expressions/ExprSignText.java | 2 +- .../expressions/ExprSpectatorTarget.java | 7 + .../skript/expressions/ExprSubstring.java | 10 +- .../skript/expressions/ExprTimeState.java | 2 +- .../ch/njol/skript/expressions/ExprTimes.java | 28 +- src/main/java/ch/njol/skript/hooks/Hook.java | 13 +- .../java/ch/njol/skript/hooks/VaultHook.java | 9 +- .../chat/expressions/ExprPrefixSuffix.java | 20 +- .../skript/hooks/economy/classes/Money.java | 3 +- .../permission/expressions/ExprGroup.java | 2 - .../skript/hooks/regions/classes/Region.java | 1 + .../regions/conditions/CondCanBuild.java | 33 +- .../regions/conditions/CondIsMember.java | 19 +- .../conditions/CondRegionContains.java | 26 +- .../hooks/regions/events/EvtRegionBorder.java | 5 +- .../expressions/ExprBlocksInRegion.java | 31 +- .../expressions/ExprMembersOfRegion.java | 25 +- .../hooks/regions/expressions/ExprRegion.java | 21 +- .../regions/expressions/ExprRegionsAt.java | 35 +- .../java/ch/njol/skript/lang/Expression.java | 25 + .../ch/njol/skript/lang/SkriptEventInfo.java | 19 + .../njol/skript/lang/function/Functions.java | 8 +- .../skript/lang/function/JavaFunction.java | 4 +- .../njol/skript/lang/function/Signature.java | 28 +- .../njol/skript/sections/SecConditional.java | 30 + .../java/ch/njol/skript/sections/SecLoop.java | 41 + .../ch/njol/skript/sections/SecWhile.java | 23 + 91 files changed, 2717 insertions(+), 2061 deletions(-) delete mode 100644 docs/assets/Logo/Skript Logo Circle 2.png delete mode 100644 docs/assets/Logo/Skript Logo Circle.png create mode 100644 docs/assets/Logo/Skript Logo Circular.png delete mode 100644 docs/assets/Logo/Skript Logo Trans.png create mode 100644 docs/assets/Logo/Skript Logo.jpg create mode 100644 docs/assets/light-off.svg create mode 100644 docs/assets/light-on.svg create mode 100644 docs/assets/search.svg delete mode 100644 docs/css/highlightjs.min.css create mode 100644 docs/docs.html create mode 100644 docs/js/functions.js delete mode 100644 docs/js/highlight.js delete mode 100644 docs/js/highlight.js.LICENSE create mode 100644 docs/js/theme-switcher.js create mode 100644 docs/sections.html create mode 100644 src/main/java/ch/njol/skript/doc/Keywords.java diff --git a/docs/README.md b/docs/README.md index 2041757711a..30f465c13e5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,9 +2,8 @@ Skript's features are documented directly in it's Java code. But we still need -1. HTML, CSS and (possible) Javascript code to create website out of these -2. Clear tutorials, not just "you can check the syntax pattern" -3. Examples explained, if needed +1. Clear tutorials, not just "you can check the syntax pattern" +2. Examples explained, if needed When generating final result, each HTML file is surrounded by template.html, which provides head element, navigation bar and so on. @@ -26,3 +25,11 @@ include <filename> - Load given file and place them here generate <expressions/effects/events/types/functions> <loop template file> - Generated reference content - In template.html, marks the point where other file is placed ``` + +## Generating the documentation + +1. You will need to create a directory named `doc-templates` in `plugins/Skript/`, and copy everything from [here](https://github.com/SkriptLang/Skript/tree/master/docs) into that directory. +2. Execute the command `/sk gen-docs`. +3. The `docs/` directory will be created _(if not created already)_ in `plugins/Skript` containing the website's files. +4. Open `index.html` and browse the documentation. +5. _(Optionally)_ Add this system property `-Dskript.forceregisterhooks` in your server startup script (before the -jar property) to force generating hooks docs. \ No newline at end of file diff --git a/docs/assets/Logo/Skript Logo Circle 2.png b/docs/assets/Logo/Skript Logo Circle 2.png deleted file mode 100644 index 9ed0ed31f749d8e804251730d4bd8d09760d9544..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18900 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 diff --git a/docs/assets/Logo/Skript Logo Circle.png b/docs/assets/Logo/Skript Logo Circle.png deleted file mode 100644 index b169552bbc4c9ab02233b87fbdd57a92b628ffe1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22859 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 E0h7=O%K!iX diff --git a/docs/assets/Logo/Skript Logo Circular.png b/docs/assets/Logo/Skript Logo Circular.png new file mode 100644 index 0000000000000000000000000000000000000000..8c93b07d381c4529ff4ceeec49474e64748b25d4 GIT binary patch literal 19415 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%0Ap6ArCC$+~Ly+g0r(y}^*LL^R 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 literal 0 HcmV?d00001 diff --git a/docs/assets/Logo/Skript Logo Trans.png b/docs/assets/Logo/Skript Logo Trans.png deleted file mode 100644 index 9575b402aa5c0873e7f5913b5eb05273fe0f70c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13278 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 MZ>0P3oWsrk0EAJ-A^-pY diff --git a/docs/assets/Logo/Skript Logo.jpg b/docs/assets/Logo/Skript Logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2a1221730d2c990dd0305d09d116319af80edbe1 GIT binary patch literal 30413 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 literal 0 HcmV?d00001 diff --git a/docs/assets/Logo/Skript Logo.png b/docs/assets/Logo/Skript Logo.png index 273b15652623addcecbac70b38835c04d7285b72..d3965b280fea418bdc79e2d4997492d83fb036c4 100644 GIT binary patch literal 10738 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 T2=B`OD|4^49X{u~Gm-xXo%vj~ literal 14704 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 diff --git a/docs/assets/icon.png b/docs/assets/icon.png index 273b15652623addcecbac70b38835c04d7285b72..8c93b07d381c4529ff4ceeec49474e64748b25d4 100644 GIT binary patch literal 19415 zcmch<1zgnI_9#ww2ug?yF@VyYLn#a;BB`Qu3?Vhd(A^;=AS&g6l#(LSFbax-5~6@G zzyK;Bsepsz+k>8S?|q-&efNIeKhEdl5%zcOz1Lp7)?V8rqsuz<G#oTUL`3v@x^NRB zBH}H=A1Vqk(my(r4E~}<>R$IHBBDJ>_(M!|Kc5}^@YU7K+|S(L63oHNL)^~M%N`*f z=z#>#L_{j;fk-<CH-sOLJ;K@5Qx&@2)&=EpbyS63lQWPsKx!gfTy=we5T?PG%^ZT= z9F!cP>S{bHfiSRu2g1*eC(y&)(-#(~3O!yI27V?COF(&!LHyiQp%(}n@|YVK@o0Ma zAb1qSrNkU0B_(+jl*E-B92{gF5u!ZOlG4%=k}?ufvSLzFFll+1loHRM|Da$yA4eyc z30(WnX5cqfsEeN;5+)%L5D*|9AS3SO<18Viq@*MvDJ>x_Ee0UOe1kmw>;lC+efj=c z0gmu>@Nq@@xq5l>5LUFa_d@xpLIJA3&)|Xlds$E4KXd{VlL)j!N=S)I63%oC=;-h_ z91`W@e!RJ(g9O4I;eqh<^98U{f5QSY85sNx{y#43;qf=Lub-Ab!06A}{zqtEvmhiw z!UW;#h4OJgX!(O3`Tj!8*Utp;57PV>(*gM3laVf7eqO#VUjK#&e}DcLQuw(#{hJL5 zU;YIY5$O7FKnY*|21UX&eGqnjUOr}CUhaPhkI`Sa@kmRH%kT(ZbM<ue3h)&o9Q@A& z1l-OKp$Y|pBqb&(D<-XECZzzAlYvPqh)Bx7Bqe`?8UWdJwDYt38(3OQQeI42+Du9Y zh#X8(`d@&(99^A){v)WP1I)?G$HNXV&eg-t86ko6bUxnAzyPM_>Fa0b>44CKt3m;) z;;ycaFhzSiIVn4Y0@&8kQOw>!)<Mk9k$^SANk(4DPSMHE?l-nzKHSRzMGz{&{ND=Z z=;Z*`_?sIrM<+Q22N@{^F$aW#qL{q2thAV-lboWMgOik$oV=2wtc<L}U)UJ>xB?xw zbN}a630OIT6_pT<4hnWoN@9-kj*4QAj<RxMO7eCxVhYl-b_#M*vi1&=4p5$d?5pYJ z?&V|P<p^jl1LYxb8wQpKsM-<qOBE{hXV9D=s_v-Yg9_3>@g*gs2><`{{rZ3S{*TXB zJY4~arJ+0od4V18ZwhP|KtNtjPDbv}xRGlh!rdJ13aI9LjEEdztK%uhpRWIJp9sc` zprbHdS6?7?L4T}`DZ=~DPwuWf$7%_)bNGD-2ZDSe9HD=PUH=Cb|98avy=;IB0+8$f zAh*B4e7&6f0_=Pc7n}i&|D#?={5#}*?fn1Gs7p)BOFJkzIRVB4p^{aQRTNW_ly(r4 zlao_)a8Q(!mXwnJ>*)WCx}2D#lo_zyl1eZsslT-7{|5CxWOZ<{^K?c4KO_PDzdwwu zy@ITxqJol`qJop8n39aFteCx|EJ6%nuVg2s;2<xn=<qim|31vWGYS9QVPyW*KK-8b z-yFul%hMm>^G||>wDYk8z7*l(s|t1U@$%rYLn7T>9gg)&!r#;JkB9nOC3*b3c>ZC4 z{w>iQ5k9W}Bm@7v%ik#Q{J*&0f0VfY&K~|I<o}OMk|2Z#zs-Qe|860E{|^66No@je zj$JS8f3gMttqcNR3<JjfKbd=ktRzA~Nm5MONnRFMXjv&SMMWnmF$E<VVCe0nq-B(V zx%-Wc*Z)&<PmrO%#S3PJ2=u#T7X;+-k8#nkbN+{!`3KxF%w-_Ds!(6Ie|8zi9|)@) zfA}W>{Wo*P|LtV)f3^w#AIvsz^+Y)M*g5&}82NZP12yz;^>pSjuyaK4XxaG&{Tsag z(=5V?kEQB2;UxZn_`i_$804Q~)xTze0GROO-=R17<?rAf;R!6I4+zca`;ae*h}3iS z;1|pS^H--L@^erHM@N);6}?4E6*}*qZ}d8!O0N_&x|ne335MgDA5CLvyuCV0jOg;6 zw?FI_^kq%M&7tu(bM0^T<*1dgHc&rKp*i>Pe4eb1-w(=<E{fk=PAdxE3q1-Pd`|2X zue4w}w%r=8vfea_J&~`pa8GK?cUrv$FXuj3sG6u-(N3yJt;nRvrTDBUoiC~+N;ArA zeSKznZ#{lHsJc6<`)A0(Smd?6{cy$&XIw2t`~hVkWJiB}{RlhQn}VS|&$dxse0VK6 zu{^$VNQ=am9Vv{wxL*6E<0zCSW9>kZnB9)0PPZ-SF}5wzIanPQRUUHm%N5JW44pg4 z(wf+mMlwJ)u(&DJiXRK$_(~I_PGYznuJUPNp4ztDPCV1x2%U0j!3F1TONYF?Uh7O+ zBC4rner`jsjy{ks@F9Mz%RHVriJ7JK_45KMOR9WE%b~sHdn;{M#il1?&UC1gsf_O} z*G4U@LX%>1X{;P=bZ~tkDjQt3oSvx`FXm6<!g0BntCnxN?C<OHlbJ2v_B%&<-4<Gd zk-;EG;3gFck^Yv?y-0j84P{Aix{4tI*FJCZLh|W^JXr2d5Xp{G4c50=E;!TN6s?bi z(6@8n#qN3E@!*vDp;*_6ci`K2;>y9q4d!{q&yHfsiLT*BU&zIYhjm0*gmlrGCs@Rc z#Th{j<HWr)UZi%G{|vwNG?kC3f%Mu+08V>D^_e$3xkMwk0>-r?xZ#Z><qT}E)RnR( zT_g6SJfscisn+EOYhQeik+n=R)G%F|t5rZQuQrjoA$up1WAD&dy;xJkIba%_t(LqR zQu(O2&w7sSi#10_ypd0P9c8x2ter4lHd6cC!(I6U7*X>l4VddY=o-hrK|!6sXVz|H zLR%<BGYwwRD6?*o97_?(9Z-=fUSY2tCqCL2b-IJQ{ERb`&`Z<vzUv7LPcku;LoZ(- zaeNqOZ_{rH<FA;$f5>YN!FJK^(>1kR?g<d~qkgX(H8`8BvNTAM>$t<Wp=#N3G_07? zlTTx{FHaXxw8Mrh@tL}LR&PrIOVPSc6j0LW|2+zP^if1YBq<fg6jOBXs*s3MdTJ|w zeyJ(!_kBVa>$1-sDLpR=wHr;%OSXwp4!`=4AAIRk!l=)$LAdSmwfHX{CbyLzoW&W> zi!$2@`w4vviIkWh?L{T$>3;u-^M4ru*@^RJP2yo`J+<78No}4t*XprQr|hKIZ$h0& zVgSQ8h3ihRJ44|7#A}qS#BS9wx;vFa2*LQ-<b9i{lv@pn{KK`#m*JGHUkoT7XynHH zl3dd$hO*U<#pm%_fO#);;>1Z?$SQQ)K11!EQr~5Cf5rfvMO(dj(zS_H_E^_Mh%;Y3 z={-}T-W@iW>=G*;l0$0qa!RL$dkbDXZ-DD<W@;<^amQBJPl-8T?~@AFyBf-DP)nlk z-PE=!JpiRJTYMwOiKqKfOLPt}g=t-d_5-oO;&63Rv4&xp9!6n!B2PEJah@yf>9Aq~ z8QiV(&5z62d&Br*X||*`x;kRKYQ!UNR_aLY#W#{9JLev-HaUqy-#nppA-Y4<mfDbL zXtwwrPmEK>+#9_B@4=qLEJTG=#U$cozi`={BT^?DEs<tpTDXMs#5T^t=SAnFSc3QI zNS#O^nDXEV3F!N=^2pxE!D2VKyGx&{^#Dm{g0j{{xTD}&H$4eg69cq#m`at4f!LFX zO9LSejcYj-u)5=n{dJ1jm;<dd!;U3p4pwVP%>EYsfMeVV0;efw3ihL?WF_j-2H?}| z32kMf=S#L<gH`SiyjV4XctPNBdpD5{yo-mbw5&$@9lPcbU2Zr4KIBMm8x191jZG&@ z9V(f3#NPOo08XMihmEtan6ZPvDax3FWkXNlACTv?B$_vq1hQ8=Wq^{Jq7x?VFg~{Q z$aarYOnS}pQAH#o$g#83rwv*Xu90sz;HCg+;6FDdH}tTszRb)V=!6mHNO`lbQ0B`E z#9c26B+v1pnSz@iMtTt!$q(>CrOnidI;pnU7j#uvf&=rZvB}zBpv<bPm|pL6XBNKu zK8y+J^JTS!1+-3M<yLMk^XQdnG@+>K$P}d2Clpf>b%oL>4!PIv`zLzefxpjgE0~Y^ zh;dxzfkL_3D0j#<icuN-#^^~!#sKvN7s9aGCx%eEom5oD!=dbujYp9-52V>L1JBUA zzp~ooClcPm(UG#wGNdTIAS`|&4P&**il%Jexoo=jT#;@^ZOu`DDU*L!YHg@$tSY7_ zhh{3m6?=Up7+aahKXuSd+3t6pGKr6+wM!ZUIp2ah!IYV?NLJGW(dOfN&(Rv8Mubi1 zvxGma^}#lN_gUugF44gEqA)N&^IM6!5or{ob2T}0Of0SVPP+XQ?0^+;{vK^yO^(71 z$|TxqbjZR9+tZShiV2CjA3|xHxpwkU8G@$h5C^giKEKH5MBVxuk%q)=)iLlKnhrl9 z+&K&jfg{j0ajbWJ@Uvhhv&GUN%C%=t@i!=$n39rgaGq{^S*VN(V>FutJvOP(P8>Sd znBd(|2#YKO#F%S8+03Tc4@*qcy`-9^JUD-PnFmmFltzJOM@iV1iAlYH{4;XQ@-zuc zu$}h&Vf4~)ITU(0BG&{aqel!=pwQ=M$lc)$#gwITTWF<|c($7ly&0G?A5IWoA}N#{ zV7kG9=Eg2H(<2G1Df4-c&5cyY#OBb1Axk1dB29r<CQ_B-YTn$~T}hw`v)xVMgEFg? zAo)=APk)xkZ5<Ynxxc6`0Ib?kA-+Ub*ucQd3RYZXw^aoTW=blb45(In1=BUJ7{ceg zfGpgvXMjSXlsV6>8catoyf`0sc!vpN7)4E>lH=>MxSA=xsu<wTloN;)NV$Q~L!sn* zJZl``iKr9$!hYpU%@eTL#KcppH?fT>^=6AmzKx*e6Huthlfn6#rKIew$<Z3}a5B0E z!py>P(omWm!)2Ze`aWnIihYF%U0x`&LR%E$Ji`OMTMt=-DFTqU)@)8OC6(uTQw~10 zprpRYOnX3vyLK!(Oy56_V9askU;*8UvgT78GC&cadPw25`$dT{&bQ4LyCc_0=BojX zbZuXe&hQcE6=j(%rbN0`X@VhK8L3S@!)(*TDzpv#zB=>9$@__kTW*XS)VQn5JmGp8 z=pxGf+p$&X#KhN;!I5|(MhnXCnpoNnx^bFYz&Jyp=sHMK&9n!k$IaVXQ=QB_6^Jf; zTAE{wzCI*FKcDvCh$Pn{0m%4J3+EV9J@c3l{&6JhQ|ilb_-+$r1&L<^!(5Ic>+W^T z!J_#!CZ^L^o5+-?-s+e@UQg3CA*>iRf$K_lOT78E%(3JwO&Uk?Yw~hTOquA{xK_R& zb7qSlF?0m_GAqeM4&F!*yka>z)QcN^=OE<)@QYd|enmW46?002fp6>7n)x+gt`bj) z{IFt9&^S`SQdz;_4^KHnmX7i!vuq3DT``8$1j1B>-lFo?&!#<a#4+I%2}XfgL}J#z zYrT%aB(dUh3nS@$qkAcdiTJc`)TM`)&!pZ&M-cC1ps0Y#REr?3PM$W-_fe+}%%EC_ zpJK|?T_hIgf$DT?d&H`7?l2LK^xcMLOsTs%hED4P&4D&H^#~w)ft~WhjRf{&L4%fb z;)zJjW40ByGu#sRT>e@M5gShB*^4F+IND#ZCWfJ&LHcgRsa<t+r9Tr>y$r*c7Mdka z2v38p`Pog-3f<xkihRcXQAM4u7it>Z6jcPv4*khM+NrP0m`@#oj$pm(0?_~z7=zoE zp%M>cgZd8OI4QsO$BBut{rZ?sn9b1(yEL0r2Q!4z>h5qRELRv&FH;@(*me-knt35K z;{@soJMDqd1Bl`XFan9Mc?vPAPTQzuo)ZH?_=w{zX9LdGG-&X;^KAtHUzbVHBzU|n z8<Jt;DJr8bbto4&Sf<QH#oHO~8MGr*2M3srQC<L(j67ZUT>88@&A|yAFV1qF6VT{o zAX$Lu%1jkWETS*$EP;|qHNH}67sToKwn~r|M}|)bVrde+5ZMsfW!@gduDxhOaDUL@ z0Op#JOXlfA{oYNL5YN*Z{8R-61zTHN=DmV9u3tZmKp;k?;4jv$U>&-YW5VZWFLcd@ zcP95)GTVQ8f_VDu*$n`vsY!a}%9Ud{q-9f>@S?)Ae4hvfWa{i5kcORE4$EHhz9&On zH}?+#7U6+sYUW8IOlfmTOjD;V;P4(VQ*SFa_Qx8v3c)sjd=F&FemSwm9rD^1Ki^AE zOe9+}vAt;I#p;J<BxR-CF9d5`?TlVln<2MaG>xh!?kWcq&Y_J;r}(aYfpu3BXG0pQ z4%U&Z;EOa4&WKYZZx+PhPcku4$81Jj?3&?!tueL;<mL8FVzu=5F~VCyn3u#Ixj;0= zZ@pZAtY2Vh{WRhXD6R2gOq{NGXoj?1A`;I8<~Jl^LJoqz^Uzq801`9xHeKFo8ye(& zu(uANp*X%~&#nBKxI+OF7_ScSKqT?ABZ5<d8tSL-uFWq4Qox7Lc|(?j)M*cBF;v_( zHUK7B8wY!D8qTsii#hta)TjlA7q3Okx4xGFL#Xrj3~R-JmZ1>MHCwm1<J9QxE_W{j zlJvZ>RKY5)>7Wf?m5coX1d;M#sF6{f)j>y0Cybo}?B^T&nHOZW2y%zu{rdpGdz;BJ z;Lh7+jU!P>?EEpv<Jsml(R;aZK>6|$YR^HTx5<jSW1iRL#vK~lKKwJn_Z`=A&YE>s zl+i{XpsROr&8U{<;FJnBSOQFt%!>M9VvTvMar88yRsamhdXXM>eqa;)C~1iX2iE<r zW<Y(eH^_MLWYmaiAOvuQUl}r1sr9{hNPYgf8(<uj8QWf5DQhszPGC|kFTix57ddsK z&V=@W4`VFQ84o}db!J-|ZA)2q<pjbz!0#)MD?G(Yhtf&a%wCLvU0z42#V(IWXPYcu zBj&rf6AW0RG(=_D%la@|qsaQj7$Ded3{9cG#IvCh%wF>T4FGT&3%49&xLzNpRvlZ* z0`Pj?LBG?c(e;466%S;i$A%o`Ar%*{B3YSO3q&Uq-A{thJos!+)4|Mc-7}U1mK>s| zRD*pm6fYa1y+6VRV2Z=sD~ilr(A>nK4=}Z0lgv3w22Q1HL(!*0=SUOSIDt^Wlf4CN zJ}^ESYTT%dbIyF=7Z5NqHD&c->`qRO@m2ZH9c+N#`IVUaF<~aW1csZqa|ju9RyBFe z4AfY)jXy!q5=}OFm3MSZjo|wnrEhVm-gih>y_1|wlcw@RS38}{ASg(w(#+U!`y|-k z;5lle@VQ!LhDH|=`}K>^)A!fbJfC??zREIGdjtM<@$jJEI+&cCq}=D_Ex0Tbz8`q@ z(p}6_dC@?Haf$2`Hm2`mx|Wnj!F7hh4~OKY*0MsQ7JQV}T9?IJTU)F6=qnySP8l5? z4GVw$+EMz_ec7a>ljnSXesXp@C4rXlnr_ELNItv$&gpu+eOxEw{6a~f`Qo+gJ6gNn zR#qGjcDLQ9+wWP5J44F(e=&tQIPek4IK9Ocr-<9Xf2h}5k=SzQ&Yhm@AXq_JP0i(* zfbX)WE6%K(jK8s{LprZ-kmWZuHH8gX@4))>ixt*}B<T72%Dj8`E>Z;VF0U`2CwEyu z@5Au$^Zj3&)edA%A0J)G&ds&xJrQw>GL6`pYmu-}7R`?3CqoO1Gj3xwQx-mYw<Yj? zuy@;IHiL*<t1`I&YPg{xqr!Ax3F;O`R!+{?*w_xDx$jF=lR}yA$#GLJVkkvcFRTB{ z&MSpZrsd%sBNPg?3K9DF;RA9CHC8$P@|yk~QN6c#YXgH+r{}k4W9Up4HC4@>o+w<u z&70dV<uM`q=<(x|z88ex;P~^s>CV8eYRxD}NZ&;*j|w<xS3P-hyTy0mMsJ2K-NZ{x zp)DVrsMnL8yySMtU-n+EuITdeURYWW+}ZgYDCLhzkz~GcG;a^AN6}Fa+{08v9ey#Q zvD#Fm93CFNfI>AUx9e^$3_Ab#{N`z6<8__P#O0+WyMcTq!o<tv*E>U7{GXm1j4V*e z)`%!dxL{veUY>B}@l`3*w-2vpf}N@XyB|FgW)-{faJco9nBMZ&{#=Q%w4aU~by;#6 z5+*u2lw+2@^xNCpqsI27EiKneP(l}PkG?x|=FDkj|0)z+z3~=R7{BL$^{$JntKGK` zl}V?m$;8xe*fieEL7hrU<gd;f`|v>$k<9ft)7DuMBeg;);%Cr=A08R$ZV%dIMj6GG z?(PPEeV11*6nNSUEZy*_%Ks@X-OxKqkuCi&DrP<4z%CW4C`sw)#%sB`i8MG5mNhht zz>;IlOh(_y+@lk@T2+_EJ8NWyHr(LV{p{!GCmFW8Wuz`EFyPa|IZ|$Tt+Jo^_7l$< z$m)|K?}LL`+qgoK=~cr!i?6@<Tq=NFL3ma@eym?AzNh=X6_Bexd^&Y`F^ul&B3tIh z=A>^6=f{s9N2MO-UbOlV92^WZq#=zdF+QGR)g||Uz8AHnMj^5VH*8v9OvywVSJGno zazsvg|I{=vpg(q_{P_%vE-awxrL25U$+rwecRKQ->L-9&YHI4JRA}xyr+ad4cL0BD zqiIw#2i4b?mWC;ssTRL|D+XH9^i<#ykcGy^MglBgDbPIm=WlGqr;ivZgtxNTaj&+U zE$VEe`c$A`O+cMzX}rX5YV&#z_GAh!Hw16r%xU3#E7V*w?e;)JeFiVc`{Dij;=Q+g zXLI|P;4QJbeXT)ZVIAPyMg=o?1yYvXxWfgi+_W@?0wOkJz<Jo7>Q_L%>7k+X@{fC+ zUuUe2d<0PB#AMVtXS~{nVbR&PK;nSq1G>n~kC?Jlo+zc-^J^@_dYi85DZ-=&fNk0O z+{S*fk3e7j%z^d2yhEA;LF|YnGrFxU&ntX0Z&a#>w7R-F$=t!<Hh)!K`G~7$>(W_| z=q&sEsAcyDM;hFubhM&r^?6>MN<Y;P&gpr0RC^*)lAb+#2CvGiweGn8<cah$JF`JI zvVeZeW^sWm_vKa4bpRD-*L(l*<I8U!A6Gl*CbhP;o&2u*p{(pI;GW6q>I$4~_npFw zJLE#Bk&R6)y$=(<R3c31r;Ib8_{6ZdwGrCQkIQR<ht2Bo)#~Jf)?c?y%lNfF4MYqH z;iV4tudfgc)+ket4nQ7=o_TKu37GlvFrnUEzMozJYYWkwbHFg|8e=M-JJq8LR=<zE ze{T=DK$8)R@?CXd<IWyw8woqoP>&&7L3+8lr5x<$pZE1`7=6#jPrdvVprEEi${@Pc zKo?EsIZ|dY%EZqzvBOTUXRa`o=Kp=zm8cuP*FF4vfoi(CY5FGQxx$j){qOTrSzf)j zPInvYKpF?TX8w2?!=9zUrs-gj2B&xLPdO(F*iE&Um1!$ayfj>oCZ#>N6vGn*?D(yZ zPp!{p*#V1vDPJ+uxI`P`2lF~=C{~8S#yi8517^YkM2lp{08L35RUV|c>JrxZ+o}aB z(*tbBxqsc~Mr&Ik5CUV?Az}`_X<|z#VPWBliVEAlY#~wW_PeQP6rT8e=`#m0qlLwl zMP3#>Y7>xb?qv$#(|!R3WevJFyifI8T;b(%*PA!(m)@5O<o-G|sXS#`cFFGDeK}yh zfr(QM3ljmPpWtNE@U~GfEFbys@uO>tPBfBZ#bPmmcwIJ$`9ZZs(+wTL8}fOG?Qb8W zZpk-%5?vmvEiWs3g~!vpIKa<%K`!hrE-oel_CC7u7$k1d(i}g<w#H{7rl+PLpO!i< zo$XF$5h3X~|IkF}>p+3Nsi~>$`ciJ&dgml8dh6TLQqqBttAT-mnVA`ofX|8~ioD)& zTvvcGRmf}YcWQ7Suj74hx&aUkrm@f!HLv4&qHfL@=l?V%GeF?c5QqjmA!tEh@V}Y{ zeEISvGa~sDhyt7-yx(u!xM3fppj8M!(IdO8!7FSb{;LxR06k_EP=F%C<OFAz&u|AX z@-9;)jsE2wcg|+LanWfrpC}(PluBXBC{PXn6fl?1qyK1}`N{pxX&EPw5%dAGTg}IR z6+8BBWa;VBle<)VEWs<~*d~ujCkWa$*U~2m6-$amcGG8C8m4cGnb@%bCVlD8&67yc zn_D$qJa_+P2JvcypBp=%OHJ2d@G`sBFyECJ0@$lp+Jl3!Cxl)kh0&@8Yl=(xe#yC} z?UT**F*Vbe!0h|?Im#~GvxjJs@k})h8A_(`W@cS1t*EJamS$Y%Tog76)3EOen^MVp zcT=Y=_RaJjh!C~S&t@L3j@Ng?UPPWXEOZB08e?^su6SE3403PrGeh})#MKN3>3U$B zhXF`me?OnXD%Y2ih!V%CR#d$eL1(BRhoaME4R}d!dU-K!YuD^IjFmuZ_+Cja0oi{i zL$q50S!KTZOGABB8gBIJ)@cVoYmsX;PtznHP@g<&4#?>2T#kwzCvbgx^W7N^j;AG7 z8X`w3TMlBHWCFLEBIyZ!Dkmogt-`(gq+i@&bpjY~8s@T?;5nx^w*{UyHCgm-X#M3M z;1t;v8tG5IeY**Gt)T!D6cI7KKm(Hdyu7?>haG6%J88F3(PegM%*7AcdqE2Y&JZ*| z@GP*^OBzB0%U^-Mvxr=EIhh}8-8nR4cOS~*y%S$mQ{yyTtd$vls;WmnpTJ+)$Jc7p zUN&mZNf`5OHLqOq@bq-r-}x?T@$Aw|@vn33p4K)t+<D9KJvN;~=~4zv4<tQBMXvq{ zV!%>__@XzHEP1mvNK;d@s)mg@0?_D#tX5=kXWDep*RM{G9zD7%@4*4=<a>|n%!!J% zd2TvwaO3dYJ*F}t62Ir<Z00%}1|cEJtE;OY#>TSVIPSdGQG4<EO_m%&urOuw*g2R9 z)C`pbH(x^U-U)#feEs&g_G>h)IBqUQVw8eg=(^?cQ52^9vO0D6Z~urZyeotUhX2VY z_hQeZZ@^{N*L$z80|f`dOTADOp#}u$nYgt{hNU>$Bx>{H<7>;7<VjVbXl~d0P}^%( zs4K-ZVG%8so<&CoVYJ~NDvVnnyom&^y0W^uF>N*lI0`XCy@y|DRgE>Dde6VQu0xe` zK&zJZM$+{?zj!l_1PDk;G#ios8dlHB%1V?QX=-8d7zj|%FO7&c*W^eb2F4)qp6pe= zBiwaZm^))zF0||DcJpR3;@;q0shE2*&Nq5b2z}a!iMXg@6|&kWyu~}wg%c4GY1#kL zuY||?%4yx>@fIc6#^3Iw=VX%DR=q>-?Kh70Ev92$z^H^ou=g1b8s;lX`7x$3oxTN} zES7Gv!I=>w&g{2*!`i|kk0E_y9JsF6P7ST9HQbwD-;GomB?7Z_LFN*($giw9=4)SZ z+Dic2Q7J;8cMpi8^gTKd`BYgnU0!&nzX;4pk8yZxXD=)<2P7S@(z(4Ld_SLj5(PwL znSYh4zfu#FI@FCWD~mslZ%=&3PBI({eZPA3>a#(#$XvRmxQ>CryXk~CpM8`-9989@ z_rmPz)kkjyo-x}j5)q6#pyj9(GoQMcde9HYJ7N~vIywm`e!a-Q+(P2cS)0Y$yJb{S zXFQuPQ0kM3M)0qGs5HF?q;7e%dZfZC6nKQn$B##}4U!r|(Op0p$biEDG5o_3*JZ(^ z57X0^f!bxAGb}4D)p{vjcmsr~^3U~FT>CoGfBf*dT5DZxq5wR|OORvWS=)3g@87=< zd=`i==agA?!_B_UW=Q;W9Vr_g9d&}}Kpz43sqcE5{g=kkW7p&i<H`gbAl^VXMQ=RS z9oaBB0fLN&Z`fLEviHC@eUQ$8tw&v~;`xuwGymnzp@4g3RFz=s(OR2Yp|%V{)c5F7 zG6>N@Fn>b7d_>fy6U0*j-aq0FTV0b+=$D#Qnb$`H+qo?G{&8n#VbJDRR~qZxZHc2F z5+GPedbl@~z>{-PDqzjBK#&azgyU_Ys>MQ2Ke1=qs{BFSxul005}0fu)A2gHm@=Fu zG`AnfryjwioubaP6z^U6)KKk^NCpc1<iZjX<&~95Ak_7^OB+>FBVJ}$RAZ7G1{esq z;{?&<Hv&@rNmh#t9C>(R;*kta^L(lJfvdYa!Q6rh5=oZCmbyiJ;v=A=tm2lhCmP+h zEm01_s3k*?KzX9FdyLQ_4~mPUOZ9V~_^;krjX$P-zLM`#-itq_oCi1$9-K?c0E^r! zOH2YuF>t>9qtzC!E-pM-Z_ITr=KuWZ?=)CgZIavZqd{R;nb*6Vsg<#>udm|KBYO}e zJbn7q$keoi-jBHNi%*qh%X?UStnRgIx#2@u<EH36_m%Pbw*|_%+sk7kA3i)Z@FNDe zf0(FT(Ntg5!_ptC&|XwXY6HJE$Qv0|Hi%j*)7sWrQvflRb?dW1Jl+)^=8{HKpy0&? zXbr@-7LASqDHX8Sg~|_WY8Y^m=g&!zeO~p7P%ba<=K#(ZsJzcoY3}XI<w4sYuLT|c zTBx3X`3h76*wsQ6PoBM0mqvoI3YdE76|rZr%$^i5sES7dAm0hs^6NTG-_62f!#OvK zy;+HYnF{)xsnfi-)+Tp<lR(@`f~<n8r9>d_1Ywq$5xDxpZBc@_O)MtS)Y^IgFy6e? zPY76G0%d@yns?HFfHL*!S*l6h9|%yTJ#YWH_7@WjTzxCkcfY;>qWT7pNqMf&jSKRg zSly72Hq^t{RNaEA-}ttGT)<#37f;V;X><30j)DkDqhV0vAz4JIMbV6l^VQeDoMeLZ zMma#-ZM4b`s3?fpT*=hQ0;Yq22Q_b=jXz_LqID9_bpUP=h)es>$EKmiGa~js$klzn zUINM|tEHX@B8jP(l_)demmoN+atM6X(xT*ll*gZ0xNfmHc=zQALlAwuN`tgD;WqgK zA@1|;1n;jQ3WBkrp&?sHw<*YCfXo7xlm?c0q18QDHA9C3lEZ-&1QswVK0dxDZQ2W{ z$1+vD@s%qqZCyIR@Eu3<Cxk{v!f34wB@$bJk^&F+^YY-i)z7V#;^kKFxLTLW^6V8h zWdN?KueFN5xuy=C6wbK_&V0QYX;<7AHu=nBl*zuNs_GVqnt;<cyLOEYSfdFqUgL)v zxrhC#o+Fh(jA}a%05w2D?Y_cwI=}ymIY{SFbeS5cM?(niUp|9gk5VS%VIH?Or>m|v z*yBlUL3q)BN30=@*Ca7ad$E>Sx$3s=PIgYtYM6;7aL%I19UUE3Bb8jhT5PGEgkI%1 zbLM)JX8~9fJx<*I^Vz}ShENJWvuCAQG7!}p78VXt%p+yz)^l=mN5;opAl<$o6b+`U zg+Djl7`?t0WXM65nF!*N$q<{6Y>%1FDL`F=d(r}T&WJ3$y;s6bBCxV6wsnp!TDJ#i zlfwJbPPZ{}KungV61(TCp+z#zX?Je&zbzzU@0{U&<+S+Z=Eq06GsRE+mQ0qZV(!bi zU3KI>(SYb@1q7~g=rK8T8Lu=gzoGNp446}p(vHSh@>dlSaun|vp3d=S9u^Oa+#3VG z&l%Y$0{ecnlYXQNiXp&)-P<3o0|k+$w7E)P%RnX$gs%pbz4&Wn-3~9ZR#Y^#K02(K z9z8#RJlanSc%Hanjn}A}VcN~YaM_yUyjA3#-rm(^3kLS^*Ss3z*}Qi^O_#nUkH+ji zjbG~m)LLub92hT1pB^87{W_CRu3P5rHS5Tl1iRjYp(%>N8S5Rld>F0v<=8BV$gEd^ zMN;8~k)&fo0nAx@!|AYZg3D9_pW1^OP_eu%bIBrAe5XW{`^k(?^_JKXe;q91ZsRt) z_^geR%Pp?Rnp~lXD!_&zeU$auS0cmO*y^HaxrG;u1Ca*w8<u{#Mq#5#%WI@G5FXZ} zkC)XC{J_$`J`ILDLF{y2sHO1u&jrf5TZ1&&zcc1cs@KiPb#7wyqN|}%j9<6Qv5b^% z11NP#6;wKYoyjBp>k+z3BjRb1dP@Gu%Zkro8hR6xlc?tzuT$oS>u~;^hDB<sEsT4g zrbrp5R<!5SM=9`~gEMQ|N6Uv7_<lC^bPe_Q_gC{}ubfl=m4qV8y#4aKi#f9NkdRCV zH8rR<(TD%wRS@v}vXtZ56ModRDjln{PYX%h*LqlAZ(QP+)ZWZRPwTm`cUwTe%7JVF zooAKV+5QHWw25PzlHC`PFh5r>>w@;`UiC|F*B5W;foZ&X5zdQ!f=EG;B}Eb@4`H!y zj}63*mubvuMJUhU3`0|A?-WO_O2}fvdUcn^jNYZRNY$hLO@OFqZvQYL5ihY(RaMRG zlrxHE83R1{w!A#wwiy<XeukW~<*IeXSW)z7f0p?-L*j@xM`c|al!1a7f@;cpD-G3r zfzYa^H(2jp$S6}X^LFW7K?1FU{<0h!(`8zRE7tfM()wXVroo_ALrJ-_sryEKQkemi z2sN*K0~?2K=FL1!GtLId9wFWNk{+M>7`TU5ZZrL56oEgMM}^|WZN9y^Zpv+fK7vK^ zL^XeV^tg%-6m?5GZJ$TKE;y0PmY~d2Tm@X{#KZ~1nL7_aMGur)0#`yKs^qXEE2KdO zLOVr+ZKcsWljV;cRKGAtdSVrqsR|!~3<1<8%h+xq`tN83uZDhpojJ<H?Dh5Sz508q zlQzI8KaVCKmAbfDKGQuK@#L~``+`?OLbz=Kl!|ft;2pR#=G>@SHzqN<dX=?}RtIDX zpj2ppTSkF%f%r%8M_W!wc(Sc!&$f$s<ml+t<B*{wu*)6EHzgRdk>af$`ZfJ`S&B08 z-GQz*_WI=X^Z+5AQ}D9pd0E*1<*rJQG$;UfcID1!r*a@57m(`iV`F2$kQ#vPfIn$S zGj1+l^QLU-xnQA^&b5X*I*iCj)acroOL0(Te$5slp1T<;tOjBnD=PzFcpb;y?tv;h zFqPMPGps+k%iq!I{tAk_Lxgy)M3QkoVwAdlbE{9W=pg*O&bi_t(@L|IqRFX=iHj(d z+`UWjS}Cmco>Q&G+H0UJ3498uoc0LhS~5RM4Ya;1>v|hS_Gu6pX;3@|<vJPXf&4|l zh;M}(`6LTDGx3|8jEtN^!+YwC9kn*ZUYwtOp(P+P8XXA<lT%Pwmys)c<H)WWEQ|9G zTu_Jud9K5HoYUUV)uh0xMRGPvTgZi<AVoIs3{_f>INGZ&*(nCxR`UId>=$dwxL8^> zPbZ%C1;2H1XCySR*L75{eT$-|1TSTN9Z%Fn{AxExP#0VPe)#YK9<F&!3MlwV<55lT z?=RUbbTYy}1KC^N3i)kt%PK41!1^%OLA+{i3wt5<kXw7vC>jaXTZF=>{1=A0k42*L zHEnCKWh#3Y5Vp^9^9qcE*rKAU>S@|+9!R#<3f2p{>MrAoR@I+f#x;hw{j!bYdi*jQ z9G8QGqsk#Mvw5nMT_=T2B5jr8DpGl-d)J-9fB!u!D#CjDRMW`(?&{YVmfgWHD*JDJ zBDJ@+$u)OD`SZ#m(+PZu9dG-f6E2QDeTdk4!m|LQe{e_+Ge@rz6>g}d6qpI;+<oW$ z<4*I;mlNl_)i~s9pFNWgRi2rD))p2a@v5w^um`nC%y4z_M7MFUd|vAIjRH>HqX-@S z{PcM8rcF>hUYb?fRGmKeNJ+T}AfH2A9>9Ek5f`p8`XNLbkZ)1)9#!%tu!|SNey)6) z720l|{1viHW_`LYE5T&(Oh-Qxm$$mVNxmPfy6p^9n0lrJM2!Oacg%Yut%lXfr7v9M zKnIYdhvW*V%!RK!#DMFG+ToyBf!UC!EiIqI`EUc~Dk&n=g+zyvLtUH~(WKZ_gF73b zN_@3y?AvY8tT#wdN&z<r%r6gsYVGdsBG*E5>#nPhcX8fCAEi@X&=AVG_%gg&yt4CW zW9<^pJ@(h!^k)KqW%5^^Nr!#<8dhplcxqQ0`>DPjq+qX(Yomz?H{E{6kGx!O2;iV{ z-XyfmC-)#JvWxf(#a=9$b+^A~g?;=0gb4|I`H0g`K+w0fGy>9v-nnpD(dL46LTvME zEkSd}uHb>0g}0T;12iiPV^xZv?sAuZY?9vW2Q#p;Dkz(95MI;ij-LEQuwu3|Taa~{ zp9i9x5jCi26MEPbLmbQQ1amYz{-Wx{9SNJuK|w7jy6<|-Y4PNQ8?K)h#Q98+Z+mdl zgD<{2-#91u5kk2at3h*cfkM$!+|%saFkn*8czu1~L@gbZ8f*mGG#)6=7zTPF*AcVL z!=uy}>q%Xd$Ir2tEQUm)w0e`+bAq3K7#;ogvzshw@3$3y+6dW#t)-5frQB3wO}j^> zlW$v4d}<j9QK`_NfiO$27J}0Kr#d^$m9drL8j$uLr}7z*0$=<`fKHM%Y^sJ89<AFU zw$4?b%)Y5RW#NLJp>MwtcxMqLR?eUlnZ!;HHwL%uz6%45X}lD-k;gRj{rdKM#KIoa zIm~IBpLo@(TP+i?3qsb;i3x2iHx}C>Luk@YdV?Y%ArB<f@pLlP2wCc*M^Dmt;|Xh= z|E<G6k5UJAm5*llT*?QMcm4KMo7Yq;G-x5Xa;;E<&!r|pAR*HQ5Bs2Hzg~!fn*xx2 zy}M79aSMcOQv+Z32Joi6y<Saw<4S;9s68X~`nwul0<X$nLEZvImnHWS%zO79<b0+$ z$J&RS1m7K^ub<N8_2Y1TXK(ic48@u+`wFHdG);8qR#9Ym3Eic8iMtYJ_RI7O!2p!- zU-a-010~bo;QO2p+S^s6X3|06hR6Fq%X-!0ET-Bv4!cn4a~^M`-f~wXfuxLRt_mc^ zpp^USQ1PN0FvX2-V>i#W(SeXrSZXE(6x|QkLW`PDFsM#-jWNF18-<6ha?-3!*wcp5 zZ#ZSzoDl)GPt>Y48{9#{tLDk8pFO){;n|{Px+2Z5r^vyI9l$S*l<xukhtsHGzYV%k z&fb#f{*-w%yJkRxZMf==4j@*S_~GW@3UWqZ;^WK8%HWnp`&X}De-lrC<}SF}89?On z>c0;T20=auT7TAlFytW3WnA*CL@Irs_l)P=m7i_(f1NDmN2KcYlpAD%gtT*L=z+b2 z2B=MgDj`76%=wAFF-T_e5|2Y?n<tJ*5njk$N?f$pFZ`~x^7nbk?F7PFo<du+7lpPa zBc}vH!`uNo0U^fQgJb|n?<aFd_w`)_QsHv*=KHPIY*8VdZW37qg}pt@eQ%q{@9IC+ zw>y|?rDsxq-?K{)2i)1%PRamzOM?@=n3x#sZW%YYx*b=B8upQ!M;n8T`R?^6;G!-f zptC5#s=0i|uz;mpnsTgKf@N2$^c3urlt6|VDDxv`U&OgjwdR1F3Z6azZs!1B2*1bC zvfYG>cM3{K820>DFTgGJ15%^ywhf4i@AD?xzKEX6x9HcNVvR!)6f2_N<(NxB+60(8 zT6Mh}5V)ec+5p}H?uBmNyh*rg1j+r|d?izSR?0e|`seft6erfaD!b-ocg725&9A9D zK`({$h*9_SU$$&wbssNOr3`~kJ`d&I@vD@$?FZbq`==*y%Iobi<IX&Lg1;+zXD{bh zEM${{#IU4MuC<9oPF5EFV`aavkgKNUD+lnH)g@%0Ap6ArCC$+~Ly+g0r(y}^*LL^R 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 literal 14704 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 diff --git a/docs/assets/light-off.svg b/docs/assets/light-off.svg new file mode 100644 index 00000000000..1b7db07723c --- /dev/null +++ b/docs/assets/light-off.svg @@ -0,0 +1,70 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<svg version="1.1" id="Layer_2_00000120519223585702451540000006968771831968122805_" + xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 148.2 149.7" + style="enable-background:new 0 0 148.2 149.7;" xml:space="preserve"> +<style type="text/css"> + .st0{fill:none;stroke:#FFFFFF;stroke-width:2;} +</style> +<g> + <g> + <path d="M89.7,126.8l-30.9,0c-1.2,0-2.2,1.1-2.2,2.6c0,1.4,1,2.6,2.2,2.6l30.9-0.1c1.2,0,2.2-1.1,2.2-2.5 + C91.9,128,90.9,126.8,89.7,126.8L89.7,126.8z"/> + <path d="M89.7,134L58.7,134c-1.2,0-2.2,1.2-2.2,2.6c0,1.4,1,2.6,2.2,2.6H62l3.3,6.8c0,0,1.6,2.2,2.4,2.4c0.8,0.2,10.3,1.3,13.3,0 + c0,0,1.4-0.4,2.6-2.6c0.9-1.7,2.7-5.1,3.5-6.6h2.5c1.2,0,2.2-1.2,2.2-2.6C91.9,135.1,90.9,134,89.7,134z"/> + <path d="M89.7,119.7l-31,0c-1.2,0-2.2,1.2-2.2,2.6c0,1.4,1,2.6,2.2,2.5l30.9,0c1.2,0,2.2-1.2,2.2-2.6 + C91.9,120.8,90.9,119.7,89.7,119.7L89.7,119.7z"/> + <g> + <path d="M84.7,74.7c0.3-0.6,0.4-1.1,0.2-1.4c-0.2-0.3-0.4-0.5-0.6-0.5c-0.5,0-1.1,0.6-1.4,1.2c-0.4,0.9-0.4,2-0.2,2.9 + C83.7,76.3,84.4,75.5,84.7,74.7L84.7,74.7z"/> + <path d="M71.4,73.5c0.2-0.5,0.3-1.3,0.1-1.7c0-0.1-0.1-0.1-0.2-0.1c-0.1,0-0.2,0-0.5,0.1c-0.6,0.2-1.1,0.8-1.4,1.4 + c-0.6,1.2-0.6,2.4-0.2,3.4c0.1-0.1,0.2-0.2,0.2-0.3C70.2,75.4,71,74.5,71.4,73.5L71.4,73.5z"/> + <path d="M61.6,87.3c4.2,9,7.8,19.1,11.1,30.7h4.7c3-10.9,6.4-20.5,10.2-29.1c-3.1-1.4-5.5-4.1-6.8-7.6c-2.1,0.9-4.3,1.2-6.6,1 + c-1.9-0.2-3.6-0.7-4.9-1.4C68.1,82.4,65.1,85.6,61.6,87.3L61.6,87.3z"/> + <path d="M54.6,81.6c-0.1,0-0.1,0-0.2,0c-0.4,0-0.8,0.2-0.9,0.5c-0.2,0.7,0.2,1.3,0.5,1.7c0.8,1,2.2,1.7,3.3,1.7 + c0.1,0,0.2,0,0.3,0c-0.2-0.5-0.5-1-0.8-1.5C56.2,82.9,55.5,81.8,54.6,81.6L54.6,81.6z"/> + <path d="M94.5,82.5L94.5,82.5c-0.1,0-0.4,0.1-1.2,1.3l-0.1,0.1c-0.5,0.9-0.9,1.8-1.4,2.8c1-0.1,1.9-0.5,2.4-1 + c0.5-0.5,0.7-1.1,0.6-1.9C94.8,82.9,94.6,82.6,94.5,82.5z"/> + <path d="M74,27.9c-23,0-41.7,18.7-41.6,41.8c0,16.8,10,31.2,24.3,37.8v8.1c0,1.4,1.1,2.5,2.5,2.5h10.4 + c-3.2-11.3-6.8-21.1-10.8-29.8c-2.3,0.5-4.4-0.1-6.2-1.7c-1.2-1.1-2.3-2.9-2.2-4.7c0.1-1.1,0.7-2,1.6-2.6 + c0.7-0.4,1.4-0.7,2.2-0.7c2.3,0,4.1,2.1,5,3.9c0.3,0.7,0.7,1.3,1,2c2.4-1.1,4.8-3.1,6.8-5.6c-0.6-0.8-0.9-1.7-1.1-2.8 + c-0.2-1.8,0.6-4.4,2-6c1.5-1.7,3.3-2,5-0.8c0.9,0.7,1.5,1.5,1.6,2.5c0.3,2.3-1.7,4.7-3.1,6.4c-0.1,0.1-0.1,0.2-0.2,0.2 + c0.4,0.2,0.8,0.3,1.3,0.4c2.5,0.5,4.8,0.5,6.4-0.2c0.3-0.1,0.6-0.2,0.9-0.4c-0.1-0.6-0.2-1.2-0.3-1.8c-0.2-2.6,0.5-4.5,2.2-5.9 + c0.7-0.6,1.6-0.9,2.5-0.9c0.9,0,1.6,0.3,2.2,0.9c1,1,1.4,2.2,1.3,3.4c-0.2,2.5-2.5,4.4-3.5,5.1c-0.3,0.2-0.6,0.4-0.9,0.6 + c0.8,2.1,2.3,5.2,5.3,6.4c0.6-1.3,1.2-2.6,1.9-3.8c0-0.1,0.1-0.2,0.2-0.2c0.5-0.8,2-2.7,3.7-2.7c0.6,0,1.2,0.2,1.8,0.7 + c1.7,1.5,1.8,4,1.1,5.8c-0.8,2.3-3.1,3.6-6,3.7c-0.3,0-0.6,0-0.9,0c-3.8,8.3-7.1,17.6-10.1,28.3h8.7c1.4,0,2.5-1.1,2.5-2.5v-8.1 + c14.3-6.6,24.2-21.1,24.2-37.9C115.8,46.4,97.1,27.8,74,27.9L74,27.9z"/> + </g> + </g> + <g> + <path class="st0" d="M89.7,126.8l-30.9,0c-1.2,0-2.2,1.1-2.2,2.6c0,1.4,1,2.6,2.2,2.6l30.9-0.1c1.2,0,2.2-1.1,2.2-2.5 + C91.9,128,90.9,126.8,89.7,126.8L89.7,126.8z"/> + <path class="st0" d="M89.7,134L58.7,134c-1.2,0-2.2,1.2-2.2,2.6c0,1.4,1,2.6,2.2,2.6H62l3.3,6.8c0,0,1.6,2.2,2.4,2.4 + c0.8,0.2,10.3,1.3,13.3,0c0,0,1.4-0.4,2.6-2.6c0.9-1.7,2.7-5.1,3.5-6.6h2.5c1.2,0,2.2-1.2,2.2-2.6C91.9,135.1,90.9,134,89.7,134z" + /> + <path class="st0" d="M89.7,119.7l-31,0c-1.2,0-2.2,1.2-2.2,2.6c0,1.4,1,2.6,2.2,2.5l30.9,0c1.2,0,2.2-1.2,2.2-2.6 + C91.9,120.8,90.9,119.7,89.7,119.7L89.7,119.7z"/> + <g> + <path class="st0" d="M84.7,74.7c0.3-0.6,0.4-1.1,0.2-1.4c-0.2-0.3-0.4-0.5-0.6-0.5c-0.5,0-1.1,0.6-1.4,1.2 + c-0.4,0.9-0.4,2-0.2,2.9C83.7,76.3,84.4,75.5,84.7,74.7L84.7,74.7z"/> + <path class="st0" d="M71.4,73.5c0.2-0.5,0.3-1.3,0.1-1.7c0-0.1-0.1-0.1-0.2-0.1c-0.1,0-0.2,0-0.5,0.1c-0.6,0.2-1.1,0.8-1.4,1.4 + c-0.6,1.2-0.6,2.4-0.2,3.4c0.1-0.1,0.2-0.2,0.2-0.3C70.2,75.4,71,74.5,71.4,73.5L71.4,73.5z"/> + <path class="st0" d="M61.6,87.3c4.2,9,7.8,19.1,11.1,30.7h4.7c3-10.9,6.4-20.5,10.2-29.1c-3.1-1.4-5.5-4.1-6.8-7.6 + c-2.1,0.9-4.3,1.2-6.6,1c-1.9-0.2-3.6-0.7-4.9-1.4C68.1,82.4,65.1,85.6,61.6,87.3L61.6,87.3z"/> + <path class="st0" d="M54.6,81.6c-0.1,0-0.1,0-0.2,0c-0.4,0-0.8,0.2-0.9,0.5c-0.2,0.7,0.2,1.3,0.5,1.7c0.8,1,2.2,1.7,3.3,1.7 + c0.1,0,0.2,0,0.3,0c-0.2-0.5-0.5-1-0.8-1.5C56.2,82.9,55.5,81.8,54.6,81.6L54.6,81.6z"/> + <path class="st0" d="M94.5,82.5L94.5,82.5c-0.1,0-0.4,0.1-1.2,1.3l-0.1,0.1c-0.5,0.9-0.9,1.8-1.4,2.8c1-0.1,1.9-0.5,2.4-1 + c0.5-0.5,0.7-1.1,0.6-1.9C94.8,82.9,94.6,82.6,94.5,82.5z"/> + <path class="st0" d="M74,27.9c-23,0-41.7,18.7-41.6,41.8c0,16.8,10,31.2,24.3,37.8v8.1c0,1.4,1.1,2.5,2.5,2.5h10.4 + c-3.2-11.3-6.8-21.1-10.8-29.8c-2.3,0.5-4.4-0.1-6.2-1.7c-1.2-1.1-2.3-2.9-2.2-4.7c0.1-1.1,0.7-2,1.6-2.6 + c0.7-0.4,1.4-0.7,2.2-0.7c2.3,0,4.1,2.1,5,3.9c0.3,0.7,0.7,1.3,1,2c2.4-1.1,4.8-3.1,6.8-5.6c-0.6-0.8-0.9-1.7-1.1-2.8 + c-0.2-1.8,0.6-4.4,2-6c1.5-1.7,3.3-2,5-0.8c0.9,0.7,1.5,1.5,1.6,2.5c0.3,2.3-1.7,4.7-3.1,6.4c-0.1,0.1-0.1,0.2-0.2,0.2 + c0.4,0.2,0.8,0.3,1.3,0.4c2.5,0.5,4.8,0.5,6.4-0.2c0.3-0.1,0.6-0.2,0.9-0.4c-0.1-0.6-0.2-1.2-0.3-1.8c-0.2-2.6,0.5-4.5,2.2-5.9 + c0.7-0.6,1.6-0.9,2.5-0.9c0.9,0,1.6,0.3,2.2,0.9c1,1,1.4,2.2,1.3,3.4c-0.2,2.5-2.5,4.4-3.5,5.1c-0.3,0.2-0.6,0.4-0.9,0.6 + c0.8,2.1,2.3,5.2,5.3,6.4c0.6-1.3,1.2-2.6,1.9-3.8c0-0.1,0.1-0.2,0.2-0.2c0.5-0.8,2-2.7,3.7-2.7c0.6,0,1.2,0.2,1.8,0.7 + c1.7,1.5,1.8,4,1.1,5.8c-0.8,2.3-3.1,3.6-6,3.7c-0.3,0-0.6,0-0.9,0c-3.8,8.3-7.1,17.6-10.1,28.3h8.7c1.4,0,2.5-1.1,2.5-2.5v-8.1 + c14.3-6.6,24.2-21.1,24.2-37.9C115.8,46.4,97.1,27.8,74,27.9L74,27.9z"/> + </g> + </g> +</g> +</svg> diff --git a/docs/assets/light-on.svg b/docs/assets/light-on.svg new file mode 100644 index 00000000000..601ccea1f8e --- /dev/null +++ b/docs/assets/light-on.svg @@ -0,0 +1,114 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<svg version="1.1" id="Layer_2_00000120519223585702451540000006968771831968122805_" + xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 148.2 149.7" + style="enable-background:new 0 0 148.2 149.7;" xml:space="preserve"> +<style type="text/css"> + .st0{fill:#F7F8F8;} + .st1{fill:#ED9E00;} + .st2{fill:#F8BF39;} + .st3{fill:none;stroke:#1D1D1B;stroke-width:2;} +</style> +<g> + <g> + <path class="st0" d="M89.7,126.8l-30.9,0c-1.2,0-2.2,1.1-2.2,2.6c0,1.4,1,2.6,2.2,2.6l30.9-0.1c1.2,0,2.2-1.1,2.2-2.5 + C91.9,128,90.9,126.8,89.7,126.8L89.7,126.8z"/> + <path class="st0" d="M89.7,134L58.7,134c-1.2,0-2.2,1.2-2.2,2.6c0,1.4,1,2.6,2.2,2.6H62l3.3,6.8c0,0,1.6,2.2,2.4,2.4 + c0.8,0.2,10.3,1.3,13.3,0c0,0,1.4-0.4,2.6-2.6c0.9-1.7,2.7-5.1,3.5-6.6h2.5c1.2,0,2.2-1.2,2.2-2.6C91.9,135.1,90.9,134,89.7,134z" + /> + <path class="st0" d="M89.7,119.7l-31,0c-1.2,0-2.2,1.2-2.2,2.6c0,1.4,1,2.6,2.2,2.5l30.9,0c1.2,0,2.2-1.2,2.2-2.6 + C91.9,120.8,90.9,119.7,89.7,119.7L89.7,119.7z"/> + <g> + <path class="st1" d="M131.4,95.2c-2.6-1.3-5.2-2.6-7.8-3.9c-1.5-0.8-3.6-0.5-4.5,1.2c0,0,0,0,0,0c-0.8,1.5-0.5,3.6,1.2,4.5 + c2.6,1.3,5.2,2.6,7.8,3.9c1.5,0.8,3.7,0.4,4.5-1.2c0.3-0.6,0.5-1.2,0.4-1.9C133,96.7,132.5,95.7,131.4,95.2L131.4,95.2z"/> + <path class="st1" d="M29.1,92.5c-0.8-1.7-3-1.9-4.5-1.2c-2.6,1.3-5.2,2.6-7.8,3.9c-1.1,0.5-1.5,1.6-1.6,2.6 + c0,0.7,0.1,1.3,0.4,1.9c0.8,1.6,3,2,4.5,1.2c2.6-1.3,5.2-2.6,7.8-3.9C29.6,96.2,29.9,94.1,29.1,92.5 + C29.1,92.6,29.1,92.5,29.1,92.5z"/> + <path class="st1" d="M133.9,53.9c-2.9,0.6-5.7,1.2-8.6,1.8c-0.8,0.2-1.4,0.7-1.8,1.3c-0.2,0.3-0.3,0.6-0.4,1 + c-0.1,0.6-0.2,1.2,0,1.7c0,0,0,0.1,0,0.1c0.3,1.6,1.5,2.3,2.8,2.4c0.4,0.1,0.8,0,1.2-0.1c2.8-0.6,5.7-1.3,8.5-1.9 + C139.8,59.3,138,53.1,133.9,53.9L133.9,53.9z"/> + <path class="st1" d="M25.1,59.4c0.3-1.5-0.4-3.1-2.3-3.5c-2.8-0.6-5.7-1.3-8.5-1.9c-1.7-0.4-3.6,0.5-4,2.3c0,0,0,0.1,0,0.1 + c-0.4,1.7,0.5,3.6,2.3,4c2.8,0.6,5.7,1.2,8.6,1.8C23.3,62.7,24.8,61.1,25.1,59.4z"/> + <path class="st1" d="M115.3,19.7C115.3,19.7,115.3,19.7,115.3,19.7c-1.3-1.1-3.5-1.5-4.7,0c-1.8,2.3-3.6,4.6-5.4,6.8 + c-1.1,1.4-1.3,3.3,0,4.6c0.1,0.1,0.2,0.1,0.2,0.2c1.2,1,3.3,1.1,4.4-0.2c1.8-2.3,3.7-4.5,5.5-6.8 + C116.4,23.1,116.8,20.9,115.3,19.7L115.3,19.7z"/> + <path class="st1" d="M42.9,26.6c-1.8-2.3-3.6-4.6-5.4-6.8c-1.2-1.5-3.4-1-4.6,0c0,0,0,0-0.1,0.1c-1.5,1.2-1,3.4,0,4.6 + c1.8,2.3,3.7,4.5,5.5,6.8c0.6,0.8,1.6,1,2.5,0.9c0.8-0.1,1.6-0.4,2.1-1c0.5-0.6,0.8-1.2,0.9-1.8C44,28.4,43.6,27.4,42.9,26.6 + L42.9,26.6z"/> + <path class="st1" d="M74.1,5.1C74.1,5.1,74.1,5.1,74.1,5.1c-1.8,0-3.3,1.5-3.3,3.2c0,2.9,0,5.8,0,8.7c0,1.8,1.5,3.3,3.3,3.3 + c0,0,0,0,0.1,0c0.9,0,1.7-0.3,2.2-0.8c0.7-0.5,1.1-1.3,1.1-2.4c0-2.9,0-5.8,0-8.7C77.4,6.6,75.9,5.1,74.1,5.1L74.1,5.1z"/> + </g> + <g> + <path class="st2" d="M84.7,74.7c0.3-0.6,0.4-1.1,0.2-1.4c-0.2-0.3-0.4-0.5-0.6-0.5c-0.5,0-1.1,0.6-1.4,1.2 + c-0.4,0.9-0.4,2-0.2,2.9C83.7,76.3,84.4,75.5,84.7,74.7L84.7,74.7z"/> + <path class="st2" d="M71.4,73.5c0.2-0.5,0.3-1.3,0.1-1.7c0-0.1-0.1-0.1-0.2-0.1c-0.1,0-0.2,0-0.5,0.1c-0.6,0.2-1.1,0.8-1.4,1.4 + c-0.6,1.2-0.6,2.4-0.2,3.4c0.1-0.1,0.2-0.2,0.2-0.3C70.2,75.4,71,74.5,71.4,73.5L71.4,73.5z"/> + <path class="st2" d="M61.6,87.3c4.2,9,7.8,19.1,11.1,30.7h4.7c3-10.9,6.4-20.5,10.2-29.1c-3.1-1.4-5.5-4.1-6.8-7.6 + c-2.1,0.9-4.3,1.2-6.6,1c-1.9-0.2-3.6-0.7-4.9-1.4C68.1,82.4,65.1,85.6,61.6,87.3L61.6,87.3z"/> + <path class="st2" d="M54.6,81.6c-0.1,0-0.1,0-0.2,0c-0.4,0-0.8,0.2-0.9,0.5c-0.2,0.7,0.2,1.3,0.5,1.7c0.8,1,2.2,1.7,3.3,1.7 + c0.1,0,0.2,0,0.3,0c-0.2-0.5-0.5-1-0.8-1.5C56.2,82.9,55.5,81.8,54.6,81.6L54.6,81.6z"/> + <path class="st2" d="M94.5,82.5L94.5,82.5c-0.1,0-0.4,0.1-1.2,1.3l-0.1,0.1c-0.5,0.9-0.9,1.8-1.4,2.8c1-0.1,1.9-0.5,2.4-1 + c0.5-0.5,0.7-1.1,0.6-1.9C94.8,82.9,94.6,82.6,94.5,82.5z"/> + <path class="st2" d="M74,27.9c-23,0-41.7,18.7-41.6,41.8c0,16.8,10,31.2,24.3,37.8v8.1c0,1.4,1.1,2.5,2.5,2.5h10.4 + c-3.2-11.3-6.8-21.1-10.8-29.8c-2.3,0.5-4.4-0.1-6.2-1.7c-1.2-1.1-2.3-2.9-2.2-4.7c0.1-1.1,0.7-2,1.6-2.6 + c0.7-0.4,1.4-0.7,2.2-0.7c2.3,0,4.1,2.1,5,3.9c0.3,0.7,0.7,1.3,1,2c2.4-1.1,4.8-3.1,6.8-5.6c-0.6-0.8-0.9-1.7-1.1-2.8 + c-0.2-1.8,0.6-4.4,2-6c1.5-1.7,3.3-2,5-0.8c0.9,0.7,1.5,1.5,1.6,2.5c0.3,2.3-1.7,4.7-3.1,6.4c-0.1,0.1-0.1,0.2-0.2,0.2 + c0.4,0.2,0.8,0.3,1.3,0.4c2.5,0.5,4.8,0.5,6.4-0.2c0.3-0.1,0.6-0.2,0.9-0.4c-0.1-0.6-0.2-1.2-0.3-1.8c-0.2-2.6,0.5-4.5,2.2-5.9 + c0.7-0.6,1.6-0.9,2.5-0.9c0.9,0,1.6,0.3,2.2,0.9c1,1,1.4,2.2,1.3,3.4c-0.2,2.5-2.5,4.4-3.5,5.1c-0.3,0.2-0.6,0.4-0.9,0.6 + c0.8,2.1,2.3,5.2,5.3,6.4c0.6-1.3,1.2-2.6,1.9-3.8c0-0.1,0.1-0.2,0.2-0.2c0.5-0.8,2-2.7,3.7-2.7c0.6,0,1.2,0.2,1.8,0.7 + c1.7,1.5,1.8,4,1.1,5.8c-0.8,2.3-3.1,3.6-6,3.7c-0.3,0-0.6,0-0.9,0c-3.8,8.3-7.1,17.6-10.1,28.3h8.7c1.4,0,2.5-1.1,2.5-2.5v-8.1 + c14.3-6.6,24.2-21.1,24.2-37.9C115.8,46.4,97.1,27.8,74,27.9L74,27.9z"/> + </g> + </g> + <g> + <path class="st3" d="M89.7,126.8l-30.9,0c-1.2,0-2.2,1.1-2.2,2.6c0,1.4,1,2.6,2.2,2.6l30.9-0.1c1.2,0,2.2-1.1,2.2-2.5 + C91.9,128,90.9,126.8,89.7,126.8L89.7,126.8z"/> + <path class="st3" d="M89.7,134L58.7,134c-1.2,0-2.2,1.2-2.2,2.6c0,1.4,1,2.6,2.2,2.6H62l3.3,6.8c0,0,1.6,2.2,2.4,2.4 + c0.8,0.2,10.3,1.3,13.3,0c0,0,1.4-0.4,2.6-2.6c0.9-1.7,2.7-5.1,3.5-6.6h2.5c1.2,0,2.2-1.2,2.2-2.6C91.9,135.1,90.9,134,89.7,134z" + /> + <path class="st3" d="M89.7,119.7l-31,0c-1.2,0-2.2,1.2-2.2,2.6c0,1.4,1,2.6,2.2,2.5l30.9,0c1.2,0,2.2-1.2,2.2-2.6 + C91.9,120.8,90.9,119.7,89.7,119.7L89.7,119.7z"/> + <g> + <path class="st3" d="M131.4,95.2c-2.6-1.3-5.2-2.6-7.8-3.9c-1.5-0.8-3.6-0.5-4.5,1.2c0,0,0,0,0,0c-0.8,1.5-0.5,3.6,1.2,4.5 + c2.6,1.3,5.2,2.6,7.8,3.9c1.5,0.8,3.7,0.4,4.5-1.2c0.3-0.6,0.5-1.2,0.4-1.9C133,96.7,132.5,95.7,131.4,95.2L131.4,95.2z"/> + <path class="st3" d="M29.1,92.5c-0.8-1.7-3-1.9-4.5-1.2c-2.6,1.3-5.2,2.6-7.8,3.9c-1.1,0.5-1.5,1.6-1.6,2.6 + c0,0.7,0.1,1.3,0.4,1.9c0.8,1.6,3,2,4.5,1.2c2.6-1.3,5.2-2.6,7.8-3.9C29.6,96.2,29.9,94.1,29.1,92.5 + C29.1,92.6,29.1,92.5,29.1,92.5z"/> + <path class="st3" d="M133.9,53.9c-2.9,0.6-5.7,1.2-8.6,1.8c-0.8,0.2-1.4,0.7-1.8,1.3c-0.2,0.3-0.3,0.6-0.4,1 + c-0.1,0.6-0.2,1.2,0,1.7c0,0,0,0.1,0,0.1c0.3,1.6,1.5,2.3,2.8,2.4c0.4,0.1,0.8,0,1.2-0.1c2.8-0.6,5.7-1.3,8.5-1.9 + C139.8,59.3,138,53.1,133.9,53.9L133.9,53.9z"/> + <path class="st3" d="M25.1,59.4c0.3-1.5-0.4-3.1-2.3-3.5c-2.8-0.6-5.7-1.3-8.5-1.9c-1.7-0.4-3.6,0.5-4,2.3c0,0,0,0.1,0,0.1 + c-0.4,1.7,0.5,3.6,2.3,4c2.8,0.6,5.7,1.2,8.6,1.8C23.3,62.7,24.8,61.1,25.1,59.4z"/> + <path class="st3" d="M115.3,19.7C115.3,19.7,115.3,19.7,115.3,19.7c-1.3-1.1-3.5-1.5-4.7,0c-1.8,2.3-3.6,4.6-5.4,6.8 + c-1.1,1.4-1.3,3.3,0,4.6c0.1,0.1,0.2,0.1,0.2,0.2c1.2,1,3.3,1.1,4.4-0.2c1.8-2.3,3.7-4.5,5.5-6.8 + C116.4,23.1,116.8,20.9,115.3,19.7L115.3,19.7z"/> + <path class="st3" d="M42.9,26.6c-1.8-2.3-3.6-4.6-5.4-6.8c-1.2-1.5-3.4-1-4.6,0c0,0,0,0-0.1,0.1c-1.5,1.2-1,3.4,0,4.6 + c1.8,2.3,3.7,4.5,5.5,6.8c0.6,0.8,1.6,1,2.5,0.9c0.8-0.1,1.6-0.4,2.1-1c0.5-0.6,0.8-1.2,0.9-1.8C44,28.4,43.6,27.4,42.9,26.6 + L42.9,26.6z"/> + <path class="st3" d="M74.1,5.1C74.1,5.1,74.1,5.1,74.1,5.1c-1.8,0-3.3,1.5-3.3,3.2c0,2.9,0,5.8,0,8.7c0,1.8,1.5,3.3,3.3,3.3 + c0,0,0,0,0.1,0c0.9,0,1.7-0.3,2.2-0.8c0.7-0.5,1.1-1.3,1.1-2.4c0-2.9,0-5.8,0-8.7C77.4,6.6,75.9,5.1,74.1,5.1L74.1,5.1z"/> + </g> + <g> + <path class="st3" d="M84.7,74.7c0.3-0.6,0.4-1.1,0.2-1.4c-0.2-0.3-0.4-0.5-0.6-0.5c-0.5,0-1.1,0.6-1.4,1.2 + c-0.4,0.9-0.4,2-0.2,2.9C83.7,76.3,84.4,75.5,84.7,74.7L84.7,74.7z"/> + <path class="st3" d="M71.4,73.5c0.2-0.5,0.3-1.3,0.1-1.7c0-0.1-0.1-0.1-0.2-0.1c-0.1,0-0.2,0-0.5,0.1c-0.6,0.2-1.1,0.8-1.4,1.4 + c-0.6,1.2-0.6,2.4-0.2,3.4c0.1-0.1,0.2-0.2,0.2-0.3C70.2,75.4,71,74.5,71.4,73.5L71.4,73.5z"/> + <path class="st3" d="M61.6,87.3c4.2,9,7.8,19.1,11.1,30.7h4.7c3-10.9,6.4-20.5,10.2-29.1c-3.1-1.4-5.5-4.1-6.8-7.6 + c-2.1,0.9-4.3,1.2-6.6,1c-1.9-0.2-3.6-0.7-4.9-1.4C68.1,82.4,65.1,85.6,61.6,87.3L61.6,87.3z"/> + <path class="st3" d="M54.6,81.6c-0.1,0-0.1,0-0.2,0c-0.4,0-0.8,0.2-0.9,0.5c-0.2,0.7,0.2,1.3,0.5,1.7c0.8,1,2.2,1.7,3.3,1.7 + c0.1,0,0.2,0,0.3,0c-0.2-0.5-0.5-1-0.8-1.5C56.2,82.9,55.5,81.8,54.6,81.6L54.6,81.6z"/> + <path class="st3" d="M94.5,82.5L94.5,82.5c-0.1,0-0.4,0.1-1.2,1.3l-0.1,0.1c-0.5,0.9-0.9,1.8-1.4,2.8c1-0.1,1.9-0.5,2.4-1 + c0.5-0.5,0.7-1.1,0.6-1.9C94.8,82.9,94.6,82.6,94.5,82.5z"/> + <path class="st3" d="M74,27.9c-23,0-41.7,18.7-41.6,41.8c0,16.8,10,31.2,24.3,37.8v8.1c0,1.4,1.1,2.5,2.5,2.5h10.4 + c-3.2-11.3-6.8-21.1-10.8-29.8c-2.3,0.5-4.4-0.1-6.2-1.7c-1.2-1.1-2.3-2.9-2.2-4.7c0.1-1.1,0.7-2,1.6-2.6 + c0.7-0.4,1.4-0.7,2.2-0.7c2.3,0,4.1,2.1,5,3.9c0.3,0.7,0.7,1.3,1,2c2.4-1.1,4.8-3.1,6.8-5.6c-0.6-0.8-0.9-1.7-1.1-2.8 + c-0.2-1.8,0.6-4.4,2-6c1.5-1.7,3.3-2,5-0.8c0.9,0.7,1.5,1.5,1.6,2.5c0.3,2.3-1.7,4.7-3.1,6.4c-0.1,0.1-0.1,0.2-0.2,0.2 + c0.4,0.2,0.8,0.3,1.3,0.4c2.5,0.5,4.8,0.5,6.4-0.2c0.3-0.1,0.6-0.2,0.9-0.4c-0.1-0.6-0.2-1.2-0.3-1.8c-0.2-2.6,0.5-4.5,2.2-5.9 + c0.7-0.6,1.6-0.9,2.5-0.9c0.9,0,1.6,0.3,2.2,0.9c1,1,1.4,2.2,1.3,3.4c-0.2,2.5-2.5,4.4-3.5,5.1c-0.3,0.2-0.6,0.4-0.9,0.6 + c0.8,2.1,2.3,5.2,5.3,6.4c0.6-1.3,1.2-2.6,1.9-3.8c0-0.1,0.1-0.2,0.2-0.2c0.5-0.8,2-2.7,3.7-2.7c0.6,0,1.2,0.2,1.8,0.7 + c1.7,1.5,1.8,4,1.1,5.8c-0.8,2.3-3.1,3.6-6,3.7c-0.3,0-0.6,0-0.9,0c-3.8,8.3-7.1,17.6-10.1,28.3h8.7c1.4,0,2.5-1.1,2.5-2.5v-8.1 + c14.3-6.6,24.2-21.1,24.2-37.9C115.8,46.4,97.1,27.8,74,27.9L74,27.9z"/> + </g> + </g> +</g> +</svg> diff --git a/docs/assets/search.svg b/docs/assets/search.svg new file mode 100644 index 00000000000..9895c46a2d2 --- /dev/null +++ b/docs/assets/search.svg @@ -0,0 +1,173 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + viewBox="0 0 249.1 281.9" style="enable-background:new 0 0 249.1 281.9;" xml:space="preserve"> +<style type="text/css"> + .st0{fill:#434041;} + .st1{fill:#9FD3D7;} + .st2{opacity:0.5;fill:#FFFFFF;enable-background:new ;} + .st3{opacity:0.5;} + .st4{fill:#FFFFFF;} + .st5{opacity:0.8;} + .st6{fill:#737277;} + .st7{fill:#F19800;} + .st8{fill:#525154;} +</style> +<g id="Paquete_de_vectores:_motivo_de_puntos_15"> +</g> +<g id="Paquete_de_vectores:_motivo_de_puntos_15_1_"> +</g> +<g id="Paquete_de_vectores:_motivo_de_puntos_03"> +</g> +<rect x="147" y="154.5" transform="matrix(0.7762 -0.6305 0.6305 0.7762 -73.2279 136.4001)" width="17" height="33.6"/> +<path class="st0" d="M20.7,151.7c32.4,39.9,91.1,45.9,130.9,13.6c39.9-32.4,45.9-91.1,13.6-130.9C132.9-5.5,74.1-11.5,34.3,20.8 + S-11.6,111.9,20.7,151.7z M30.4,143.9C2.3,109.4,7.6,58.5,42.1,30.5C76.6,2.4,127.5,7.7,155.5,42.2c28.1,34.5,22.8,85.4-11.7,113.4 + C109.3,183.7,58.4,178.4,30.4,143.9z"/> +<g> + <path class="st1" d="M30.4,143.8c28,34.5,78.9,39.7,113.4,11.7s39.7-78.9,11.7-113.4S76.6,2.4,42.1,30.4S2.3,109.3,30.4,143.8z"/> + <g> + <path class="st2" d="M49,37.1c24.5-19.9,52.5-26.1,62.4-13.9c10,12.2-1.9,38.3-26.4,58.2s-52.5,26.1-62.4,13.9 + C12.6,83.1,24.5,57,49,37.1z"/> + <g class="st3"> + <g> + <g> + <path class="st4" d="M68.6,152.2c7.4,3.7,15.6,5.9,23.8,6.6c16.4,1.4,33.2-3.8,45.9-14.2c12.8-10.2,21.3-25.6,23.3-42 + c1-8.2,0.5-16.6-1.6-24.7c-2-8.1-5.8-15.8-10.7-22.7c5.8,6.3,10.2,13.9,13,22.1s3.8,17,3.1,25.7c-0.7,8.7-3.2,17.3-7.3,25.1 + c-4.2,7.8-9.9,14.7-16.7,20.3c-6.8,5.5-14.7,9.7-23.2,12.1c-8.4,2.4-17.3,3.1-26,2s-17.1-3.9-24.6-8.3 + c-7.4-4.4-13.9-10.3-18.9-17.2C54.4,143.2,61.2,148.5,68.6,152.2z"/> + </g> + </g> + <g> + <g> + <path class="st4" d="M78.7,144.5c5.9,2.9,12.3,4.6,18.8,5.1c12.9,1,26-3.1,35.9-11.2c10-8.1,16.6-20,18.3-32.9 + c0.9-6.4,0.5-13-1.1-19.4c-1.5-6.4-4.4-12.5-8.2-18c4.7,4.9,8.2,10.9,10.5,17.4c2.2,6.5,3.2,13.5,2.6,20.4 + c-0.5,6.9-2.6,13.8-5.8,20c-3.2,6.1-7.8,11.7-13.2,16.1c-5.5,4.4-11.8,7.8-18.6,9.7c-6.8,1.8-13.9,2.4-20.8,1.5 + s-13.6-3.2-19.5-6.7c-5.8-3.5-11-8.3-14.8-13.8C67.4,137.6,72.8,141.7,78.7,144.5z"/> + </g> + </g> + </g> + <g class="st5"> + <path class="st4" d="M101.1,30.7c6.5-5.2,16-4.3,21.2,2.2s4.3,16-2.2,21.2c-6.5,5.2-16,4.3-21.2-2.2 + C93.6,45.5,94.6,36,101.1,30.7z"/> + <path class="st4" d="M134.5,42.2c2.9-2.3,7.1-1.9,9.4,1s1.9,7.1-1,9.4s-7.1,1.9-9.4-1C131.2,48.7,131.6,44.6,134.5,42.2z"/> + </g> + </g> +</g> +<g> + <path class="st0" d="M153.3,188.4c-3-4.1-3.6-8.6-1.2-10.2c3.9-2.6,7.5-5.6,10.9-8.9c2-2,6.3-0.5,9.7,3.3 + c24.5,27.6,49,55.3,73.6,83c3.4,3.9,3.9,9.5,1,12.3c-4.9,4.7-10.3,9.1-15.9,13c-3.4,2.4-8.8,0.7-11.8-3.4 + C197.4,247.8,175.3,218.1,153.3,188.4z"/> + <path class="st6" d="M165.9,179.3c-0.9-1.1-0.8-2.7,0.3-3.6l0,0c1.1-0.9,2.7-0.8,3.6,0.3l72.5,82.8c0.9,1.1,0.8,2.7-0.3,3.6l0,0 + c-1.1,0.9-2.7,0.8-3.6-0.3L165.9,179.3z"/> +</g> +<rect x="-904.2" y="-25.8" class="st7" width="425.2" height="425.2"/> +<g> + <rect x="-733.1" y="184.5" transform="matrix(0.6305 -0.7762 0.7762 0.6305 -414.7179 -476.2191)" width="51.1" height="25.9"/> + <path class="st0" d="M-577.4,37.3C-617.2,5-675.9,11-708.3,50.9c-32.3,39.8-26.3,98.5,13.6,130.9c39.8,32.3,98.5,26.3,130.9-13.6 + C-531.5,128.4-537.6,69.7-577.4,37.3z M-686.9,172.2c-34.5-28-39.8-78.9-11.7-113.4c28-34.5,78.9-39.8,113.4-11.7 + c34.5,28,39.8,78.9,11.7,113.4C-601.5,194.9-652.4,200.2-686.9,172.2z"/> + <g> + <path class="st1" d="M-585.2,46.9c-34.5-28-85.4-22.8-113.4,11.7S-721.4,144-686.9,172s85.4,22.8,113.4-11.7 + C-545.4,125.9-550.7,75-585.2,46.9z"/> + <g> + <path class="st2" d="M-565.7,111.8c-9.9,12.2-37.9,6-62.4-13.9s-36.4-46-26.4-58.2c9.9-12.2,37.9-6,62.4,13.9 + C-567.6,73.5-555.7,99.6-565.7,111.8z"/> + <g class="st3"> + <g> + <g> + <path class="st4" d="M-591.8,153.5c-5,6.9-11.5,12.8-18.9,17.2c-7.5,4.4-15.9,7.2-24.6,8.3s-17.6,0.4-26-2 + c-8.5-2.4-16.4-6.6-23.2-12.1c-6.8-5.6-12.5-12.5-16.7-20.3c-4.1-7.8-6.6-16.4-7.3-25.1c-0.7-8.7,0.3-17.5,3.1-25.7 + s7.2-15.8,13-22.1c-4.9,6.9-8.7,14.6-10.7,22.7c-2.1,8.1-2.6,16.5-1.6,24.7c2,16.4,10.5,31.8,23.3,42 + c12.7,10.4,29.5,15.6,45.9,14.2c8.2-0.7,16.4-2.9,23.8-6.6C-604.3,165-597.5,159.8-591.8,153.5z"/> + </g> + </g> + <g> + <g> + <path class="st4" d="M-605.9,149.3c-3.8,5.5-9,10.3-14.8,13.8c-5.9,3.5-12.6,5.8-19.5,6.7c-6.9,0.9-14,0.3-20.8-1.5 + c-6.8-1.9-13.1-5.3-18.6-9.7c-5.4-4.4-10-10-13.2-16.1c-3.2-6.2-5.3-13.1-5.8-20c-0.6-6.9,0.4-13.9,2.6-20.4 + c2.3-6.5,5.8-12.5,10.5-17.4c-3.8,5.5-6.7,11.6-8.2,18c-1.6,6.4-2,13-1.1,19.4c1.7,12.9,8.3,24.8,18.3,32.9 + c9.9,8.1,23,12.2,35.9,11.2c6.5-0.5,12.9-2.2,18.8-5.1C-615.9,158.2-610.5,154.2-605.9,149.3z"/> + </g> + </g> + </g> + <g class="st5"> + <path class="st4" d="M-642,68.5c-5.2,6.5-14.7,7.4-21.2,2.2c-6.5-5.2-7.4-14.7-2.2-21.2s14.7-7.4,21.2-2.2 + C-637.7,52.5-636.7,62-642,68.5z"/> + <path class="st4" d="M-676.6,68.1c-2.3,2.9-6.5,3.3-9.4,1s-3.3-6.5-1-9.4s6.5-3.3,9.4-1C-674.7,61.1-674.3,65.3-676.6,68.1z"/> + </g> + </g> + </g> + <g> + <path class="st0" d="M-804.8,358.8c-4.6,6.3-12.8,8.8-18,5.2c-8.6-5.9-16.7-12.5-24.2-19.7c-4.5-4.3-3.7-12.8,1.5-18.7 + c37.3-42.1,74.6-84.2,111.9-126.2c5.2-5.8,11.7-8,14.8-5c5.2,5,10.7,9.5,16.6,13.5c3.5,2.4,2.7,9.2-1.9,15.5 + C-737.7,268.6-771.2,313.7-804.8,358.8z"/> + <path class="st6" d="M-833.5,335.5c-1.4,1.6-3.9,1.8-5.5,0.4l0,0c-1.6-1.4-1.8-3.9-0.4-5.5l110.1-125.8c1.4-1.6,3.9-1.8,5.5-0.4 + l0,0c1.6,1.4,1.8,3.9,0.4,5.5L-833.5,335.5z"/> + </g> +</g> +<g> + <g> + <g> + <path class="st8" d="M-700.3,292.9h6.2v2.2c0,2.1,0.3,3.5,0.8,4.2s1.4,1.1,2.7,1.1s2.3-0.3,3-1s1-1.7,1-3c0-1-0.2-1.9-0.7-2.6 + s-1.5-1.6-3.1-2.8l-3.2-2.3c-2.8-1.9-4.5-3.5-5.3-4.7c-0.7-1.2-1.1-2.8-1.1-4.7c0-2.8,0.8-5,2.5-6.6s4-2.4,6.9-2.4 + c3.1,0,5.5,0.8,7.1,2.5c1.7,1.7,2.5,4.1,2.5,7.3c0,0.3,0,0.6,0,0.7c0,0.2,0,0.3,0,0.5h-5.9v-0.9c0-1.8-0.3-3.1-0.9-3.9 + c-0.6-0.8-1.6-1.2-2.9-1.2c-1,0-1.8,0.3-2.4,0.9c-0.6,0.6-0.9,1.4-0.9,2.4c0,1.4,1.3,3,3.8,4.7h0.1l3.4,2.3 + c2.4,1.6,4.1,3.2,5,4.6s1.3,3.3,1.3,5.5c0,3-0.9,5.4-2.6,7c-1.7,1.7-4.2,2.5-7.5,2.5c-3.4,0-5.9-0.9-7.5-2.6s-2.5-4.4-2.5-8 + c0-0.4,0-1,0.1-1.9v0.2H-700.3z"/> + <path class="st8" d="M-675.2,304.6v-33.5h17.2v5.6h-11v7.7h10.2v5.3H-669v9.2h11.3v5.7L-675.2,304.6L-675.2,304.6z"/> + <path class="st8" d="M-655.6,304.6l7.5-33.5h8.4l7.6,33.5h-6.7l-1.7-8.4h-7.2l-1.6,8.4H-655.6z M-646.7,291.2h5.3l-2.7-14 + L-646.7,291.2z"/> + <path class="st8" d="M-628.8,304.6v-33.5h11.2c3.4,0,5.8,0.7,7.3,2.1s2.2,3.6,2.2,6.7c0,2.2-0.4,3.9-1.2,5.1 + c-0.8,1.3-2,2-3.5,2.2c3,0.8,4.5,3.3,4.6,7.5c0,0.2,0,0.3,0,0.4l0.2,4.4c0.1,1.3,0.2,2.3,0.5,3c0.3,0.8,0.7,1.4,1.2,2h-7.4 + c-0.2-0.6-0.3-1.2-0.4-2c-0.1-0.8-0.1-1.8-0.2-3.2l-0.1-3.5v-0.9c0-1.9-0.4-3.3-1.2-4s-2.5-1.1-5-1.1h-2.1v14.7L-628.8,304.6 + L-628.8,304.6z M-622.8,285.3h3.6c1.8,0,3.1-0.4,3.8-1.1c0.7-0.7,1.1-2,1.1-3.9c0-1.7-0.4-2.8-1.2-3.5s-2.2-1-4.2-1h-3.1 + L-622.8,285.3L-622.8,285.3z"/> + <path class="st8" d="M-589.8,292.7h6.1c0,0.1,0,0.4,0,0.6c0,0.3,0,0.5,0,0.7c0,3.9-0.8,6.7-2.4,8.6c-1.6,1.9-4.1,2.8-7.5,2.8 + c-1.6,0-3-0.2-4.1-0.6c-1.1-0.4-2.1-1.1-3-1.9c-1.1-1.2-1.9-2.8-2.4-4.8s-0.7-5.6-0.7-10.6c0-3.4,0.1-6.2,0.4-8.2 + s0.7-3.6,1.3-4.6c0.9-1.5,2-2.6,3.3-3.3c1.3-0.7,3-1,5.1-1c3.4,0,5.9,0.9,7.4,2.7c1.5,1.8,2.3,4.6,2.3,8.5h-6v-0.9 + c0-1.8-0.3-3.2-0.9-4.1c-0.6-0.9-1.5-1.3-2.7-1.3c-1.5,0-2.5,0.6-3.1,1.8c-0.5,1.2-0.8,4.6-0.8,10.2c0,6,0.3,9.7,0.8,11 + s1.5,2,3,2c1.4,0,2.4-0.5,2.9-1.5c0.6-1,0.8-2.9,0.8-5.7v-0.4H-589.8z"/> + <path class="st8" d="M-578.7,304.6v-33.5h6.2v13.1h8.5v-13.1h6.2v33.5h-6.2v-15.2h-8.5v15.2H-578.7z"/> + </g> + </g> + <g> + <g> + <path class="st8" d="M-694.6,320.4l-2.4-10.8h2.2l1.6,8.7l1.5-8.7h2.1l-2.3,10.8H-694.6z"/> + <path class="st8" d="M-684.8,320.4v-10.8h5.6v1.8h-3.6v2.5h3.3v1.7h-3.3v3h3.6v1.8H-684.8z"/> + <path class="st8" d="M-669.8,316.6h2c0,0,0,0.1,0,0.2s0,0.2,0,0.2c0,1.3-0.3,2.2-0.8,2.8c-0.5,0.6-1.3,0.9-2.4,0.9 + c-0.5,0-1-0.1-1.3-0.2c-0.4-0.1-0.7-0.3-1-0.6c-0.4-0.4-0.6-0.9-0.8-1.5c-0.2-0.7-0.2-1.8-0.2-3.4c0-1.1,0-2,0.1-2.7 + s0.2-1.2,0.4-1.5c0.3-0.5,0.7-0.9,1.1-1.1c0.4-0.2,1-0.3,1.7-0.3c1.1,0,1.9,0.3,2.4,0.9s0.8,1.5,0.8,2.8h-2v-0.4 + c0-0.6-0.1-1-0.3-1.3s-0.5-0.4-0.9-0.4c-0.5,0-0.8,0.2-1,0.6s-0.3,1.5-0.3,3.3c0,2,0.1,3.1,0.3,3.6c0.2,0.4,0.5,0.6,1,0.6 + s0.8-0.2,0.9-0.5c0.2-0.3,0.3-0.9,0.3-1.8V316.6z"/> + <path class="st8" d="M-661.1,320.4v-9.1h-2.3v-1.8h6.7v1.8h-2.3v9.1H-661.1z"/> + <path class="st8" d="M-652.3,315c0-1.1,0.1-2.1,0.2-2.8c0.1-0.7,0.3-1.2,0.5-1.6c0.3-0.5,0.7-0.8,1.1-1.1c0.4-0.2,1-0.3,1.7-0.3 + s1.3,0.1,1.7,0.3c0.4,0.2,0.8,0.6,1.1,1.1c0.2,0.4,0.4,0.9,0.5,1.6s0.2,1.6,0.2,2.7s-0.1,2.1-0.2,2.7s-0.3,1.2-0.5,1.6 + c-0.3,0.5-0.7,0.8-1.1,1.1c-0.4,0.2-1,0.3-1.7,0.3s-1.3-0.1-1.7-0.3c-0.4-0.2-0.8-0.6-1.1-1.1c-0.2-0.3-0.4-0.9-0.5-1.6 + C-652.2,317.1-652.3,316.2-652.3,315z M-650.2,315c0,1.8,0.1,2.9,0.3,3.4s0.6,0.7,1.2,0.7c0.6,0,0.9-0.2,1.1-0.7 + c0.2-0.5,0.3-1.6,0.3-3.4s-0.1-2.9-0.3-3.4s-0.6-0.7-1.1-0.7c-0.6,0-0.9,0.2-1.1,0.7C-650.1,312.1-650.2,313.2-650.2,315z"/> + <path class="st8" d="M-640,320.4v-10.8h3.6c1.1,0,1.9,0.2,2.4,0.7c0.5,0.4,0.7,1.2,0.7,2.2c0,0.7-0.1,1.3-0.4,1.7 + c-0.3,0.4-0.6,0.6-1.1,0.7c1,0.3,1.5,1.1,1.5,2.4c0,0.1,0,0.1,0,0.1l0.1,1.4c0,0.4,0.1,0.7,0.2,1c0.1,0.2,0.2,0.5,0.4,0.6h-2.4 + c-0.1-0.2-0.1-0.4-0.1-0.6s0-0.6-0.1-1v-1.1v-0.3c0-0.6-0.1-1.1-0.4-1.3c-0.3-0.2-0.8-0.4-1.6-0.4h-0.7v4.8h-2.1V320.4z + M-638,314.2h1.2c0.6,0,1-0.1,1.2-0.4c0.2-0.2,0.3-0.7,0.3-1.3c0-0.5-0.1-0.9-0.4-1.1c-0.2-0.2-0.7-0.3-1.4-0.3h-1v3.1H-638z"/> + <path class="st8" d="M-620.8,320.4v-10.8h3.2c0.7,0,1.2,0.1,1.6,0.2c0.4,0.1,0.7,0.3,1,0.5c0.4,0.4,0.6,0.8,0.8,1.4 + c0.2,0.6,0.2,1.4,0.2,2.6c0,1.5-0.1,2.6-0.2,3.3c-0.1,0.6-0.3,1.2-0.5,1.6s-0.6,0.8-1,0.9c-0.4,0.2-1.2,0.3-2.3,0.3H-620.8z + M-618.9,318.8h0.9c0.5,0,0.8,0,1-0.1s0.3-0.2,0.4-0.3c0.2-0.3,0.3-0.7,0.4-1.2c0.1-0.5,0.1-1.3,0.1-2.4s0-1.8-0.1-2.3 + c-0.1-0.5-0.3-0.8-0.5-1.1c-0.1-0.1-0.3-0.2-0.5-0.3c-0.2,0-0.5-0.1-0.9-0.1h-0.9L-618.9,318.8L-618.9,318.8z"/> + <path class="st8" d="M-608.8,320.4v-10.8h5.6v1.8h-3.6v2.5h3.3v1.7h-3.3v3h3.6v1.8H-608.8z"/> + <path class="st8" d="M-598.2,316.6h2v0.7c0,0.7,0.1,1.1,0.2,1.4c0.2,0.2,0.5,0.4,0.9,0.4c0.4,0,0.8-0.1,1-0.3 + c0.2-0.2,0.3-0.5,0.3-1c0-0.3-0.1-0.6-0.2-0.8c-0.2-0.2-0.5-0.5-1-0.9l-1-0.7c-0.9-0.6-1.5-1.1-1.7-1.5s-0.4-0.9-0.4-1.5 + c0-0.9,0.3-1.6,0.8-2.1s1.3-0.8,2.2-0.8c1,0,1.8,0.3,2.3,0.8s0.8,1.3,0.8,2.4c0,0.1,0,0.2,0,0.2c0,0.1,0,0.1,0,0.1h-1.9v-0.3 + c0-0.6-0.1-1-0.3-1.3s-0.5-0.4-1-0.4c-0.3,0-0.6,0.1-0.8,0.3c-0.2,0.2-0.3,0.5-0.3,0.8c0,0.5,0.4,1,1.2,1.5l0,0l1.1,0.8 + c0.8,0.5,1.3,1,1.6,1.5s0.4,1.1,0.4,1.8c0,1-0.3,1.7-0.8,2.3c-0.6,0.5-1.4,0.8-2.4,0.8c-1.1,0-1.9-0.3-2.4-0.8 + c-0.5-0.6-0.8-1.4-0.8-2.6C-598.3,317.1-598.2,316.9-598.2,316.6L-598.2,316.6z"/> + <path class="st8" d="M-586.5,320.4v-10.8h2v10.8H-586.5z"/> + <path class="st8" d="M-573.7,320.4l-0.1-1.2c-0.2,0.5-0.5,0.9-0.8,1.1c-0.4,0.2-0.9,0.3-1.5,0.3c-1.1,0-1.9-0.4-2.4-1.3 + s-0.7-2.4-0.7-4.7c0-1.1,0.1-1.9,0.2-2.6c0.1-0.6,0.3-1.1,0.5-1.5c0.3-0.4,0.7-0.8,1.1-1c0.5-0.2,1-0.3,1.7-0.3 + c1.2,0,2,0.3,2.6,0.8c0.6,0.5,0.8,1.4,0.8,2.6v0.3h-1.9v-0.1c0-0.6-0.1-1.1-0.4-1.5c-0.2-0.3-0.6-0.5-1.1-0.5 + c-0.6,0-1,0.2-1.2,0.7s-0.3,1.6-0.3,3.6c0,1.5,0.1,2.6,0.3,3.1c0.2,0.5,0.6,0.8,1.1,0.8s0.9-0.2,1.1-0.5c0.2-0.4,0.3-1,0.3-1.9 + v-0.4h-1.4v-1.7h3.3v5.9L-573.7,320.4L-573.7,320.4z"/> + <path class="st8" d="M-567,320.4v-10.8h2.1l2.8,7.6l-0.1-7.6h1.9v10.8h-2.1l-2.8-7.6l0.1,7.6H-567z"/> + </g> + </g> +</g> +</svg> diff --git a/docs/css/highlightjs.min.css b/docs/css/highlightjs.min.css deleted file mode 100644 index e41d2e34833..00000000000 --- a/docs/css/highlightjs.min.css +++ /dev/null @@ -1,2 +0,0 @@ -/* Source: https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.7.2/styles/default.min.css */ -.hljs{display:block;overflow-x:auto;padding:.5em;background:#f0f0f0}.hljs,.hljs-subst{color:#444}.hljs-comment{color:#888}.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-meta-keyword,.hljs-name,.hljs-selector-tag{font-weight:700}.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type{color:#800}.hljs-section,.hljs-title{color:#800;font-weight:700}.hljs-link,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#bc6060}.hljs-literal{color:#78a960}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-code{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta-string{color:#4d99bf}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700} \ No newline at end of file diff --git a/docs/css/styles.css b/docs/css/styles.css index ceda06f36e9..22ae63678e0 100644 --- a/docs/css/styles.css +++ b/docs/css/styles.css @@ -1,19 +1,114 @@ +:root { + --font-color: #f7f7f7; + --bg-color: #1e1e1e; + + --search-color: white; + --search-bg-color: rgb(15, 15, 15); + --search-ver-bg-color: rgb(51, 51, 51); + + --table-row-color: rgba(38, 38, 38, 0.3); + --table-row2-color: rgba(0, 0, 0, 0.3); + --table-side-color: rgba(114, 114, 114, 0.1); + --table-side2-color: rgba(116, 116, 116, 0.05); + --table-desc-color: rgba(0, 0, 0, 0.45); + + --code-font-color: white; + --code-bg-color: rgba(99, 99, 99, 0.2); + + --sidebar-items-bg: rgba(0, 0, 0, .2); + + --element-bottom-border: rgba(255, 255, 255, .2); + + --link-color: #ffad32; + --link-hover-color: #dd8500; + --link-visited-color: #ff9800; + + --table-shadow: 0 0 20px rgba(0, 0, 0, .4); + + --box-font-color: black; + --box-inner-font-color: white; + --box-bg: rgba(0, 0, 0, 0.4) !important; + --red-box-font-color: #1e1e1e; + --red-box-bg: rgba(0, 0, 0, 0.4); + + --syntax-item-shadow: 0px 0px 15px rgba(0, 0, 0, .5); + + --wrapper-bg-color: transparent; + + --scrollbar-track-bg-color: rgba(255, 255, 255, 0.2); + --scrollbar-thumb-bg-color: rgba(255, 255, 255, 0.4); + + --inline-code-bg-color: #3e3e3e9e; +} + +[data-theme="white"] { + --font-color: black; + --font-secondary-color: rgba(0, 0, 0, .65); + --bg-color: #f5f5f5; + --table-color: white; + + --search-color: black; + --search-bg-color: white; + --search-ver-bg-color: rgba(253, 253, 253, 0.7); + + --table-row-color: rgba(0, 0, 0, 0.02); + --table-row2-color: rgba(0, 0, 0, 0.05); + --table-side-color: rgb(224 224 224 / 60%); + --table-side2-color: rgb(202 202 202 / 45%); + --table-desc-color: rgb(211 211 211 / 40%); + + --code-font-color: white; + --code-bg-color: rgb(41, 41, 41); + + --sidebar-items-bg: rgba(0, 0, 0, 0.07); + + --element-bottom-border: rgba(0, 0, 0, 0.1); + + --link-color: #bb7000; + --link-hover-color: #dd8500; + --link-visited-color: #ff9800; + + --table-shadow: 0 0 20px rgba(0, 0, 0, 0.15); + + --box-font-color: black; + --box-inner-font-color: white; + --box-bg: rgb(41, 41, 41) !important; + --red-box-font-color: white; + --red-box-bg: rgba(0, 0, 0, 0.1); + + --syntax-item-shadow: 0px 0px 15px rgba(0, 0, 0, .2); + + --wrapper-bg-color: white; + + --scrollbar-track-bg-color: rgba(0, 0, 0, 0.03); + --scrollbar-thumb-bg-color: rgba(48, 48, 51, 0.4); + + --inline-code-bg-color: #a9a9a99e; + + /* transition: all 1s; */ +} + +::selection { + background-color: #ff9800; + color: black; +} + html { scroll-behavior: smooth; } ::-webkit-scrollbar { - width: 5px; - height: 5px; + width: 6px; + height: 6px; } ::-webkit-scrollbar-track { - background-color: rgba(0, 0, 0, 0.03); + background-color: var(--scrollbar-track-bg-color); border-radius: 10px; } ::-webkit-scrollbar-thumb { - background-color: rgba(48, 48, 51, 0.2); + background-color: var(--scrollbar-thumb-bg-color); border-radius: 10px; } @@ -24,7 +119,7 @@ html { body { font-family: "Poppins", sans-serif; - background-color: oldlace; + background-color: var(--bg-color); display: grid; /* grid-template-rows: 100vh 100vh; @@ -40,28 +135,139 @@ body { } a { - color: darkcyan; + color: var(--link-color); transition: all 0.1s; text-decoration: none; } a:hover { - color: rgb(0, 184, 184); + color: var(--link-hover-color); } a:visited { - color: rgb(37, 89, 142); + color: var(--link-visited-color); +} + +.item-wrapper { + box-shadow: var(--syntax-item-shadow); + border-left: 5px solid rgb(255, 152, 0); + margin: 35px 0; + background-color: var(--wrapper-bg-color) +} + +.type-Event { + border-left: 5px solid rgb(28, 181, 152); +} + +.type-Event .item-type { + color: rgb(28, 181, 152); +} + +.type-Event .item-examples p { + background-color: rgb(28, 181, 152); +} + +.type-Effect { + border-left: 5px solid rgb(139, 255, 0); +} + +.type-Effect .item-type { + color: rgb(139, 255, 0); +} + +.type-Effect .item-examples p { + background-color: rgb(139, 255, 0); +} + +.type-Expression { + border-left: 5px solid rgb(255, 152, 0); +} + +.type-Expression .item-type { + color: rgb(255, 152, 0); +} + +.type-Expression .item-examples p { + background-color: rgb(255, 152, 0); +} + +.type-Condition { + border-left: 5px solid rgb(181, 28, 28); +} + +.type-Condition .item-type { + color: rgb(181, 28, 28); +} + +.type-Condition .item-examples p { + background-color: rgb(181, 28, 28); + color: white; +} + +.type-Section { + border-left: 5px solid rgb(181, 28, 121); +} + +.type-Section .item-type { + color: rgb(181, 28, 121); +} + +.type-Section .item-examples p { + background-color: rgb(181, 28, 121); + color: white; +} + +.type-EffectSection { + border-left: 5px solid rgb(199, 0, 255); +} + +.type-EffectSection .item-type { + color: rgb(199, 0, 255); +} + +.type-EffectSection .item-examples p { + background-color: rgb(199, 0, 255); + color: white; +} + +.type-Type { + border-left: 5px solid rgb(0, 100, 255); +} + +.type-Type .item-type { + color: rgb(0, 100, 255); +} + +.type-Type .item-examples p { + background-color: rgb(0, 100, 255); + color: white; +} + +.type-Function { + border-left: 5px solid rgb(128, 0, 255); } +.type-Function .item-type { + color: rgb(128, 0, 255); +} + +.type-Function .item-examples p { + background-color: rgb(128, 0, 255); + color: white; +} /* Pattern right section list items */ .item-details:nth-child(1) ul li:nth-child(odd) { - background-color: rgba(0, 0, 0, 0.1); + background-color: var(--table-row-color); + color: var(--font-color); + border-left: 2px solid rgb(255, 152, 0); } .item-details:nth-child(1) ul li:nth-child(even) { - background-color: rgba(0, 0, 0, 0.05); + background-color: var(--table-row2-color); + color: var(--font-color); + border-left: 1px solid rgb(255, 152, 0); } .no-list-style { @@ -78,6 +284,7 @@ a:visited { padding: 15px; z-index: 100; bottom: 0px; + display: flex; } #global-navigation li { @@ -92,6 +299,7 @@ a:visited { transition: all 0.1s; } +/* Active tab for menu items */ #global-navigation a:hover, .active-tab { color: #ffc107; @@ -99,6 +307,36 @@ a:visited { box-shadow: inset 0 -3px 0 #ff9800; } +/* Active syntax */ +div.active-syntax { + border-left: 5px solid #ff4e4e; +} + +div.active-syntax .item-examples p { + background-color: #ff4e4e; + color: white; +} + +.item-examples p { + cursor: pointer; +} + +.example-details-closed:before { + content: '▶ '; +} + +.example-details-opened:before { + content: '▼ '; +} + +/* Active tab for sub menus */ +.menu-subtabs .active-tab { + color: #ffc107 !important; + /* border-left: 3px solid #ff9800; */ + background: #3a3a3a; + box-shadow: unset; +} + #side-nav { grid-row-start: 1; grid-row-end: 1; @@ -118,7 +356,7 @@ a:visited { border-left: solid 3px #ff9800; text-decoration: none; background-color: #353535; - font-size: 26px; + font-size: 1.5vw; font-weight: bold; color: #ff9800; height: 35px; @@ -129,16 +367,17 @@ a:visited { width: 100%; } -#nav-title { +/* #nav-title { font-size: clamp(1.25em, 1.35vw, 2em); -} +} */ #nav-title:hover { color: rgba(255, 152, 0, 0.9); } #nav-contents { - height: calc(100vh - 60px); + /* height: calc(100vh - 60px); */ + height: 93vh; /* Fix the TOP css of #side-nav */ width: 100%; overflow: scroll; @@ -146,12 +385,12 @@ a:visited { position: sticky; } -#nav-contents > a { +#nav-contents>a { display: block; font-size: 18px; - background-color: rgba(0, 0, 0, 0.03); + background-color: var(--sidebar-items-bg); text-decoration: none; - color: #2f3e46; + color: var(--font-color); margin: 6px 10px; word-wrap: break-word; padding: 5px 15px; @@ -165,21 +404,24 @@ a:visited { #nav-contents a:hover, .active-item { - color: black; + color: var(--font-color); background-color: rgba(0, 0, 0, 0.08); border-left: solid 3px #ff9800; + /* font-weight: bold; */ } #content { grid-row-start: 1; grid-row-end: 1; grid-column-start: 2; - margin-left: 30px; + padding-left: 30px; position: relative; - top: 110px; + /* top: 110px; */ + margin-top: 110px; height: calc(100vh - 110px); /* Fixed the TOP CSS 110px due to body having overflow hidden and top = 110px so the last 110px is hidden, this will fix it */ - padding: 0.22em; + /* padding: calc(0.22em + 30px); */ + /* When applied it will break the padding when an anchor is clicked */ padding-right: 30px; overflow-x: hidden; overflow-y: scroll; @@ -232,59 +474,90 @@ table { font-weight: bold; padding-bottom: 10px; padding-top: 0.5em; - padding-left: 0; - color: #261908; + padding-left: 10px; + color: var(--font-color); } -.item-title > a { +.item-title>a { text-decoration: none; font-size: 100%; } +.item-type { + font-size: 16px; + padding: 10px; + color: var(--font-color); + padding: 10px; + padding-right: 20px; + float: right; +} + +#search-icon { + position: fixed; + right: 1vw; + top: 60px; + cursor: pointer; +} + +.new-element { + background: red; + color: white; + font-size: 12px; + padding: 3px; + border-radius: 5px; + margin-left: 10px; + cursor: pointer; + display: inline; + position: relative; + top: -2px; +} + .item-content { - padding-bottom: 40px; - margin-bottom: 20px; - max-width: 80vw; - border-bottom: 1px solid rgba(0, 0, 0, 0.1); + padding-bottom: 25px; + border-bottom: 1px solid var(--element-bottom-border); + content-visibility: auto; } .item-table-label { background-color: #deb887; text-align: center; font-weight: bold; - color: rgba(0, 0, 0, 0.8); + color: var(--font-color); width: 8em; padding: 0.3em; } .item-content .item-details:nth-child(odd) td:nth-child(2) { - background-color: rgba(0, 0, 0, 0.1); + background-color: var(--table-row-color); + color: var(--font-color); } .item-content .item-details:nth-child(even) td:nth-child(2) { - background-color: rgba(0, 0, 0, 0.08); + background-color: var(--table-row2-color); + color: var(--font-color); } /* OVERRIDE */ .item-content .item-details:nth-child(1) td:nth-child(1) { - background-color: rgba(185, 183, 180, .6); + /* Patterns */ + background-color: var(--table-side-color); } -.item-content .item-details:nth-child(1) td:nth-child(2) { - background-color: unset; -} +/* .item-content .item-details:nth-child(1) td:nth-child(2) { + background-color: var(--table-side2-color); +} */ /* OVERRIDE */ .item-content .item-details:nth-child(odd) .item-table-label { - background-color: rgba(185, 183, 180, .6); + background-color: var(--table-side-color); } .item-content .item-details:nth-child(even) .item-table-label { - background-color: rgba(181, 180, 178, 0.45); + background-color: var(--table-side2-color); } .item-details { @@ -297,7 +570,7 @@ table { /* 1n+2 will choose all elements excpet the first */ padding: 8px; font-weight: 500; - color: rgba(0,0,0,.65); + color: var(--font-secondary-color); } .noleftpad { @@ -311,10 +584,11 @@ td ul { .item-description { padding: 15px; - background-color: rgba(217, 211, 204, 0.4); + background-color: var(--table-desc-color); + color: var(--font-color); } -.item-description > p { +.item-description>p { margin-top: 0.7em; } @@ -323,30 +597,37 @@ td ul { font-family: "Source Code Pro", monospace; } +.item-examples { + /* margin: 25px 10px 0 10px; */ + margin-top: 25px; +} + .item-examples p { background-color: rgb(255, 152, 0); - border-left: 3px solid rgba(255, 152, 0, 0.5); + /* border-left: 3px solid rgba(255, 152, 0, 0.5); */ padding: 5px; - color: rgba(0, 0, 0, 0.8); + color: var(--box-font-color); width: max-content; margin-top: 25px; } .item-examples .skript-code-block { - background-color: rgba(0, 0, 0, 0.08); + background-color: var(--code-bg-color); visibility: visible; font-family: "Source Code Pro", monospace; font-weight: 400; } -.skript-code-block > a { +.skript-code-block>a { text-decoration: none; } -.item-examples > .skript-code-block { - border-left: solid 3px #ff9800; +.item-examples>.skript-code-block { + /* border-left: solid 3px #ff9800; */ padding: 30px; font-size: 0.9em; + color: var(--code-font-color); + display: none; } .box-title { @@ -355,14 +636,15 @@ td ul { padding: 5px; padding-right: 7px; margin-top: 20px; - color: rgba(0, 0, 0, 0.8); + color: var(--box-font-color); } .box { border-left: 3px solid #ff9800; padding: 15px; - background-color: rgba(0, 0, 0, 0.1) !important; + background-color: var(--box-bg); margin-bottom: 10px; + color: var(--box-inner-font-color); } .box-title-red { @@ -371,117 +653,193 @@ td ul { padding: 5px; padding-right: 7px; margin-top: 20px; - color: white; + color: var(--red-box-font-color); font-weight: bold; } .box-red { border-left: 3px solid #ff4e4e; padding: 15px; - background-color: rgba(0, 0, 0, 0.1); + background-color: var(--red-box-bg); + color: var(--font-color); margin-bottom: 10px; } -@media (max-width: 768px) { + + +@media (max-width: 1200px) { body { grid-template-columns: 20% minmax(80%, 100%); } - #nav-contents { - margin-top: 100px; - } - - #content { - padding-top: 0px !important; - margin-top: 50px; - margin-left: 10px; - /* !important to override home */ + #global-navigation li { + white-space: nowrap; } #global-navigation { - height: calc(4.1em + 2px); - /* +2px due to 18px padding botton not 16px */ display: flex; - flex-wrap: wrap; } - #global-navigation > li { - padding-bottom: 18px; + #global-navigation { + grid-column-start: 1; + grid-column-end: none; } - #global-navigation > a { + #global-navigation>a { padding: 10px; padding-top: 0.1em; padding-bottom: 0.1em; } - #nav-contents > a { - font-size: 12px; + #nav-title { + display: none; } - .item-description { - font-size: 14px; + span .search-bar-version { + /* More specific to override the other */ + left: calc(20% + 150px) !important; } - .item-table-label { - width: 5em; + + input#search-bar { + left: 20%; } - #search-bar { - top: calc(4.1em + 24px) !important; + #search-version { + left: 20% !important; } } + @media (max-width: 1024px) { body { grid-template-columns: 20% minmax(80%, 100%); } - #global-navigation > a { + #global-navigation>a { padding: 10px; padding-top: 0.1em; padding-bottom: 0.1em; } } -@media (max-width: 1200px) { +@media (max-width: 768px) { body { grid-template-columns: 20% minmax(80%, 100%); } - #global-navigation li { - white-space: nowrap; - } - #global-navigation { display: flex; + flex-wrap: wrap; + } + + #nav-contents>a { + font-size: 12px; + } + + .item-description { + font-size: 14px; + } + + .item-table-label { + width: 5em; + } + + #cookies-bar { + flex-direction: column; + } +} + +@media (max-width: 550px) { + body { + grid-template-columns: 0 100%; } #global-navigation { - grid-column-start: 1; - grid-column-end: none; + /* +2px due to 18px padding botton not 16px */ + height: calc(4.1em + 2px); + } + + #nav-contents { + display: none; + } + + #content { + padding-top: 0px !important; + top: 50px; + margin-left: 10px; + height: calc(100vh - 160px); + /* !important to override home */ + } + + img#theme-switch { + width: 45px; + } + + #global-navigation>li { + padding-bottom: 18px; + } + + input#search-bar { + top: calc(4.1em + 24px) !important; + left: 0; + width: 100% !important; + } + + a#search-icon { + top: calc(4.1em + 38px); + right: 3vw; } - #global-navigation > a { + #search-bar-after { + top: calc(4.1em + 94px) !important; + } + + span .search-bar-version { + /* More specific to override the other */ + left: 150px !important; + } + + #search-version { + top: calc(4.1em + 24px) !important; + left: 0% !important; + } + + #global-navigation>a { padding: 10px; padding-top: 0.1em; padding-bottom: 0.1em; } - #nav-title { - display: none; + div.item-content { + max-width: 100vw; } - #search-bar { - left: 20% !important; + div.grid-container { + grid-template-columns: initial; + } + + .box.code { + height: 300px; + font-size: 14px; + } + + p.item-type { + display: none; } } .title { margin-top: 32px; + color: var(--font-color); } .subtitle { padding-left: 20px !important; + color: var(--font-color); +} + +p, h1, h2, h3, h4, h5 { + color: var(--font-color); } .left-margin { @@ -494,7 +852,8 @@ td ul { border-collapse: collapse; font-size: 0.9em; font-family: "Poppins", sans-serif; - box-shadow: 0 0 20px rgba(0, 0, 0, 0.15); + box-shadow: var(--table-shadow); + color: var(--font-color); /*border-radius: 5px;*/ } @@ -504,7 +863,7 @@ td ul { } .colors-table tbody tr { - border-bottom: 1px solid #dddddd; + border-bottom: 1px solid rgba(0, 0, 0, .1); } .colors-table tbody tr:nth-of-type(even) { @@ -532,44 +891,54 @@ ol.custom-list li::before { code { font-family: "Source Code Pro", monospace; + background-color: var(--inline-code-bg-color); + padding: 0px 5px; + border-radius: 5px; } pre { font-family: "Source Code Pro", monospace; } +pre code { /* override styling for code blocks */ + font-family: "Source Code Pro", monospace; + background-color: transparent; + padding: 0; + border-radius: 0; +} + #notification-box { - background-color: rgb(36, 36, 36); - border-radius: 15px; - box-shadow: 0px 3px 10px 0px rgba(0, 0, 0, .2); - text-align: center; - color: rgb(40, 236, 40); - font-weight: bold; - transition: all .2s; - /*transition-delay: .3s;*/ - opacity: 0; - position: fixed; - left: 50%; - top: 92%; - padding: 10px; - transform: translate(-50%, -50%); + background-color: rgb(36, 36, 36); + border-radius: 15px; + box-shadow: 0px 3px 10px 0px rgba(0, 0, 0, .2); + text-align: center; + color: rgb(40, 236, 40); + font-weight: bold; + transition: all .2s; + /*transition-delay: .3s;*/ + opacity: 0; + position: fixed; + left: 50%; + top: 92%; + padding: 10px; + transform: translate(-50%, -50%); } .activate-notification { - opacity: 1 !important; - transform: translateY(-20px); + opacity: 1 !important; + transform: translateY(-20px); } .grid-container { - width: 100%; - display: inline-grid; - grid-template-columns: calc(33% - 1.7%) calc(33% - 1.7%) calc(33% - 1.7%); - grid-column-gap: 3%; - overflow: hidden; + width: 100%; + display: inline-grid; + grid-template-columns: calc(33% - 1.7%) calc(33% - 1.7%) calc(33% - 1.7%); + grid-column-gap: 3%; + overflow: hidden; } .link { - display: inline-block; + display: inline-block; } .padding { @@ -592,26 +961,237 @@ pre { padding-top: 64px; } +#search-version { + padding: 12px; + border: none; + font-size: 18px; + font-weight: bold; + position: fixed; + background: var(--search-ver-bg-color); + width: 150px; + left: 16vw; + top: 55px; + box-shadow: 0 3px 5px 0 rgba(0, 0, 0, .05); + color: var(--search-color); + /* border-bottom: 2px solid rgba(255, 152, 0, 0.9); */ +} + #search-bar { padding: 12px; border: none; font-size: 18px; position: fixed; - background: white; - width: calc(100% - 5px); /* -5px for the scrollbar */ + background: var(--search-bg-color); + width: calc(84vw); left: 16vw; top: 55px; box-shadow: 0 3px 5px 0 rgba(0, 0, 0, .05); + color: var(--search-color); +} + +.search-bar-version { + left: calc(16vw + 150px) !important; } #search-bar:focus { outline: none; } +#search-bar-after { + padding: 3px; + font-size: 12px; + position: fixed; + background: var(--search-bg-color); + color: var(--search-color); + box-shadow: 0 3px 5px 0 rgba(0, 0, 0, .05); + right: 15px; + top: 100px; + text-align: right; +} + .pre { white-space: pre; } .pre-wrap { white-space: pre-wrap; +} + +#theme-switch { + position: fixed; + right: 40px; + bottom: 30px; + text-align: right; + display: inline-block; + width: 60px; +} + +#theme-switch:hover { + cursor: pointer; +} + +.link-icon { + margin-left: 5px; +} + + +.menu-tab { + color: white; + font-size: 16px; + border: none; +} + +.menu-subtabs { + display: none; + position: absolute; + top: 55px; + background-color: #292929; + min-width: 120px; + box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); + z-index: 9; + transition: all 3s ease-out; + opacity: 0; + transform: translateY(-10px); +} + +/* Links inside the dropdown */ +.menu-subtabs a { + color: white; + background: #292929; + padding: 12px 16px; + text-decoration: none; + display: block; +} + +/* Change color of dropdown links on hover */ +.menu-subtabs a:hover { + background-color: #3a3a3a; +} + +/* Show the dropdown menu on hover */ +.menu-tab:hover .menu-subtabs { + transition: all 3s ease-out; + display: block; + opacity: 1; + transform: translateY(0px); +} + +/* Keep the bottom border when hover over the sub-tabs */ +.menu-tab:hover .menu-tab-item { + box-shadow: inset 0 -3px 0 #ff9800; +} + +/* Hover over submenu items */ +#global-navigation .menu-subtabs a:hover { + box-shadow: unset; + /* border-left: 3px solid #ff9800; */ + background: rgba(0, 0, 0, .1); +} + +/* Syntax Highlighting */ +.sk-cond, +.sk-loop { + color: #a52e91; + font-weight: bold; +} + +.sk-eff { + color: rgb(255, 174, 0); +} + +.sk-expr { + color: rgb(255, 174, 25); +} + +.sk-event { + color: #c23838; +} + +.sk-command { + color: #c27715; +} + +.sk-arg-type { + color: #dbab28; +} + +.sk-loop-value { + color: #ff6600; +} + +.sk-false { + color: #b10000; + font-weight: bold; +} + +.sk-true { + color: #20b628; + font-weight: bold; +} + +.sk-operators { + font-weight: bold; +} + +.sk-var { + color: #e74040; +} + +.sk-string [class*="sk-"], +.sk-string { + /* More specific = higher priority to override other css in strings and comments */ + color: #34a52a !important; + font-weight: unset; +} + +.sk-comment, +.sk-comment [class*="sk-"] { + color: #696969 !important; + font-weight: unset; +} + +.sk-function { + color: #e873ff; + font-weight: bold; +} + +.sk-timespan { + color: #ff5967; + font-style: italic; +} + +.new-tab { + color: rgb(255, 91, 91) !important; + font-weight: 600; +} + +#cookies-bar { + position: sticky; + margin: 0 -8vw; + bottom: 0; + background-color: #353535; + color: white; + padding: 10px; + z-index: 100; + display: flex; + align-items: center; + justify-content: center; +} + +#cookies-accept { + padding: 5px 10px; + border: unset; + background-color: rgb(29, 167, 29); + margin: 0 5px; + cursor: pointer; + color: white; +} + +#cookies-deny { + padding: 5px 10px; + border: unset; + background-color: rgb(167, 29, 29); + margin: 0 5px; + cursor: pointer; + color: white; } \ No newline at end of file diff --git a/docs/docs.html b/docs/docs.html new file mode 100644 index 00000000000..f54970f30af --- /dev/null +++ b/docs/docs.html @@ -0,0 +1,5 @@ +<h1 id="nav-title">All Elements</h1> + +<div id="content"> + ${generate docs desc_full.html} +</div> diff --git a/docs/index.html b/docs/index.html index 1e91dfe9a03..16d3bfc923a 100644 --- a/docs/index.html +++ b/docs/index.html @@ -11,7 +11,7 @@ <h1 id="nav-title">Documentation</h1> prefer. </p> <p class="box-title">Quick Look</p> -<pre class="box code" style="height: 400px; overflow: scroll;"><code class="bash"> +<pre class="box code" style="height: 460px; overflow: auto;"><code class="bash"> command /sethome:<br/> permission: skript.home # Permission required for this command<br/> description: Set your home # Description of this command<br/> @@ -37,16 +37,16 @@ <h1 id="nav-title">Documentation</h1> <div id="info" class="grid-container padding"> <div class="grid-item"> - <p class="box-title">Latest Version</p> - <p class="box"><a class="link" href="https://github.com/SkriptLang/Skript/releases/tag/${latest-version}" target="_blank">${latest-version}</a></p> + <p class="box-title">Latest Stable Version</p> + <p class="box placeholder"><a class="link" href="https://github.com/SkriptLang/Skript/releases/tag/${stable-version}" target="_blank">Skript ${stable-version}</a></p> </div> <div class="grid-item"> - <p class="box-title">Download</p> - <p class="box"><a class="link" href="https://github.com/SkriptLang/Skript/releases" target="_blank">Skript ${latest-version}</a></p> + <p class="box-title">Latest Version</p> + <p class="box placeholder"><a class="link" href="https://github.com/SkriptLang/Skript/releases/tag/${latest-version}" target="_blank">Skript ${latest-version}</a></p> </div> <div class="grid-item"> <p class="box-title">Contributors</p> - <p class="box"><a class="link" href="https://github.com/SkriptLang/Skript/graphs/contributors" target="_blank">${contributors-size} Contributors</a></p> + <p class="box placeholder"><a class="link" href="https://github.com/SkriptLang/Skript/graphs/contributors" target="_blank">${contributors-size} Contributors</a></p> </div> </div> @@ -60,6 +60,6 @@ <h1 id="nav-title">Documentation</h1> <div style="padding-top: 64px;"></div> <!-- Space --> -<p style="font-size: 14px; text-align: center;">Skript • <a href="https://github.com/SkriptLang/Skript/">SkriptLang Team</a> • - Styling by <a href="https://github.com/AyhamAl-Ali">Ayham Al-Ali</a> | 2021 • Generated on ${skript.build.date}</p> +<p style="font-size: 14px; text-align: center;" class="placeholder"><a href="https://github.com/SkriptLang/Skript/">SkriptLang Team</a> • + Site developed by <a href="https://github.com/AyhamAl-Ali">Ayham Al-Ali</a> • Site Version <b>${site-version}</b> • Generated on <b>${skript.build.date}</b></p> <div style="padding-top: 16px;"></div> <!-- Space --> \ No newline at end of file diff --git a/docs/js/functions.js b/docs/js/functions.js new file mode 100644 index 00000000000..d6f785ee7d9 --- /dev/null +++ b/docs/js/functions.js @@ -0,0 +1,353 @@ +let isCookiesAccepted = getCookie("cookies-accepted") === "true"; + +// <> Cookies +function setCookie(cname, cvalue, exdays, force = false) { + if (!isCookiesAccepted && !force) + return; + + const d = new Date(); + d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000)); + let expires = "expires=" + d.toUTCString(); + document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/; SameSite=None; Secure"; +} + +function getCookie(cname) { + let name = cname + "="; + let ca = document.cookie.split(';'); + for (let i = 0; i < ca.length; i++) { + let c = ca[i]; + while (c.charAt(0) == ' ') { + c = c.substring(1); + } + if (c.indexOf(name) == 0) { + return c.substring(name.length, c.length); + } + } + return ""; +} + +// Cookies </> + +// <> localStorage + +/** + * Set the value of local storage item + * @param {string} item id + * @param {object} value + * @param {double} exdays time in days + */ +function setStorageItem(item, value, exdays, force = false) { + if (!isCookiesAccepted && !force) + return; + + const d = new Date(); + d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000)); + localStorage.setItem(item, value + "; " + d.getTime()); +} + +/** + * Remove a local storage item + * @param {string} item the id of the item + */ +function removeStorageItem(item) { + localStorage.removeItem(item) +} + +/** + * Get local storage item (value & time if has one) + * @param {string} item the item id + * @param {boolean} noExpireationCheck whether to check for expiration time and remove the item + * @returns the item object + */ +function getStorageItem(item, noExpireationCheck = false) { + let result = localStorage.getItem(item); + if (!result) + return null; + + if (!noExpireationCheck) { + if (isStorageItemExpired(result)) { + removeStorageItem(item); + return null; + } + } + result = result.split("; ")[0]; + return result; +} + +/** + * Get local storage item value after split at ';' + * @param {string} item the id of th item + * @returns the item value + */ +function getStorageItemValue(item) { + let result = localStorage.getItem(item); + if (!result) + return null; + return result.split("; ")[0]; +} + +/** + * @param {string} string the value of the item not the item id (the value without splitting) + * @returns the expiration date + */ +function getStorageItemExpiration(value) { + let expires = localStorage.getItem(value).split("; ")[1]; + if (!expires) { // item with no expiration date + return null; + } + return new Date(expires); +} + +/** + * + * @param string the value of the item not the item id (the value without splitting) + * @returns whether expired or not + */ +function isStorageItemExpired(value) { + let expires = parseInt(value.split("; ")[1]); + if (!expires) { // item with no expiration date + return null; + } + return new Date(expires) < new Date(); +} + +// localStorage </> + + + +function getApiValue(element, placeholder, apiPathName, callback, noReplace = false) { + let placeholderName = placeholder.replace("ghapi-", ""); + let cv = getStorageItem(placeholder); // cached value + if (noReplace) { + if (cv) { + callback(cv, true); + } else { + $.getJSON(ghAPI + `/${apiPathName}`, (data) => { + let value = callback(data, false); + setStorageItem(placeholder, value, 0.2); + }) + } + return; + } + + let innerHTML = element.innerHTML; + if (innerHTML.includes(`\${${placeholderName}}`)) { + if (cv) { + element.innerHTML = element.innerHTML.replaceAll(`\${${placeholderName}}`, cv); + } else { + $.getJSON(ghAPI + `/${apiPathName}`, (data) => { + let value = callback(data, false); + element.innerHTML = element.innerHTML.replaceAll(`\${${placeholderName}}`, value); + setStorageItem(placeholder, value, 0.2); + }) + } + } +} + +// Copy texts/links to clipboard +function copyToClipboard(value) { + setTimeout(() => { + let cb = document.body.appendChild(document.createElement("input")); + cb.value = value; + cb.focus(); + cb.select(); + document.execCommand('copy'); + cb.parentNode.removeChild(cb); + }, 50) +} + +// Show notification +function showNotification(text, bgColor, color) { + var noti = document.body.appendChild(document.createElement("span")); + noti.id = "notification-box"; + + setTimeout(() => { + noti.textContent = text; + if (bgColor) + noti.styles.backgroundColor = bgColor; + if (color) + noti.styles.backgroundColor = color; + noti.classList.add("activate-notification"); + setTimeout(() => { + noti.classList.remove("activate-notification"); + setTimeout(() => { + noti.parentNode.removeChild(noti); + }, 200); + }, 1500); + }, 50); +} + +// <> Magic Text (&k) +function getRandomChar() { + chars = "ÂÃÉÊÐÑÙÚÛÜéêëãòóôēĔąĆćŇň1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ~!@#$%^&*()-=_+{}["; + return chars.charAt(Math.floor(Math.random() * chars.length) + 1) +} + +function magicTextGen(element) { + var msg = element.textContent; + var length = msg.length; + + setInterval(() => { + var newMsg = ""; + for (i = 0; i <= length; i++) { + newMsg += getRandomChar(msg.charAt(i)); + } + element.textContent = newMsg; + + }, 30) +} + +function renderMagicText() { + document.querySelectorAll('.magic-text').forEach((e) => { + magicTextGen(e); + }) +} +// Magic Text (&k) </> + +// Replace Github placeholders +function replacePlaceholders(element) { + let innerHTML = element.innerHTML; + if (innerHTML.includes("${latest-version}")) { + getApiValue(element, "ghapi-latest-version", "releases", (data) => { + return data[0]["tag_name"]; + }); + } + + if (innerHTML.includes("${latest-version-changelog}")) { + getApiValue(element, "ghapi-latest-version-changelog", "releases", (data) => { + return data["body"].replaceAll("\\r\\n", "<br>"); + }); + } + + if (innerHTML.includes("${stable-version}")) { + getApiValue(element, "ghapi-stable-version", "releases/latest", (data) => { + return data["tag_name"]; + }); + } + + if (innerHTML.includes("${stable-version-changelog}")) { + getApiValue(element, "ghapi-stable-version-changelog", "releases/latest", (data) => { + return data["body"].replaceAll("\\r\\n", "<br>"); + }); + } + + if (innerHTML.includes("${latest-issue-")) { + getApiValue(element, "ghapi-latest-issue-user", "issues?per_page=1", (data) => { + return data[0]["user"]["login"]; + }); + getApiValue(element, "ghapi-latest-issue-title", "issues?per_page=1", (data) => { + return data[0]["title"]; + }); + getApiValue(element, "ghapi-latest-issue-date", "issues?per_page=1", (data) => { + return data[0]["created_at"]; + }); + } + + if (innerHTML.includes("${latest-pull-")) { + getApiValue(element, "ghapi-latest-pull-user", "pulls?per_page=1", (data) => { + return data[0]["user"]["login"]; + }); + getApiValue(element, "ghapi-latest-pull-title", "pulls?per_page=1", (data) => { + return data[0]["title"]; + }); + getApiValue(element, "ghapi-latest-pull-date", "pulls?per_page=1", (data) => { + return data[0]["created_at"]; + }); + } + + if (innerHTML.includes("${site-version}")) { + element.innerHTML = element.innerHTML.replaceAll("${site-version}", siteVersion); + } + + if (innerHTML.includes("${contributors-size}")) { + getApiValue(element, "ghapi-contributors-size", "contributors?per_page=500", (data) => { + return data.length; + }); + } +} + +// <> Syntax Highlighting + +// ORDER MATTERS!! +// All regexes must be sorrouneded with () to be able to use group 1 as the whole match since Js doesn't have group 0 +// Example: .+ = X +// Example: (.+) = ✓ +var patterns = []; // [REGEX, CLASS] + +function registerSyntax(regexString, flags, clazz) { + try { + regex = new RegExp(regexString, flags); + patterns.push([regex, clazz]); + } catch (error) { + console.warn(`Either your browser doesn't support this regex or the regex is incorrect (${regexString}):` + error); + } +} + +registerSyntax("((?<!#)#(?!#).*)", "gi", "sk-comment") // Must be first, : must be before :: +registerSyntax("(\\:|\\:\\:)", "gi", "sk-var") +registerSyntax("((?<!href=)\\\".+?\\\")", "gi", "sk-string") // before others to not edit non skript code +// registerSyntax("\\b(add|give|increase|set|make|remove( all| every|)|subtract|reduce|delete|clear|reset|send|broadcast|wait|halt|create|(dis)?enchant|shoot|rotate|reload|enable|(re)?start|teleport|feed|heal|hide|kick|(IP(-| )|un|)ban|break|launch|leash|force|message|close|show|reveal|cure|poison|spawn)(?=[ <])\\b", "gi", "sk-eff") // better to be off since it can't be much improved due to how current codes are made (can't detect \\s nor \\t) +registerSyntax("\\b(on (?=.+\\:))", "gi", "sk-event") +registerSyntax("\\b((parse )?if|else if|else|(do )?while|loop(?!-)|return|continue( loop|)|at)\\b", "gi", "sk-cond") +registerSyntax("\\b((|all )player(s|)|victim|attacker|sender|loop-player|shooter|uuid of |'s uuid|(location of |'s location)|console)\\b", "gi", "sk-expr") +registerSyntax("\\b((loop|event)-\\w+)\\b", "gi", "sk-loop-value") +registerSyntax("\\b(contains?|(has|have|is|was|were|are|does)(n't| not|)|can('t| ?not|))\\b", "gi", "sk-cond") +registerSyntax("\\b(command \\/.+(?=.*?:))", "gi", "sk-command") +registerSyntax("(<.+?>)", "gi", "sk-arg-type") +registerSyntax("\\b(true)\\b", "gi", "sk-true") +registerSyntax("\\b(stop( (the |)|)(trigger|server|loop|)|cancel( event)?|false)\\b", "gi", "sk-false") +registerSyntax("({|})", "gi", "sk-var") +registerSyntax("(\\w+?(?=\\(.*?\\)))", "gi", "sk-function") +registerSyntax("((\\d+?(\\.\\d+?)? |a |)(|minecraft |mc |real |rl |irl )(tick|second|minute|hour|day)s?)", "gi", "sk-timespan") +registerSyntax("\\b(now)\\b", "gi", "sk-timespan") + +function highlightElement(element) { + + let lines = element.innerHTML.split("<br>") + + for (let j = 0; j < lines.length; j++) { + Loop2: for (let i = 0; i < patterns.length; i++) { + let match; + let regex = patterns[i][0]; + let oldLine = lines[j]; + + while ((match = regex.exec(oldLine)) != null) { + lines[j] = lines[j].replaceAll(regex, `<span class='${patterns[i][1]}'>$1</span>`) + if (regex.lastIndex == 0) // Break after it reaches the end of exec count to avoid inf loop + continue Loop2; + } + } + } + element.innerHTML = lines.join("<br>") +} +// Syntax Highlighting </> + +// Offset page's scroll - Used for anchor scroll correction +function offsetAnchor(event, id) { // event can be null + let content = document.querySelector("#content"); + let element = document.getElementById(id); + + if (content && element) { + if (event != null) + event.preventDefault(); + content.scroll(0, element.offsetTop - 25); // Should be less than the margin in .item-wrapper so it doesn't show the part of the previous .item-wrapper + } +} + +// <> Toggle a specific doc element block + +// Active syntax +var lastActiveSyntaxID; +function toggleSyntax(elementID) { + let element = document.getElementById(elementID) + if (!element) + return + + if (lastActiveSyntaxID != null) + document.getElementById(lastActiveSyntaxID).classList.remove("active-syntax"); + + element.classList.add("active-syntax"); + lastActiveSyntaxID = elementID; +} + +// Toggle a specific doc element block </> \ No newline at end of file diff --git a/docs/js/highlight.js b/docs/js/highlight.js deleted file mode 100644 index 51d16c9080d..00000000000 --- a/docs/js/highlight.js +++ /dev/null @@ -1,1359 +0,0 @@ -// Source: https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.7.2/highlight.min.js - Don't use higher versions as it breaks <br> see https://github.com/highlightjs/highlight.js/issues/3302 -/* - Highlight.js 10.7.2 (00233d63) - License: BSD-3-Clause - Copyright (c) 2006-2021, Ivan Sagalaev -*/ -var hljs=function(){"use strict";function e(t){ - return t instanceof Map?t.clear=t.delete=t.set=()=>{ - throw Error("map is read-only")}:t instanceof Set&&(t.add=t.clear=t.delete=()=>{ - throw Error("set is read-only") - }),Object.freeze(t),Object.getOwnPropertyNames(t).forEach((n=>{var i=t[n] - ;"object"!=typeof i||Object.isFrozen(i)||e(i)})),t}var t=e,n=e;t.default=n - ;class i{constructor(e){ - void 0===e.data&&(e.data={}),this.data=e.data,this.isMatchIgnored=!1} - ignoreMatch(){this.isMatchIgnored=!0}}function s(e){ - return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'") - }function a(e,...t){const n=Object.create(null);for(const t in e)n[t]=e[t] - ;return t.forEach((e=>{for(const t in e)n[t]=e[t]})),n}const r=e=>!!e.kind - ;class l{constructor(e,t){ - this.buffer="",this.classPrefix=t.classPrefix,e.walk(this)}addText(e){ - this.buffer+=s(e)}openNode(e){if(!r(e))return;let t=e.kind - ;e.sublanguage||(t=`${this.classPrefix}${t}`),this.span(t)}closeNode(e){ - r(e)&&(this.buffer+="</span>")}value(){return this.buffer}span(e){ - this.buffer+=`<span class="${e}">`}}class o{constructor(){this.rootNode={ - children:[]},this.stack=[this.rootNode]}get top(){ - return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){ - this.top.children.push(e)}openNode(e){const t={kind:e,children:[]} - ;this.add(t),this.stack.push(t)}closeNode(){ - if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){ - for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)} - walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,t){ - return"string"==typeof t?e.addText(t):t.children&&(e.openNode(t), - t.children.forEach((t=>this._walk(e,t))),e.closeNode(t)),e}static _collapse(e){ - "string"!=typeof e&&e.children&&(e.children.every((e=>"string"==typeof e))?e.children=[e.children.join("")]:e.children.forEach((e=>{ - o._collapse(e)})))}}class c extends o{constructor(e){super(),this.options=e} - addKeyword(e,t){""!==e&&(this.openNode(t),this.addText(e),this.closeNode())} - addText(e){""!==e&&this.add(e)}addSublanguage(e,t){const n=e.root - ;n.kind=t,n.sublanguage=!0,this.add(n)}toHTML(){ - return new l(this,this.options).value()}finalize(){return!0}}function g(e){ - return e?"string"==typeof e?e:e.source:null} - const u=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./,h="[a-zA-Z]\\w*",d="[a-zA-Z_]\\w*",f="\\b\\d+(\\.\\d+)?",p="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",m="\\b(0b[01]+)",b={ - begin:"\\\\[\\s\\S]",relevance:0},E={className:"string",begin:"'",end:"'", - illegal:"\\n",contains:[b]},x={className:"string",begin:'"',end:'"', - illegal:"\\n",contains:[b]},v={ - begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/ - },w=(e,t,n={})=>{const i=a({className:"comment",begin:e,end:t,contains:[]},n) - ;return i.contains.push(v),i.contains.push({className:"doctag", - begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0}),i - },y=w("//","$"),N=w("/\\*","\\*/"),R=w("#","$");var _=Object.freeze({ - __proto__:null,MATCH_NOTHING_RE:/\b\B/,IDENT_RE:h,UNDERSCORE_IDENT_RE:d, - NUMBER_RE:f,C_NUMBER_RE:p,BINARY_NUMBER_RE:m, - RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~", - SHEBANG:(e={})=>{const t=/^#![ ]*\// - ;return e.binary&&(e.begin=((...e)=>e.map((e=>g(e))).join(""))(t,/.*\b/,e.binary,/\b.*/)), - a({className:"meta",begin:t,end:/$/,relevance:0,"on:begin":(e,t)=>{ - 0!==e.index&&t.ignoreMatch()}},e)},BACKSLASH_ESCAPE:b,APOS_STRING_MODE:E, - QUOTE_STRING_MODE:x,PHRASAL_WORDS_MODE:v,COMMENT:w,C_LINE_COMMENT_MODE:y, - C_BLOCK_COMMENT_MODE:N,HASH_COMMENT_MODE:R,NUMBER_MODE:{className:"number", - begin:f,relevance:0},C_NUMBER_MODE:{className:"number",begin:p,relevance:0}, - BINARY_NUMBER_MODE:{className:"number",begin:m,relevance:0},CSS_NUMBER_MODE:{ - className:"number", - begin:f+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?", - relevance:0},REGEXP_MODE:{begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp", - begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[b,{begin:/\[/,end:/\]/, - relevance:0,contains:[b]}]}]},TITLE_MODE:{className:"title",begin:h,relevance:0 - },UNDERSCORE_TITLE_MODE:{className:"title",begin:d,relevance:0},METHOD_GUARD:{ - begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0},END_SAME_AS_BEGIN:e=>Object.assign(e,{ - "on:begin":(e,t)=>{t.data._beginMatch=e[1]},"on:end":(e,t)=>{ - t.data._beginMatch!==e[1]&&t.ignoreMatch()}})});function k(e,t){ - "."===e.input[e.index-1]&&t.ignoreMatch()}function M(e,t){ - t&&e.beginKeywords&&(e.begin="\\b("+e.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)", - e.__beforeBegin=k,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords, - void 0===e.relevance&&(e.relevance=0))}function O(e,t){ - Array.isArray(e.illegal)&&(e.illegal=((...e)=>"("+e.map((e=>g(e))).join("|")+")")(...e.illegal)) - }function A(e,t){if(e.match){ - if(e.begin||e.end)throw Error("begin & end are not supported with match") - ;e.begin=e.match,delete e.match}}function L(e,t){ - void 0===e.relevance&&(e.relevance=1)} - const I=["of","and","for","in","not","or","if","then","parent","list","value"] - ;function j(e,t,n="keyword"){const i={} - ;return"string"==typeof e?s(n,e.split(" ")):Array.isArray(e)?s(n,e):Object.keys(e).forEach((n=>{ - Object.assign(i,j(e[n],t,n))})),i;function s(e,n){ - t&&(n=n.map((e=>e.toLowerCase()))),n.forEach((t=>{const n=t.split("|") - ;i[n[0]]=[e,B(n[0],n[1])]}))}}function B(e,t){ - return t?Number(t):(e=>I.includes(e.toLowerCase()))(e)?0:1} - function T(e,{plugins:t}){function n(t,n){ - return RegExp(g(t),"m"+(e.case_insensitive?"i":"")+(n?"g":""))}class i{ - constructor(){ - this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0} - addRule(e,t){ - t.position=this.position++,this.matchIndexes[this.matchAt]=t,this.regexes.push([t,e]), - this.matchAt+=(e=>RegExp(e.toString()+"|").exec("").length-1)(e)+1}compile(){ - 0===this.regexes.length&&(this.exec=()=>null) - ;const e=this.regexes.map((e=>e[1]));this.matcherRe=n(((e,t="|")=>{let n=0 - ;return e.map((e=>{n+=1;const t=n;let i=g(e),s="";for(;i.length>0;){ - const e=u.exec(i);if(!e){s+=i;break} - s+=i.substring(0,e.index),i=i.substring(e.index+e[0].length), - "\\"===e[0][0]&&e[1]?s+="\\"+(Number(e[1])+t):(s+=e[0],"("===e[0]&&n++)}return s - })).map((e=>`(${e})`)).join(t)})(e),!0),this.lastIndex=0}exec(e){ - this.matcherRe.lastIndex=this.lastIndex;const t=this.matcherRe.exec(e) - ;if(!t)return null - ;const n=t.findIndex(((e,t)=>t>0&&void 0!==e)),i=this.matchIndexes[n] - ;return t.splice(0,n),Object.assign(t,i)}}class s{constructor(){ - this.rules=[],this.multiRegexes=[], - this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){ - if(this.multiRegexes[e])return this.multiRegexes[e];const t=new i - ;return this.rules.slice(e).forEach((([e,n])=>t.addRule(e,n))), - t.compile(),this.multiRegexes[e]=t,t}resumingScanAtSamePosition(){ - return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(e,t){ - this.rules.push([e,t]),"begin"===t.type&&this.count++}exec(e){ - const t=this.getMatcher(this.regexIndex);t.lastIndex=this.lastIndex - ;let n=t.exec(e) - ;if(this.resumingScanAtSamePosition())if(n&&n.index===this.lastIndex);else{ - const t=this.getMatcher(0);t.lastIndex=this.lastIndex+1,n=t.exec(e)} - return n&&(this.regexIndex+=n.position+1, - this.regexIndex===this.count&&this.considerAll()),n}} - if(e.compilerExtensions||(e.compilerExtensions=[]), - e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.") - ;return e.classNameAliases=a(e.classNameAliases||{}),function t(i,r){const l=i - ;if(i.isCompiled)return l - ;[A].forEach((e=>e(i,r))),e.compilerExtensions.forEach((e=>e(i,r))), - i.__beforeBegin=null,[M,O,L].forEach((e=>e(i,r))),i.isCompiled=!0;let o=null - ;if("object"==typeof i.keywords&&(o=i.keywords.$pattern, - delete i.keywords.$pattern), - i.keywords&&(i.keywords=j(i.keywords,e.case_insensitive)), - i.lexemes&&o)throw Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ") - ;return o=o||i.lexemes||/\w+/, - l.keywordPatternRe=n(o,!0),r&&(i.begin||(i.begin=/\B|\b/), - l.beginRe=n(i.begin),i.endSameAsBegin&&(i.end=i.begin), - i.end||i.endsWithParent||(i.end=/\B|\b/), - i.end&&(l.endRe=n(i.end)),l.terminatorEnd=g(i.end)||"", - i.endsWithParent&&r.terminatorEnd&&(l.terminatorEnd+=(i.end?"|":"")+r.terminatorEnd)), - i.illegal&&(l.illegalRe=n(i.illegal)), - i.contains||(i.contains=[]),i.contains=[].concat(...i.contains.map((e=>(e=>(e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map((t=>a(e,{ - variants:null},t)))),e.cachedVariants?e.cachedVariants:S(e)?a(e,{ - starts:e.starts?a(e.starts):null - }):Object.isFrozen(e)?a(e):e))("self"===e?i:e)))),i.contains.forEach((e=>{t(e,l) - })),i.starts&&t(i.starts,r),l.matcher=(e=>{const t=new s - ;return e.contains.forEach((e=>t.addRule(e.begin,{rule:e,type:"begin" - }))),e.terminatorEnd&&t.addRule(e.terminatorEnd,{type:"end" - }),e.illegal&&t.addRule(e.illegal,{type:"illegal"}),t})(l),l}(e)}function S(e){ - return!!e&&(e.endsWithParent||S(e.starts))}function P(e){const t={ - props:["language","code","autodetect"],data:()=>({detectedLanguage:"", - unknownLanguage:!1}),computed:{className(){ - return this.unknownLanguage?"":"hljs "+this.detectedLanguage},highlighted(){ - if(!this.autoDetect&&!e.getLanguage(this.language))return console.warn(`The language "${this.language}" you specified could not be found.`), - this.unknownLanguage=!0,s(this.code);let t={} - ;return this.autoDetect?(t=e.highlightAuto(this.code), - this.detectedLanguage=t.language):(t=e.highlight(this.language,this.code,this.ignoreIllegals), - this.detectedLanguage=this.language),t.value},autoDetect(){ - return!(this.language&&(e=this.autodetect,!e&&""!==e));var e}, - ignoreIllegals:()=>!0},render(e){return e("pre",{},[e("code",{ - class:this.className,domProps:{innerHTML:this.highlighted}})])}};return{ - Component:t,VuePlugin:{install(e){e.component("highlightjs",t)}}}}const D={ - "after:highlightElement":({el:e,result:t,text:n})=>{const i=H(e) - ;if(!i.length)return;const a=document.createElement("div") - ;a.innerHTML=t.value,t.value=((e,t,n)=>{let i=0,a="";const r=[];function l(){ - return e.length&&t.length?e[0].offset!==t[0].offset?e[0].offset<t[0].offset?e:t:"start"===t[0].event?e:t:e.length?e:t - }function o(e){a+="<"+C(e)+[].map.call(e.attributes,(function(e){ - return" "+e.nodeName+'="'+s(e.value)+'"'})).join("")+">"}function c(e){ - a+="</"+C(e)+">"}function g(e){("start"===e.event?o:c)(e.node)} - for(;e.length||t.length;){let t=l() - ;if(a+=s(n.substring(i,t[0].offset)),i=t[0].offset,t===e){r.reverse().forEach(c) - ;do{g(t.splice(0,1)[0]),t=l()}while(t===e&&t.length&&t[0].offset===i) - ;r.reverse().forEach(o) - }else"start"===t[0].event?r.push(t[0].node):r.pop(),g(t.splice(0,1)[0])} - return a+s(n.substr(i))})(i,H(a),n)}};function C(e){ - return e.nodeName.toLowerCase()}function H(e){const t=[];return function e(n,i){ - for(let s=n.firstChild;s;s=s.nextSibling)3===s.nodeType?i+=s.nodeValue.length:1===s.nodeType&&(t.push({ - event:"start",offset:i,node:s}),i=e(s,i),C(s).match(/br|hr|img|input/)||t.push({ - event:"stop",offset:i,node:s}));return i}(e,0),t}const $={},U=e=>{ - console.error(e)},z=(e,...t)=>{console.log("WARN: "+e,...t)},K=(e,t)=>{ - $[`${e}/${t}`]||(console.log(`Deprecated as of ${e}. ${t}`),$[`${e}/${t}`]=!0) - },G=s,V=a,W=Symbol("nomatch");return(e=>{ - const n=Object.create(null),s=Object.create(null),a=[];let r=!0 - ;const l=/(^(<[^>]+>|\t|)+|\n)/gm,o="Could not find the language '{}', did you forget to load/include a language module?",g={ - disableAutodetect:!0,name:"Plain text",contains:[]};let u={ - noHighlightRe:/^(no-?highlight)$/i, - languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-", - tabReplace:null,useBR:!1,languages:null,__emitter:c};function h(e){ - return u.noHighlightRe.test(e)}function d(e,t,n,i){let s="",a="" - ;"object"==typeof t?(s=e, - n=t.ignoreIllegals,a=t.language,i=void 0):(K("10.7.0","highlight(lang, code, ...args) has been deprecated."), - K("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"), - a=e,s=t);const r={code:s,language:a};M("before:highlight",r) - ;const l=r.result?r.result:f(r.language,r.code,n,i) - ;return l.code=r.code,M("after:highlight",l),l}function f(e,t,s,l){ - function c(e,t){const n=v.case_insensitive?t[0].toLowerCase():t[0] - ;return Object.prototype.hasOwnProperty.call(e.keywords,n)&&e.keywords[n]} - function g(){null!=R.subLanguage?(()=>{if(""===M)return;let e=null - ;if("string"==typeof R.subLanguage){ - if(!n[R.subLanguage])return void k.addText(M) - ;e=f(R.subLanguage,M,!0,_[R.subLanguage]),_[R.subLanguage]=e.top - }else e=p(M,R.subLanguage.length?R.subLanguage:null) - ;R.relevance>0&&(O+=e.relevance),k.addSublanguage(e.emitter,e.language) - })():(()=>{if(!R.keywords)return void k.addText(M);let e=0 - ;R.keywordPatternRe.lastIndex=0;let t=R.keywordPatternRe.exec(M),n="";for(;t;){ - n+=M.substring(e,t.index);const i=c(R,t);if(i){const[e,s]=i - ;if(k.addText(n),n="",O+=s,e.startsWith("_"))n+=t[0];else{ - const n=v.classNameAliases[e]||e;k.addKeyword(t[0],n)}}else n+=t[0] - ;e=R.keywordPatternRe.lastIndex,t=R.keywordPatternRe.exec(M)} - n+=M.substr(e),k.addText(n)})(),M=""}function h(e){ - return e.className&&k.openNode(v.classNameAliases[e.className]||e.className), - R=Object.create(e,{parent:{value:R}}),R}function d(e,t,n){let s=((e,t)=>{ - const n=e&&e.exec(t);return n&&0===n.index})(e.endRe,n);if(s){if(e["on:end"]){ - const n=new i(e);e["on:end"](t,n),n.isMatchIgnored&&(s=!1)}if(s){ - for(;e.endsParent&&e.parent;)e=e.parent;return e}} - if(e.endsWithParent)return d(e.parent,t,n)}function m(e){ - return 0===R.matcher.regexIndex?(M+=e[0],1):(I=!0,0)}function b(e){ - const n=e[0],i=t.substr(e.index),s=d(R,e,i);if(!s)return W;const a=R - ;a.skip?M+=n:(a.returnEnd||a.excludeEnd||(M+=n),g(),a.excludeEnd&&(M=n));do{ - R.className&&k.closeNode(),R.skip||R.subLanguage||(O+=R.relevance),R=R.parent - }while(R!==s.parent) - ;return s.starts&&(s.endSameAsBegin&&(s.starts.endRe=s.endRe), - h(s.starts)),a.returnEnd?0:n.length}let E={};function x(n,a){const l=a&&a[0] - ;if(M+=n,null==l)return g(),0 - ;if("begin"===E.type&&"end"===a.type&&E.index===a.index&&""===l){ - if(M+=t.slice(a.index,a.index+1),!r){const t=Error("0 width match regex") - ;throw t.languageName=e,t.badRule=E.rule,t}return 1} - if(E=a,"begin"===a.type)return function(e){ - const t=e[0],n=e.rule,s=new i(n),a=[n.__beforeBegin,n["on:begin"]] - ;for(const n of a)if(n&&(n(e,s),s.isMatchIgnored))return m(t) - ;return n&&n.endSameAsBegin&&(n.endRe=RegExp(t.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")), - n.skip?M+=t:(n.excludeBegin&&(M+=t), - g(),n.returnBegin||n.excludeBegin||(M=t)),h(n),n.returnBegin?0:t.length}(a) - ;if("illegal"===a.type&&!s){ - const e=Error('Illegal lexeme "'+l+'" for mode "'+(R.className||"<unnamed>")+'"') - ;throw e.mode=R,e}if("end"===a.type){const e=b(a);if(e!==W)return e} - if("illegal"===a.type&&""===l)return 1 - ;if(L>1e5&&L>3*a.index)throw Error("potential infinite loop, way more iterations than matches") - ;return M+=l,l.length}const v=N(e) - ;if(!v)throw U(o.replace("{}",e)),Error('Unknown language: "'+e+'"') - ;const w=T(v,{plugins:a});let y="",R=l||w;const _={},k=new u.__emitter(u);(()=>{ - const e=[];for(let t=R;t!==v;t=t.parent)t.className&&e.unshift(t.className) - ;e.forEach((e=>k.openNode(e)))})();let M="",O=0,A=0,L=0,I=!1;try{ - for(R.matcher.considerAll();;){ - L++,I?I=!1:R.matcher.considerAll(),R.matcher.lastIndex=A - ;const e=R.matcher.exec(t);if(!e)break;const n=x(t.substring(A,e.index),e) - ;A=e.index+n}return x(t.substr(A)),k.closeAllNodes(),k.finalize(),y=k.toHTML(),{ - relevance:Math.floor(O),value:y,language:e,illegal:!1,emitter:k,top:R}}catch(n){ - if(n.message&&n.message.includes("Illegal"))return{illegal:!0,illegalBy:{ - msg:n.message,context:t.slice(A-100,A+100),mode:n.mode},sofar:y,relevance:0, - value:G(t),emitter:k};if(r)return{illegal:!1,relevance:0,value:G(t),emitter:k, - language:e,top:R,errorRaised:n};throw n}}function p(e,t){ - t=t||u.languages||Object.keys(n);const i=(e=>{const t={relevance:0, - emitter:new u.__emitter(u),value:G(e),illegal:!1,top:g} - ;return t.emitter.addText(e),t})(e),s=t.filter(N).filter(k).map((t=>f(t,e,!1))) - ;s.unshift(i);const a=s.sort(((e,t)=>{ - if(e.relevance!==t.relevance)return t.relevance-e.relevance - ;if(e.language&&t.language){if(N(e.language).supersetOf===t.language)return 1 - ;if(N(t.language).supersetOf===e.language)return-1}return 0})),[r,l]=a,o=r - ;return o.second_best=l,o}const m={"before:highlightElement":({el:e})=>{ - u.useBR&&(e.innerHTML=e.innerHTML.replace(/\n/g,"").replace(/<br[ /]*>/g,"\n")) - },"after:highlightElement":({result:e})=>{ - u.useBR&&(e.value=e.value.replace(/\n/g,"<br>"))}},b=/^(<[^>]+>|\t)+/gm,E={ - "after:highlightElement":({result:e})=>{ - u.tabReplace&&(e.value=e.value.replace(b,(e=>e.replace(/\t/g,u.tabReplace))))}} - ;function x(e){let t=null;const n=(e=>{let t=e.className+" " - ;t+=e.parentNode?e.parentNode.className:"";const n=u.languageDetectRe.exec(t) - ;if(n){const t=N(n[1]) - ;return t||(z(o.replace("{}",n[1])),z("Falling back to no-highlight mode for this block.",e)), - t?n[1]:"no-highlight"}return t.split(/\s+/).find((e=>h(e)||N(e)))})(e) - ;if(h(n))return;M("before:highlightElement",{el:e,language:n}),t=e - ;const i=t.textContent,a=n?d(i,{language:n,ignoreIllegals:!0}):p(i) - ;M("after:highlightElement",{el:e,result:a,text:i - }),e.innerHTML=a.value,((e,t,n)=>{const i=t?s[t]:n - ;e.classList.add("hljs"),i&&e.classList.add(i)})(e,n,a.language),e.result={ - language:a.language,re:a.relevance,relavance:a.relevance - },a.second_best&&(e.second_best={language:a.second_best.language, - re:a.second_best.relevance,relavance:a.second_best.relevance})}const v=()=>{ - v.called||(v.called=!0, - K("10.6.0","initHighlighting() is deprecated. Use highlightAll() instead."), - document.querySelectorAll("pre code").forEach(x))};let w=!1;function y(){ - "loading"!==document.readyState?document.querySelectorAll("pre code").forEach(x):w=!0 - }function N(e){return e=(e||"").toLowerCase(),n[e]||n[s[e]]} - function R(e,{languageName:t}){"string"==typeof e&&(e=[e]),e.forEach((e=>{ - s[e.toLowerCase()]=t}))}function k(e){const t=N(e) - ;return t&&!t.disableAutodetect}function M(e,t){const n=e;a.forEach((e=>{ - e[n]&&e[n](t)}))} - "undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",(()=>{ - w&&y()}),!1),Object.assign(e,{highlight:d,highlightAuto:p,highlightAll:y, - fixMarkup:e=>{ - return K("10.2.0","fixMarkup will be removed entirely in v11.0"),K("10.2.0","Please see https://github.com/highlightjs/highlight.js/issues/2534"), - t=e, - u.tabReplace||u.useBR?t.replace(l,(e=>"\n"===e?u.useBR?"<br>":e:u.tabReplace?e.replace(/\t/g,u.tabReplace):e)):t - ;var t},highlightElement:x, - highlightBlock:e=>(K("10.7.0","highlightBlock will be removed entirely in v12.0"), - K("10.7.0","Please use highlightElement now."),x(e)),configure:e=>{ - e.useBR&&(K("10.3.0","'useBR' will be removed entirely in v11.0"), - K("10.3.0","Please see https://github.com/highlightjs/highlight.js/issues/2559")), - u=V(u,e)},initHighlighting:v,initHighlightingOnLoad:()=>{ - K("10.6.0","initHighlightingOnLoad() is deprecated. Use highlightAll() instead."), - w=!0},registerLanguage:(t,i)=>{let s=null;try{s=i(e)}catch(e){ - if(U("Language definition for '{}' could not be registered.".replace("{}",t)), - !r)throw e;U(e),s=g} - s.name||(s.name=t),n[t]=s,s.rawDefinition=i.bind(null,e),s.aliases&&R(s.aliases,{ - languageName:t})},unregisterLanguage:e=>{delete n[e] - ;for(const t of Object.keys(s))s[t]===e&&delete s[t]}, - listLanguages:()=>Object.keys(n),getLanguage:N,registerAliases:R, - requireLanguage:e=>{ - K("10.4.0","requireLanguage will be removed entirely in v11."), - K("10.4.0","Please see https://github.com/highlightjs/highlight.js/pull/2844") - ;const t=N(e);if(t)return t - ;throw Error("The '{}' language is required, but not loaded.".replace("{}",e))}, - autoDetection:k,inherit:V,addPlugin:e=>{(e=>{ - e["before:highlightBlock"]&&!e["before:highlightElement"]&&(e["before:highlightElement"]=t=>{ - e["before:highlightBlock"](Object.assign({block:t.el},t)) - }),e["after:highlightBlock"]&&!e["after:highlightElement"]&&(e["after:highlightElement"]=t=>{ - e["after:highlightBlock"](Object.assign({block:t.el},t))})})(e),a.push(e)}, - vuePlugin:P(e).VuePlugin}),e.debugMode=()=>{r=!1},e.safeMode=()=>{r=!0 - },e.versionString="10.7.2";for(const e in _)"object"==typeof _[e]&&t(_[e]) - ;return Object.assign(e,_),e.addPlugin(m),e.addPlugin(D),e.addPlugin(E),e})({}) - }();"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs); - hljs.registerLanguage("apache",(()=>{"use strict";return e=>{const n={ - className:"number",begin:/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(:\d{1,5})?/} - ;return{name:"Apache config",aliases:["apacheconf"],case_insensitive:!0, - contains:[e.HASH_COMMENT_MODE,{className:"section",begin:/<\/?/,end:/>/, - contains:[n,{className:"number",begin:/:\d{1,5}/ - },e.inherit(e.QUOTE_STRING_MODE,{relevance:0})]},{className:"attribute", - begin:/\w+/,relevance:0,keywords:{ - nomarkup:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername" - },starts:{end:/$/,relevance:0,keywords:{literal:"on off all deny allow"}, - contains:[{className:"meta",begin:/\s\[/,end:/\]$/},{className:"variable", - begin:/[\$%]\{/,end:/\}/,contains:["self",{className:"number",begin:/[$%]\d+/}] - },n,{className:"number",begin:/\d+/},e.QUOTE_STRING_MODE]}}],illegal:/\S/}} - })()); - hljs.registerLanguage("bash",(()=>{"use strict";function e(...e){ - return e.map((e=>{return(s=e)?"string"==typeof s?s:s.source:null;var s - })).join("")}return s=>{const n={},t={begin:/\$\{/,end:/\}/,contains:["self",{ - begin:/:-/,contains:[n]}]};Object.assign(n,{className:"variable",variants:[{ - begin:e(/\$[\w\d#@][\w\d_]*/,"(?![\\w\\d])(?![$])")},t]});const a={ - className:"subst",begin:/\$\(/,end:/\)/,contains:[s.BACKSLASH_ESCAPE]},i={ - begin:/<<-?\s*(?=\w+)/,starts:{contains:[s.END_SAME_AS_BEGIN({begin:/(\w+)/, - end:/(\w+)/,className:"string"})]}},c={className:"string",begin:/"/,end:/"/, - contains:[s.BACKSLASH_ESCAPE,n,a]};a.contains.push(c);const o={begin:/\$\(\(/, - end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},s.NUMBER_MODE,n] - },r=s.SHEBANG({binary:"(fish|bash|zsh|sh|csh|ksh|tcsh|dash|scsh)",relevance:10 - }),l={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0, - contains:[s.inherit(s.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{ - name:"Bash",aliases:["sh","zsh"],keywords:{$pattern:/\b[a-z._-]+\b/, - keyword:"if then else elif fi for while in do done case esac function", - literal:"true false", - built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp" - },contains:[r,s.SHEBANG(),l,o,s.HASH_COMMENT_MODE,i,c,{className:"",begin:/\\"/ - },{className:"string",begin:/'/,end:/'/},n]}}})()); - hljs.registerLanguage("c",(()=>{"use strict";function e(e){ - return((...e)=>e.map((e=>(e=>e?"string"==typeof e?e:e.source:null)(e))).join(""))("(",e,")?") - }return t=>{const n=t.COMMENT("//","$",{contains:[{begin:/\\\n/}] - }),r="[a-zA-Z_]\\w*::",a="(decltype\\(auto\\)|"+e(r)+"[a-zA-Z_]\\w*"+e("<[^<>]+>")+")",i={ - className:"keyword",begin:"\\b[a-z\\d_]*_t\\b"},s={className:"string", - variants:[{begin:'(u8?|U|L)?"',end:'"',illegal:"\\n", - contains:[t.BACKSLASH_ESCAPE]},{ - begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)", - end:"'",illegal:"."},t.END_SAME_AS_BEGIN({ - begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},o={ - className:"number",variants:[{begin:"\\b(0b[01']+)"},{ - begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)((ll|LL|l|L)(u|U)?|(u|U)(ll|LL|l|L)?|f|F|b|B)" - },{ - begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)" - }],relevance:0},c={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{ - "meta-keyword":"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include" - },contains:[{begin:/\\\n/,relevance:0},t.inherit(s,{className:"meta-string"}),{ - className:"meta-string",begin:/<.*?>/},n,t.C_BLOCK_COMMENT_MODE]},l={ - className:"title",begin:e(r)+t.IDENT_RE,relevance:0 - },d=e(r)+t.IDENT_RE+"\\s*\\(",u={ - keyword:"int float while private char char8_t char16_t char32_t catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid wchar_t short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignas alignof constexpr consteval constinit decltype concept co_await co_return co_yield requires noexcept static_assert thread_local restrict final override atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq", - built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set pair bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap priority_queue make_pair array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr _Bool complex _Complex imaginary _Imaginary", - literal:"true false nullptr NULL"},m=[c,i,n,t.C_BLOCK_COMMENT_MODE,o,s],p={ - variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{ - beginKeywords:"new throw return else",end:/;/}],keywords:u,contains:m.concat([{ - begin:/\(/,end:/\)/,keywords:u,contains:m.concat(["self"]),relevance:0}]), - relevance:0},_={className:"function",begin:"("+a+"[\\*&\\s]+)+"+d, - returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:u,illegal:/[^\w\s\*&:<>.]/, - contains:[{begin:"decltype\\(auto\\)",keywords:u,relevance:0},{begin:d, - returnBegin:!0,contains:[l],relevance:0},{className:"params",begin:/\(/, - end:/\)/,keywords:u,relevance:0,contains:[n,t.C_BLOCK_COMMENT_MODE,s,o,i,{ - begin:/\(/,end:/\)/,keywords:u,relevance:0, - contains:["self",n,t.C_BLOCK_COMMENT_MODE,s,o,i]}] - },i,n,t.C_BLOCK_COMMENT_MODE,c]};return{name:"C",aliases:["h"],keywords:u, - disableAutodetect:!0,illegal:"</",contains:[].concat(p,_,m,[c,{ - begin:"\\b(deque|list|queue|priority_queue|pair|stack|vector|map|set|bitset|multiset|multimap|unordered_map|unordered_set|unordered_multiset|unordered_multimap|array)\\s*<", - end:">",keywords:u,contains:["self",i]},{begin:t.IDENT_RE+"::",keywords:u},{ - className:"class",beginKeywords:"enum class struct union",end:/[{;:<>=]/, - contains:[{beginKeywords:"final class struct"},t.TITLE_MODE]}]),exports:{ - preprocessor:c,strings:s,keywords:u}}}})()); - hljs.registerLanguage("coffeescript",(()=>{"use strict" - ;const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer","BigInt64Array","BigUint64Array","BigInt"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]) - ;return r=>{const t={ - keyword:e.concat(["then","unless","until","loop","by","when","and","or","is","isnt","not"]).filter((i=["var","const","let","function","static"], - e=>!i.includes(e))),literal:n.concat(["yes","no","on","off"]), - built_in:a.concat(["npm","print"])};var i;const s="[A-Za-z$_][0-9A-Za-z$_]*",o={ - className:"subst",begin:/#\{/,end:/\}/,keywords:t - },c=[r.BINARY_NUMBER_MODE,r.inherit(r.C_NUMBER_MODE,{starts:{end:"(\\s*/)?", - relevance:0}}),{className:"string",variants:[{begin:/'''/,end:/'''/, - contains:[r.BACKSLASH_ESCAPE]},{begin:/'/,end:/'/,contains:[r.BACKSLASH_ESCAPE] - },{begin:/"""/,end:/"""/,contains:[r.BACKSLASH_ESCAPE,o]},{begin:/"/,end:/"/, - contains:[r.BACKSLASH_ESCAPE,o]}]},{className:"regexp",variants:[{begin:"///", - end:"///",contains:[o,r.HASH_COMMENT_MODE]},{begin:"//[gim]{0,3}(?=\\W)", - relevance:0},{begin:/\/(?![ *]).*?(?![\\]).\/[gim]{0,3}(?=\W)/}]},{begin:"@"+s - },{subLanguage:"javascript",excludeBegin:!0,excludeEnd:!0,variants:[{ - begin:"```",end:"```"},{begin:"`",end:"`"}]}];o.contains=c - ;const l=r.inherit(r.TITLE_MODE,{begin:s}),d="(\\(.*\\)\\s*)?\\B[-=]>",g={ - className:"params",begin:"\\([^\\(]",returnBegin:!0,contains:[{begin:/\(/, - end:/\)/,keywords:t,contains:["self"].concat(c)}]};return{name:"CoffeeScript", - aliases:["coffee","cson","iced"],keywords:t,illegal:/\/\*/, - contains:c.concat([r.COMMENT("###","###"),r.HASH_COMMENT_MODE,{ - className:"function",begin:"^\\s*"+s+"\\s*=\\s*"+d,end:"[-=]>",returnBegin:!0, - contains:[l,g]},{begin:/[:\(,=]\s*/,relevance:0,contains:[{className:"function", - begin:d,end:"[-=]>",returnBegin:!0,contains:[g]}]},{className:"class", - beginKeywords:"class",end:"$",illegal:/[:="\[\]]/,contains:[{ - beginKeywords:"extends",endsWithParent:!0,illegal:/[:="\[\]]/,contains:[l]},l] - },{begin:s+":",end:":",returnBegin:!0,returnEnd:!0,relevance:0}])}}})()); - hljs.registerLanguage("cpp",(()=>{"use strict";function e(e){ - return t("(",e,")?")}function t(...e){return e.map((e=>{ - return(t=e)?"string"==typeof t?t:t.source:null;var t})).join("")}return n=>{ - const r=n.COMMENT("//","$",{contains:[{begin:/\\\n/}] - }),a="[a-zA-Z_]\\w*::",i="(decltype\\(auto\\)|"+e(a)+"[a-zA-Z_]\\w*"+e("<[^<>]+>")+")",s={ - className:"keyword",begin:"\\b[a-z\\d_]*_t\\b"},c={className:"string", - variants:[{begin:'(u8?|U|L)?"',end:'"',illegal:"\\n", - contains:[n.BACKSLASH_ESCAPE]},{ - begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)", - end:"'",illegal:"."},n.END_SAME_AS_BEGIN({ - begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},o={ - className:"number",variants:[{begin:"\\b(0b[01']+)"},{ - begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)((ll|LL|l|L)(u|U)?|(u|U)(ll|LL|l|L)?|f|F|b|B)" - },{ - begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)" - }],relevance:0},l={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{ - "meta-keyword":"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include" - },contains:[{begin:/\\\n/,relevance:0},n.inherit(c,{className:"meta-string"}),{ - className:"meta-string",begin:/<.*?>/},r,n.C_BLOCK_COMMENT_MODE]},d={ - className:"title",begin:e(a)+n.IDENT_RE,relevance:0 - },u=e(a)+n.IDENT_RE+"\\s*\\(",m={ - keyword:"int float while private char char8_t char16_t char32_t catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid wchar_t short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignas alignof constexpr consteval constinit decltype concept co_await co_return co_yield requires noexcept static_assert thread_local restrict final override atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq", - built_in:"_Bool _Complex _Imaginary", - _relevance_hints:["asin","atan2","atan","calloc","ceil","cosh","cos","exit","exp","fabs","floor","fmod","fprintf","fputs","free","frexp","auto_ptr","deque","list","queue","stack","vector","map","set","pair","bitset","multiset","multimap","unordered_set","fscanf","future","isalnum","isalpha","iscntrl","isdigit","isgraph","islower","isprint","ispunct","isspace","isupper","isxdigit","tolower","toupper","labs","ldexp","log10","log","malloc","realloc","memchr","memcmp","memcpy","memset","modf","pow","printf","putchar","puts","scanf","sinh","sin","snprintf","sprintf","sqrt","sscanf","strcat","strchr","strcmp","strcpy","strcspn","strlen","strncat","strncmp","strncpy","strpbrk","strrchr","strspn","strstr","tanh","tan","unordered_map","unordered_multiset","unordered_multimap","priority_queue","make_pair","array","shared_ptr","abort","terminate","abs","acos","vfprintf","vprintf","vsprintf","endl","initializer_list","unique_ptr","complex","imaginary","std","string","wstring","cin","cout","cerr","clog","stdin","stdout","stderr","stringstream","istringstream","ostringstream"], - literal:"true false nullptr NULL"},p={className:"function.dispatch",relevance:0, - keywords:m, - begin:t(/\b/,/(?!decltype)/,/(?!if)/,/(?!for)/,/(?!while)/,n.IDENT_RE,(_=/\s*\(/, - t("(?=",_,")")))};var _;const g=[p,l,s,r,n.C_BLOCK_COMMENT_MODE,o,c],b={ - variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{ - beginKeywords:"new throw return else",end:/;/}],keywords:m,contains:g.concat([{ - begin:/\(/,end:/\)/,keywords:m,contains:g.concat(["self"]),relevance:0}]), - relevance:0},f={className:"function",begin:"("+i+"[\\*&\\s]+)+"+u, - returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:m,illegal:/[^\w\s\*&:<>.]/, - contains:[{begin:"decltype\\(auto\\)",keywords:m,relevance:0},{begin:u, - returnBegin:!0,contains:[d],relevance:0},{begin:/::/,relevance:0},{begin:/:/, - endsWithParent:!0,contains:[c,o]},{className:"params",begin:/\(/,end:/\)/, - keywords:m,relevance:0,contains:[r,n.C_BLOCK_COMMENT_MODE,c,o,s,{begin:/\(/, - end:/\)/,keywords:m,relevance:0,contains:["self",r,n.C_BLOCK_COMMENT_MODE,c,o,s] - }]},s,r,n.C_BLOCK_COMMENT_MODE,l]};return{name:"C++", - aliases:["cc","c++","h++","hpp","hh","hxx","cxx"],keywords:m,illegal:"</", - classNameAliases:{"function.dispatch":"built_in"}, - contains:[].concat(b,f,p,g,[l,{ - begin:"\\b(deque|list|queue|priority_queue|pair|stack|vector|map|set|bitset|multiset|multimap|unordered_map|unordered_set|unordered_multiset|unordered_multimap|array)\\s*<", - end:">",keywords:m,contains:["self",s]},{begin:n.IDENT_RE+"::",keywords:m},{ - className:"class",beginKeywords:"enum class struct union",end:/[{;:<>=]/, - contains:[{beginKeywords:"final class struct"},n.TITLE_MODE]}]),exports:{ - preprocessor:l,strings:c,keywords:m}}}})()); - hljs.registerLanguage("csharp",(()=>{"use strict";return e=>{const n={ - keyword:["abstract","as","base","break","case","class","const","continue","do","else","event","explicit","extern","finally","fixed","for","foreach","goto","if","implicit","in","interface","internal","is","lock","namespace","new","operator","out","override","params","private","protected","public","readonly","record","ref","return","sealed","sizeof","stackalloc","static","struct","switch","this","throw","try","typeof","unchecked","unsafe","using","virtual","void","volatile","while"].concat(["add","alias","and","ascending","async","await","by","descending","equals","from","get","global","group","init","into","join","let","nameof","not","notnull","on","or","orderby","partial","remove","select","set","unmanaged","value|0","var","when","where","with","yield"]), - built_in:["bool","byte","char","decimal","delegate","double","dynamic","enum","float","int","long","nint","nuint","object","sbyte","short","string","ulong","uint","ushort"], - literal:["default","false","null","true"]},a=e.inherit(e.TITLE_MODE,{ - begin:"[a-zA-Z](\\.?\\w)*"}),i={className:"number",variants:[{ - begin:"\\b(0b[01']+)"},{ - begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{ - begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)" - }],relevance:0},s={className:"string",begin:'@"',end:'"',contains:[{begin:'""'}] - },t=e.inherit(s,{illegal:/\n/}),r={className:"subst",begin:/\{/,end:/\}/, - keywords:n},l=e.inherit(r,{illegal:/\n/}),c={className:"string",begin:/\$"/, - end:'"',illegal:/\n/,contains:[{begin:/\{\{/},{begin:/\}\}/ - },e.BACKSLASH_ESCAPE,l]},o={className:"string",begin:/\$@"/,end:'"',contains:[{ - begin:/\{\{/},{begin:/\}\}/},{begin:'""'},r]},d=e.inherit(o,{illegal:/\n/, - contains:[{begin:/\{\{/},{begin:/\}\}/},{begin:'""'},l]}) - ;r.contains=[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,i,e.C_BLOCK_COMMENT_MODE], - l.contains=[d,c,t,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,i,e.inherit(e.C_BLOCK_COMMENT_MODE,{ - illegal:/\n/})];const g={variants:[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE] - },E={begin:"<",end:">",contains:[{beginKeywords:"in out"},a] - },_=e.IDENT_RE+"(<"+e.IDENT_RE+"(\\s*,\\s*"+e.IDENT_RE+")*>)?(\\[\\])?",b={ - begin:"@"+e.IDENT_RE,relevance:0};return{name:"C#",aliases:["cs","c#"], - keywords:n,illegal:/::/,contains:[e.COMMENT("///","$",{returnBegin:!0, - contains:[{className:"doctag",variants:[{begin:"///",relevance:0},{ - begin:"\x3c!--|--\x3e"},{begin:"</?",end:">"}]}] - }),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"meta",begin:"#", - end:"$",keywords:{ - "meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum" - }},g,i,{beginKeywords:"class interface",relevance:0,end:/[{;=]/, - illegal:/[^\s:,]/,contains:[{beginKeywords:"where class" - },a,E,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:"namespace", - relevance:0,end:/[{;=]/,illegal:/[^\s:]/, - contains:[a,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{ - beginKeywords:"record",relevance:0,end:/[{;=]/,illegal:/[^\s:]/, - contains:[a,E,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"meta", - begin:"^\\s*\\[",excludeBegin:!0,end:"\\]",excludeEnd:!0,contains:[{ - className:"meta-string",begin:/"/,end:/"/}]},{ - beginKeywords:"new return throw await else",relevance:0},{className:"function", - begin:"("+_+"\\s+)+"+e.IDENT_RE+"\\s*(<.+>\\s*)?\\(",returnBegin:!0, - end:/\s*[{;=]/,excludeEnd:!0,keywords:n,contains:[{ - beginKeywords:"public private protected static internal protected abstract async extern override unsafe virtual new sealed partial", - relevance:0},{begin:e.IDENT_RE+"\\s*(<.+>\\s*)?\\(",returnBegin:!0, - contains:[e.TITLE_MODE,E],relevance:0},{className:"params",begin:/\(/,end:/\)/, - excludeBegin:!0,excludeEnd:!0,keywords:n,relevance:0, - contains:[g,i,e.C_BLOCK_COMMENT_MODE] - },e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},b]}}})()); - hljs.registerLanguage("css",(()=>{"use strict" - ;const e=["a","abbr","address","article","aside","audio","b","blockquote","body","button","canvas","caption","cite","code","dd","del","details","dfn","div","dl","dt","em","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","html","i","iframe","img","input","ins","kbd","label","legend","li","main","mark","menu","nav","object","ol","p","q","quote","samp","section","span","strong","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","ul","var","video"],t=["any-hover","any-pointer","aspect-ratio","color","color-gamut","color-index","device-aspect-ratio","device-height","device-width","display-mode","forced-colors","grid","height","hover","inverted-colors","monochrome","orientation","overflow-block","overflow-inline","pointer","prefers-color-scheme","prefers-contrast","prefers-reduced-motion","prefers-reduced-transparency","resolution","scan","scripting","update","width","min-width","max-width","min-height","max-height"],i=["active","any-link","blank","checked","current","default","defined","dir","disabled","drop","empty","enabled","first","first-child","first-of-type","fullscreen","future","focus","focus-visible","focus-within","has","host","host-context","hover","indeterminate","in-range","invalid","is","lang","last-child","last-of-type","left","link","local-link","not","nth-child","nth-col","nth-last-child","nth-last-col","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","past","placeholder-shown","read-only","read-write","required","right","root","scope","target","target-within","user-invalid","valid","visited","where"],o=["after","backdrop","before","cue","cue-region","first-letter","first-line","grammar-error","marker","part","placeholder","selection","slotted","spelling-error"],r=["align-content","align-items","align-self","animation","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","auto","backface-visibility","background","background-attachment","background-clip","background-color","background-image","background-origin","background-position","background-repeat","background-size","border","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-style","border-top","border-top-color","border-top-left-radius","border-top-right-radius","border-top-style","border-top-width","border-width","bottom","box-decoration-break","box-shadow","box-sizing","break-after","break-before","break-inside","caption-side","clear","clip","clip-path","color","column-count","column-fill","column-gap","column-rule","column-rule-color","column-rule-style","column-rule-width","column-span","column-width","columns","content","counter-increment","counter-reset","cursor","direction","display","empty-cells","filter","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","font","font-display","font-family","font-feature-settings","font-kerning","font-language-override","font-size","font-size-adjust","font-smoothing","font-stretch","font-style","font-variant","font-variant-ligatures","font-variation-settings","font-weight","height","hyphens","icon","image-orientation","image-rendering","image-resolution","ime-mode","inherit","initial","justify-content","left","letter-spacing","line-height","list-style","list-style-image","list-style-position","list-style-type","margin","margin-bottom","margin-left","margin-right","margin-top","marks","mask","max-height","max-width","min-height","min-width","nav-down","nav-index","nav-left","nav-right","nav-up","none","normal","object-fit","object-position","opacity","order","orphans","outline","outline-color","outline-offset","outline-style","outline-width","overflow","overflow-wrap","overflow-x","overflow-y","padding","padding-bottom","padding-left","padding-right","padding-top","page-break-after","page-break-before","page-break-inside","perspective","perspective-origin","pointer-events","position","quotes","resize","right","src","tab-size","table-layout","text-align","text-align-last","text-decoration","text-decoration-color","text-decoration-line","text-decoration-style","text-indent","text-overflow","text-rendering","text-shadow","text-transform","text-underline-position","top","transform","transform-origin","transform-style","transition","transition-delay","transition-duration","transition-property","transition-timing-function","unicode-bidi","vertical-align","visibility","white-space","widows","width","word-break","word-spacing","word-wrap","z-index"].reverse() - ;return n=>{const a=(e=>({IMPORTANT:{className:"meta",begin:"!important"}, - HEXCOLOR:{className:"number",begin:"#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})"}, - ATTRIBUTE_SELECTOR_MODE:{className:"selector-attr",begin:/\[/,end:/\]/, - illegal:"$",contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]} - }))(n),l=[n.APOS_STRING_MODE,n.QUOTE_STRING_MODE];return{name:"CSS", - case_insensitive:!0,illegal:/[=|'\$]/,keywords:{keyframePosition:"from to"}, - classNameAliases:{keyframePosition:"selector-tag"}, - contains:[n.C_BLOCK_COMMENT_MODE,{begin:/-(webkit|moz|ms|o)-(?=[a-z])/ - },n.CSS_NUMBER_MODE,{className:"selector-id",begin:/#[A-Za-z0-9_-]+/,relevance:0 - },{className:"selector-class",begin:"\\.[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0 - },a.ATTRIBUTE_SELECTOR_MODE,{className:"selector-pseudo",variants:[{ - begin:":("+i.join("|")+")"},{begin:"::("+o.join("|")+")"}]},{ - className:"attribute",begin:"\\b("+r.join("|")+")\\b"},{begin:":",end:"[;}]", - contains:[a.HEXCOLOR,a.IMPORTANT,n.CSS_NUMBER_MODE,...l,{ - begin:/(url|data-uri)\(/,end:/\)/,relevance:0,keywords:{built_in:"url data-uri" - },contains:[{className:"string",begin:/[^)]/,endsWithParent:!0,excludeEnd:!0}] - },{className:"built_in",begin:/[\w-]+(?=\()/}]},{ - begin:(s=/@/,((...e)=>e.map((e=>(e=>e?"string"==typeof e?e:e.source:null)(e))).join(""))("(?=",s,")")), - end:"[{;]",relevance:0,illegal:/:/,contains:[{className:"keyword", - begin:/@-?\w[\w]*(-\w+)*/},{begin:/\s/,endsWithParent:!0,excludeEnd:!0, - relevance:0,keywords:{$pattern:/[a-z-]+/,keyword:"and or not only", - attribute:t.join(" ")},contains:[{begin:/[a-z-]+(?=:)/,className:"attribute" - },...l,n.CSS_NUMBER_MODE]}]},{className:"selector-tag", - begin:"\\b("+e.join("|")+")\\b"}]};var s}})()); - hljs.registerLanguage("diff",(()=>{"use strict";return e=>({name:"Diff", - aliases:["patch"],contains:[{className:"meta",relevance:10,variants:[{ - begin:/^@@ +-\d+,\d+ +\+\d+,\d+ +@@/},{begin:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{ - begin:/^--- +\d+,\d+ +----$/}]},{className:"comment",variants:[{begin:/Index: /, - end:/$/},{begin:/^index/,end:/$/},{begin:/={3,}/,end:/$/},{begin:/^-{3}/,end:/$/ - },{begin:/^\*{3} /,end:/$/},{begin:/^\+{3}/,end:/$/},{begin:/^\*{15}$/},{ - begin:/^diff --git/,end:/$/}]},{className:"addition",begin:/^\+/,end:/$/},{ - className:"deletion",begin:/^-/,end:/$/},{className:"addition",begin:/^!/, - end:/$/}]})})()); - hljs.registerLanguage("go",(()=>{"use strict";return e=>{const n={ - keyword:"break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune", - literal:"true false iota nil", - built_in:"append cap close complex copy imag len make new panic print println real recover delete" - };return{name:"Go",aliases:["golang"],keywords:n,illegal:"</", - contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"string", - variants:[e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{begin:"`",end:"`"}]},{ - className:"number",variants:[{begin:e.C_NUMBER_RE+"[i]",relevance:1 - },e.C_NUMBER_MODE]},{begin:/:=/},{className:"function",beginKeywords:"func", - end:"\\s*(\\{|$)",excludeEnd:!0,contains:[e.TITLE_MODE,{className:"params", - begin:/\(/,end:/\)/,keywords:n,illegal:/["']/}]}]}}})()); - hljs.registerLanguage("http",(()=>{"use strict";function e(...e){ - return e.map((e=>{return(n=e)?"string"==typeof n?n:n.source:null;var n - })).join("")}return n=>{const a="HTTP/(2|1\\.[01])",s={className:"attribute", - begin:e("^",/[A-Za-z][A-Za-z0-9-]*/,"(?=\\:\\s)"),starts:{contains:[{ - className:"punctuation",begin:/: /,relevance:0,starts:{end:"$",relevance:0}}]} - },t=[s,{begin:"\\n\\n",starts:{subLanguage:[],endsWithParent:!0}}];return{ - name:"HTTP",aliases:["https"],illegal:/\S/,contains:[{begin:"^(?="+a+" \\d{3})", - end:/$/,contains:[{className:"meta",begin:a},{className:"number", - begin:"\\b\\d{3}\\b"}],starts:{end:/\b\B/,illegal:/\S/,contains:t}},{ - begin:"(?=^[A-Z]+ (.*?) "+a+"$)",end:/$/,contains:[{className:"string", - begin:" ",end:" ",excludeBegin:!0,excludeEnd:!0},{className:"meta",begin:a},{ - className:"keyword",begin:"[A-Z]+"}],starts:{end:/\b\B/,illegal:/\S/,contains:t} - },n.inherit(s,{relevance:0})]}}})()); - hljs.registerLanguage("ini",(()=>{"use strict";function e(e){ - return e?"string"==typeof e?e:e.source:null}function n(...n){ - return n.map((n=>e(n))).join("")}return s=>{const a={className:"number", - relevance:0,variants:[{begin:/([+-]+)?[\d]+_[\d_]+/},{begin:s.NUMBER_RE}] - },i=s.COMMENT();i.variants=[{begin:/;/,end:/$/},{begin:/#/,end:/$/}];const t={ - className:"variable",variants:[{begin:/\$[\w\d"][\w\d_]*/},{begin:/\$\{(.*?)\}/ - }]},r={className:"literal",begin:/\bon|off|true|false|yes|no\b/},l={ - className:"string",contains:[s.BACKSLASH_ESCAPE],variants:[{begin:"'''", - end:"'''",relevance:10},{begin:'"""',end:'"""',relevance:10},{begin:'"',end:'"' - },{begin:"'",end:"'"}]},c={begin:/\[/,end:/\]/,contains:[i,r,t,l,a,"self"], - relevance:0 - },g="("+[/[A-Za-z0-9_-]+/,/"(\\"|[^"])*"/,/'[^']*'/].map((n=>e(n))).join("|")+")" - ;return{name:"TOML, also INI",aliases:["toml"],case_insensitive:!0,illegal:/\S/, - contains:[i,{className:"section",begin:/\[+/,end:/\]+/},{ - begin:n(g,"(\\s*\\.\\s*",g,")*",n("(?=",/\s*=\s*[^#\s]/,")")),className:"attr", - starts:{end:/$/,contains:[i,c,r,t,l,a]}}]}}})()); - hljs.registerLanguage("java",(()=>{"use strict" - ;var e="\\.([0-9](_*[0-9])*)",n="[0-9a-fA-F](_*[0-9a-fA-F])*",a={ - className:"number",variants:[{ - begin:`(\\b([0-9](_*[0-9])*)((${e})|\\.)?|(${e}))[eE][+-]?([0-9](_*[0-9])*)[fFdD]?\\b` - },{begin:`\\b([0-9](_*[0-9])*)((${e})[fFdD]?\\b|\\.([fFdD]\\b)?)`},{ - begin:`(${e})[fFdD]?\\b`},{begin:"\\b([0-9](_*[0-9])*)[fFdD]\\b"},{ - begin:`\\b0[xX]((${n})\\.?|(${n})?\\.(${n}))[pP][+-]?([0-9](_*[0-9])*)[fFdD]?\\b` - },{begin:"\\b(0|[1-9](_*[0-9])*)[lL]?\\b"},{begin:`\\b0[xX](${n})[lL]?\\b`},{ - begin:"\\b0(_*[0-7])*[lL]?\\b"},{begin:"\\b0[bB][01](_*[01])*[lL]?\\b"}], - relevance:0};return e=>{ - var n="false synchronized int abstract float private char boolean var static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",s={ - className:"meta",begin:"@[\xc0-\u02b8a-zA-Z_$][\xc0-\u02b8a-zA-Z_$0-9]*", - contains:[{begin:/\(/,end:/\)/,contains:["self"]}]};const r=a;return{ - name:"Java",aliases:["jsp"],keywords:n,illegal:/<\/|#/, - contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/, - relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]}),{ - begin:/import java\.[a-z]+\./,keywords:"import",relevance:2 - },e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{ - className:"class",beginKeywords:"class interface enum",end:/[{;=]/, - excludeEnd:!0,relevance:1,keywords:"class interface enum",illegal:/[:"\[\]]/, - contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{ - beginKeywords:"new throw return else",relevance:0},{className:"class", - begin:"record\\s+"+e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,excludeEnd:!0, - end:/[{;=]/,keywords:n,contains:[{beginKeywords:"record"},{ - begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0, - contains:[e.UNDERSCORE_TITLE_MODE]},{className:"params",begin:/\(/,end:/\)/, - keywords:n,relevance:0,contains:[e.C_BLOCK_COMMENT_MODE] - },e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"function", - begin:"([\xc0-\u02b8a-zA-Z_$][\xc0-\u02b8a-zA-Z_$0-9]*(<[\xc0-\u02b8a-zA-Z_$][\xc0-\u02b8a-zA-Z_$0-9]*(\\s*,\\s*[\xc0-\u02b8a-zA-Z_$][\xc0-\u02b8a-zA-Z_$0-9]*)*>)?\\s+)+"+e.UNDERSCORE_IDENT_RE+"\\s*\\(", - returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:n,contains:[{ - begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0, - contains:[e.UNDERSCORE_TITLE_MODE]},{className:"params",begin:/\(/,end:/\)/, - keywords:n,relevance:0, - contains:[s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,r,e.C_BLOCK_COMMENT_MODE] - },e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},r,s]}}})()); - hljs.registerLanguage("javascript",(()=>{"use strict" - ;const e="[A-Za-z$_][0-9A-Za-z$_]*",n=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],a=["true","false","null","undefined","NaN","Infinity"],s=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer","BigInt64Array","BigUint64Array","BigInt"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]) - ;function r(e){return t("(?=",e,")")}function t(...e){return e.map((e=>{ - return(n=e)?"string"==typeof n?n:n.source:null;var n})).join("")}return i=>{ - const c=e,o={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/, - isTrulyOpeningTag:(e,n)=>{const a=e[0].length+e.index,s=e.input[a] - ;"<"!==s?">"===s&&(((e,{after:n})=>{const a="</"+e[0].slice(1) - ;return-1!==e.input.indexOf(a,n)})(e,{after:a - })||n.ignoreMatch()):n.ignoreMatch()}},l={$pattern:e,keyword:n,literal:a, - built_in:s},g="\\.([0-9](_?[0-9])*)",b="0|[1-9](_?[0-9])*|0[0-7]*[89][0-9]*",d={ - className:"number",variants:[{ - begin:`(\\b(${b})((${g})|\\.)?|(${g}))[eE][+-]?([0-9](_?[0-9])*)\\b`},{ - begin:`\\b(${b})\\b((${g})\\b|\\.)?|(${g})\\b`},{ - begin:"\\b(0|[1-9](_?[0-9])*)n\\b"},{ - begin:"\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?\\b"},{ - begin:"\\b0[bB][0-1](_?[0-1])*n?\\b"},{begin:"\\b0[oO][0-7](_?[0-7])*n?\\b"},{ - begin:"\\b0[0-7]+n?\\b"}],relevance:0},E={className:"subst",begin:"\\$\\{", - end:"\\}",keywords:l,contains:[]},u={begin:"html`",end:"",starts:{end:"`", - returnEnd:!1,contains:[i.BACKSLASH_ESCAPE,E],subLanguage:"xml"}},_={ - begin:"css`",end:"",starts:{end:"`",returnEnd:!1, - contains:[i.BACKSLASH_ESCAPE,E],subLanguage:"css"}},m={className:"string", - begin:"`",end:"`",contains:[i.BACKSLASH_ESCAPE,E]},y={className:"comment", - variants:[i.COMMENT(/\/\*\*(?!\/)/,"\\*/",{relevance:0,contains:[{ - className:"doctag",begin:"@[A-Za-z]+",contains:[{className:"type",begin:"\\{", - end:"\\}",relevance:0},{className:"variable",begin:c+"(?=\\s*(-)|$)", - endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}] - }),i.C_BLOCK_COMMENT_MODE,i.C_LINE_COMMENT_MODE] - },N=[i.APOS_STRING_MODE,i.QUOTE_STRING_MODE,u,_,m,d,i.REGEXP_MODE] - ;E.contains=N.concat({begin:/\{/,end:/\}/,keywords:l,contains:["self"].concat(N) - });const A=[].concat(y,E.contains),f=A.concat([{begin:/\(/,end:/\)/,keywords:l, - contains:["self"].concat(A)}]),p={className:"params",begin:/\(/,end:/\)/, - excludeBegin:!0,excludeEnd:!0,keywords:l,contains:f};return{name:"Javascript", - aliases:["js","jsx","mjs","cjs"],keywords:l,exports:{PARAMS_CONTAINS:f}, - illegal:/#(?![$_A-z])/,contains:[i.SHEBANG({label:"shebang",binary:"node", - relevance:5}),{label:"use_strict",className:"meta",relevance:10, - begin:/^\s*['"]use (strict|asm)['"]/ - },i.APOS_STRING_MODE,i.QUOTE_STRING_MODE,u,_,m,y,d,{ - begin:t(/[{,\n]\s*/,r(t(/(((\/\/.*$)|(\/\*(\*[^/]|[^*])*\*\/))\s*)*/,c+"\\s*:"))), - relevance:0,contains:[{className:"attr",begin:c+r("\\s*:"),relevance:0}]},{ - begin:"("+i.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*", - keywords:"return throw case",contains:[y,i.REGEXP_MODE,{className:"function", - begin:"(\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)|"+i.UNDERSCORE_IDENT_RE+")\\s*=>", - returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{ - begin:i.UNDERSCORE_IDENT_RE,relevance:0},{className:null,begin:/\(\s*\)/,skip:!0 - },{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:l,contains:f}]}] - },{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{ - variants:[{begin:"<>",end:"</>"},{begin:o.begin,"on:begin":o.isTrulyOpeningTag, - end:o.end}],subLanguage:"xml",contains:[{begin:o.begin,end:o.end,skip:!0, - contains:["self"]}]}],relevance:0},{className:"function", - beginKeywords:"function",end:/[{;]/,excludeEnd:!0,keywords:l, - contains:["self",i.inherit(i.TITLE_MODE,{begin:c}),p],illegal:/%/},{ - beginKeywords:"while if switch catch for"},{className:"function", - begin:i.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{", - returnBegin:!0,contains:[p,i.inherit(i.TITLE_MODE,{begin:c})]},{variants:[{ - begin:"\\."+c},{begin:"\\$"+c}],relevance:0},{className:"class", - beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"[\]]/,contains:[{ - beginKeywords:"extends"},i.UNDERSCORE_TITLE_MODE]},{begin:/\b(?=constructor)/, - end:/[{;]/,excludeEnd:!0,contains:[i.inherit(i.TITLE_MODE,{begin:c}),"self",p] - },{begin:"(get|set)\\s+(?="+c+"\\()",end:/\{/,keywords:"get set", - contains:[i.inherit(i.TITLE_MODE,{begin:c}),{begin:/\(\)/},p]},{begin:/\$[(.]/}] - }}})()); - hljs.registerLanguage("json",(()=>{"use strict";return n=>{const e={ - literal:"true false null" - },i=[n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE],a=[n.QUOTE_STRING_MODE,n.C_NUMBER_MODE],l={ - end:",",endsWithParent:!0,excludeEnd:!0,contains:a,keywords:e},t={begin:/\{/, - end:/\}/,contains:[{className:"attr",begin:/"/,end:/"/, - contains:[n.BACKSLASH_ESCAPE],illegal:"\\n"},n.inherit(l,{begin:/:/ - })].concat(i),illegal:"\\S"},s={begin:"\\[",end:"\\]",contains:[n.inherit(l)], - illegal:"\\S"};return a.push(t,s),i.forEach((n=>{a.push(n)})),{name:"JSON", - contains:a,keywords:e,illegal:"\\S"}}})()); - hljs.registerLanguage("kotlin",(()=>{"use strict" - ;var e="\\.([0-9](_*[0-9])*)",n="[0-9a-fA-F](_*[0-9a-fA-F])*",a={ - className:"number",variants:[{ - begin:`(\\b([0-9](_*[0-9])*)((${e})|\\.)?|(${e}))[eE][+-]?([0-9](_*[0-9])*)[fFdD]?\\b` - },{begin:`\\b([0-9](_*[0-9])*)((${e})[fFdD]?\\b|\\.([fFdD]\\b)?)`},{ - begin:`(${e})[fFdD]?\\b`},{begin:"\\b([0-9](_*[0-9])*)[fFdD]\\b"},{ - begin:`\\b0[xX]((${n})\\.?|(${n})?\\.(${n}))[pP][+-]?([0-9](_*[0-9])*)[fFdD]?\\b` - },{begin:"\\b(0|[1-9](_*[0-9])*)[lL]?\\b"},{begin:`\\b0[xX](${n})[lL]?\\b`},{ - begin:"\\b0(_*[0-7])*[lL]?\\b"},{begin:"\\b0[bB][01](_*[01])*[lL]?\\b"}], - relevance:0};return e=>{const n={ - keyword:"abstract as val var vararg get set class object open private protected public noinline crossinline dynamic final enum if else do while for when throw try catch finally import package is in fun override companion reified inline lateinit init interface annotation data sealed internal infix operator out by constructor super tailrec where const inner suspend typealias external expect actual", - built_in:"Byte Short Char Int Long Boolean Float Double Void Unit Nothing", - literal:"true false null"},i={className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"@" - },s={className:"subst",begin:/\$\{/,end:/\}/,contains:[e.C_NUMBER_MODE]},t={ - className:"variable",begin:"\\$"+e.UNDERSCORE_IDENT_RE},r={className:"string", - variants:[{begin:'"""',end:'"""(?=[^"])',contains:[t,s]},{begin:"'",end:"'", - illegal:/\n/,contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"',illegal:/\n/, - contains:[e.BACKSLASH_ESCAPE,t,s]}]};s.contains.push(r);const l={ - className:"meta", - begin:"@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\s*:(?:\\s*"+e.UNDERSCORE_IDENT_RE+")?" - },c={className:"meta",begin:"@"+e.UNDERSCORE_IDENT_RE,contains:[{begin:/\(/, - end:/\)/,contains:[e.inherit(r,{className:"meta-string"})]}] - },o=a,b=e.COMMENT("/\\*","\\*/",{contains:[e.C_BLOCK_COMMENT_MODE]}),E={ - variants:[{className:"type",begin:e.UNDERSCORE_IDENT_RE},{begin:/\(/,end:/\)/, - contains:[]}]},d=E;return d.variants[1].contains=[E],E.variants[1].contains=[d], - {name:"Kotlin",aliases:["kt","kts"],keywords:n, - contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag", - begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,b,{className:"keyword", - begin:/\b(break|continue|return|this)\b/,starts:{contains:[{className:"symbol", - begin:/@\w+/}]}},i,l,c,{className:"function",beginKeywords:"fun",end:"[(]|$", - returnBegin:!0,excludeEnd:!0,keywords:n,relevance:5,contains:[{ - begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0, - contains:[e.UNDERSCORE_TITLE_MODE]},{className:"type",begin:/</,end:/>/, - keywords:"reified",relevance:0},{className:"params",begin:/\(/,end:/\)/, - endsParent:!0,keywords:n,relevance:0,contains:[{begin:/:/,end:/[=,\/]/, - endsWithParent:!0,contains:[E,e.C_LINE_COMMENT_MODE,b],relevance:0 - },e.C_LINE_COMMENT_MODE,b,l,c,r,e.C_NUMBER_MODE]},b]},{className:"class", - beginKeywords:"class interface trait",end:/[:\{(]|$/,excludeEnd:!0, - illegal:"extends implements",contains:[{ - beginKeywords:"public protected internal private constructor" - },e.UNDERSCORE_TITLE_MODE,{className:"type",begin:/</,end:/>/,excludeBegin:!0, - excludeEnd:!0,relevance:0},{className:"type",begin:/[,:]\s*/,end:/[<\(,]|$/, - excludeBegin:!0,returnEnd:!0},l,c]},r,{className:"meta",begin:"^#!/usr/bin/env", - end:"$",illegal:"\n"},o]}}})()); - hljs.registerLanguage("less",(()=>{"use strict" - ;const e=["a","abbr","address","article","aside","audio","b","blockquote","body","button","canvas","caption","cite","code","dd","del","details","dfn","div","dl","dt","em","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","html","i","iframe","img","input","ins","kbd","label","legend","li","main","mark","menu","nav","object","ol","p","q","quote","samp","section","span","strong","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","ul","var","video"],t=["any-hover","any-pointer","aspect-ratio","color","color-gamut","color-index","device-aspect-ratio","device-height","device-width","display-mode","forced-colors","grid","height","hover","inverted-colors","monochrome","orientation","overflow-block","overflow-inline","pointer","prefers-color-scheme","prefers-contrast","prefers-reduced-motion","prefers-reduced-transparency","resolution","scan","scripting","update","width","min-width","max-width","min-height","max-height"],i=["active","any-link","blank","checked","current","default","defined","dir","disabled","drop","empty","enabled","first","first-child","first-of-type","fullscreen","future","focus","focus-visible","focus-within","has","host","host-context","hover","indeterminate","in-range","invalid","is","lang","last-child","last-of-type","left","link","local-link","not","nth-child","nth-col","nth-last-child","nth-last-col","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","past","placeholder-shown","read-only","read-write","required","right","root","scope","target","target-within","user-invalid","valid","visited","where"],o=["after","backdrop","before","cue","cue-region","first-letter","first-line","grammar-error","marker","part","placeholder","selection","slotted","spelling-error"],n=["align-content","align-items","align-self","animation","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","auto","backface-visibility","background","background-attachment","background-clip","background-color","background-image","background-origin","background-position","background-repeat","background-size","border","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-style","border-top","border-top-color","border-top-left-radius","border-top-right-radius","border-top-style","border-top-width","border-width","bottom","box-decoration-break","box-shadow","box-sizing","break-after","break-before","break-inside","caption-side","clear","clip","clip-path","color","column-count","column-fill","column-gap","column-rule","column-rule-color","column-rule-style","column-rule-width","column-span","column-width","columns","content","counter-increment","counter-reset","cursor","direction","display","empty-cells","filter","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","font","font-display","font-family","font-feature-settings","font-kerning","font-language-override","font-size","font-size-adjust","font-smoothing","font-stretch","font-style","font-variant","font-variant-ligatures","font-variation-settings","font-weight","height","hyphens","icon","image-orientation","image-rendering","image-resolution","ime-mode","inherit","initial","justify-content","left","letter-spacing","line-height","list-style","list-style-image","list-style-position","list-style-type","margin","margin-bottom","margin-left","margin-right","margin-top","marks","mask","max-height","max-width","min-height","min-width","nav-down","nav-index","nav-left","nav-right","nav-up","none","normal","object-fit","object-position","opacity","order","orphans","outline","outline-color","outline-offset","outline-style","outline-width","overflow","overflow-wrap","overflow-x","overflow-y","padding","padding-bottom","padding-left","padding-right","padding-top","page-break-after","page-break-before","page-break-inside","perspective","perspective-origin","pointer-events","position","quotes","resize","right","src","tab-size","table-layout","text-align","text-align-last","text-decoration","text-decoration-color","text-decoration-line","text-decoration-style","text-indent","text-overflow","text-rendering","text-shadow","text-transform","text-underline-position","top","transform","transform-origin","transform-style","transition","transition-delay","transition-duration","transition-property","transition-timing-function","unicode-bidi","vertical-align","visibility","white-space","widows","width","word-break","word-spacing","word-wrap","z-index"].reverse(),r=i.concat(o) - ;return a=>{const s=(e=>({IMPORTANT:{className:"meta",begin:"!important"}, - HEXCOLOR:{className:"number",begin:"#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})"}, - ATTRIBUTE_SELECTOR_MODE:{className:"selector-attr",begin:/\[/,end:/\]/, - illegal:"$",contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]} - }))(a),l=r,d="([\\w-]+|@\\{[\\w-]+\\})",c=[],g=[],b=e=>({className:"string", - begin:"~?"+e+".*?"+e}),m=(e,t,i)=>({className:e,begin:t,relevance:i}),u={ - $pattern:/[a-z-]+/,keyword:"and or not only",attribute:t.join(" ")},p={ - begin:"\\(",end:"\\)",contains:g,keywords:u,relevance:0} - ;g.push(a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,b("'"),b('"'),a.CSS_NUMBER_MODE,{ - begin:"(url|data-uri)\\(",starts:{className:"string",end:"[\\)\\n]", - excludeEnd:!0} - },s.HEXCOLOR,p,m("variable","@@?[\\w-]+",10),m("variable","@\\{[\\w-]+\\}"),m("built_in","~?`[^`]*?`"),{ - className:"attribute",begin:"[\\w-]+\\s*:",end:":",returnBegin:!0,excludeEnd:!0 - },s.IMPORTANT);const f=g.concat({begin:/\{/,end:/\}/,contains:c}),h={ - beginKeywords:"when",endsWithParent:!0,contains:[{beginKeywords:"and not" - }].concat(g)},w={begin:d+"\\s*:",returnBegin:!0,end:/[;}]/,relevance:0, - contains:[{begin:/-(webkit|moz|ms|o)-/},{className:"attribute", - begin:"\\b("+n.join("|")+")\\b",end:/(?=:)/,starts:{endsWithParent:!0, - illegal:"[<=$]",relevance:0,contains:g}}]},v={className:"keyword", - begin:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b", - starts:{end:"[;{}]",keywords:u,returnEnd:!0,contains:g,relevance:0}},y={ - className:"variable",variants:[{begin:"@[\\w-]+\\s*:",relevance:15},{ - begin:"@[\\w-]+"}],starts:{end:"[;}]",returnEnd:!0,contains:f}},k={variants:[{ - begin:"[\\.#:&\\[>]",end:"[;{}]"},{begin:d,end:/\{/}],returnBegin:!0, - returnEnd:!0,illegal:"[<='$\"]",relevance:0, - contains:[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,h,m("keyword","all\\b"),m("variable","@\\{[\\w-]+\\}"),{ - begin:"\\b("+e.join("|")+")\\b",className:"selector-tag" - },m("selector-tag",d+"%?",0),m("selector-id","#"+d),m("selector-class","\\."+d,0),m("selector-tag","&",0),s.ATTRIBUTE_SELECTOR_MODE,{ - className:"selector-pseudo",begin:":("+i.join("|")+")"},{ - className:"selector-pseudo",begin:"::("+o.join("|")+")"},{begin:"\\(",end:"\\)", - contains:f},{begin:"!important"}]},E={begin:`[\\w-]+:(:)?(${l.join("|")})`, - returnBegin:!0,contains:[k]} - ;return c.push(a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,v,y,E,w,k),{ - name:"Less",case_insensitive:!0,illegal:"[=>'/<($\"]",contains:c}}})()); - hljs.registerLanguage("lua",(()=>{"use strict";return e=>{ - const t="\\[=*\\[",a="\\]=*\\]",n={begin:t,end:a,contains:["self"] - },o=[e.COMMENT("--(?!\\[=*\\[)","$"),e.COMMENT("--\\[=*\\[",a,{contains:[n], - relevance:10})];return{name:"Lua",keywords:{$pattern:e.UNDERSCORE_IDENT_RE, - literal:"true false nil", - keyword:"and break do else elseif end for goto if in local not or repeat return then until while", - built_in:"_G _ENV _VERSION __index __newindex __mode __call __metatable __tostring __len __gc __add __sub __mul __div __mod __pow __concat __unm __eq __lt __le assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstring module next pairs pcall print rawequal rawget rawset require select setfenv setmetatable tonumber tostring type unpack xpcall arg self coroutine resume yield status wrap create running debug getupvalue debug sethook getmetatable gethook setmetatable setlocal traceback setfenv getinfo setupvalue getlocal getregistry getfenv io lines write close flush open output type read stderr stdin input stdout popen tmpfile math log max acos huge ldexp pi cos tanh pow deg tan cosh sinh random randomseed frexp ceil floor rad abs sqrt modf asin min mod fmod log10 atan2 exp sin atan os exit setlocale date getenv difftime remove time clock tmpname rename execute package preload loadlib loaded loaders cpath config path seeall string sub upper len gfind rep find match char dump gmatch reverse byte format gsub lower table setn insert getn foreachi maxn foreach concat sort remove" - },contains:o.concat([{className:"function",beginKeywords:"function",end:"\\)", - contains:[e.inherit(e.TITLE_MODE,{ - begin:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),{className:"params", - begin:"\\(",endsWithParent:!0,contains:o}].concat(o) - },e.C_NUMBER_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"string", - begin:t,end:a,contains:[n],relevance:5}])}}})()); - hljs.registerLanguage("makefile",(()=>{"use strict";return e=>{const i={ - className:"variable",variants:[{begin:"\\$\\("+e.UNDERSCORE_IDENT_RE+"\\)", - contains:[e.BACKSLASH_ESCAPE]},{begin:/\$[@%<?\^\+\*]/}]},a={className:"string", - begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,i]},n={className:"variable", - begin:/\$\([\w-]+\s/,end:/\)/,keywords:{ - built_in:"subst patsubst strip findstring filter filter-out sort word wordlist firstword lastword dir notdir suffix basename addsuffix addprefix join wildcard realpath abspath error warning shell origin flavor foreach if or and call eval file value" - },contains:[i]},s={begin:"^"+e.UNDERSCORE_IDENT_RE+"\\s*(?=[:+?]?=)"},r={ - className:"section",begin:/^[^\s]+:/,end:/$/,contains:[i]};return{ - name:"Makefile",aliases:["mk","mak","make"],keywords:{$pattern:/[\w-]+/, - keyword:"define endef undefine ifdef ifndef ifeq ifneq else endif include -include sinclude override export unexport private vpath" - },contains:[e.HASH_COMMENT_MODE,i,a,n,s,{className:"meta",begin:/^\.PHONY:/, - end:/$/,keywords:{$pattern:/[\.\w]+/,"meta-keyword":".PHONY"}},r]}}})()); - hljs.registerLanguage("xml",(()=>{"use strict";function e(e){ - return e?"string"==typeof e?e:e.source:null}function n(e){return a("(?=",e,")")} - function a(...n){return n.map((n=>e(n))).join("")}function s(...n){ - return"("+n.map((n=>e(n))).join("|")+")"}return e=>{ - const t=a(/[A-Z_]/,a("(",/[A-Z0-9_.-]*:/,")?"),/[A-Z0-9_.-]*/),i={ - className:"symbol",begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},r={begin:/\s/, - contains:[{className:"meta-keyword",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}] - },c=e.inherit(r,{begin:/\(/,end:/\)/}),l=e.inherit(e.APOS_STRING_MODE,{ - className:"meta-string"}),g=e.inherit(e.QUOTE_STRING_MODE,{ - className:"meta-string"}),m={endsWithParent:!0,illegal:/</,relevance:0, - contains:[{className:"attr",begin:/[A-Za-z0-9._:-]+/,relevance:0},{begin:/=\s*/, - relevance:0,contains:[{className:"string",endsParent:!0,variants:[{begin:/"/, - end:/"/,contains:[i]},{begin:/'/,end:/'/,contains:[i]},{begin:/[^\s"'=<>`]+/}]}] - }]};return{name:"HTML, XML", - aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"], - case_insensitive:!0,contains:[{className:"meta",begin:/<![a-z]/,end:/>/, - relevance:10,contains:[r,g,l,c,{begin:/\[/,end:/\]/,contains:[{className:"meta", - begin:/<![a-z]/,end:/>/,contains:[r,c,g,l]}]}]},e.COMMENT(/<!--/,/-->/,{ - relevance:10}),{begin:/<!\[CDATA\[/,end:/\]\]>/,relevance:10},i,{ - className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag", - begin:/<style(?=\s|>)/,end:/>/,keywords:{name:"style"},contains:[m],starts:{ - end:/<\/style>/,returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag", - begin:/<script(?=\s|>)/,end:/>/,keywords:{name:"script"},contains:[m],starts:{ - end:/<\/script>/,returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{ - className:"tag",begin:/<>|<\/>/},{className:"tag", - begin:a(/</,n(a(t,s(/\/>/,/>/,/\s/)))),end:/\/?>/,contains:[{className:"name", - begin:t,relevance:0,starts:m}]},{className:"tag",begin:a(/<\//,n(a(t,/>/))), - contains:[{className:"name",begin:t,relevance:0},{begin:/>/,relevance:0, - endsParent:!0}]}]}}})()); - hljs.registerLanguage("markdown",(()=>{"use strict";function n(...n){ - return n.map((n=>{return(e=n)?"string"==typeof e?e:e.source:null;var e - })).join("")}return e=>{const a={begin:/<\/?[A-Za-z_]/,end:">", - subLanguage:"xml",relevance:0},i={variants:[{begin:/\[.+?\]\[.*?\]/,relevance:0 - },{begin:/\[.+?\]\(((data|javascript|mailto):|(?:http|ftp)s?:\/\/).*?\)/, - relevance:2},{begin:n(/\[.+?\]\(/,/[A-Za-z][A-Za-z0-9+.-]*/,/:\/\/.*?\)/), - relevance:2},{begin:/\[.+?\]\([./?&#].*?\)/,relevance:1},{ - begin:/\[.+?\]\(.*?\)/,relevance:0}],returnBegin:!0,contains:[{ - className:"string",relevance:0,begin:"\\[",end:"\\]",excludeBegin:!0, - returnEnd:!0},{className:"link",relevance:0,begin:"\\]\\(",end:"\\)", - excludeBegin:!0,excludeEnd:!0},{className:"symbol",relevance:0,begin:"\\]\\[", - end:"\\]",excludeBegin:!0,excludeEnd:!0}]},s={className:"strong",contains:[], - variants:[{begin:/_{2}/,end:/_{2}/},{begin:/\*{2}/,end:/\*{2}/}]},c={ - className:"emphasis",contains:[],variants:[{begin:/\*(?!\*)/,end:/\*/},{ - begin:/_(?!_)/,end:/_/,relevance:0}]};s.contains.push(c),c.contains.push(s) - ;let t=[a,i] - ;return s.contains=s.contains.concat(t),c.contains=c.contains.concat(t), - t=t.concat(s,c),{name:"Markdown",aliases:["md","mkdown","mkd"],contains:[{ - className:"section",variants:[{begin:"^#{1,6}",end:"$",contains:t},{ - begin:"(?=^.+?\\n[=-]{2,}$)",contains:[{begin:"^[=-]*$"},{begin:"^",end:"\\n", - contains:t}]}]},a,{className:"bullet",begin:"^[ \t]*([*+-]|(\\d+\\.))(?=\\s+)", - end:"\\s+",excludeEnd:!0},s,c,{className:"quote",begin:"^>\\s+",contains:t, - end:"$"},{className:"code",variants:[{begin:"(`{3,})[^`](.|\\n)*?\\1`*[ ]*"},{ - begin:"(~{3,})[^~](.|\\n)*?\\1~*[ ]*"},{begin:"```",end:"```+[ ]*$"},{ - begin:"~~~",end:"~~~+[ ]*$"},{begin:"`.+?`"},{begin:"(?=^( {4}|\\t))", - contains:[{begin:"^( {4}|\\t)",end:"(\\n)$"}],relevance:0}]},{ - begin:"^[-\\*]{3,}",end:"$"},i,{begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{ - className:"symbol",begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{ - className:"link",begin:/:\s*/,end:/$/,excludeBegin:!0}]}]}}})()); - hljs.registerLanguage("nginx",(()=>{"use strict";return e=>{const n={ - className:"variable",variants:[{begin:/\$\d+/},{begin:/\$\{/,end:/\}/},{ - begin:/[$@]/+e.UNDERSCORE_IDENT_RE}]},a={endsWithParent:!0,keywords:{ - $pattern:"[a-z/_]+", - literal:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll" - },relevance:0,illegal:"=>",contains:[e.HASH_COMMENT_MODE,{className:"string", - contains:[e.BACKSLASH_ESCAPE,n],variants:[{begin:/"/,end:/"/},{begin:/'/,end:/'/ - }]},{begin:"([a-z]+):/",end:"\\s",endsWithParent:!0,excludeEnd:!0,contains:[n] - },{className:"regexp",contains:[e.BACKSLASH_ESCAPE,n],variants:[{begin:"\\s\\^", - end:"\\s|\\{|;",returnEnd:!0},{begin:"~\\*?\\s+",end:"\\s|\\{|;",returnEnd:!0},{ - begin:"\\*(\\.[a-z\\-]+)+"},{begin:"([a-z\\-]+\\.)+\\*"}]},{className:"number", - begin:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{ - className:"number",begin:"\\b\\d+[kKmMgGdshdwy]*\\b",relevance:0},n]};return{ - name:"Nginx config",aliases:["nginxconf"],contains:[e.HASH_COMMENT_MODE,{ - begin:e.UNDERSCORE_IDENT_RE+"\\s+\\{",returnBegin:!0,end:/\{/,contains:[{ - className:"section",begin:e.UNDERSCORE_IDENT_RE}],relevance:0},{ - begin:e.UNDERSCORE_IDENT_RE+"\\s",end:";|\\{",returnBegin:!0,contains:[{ - className:"attribute",begin:e.UNDERSCORE_IDENT_RE,starts:a}],relevance:0}], - illegal:"[^\\s\\}]"}}})()); - hljs.registerLanguage("objectivec",(()=>{"use strict";return e=>{ - const n=/[a-zA-Z@][a-zA-Z0-9_]*/,_={$pattern:n, - keyword:"@interface @class @protocol @implementation"};return{ - name:"Objective-C",aliases:["mm","objc","obj-c","obj-c++","objective-c++"], - keywords:{$pattern:n, - keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required @encode @package @import @defs @compatibility_alias __bridge __bridge_transfer __bridge_retained __bridge_retain __covariant __contravariant __kindof _Nonnull _Nullable _Null_unspecified __FUNCTION__ __PRETTY_FUNCTION__ __attribute__ getter setter retain unsafe_unretained nonnull nullable null_unspecified null_resettable class instancetype NS_DESIGNATED_INITIALIZER NS_UNAVAILABLE NS_REQUIRES_SUPER NS_RETURNS_INNER_POINTER NS_INLINE NS_AVAILABLE NS_DEPRECATED NS_ENUM NS_OPTIONS NS_SWIFT_UNAVAILABLE NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END NS_REFINED_FOR_SWIFT NS_SWIFT_NAME NS_SWIFT_NOTHROW NS_DURING NS_HANDLER NS_ENDHANDLER NS_VALUERETURN NS_VOIDRETURN", - literal:"false true FALSE TRUE nil YES NO NULL", - built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once" - },illegal:"</",contains:[{className:"built_in", - begin:"\\b(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)\\w+" - },e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.C_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{ - className:"string",variants:[{begin:'@"',end:'"',illegal:"\\n", - contains:[e.BACKSLASH_ESCAPE]}]},{className:"meta",begin:/#\s*[a-z]+\b/,end:/$/, - keywords:{ - "meta-keyword":"if else elif endif define undef warning error line pragma ifdef ifndef include" - },contains:[{begin:/\\\n/,relevance:0},e.inherit(e.QUOTE_STRING_MODE,{ - className:"meta-string"}),{className:"meta-string",begin:/<.*?>/,end:/$/, - illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{ - className:"class",begin:"("+_.keyword.split(" ").join("|")+")\\b",end:/(\{|$)/, - excludeEnd:!0,keywords:_,contains:[e.UNDERSCORE_TITLE_MODE]},{ - begin:"\\."+e.UNDERSCORE_IDENT_RE,relevance:0}]}}})()); - hljs.registerLanguage("perl",(()=>{"use strict";function e(e){ - return e?"string"==typeof e?e:e.source:null}function n(...n){ - return n.map((n=>e(n))).join("")}function t(...n){ - return"("+n.map((n=>e(n))).join("|")+")"}return e=>{ - const r=/[dualxmsipngr]{0,12}/,s={$pattern:/[\w.]+/, - keyword:"abs accept alarm and atan2 bind binmode bless break caller chdir chmod chomp chop chown chr chroot close closedir connect continue cos crypt dbmclose dbmopen defined delete die do dump each else elsif endgrent endhostent endnetent endprotoent endpwent endservent eof eval exec exists exit exp fcntl fileno flock for foreach fork format formline getc getgrent getgrgid getgrnam gethostbyaddr gethostbyname gethostent getlogin getnetbyaddr getnetbyname getnetent getpeername getpgrp getpriority getprotobyname getprotobynumber getprotoent getpwent getpwnam getpwuid getservbyname getservbyport getservent getsockname getsockopt given glob gmtime goto grep gt hex if index int ioctl join keys kill last lc lcfirst length link listen local localtime log lstat lt ma map mkdir msgctl msgget msgrcv msgsnd my ne next no not oct open opendir or ord our pack package pipe pop pos print printf prototype push q|0 qq quotemeta qw qx rand read readdir readline readlink readpipe recv redo ref rename require reset return reverse rewinddir rindex rmdir say scalar seek seekdir select semctl semget semop send setgrent sethostent setnetent setpgrp setpriority setprotoent setpwent setservent setsockopt shift shmctl shmget shmread shmwrite shutdown sin sleep socket socketpair sort splice split sprintf sqrt srand stat state study sub substr symlink syscall sysopen sysread sysseek system syswrite tell telldir tie tied time times tr truncate uc ucfirst umask undef unless unlink unpack unshift untie until use utime values vec wait waitpid wantarray warn when while write x|0 xor y|0" - },i={className:"subst",begin:"[$@]\\{",end:"\\}",keywords:s},a={begin:/->\{/, - end:/\}/},o={variants:[{begin:/\$\d/},{ - begin:n(/[$%@](\^\w\b|#\w+(::\w+)*|\{\w+\}|\w+(::\w*)*)/,"(?![A-Za-z])(?![@$%])") - },{begin:/[$%@][^\s\w{]/,relevance:0}] - },c=[e.BACKSLASH_ESCAPE,i,o],g=[/!/,/\//,/\|/,/\?/,/'/,/"/,/#/],l=(e,t,s="\\1")=>{ - const i="\\1"===s?s:n(s,t) - ;return n(n("(?:",e,")"),t,/(?:\\.|[^\\\/])*?/,i,/(?:\\.|[^\\\/])*?/,s,r) - },d=(e,t,s)=>n(n("(?:",e,")"),t,/(?:\\.|[^\\\/])*?/,s,r),p=[o,e.HASH_COMMENT_MODE,e.COMMENT(/^=\w/,/=cut/,{ - endsWithParent:!0}),a,{className:"string",contains:c,variants:[{ - begin:"q[qwxr]?\\s*\\(",end:"\\)",relevance:5},{begin:"q[qwxr]?\\s*\\[", - end:"\\]",relevance:5},{begin:"q[qwxr]?\\s*\\{",end:"\\}",relevance:5},{ - begin:"q[qwxr]?\\s*\\|",end:"\\|",relevance:5},{begin:"q[qwxr]?\\s*<",end:">", - relevance:5},{begin:"qw\\s+q",end:"q",relevance:5},{begin:"'",end:"'", - contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"'},{begin:"`",end:"`", - contains:[e.BACKSLASH_ESCAPE]},{begin:/\{\w+\}/,relevance:0},{ - begin:"-?\\w+\\s*=>",relevance:0}]},{className:"number", - begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b", - relevance:0},{ - begin:"(\\/\\/|"+e.RE_STARTERS_RE+"|\\b(split|return|print|reverse|grep)\\b)\\s*", - keywords:"split return print reverse grep",relevance:0, - contains:[e.HASH_COMMENT_MODE,{className:"regexp",variants:[{ - begin:l("s|tr|y",t(...g))},{begin:l("s|tr|y","\\(","\\)")},{ - begin:l("s|tr|y","\\[","\\]")},{begin:l("s|tr|y","\\{","\\}")}],relevance:2},{ - className:"regexp",variants:[{begin:/(m|qr)\/\//,relevance:0},{ - begin:d("(?:m|qr)?",/\//,/\//)},{begin:d("m|qr",t(...g),/\1/)},{ - begin:d("m|qr",/\(/,/\)/)},{begin:d("m|qr",/\[/,/\]/)},{ - begin:d("m|qr",/\{/,/\}/)}]}]},{className:"function",beginKeywords:"sub", - end:"(\\s*\\(.*?\\))?[;{]",excludeEnd:!0,relevance:5,contains:[e.TITLE_MODE]},{ - begin:"-\\w\\b",relevance:0},{begin:"^__DATA__$",end:"^__END__$", - subLanguage:"mojolicious",contains:[{begin:"^@@.*",end:"$",className:"comment"}] - }];return i.contains=p,a.contains=p,{name:"Perl",aliases:["pl","pm"],keywords:s, - contains:p}}})()); - hljs.registerLanguage("php",(()=>{"use strict";return e=>{const r={ - className:"variable", - begin:"\\$+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(?![A-Za-z0-9])(?![$])"},t={ - className:"meta",variants:[{begin:/<\?php/,relevance:10},{begin:/<\?[=]?/},{ - begin:/\?>/}]},a={className:"subst",variants:[{begin:/\$\w+/},{begin:/\{\$/, - end:/\}/}]},n=e.inherit(e.APOS_STRING_MODE,{illegal:null - }),i=e.inherit(e.QUOTE_STRING_MODE,{illegal:null, - contains:e.QUOTE_STRING_MODE.contains.concat(a)}),o=e.END_SAME_AS_BEGIN({ - begin:/<<<[ \t]*(\w+)\n/,end:/[ \t]*(\w+)\b/, - contains:e.QUOTE_STRING_MODE.contains.concat(a)}),l={className:"string", - contains:[e.BACKSLASH_ESCAPE,t],variants:[e.inherit(n,{begin:"b'",end:"'" - }),e.inherit(i,{begin:'b"',end:'"'}),i,n,o]},s={className:"number",variants:[{ - begin:"\\b0b[01]+(?:_[01]+)*\\b"},{begin:"\\b0o[0-7]+(?:_[0-7]+)*\\b"},{ - begin:"\\b0x[\\da-f]+(?:_[\\da-f]+)*\\b"},{ - begin:"(?:\\b\\d+(?:_\\d+)*(\\.(?:\\d+(?:_\\d+)*))?|\\B\\.\\d+)(?:e[+-]?\\d+)?" - }],relevance:0},c={ - keyword:"__CLASS__ __DIR__ __FILE__ __FUNCTION__ __LINE__ __METHOD__ __NAMESPACE__ __TRAIT__ die echo exit include include_once print require require_once array abstract and as binary bool boolean break callable case catch class clone const continue declare default do double else elseif empty enddeclare endfor endforeach endif endswitch endwhile enum eval extends final finally float for foreach from global goto if implements instanceof insteadof int integer interface isset iterable list match|0 mixed new object or private protected public real return string switch throw trait try unset use var void while xor yield", - literal:"false null true", - built_in:"Error|0 AppendIterator ArgumentCountError ArithmeticError ArrayIterator ArrayObject AssertionError BadFunctionCallException BadMethodCallException CachingIterator CallbackFilterIterator CompileError Countable DirectoryIterator DivisionByZeroError DomainException EmptyIterator ErrorException Exception FilesystemIterator FilterIterator GlobIterator InfiniteIterator InvalidArgumentException IteratorIterator LengthException LimitIterator LogicException MultipleIterator NoRewindIterator OutOfBoundsException OutOfRangeException OuterIterator OverflowException ParentIterator ParseError RangeException RecursiveArrayIterator RecursiveCachingIterator RecursiveCallbackFilterIterator RecursiveDirectoryIterator RecursiveFilterIterator RecursiveIterator RecursiveIteratorIterator RecursiveRegexIterator RecursiveTreeIterator RegexIterator RuntimeException SeekableIterator SplDoublyLinkedList SplFileInfo SplFileObject SplFixedArray SplHeap SplMaxHeap SplMinHeap SplObjectStorage SplObserver SplObserver SplPriorityQueue SplQueue SplStack SplSubject SplSubject SplTempFileObject TypeError UnderflowException UnexpectedValueException UnhandledMatchError ArrayAccess Closure Generator Iterator IteratorAggregate Serializable Stringable Throwable Traversable WeakReference WeakMap Directory __PHP_Incomplete_Class parent php_user_filter self static stdClass" - };return{aliases:["php3","php4","php5","php6","php7","php8"], - case_insensitive:!0,keywords:c, - contains:[e.HASH_COMMENT_MODE,e.COMMENT("//","$",{contains:[t] - }),e.COMMENT("/\\*","\\*/",{contains:[{className:"doctag",begin:"@[A-Za-z]+"}] - }),e.COMMENT("__halt_compiler.+?;",!1,{endsWithParent:!0, - keywords:"__halt_compiler"}),t,{className:"keyword",begin:/\$this\b/},r,{ - begin:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{className:"function", - relevance:0,beginKeywords:"fn function",end:/[;{]/,excludeEnd:!0, - illegal:"[$%\\[]",contains:[{beginKeywords:"use"},e.UNDERSCORE_TITLE_MODE,{ - begin:"=>",endsParent:!0},{className:"params",begin:"\\(",end:"\\)", - excludeBegin:!0,excludeEnd:!0,keywords:c, - contains:["self",r,e.C_BLOCK_COMMENT_MODE,l,s]}]},{className:"class",variants:[{ - beginKeywords:"enum",illegal:/[($"]/},{beginKeywords:"class interface trait", - illegal:/[:($"]/}],relevance:0,end:/\{/,excludeEnd:!0,contains:[{ - beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{ - beginKeywords:"namespace",relevance:0,end:";",illegal:/[.']/, - contains:[e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"use",relevance:0,end:";", - contains:[e.UNDERSCORE_TITLE_MODE]},l,s]}}})()); - hljs.registerLanguage("php-template",(()=>{"use strict";return n=>({ - name:"PHP template",subLanguage:"xml",contains:[{begin:/<\?(php|=)?/,end:/\?>/, - subLanguage:"php",contains:[{begin:"/\\*",end:"\\*/",skip:!0},{begin:'b"', - end:'"',skip:!0},{begin:"b'",end:"'",skip:!0},n.inherit(n.APOS_STRING_MODE,{ - illegal:null,className:null,contains:null,skip:!0 - }),n.inherit(n.QUOTE_STRING_MODE,{illegal:null,className:null,contains:null, - skip:!0})]}]})})()); - hljs.registerLanguage("plaintext",(()=>{"use strict";return t=>({ - name:"Plain text",aliases:["text","txt"],disableAutodetect:!0})})()); - hljs.registerLanguage("properties",(()=>{"use strict";return e=>{ - var n="[ \\t\\f]*",a=n+"[:=]"+n,t="("+a+"|[ \\t\\f]+)",r="([^\\\\\\W:= \\t\\f\\n]|\\\\.)+",s="([^\\\\:= \\t\\f\\n]|\\\\.)+",i={ - end:t,relevance:0,starts:{className:"string",end:/$/,relevance:0,contains:[{ - begin:"\\\\\\\\"},{begin:"\\\\\\n"}]}};return{name:".properties", - case_insensitive:!0,illegal:/\S/,contains:[e.COMMENT("^\\s*[!#]","$"),{ - returnBegin:!0,variants:[{begin:r+a,relevance:1},{begin:r+"[ \\t\\f]+", - relevance:0}],contains:[{className:"attr",begin:r,endsParent:!0,relevance:0}], - starts:i},{begin:s+t,returnBegin:!0,relevance:0,contains:[{className:"meta", - begin:s,endsParent:!0,relevance:0}],starts:i},{className:"attr",relevance:0, - begin:s+n+"$"}]}}})()); - hljs.registerLanguage("python",(()=>{"use strict";return e=>{const n={ - $pattern:/[A-Za-z]\w+|__\w+__/, - keyword:["and","as","assert","async","await","break","class","continue","def","del","elif","else","except","finally","for","from","global","if","import","in","is","lambda","nonlocal|10","not","or","pass","raise","return","try","while","with","yield"], - built_in:["__import__","abs","all","any","ascii","bin","bool","breakpoint","bytearray","bytes","callable","chr","classmethod","compile","complex","delattr","dict","dir","divmod","enumerate","eval","exec","filter","float","format","frozenset","getattr","globals","hasattr","hash","help","hex","id","input","int","isinstance","issubclass","iter","len","list","locals","map","max","memoryview","min","next","object","oct","open","ord","pow","print","property","range","repr","reversed","round","set","setattr","slice","sorted","staticmethod","str","sum","super","tuple","type","vars","zip"], - literal:["__debug__","Ellipsis","False","None","NotImplemented","True"], - type:["Any","Callable","Coroutine","Dict","List","Literal","Generic","Optional","Sequence","Set","Tuple","Type","Union"] - },a={className:"meta",begin:/^(>>>|\.\.\.) /},i={className:"subst",begin:/\{/, - end:/\}/,keywords:n,illegal:/#/},s={begin:/\{\{/,relevance:0},t={ - className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{ - begin:/([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?'''/,end:/'''/, - contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{ - begin:/([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?"""/,end:/"""/, - contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{ - begin:/([fF][rR]|[rR][fF]|[fF])'''/,end:/'''/, - contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/([fF][rR]|[rR][fF]|[fF])"""/, - end:/"""/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/([uU]|[rR])'/,end:/'/, - relevance:10},{begin:/([uU]|[rR])"/,end:/"/,relevance:10},{ - begin:/([bB]|[bB][rR]|[rR][bB])'/,end:/'/},{begin:/([bB]|[bB][rR]|[rR][bB])"/, - end:/"/},{begin:/([fF][rR]|[rR][fF]|[fF])'/,end:/'/, - contains:[e.BACKSLASH_ESCAPE,s,i]},{begin:/([fF][rR]|[rR][fF]|[fF])"/,end:/"/, - contains:[e.BACKSLASH_ESCAPE,s,i]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE] - },r="[0-9](_?[0-9])*",l=`(\\b(${r}))?\\.(${r})|\\b(${r})\\.`,b={ - className:"number",relevance:0,variants:[{ - begin:`(\\b(${r})|(${l}))[eE][+-]?(${r})[jJ]?\\b`},{begin:`(${l})[jJ]?`},{ - begin:"\\b([1-9](_?[0-9])*|0+(_?0)*)[lLjJ]?\\b"},{ - begin:"\\b0[bB](_?[01])+[lL]?\\b"},{begin:"\\b0[oO](_?[0-7])+[lL]?\\b"},{ - begin:"\\b0[xX](_?[0-9a-fA-F])+[lL]?\\b"},{begin:`\\b(${r})[jJ]\\b`}]},o={ - className:"comment", - begin:(d=/# type:/,((...e)=>e.map((e=>(e=>e?"string"==typeof e?e:e.source:null)(e))).join(""))("(?=",d,")")), - end:/$/,keywords:n,contains:[{begin:/# type:/},{begin:/#/,end:/\b\B/, - endsWithParent:!0}]},c={className:"params",variants:[{className:"", - begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0, - keywords:n,contains:["self",a,b,t,e.HASH_COMMENT_MODE]}]};var d - ;return i.contains=[t,b,a],{name:"Python",aliases:["py","gyp","ipython"], - keywords:n,illegal:/(<\/|->|\?)|=>/,contains:[a,b,{begin:/\bself\b/},{ - beginKeywords:"if",relevance:0},t,o,e.HASH_COMMENT_MODE,{variants:[{ - className:"function",beginKeywords:"def"},{className:"class", - beginKeywords:"class"}],end:/:/,illegal:/[${=;\n,]/, - contains:[e.UNDERSCORE_TITLE_MODE,c,{begin:/->/,endsWithParent:!0,keywords:n}] - },{className:"meta",begin:/^[\t ]*@/,end:/(?=#)|$/,contains:[b,c,t]}]}}})()); - hljs.registerLanguage("python-repl",(()=>{"use strict";return s=>({ - aliases:["pycon"],contains:[{className:"meta",starts:{end:/ |$/,starts:{end:"$", - subLanguage:"python"}},variants:[{begin:/^>>>(?=[ ]|$)/},{ - begin:/^\.\.\.(?=[ ]|$)/}]}]})})()); - hljs.registerLanguage("r",(()=>{"use strict";function e(...e){return e.map((e=>{ - return(a=e)?"string"==typeof a?a:a.source:null;var a})).join("")}return a=>{ - const n=/(?:(?:[a-zA-Z]|\.[._a-zA-Z])[._a-zA-Z0-9]*)|\.(?!\d)/;return{name:"R", - illegal:/->/,keywords:{$pattern:n, - keyword:"function if in break next repeat else for while", - literal:"NULL NA TRUE FALSE Inf NaN NA_integer_|10 NA_real_|10 NA_character_|10 NA_complex_|10", - built_in:"LETTERS letters month.abb month.name pi T F abs acos acosh all any anyNA Arg as.call as.character as.complex as.double as.environment as.integer as.logical as.null.default as.numeric as.raw asin asinh atan atanh attr attributes baseenv browser c call ceiling class Conj cos cosh cospi cummax cummin cumprod cumsum digamma dim dimnames emptyenv exp expression floor forceAndCall gamma gc.time globalenv Im interactive invisible is.array is.atomic is.call is.character is.complex is.double is.environment is.expression is.finite is.function is.infinite is.integer is.language is.list is.logical is.matrix is.na is.name is.nan is.null is.numeric is.object is.pairlist is.raw is.recursive is.single is.symbol lazyLoadDBfetch length lgamma list log max min missing Mod names nargs nzchar oldClass on.exit pos.to.env proc.time prod quote range Re rep retracemem return round seq_along seq_len seq.int sign signif sin sinh sinpi sqrt standardGeneric substitute sum switch tan tanh tanpi tracemem trigamma trunc unclass untracemem UseMethod xtfrm" - },compilerExtensions:[(a,n)=>{if(!a.beforeMatch)return - ;if(a.starts)throw Error("beforeMatch cannot be used with starts") - ;const i=Object.assign({},a);Object.keys(a).forEach((e=>{delete a[e] - })),a.begin=e(i.beforeMatch,e("(?=",i.begin,")")),a.starts={relevance:0, - contains:[Object.assign(i,{endsParent:!0})]},a.relevance=0,delete i.beforeMatch - }],contains:[a.COMMENT(/#'/,/$/,{contains:[{className:"doctag", - begin:"@examples",starts:{contains:[{begin:/\n/},{begin:/#'\s*(?=@[a-zA-Z]+)/, - endsParent:!0},{begin:/#'/,end:/$/,excludeBegin:!0}]}},{className:"doctag", - begin:"@param",end:/$/,contains:[{className:"variable",variants:[{begin:n},{ - begin:/`(?:\\.|[^`\\])+`/}],endsParent:!0}]},{className:"doctag", - begin:/@[a-zA-Z]+/},{className:"meta-keyword",begin:/\\[a-zA-Z]+/}] - }),a.HASH_COMMENT_MODE,{className:"string",contains:[a.BACKSLASH_ESCAPE], - variants:[a.END_SAME_AS_BEGIN({begin:/[rR]"(-*)\(/,end:/\)(-*)"/ - }),a.END_SAME_AS_BEGIN({begin:/[rR]"(-*)\{/,end:/\}(-*)"/ - }),a.END_SAME_AS_BEGIN({begin:/[rR]"(-*)\[/,end:/\](-*)"/ - }),a.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\(/,end:/\)(-*)'/ - }),a.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\{/,end:/\}(-*)'/ - }),a.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\[/,end:/\](-*)'/}),{begin:'"',end:'"', - relevance:0},{begin:"'",end:"'",relevance:0}]},{className:"number",relevance:0, - beforeMatch:/([^a-zA-Z0-9._])/,variants:[{ - match:/0[xX][0-9a-fA-F]+\.[0-9a-fA-F]*[pP][+-]?\d+i?/},{ - match:/0[xX][0-9a-fA-F]+([pP][+-]?\d+)?[Li]?/},{ - match:/(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?[Li]?/}]},{begin:"%",end:"%"},{ - begin:e(/[a-zA-Z][a-zA-Z_0-9]*/,"\\s+<-\\s+")},{begin:"`",end:"`",contains:[{ - begin:/\\./}]}]}}})()); - hljs.registerLanguage("ruby",(()=>{"use strict";function e(...e){ - return e.map((e=>{return(n=e)?"string"==typeof n?n:n.source:null;var n - })).join("")}return n=>{ - const a="([a-zA-Z_]\\w*[!?=]?|[-+~]@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?)",i={ - keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor __FILE__", - built_in:"proc lambda",literal:"true false nil"},s={className:"doctag", - begin:"@[A-Za-z]+"},r={begin:"#<",end:">"},b=[n.COMMENT("#","$",{contains:[s] - }),n.COMMENT("^=begin","^=end",{contains:[s],relevance:10 - }),n.COMMENT("^__END__","\\n$")],c={className:"subst",begin:/#\{/,end:/\}/, - keywords:i},t={className:"string",contains:[n.BACKSLASH_ESCAPE,c],variants:[{ - begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{begin:/%[qQwWx]?\(/, - end:/\)/},{begin:/%[qQwWx]?\[/,end:/\]/},{begin:/%[qQwWx]?\{/,end:/\}/},{ - begin:/%[qQwWx]?</,end:/>/},{begin:/%[qQwWx]?\//,end:/\//},{begin:/%[qQwWx]?%/, - end:/%/},{begin:/%[qQwWx]?-/,end:/-/},{begin:/%[qQwWx]?\|/,end:/\|/},{ - begin:/\B\?(\\\d{1,3})/},{begin:/\B\?(\\x[A-Fa-f0-9]{1,2})/},{ - begin:/\B\?(\\u\{?[A-Fa-f0-9]{1,6}\}?)/},{ - begin:/\B\?(\\M-\\C-|\\M-\\c|\\c\\M-|\\M-|\\C-\\M-)[\x20-\x7e]/},{ - begin:/\B\?\\(c|C-)[\x20-\x7e]/},{begin:/\B\?\\?\S/},{ - begin:/<<[-~]?'?(\w+)\n(?:[^\n]*\n)*?\s*\1\b/,returnBegin:!0,contains:[{ - begin:/<<[-~]?'?/},n.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/, - contains:[n.BACKSLASH_ESCAPE,c]})]}]},g="[0-9](_?[0-9])*",d={className:"number", - relevance:0,variants:[{ - begin:`\\b([1-9](_?[0-9])*|0)(\\.(${g}))?([eE][+-]?(${g})|r)?i?\\b`},{ - begin:"\\b0[dD][0-9](_?[0-9])*r?i?\\b"},{begin:"\\b0[bB][0-1](_?[0-1])*r?i?\\b" - },{begin:"\\b0[oO][0-7](_?[0-7])*r?i?\\b"},{ - begin:"\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*r?i?\\b"},{ - begin:"\\b0(_?[0-7])+r?i?\\b"}]},l={className:"params",begin:"\\(",end:"\\)", - endsParent:!0,keywords:i},o=[t,{className:"class",beginKeywords:"class module", - end:"$|;",illegal:/=/,contains:[n.inherit(n.TITLE_MODE,{ - begin:"[A-Za-z_]\\w*(::\\w+)*(\\?|!)?"}),{begin:"<\\s*",contains:[{ - begin:"("+n.IDENT_RE+"::)?"+n.IDENT_RE,relevance:0}]}].concat(b)},{ - className:"function",begin:e(/def\s+/,(_=a+"\\s*(\\(|;|$)",e("(?=",_,")"))), - relevance:0,keywords:"def",end:"$|;",contains:[n.inherit(n.TITLE_MODE,{begin:a - }),l].concat(b)},{begin:n.IDENT_RE+"::"},{className:"symbol", - begin:n.UNDERSCORE_IDENT_RE+"(!|\\?)?:",relevance:0},{className:"symbol", - begin:":(?!\\s)",contains:[t,{begin:a}],relevance:0},d,{className:"variable", - begin:"(\\$\\W)|((\\$|@@?)(\\w+))(?=[^@$?])(?![A-Za-z])(?![@$?'])"},{ - className:"params",begin:/\|/,end:/\|/,relevance:0,keywords:i},{ - begin:"("+n.RE_STARTERS_RE+"|unless)\\s*",keywords:"unless",contains:[{ - className:"regexp",contains:[n.BACKSLASH_ESCAPE,c],illegal:/\n/,variants:[{ - begin:"/",end:"/[a-z]*"},{begin:/%r\{/,end:/\}[a-z]*/},{begin:"%r\\(", - end:"\\)[a-z]*"},{begin:"%r!",end:"![a-z]*"},{begin:"%r\\[",end:"\\][a-z]*"}] - }].concat(r,b),relevance:0}].concat(r,b);var _;c.contains=o,l.contains=o - ;const E=[{begin:/^\s*=>/,starts:{end:"$",contains:o}},{className:"meta", - begin:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+>|(\\w+-)?\\d+\\.\\d+\\.\\d+(p\\d+)?[^\\d][^>]+>)(?=[ ])", - starts:{end:"$",contains:o}}];return b.unshift(r),{name:"Ruby", - aliases:["rb","gemspec","podspec","thor","irb"],keywords:i,illegal:/\/\*/, - contains:[n.SHEBANG({binary:"ruby"})].concat(E).concat(b).concat(o)}}})()); - hljs.registerLanguage("rust",(()=>{"use strict";return e=>{ - const n="([ui](8|16|32|64|128|size)|f(32|64))?",t="drop i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize f32 f64 str char bool Box Option Result String Vec Copy Send Sized Sync Drop Fn FnMut FnOnce ToOwned Clone Debug PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator Extend IntoIterator DoubleEndedIterator ExactSizeIterator SliceConcatExt ToString assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! panic! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln! macro_rules! assert_ne! debug_assert_ne!" - ;return{name:"Rust",aliases:["rs"],keywords:{$pattern:e.IDENT_RE+"!?", - keyword:"abstract as async await become box break const continue crate do dyn else enum extern false final fn for if impl in let loop macro match mod move mut override priv pub ref return self Self static struct super trait true try type typeof unsafe unsized use virtual where while yield", - literal:"true false Some None Ok Err",built_in:t},illegal:"</", - contains:[e.C_LINE_COMMENT_MODE,e.COMMENT("/\\*","\\*/",{contains:["self"] - }),e.inherit(e.QUOTE_STRING_MODE,{begin:/b?"/,illegal:null}),{ - className:"string",variants:[{begin:/r(#*)"(.|\n)*?"\1(?!#)/},{ - begin:/b?'\\?(x\w{2}|u\w{4}|U\w{8}|.)'/}]},{className:"symbol", - begin:/'[a-zA-Z_][a-zA-Z0-9_]*/},{className:"number",variants:[{ - begin:"\\b0b([01_]+)"+n},{begin:"\\b0o([0-7_]+)"+n},{ - begin:"\\b0x([A-Fa-f0-9_]+)"+n},{ - begin:"\\b(\\d[\\d_]*(\\.[0-9_]+)?([eE][+-]?[0-9_]+)?)"+n}],relevance:0},{ - className:"function",beginKeywords:"fn",end:"(\\(|<)",excludeEnd:!0, - contains:[e.UNDERSCORE_TITLE_MODE]},{className:"meta",begin:"#!?\\[",end:"\\]", - contains:[{className:"meta-string",begin:/"/,end:/"/}]},{className:"class", - beginKeywords:"type",end:";",contains:[e.inherit(e.UNDERSCORE_TITLE_MODE,{ - endsParent:!0})],illegal:"\\S"},{className:"class", - beginKeywords:"trait enum struct union",end:/\{/, - contains:[e.inherit(e.UNDERSCORE_TITLE_MODE,{endsParent:!0})],illegal:"[\\w\\d]" - },{begin:e.IDENT_RE+"::",keywords:{built_in:t}},{begin:"->"}]}}})()); - hljs.registerLanguage("scss",(()=>{"use strict" - ;const e=["a","abbr","address","article","aside","audio","b","blockquote","body","button","canvas","caption","cite","code","dd","del","details","dfn","div","dl","dt","em","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","html","i","iframe","img","input","ins","kbd","label","legend","li","main","mark","menu","nav","object","ol","p","q","quote","samp","section","span","strong","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","ul","var","video"],t=["any-hover","any-pointer","aspect-ratio","color","color-gamut","color-index","device-aspect-ratio","device-height","device-width","display-mode","forced-colors","grid","height","hover","inverted-colors","monochrome","orientation","overflow-block","overflow-inline","pointer","prefers-color-scheme","prefers-contrast","prefers-reduced-motion","prefers-reduced-transparency","resolution","scan","scripting","update","width","min-width","max-width","min-height","max-height"],i=["active","any-link","blank","checked","current","default","defined","dir","disabled","drop","empty","enabled","first","first-child","first-of-type","fullscreen","future","focus","focus-visible","focus-within","has","host","host-context","hover","indeterminate","in-range","invalid","is","lang","last-child","last-of-type","left","link","local-link","not","nth-child","nth-col","nth-last-child","nth-last-col","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","past","placeholder-shown","read-only","read-write","required","right","root","scope","target","target-within","user-invalid","valid","visited","where"],o=["after","backdrop","before","cue","cue-region","first-letter","first-line","grammar-error","marker","part","placeholder","selection","slotted","spelling-error"],r=["align-content","align-items","align-self","animation","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","auto","backface-visibility","background","background-attachment","background-clip","background-color","background-image","background-origin","background-position","background-repeat","background-size","border","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-style","border-top","border-top-color","border-top-left-radius","border-top-right-radius","border-top-style","border-top-width","border-width","bottom","box-decoration-break","box-shadow","box-sizing","break-after","break-before","break-inside","caption-side","clear","clip","clip-path","color","column-count","column-fill","column-gap","column-rule","column-rule-color","column-rule-style","column-rule-width","column-span","column-width","columns","content","counter-increment","counter-reset","cursor","direction","display","empty-cells","filter","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","font","font-display","font-family","font-feature-settings","font-kerning","font-language-override","font-size","font-size-adjust","font-smoothing","font-stretch","font-style","font-variant","font-variant-ligatures","font-variation-settings","font-weight","height","hyphens","icon","image-orientation","image-rendering","image-resolution","ime-mode","inherit","initial","justify-content","left","letter-spacing","line-height","list-style","list-style-image","list-style-position","list-style-type","margin","margin-bottom","margin-left","margin-right","margin-top","marks","mask","max-height","max-width","min-height","min-width","nav-down","nav-index","nav-left","nav-right","nav-up","none","normal","object-fit","object-position","opacity","order","orphans","outline","outline-color","outline-offset","outline-style","outline-width","overflow","overflow-wrap","overflow-x","overflow-y","padding","padding-bottom","padding-left","padding-right","padding-top","page-break-after","page-break-before","page-break-inside","perspective","perspective-origin","pointer-events","position","quotes","resize","right","src","tab-size","table-layout","text-align","text-align-last","text-decoration","text-decoration-color","text-decoration-line","text-decoration-style","text-indent","text-overflow","text-rendering","text-shadow","text-transform","text-underline-position","top","transform","transform-origin","transform-style","transition","transition-delay","transition-duration","transition-property","transition-timing-function","unicode-bidi","vertical-align","visibility","white-space","widows","width","word-break","word-spacing","word-wrap","z-index"].reverse() - ;return a=>{const n=(e=>({IMPORTANT:{className:"meta",begin:"!important"}, - HEXCOLOR:{className:"number",begin:"#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})"}, - ATTRIBUTE_SELECTOR_MODE:{className:"selector-attr",begin:/\[/,end:/\]/, - illegal:"$",contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]} - }))(a),l=o,s=i,d="@[a-z-]+",c={className:"variable", - begin:"(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b"};return{name:"SCSS",case_insensitive:!0, - illegal:"[=/|']",contains:[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,{ - className:"selector-id",begin:"#[A-Za-z0-9_-]+",relevance:0},{ - className:"selector-class",begin:"\\.[A-Za-z0-9_-]+",relevance:0 - },n.ATTRIBUTE_SELECTOR_MODE,{className:"selector-tag", - begin:"\\b("+e.join("|")+")\\b",relevance:0},{className:"selector-pseudo", - begin:":("+s.join("|")+")"},{className:"selector-pseudo", - begin:"::("+l.join("|")+")"},c,{begin:/\(/,end:/\)/,contains:[a.CSS_NUMBER_MODE] - },{className:"attribute",begin:"\\b("+r.join("|")+")\\b"},{ - begin:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b" - },{begin:":",end:";", - contains:[c,n.HEXCOLOR,a.CSS_NUMBER_MODE,a.QUOTE_STRING_MODE,a.APOS_STRING_MODE,n.IMPORTANT] - },{begin:"@(page|font-face)",lexemes:d,keywords:"@page @font-face"},{begin:"@", - end:"[{;]",returnBegin:!0,keywords:{$pattern:/[a-z-]+/, - keyword:"and or not only",attribute:t.join(" ")},contains:[{begin:d, - className:"keyword"},{begin:/[a-z-]+(?=:)/,className:"attribute" - },c,a.QUOTE_STRING_MODE,a.APOS_STRING_MODE,n.HEXCOLOR,a.CSS_NUMBER_MODE]}]}} - })()); - hljs.registerLanguage("shell",(()=>{"use strict";return s=>({ - name:"Shell Session",aliases:["console"],contains:[{className:"meta", - begin:/^\s{0,3}[/~\w\d[\]()@-]*[>%$#]/,starts:{end:/[^\\](?=\s*$)/, - subLanguage:"bash"}}]})})()); - hljs.registerLanguage("sql",(()=>{"use strict";function e(e){ - return e?"string"==typeof e?e:e.source:null}function r(...r){ - return r.map((r=>e(r))).join("")}function t(...r){ - return"("+r.map((r=>e(r))).join("|")+")"}return e=>{ - const n=e.COMMENT("--","$"),a=["true","false","unknown"],i=["bigint","binary","blob","boolean","char","character","clob","date","dec","decfloat","decimal","float","int","integer","interval","nchar","nclob","national","numeric","real","row","smallint","time","timestamp","varchar","varying","varbinary"],s=["abs","acos","array_agg","asin","atan","avg","cast","ceil","ceiling","coalesce","corr","cos","cosh","count","covar_pop","covar_samp","cume_dist","dense_rank","deref","element","exp","extract","first_value","floor","json_array","json_arrayagg","json_exists","json_object","json_objectagg","json_query","json_table","json_table_primitive","json_value","lag","last_value","lead","listagg","ln","log","log10","lower","max","min","mod","nth_value","ntile","nullif","percent_rank","percentile_cont","percentile_disc","position","position_regex","power","rank","regr_avgx","regr_avgy","regr_count","regr_intercept","regr_r2","regr_slope","regr_sxx","regr_sxy","regr_syy","row_number","sin","sinh","sqrt","stddev_pop","stddev_samp","substring","substring_regex","sum","tan","tanh","translate","translate_regex","treat","trim","trim_array","unnest","upper","value_of","var_pop","var_samp","width_bucket"],o=["create table","insert into","primary key","foreign key","not null","alter table","add constraint","grouping sets","on overflow","character set","respect nulls","ignore nulls","nulls first","nulls last","depth first","breadth first"],c=s,l=["abs","acos","all","allocate","alter","and","any","are","array","array_agg","array_max_cardinality","as","asensitive","asin","asymmetric","at","atan","atomic","authorization","avg","begin","begin_frame","begin_partition","between","bigint","binary","blob","boolean","both","by","call","called","cardinality","cascaded","case","cast","ceil","ceiling","char","char_length","character","character_length","check","classifier","clob","close","coalesce","collate","collect","column","commit","condition","connect","constraint","contains","convert","copy","corr","corresponding","cos","cosh","count","covar_pop","covar_samp","create","cross","cube","cume_dist","current","current_catalog","current_date","current_default_transform_group","current_path","current_role","current_row","current_schema","current_time","current_timestamp","current_path","current_role","current_transform_group_for_type","current_user","cursor","cycle","date","day","deallocate","dec","decimal","decfloat","declare","default","define","delete","dense_rank","deref","describe","deterministic","disconnect","distinct","double","drop","dynamic","each","element","else","empty","end","end_frame","end_partition","end-exec","equals","escape","every","except","exec","execute","exists","exp","external","extract","false","fetch","filter","first_value","float","floor","for","foreign","frame_row","free","from","full","function","fusion","get","global","grant","group","grouping","groups","having","hold","hour","identity","in","indicator","initial","inner","inout","insensitive","insert","int","integer","intersect","intersection","interval","into","is","join","json_array","json_arrayagg","json_exists","json_object","json_objectagg","json_query","json_table","json_table_primitive","json_value","lag","language","large","last_value","lateral","lead","leading","left","like","like_regex","listagg","ln","local","localtime","localtimestamp","log","log10","lower","match","match_number","match_recognize","matches","max","member","merge","method","min","minute","mod","modifies","module","month","multiset","national","natural","nchar","nclob","new","no","none","normalize","not","nth_value","ntile","null","nullif","numeric","octet_length","occurrences_regex","of","offset","old","omit","on","one","only","open","or","order","out","outer","over","overlaps","overlay","parameter","partition","pattern","per","percent","percent_rank","percentile_cont","percentile_disc","period","portion","position","position_regex","power","precedes","precision","prepare","primary","procedure","ptf","range","rank","reads","real","recursive","ref","references","referencing","regr_avgx","regr_avgy","regr_count","regr_intercept","regr_r2","regr_slope","regr_sxx","regr_sxy","regr_syy","release","result","return","returns","revoke","right","rollback","rollup","row","row_number","rows","running","savepoint","scope","scroll","search","second","seek","select","sensitive","session_user","set","show","similar","sin","sinh","skip","smallint","some","specific","specifictype","sql","sqlexception","sqlstate","sqlwarning","sqrt","start","static","stddev_pop","stddev_samp","submultiset","subset","substring","substring_regex","succeeds","sum","symmetric","system","system_time","system_user","table","tablesample","tan","tanh","then","time","timestamp","timezone_hour","timezone_minute","to","trailing","translate","translate_regex","translation","treat","trigger","trim","trim_array","true","truncate","uescape","union","unique","unknown","unnest","update ","upper","user","using","value","values","value_of","var_pop","var_samp","varbinary","varchar","varying","versioning","when","whenever","where","width_bucket","window","with","within","without","year","add","asc","collation","desc","final","first","last","view"].filter((e=>!s.includes(e))),u={ - begin:r(/\b/,t(...c),/\s*\(/),keywords:{built_in:c}};return{name:"SQL", - case_insensitive:!0,illegal:/[{}]|<\//,keywords:{$pattern:/\b[\w\.]+/, - keyword:((e,{exceptions:r,when:t}={})=>{const n=t - ;return r=r||[],e.map((e=>e.match(/\|\d+$/)||r.includes(e)?e:n(e)?e+"|0":e)) - })(l,{when:e=>e.length<3}),literal:a,type:i, - built_in:["current_catalog","current_date","current_default_transform_group","current_path","current_role","current_schema","current_transform_group_for_type","current_user","session_user","system_time","system_user","current_time","localtime","current_timestamp","localtimestamp"] - },contains:[{begin:t(...o),keywords:{$pattern:/[\w\.]+/,keyword:l.concat(o), - literal:a,type:i}},{className:"type", - begin:t("double precision","large object","with timezone","without timezone") - },u,{className:"variable",begin:/@[a-z0-9]+/},{className:"string",variants:[{ - begin:/'/,end:/'/,contains:[{begin:/''/}]}]},{begin:/"/,end:/"/,contains:[{ - begin:/""/}]},e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,n,{className:"operator", - begin:/[-+*/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?/,relevance:0}]}}})()); - hljs.registerLanguage("swift",(()=>{"use strict";function e(e){ - return e?"string"==typeof e?e:e.source:null}function n(e){return a("(?=",e,")")} - function a(...n){return n.map((n=>e(n))).join("")}function t(...n){ - return"("+n.map((n=>e(n))).join("|")+")"} - const i=e=>a(/\b/,e,/\w$/.test(e)?/\b/:/\B/),s=["Protocol","Type"].map(i),u=["init","self"].map(i),c=["Any","Self"],r=["associatedtype","async","await",/as\?/,/as!/,"as","break","case","catch","class","continue","convenience","default","defer","deinit","didSet","do","dynamic","else","enum","extension","fallthrough",/fileprivate\(set\)/,"fileprivate","final","for","func","get","guard","if","import","indirect","infix",/init\?/,/init!/,"inout",/internal\(set\)/,"internal","in","is","lazy","let","mutating","nonmutating",/open\(set\)/,"open","operator","optional","override","postfix","precedencegroup","prefix",/private\(set\)/,"private","protocol",/public\(set\)/,"public","repeat","required","rethrows","return","set","some","static","struct","subscript","super","switch","throws","throw",/try\?/,/try!/,"try","typealias",/unowned\(safe\)/,/unowned\(unsafe\)/,"unowned","var","weak","where","while","willSet"],o=["false","nil","true"],l=["assignment","associativity","higherThan","left","lowerThan","none","right"],m=["#colorLiteral","#column","#dsohandle","#else","#elseif","#endif","#error","#file","#fileID","#fileLiteral","#filePath","#function","#if","#imageLiteral","#keyPath","#line","#selector","#sourceLocation","#warn_unqualified_access","#warning"],d=["abs","all","any","assert","assertionFailure","debugPrint","dump","fatalError","getVaList","isKnownUniquelyReferenced","max","min","numericCast","pointwiseMax","pointwiseMin","precondition","preconditionFailure","print","readLine","repeatElement","sequence","stride","swap","swift_unboxFromSwiftValueWithType","transcode","type","unsafeBitCast","unsafeDowncast","withExtendedLifetime","withUnsafeMutablePointer","withUnsafePointer","withVaList","withoutActuallyEscaping","zip"],p=t(/[/=\-+!*%<>&|^~?]/,/[\u00A1-\u00A7]/,/[\u00A9\u00AB]/,/[\u00AC\u00AE]/,/[\u00B0\u00B1]/,/[\u00B6\u00BB\u00BF\u00D7\u00F7]/,/[\u2016-\u2017]/,/[\u2020-\u2027]/,/[\u2030-\u203E]/,/[\u2041-\u2053]/,/[\u2055-\u205E]/,/[\u2190-\u23FF]/,/[\u2500-\u2775]/,/[\u2794-\u2BFF]/,/[\u2E00-\u2E7F]/,/[\u3001-\u3003]/,/[\u3008-\u3020]/,/[\u3030]/),F=t(p,/[\u0300-\u036F]/,/[\u1DC0-\u1DFF]/,/[\u20D0-\u20FF]/,/[\uFE00-\uFE0F]/,/[\uFE20-\uFE2F]/),b=a(p,F,"*"),h=t(/[a-zA-Z_]/,/[\u00A8\u00AA\u00AD\u00AF\u00B2-\u00B5\u00B7-\u00BA]/,/[\u00BC-\u00BE\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF]/,/[\u0100-\u02FF\u0370-\u167F\u1681-\u180D\u180F-\u1DBF]/,/[\u1E00-\u1FFF]/,/[\u200B-\u200D\u202A-\u202E\u203F-\u2040\u2054\u2060-\u206F]/,/[\u2070-\u20CF\u2100-\u218F\u2460-\u24FF\u2776-\u2793]/,/[\u2C00-\u2DFF\u2E80-\u2FFF]/,/[\u3004-\u3007\u3021-\u302F\u3031-\u303F\u3040-\uD7FF]/,/[\uF900-\uFD3D\uFD40-\uFDCF\uFDF0-\uFE1F\uFE30-\uFE44]/,/[\uFE47-\uFEFE\uFF00-\uFFFD]/),f=t(h,/\d/,/[\u0300-\u036F\u1DC0-\u1DFF\u20D0-\u20FF\uFE20-\uFE2F]/),w=a(h,f,"*"),y=a(/[A-Z]/,f,"*"),g=["autoclosure",a(/convention\(/,t("swift","block","c"),/\)/),"discardableResult","dynamicCallable","dynamicMemberLookup","escaping","frozen","GKInspectable","IBAction","IBDesignable","IBInspectable","IBOutlet","IBSegueAction","inlinable","main","nonobjc","NSApplicationMain","NSCopying","NSManaged",a(/objc\(/,w,/\)/),"objc","objcMembers","propertyWrapper","requires_stored_property_inits","testable","UIApplicationMain","unknown","usableFromInline"],E=["iOS","iOSApplicationExtension","macOS","macOSApplicationExtension","macCatalyst","macCatalystApplicationExtension","watchOS","watchOSApplicationExtension","tvOS","tvOSApplicationExtension","swift"] - ;return e=>{const p={match:/\s+/,relevance:0},h=e.COMMENT("/\\*","\\*/",{ - contains:["self"]}),v=[e.C_LINE_COMMENT_MODE,h],N={className:"keyword", - begin:a(/\./,n(t(...s,...u))),end:t(...s,...u),excludeBegin:!0},A={ - match:a(/\./,t(...r)),relevance:0 - },C=r.filter((e=>"string"==typeof e)).concat(["_|0"]),_={variants:[{ - className:"keyword", - match:t(...r.filter((e=>"string"!=typeof e)).concat(c).map(i),...u)}]},D={ - $pattern:t(/\b\w+/,/#\w+/),keyword:C.concat(m),literal:o},B=[N,A,_],k=[{ - match:a(/\./,t(...d)),relevance:0},{className:"built_in", - match:a(/\b/,t(...d),/(?=\()/)}],M={match:/->/,relevance:0},S=[M,{ - className:"operator",relevance:0,variants:[{match:b},{match:`\\.(\\.|${F})+`}] - }],x="([0-9a-fA-F]_*)+",I={className:"number",relevance:0,variants:[{ - match:"\\b(([0-9]_*)+)(\\.(([0-9]_*)+))?([eE][+-]?(([0-9]_*)+))?\\b"},{ - match:`\\b0x(${x})(\\.(${x}))?([pP][+-]?(([0-9]_*)+))?\\b`},{ - match:/\b0o([0-7]_*)+\b/},{match:/\b0b([01]_*)+\b/}]},O=(e="")=>({ - className:"subst",variants:[{match:a(/\\/,e,/[0\\tnr"']/)},{ - match:a(/\\/,e,/u\{[0-9a-fA-F]{1,8}\}/)}]}),T=(e="")=>({className:"subst", - match:a(/\\/,e,/[\t ]*(?:[\r\n]|\r\n)/)}),L=(e="")=>({className:"subst", - label:"interpol",begin:a(/\\/,e,/\(/),end:/\)/}),P=(e="")=>({begin:a(e,/"""/), - end:a(/"""/,e),contains:[O(e),T(e),L(e)]}),$=(e="")=>({begin:a(e,/"/), - end:a(/"/,e),contains:[O(e),L(e)]}),K={className:"string", - variants:[P(),P("#"),P("##"),P("###"),$(),$("#"),$("##"),$("###")]},j={ - match:a(/`/,w,/`/)},z=[j,{className:"variable",match:/\$\d+/},{ - className:"variable",match:`\\$${f}+`}],q=[{match:/(@|#)available/, - className:"keyword",starts:{contains:[{begin:/\(/,end:/\)/,keywords:E, - contains:[...S,I,K]}]}},{className:"keyword",match:a(/@/,t(...g))},{ - className:"meta",match:a(/@/,w)}],U={match:n(/\b[A-Z]/),relevance:0,contains:[{ - className:"type", - match:a(/(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)/,f,"+") - },{className:"type",match:y,relevance:0},{match:/[?!]+/,relevance:0},{ - match:/\.\.\./,relevance:0},{match:a(/\s+&\s+/,n(y)),relevance:0}]},Z={ - begin:/</,end:/>/,keywords:D,contains:[...v,...B,...q,M,U]};U.contains.push(Z) - ;const G={begin:/\(/,end:/\)/,relevance:0,keywords:D,contains:["self",{ - match:a(w,/\s*:/),keywords:"_|0",relevance:0 - },...v,...B,...k,...S,I,K,...z,...q,U]},H={beginKeywords:"func",contains:[{ - className:"title",match:t(j.match,w,b),endsParent:!0,relevance:0},p]},R={ - begin:/</,end:/>/,contains:[...v,U]},V={begin:/\(/,end:/\)/,keywords:D, - contains:[{begin:t(n(a(w,/\s*:/)),n(a(w,/\s+/,w,/\s*:/))),end:/:/,relevance:0, - contains:[{className:"keyword",match:/\b_\b/},{className:"params",match:w}] - },...v,...B,...S,I,K,...q,U,G],endsParent:!0,illegal:/["']/},W={ - className:"function",match:n(/\bfunc\b/),contains:[H,R,V,p],illegal:[/\[/,/%/] - },X={className:"function",match:/\b(subscript|init[?!]?)\s*(?=[<(])/,keywords:{ - keyword:"subscript init init? init!",$pattern:/\w+[?!]?/},contains:[R,V,p], - illegal:/\[|%/},J={beginKeywords:"operator",end:e.MATCH_NOTHING_RE,contains:[{ - className:"title",match:b,endsParent:!0,relevance:0}]},Q={ - beginKeywords:"precedencegroup",end:e.MATCH_NOTHING_RE,contains:[{ - className:"title",match:y,relevance:0},{begin:/{/,end:/}/,relevance:0, - endsParent:!0,keywords:[...l,...o],contains:[U]}]};for(const e of K.variants){ - const n=e.contains.find((e=>"interpol"===e.label));n.keywords=D - ;const a=[...B,...k,...S,I,K,...z];n.contains=[...a,{begin:/\(/,end:/\)/, - contains:["self",...a]}]}return{name:"Swift",keywords:D,contains:[...v,W,X,{ - className:"class",beginKeywords:"struct protocol class extension enum", - end:"\\{",excludeEnd:!0,keywords:D,contains:[e.inherit(e.TITLE_MODE,{ - begin:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/}),...B]},J,Q,{ - beginKeywords:"import",end:/$/,contains:[...v],relevance:0 - },...B,...k,...S,I,K,...z,...q,U,G]}}})()); - hljs.registerLanguage("typescript",(()=>{"use strict" - ;const e="[A-Za-z$_][0-9A-Za-z$_]*",n=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],a=["true","false","null","undefined","NaN","Infinity"],s=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer","BigInt64Array","BigUint64Array","BigInt"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]) - ;function t(e){return r("(?=",e,")")}function r(...e){return e.map((e=>{ - return(n=e)?"string"==typeof n?n:n.source:null;var n})).join("")}return i=>{ - const c={$pattern:e, - keyword:n.concat(["type","namespace","typedef","interface","public","private","protected","implements","declare","abstract","readonly"]), - literal:a, - built_in:s.concat(["any","void","number","boolean","string","object","never","enum"]) - },o={className:"meta",begin:"@[A-Za-z$_][0-9A-Za-z$_]*"},l=(e,n,a)=>{ - const s=e.contains.findIndex((e=>e.label===n)) - ;if(-1===s)throw Error("can not find mode to replace");e.contains.splice(s,1,a) - },b=(i=>{const c=e,o={begin:/<[A-Za-z0-9\\._:-]+/, - end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(e,n)=>{ - const a=e[0].length+e.index,s=e.input[a];"<"!==s?">"===s&&(((e,{after:n})=>{ - const a="</"+e[0].slice(1);return-1!==e.input.indexOf(a,n)})(e,{after:a - })||n.ignoreMatch()):n.ignoreMatch()}},l={$pattern:e,keyword:n,literal:a, - built_in:s},b="\\.([0-9](_?[0-9])*)",d="0|[1-9](_?[0-9])*|0[0-7]*[89][0-9]*",g={ - className:"number",variants:[{ - begin:`(\\b(${d})((${b})|\\.)?|(${b}))[eE][+-]?([0-9](_?[0-9])*)\\b`},{ - begin:`\\b(${d})\\b((${b})\\b|\\.)?|(${b})\\b`},{ - begin:"\\b(0|[1-9](_?[0-9])*)n\\b"},{ - begin:"\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?\\b"},{ - begin:"\\b0[bB][0-1](_?[0-1])*n?\\b"},{begin:"\\b0[oO][0-7](_?[0-7])*n?\\b"},{ - begin:"\\b0[0-7]+n?\\b"}],relevance:0},u={className:"subst",begin:"\\$\\{", - end:"\\}",keywords:l,contains:[]},E={begin:"html`",end:"",starts:{end:"`", - returnEnd:!1,contains:[i.BACKSLASH_ESCAPE,u],subLanguage:"xml"}},m={ - begin:"css`",end:"",starts:{end:"`",returnEnd:!1, - contains:[i.BACKSLASH_ESCAPE,u],subLanguage:"css"}},y={className:"string", - begin:"`",end:"`",contains:[i.BACKSLASH_ESCAPE,u]},_={className:"comment", - variants:[i.COMMENT(/\/\*\*(?!\/)/,"\\*/",{relevance:0,contains:[{ - className:"doctag",begin:"@[A-Za-z]+",contains:[{className:"type",begin:"\\{", - end:"\\}",relevance:0},{className:"variable",begin:c+"(?=\\s*(-)|$)", - endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}] - }),i.C_BLOCK_COMMENT_MODE,i.C_LINE_COMMENT_MODE] - },p=[i.APOS_STRING_MODE,i.QUOTE_STRING_MODE,E,m,y,g,i.REGEXP_MODE] - ;u.contains=p.concat({begin:/\{/,end:/\}/,keywords:l,contains:["self"].concat(p) - });const N=[].concat(_,u.contains),f=N.concat([{begin:/\(/,end:/\)/,keywords:l, - contains:["self"].concat(N)}]),A={className:"params",begin:/\(/,end:/\)/, - excludeBegin:!0,excludeEnd:!0,keywords:l,contains:f};return{name:"Javascript", - aliases:["js","jsx","mjs","cjs"],keywords:l,exports:{PARAMS_CONTAINS:f}, - illegal:/#(?![$_A-z])/,contains:[i.SHEBANG({label:"shebang",binary:"node", - relevance:5}),{label:"use_strict",className:"meta",relevance:10, - begin:/^\s*['"]use (strict|asm)['"]/ - },i.APOS_STRING_MODE,i.QUOTE_STRING_MODE,E,m,y,_,g,{ - begin:r(/[{,\n]\s*/,t(r(/(((\/\/.*$)|(\/\*(\*[^/]|[^*])*\*\/))\s*)*/,c+"\\s*:"))), - relevance:0,contains:[{className:"attr",begin:c+t("\\s*:"),relevance:0}]},{ - begin:"("+i.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*", - keywords:"return throw case",contains:[_,i.REGEXP_MODE,{className:"function", - begin:"(\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)|"+i.UNDERSCORE_IDENT_RE+")\\s*=>", - returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{ - begin:i.UNDERSCORE_IDENT_RE,relevance:0},{className:null,begin:/\(\s*\)/,skip:!0 - },{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:l,contains:f}]}] - },{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{ - variants:[{begin:"<>",end:"</>"},{begin:o.begin,"on:begin":o.isTrulyOpeningTag, - end:o.end}],subLanguage:"xml",contains:[{begin:o.begin,end:o.end,skip:!0, - contains:["self"]}]}],relevance:0},{className:"function", - beginKeywords:"function",end:/[{;]/,excludeEnd:!0,keywords:l, - contains:["self",i.inherit(i.TITLE_MODE,{begin:c}),A],illegal:/%/},{ - beginKeywords:"while if switch catch for"},{className:"function", - begin:i.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{", - returnBegin:!0,contains:[A,i.inherit(i.TITLE_MODE,{begin:c})]},{variants:[{ - begin:"\\."+c},{begin:"\\$"+c}],relevance:0},{className:"class", - beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"[\]]/,contains:[{ - beginKeywords:"extends"},i.UNDERSCORE_TITLE_MODE]},{begin:/\b(?=constructor)/, - end:/[{;]/,excludeEnd:!0,contains:[i.inherit(i.TITLE_MODE,{begin:c}),"self",A] - },{begin:"(get|set)\\s+(?="+c+"\\()",end:/\{/,keywords:"get set", - contains:[i.inherit(i.TITLE_MODE,{begin:c}),{begin:/\(\)/},A]},{begin:/\$[(.]/}] - }})(i) - ;return Object.assign(b.keywords,c),b.exports.PARAMS_CONTAINS.push(o),b.contains=b.contains.concat([o,{ - beginKeywords:"namespace",end:/\{/,excludeEnd:!0},{beginKeywords:"interface", - end:/\{/,excludeEnd:!0,keywords:"interface extends" - }]),l(b,"shebang",i.SHEBANG()),l(b,"use_strict",{className:"meta",relevance:10, - begin:/^\s*['"]use strict['"]/ - }),b.contains.find((e=>"function"===e.className)).relevance=0,Object.assign(b,{ - name:"TypeScript",aliases:["ts","tsx"]}),b}})()); - hljs.registerLanguage("vbnet",(()=>{"use strict";function e(e){ - return e?"string"==typeof e?e:e.source:null}function n(...n){ - return n.map((n=>e(n))).join("")}function t(...n){ - return"("+n.map((n=>e(n))).join("|")+")"}return e=>{ - const a=/\d{1,2}\/\d{1,2}\/\d{4}/,i=/\d{4}-\d{1,2}-\d{1,2}/,s=/(\d|1[012])(:\d+){0,2} *(AM|PM)/,r=/\d{1,2}(:\d{1,2}){1,2}/,o={ - className:"literal",variants:[{begin:n(/# */,t(i,a),/ *#/)},{ - begin:n(/# */,r,/ *#/)},{begin:n(/# */,s,/ *#/)},{ - begin:n(/# */,t(i,a),/ +/,t(s,r),/ *#/)}]},l=e.COMMENT(/'''/,/$/,{contains:[{ - className:"doctag",begin:/<\/?/,end:/>/}]}),c=e.COMMENT(null,/$/,{variants:[{ - begin:/'/},{begin:/([\t ]|^)REM(?=\s)/}]});return{name:"Visual Basic .NET", - aliases:["vb"],case_insensitive:!0,classNameAliases:{label:"symbol"},keywords:{ - keyword:"addhandler alias aggregate ansi as async assembly auto binary by byref byval call case catch class compare const continue custom declare default delegate dim distinct do each equals else elseif end enum erase error event exit explicit finally for friend from function get global goto group handles if implements imports in inherits interface into iterator join key let lib loop me mid module mustinherit mustoverride mybase myclass namespace narrowing new next notinheritable notoverridable of off on operator option optional order overloads overridable overrides paramarray partial preserve private property protected public raiseevent readonly redim removehandler resume return select set shadows shared skip static step stop structure strict sub synclock take text then throw to try unicode until using when where while widening with withevents writeonly yield", - built_in:"addressof and andalso await directcast gettype getxmlnamespace is isfalse isnot istrue like mod nameof new not or orelse trycast typeof xor cbool cbyte cchar cdate cdbl cdec cint clng cobj csbyte cshort csng cstr cuint culng cushort", - type:"boolean byte char date decimal double integer long object sbyte short single string uinteger ulong ushort", - literal:"true false nothing"}, - illegal:"//|\\{|\\}|endif|gosub|variant|wend|^\\$ ",contains:[{ - className:"string",begin:/"(""|[^/n])"C\b/},{className:"string",begin:/"/, - end:/"/,illegal:/\n/,contains:[{begin:/""/}]},o,{className:"number",relevance:0, - variants:[{begin:/\b\d[\d_]*((\.[\d_]+(E[+-]?[\d_]+)?)|(E[+-]?[\d_]+))[RFD@!#]?/ - },{begin:/\b\d[\d_]*((U?[SIL])|[%&])?/},{begin:/&H[\dA-F_]+((U?[SIL])|[%&])?/},{ - begin:/&O[0-7_]+((U?[SIL])|[%&])?/},{begin:/&B[01_]+((U?[SIL])|[%&])?/}]},{ - className:"label",begin:/^\w+:/},l,c,{className:"meta", - begin:/[\t ]*#(const|disable|else|elseif|enable|end|externalsource|if|region)\b/, - end:/$/,keywords:{ - "meta-keyword":"const disable else elseif enable end externalsource if region then" - },contains:[c]}]}}})()); - hljs.registerLanguage("yaml",(()=>{"use strict";return e=>{ - var n="true false yes no null",a="[\\w#;/?:@&=+$,.~*'()[\\]]+",s={ - className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/ - },{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:"template-variable", - variants:[{begin:/\{\{/,end:/\}\}/},{begin:/%\{/,end:/\}/}]}]},i=e.inherit(s,{ - variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),l={ - end:",",endsWithParent:!0,excludeEnd:!0,keywords:n,relevance:0},t={begin:/\{/, - end:/\}/,contains:[l],illegal:"\\n",relevance:0},g={begin:"\\[",end:"\\]", - contains:[l],illegal:"\\n",relevance:0},b=[{className:"attr",variants:[{ - begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{ - begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---\\s*$", - relevance:10},{className:"string", - begin:"[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^ ][^\\n]*\\n(\\2[^\\n]+\\n?)*"},{ - begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0, - relevance:0},{className:"type",begin:"!\\w+!"+a},{className:"type", - begin:"!<"+a+">"},{className:"type",begin:"!"+a},{className:"type",begin:"!!"+a - },{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta", - begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"-(?=[ ]|$)", - relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:n,keywords:{literal:n}},{ - className:"number", - begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b" - },{className:"number",begin:e.C_NUMBER_RE+"\\b",relevance:0},t,g,s],r=[...b] - ;return r.pop(),r.push(i),l.contains=r,{name:"YAML",case_insensitive:!0, - aliases:["yml"],contains:b}}})()); \ No newline at end of file diff --git a/docs/js/highlight.js.LICENSE b/docs/js/highlight.js.LICENSE deleted file mode 100644 index d4906cd67cb..00000000000 --- a/docs/js/highlight.js.LICENSE +++ /dev/null @@ -1,11 +0,0 @@ -Copyright (c) 2006-2021, Ivan Sagalaev - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/docs/js/main.js b/docs/js/main.js index eb8abfe8b2d..0b48675990d 100644 --- a/docs/js/main.js +++ b/docs/js/main.js @@ -1,32 +1,38 @@ +const siteVersion = "2.2.0"; // site version is different from skript version +const ghAPI = "https://api.github.com/repos/SkriptLang/Skript"; + // ID Scroll const links = document.querySelectorAll("div.item-wrapper"); -const contents = document.querySelectorAll("#content")[0]; +const contents = document.querySelector("#content"); -lastActive = null; +lastActiveSideElement = null; +navContents = document.getElementById("nav-contents"); if (contents) { - contents.addEventListener('scroll', (e) => { - links.forEach((ha) => { - const rect = ha.getBoundingClientRect(); - if (rect.top > 0 && rect.top < 150) { - const location = window.location.toString().split("#")[0]; - history.replaceState(null, null, location + "#" + ha.id); - - if (lastActive != null) { - lastActive.classList.remove("active-item"); + setTimeout(() => { + contents.addEventListener('scroll', (e) => { + links.forEach((ha) => { + const rect = ha.getBoundingClientRect(); + if (rect.top > 0 && rect.top < 350) { + // const location = window.location.toString().split("#")[0]; + // history.replaceState(null, null, location + "#" + ha.id); // Not needed since lastActiveSideElement + causes history spam + + if (lastActiveSideElement != null) { + lastActiveSideElement.classList.remove("active-item"); } - lastActive = document.querySelectorAll(`#nav-contents a[href="#${ha.id}"]`)[0]; - if (lastActive != null) { - lastActive.classList.add("active-item"); + lastActiveSideElement = document.querySelectorAll(`#nav-contents a[href="#${ha.id}"]`)[0]; + if (lastActiveSideElement != null) { + lastActiveSideElement.classList.add("active-item"); + navContents.scroll(0, lastActiveSideElement.offsetTop - 100); } } }); - }); + })}, 50); // respect auto hash scroll } - // Active Tab +// Active Tab const pageLink = window.location.toString().replaceAll(/(.*)\/(.+?).html(.*)/gi, '$2'); if (pageLink === "" || pageLink == window.location.toString()) // home page - when there is no `.+?.html` pageLink will = windown.location due to current regex document.querySelectorAll('#global-navigation a[href="index.html"]')[0].classList.add("active-tab"); @@ -41,101 +47,60 @@ for (e in {"content-no-docs": 0, "content": 1}) { document.querySelectorAll('#side-nav')[0].classList.add('no-left-panel'); } -// <> Magic Text -function getRandomChar() { - chars = "ÂÃÉÊÐÑÙÚÛÜéêëãòóôēĔąĆćŇň1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ~!@#$%^&*()-=_+{}["; - return chars.charAt(Math.floor(Math.random() * chars.length) + 1) -} - -function magicTextGen(element) { - var msg = element.textContent; - var length = msg.length; - - setInterval(() => { - var newMsg = ""; - for (i = 0; i <= length; i++) { - newMsg += getRandomChar(msg.charAt(i)); - } - element.textContent = newMsg; - - }, 30) -} - -function renderMagicText() { - document.querySelectorAll('.magic-text').forEach((e) => { - magicTextGen(e); - }) -} +// Magic Text renderMagicText(); -// Magic Text </> - -// <> Mobile anchor correction due to doubled size header) -function offsetAnchor(event, element) { - if (window.innerWidth <= 768) { - event.preventDefault(); - content = document.querySelectorAll("#content")[0]; - actualElement = document.getElementById(element.getAttribute("href").replace("#", "")); - content.scroll(0, actualElement.offsetTop - 15); - } -} - -document.querySelectorAll("#nav-contents a").forEach((e) => { +// Anchor scroll correction +document.querySelectorAll(".link-icon").forEach((e) => { e.addEventListener("click", (event) => { - offsetAnchor(event, e); + let id = e.getAttribute("href").replace("#", ""); + if (id != "" && id != null) { + // offsetAnchor(event, id); + event.preventDefault(); + toggleSyntax(id); + } }); }) -// Mobile anchor correction </> -// <> Anchor click copy link -function copyToClipboard() { - setTimeout(() => { - var cb = document.body.appendChild(document.createElement("input")); - cb.value = window.location.href; - cb.focus(); - cb.select(); - document.execCommand('copy'); - cb.parentNode.removeChild(cb); - }, 50) -} -function showNotification(text, bgColor, color) { - var noti = document.body.appendChild(document.createElement("span")); - noti.id = "notification-box"; +// Open description/pattern links in same tab rather than scrolling because hash links uses search bar +document.querySelectorAll(".item-wrapper a:not(.link-icon)").forEach((e) => { + e.addEventListener("click", (event) => { + event.preventDefault(); + window.open(e.href); + }); +}) - setTimeout(() => { - noti.textContent = text; - if (bgColor) - noti.styles.backgroundColor = bgColor; - if (color) - noti.styles.backgroundColor = color; - noti.classList.add("activate-notification"); - setTimeout(() => { - noti.classList.remove("activate-notification"); - setTimeout(() => { - noti.parentNode.removeChild(noti); - }, 200); - }, 1500); - }, 50); -} +// Anchor click copy link const currentPageLink = window.location.toString().replaceAll(/(.+?.html)(.*)/gi, '$1'); document.querySelectorAll(".item-title > a").forEach((e) => { e.addEventListener("click", (event) => { - copyToClipboard(); + copyToClipboard(window.location.toString().split(/[?#]/g)[0] + "?search=#" + e.parentElement.parentElement.id); showNotification("✅ Link copied successfully.") }); }) -// Anchor click copy link </> + +// New element label click +document.querySelectorAll(".new-element").forEach((e) => { + e.addEventListener("click", (event) => { + searchNow("is:new"); + }); +}) // <> Search Bar +const versionComparePattern = /.*?(\d\.\d(?:\.\d|))(\+|-|).*/gi; +const versionPattern = / ?v(?:ersion|):(\d\.\d(?:\.\d|-(?:beta|alpha|dev)\d*|))(\+|-|)/gi; +const typePattern = / ?t(?:ype|):(condition|expression|type|effect|event|section|effectsection|function)/gi; +const newPattern = / ?is:(new)/gi; +const resultsFoundText = "result(s) found"; + function versionCompare(base, target) { // Return -1, 0, 1 - base = base.replaceAll(/(\d\.\d(\.\d|)).*/gi, "$1").replaceAll(/[^0-9]/gi, ""); // Handle special chars and versions like -dev21 and filter non digits - target = target.replaceAll(/(\d\.\d(\.\d|)).*/gi, "$1").replaceAll(/[^0-9]/gi, ""); + base = base.replaceAll(versionComparePattern, "$1").replaceAll(/[^0-9]/gi, ""); + target = target.replaceAll(versionComparePattern, "$1").replaceAll(/[^0-9]/gi, ""); - base = parseInt(base) < 100 ? parseInt(base) * 10 : parseInt(base); // convert ten's to hundred's to fix (2.5.1+ not reiggering 2.6 by converting 26 -> 260) + base = parseInt(base) < 100 ? parseInt(base) * 10 : parseInt(base); // convert ten's to hundred's to fix (2.5.1+ not triggering 2.6 by converting 26 -> 260) target = parseInt(target) < 100 ? parseInt(target) * 10 : parseInt(target); - if (target > base) return 1 if (target == base) @@ -144,112 +109,300 @@ function versionCompare(base, target) { // Return -1, 0, 1 return -1 } +var searchBar; +var searchIcon; + +// Load search link +var linkParams = new URLSearchParams(window.location.href.replace("+", "%2B").split("?")[1]) // URLSearchParams decode '+' as space while encodeURI keeps + as is +if (linkParams && linkParams.get("search")) { + setTimeout(() => { + searchNow(linkParams.get("search")) // anchor link sometimes appear after the search param so filter it + }, 20) // Until searchBar is loaded +} else { + // Search the hash value if available + requestedElementID = window.location.hash; + if (requestedElementID != undefined && requestedElementID != "") { + setTimeout(() => { + searchNow(requestedElementID); + }, 20) // Until searchBar is loaded + } +} + var content = document.getElementById("content"); if (content) { - content.insertAdjacentHTML('afterbegin', '<input id="search-bar" type="text" placeholder="🔍 Search the documents.. (filters: v:1.0[.0][+])">'); -} + let isNewPage = linkParams.get("isNew") != null; + content.insertAdjacentHTML('afterbegin', `<a id="search-icon" ${isNewPage ? 'class="search-icon-new"' : ""} title="Copy the search link."><img style="width: 28px;" src="./assets/search.svg"></a>`); + content.insertAdjacentHTML('afterbegin', `<span><input id="search-bar" ${isNewPage ? 'class="search-bar-version"' : ""} type="text" placeholder="Search the docs 🔍" title="Available Filters: Version: v:2.5.3 v:2.2+ v:2.4- Type: t:expression t:condition etc. New: is:new"><span id="search-bar-after" style="display: none;">0 ${resultsFoundText}</span></span>`); + searchBar = document.getElementById("search-bar"); + searchIcon = document.getElementById("search-icon"); -var searchBar = document.getElementById("search-bar"); -if (searchBar) { - searchBar.focus() // To easily search without the need to click - searchBar.addEventListener('keydown', (event) => { - setTimeout(() => { // Important to actually get the value after typing or deleting - let allElements = document.querySelectorAll(".item-wrapper"); - let searchValue = searchBar.value; - let count = 0; // Check if any matches found - let pass; + if (isNewPage) { + let tags = [] + let options = "<select id='search-version' name='versions' id='versions' onchange='checkVersionFilter()'></select>" + content.insertAdjacentHTML('afterbegin', `<span>${options}</span>`); + options = document.getElementById("search-version"); + + getApiValue(null, "skript-versions", "tags?per_page=83&page=2", (data, isCached) => { // 83 and page 2 matters to filter dev branches (temporary solution) + if (isCached) + data = data.split(","); + + for (let i = 0; i < data.length; i++) { + let tag; + if (isCached) { + tag = data[i]; + } else { + tag = data[i]["name"]; + } + tags.push(tag.replaceAll(/(.*)-(dev|beta|alpha).*/gi, "$1")); + } + + tags = [...new Set(tags)] // remove duplicates + + for (let i = 0; i < tags.length; i++) { + let option = document.createElement('option') + option.value = tags[i] + option.textContent = "Since v" + tags[i] + options.appendChild(option) + } + + if (!linkParams.get("search") && !window.location.href.match(/.*?#.+/)) + searchNow(`v:${tags[0]}+`) + + return tags; + }, true) - let version = searchValue.replaceAll(/v(?:ersion|)\:(\d\.\d(?:\.\d)?)\+?/gi, "$1").replaceAll(/[^0-9.]/gi, ""); - let versionAndUp = searchValue.replaceAll(/v(?:ersion|)\:\d\.\d(?:\.\d)?(\+?)/gi, "$1").replaceAll(/[^+]/g, "") == "+"; - searchValue = searchValue.replaceAll(/ ?v(ersion|)\:(\d\.\d(\.\d)?)\+?/gi, "") // Don't include filters in the search - searchValue = searchValue.replaceAll(/( ){2,}/gi, " ") // Filter duplicate spaces - - searchValue = searchValue.replaceAll(/[^a-zA-Z0-9 ]/gi, ""); // Filter none alphabet and digits to avoid regex errors - - allElements.forEach((e) => { - let patterns = document.querySelectorAll(`#${e.id} .item-details .skript-code-block`); - for (let i = 0; i < patterns.length; i++) { // Search in the patterns for better results - let pattern = patterns[i]; - // let regex = new RegExp("\\b" + searchValue + "\\b", "gi") // Makes live character update useless - let regex = new RegExp(searchValue, "gi") - let name = document.querySelectorAll(`#${e.id} .item-title h1`)[0].textContent // Syntax Name - let versionFound; - - if (version != "") { - versionFound = document.querySelectorAll(`#${e.id} .item-details:nth-child(2) td:nth-child(2)`)[0].textContent.includes(version); - + } +} else { + content = document.getElementById("content-no-docs") +} + +// Copy search link +if (searchIcon) { + searchIcon.addEventListener('click', (event) => { + let link = window.location.href.split(/[?#]/g)[0] // link without search param + link += `?search=${encodeURI(searchBar.value)}` + copyToClipboard(link) + showNotification("✅ Search link copied.") + }) +} + +// Used when selecting a version from the dropdown +function checkVersionFilter() { + let el = document.getElementById("search-version") + if (el) { + searchNow(`v:${el.value}+`) + } +} + +function searchNow(value = "") { + if (value != "") // Update searchBar value + searchBar.value = value; + + let allElements = document.querySelectorAll(".item-wrapper"); + let searchValue = searchBar.value; + let count = 0; // Check if any matches found + let pass; + + // version + let version = ""; + let versionAndUp = false; + let versionAndDown = false; + if (searchValue.match(versionPattern)) { + let verExec = versionPattern.exec(searchValue); + version = verExec[1]; + if (verExec.length > 2) { + versionAndUp = verExec[2] == "+" == true; + versionAndDown = verExec[2] == "-" == true; + } + searchValue = searchValue.replaceAll(versionPattern, "") // Don't include filters in the search + } + + // Type + let filterType; + if (searchValue.match(typePattern)) { + filterType = typePattern.exec(searchValue)[1]; + searchValue = searchValue.replaceAll(typePattern, "") + } + + // News + let filterNew; + if (searchValue.match(newPattern)) { + filterNew = newPattern.exec(searchValue)[1] == "new"; + searchValue = searchValue.replaceAll(newPattern, "") + } + + searchValue = searchValue.replaceAll(/( ){2,}/gi, " ") // Filter duplicate spaces + searchValue = searchValue.replaceAll(/[^a-zA-Z0-9 #_]/gi, ""); // Filter none alphabet and digits to avoid regex errors + + allElements.forEach((e) => { + let patterns = document.querySelectorAll(`#${e.id} .item-details .skript-code-block`); + for (let i = 0; i < patterns.length; i++) { // Search in the patterns for better results + let pattern = patterns[i]; + let regex = new RegExp(searchValue, "gi") + let name = document.querySelectorAll(`#${e.id} .item-title h1`)[0].textContent // Syntax Name + let desc = document.querySelectorAll(`#${e.id} .item-description`)[0].textContent // Syntax Desc + let keywords = e.getAttribute("data-keywords") + let id = e.id // Syntax ID + let filtersFound = false; + + // Version check + let versionFound; + if (version != "") { + versionFound = versionCompare(version, document.querySelectorAll(`#${e.id} .item-details:nth-child(2) td:nth-child(2)`)[0].textContent) == 0; + + if (versionAndUp || versionAndDown) { + let versions = document.querySelectorAll(`#${e.id} .item-details:nth-child(2) td:nth-child(2)`)[0].textContent.split(","); + for (const v in versions) { // split on ',' without space in case some version didn't have space and versionCompare will handle it if (versionAndUp) { - let versions = document.querySelectorAll(`#${e.id} .item-details:nth-child(2) td:nth-child(2)`)[0].textContent.split(","); - for (const v in versions) { // split on ',' without space in case some version didn't have space and versionCompare will handle it - if (versionCompare(version, versions[v]) == 1 == true) { - versionFound = true; - break; // Performance - } + if (versionCompare(version, versions[v]) == 1) { + versionFound = true; + break; // Performance + } + } else if (versionAndDown) { + if (versionCompare(version, versions[v]) == -1) { + versionFound = true; + break; // Performance } } - } else { - versionFound = true; - } - if ((regex.test(pattern.textContent) || regex.test(name) || searchValue == "") && versionFound) { - pass = true - break; // Performance } } + } else { + versionFound = true; + } - // Filter - let sideNavItem = document.querySelectorAll(`#nav-contents a[href="#${e.id}"]`)[0]; - if (pass) { + let filterNewFound = true; + if (filterNew) { + filterNewFound = document.querySelector(`#${e.id} .item-title .new-element`) != null + } + + let filterTypeFound = true; + let filterTypeEl = document.querySelector(`#${e.id} .item-title .item-type`); + if (filterType) { + filterTypeFound = filterType.toLowerCase() === filterTypeEl.textContent.toLowerCase() + } + + if (filterNewFound && versionFound && filterTypeFound) + filtersFound = true + + if ((regex.test(pattern.textContent.replaceAll("[ ]", " ")) || regex.test(name) || + regex.test(desc) || regex.test(keywords) || "#" + id == searchValue || searchValue == "") && filtersFound) { // Replacing '[ ]' will improve some searching cases such as 'off[ ]hand' + pass = true + break; // Performance + } + } + + // Filter + let sideNavItem = document.querySelectorAll(`#nav-contents a[href="#${e.id}"]`); // Since we have new addition filter we need to loop this + if (pass) { + e.style.display = null; + if (sideNavItem) + sideNavItem.forEach(e => { e.style.display = null; - if (sideNavItem) - sideNavItem.style.display = null; - count++; - } else { + }) + count++; + } else { + e.style.display = "none"; + if (sideNavItem) + sideNavItem.forEach(e => { e.style.display = "none"; - if (sideNavItem) - sideNavItem.style.display = "none"; - } + }) + } - pass = false; // reset - }) + pass = false; // Reset + }) - if (count == 0) { - if (document.getElementById("no-matches") == null) - document.getElementById("content").insertAdjacentHTML('beforeend', '<p id="no-matches" style="text-align: center;">No matches found.</p>'); - } else { - if (document.getElementById("no-matches") != null) - document.getElementById("no-matches").remove(); - } - - count = 0; // reset - }, 100); // Spam delay for better performance + searchResultBox = document.getElementById("search-bar-after"); + if (count > 0 && (version != "" || searchValue != "" || filterType || filterNew)) { + searchResultBox.textContent = `${count} ${resultsFoundText}` + searchResultBox.style.display = null; + } else { + searchResultBox.style.display = "none"; + } + + if (count == 0) { + if (document.getElementById("no-matches") == null) + document.getElementById("content").insertAdjacentHTML('beforeend', '<p id="no-matches" style="text-align: center;">No matches found.</p>'); + } else { + if (document.getElementById("no-matches") != null) + document.getElementById("no-matches").remove(); + } + + count = 0; // reset +} - }); +if (searchBar) { + searchBar.focus() // To easily search after page loading without the need to click + searchBar.addEventListener('keydown', (event) => { + setTimeout(() => { // Important to actually get the value after typing or deleting + better performance + searchNow(); + }, 100); + }); } // Search Bar </> -// <> HighlightJS -document.querySelectorAll('pre.code').forEach(el => { // Apply the code formatting on the same <pre> not the <code> inside to not break the styling - hljs.highlightElement(el); +// <> Placeholders +// To save performance we use the class "placeholder" on the wrapper element of elements that contains the placeholder +// To only select those elements and replace their innerHTML +document.querySelectorAll(".placeholder").forEach(e => { + replacePlaceholders(e); }); -document.querySelectorAll('div .skript-code-block').forEach(el => { // This lags the docs pages due to the huge amount of elements being parsed, we can disable this if lag is so bad for some people or keep it because it looks AMAZING! - hljs.highlightElement(el); +// Placeholders </> + +// <> Syntax Highlighting +document.addEventListener("DOMContentLoaded", function (event) { + setTimeout(() => { + document.querySelectorAll('.item-examples .skript-code-block').forEach(el => { + highlightElement(el); + }); + document.querySelectorAll('pre code').forEach(el => { + highlightElement(el); + }); + document.querySelectorAll('.box.skript-code-block').forEach(el => { + highlightElement(el); + }); + }, 100); }); -// HighlightJS </> +// Syntax Highlighting </> -// <> Placeholders -function replacePlaceholders(html) { - let innerHTML = html.innerHTML; - if (innerHTML.includes("${latest-version}")) { - let lv = $.getJSON("https://api.github.com/repos/SkriptLang/Skript/releases?per_page=1", (data) => { - html.innerHTML = html.innerHTML.replaceAll("${latest-version}", data[0]["tag_name"]); - }) - } - if (innerHTML.includes("${contributors-size}")) { - let lv = $.getJSON("https://api.github.com/repos/SkriptLang/Skript/contributors?per_page=500", (data) => { - html.innerHTML = html.innerHTML.replaceAll("${contributors-size}", data.length); + +// <> Example Collapse +var examples = document.querySelectorAll(".item-examples p"); +if (examples) { + setTimeout(() => { + examples.forEach(e => { + let pElement = e; + let divElement = e.parentElement.children[1]; + pElement.addEventListener("click", ev => { + if (pElement.classList.contains("example-details-opened")) { + pElement.classList.remove("example-details-opened"); + pElement.classList.add("example-details-closed"); + divElement.style.display = "none"; + } else { + pElement.classList.remove("example-details-closed"); + pElement.classList.add("example-details-opened"); + divElement.style.display = "block"; + } + }) }) - } + }, 50) +} +// Example Collapse </> + +// <> Cookies Accecpt +if (!isCookiesAccepted) { + content.insertAdjacentHTML('beforeend', `<div id="cookies-bar"> <p> We use cookies and local storage to enhance your browsing experience and store github related statistics. By clicking "Accept", you consent to our use of cookies and local storage. </p><div style="padding: 10px; white-space: nowrap;"> <button id="cookies-accept">Accept</button> <button id="cookies-deny">Deny</button> </div></div>`); } -replacePlaceholders(document.querySelector("body")); -// Placeholders </> +let cookiesBar = document.querySelector("#cookies-bar");; +let cookiesAccept = document.querySelector("#cookies-accept"); +let cookiesDeny = document.querySelector("#cookies-deny"); +if (cookiesAccept && cookiesDeny) { + cookiesAccept.addEventListener('click', () => { + setCookie('cookies-accepted', true, 99, true); + cookiesBar.remove(); + }); + cookiesDeny.addEventListener('click', () => { + cookiesBar.remove(); + }); +} +// Cookies Accecpt </> \ No newline at end of file diff --git a/docs/js/theme-switcher.js b/docs/js/theme-switcher.js new file mode 100644 index 00000000000..9e6314187fb --- /dev/null +++ b/docs/js/theme-switcher.js @@ -0,0 +1,34 @@ +// +// This file is made to fix theme flicker at load due to 'defer' in main.js loading +// + +// Auto load DarkMode from cookies +if (getCookie("darkMode") == "false") { + document.body.setAttribute('data-theme', 'white') + document.body.insertAdjacentHTML('beforeend', `<img style="z-index: 99;" src="./assets/light-on.svg" id="theme-switch">`); +} else { + document.body.insertAdjacentHTML('beforeend', `<img style="z-index: 99;" src="./assets/light-off.svg" id="theme-switch">`); + + // Auto load from system theme + // const darkThemeMq = window.matchMedia("(prefers-color-scheme: dark)"); + // if (darkThemeMq.matches) { + // document.body.removeAttribute('data-theme'); + // } else { + // document.body.setAttribute('data-theme', 'white') + // } + } + +setTimeout(() => { + var themeSwitcher = document.getElementById('theme-switch'); + themeSwitcher.addEventListener('click', (event) => { + if (document.body.getAttribute("data-theme") == null) { + document.body.setAttribute('data-theme', 'white'); + event.target.src = "./assets/light-on.svg"; + setCookie("darkMode", "false", 99); + } else { + event.target.src = "./assets/light-off.svg"; + document.body.removeAttribute('data-theme'); + setCookie("darkMode", "true", 99); + } + }); +}, 500); // For some reason this wouldn't work in index.html (only) unless some delay is added o.O \ No newline at end of file diff --git a/docs/sections.html b/docs/sections.html new file mode 100644 index 00000000000..679e7adae44 --- /dev/null +++ b/docs/sections.html @@ -0,0 +1,5 @@ +<h1 id="nav-title">Sections</h1> + +<div id="content"> + ${generate sections desc_full.html} +</div> diff --git a/docs/template.html b/docs/template.html index 0596815789c..2dbe1bf6c4a 100644 --- a/docs/template.html +++ b/docs/template.html @@ -1,5 +1,5 @@ <!doctype html> -<html> +<html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=0.8, maximum-scale=1.0"> @@ -7,20 +7,23 @@ <meta name="description" content="Skript is a Bukkit plugin which allows server admins to customize their server easily, but without the hassle of programming a plugin or asking/paying someone to program a plugin for them. - SkriptLang/Skript"> <link rel="icon" type="image/png" href="./assets/icon.png"> - + <meta property="og:type" content="website"> <meta property="og:title" content="Skript Documentation"> + <meta property="og:site_name" content="Skript Documentation"> <meta property="og:description" content="Skript is a Bukkit plugin which allows server admins to customize their server easily, but without the hassle of programming a plugin or asking/paying someone to program a plugin for them."> <meta property="og:image" content="https://skriptlang.github.io/Skript/assets/icon.png"> <meta property="og:url" content="https://skriptlang.github.io/Skript/"> + <meta name="theme-color" content="#ff9800"> <title>Skript Documentation - ${skript.version}</title> - <link href="css/styles.css" rel="stylesheet"> - <link rel="stylesheet" href="css/highlightjs.min.css"> - <script src="./js/highlight.js"></script> + <link href="css/styles.css" rel="stylesheet"> - <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> + <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js" defer></script> + + <script src="./js/functions.js"></script> + <script src="./js/main.js" defer></script> <link rel="preconnect" href="https://fonts.gstatic.com"> <link href="https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;0,900;1,200;1,300;1,400;1,500;1,600;1,700;1,900&display=swap" rel="stylesheet"> @@ -28,8 +31,13 @@ <link rel="preconnect" href="https://fonts.gstatic.com"> <link href="https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap" rel="stylesheet"> + <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.15.4/css/all.css" integrity="sha384-DyZ88mC6Up2uqS4h/KRgHuoeGwBcD4Ng9SiP4dIRy0EXTlnuz47vAwmeGwVChigm" crossorigin="anonymous"> + </head> <body> + <!-- loaded inside body without defer to fix theme flicker --> + <script src="./js/theme-switcher.js"></script> + ${include navbar.html} <nav id="side-nav"> <div id="nav-contents"> @@ -38,7 +46,6 @@ </nav> ${content} - - <script src="./js/main.js"></script> + </body> </html> diff --git a/docs/templates/desc_full.html b/docs/templates/desc_full.html index 9a88a31ec74..2a82c110f95 100644 --- a/docs/templates/desc_full.html +++ b/docs/templates/desc_full.html @@ -1,6 +1,7 @@ -<div class="item-wrapper" id="${element.id}"> +<div class="item-wrapper type-${element.type}" id="${element.id}" data-keywords="${element.keywords}"> <div class="item-title"> - <h1 style="display: inline-block">${element.name}</h1> <a href="#${element.id}">🔗</a> + <h1 style="display: inline-block">${element.name}</h1> <a href="#${element.id}" class="link-icon">🔗</a> ${if new-element} <p class="new-element">New</p> ${end} + <p class="item-type">${element.type}</p> </div> <div class="item-content"> <table> @@ -29,6 +30,17 @@ <h1 style="display: inline-block">${element.name}</h1> <a href="#${element.id}"> <td class="item-table-label">Requirements:</td> <td>${element.required-plugins}</td> </tr> + + ${end} ${if by-addon} + <tr class="item-details"> + <td class="item-table-label">By Addon:</td> + <td>${element.by-addon}</td> + </tr> + ${end} ${if return-type} + <tr class="item-details"> + <td class="item-table-label">Return Type:</td> + <td><a href="classes.html#${element.return-type-linkcheck}">${element.return-type}</a></td> + </tr> ${end} </table> @@ -37,7 +49,7 @@ <h1 style="display: inline-block">${element.name}</h1> <a href="#${element.id}"> </div> <div class="item-examples"> - <p>Examples:</p> + <p class="example-details-closed">Examples:</p> <div class="skript-code-block"> ${element.examples} diff --git a/docs/templates/navbar.html b/docs/templates/navbar.html index a764d5912aa..6d34dc12128 100644 --- a/docs/templates/navbar.html +++ b/docs/templates/navbar.html @@ -1,13 +1,35 @@ <ul id="global-navigation"> <li><a href="index.html">Home</a></li> - <li><a href="events.html">Events</a></li> - <li><a href="conditions.html">Conditions</a></li> - <li><a href="effects.html">Effects</a></li> - <li><a href="expressions.html">Expressions</a></li> - <li><a href="classes.html">Types</a></li> - <li><a href="text.html">Text</a></li> - <li><a href="functions.html">Functions</a></li> - <li><a href="tutorials.html">Tutorials</a></li> + + <div class="menu-tab"> + <li><a class="menu-tab-item" href="docs.html">Docs <i class="fas fa-caret-down"></i></a></li> + <div class="menu-subtabs"> + <a href="events.html">Events</a> + <a href="conditions.html">Conditions</a> + <a href="sections.html">Sections</a> + <a href="effects.html">Effects</a> + <a href="expressions.html">Expressions</a> + <a href="classes.html">Types</a> + <a href="functions.html">Functions</a> + </div> + </div> + + <li><a href="docs.html?isNew" class="new-tab">New</a></li> + + <div class="menu-tab"> + <li><a class="menu-tab-item" href="tutorials.html">Tutorials <i class="fas fa-caret-down"></i></a></li> + <div class="menu-subtabs"> + <a href="text.html">Text</a> + </div> + </div> + + <div class="menu-tab"> + <li><a class="menu-tab-item" href="#">Dev Tools <i class="fas fa-caret-down"></i></a></li> + <div class="menu-subtabs"> + <a href="javadocs/" target="_blank">Javadocs</a> + </div> + </div> + <li><a href="https://github.com/SkriptLang/Skript/" target="_blank" style="font-weight: bold;">GitHub</a></li> - <li style="font-weight: bold; color: #ff9800; float: right;">v${skript.version}</li> + <li style="margin-left: auto;"><a style="font-weight: bold; color: #ff9800" href="https://github.com/SkriptLang/Skript/releases/tag/${skript.version}" target="_blank">v${skript.version}</a></li> </ul> diff --git a/docs/text.html b/docs/text.html index a199fa4e222..bf7aa0c2009 100644 --- a/docs/text.html +++ b/docs/text.html @@ -126,8 +126,8 @@ <h2 class="title">Colors</h2> height: 10px; "></td> <td>§8</td> - <td>grey</td> - <td>light grey, gray, light gray, silver</td> + <td>dark gray</td> + <td>dark grey</td> </tr> <tr> <td style=" @@ -304,6 +304,11 @@ <h2 class="title">Other Styles</h2> <td>show text, ttp</td> <td>Shows a tooltip when player hovers over text with their mouse</td> </tr> + <tr> + <td>font</td> + <td>f</td> + <td>Change the font of the text (1.16+)</td> + </tr> <tr> <td>insertion</td> <td>insert, ins</td> diff --git a/src/main/java/ch/njol/skript/PatcherTool.java b/src/main/java/ch/njol/skript/PatcherTool.java index 49159da2740..6b2355fdaf9 100644 --- a/src/main/java/ch/njol/skript/PatcherTool.java +++ b/src/main/java/ch/njol/skript/PatcherTool.java @@ -94,7 +94,7 @@ private void patch(Set<Option> options) throws IOException { // JSON injection, see issue #2198 copy("effects.EffMessage", true); copy("expressions.ExprArgument", true); - copy("expressions.ExprColoured", true); + copy("expressions.ExprColored", true); copy("lang.VariableString", true); copy("util.chat.BungeeConverter", true); copy("util.chat.ChatCode", true); diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index b9a8b82c5af..9909763239b 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -479,7 +479,7 @@ public void onEnable() { // Config must be loaded after Java and Skript classes are parseable // ... but also before platform check, because there is a config option to ignore some errors SkriptConfig.load(); - + // Check server software, Minecraft version, etc. if (!checkServerPlatform()) { disabled = true; // Nothing was loaded, nothing needs to be unloaded diff --git a/src/main/java/ch/njol/skript/SkriptCommand.java b/src/main/java/ch/njol/skript/SkriptCommand.java index d29523cfd4d..b0bed1c8000 100644 --- a/src/main/java/ch/njol/skript/SkriptCommand.java +++ b/src/main/java/ch/njol/skript/SkriptCommand.java @@ -20,6 +20,7 @@ import ch.njol.skript.aliases.Aliases; import ch.njol.skript.command.CommandHelp; +import ch.njol.skript.doc.Documentation; import ch.njol.skript.doc.HTMLGenerator; import ch.njol.skript.localization.ArgsMessage; import ch.njol.skript.localization.Language; @@ -55,7 +56,8 @@ public class SkriptCommand implements CommandExecutor { private static final String CONFIG_NODE = "skript command"; - + private static final ArgsMessage m_reloading = new ArgsMessage(CONFIG_NODE + ".reload.reloading"); + // TODO /skript scripts show/list - lists all enabled and/or disabled scripts in the scripts folder and/or subfolders (maybe add a pattern [using * and **]) // TODO document this command on the website private static final CommandHelp SKRIPT_COMMAND_HELP = new CommandHelp("<gray>/<gold>skript", SkriptColor.LIGHT_CYAN, CONFIG_NODE + ".help") @@ -77,10 +79,10 @@ public class SkriptCommand implements CommandExecutor { .add("download") ).add("info" ).add("help"); - + static { // Add command to generate documentation - if (TestMode.GEN_DOCS || new File(Skript.getInstance().getDataFolder() + "/doc-templates").exists()) + if (TestMode.GEN_DOCS || Documentation.isDocsTemplateFound()) SKRIPT_COMMAND_HELP.add("gen-docs"); // Add command to run individual tests @@ -88,8 +90,6 @@ public class SkriptCommand implements CommandExecutor { SKRIPT_COMMAND_HELP.add("test"); } - private static final ArgsMessage m_reloading = new ArgsMessage(CONFIG_NODE + ".reload.reloading"); - private static void reloading(CommandSender sender, String what, Object... args) { what = args.length == 0 ? Language.get(CONFIG_NODE + ".reload." + what) : Language.format(CONFIG_NODE + ".reload." + what, args); Skript.info(sender, StringUtils.fixCapitalization(m_reloading.toString(what))); @@ -395,13 +395,13 @@ else if (args[0].equalsIgnoreCase("info")) { } else if (args[0].equalsIgnoreCase("gen-docs")) { - File templateDir = new File(Skript.getInstance().getDataFolder() + "/doc-templates/"); + File templateDir = Documentation.getDocsTemplateDirectory(); if (!templateDir.exists()) { Skript.error(sender, "Cannot generate docs! Documentation templates not found at 'plugins/Skript/doc-templates/'"); TestMode.docsFailed = true; return true; } - File outputDir = new File(Skript.getInstance().getDataFolder() + "/docs"); + File outputDir = Documentation.getDocsOutputDirectory(); outputDir.mkdirs(); HTMLGenerator generator = new HTMLGenerator(templateDir, outputDir); Skript.info(sender, "Generating docs..."); diff --git a/src/main/java/ch/njol/skript/classes/ClassInfo.java b/src/main/java/ch/njol/skript/classes/ClassInfo.java index 01c3cc3d808..a7c09a76493 100644 --- a/src/main/java/ch/njol/skript/classes/ClassInfo.java +++ b/src/main/java/ch/njol/skript/classes/ClassInfo.java @@ -386,6 +386,11 @@ public String getSince() { public String getDocName() { return docName; } + + @Nullable + public String[] getRequiredPlugins() { + return requiredPlugins; + } /** * Gets overridden documentation id of this this type. If no override has @@ -397,6 +402,10 @@ public String getDocName() { public String getDocumentationID() { return documentationId; } + + public boolean hasDocs() { + return getDocName() != null && !ClassInfo.NO_DOC.equals(getDocName()); + } // === ORDERING === 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 0524fe2f5d8..7302fc5d386 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -1346,8 +1346,8 @@ public String toVariableNameString(final CachedServerIcon o) { "See the <a href='expressions.html#ExprFireworkEffect'>firework effect</a> expression for detailed patterns.") .defaultExpression(new EventValueExpression<>(FireworkEffect.class)) .examples("launch flickering trailing burst firework colored blue and green at player", - "launch trailing flickering star coloured purple, yellow, blue, green and red fading to pink at target entity", - "launch ball large coloured red, purple and white fading to light green and black at player's location with duration 1") + "launch trailing flickering star colored purple, yellow, blue, green and red fading to pink at target entity", + "launch ball large colored red, purple and white fading to light green and black at player's location with duration 1") .since("2.4") .parser(new Parser<FireworkEffect>() { @Override diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java b/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java index c38ff20d433..8cf2087c3fb 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java @@ -311,9 +311,7 @@ public World[] executeSimple(Object[][] params) { }).description("Gets a world from its name.") .examples("set {_nether} to world(\"%{_world}%_nether\")") .since("2.2"); - - // the location expression doesn't work, so why not make a function for the same purpose - // FIXME document on ExprLocation as well + Functions.registerFunction(new JavaFunction<Location>("location", new Parameter[] { new Parameter<>("x", DefaultClasses.NUMBER, true, null), new Parameter<>("y", DefaultClasses.NUMBER, true, null), diff --git a/src/main/java/ch/njol/skript/classes/data/JavaClasses.java b/src/main/java/ch/njol/skript/classes/data/JavaClasses.java index 9f87be66e39..1f463e87361 100644 --- a/src/main/java/ch/njol/skript/classes/data/JavaClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/JavaClasses.java @@ -551,7 +551,7 @@ public boolean mustSyncDeserialization() { .description("Text is simply text, i.e. a sequence of characters, which can optionally contain expressions which will be replaced with a meaningful representation " + "(e.g. %player% will be replaced with the player's name).", "Because scripts are also text, you have to put text into double quotes to tell Skript which part of the line is an effect/expression and which part is the text.", - "Please read the article on <a href='../strings/'>Texts and Variable Names</a> to learn more.") + "Please read the article on <a href='./strings/'>Texts and Variable Names</a> to learn more.") .usage("simple: \"...\"", "quotes: \"...\"\"...\"", "expressions: \"...%expression%...\"", 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 fd7eb69bfa3..eb7ff0e418b 100644 --- a/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java @@ -438,7 +438,7 @@ public Timeperiod deserialize(final String s) { Classes.registerClass(new ClassInfo<>(Date.class, "date") .user("dates?") .name("Date") - .description("A date is a certain point in the real world's time which can currently only be obtained with <a href='../expressions.html#ExprNow'>now</a>.", + .description("A date is a certain point in the real world's time which can be obtained with <a href='./expressions.html#ExprNow'>now expression</a>, <a href='./expressions.html#ExprUnixDate'>unix date expression</a> and <a href='./functions.html#date'>date function</a>.", "See <a href='#time'>time</a> and <a href='#timespan'>timespan</a> for the other time types of Skript.") .usage("") .examples("set {_yesterday} to now", @@ -494,7 +494,7 @@ public Date power(Date value, Timespan exponent) { .description("A direction, e.g. north, east, behind, 5 south east, 1.3 meters to the right, etc.", "<a href='#location'>Locations</a> and some <a href='#block'>blocks</a> also have a direction, but without a length.", "Please note that directions have changed extensively in the betas and might not work perfectly. They can also not be used as command arguments.") - .usage("see <a href='../expressions.html#ExprDirection'>direction (expression)</a>") + .usage("see <a href='./expressions.html#ExprDirection'>direction (expression)</a>") .examples("set the block below the victim to a chest", "loop blocks from the block infront of the player to the block 10 below the player:", " set the block behind the loop-block to water") @@ -536,15 +536,15 @@ public Direction deserialize(final String s) { .user("(inventory )?slots?") .name("Inventory Slot") .description("Represents a single slot of an <a href='#inventory'>inventory</a>. " + - "Notable slots are the <a href='../expressions.html#ExprArmorSlot'>armour slots</a> and <a href='../expressions/#ExprFurnaceSlot'>furnace slots</a>. ", + "Notable slots are the <a href='./expressions.html#ExprArmorSlot'>armour slots</a> and <a href='./expressions/#ExprFurnaceSlot'>furnace slots</a>. ", "The most important property that distinguishes a slot from an <a href='#itemstack'>item</a> is its ability to be changed, e.g. it can be set, deleted, enchanted, etc. " + "(Some item expressions can be changed as well, e.g. items stored in variables. " + "For that matter: slots are never saved to variables, only the items they represent at the time when the variable is set).", - "Please note that <a href='../expressions.html#ExprTool'>tool</a> can be regarded a slot, but it can actually change it's position, i.e. doesn't represent always the same slot.") + "Please note that <a href='./expressions.html#ExprTool'>tool</a> can be regarded a slot, but it can actually change it's position, i.e. doesn't represent always the same slot.") .usage("") .examples("set tool of player to dirt", "delete helmet of the victim", - "set the colour of the player's tool to green", + "set the color of the player's tool to green", "enchant the player's chestplate with projectile protection 5") .since("") .defaultExpression(new EventValueExpression<>(Slot.class)) @@ -651,11 +651,11 @@ public String toVariableNameString(Slot o) { Classes.registerClass(new ClassInfo<>(Color.class, "color") .user("colou?rs?") - .name("Colour") - .description("Wool, dye and chat colours.") + .name("Color") + .description("Wool, dye and chat colors.") .usage("black, dark grey/dark gray, grey/light grey/gray/light gray/silver, white, blue/dark blue, cyan/aqua/dark cyan/dark aqua, light blue/light cyan/light aqua, green/dark green, light green/lime/lime green, yellow/light yellow, orange/gold/dark yellow, red/dark red, pink/light red, purple/dark purple, magenta/light purple, brown/indigo") .examples("color of the sheep is red or black", - "set the colour of the block to green", + "set the color of the block to green", "message \"You're holding a <%color of tool%>%color of tool%<reset> wool block\"") .since("") .parser(new Parser<Color>() { @@ -682,7 +682,7 @@ public String toVariableNameString(Color color) { Classes.registerClass(new ClassInfo<>(StructureType.class, "structuretype") .user("tree ?types?", "trees?") .name("Tree Type") - .description("A tree type represents a tree species or a huge mushroom species. These can be generated in a world with the <a href='../effects.html#EffTree'>generate tree</a> effect.") + .description("A tree type represents a tree species or a huge mushroom species. These can be generated in a world with the <a href='./effects.html#EffTree'>generate tree</a> effect.") .usage("[any] <general tree/mushroom type>, e.g. tree/any jungle tree/etc.", "<specific tree/mushroom species>, e.g. red mushroom/small jungle tree/big regular tree/etc.") .examples("grow any regular tree at the block", "grow a huge red mushroom above the block") @@ -754,7 +754,7 @@ public EnchantmentType deserialize(final String s) { .user("experience ?(points?)?") .name("Experience") .description("Experience points. Please note that Bukkit only allows to give XP, but not remove XP from players. " + - "You can however change a player's <a href='../expressions.html#ExprLevel'>level</a> and <a href='../expressions/#ExprLevelProgress'>level progress</a> freely.") + "You can however change a player's <a href='./expressions.html#ExprLevel'>level</a> and <a href='./expressions/#ExprLevelProgress'>level progress</a> freely.") .usage("[<number>] ([e]xp|experience [point[s]])") .examples("give 10 xp to the player") .since("2.0") diff --git a/src/main/java/ch/njol/skript/doc/Documentation.java b/src/main/java/ch/njol/skript/doc/Documentation.java index 9ae28a437ac..0ce241f705f 100644 --- a/src/main/java/ch/njol/skript/doc/Documentation.java +++ b/src/main/java/ch/njol/skript/doc/Documentation.java @@ -52,11 +52,41 @@ * TODO list special expressions for events and event values * TODO compare doc in code with changed one of the webserver and warn about differences? * - * @author Peter Güttinger */ @SuppressFBWarnings("ES_COMPARING_STRINGS_WITH_EQ") public class Documentation { + private static final Pattern CP_PARSE_MARKS_PATTERN = Pattern.compile("(?<=[(|])[-0-9]+?¦"); + private static final Pattern CP_EMPTY_PARSE_MARKS_PATTERN = Pattern.compile("\\(\\)"); + private static final Pattern CP_PARSE_TAGS_PATTERN = Pattern.compile("(?<=[(|\\[ ])[-a-zA-Z0-9!$#%^&*_+~=\"'<>?,.]*?:"); + private static final Pattern CP_EXTRA_OPTIONAL_PATTERN = Pattern.compile("\\[\\(((\\w+? ?)+)\\)]"); + private static final File DOCS_TEMPLATE_DIRECTORY = new File(Skript.getInstance().getDataFolder(), "doc-templates"); + private static final File DOCS_OUTPUT_DIRECTORY = new File(Skript.getInstance().getDataFolder(), "docs"); + + /** + * Force register hooks even if their plugins are not present in the server + */ + public static final boolean FORCE_HOOKS_SYSTEM_PROPERTY = "true".equals(System.getProperty("skript.forceregisterhooks")); + + public static boolean isDocsTemplateFound() { + return getDocsTemplateDirectory().isDirectory(); + } + + /** + * Checks if server java args have 'skript.forceregisterhooks' property set to true and docs template folder is found + */ + public static boolean canGenerateUnsafeDocs() { + return isDocsTemplateFound() && FORCE_HOOKS_SYSTEM_PROPERTY; + } + + public static File getDocsTemplateDirectory() { + return DOCS_TEMPLATE_DIRECTORY; + } + + public static File getDocsOutputDirectory() { + return DOCS_OUTPUT_DIRECTORY; + } + public static void generate() { if (!generate) return; @@ -165,12 +195,14 @@ protected static String cleanPatterns(final String patterns) { return cleanPatterns(patterns, true); } - protected static String cleanPatterns(final String patterns, boolean escapeHTML) { + protected static String cleanPatterns(String patterns, boolean escapeHTML) { + + String cleanedPatterns = escapeHTML ? escapeHTML(patterns) : patterns; - String cleanedPatterns = - (escapeHTML ? escapeHTML(patterns) : patterns) // Escape HTML if escapeHTML == true - .replaceAll("(?<=[(|])[-0-9]+?¦", "") // Remove marks - .replace("()", ""); // Remove empty mark setting groups (mark¦) + cleanedPatterns = CP_PARSE_MARKS_PATTERN.matcher(cleanedPatterns).replaceAll(""); // Remove marks + cleanedPatterns = CP_EMPTY_PARSE_MARKS_PATTERN.matcher(cleanedPatterns).replaceAll(""); // Remove empty mark setting groups (mark¦) + cleanedPatterns = CP_PARSE_TAGS_PATTERN.matcher(cleanedPatterns).replaceAll(""); // Remove new parse tags, see https://regex101.com/r/mTebpn/1 + cleanedPatterns = CP_EXTRA_OPTIONAL_PATTERN.matcher(cleanedPatterns).replaceAll("[$1]"); // Remove unnecessary parentheses such as [(script)] Callback<String, Matcher> callback = m -> { // Replace optional parentheses with optional brackets String group = m.group(); @@ -221,7 +253,7 @@ protected static String cleanPatterns(final String patterns, boolean escapeHTML) cleanedPatterns = cleanedPatterns.replaceAll("\\(([^()]+?)\\|\\)", "[($1)]"); // Matches optional syntaxes that doesn't have nested parentheses cleanedPatterns = cleanedPatterns.replaceAll("\\(\\|([^()]+?)\\)", "[($1)]"); - cleanedPatterns = StringUtils.replaceAll(cleanedPatterns, "\\((.+)\\|\\)", callback); // Matches complex optional parentheses at has nested parentheses + cleanedPatterns = StringUtils.replaceAll(cleanedPatterns, "\\((.+)\\|\\)", callback); // Matches complex optional parentheses that has nested parentheses assert cleanedPatterns != null; cleanedPatterns = StringUtils.replaceAll(cleanedPatterns, "\\((.+?)\\|\\)", callback); assert cleanedPatterns != null; @@ -255,11 +287,11 @@ public String run(final Matcher m) { first = false; final NonNullPair<String, Boolean> p = Utils.getEnglishPlural(c); final ClassInfo<?> ci = Classes.getClassInfoNoError(p.getFirst()); - if (ci != null && ci.getDocName() != null && ci.getDocName() != ClassInfo.NO_DOC) { + if (ci != null && ci.hasDocs()) { // equals method throws null error when doc name is null b.append("<a href='./classes.html#").append(p.getFirst()).append("'>").append(ci.getName().toString(p.getSecond())).append("</a>"); } else { b.append(c); - if (ci != null && !ci.getDocName().equals(ClassInfo.NO_DOC)) + if (ci != null && ci.hasDocs()) Skript.warning("Used class " + p.getFirst() + " has no docName/name defined"); } } diff --git a/src/main/java/ch/njol/skript/doc/HTMLGenerator.java b/src/main/java/ch/njol/skript/doc/HTMLGenerator.java index 73dceca1944..39ead176641 100644 --- a/src/main/java/ch/njol/skript/doc/HTMLGenerator.java +++ b/src/main/java/ch/njol/skript/doc/HTMLGenerator.java @@ -18,30 +18,38 @@ */ package ch.njol.skript.doc; -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.text.SimpleDateFormat; -import java.util.*; - -import org.apache.commons.lang.StringUtils; -import org.eclipse.jdt.annotation.Nullable; - -import com.google.common.base.Joiner; -import com.google.common.collect.Lists; -import com.google.common.io.Files; import ch.njol.skript.Skript; import ch.njol.skript.SkriptAPIException; import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.lang.Condition; import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.EffectSection; import ch.njol.skript.lang.ExpressionInfo; +import ch.njol.skript.lang.Section; import ch.njol.skript.lang.SkriptEventInfo; import ch.njol.skript.lang.SyntaxElementInfo; import ch.njol.skript.lang.function.Functions; import ch.njol.skript.lang.function.JavaFunction; import ch.njol.skript.lang.function.Parameter; import ch.njol.skript.registrations.Classes; +import com.google.common.base.Joiner; +import com.google.common.collect.Lists; +import com.google.common.io.Files; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.WordUtils; +import org.eclipse.jdt.annotation.Nullable; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.regex.Pattern; /** * Template engine, primarily used for generating Skript documentation @@ -49,66 +57,72 @@ * */ public class HTMLGenerator { - - private File template; - private File output; - - private String skeleton; - + + private static final String SKRIPT_VERSION = Skript.getVersion().toString().replaceAll("-(dev|alpha|beta)\\d*", ""); // Filter branches + private static final Pattern NEW_TAG_PATTERN = Pattern.compile(SKRIPT_VERSION + "(?!\\.)"); // (?!\\.) to avoid matching 2.6 in 2.6.1 etc. + private static final Pattern RETURN_TYPE_LINK_PATTERN = Pattern.compile("( ?href=\"(classes\\.html|)#|)\\$\\{element\\.return-type-linkcheck}"); + + private final File template; + private final File output; + private final String skeleton; + public HTMLGenerator(File templateDir, File outputDir) { this.template = templateDir; this.output = outputDir; this.skeleton = readFile(new File(template + "/template.html")); // Skeleton which contains every other page } - - @SuppressWarnings("null") - private static <T> Iterator<T> sortedIterator(Iterator<T> it, Comparator<? super T> comparator) { - List<T> list = new ArrayList<>(); - while (it.hasNext()) { - list.add(it.next()); - } - - Collections.sort(list, comparator); - return list.iterator(); - } - + /** * Sorts annotated documentation entries alphabetically. */ - private static class AnnotatedComparator implements Comparator<SyntaxElementInfo<?>> { + private static final Comparator<? super SyntaxElementInfo<?>> annotatedComparator = (o1, o2) -> { + // Nullness check + if (o1 == null || o2 == null) { + assert false; + throw new NullPointerException(); + } - public AnnotatedComparator() {} + if (o1.c.getAnnotation(NoDoc.class) != null) { + if (o2.c.getAnnotation(NoDoc.class) != null) + return 0; + return 1; + } else if (o2.c.getAnnotation(NoDoc.class) != null) + return -1; - @Override - public int compare(@Nullable SyntaxElementInfo<?> o1, @Nullable SyntaxElementInfo<?> o2) { - // Nullness check - if (o1 == null || o2 == null) { - assert false; - throw new NullPointerException(); - } + Name name1 = o1.c.getAnnotation(Name.class); + Name name2 = o2.c.getAnnotation(Name.class); + if (name1 == null) + throw new SkriptAPIException("Name annotation expected: " + o1.c); + if (name2 == null) + throw new SkriptAPIException("Name annotation expected: " + o2.c); + return name1.value().compareTo(name2.value()); + }; - if (o1.c.getAnnotation(NoDoc.class) != null) { - if (o2.c.getAnnotation(NoDoc.class) != null) - return 0; - return 1; - } else if (o2.c.getAnnotation(NoDoc.class) != null) - return -1; - - Name name1 = o1.c.getAnnotation(Name.class); - Name name2 = o2.c.getAnnotation(Name.class); - if (name1 == null) - throw new SkriptAPIException("Name annotation expected: " + o1.c); - if (name2 == null) - throw new SkriptAPIException("Name annotation expected: " + o2.c); - - return name1.value().compareTo(name2.value()); + /** + * Sort iterator of {@link SyntaxElementInfo} by name. + * Elements with no name will be skipped with a console warning. + * + * @param it The {@link SyntaxElementInfo} iterator. + * @return The sorted (by name) iterator. + */ + private static <T> Iterator<SyntaxElementInfo<? extends T>> sortedAnnotatedIterator(Iterator<SyntaxElementInfo<? extends T>> it) { + List<SyntaxElementInfo<? extends T>> list = new ArrayList<>(); + while (it.hasNext()) { + SyntaxElementInfo<? extends T> item = it.next(); + // Filter unnamed expressions (mostly caused by addons) to avoid throwing exceptions and stop the generation process + if (item.c.getAnnotation(Name.class) == null && item.c.getAnnotation(NoDoc.class) == null) { + Skript.warning("Skipped generating '" + item.c + "' class due to missing Name annotation"); + continue; + } + list.add(item); } + + list.sort(annotatedComparator); + return list.iterator(); } - private static final AnnotatedComparator annotatedComparator = new AnnotatedComparator(); - /** * Sorts events alphabetically. */ @@ -131,7 +145,7 @@ else if (o2.c.getAnnotation(NoDoc.class) != null) return o1.name.compareTo(o2.name); } - + } private static final EventComparator eventComparator = new EventComparator(); @@ -191,6 +205,7 @@ public int compare(@Nullable JavaFunction<?> o1, @Nullable JavaFunction<?> o2) { * Generates documentation using template and output directories * given in the constructor. */ + @SuppressWarnings("unchecked") public void generate() { for (File f : template.listFiles()) { if (f.getName().matches("css|js|assets")) { // Copy CSS/JS/Assets folders @@ -257,55 +272,80 @@ else if (!filesInside.getName().matches("(?i)(.*)\\.(html?|js|css|json)")) { String descTemp = readFile(new File(template + "/templates/" + genParams[1])); String genType = genParams[0]; - if (genType.equals("expressions")) { - Iterator<ExpressionInfo<?,?>> it = sortedIterator(Skript.getExpressions(), annotatedComparator); - while (it.hasNext()) { + boolean isDocsPage = genType.equals("docs"); + + if (genType.equals("expressions") || isDocsPage) { + for (Iterator<ExpressionInfo<?,?>> it = sortedAnnotatedIterator((Iterator) Skript.getExpressions()); it.hasNext(); ) { ExpressionInfo<?,?> info = it.next(); assert info != null; if (info.c.getAnnotation(NoDoc.class) != null) continue; - String desc = generateAnnotated(descTemp, info, generated.toString()); + String desc = generateAnnotated(descTemp, info, generated.toString(), "Expression"); generated.append(desc); } - } else if (genType.equals("effects")) { - List<SyntaxElementInfo<? extends Effect>> effects = new ArrayList<>(Skript.getEffects()); - Collections.sort(effects, annotatedComparator); - for (SyntaxElementInfo<? extends Effect> info : effects) { + } + if (genType.equals("effects") || isDocsPage) { + for (Iterator<SyntaxElementInfo<? extends Effect>> it = sortedAnnotatedIterator(Skript.getEffects().iterator()); it.hasNext(); ) { + SyntaxElementInfo<? extends Effect> info = it.next(); assert info != null; if (info.c.getAnnotation(NoDoc.class) != null) continue; - generated.append(generateAnnotated(descTemp, info, generated.toString())); + generated.append(generateAnnotated(descTemp, info, generated.toString(), "Effect")); } - } else if (genType.equals("conditions")) { - List<SyntaxElementInfo<? extends Condition>> conditions = new ArrayList<>(Skript.getConditions()); - Collections.sort(conditions, annotatedComparator); - for (SyntaxElementInfo<? extends Condition> info : conditions) { + + for (Iterator<SyntaxElementInfo<? extends Section>> it = sortedAnnotatedIterator(Skript.getSections().iterator()); it.hasNext(); ) { + SyntaxElementInfo<? extends Section> info = it.next(); + assert info != null; + if (EffectSection.class.isAssignableFrom(info.c)) { + if (info.c.getAnnotation(NoDoc.class) != null) + continue; + generated.append(generateAnnotated(descTemp, info, generated.toString(), "EffectSection")); + } + } + } + if (genType.equals("conditions") || isDocsPage) { + for (Iterator<SyntaxElementInfo<? extends Condition>> it = sortedAnnotatedIterator(Skript.getConditions().iterator()); it.hasNext(); ) { + SyntaxElementInfo<? extends Condition> info = it.next(); assert info != null; if (info.c.getAnnotation(NoDoc.class) != null) continue; - generated.append(generateAnnotated(descTemp, info, generated.toString())); + generated.append(generateAnnotated(descTemp, info, generated.toString(), "Condition")); } - } else if (genType.equals("events")) { + } + if (genType.equals("sections") || isDocsPage) { + for (Iterator<SyntaxElementInfo<? extends Section>> it = sortedAnnotatedIterator(Skript.getSections().iterator()); it.hasNext(); ) { + SyntaxElementInfo<? extends Section> info = it.next(); + assert info != null; + boolean isEffectSection = EffectSection.class.isAssignableFrom(info.c); + // exclude sections that are EffectSection from isDocsPage, they are added by the effects block above + if ((isEffectSection && isDocsPage) || info.c.getAnnotation(NoDoc.class) != null) + continue; + generated.append(generateAnnotated(descTemp, info, generated.toString(), (isEffectSection ? "Effect" : "") + "Section")); + } + } + if (genType.equals("events") || isDocsPage) { List<SkriptEventInfo<?>> events = new ArrayList<>(Skript.getEvents()); - Collections.sort(events, eventComparator); + events.sort(eventComparator); for (SkriptEventInfo<?> info : events) { assert info != null; if (info.c.getAnnotation(NoDoc.class) != null) continue; generated.append(generateEvent(descTemp, info, generated.toString())); } - } else if (genType.equals("classes")) { + } + if (genType.equals("classes") || isDocsPage) { List<ClassInfo<?>> classes = new ArrayList<>(Classes.getClassInfos()); - Collections.sort(classes, classInfoComparator); + classes.sort(classInfoComparator); for (ClassInfo<?> info : classes) { - if (ClassInfo.NO_DOC.equals(info.getDocName())) + if (!info.hasDocs()) continue; assert info != null; generated.append(generateClass(descTemp, info, generated.toString())); } - } else if (genType.equals("functions")) { + } + if (genType.equals("functions") || isDocsPage) { List<JavaFunction<?>> functions = new ArrayList<>(Functions.getJavaFunctions()); - Collections.sort(functions, functionComparator); + functions.sort(functionComparator); for (JavaFunction<?> info : functions) { assert info != null; generated.append(generateFunction(descTemp, info)); @@ -345,14 +385,19 @@ private static String minifyHtml(String page) { i += Character.charCount(c); } - return replaceBR(sb.toString()); + return replaceBr(sb.toString()); } - private static String replaceBR(String page) { // Replaces specifically `<br/>` with `\n` - This is useful in code blocks where you can't use newlines due to the minifyHtml method (Execute after minifyHtml) + /** + * Replaces specifically `<br/>` with `\n` - This is useful in code blocks where you can't use newlines due to the + * minifyHtml method (execute after minifyHtml) + */ + private static String replaceBr(String page) { return page.replaceAll("<br/>", "\n"); } private static String handleIf(String desc, String start, boolean value) { + assert desc != null; int ifStart = desc.indexOf(start); while (ifStart != -1) { int ifEnd = desc.indexOf("${end}", ifStart); @@ -377,66 +422,91 @@ private static String handleIf(String desc, String start, boolean value) { * @param descTemp Template for description. * @param info Syntax element info. * @param page The page's code to check for ID duplications, can be left empty. + * @param type The generated element's type such as "Expression", to replace type placeholders * @return Generated HTML entry. */ - private String generateAnnotated(String descTemp, SyntaxElementInfo<?> info, @Nullable String page) { + private String generateAnnotated(String descTemp, SyntaxElementInfo<?> info, @Nullable String page, String type) { Class<?> c = info.c; - String desc = ""; + String desc; + // Name Name name = c.getAnnotation(Name.class); - desc = descTemp.replace("${element.name}", getNullOrEmptyDefault(name.value(), "Unknown Name")); + desc = descTemp.replace("${element.name}", getDefaultIfNullOrEmpty((name != null ? name.value() : null), "Unknown Name")); + // Since Since since = c.getAnnotation(Since.class); - desc = desc.replace("${element.since}", getNullOrEmptyDefault(since.value(), "Unknown")); + desc = desc.replace("${element.since}", getDefaultIfNullOrEmpty((since != null ? since.value() : null), "Unknown")); + Keywords keywords = c.getAnnotation(Keywords.class); + desc = desc.replace("${element.keywords}", keywords == null ? "" : Joiner.on(", ").join(keywords.value())); + + // Description Description description = c.getAnnotation(Description.class); - desc = desc.replace("${element.desc}", Joiner.on("\n").join(getNullOrEmptyDefault(description.value(), "Unknown description.")).replace("\n\n", "<p>")); - desc = desc.replace("${element.desc-safe}", Joiner.on("\n").join(getNullOrEmptyDefault(description.value(), "Unknown description.")) + desc = desc.replace("${element.desc}", Joiner.on("\n").join(getDefaultIfNullOrEmpty((description != null ? description.value() : null), "Unknown description.")).replace("\n\n", "<p>")); + desc = desc.replace("${element.desc-safe}", Joiner.on("\n").join(getDefaultIfNullOrEmpty((description != null ? description.value() : null), "Unknown description.")) .replace("\\", "\\\\").replace("\"", "\\\"").replace("\t", " ")); + // Examples Examples examples = c.getAnnotation(Examples.class); - desc = desc.replace("${element.examples}", Joiner.on("<br>").join(getNullOrEmptyDefault(examples.value(), "Missing examples."))); - desc = desc.replace("${element.examples-safe}", Joiner.on("\\n").join(getNullOrEmptyDefault(examples.value(), "Missing examples.")) + desc = desc.replace("${element.examples}", Joiner.on("<br>").join(getDefaultIfNullOrEmpty((examples != null ? examples.value() : null), "Missing examples."))); + desc = desc.replace("${element.examples-safe}", Joiner.on("\\n").join(getDefaultIfNullOrEmpty((examples != null ? examples.value() : null), "Missing examples.")) .replace("\\", "\\\\").replace("\"", "\\\"").replace("\t", " ")); - DocumentationId DocID = c.getAnnotation(DocumentationId.class); - String ID = DocID != null ? DocID.value() : info.c.getSimpleName(); + // Documentation ID + DocumentationId docId = c.getAnnotation(DocumentationId.class); + String ID = docId != null ? (docId != null ? docId.value() : null) : info.c.getSimpleName(); // Fix duplicated IDs if (page != null) { - if (page.contains("#" + ID + "\"")) { - ID = ID + "-" + (StringUtils.countMatches(page, "#" + ID + "\"") + 1); + if (page.contains("href=\"#" + ID + "\"")) { + ID = ID + "-" + (StringUtils.countMatches(page, "href=\"#" + ID + "\"") + 1); } } desc = desc.replace("${element.id}", ID); + // Events Events events = c.getAnnotation(Events.class); - assert desc != null; desc = handleIf(desc, "${if events}", events != null); if (events != null) { - String[] eventNames = events.value(); + String[] eventNames = events != null ? events.value() : null; String[] eventLinks = new String[eventNames.length]; for (int i = 0; i < eventNames.length; i++) { String eventName = eventNames[i]; - eventLinks[i] = "<a href=\"events.html#" + eventName.replace(" ", "_") + "\">" + eventName + "</a>"; + eventLinks[i] = "<a href=\"events.html#" + eventName.replaceAll("( ?/ ?| +)", "_") + "\">" + eventName + "</a>"; } desc = desc.replace("${element.events}", Joiner.on(", ").join(eventLinks)); } - desc = desc.replace("${element.events-safe}", events == null ? "" : Joiner.on(", ").join(events.value())); - + desc = desc.replace("${element.events-safe}", events == null ? "" : Joiner.on(", ").join((events != null ? events.value() : null))); + + // RequiredPlugins RequiredPlugins plugins = c.getAnnotation(RequiredPlugins.class); - assert desc != null; desc = handleIf(desc, "${if required-plugins}", plugins != null); - desc = desc.replace("${element.required-plugins}", plugins == null ? "" : Joiner.on(", ").join(plugins.value())); - + desc = desc.replace("${element.required-plugins}", plugins == null ? "" : Joiner.on(", ").join((plugins != null ? plugins.value() : null))); + + // Return Type + ClassInfo<?> returnType = info instanceof ExpressionInfo ? Classes.getSuperClassInfo(((ExpressionInfo<?,?>) info).getReturnType()) : null; + desc = replaceReturnType(desc, returnType); + + // By Addon +// TODO LATER +// String addon = info.originClassPath; +// desc = handleIf(desc, "${if by-addon}", true && !addon.isEmpty()); +// desc = desc.replace("${element.by-addon}", addon); + desc = handleIf(desc, "${if by-addon}", false); + + // New Elements + desc = handleIf(desc, "${if new-element}", NEW_TAG_PATTERN.matcher((since != null ? since.value() : "")).find()); + + // Type + desc = desc.replace("${element.type}", type); + + // Generate Templates List<String> toGen = Lists.newArrayList(); int generate = desc.indexOf("${generate"); while (generate != -1) { - //Skript.info("Found generate!"); int nextBracket = desc.indexOf("}", generate); String data = desc.substring(generate + 11, nextBracket); toGen.add(data); - //Skript.info("Added " + data); - + generate = desc.indexOf("${generate", nextBracket); } @@ -444,18 +514,15 @@ private String generateAnnotated(String descTemp, SyntaxElementInfo<?> info, @Nu for (String data : toGen) { String[] split = data.split(" "); String pattern = readFile(new File(template + "/templates/" + split[1])); - //Skript.info("Pattern is " + pattern); StringBuilder patterns = new StringBuilder(); - for (String line : getNullOrEmptyDefault(info.patterns, "Missing patterns.")) { + for (String line : getDefaultIfNullOrEmpty(info.patterns, "Missing patterns.")) { assert line != null; line = cleanPatterns(line); String parsed = pattern.replace("${element.pattern}", line); - //Skript.info("parsed is " + parsed); patterns.append(parsed); } String toReplace = "${generate element.patterns " + split[1] + "}"; - //Skript.info("toReplace " + toReplace); desc = desc.replace(toReplace, patterns.toString()); desc = desc.replace("${generate element.patterns-safe " + split[1] + "}", patterns.toString().replace("\\", "\\\\")); } @@ -465,39 +532,79 @@ private String generateAnnotated(String descTemp, SyntaxElementInfo<?> info, @Nu } private String generateEvent(String descTemp, SkriptEventInfo<?> info, @Nullable String page) { - String desc = ""; - - String docName = getNullOrEmptyDefault(info.getName(), "Unknown Name"); + Class<?> c = info.c; + String desc; + + // Name + String docName = getDefaultIfNullOrEmpty(info.getName(), "Unknown Name"); desc = descTemp.replace("${element.name}", docName); - - String since = getNullOrEmptyDefault(info.getSince(), "Unknown"); + + // Since + String since = getDefaultIfNullOrEmpty(info.getSince(), "Unknown"); desc = desc.replace("${element.since}", since); - - String[] description = getNullOrEmptyDefault(info.getDescription(), "Missing description."); + + // Description + String[] description = getDefaultIfNullOrEmpty(info.getDescription(), "Missing description."); desc = desc.replace("${element.desc}", Joiner.on("\n").join(description).replace("\n\n", "<p>")); desc = desc .replace("${element.desc-safe}", Joiner.on("\\n").join(description) .replace("\\", "\\\\").replace("\"", "\\\"").replace("\t", " ")); - - String[] examples = getNullOrEmptyDefault(info.getExamples(), "Missing examples."); + + // By Addon +// String addon = info.originClassPath; +// desc = handleIf(desc, "${if by-addon}", true && !addon.isEmpty()); +// desc = desc.replace("${element.by-addon}", addon); + desc = handleIf(desc, "${if by-addon}", false); + + // Examples + String[] examples = getDefaultIfNullOrEmpty(info.getExamples(), "Missing examples."); desc = desc.replace("${element.examples}", Joiner.on("\n<br>").join(examples)); desc = desc .replace("${element.examples-safe}", Joiner.on("\\n").join(examples) .replace("\\", "\\\\").replace("\"", "\\\"").replace("\t", " ")); + String[] keywords = info.getKeywords(); + desc = desc.replace("${element.keywords}", keywords == null ? "" : Joiner.on(", ").join(keywords)); + + // Documentation ID String ID = info.getDocumentationID() != null ? info.getDocumentationID() : info.getId(); // Fix duplicated IDs if (page != null) { - if (page.contains("#" + ID + "\"")) { - ID = ID + "-" + (StringUtils.countMatches(page, "#" + ID + "\"") + 1); + if (page.contains("href=\"#" + ID + "\"")) { + ID = ID + "-" + (StringUtils.countMatches(page, "href=\"#" + ID + "\"") + 1); } } desc = desc.replace("${element.id}", ID); - - assert desc != null; - desc = handleIf(desc, "${if events}", false); - desc = handleIf(desc, "${if required-plugins}", false); - + + // Events + Events events = c.getAnnotation(Events.class); + desc = handleIf(desc, "${if events}", events != null); + if (events != null) { + String[] eventNames = events != null ? events.value() : null; + String[] eventLinks = new String[eventNames.length]; + for (int i = 0; i < eventNames.length; i++) { + String eventName = eventNames[i]; + eventLinks[i] = "<a href=\"events.html#" + eventName.replaceAll(" ?/ ?", "_").replaceAll(" +", "_") + "\">" + eventName + "</a>"; + } + desc = desc.replace("${element.events}", Joiner.on(", ").join(eventLinks)); + } + desc = desc.replace("${element.events-safe}", events == null ? "" : Joiner.on(", ").join((events != null ? events.value() : null))); + + // Required Plugins + String[] requiredPlugins = info.getRequiredPlugins(); + desc = handleIf(desc, "${if required-plugins}", requiredPlugins != null); + desc = desc.replace("${element.required-plugins}", Joiner.on(", ").join(requiredPlugins == null ? new String[0] : requiredPlugins)); + + // New Elements + desc = handleIf(desc, "${if new-element}", NEW_TAG_PATTERN.matcher(since).find()); + + // Type + desc = desc.replace("${element.type}", "Event"); + + // Return Type + desc = handleIf(desc, "${if return-type}", false); + + // Generate Templates List<String> toGen = Lists.newArrayList(); int generate = desc.indexOf("${generate"); while (generate != -1) { @@ -513,9 +620,9 @@ private String generateEvent(String descTemp, SkriptEventInfo<?> info, @Nullable String[] split = data.split(" "); String pattern = readFile(new File(template + "/templates/" + split[1])); StringBuilder patterns = new StringBuilder(); - for (String line : getNullOrEmptyDefault(info.patterns, "Missing patterns.")) { + for (String line : getDefaultIfNullOrEmpty(info.patterns, "Missing patterns.")) { assert line != null; - line = cleanPatterns(info.getName().startsWith("On ") ? "[on] " + line : line); + line = cleanPatterns(line); String parsed = pattern.replace("${element.pattern}", line); patterns.append(parsed); } @@ -529,38 +636,75 @@ private String generateEvent(String descTemp, SkriptEventInfo<?> info, @Nullable } private String generateClass(String descTemp, ClassInfo<?> info, @Nullable String page) { - String desc = ""; - - String docName = getNullOrEmptyDefault(info.getDocName(), "Unknown Name"); + Class<?> c = info.getC(); + String desc; + + // Name + String docName = getDefaultIfNullOrEmpty(info.getDocName(), "Unknown Name"); desc = descTemp.replace("${element.name}", docName); - - String since = getNullOrEmptyDefault(info.getSince(), "Unknown"); + + // Since + String since = getDefaultIfNullOrEmpty(info.getSince(), "Unknown"); desc = desc.replace("${element.since}", since); - - String[] description = getNullOrEmptyDefault(info.getDescription(), "Missing description."); + + // Description + String[] description = getDefaultIfNullOrEmpty(info.getDescription(), "Missing description."); desc = desc.replace("${element.desc}", Joiner.on("\n").join(description).replace("\n\n", "<p>")); desc = desc .replace("${element.desc-safe}", Joiner.on("\\n").join(description) .replace("\\", "\\\\").replace("\"", "\\\"").replace("\t", " ")); - - String[] examples = getNullOrEmptyDefault(info.getExamples(), "Missing examples."); + + // By Addon +// String addon = c.getPackageName(); +// desc = handleIf(desc, "${if by-addon}", true && !addon.isEmpty()); +// desc = desc.replace("${element.by-addon}", addon); + desc = handleIf(desc, "${if by-addon}", false); + + // Examples + String[] examples = getDefaultIfNullOrEmpty(info.getExamples(), "Missing examples."); desc = desc.replace("${element.examples}", Joiner.on("\n<br>").join(examples)); desc = desc.replace("${element.examples-safe}", Joiner.on("\\n").join(examples) .replace("\\", "\\\\").replace("\"", "\\\"").replace("\t", " ")); + // Documentation ID String ID = info.getDocumentationID() != null ? info.getDocumentationID() : info.getCodeName(); // Fix duplicated IDs if (page != null) { - if (page.contains("#" + ID + "\"")) { - ID = ID + "-" + (StringUtils.countMatches(page, "#" + ID + "\"") + 1); + if (page.contains("href=\"#" + ID + "\"")) { + ID = ID + "-" + (StringUtils.countMatches(page, "href=\"#" + ID + "\"") + 1); } } desc = desc.replace("${element.id}", ID); - assert desc != null; - desc = handleIf(desc, "${if events}", false); - desc = handleIf(desc, "${if required-plugins}", false); - + // Events + Events events = c.getAnnotation(Events.class); + desc = handleIf(desc, "${if events}", events != null); + if (events != null) { + String[] eventNames = events != null ? events.value() : null; + String[] eventLinks = new String[eventNames.length]; + for (int i = 0; i < eventNames.length; i++) { + String eventName = eventNames[i]; + eventLinks[i] = "<a href=\"events.html#" + eventName.replaceAll(" ?/ ?", "_").replaceAll(" +", "_") + "\">" + eventName + "</a>"; + } + desc = desc.replace("${element.events}", Joiner.on(", ").join(eventLinks)); + } + desc = desc.replace("${element.events-safe}", events == null ? "" : Joiner.on(", ").join((events != null ? events.value() : null))); + + // Required Plugins + String[] requiredPlugins = info.getRequiredPlugins(); + desc = handleIf(desc, "${if required-plugins}", requiredPlugins != null); + desc = desc.replace("${element.required-plugins}", Joiner.on(", ").join(requiredPlugins == null ? new String[0] : requiredPlugins)); + + // New Elements + desc = handleIf(desc, "${if new-element}", NEW_TAG_PATTERN.matcher(since).find()); + + // Type + desc = desc.replace("${element.type}", "Type"); + + // Return Type + desc = handleIf(desc, "${if return-type}", false); + + // Generate Templates List<String> toGen = Lists.newArrayList(); int generate = desc.indexOf("${generate"); while (generate != -1) { @@ -576,12 +720,12 @@ private String generateClass(String descTemp, ClassInfo<?> info, @Nullable Strin String[] split = data.split(" "); String pattern = readFile(new File(template + "/templates/" + split[1])); StringBuilder patterns = new StringBuilder(); - String[] lines = getNullOrEmptyDefault(info.getUsage(), "Missing patterns."); + String[] lines = getDefaultIfNullOrEmpty(info.getUsage(), "Missing patterns."); if (lines == null) continue; for (String line : lines) { assert line != null; - line = cleanPatterns(line, false); +// line = cleanPatterns(line, false); // class infos don't have real patterns, they are just the usage String parsed = pattern.replace("${element.pattern}", line); patterns.append(parsed); } @@ -596,31 +740,55 @@ private String generateClass(String descTemp, ClassInfo<?> info, @Nullable Strin private String generateFunction(String descTemp, JavaFunction<?> info) { String desc = ""; - - String docName = getNullOrEmptyDefault(info.getName(), "Unknown Name"); + + // Name + String docName = getDefaultIfNullOrEmpty(info.getName(), "Unknown Name"); desc = descTemp.replace("${element.name}", docName); - - String since = getNullOrEmptyDefault(info.getSince(), "Unknown"); + + // Since + String since = getDefaultIfNullOrEmpty(info.getSince(), "Unknown"); desc = desc.replace("${element.since}", since); - - String[] description = getNullOrEmptyDefault(info.getDescription(), "Missing description."); + + // Description + String[] description = getDefaultIfNullOrEmpty(info.getDescription(), "Missing description."); desc = desc.replace("${element.desc}", Joiner.on("\n").join(description)); desc = desc .replace("${element.desc-safe}", Joiner.on("\\n").join(description) .replace("\\", "\\\\").replace("\"", "\\\"").replace("\t", " ")); - - String[] examples = getNullOrEmptyDefault(info.getExamples(), "Missing examples."); + + // By Addon +// String addon = info.getSignature().getOriginClassPath(); +// desc = handleIf(desc, "${if by-addon}", true && !addon.isEmpty()); +// desc = desc.replace("${element.by-addon}", addon); + desc = handleIf(desc, "${if by-addon}", false); + + // Examples + String[] examples = getDefaultIfNullOrEmpty(info.getExamples(), "Missing examples."); desc = desc.replace("${element.examples}", Joiner.on("\n<br>").join(examples)); desc = desc .replace("${element.examples-safe}", Joiner.on("\\n").join(examples) .replace("\\", "\\\\").replace("\"", "\\\"").replace("\t", " ")); + // Documentation ID desc = desc.replace("${element.id}", info.getName()); - - assert desc != null; - desc = handleIf(desc, "${if events}", false); + + // Events + desc = handleIf(desc, "${if events}", false); // Functions do not require events nor plugins (at time writing this) + + // Required Plugins desc = handleIf(desc, "${if required-plugins}", false); - + + // Return Type + ClassInfo<?> returnType = info.getReturnType(); + desc = replaceReturnType(desc, returnType); + + // New Elements + desc = handleIf(desc, "${if new-element}", NEW_TAG_PATTERN.matcher(since).find()); + + // Type + desc = desc.replace("${element.type}", "Function"); + + // Generate Templates List<String> toGen = Lists.newArrayList(); int generate = desc.indexOf("${generate"); while (generate != -1) { @@ -687,13 +855,26 @@ private static String cleanPatterns(final String patterns, boolean escapeHTML) { * @param string the String to check * @param message the String to return if either condition is true */ - public String getNullOrEmptyDefault(@Nullable String string, String message) { + public String getDefaultIfNullOrEmpty(@Nullable String string, String message) { return (string == null || string.isEmpty()) ? message : string; // Null check first otherwise NullPointerException is thrown } - public String[] getNullOrEmptyDefault(@Nullable String[] string, String message) { + public String[] getDefaultIfNullOrEmpty(@Nullable String[] string, String message) { return (string == null || string.length == 0 || string[0].equals("")) ? new String[]{ message } : string; // Null check first otherwise NullPointerException is thrown } - - + + private String replaceReturnType(String desc, @Nullable ClassInfo<?> returnType) { + if (returnType == null) + return handleIf(desc, "${if return-type}", false); + + boolean noDoc = returnType.hasDocs(); + String returnTypeName = noDoc ? returnType.getCodeName() : returnType.getDocName(); + String returnTypeLink = noDoc ? "" : "$1" + getDefaultIfNullOrEmpty(returnType.getDocumentationID(), returnType.getCodeName()); + + desc = handleIf(desc, "${if return-type}", true); + desc = RETURN_TYPE_LINK_PATTERN.matcher(desc).replaceAll(returnTypeLink); + desc = desc.replace("${element.return-type}", returnTypeName); + return desc; + } + } diff --git a/src/main/java/ch/njol/skript/doc/Keywords.java b/src/main/java/ch/njol/skript/doc/Keywords.java new file mode 100644 index 00000000000..b9c69ccf5bf --- /dev/null +++ b/src/main/java/ch/njol/skript/doc/Keywords.java @@ -0,0 +1,36 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.doc; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Provides a list of keywords that will allow for easier documentation searching. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Keywords { + + public String[] value(); +} diff --git a/src/main/java/ch/njol/skript/effects/EffChange.java b/src/main/java/ch/njol/skript/effects/EffChange.java index 12d46e56b4c..80ddd172646 100644 --- a/src/main/java/ch/njol/skript/effects/EffChange.java +++ b/src/main/java/ch/njol/skript/effects/EffChange.java @@ -53,12 +53,12 @@ * @author Peter Güttinger */ @Name("Change: Set/Add/Remove/Delete/Reset") -@Description("A very general effect that can change many <a href='../expressions'>expressions</a>. Many expressions can only be set and/or deleted, while some can have things added to or removed from them.") +@Description("A very general effect that can change many <a href='./expressions'>expressions</a>. Many expressions can only be set and/or deleted, while some can have things added to or removed from them.") @Examples({"# set:", "Set the player's display name to \"<red>%name of player%\"", "set the block above the victim to lava", "# add:", - "add 2 to the player's health # preferably use '<a href='#heal'>heal</a>' for this", + "add 2 to the player's health # preferably use '<a href='#EffHealth'>heal</a>' for this", "add argument to {blacklist::*}", "give a diamond pickaxe of efficiency 5 to the player", "increase the data value of the clicked block by 1", diff --git a/src/main/java/ch/njol/skript/effects/EffColorItems.java b/src/main/java/ch/njol/skript/effects/EffColorItems.java index 81ac09779d3..4da123e5d76 100644 --- a/src/main/java/ch/njol/skript/effects/EffColorItems.java +++ b/src/main/java/ch/njol/skript/effects/EffColorItems.java @@ -40,13 +40,13 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -@Name("Colour Items") -@Description("Colours items in a given <a href='classes.html#color'>colour</a>. " + - "You can also use RGB codes if you feel limited with the 16 default colours. " + +@Name("Color Items") +@Description("Colors items in a given <a href='classes.html#color'>color</a>. " + + "You can also use RGB codes if you feel limited with the 16 default colors. " + "RGB codes are three numbers from 0 to 255 in the order <code>(red, green, blue)</code>, where <code>(0,0,0)</code> is black and <code>(255,255,255)</code> is white. " + - "Armor is colourable for all Minecraft versions. With Minecraft 1.11 or newer you can also colour potions and maps. Note that the colours might not look exactly how you'd expect.") + "Armor is colorable for all Minecraft versions. With Minecraft 1.11 or newer you can also color potions and maps. Note that the colors might not look exactly how you'd expect.") @Examples({"dye player's helmet blue", - "colour the player's tool red"}) + "color the player's tool red"}) @Since("2.0, 2.2-dev26 (maps and potions)") public class EffColorItems extends Effect { diff --git a/src/main/java/ch/njol/skript/effects/EffFireworkLaunch.java b/src/main/java/ch/njol/skript/effects/EffFireworkLaunch.java index 85b1643012d..6f610ad6e4b 100644 --- a/src/main/java/ch/njol/skript/effects/EffFireworkLaunch.java +++ b/src/main/java/ch/njol/skript/effects/EffFireworkLaunch.java @@ -39,7 +39,7 @@ @Name("Launch firework") @Description("Launch firework effects at the given location(s).") -@Examples("launch ball large coloured red, purple and white fading to light green and black at player's location with duration 1") +@Examples("launch ball large colored red, purple and white fading to light green and black at player's location with duration 1") @Since("2.4") public class EffFireworkLaunch extends Effect { diff --git a/src/main/java/ch/njol/skript/effects/EffMessage.java b/src/main/java/ch/njol/skript/effects/EffMessage.java index c8a773defa2..1f1b7a0dba2 100644 --- a/src/main/java/ch/njol/skript/effects/EffMessage.java +++ b/src/main/java/ch/njol/skript/effects/EffMessage.java @@ -49,7 +49,7 @@ @Name("Message") @Description({"Sends a message to the given player. Only styles written", - "in given string or in <a href=expressions.html#ExprColoured>formatted expressions</a> will be parsed.", + "in given string or in <a href=expressions.html#ExprColored>formatted expressions</a> will be parsed.", "Adding an optional sender allows the messages to be sent as if a specific player sent them.", "This is useful with Minecraft 1.16.4's new chat ignore system, in which players can choose to ignore other players,", "but for this to work, the message needs to be sent from a player."}) diff --git a/src/main/java/ch/njol/skript/effects/EffReplace.java b/src/main/java/ch/njol/skript/effects/EffReplace.java index befc0ecda8a..f6a1fd5bad8 100644 --- a/src/main/java/ch/njol/skript/effects/EffReplace.java +++ b/src/main/java/ch/njol/skript/effects/EffReplace.java @@ -42,7 +42,7 @@ import java.util.regex.Matcher; @Name("Replace") -@Description("Replaces all occurrences of a given text with another text. Please note that you can only change variables and a few expressions, e.g. a <a href='../expressions.html#ExprMessage'>message</a> or a line of a sign.") +@Description("Replaces all occurrences of a given text with another text. Please note that you can only change variables and a few expressions, e.g. a <a href='./expressions.html#ExprMessage'>message</a> or a line of a sign.") @Examples({"replace \"<item>\" in {textvar} with \"%item%\"", "replace every \"&\" with \"§\" in line 1", "# The following acts as a simple chat censor, but it will e.g. censor mass, hassle, assassin, etc. as well:", diff --git a/src/main/java/ch/njol/skript/events/EvtAtTime.java b/src/main/java/ch/njol/skript/events/EvtAtTime.java index ac826cb4368..76a928d731d 100644 --- a/src/main/java/ch/njol/skript/events/EvtAtTime.java +++ b/src/main/java/ch/njol/skript/events/EvtAtTime.java @@ -47,8 +47,8 @@ @SuppressFBWarnings("EQ_COMPARETO_USE_OBJECT_EQUALS") public class EvtAtTime extends SelfRegisteringSkriptEvent implements Comparable<EvtAtTime> { static { - Skript.registerEvent("*At Time", EvtAtTime.class, ScheduledEvent.class, "at %time% [in %-worlds%]") - .description("An event that occurs at a given <a href='../classes.html#time'>minecraft time</a> in every world or only in specific worlds.") + Skript.registerEvent("*At Time", EvtAtTime.class, ScheduledEvent.class, "at %time% [in %worlds%]") + .description("An event that occurs at a given <a href='./classes.html#time'>minecraft time</a> in every world or only in specific worlds.") .examples("at 18:00", "at 7am in \"world\"") .since("1.3.4"); } diff --git a/src/main/java/ch/njol/skript/events/EvtChat.java b/src/main/java/ch/njol/skript/events/EvtChat.java index ebcd75e400b..f80e4dea7d2 100644 --- a/src/main/java/ch/njol/skript/events/EvtChat.java +++ b/src/main/java/ch/njol/skript/events/EvtChat.java @@ -47,7 +47,7 @@ public class EvtChat extends SelfRegisteringSkriptEvent { static { Skript.registerEvent("Chat", EvtChat.class, PlayerChatEventHandler.usesAsyncEvent ? AsyncPlayerChatEvent.class : PlayerChatEvent.class, "chat") - .description("Called whenever a player chats. Use <a href='../expressions.html#ExprChatFormat'>chat format</a> to change message format, use <a href='../expressions.html#ExprChatRecipients'>chat recipients</a> to edit chat recipients.") + .description("Called whenever a player chats. Use <a href='./expressions.html#ExprChatFormat'>chat format</a> to change message format, use <a href='./expressions.html#ExprChatRecipients'>chat recipients</a> to edit chat recipients.") .examples("on chat:", " if player has permission \"owner\":", " set chat format to \"<red>[player]<light gray>: <light red>[message]\"", diff --git a/src/main/java/ch/njol/skript/events/EvtClick.java b/src/main/java/ch/njol/skript/events/EvtClick.java index eec7125113c..9b16637e587 100644 --- a/src/main/java/ch/njol/skript/events/EvtClick.java +++ b/src/main/java/ch/njol/skript/events/EvtClick.java @@ -72,7 +72,8 @@ public class EvtClick extends SkriptEvent { "[(" + RIGHT + "¦right|" + LEFT + "¦left)(| |-)][mouse(| |-)]click[ing] [on %-entitydata/itemtype%] [(with|using|holding) %-itemtype%]", "[(" + RIGHT + "¦right|" + LEFT + "¦left)(| |-)][mouse(| |-)]click[ing] (with|using|holding) %itemtype% on %entitydata/itemtype%") .description("Called when a user clicks on a block, an entity or air with or without an item in their hand.", - "Please note that rightclick events with an empty hand while not looking at a block are not sent to the server, so there's no way to detect them.") + "Please note that rightclick events with an empty hand while not looking at a block are not sent to the server, so there's no way to detect them.", + "Also note that a leftclick on an entity is an attack and thus not covered by the 'click' event, but the 'damage' event.") .examples("on click:", "on rightclick holding a fishing rod:", "on leftclick on a stone or obsidian:", diff --git a/src/main/java/ch/njol/skript/events/EvtFirework.java b/src/main/java/ch/njol/skript/events/EvtFirework.java index b544c7af82e..7f820be68b1 100644 --- a/src/main/java/ch/njol/skript/events/EvtFirework.java +++ b/src/main/java/ch/njol/skript/events/EvtFirework.java @@ -43,7 +43,7 @@ public class EvtFirework extends SkriptEvent { .description("Called when a firework explodes.") .examples("on firework explode", "on firework exploding colored red, light green and black", - "on firework explosion coloured light green:", + "on firework explosion colored light green:", " broadcast \"A firework colored %colors% was exploded at %location%!\"")//TODO fix .since("2.4"); } diff --git a/src/main/java/ch/njol/skript/events/EvtGameMode.java b/src/main/java/ch/njol/skript/events/EvtGameMode.java index cd9233bb8f9..c48922f922e 100644 --- a/src/main/java/ch/njol/skript/events/EvtGameMode.java +++ b/src/main/java/ch/njol/skript/events/EvtGameMode.java @@ -36,8 +36,8 @@ */ public final class EvtGameMode extends SkriptEvent { static { - Skript.registerEvent("Gamemode Change", EvtGameMode.class, PlayerGameModeChangeEvent.class, "game[ ]mode change [to %-gamemode%]") - .description("Called when a player's <a href='../classes.html#gamemode'>gamemode</a> changes.") + Skript.registerEvent("Gamemode Change", EvtGameMode.class, PlayerGameModeChangeEvent.class, "game[ ]mode change [to %gamemode%]") + .description("Called when a player's <a href='./classes.html#gamemode'>gamemode</a> changes.") .examples("on gamemode change:", "on gamemode change to adventure:") .since("1.0"); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprAmountOfItems.java b/src/main/java/ch/njol/skript/expressions/ExprAmountOfItems.java index 4e3c9029d88..27a60e1d4ee 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprAmountOfItems.java +++ b/src/main/java/ch/njol/skript/expressions/ExprAmountOfItems.java @@ -35,7 +35,7 @@ import org.eclipse.jdt.annotation.Nullable; @Name("Amount of Items") -@Description("Counts how many of a particular <a href='../classes.html#itemtype'>item type</a> are in a given inventory.") +@Description("Counts how many of a particular <a href='./classes.html#itemtype'>item type</a> are in a given inventory.") @Examples("message \"You have %number of ores in the player's inventory% ores in your inventory.\"") @Since("2.0") public class ExprAmountOfItems extends SimpleExpression<Long> { diff --git a/src/main/java/ch/njol/skript/expressions/ExprChunk.java b/src/main/java/ch/njol/skript/expressions/ExprChunk.java index 0c7c6e6f7dd..d82545200ed 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprChunk.java +++ b/src/main/java/ch/njol/skript/expressions/ExprChunk.java @@ -41,7 +41,7 @@ * @author Peter Güttinger */ @Name("Chunk") -@Description("The <a href='../classes.html#chunk'>chunk</a> a block, location or entity is in.") +@Description("The <a href='./classes.html#chunk'>chunk</a> a block, location or entity is in.") @Examples("add the chunk at the player to {protected chunks::*}") @Since("2.0") public class ExprChunk extends PropertyExpression<Location, Chunk> { diff --git a/src/main/java/ch/njol/skript/expressions/ExprColorOf.java b/src/main/java/ch/njol/skript/expressions/ExprColorOf.java index 8e97612c38e..dd179b0d1e4 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprColorOf.java +++ b/src/main/java/ch/njol/skript/expressions/ExprColorOf.java @@ -48,11 +48,11 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -@Name("Colour of") -@Description("The <a href='../classes.html#color'>colour</a> of an item, can also be used to colour chat messages with \"<%colour of ...%>this text is coloured!\".") +@Name("Color of") +@Description("The <a href='./classes.html#color'>color</a> of an item, can also be used to color chat messages with \"<%color of ...%>this text is colored!\".") @Examples({"on click on wool:", - " message \"This wool block is <%colour of block%>%colour of block%<reset>!\"", - " set the colour of the block to black"}) + " message \"This wool block is <%color of block%>%color of block%<reset>!\"", + " set the color of the block to black"}) @Since("1.2") public class ExprColorOf extends PropertyExpression<Object, Color> { @@ -102,7 +102,7 @@ public Class<? extends Color> getReturnType() { @Override public String toString(@Nullable Event e, boolean debug) { - return "colour of " + getExpr().toString(e, debug); + return "color of " + getExpr().toString(e, debug); } @Override @@ -157,8 +157,8 @@ public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { colorable.setColor(color); } catch (UnsupportedOperationException ex) { // https://github.com/SkriptLang/Skript/issues/2931 - Skript.error("Tried setting the colour of a bed, but this isn't possible in your Minecraft version, " + - "since different coloured beds are different materials. " + + Skript.error("Tried setting the color of a bed, but this isn't possible in your Minecraft version, " + + "since different colored beds are different materials. " + "Instead, set the block to right material, such as a blue bed."); // Let's just assume it's a bed } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprColoured.java b/src/main/java/ch/njol/skript/expressions/ExprColoured.java index 279a00d5ed1..2ab501d5d9c 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprColoured.java +++ b/src/main/java/ch/njol/skript/expressions/ExprColoured.java @@ -35,16 +35,16 @@ import ch.njol.skript.util.chat.ChatMessages; import ch.njol.util.Kleenean; -@Name("Coloured / Uncoloured") -@Description({"Parses <colour>s and, optionally, chat styles in a message or removes", - "any colours <i>and</i> chat styles from the message. Parsing all", +@Name("Colored / Uncolored") +@Description({"Parses <color>s and, optionally, chat styles in a message or removes", + "any colors <i>and</i> chat styles from the message. Parsing all", "chat styles requires this expression to be used in same line with", "the <a href=effects.html#EffSend>send effect</a>."}) @Examples({"on chat:", - " set message to coloured message # Safe; only colors get parsed", + " set message to colored message # Safe; only colors get parsed", "command /fade <player>:", " trigger:", - " set display name of the player-argument to uncoloured display name of the player-argument", + " set display name of the player-argument to uncolored display name of the player-argument", "command /format <text>:", " trigger:", " message formatted text-argument # Safe, because we're sending to whoever used this command"}) @@ -93,7 +93,7 @@ public Class<? extends String> getReturnType() { @Override public String toString(final @Nullable Event e, final boolean debug) { - return (color ? "" : "un") + "coloured " + getExpr().toString(e, debug); + return (color ? "" : "un") + "colored " + getExpr().toString(e, debug); } /** diff --git a/src/main/java/ch/njol/skript/expressions/ExprDamageCause.java b/src/main/java/ch/njol/skript/expressions/ExprDamageCause.java index fdee255b54c..c9e8756efc3 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprDamageCause.java +++ b/src/main/java/ch/njol/skript/expressions/ExprDamageCause.java @@ -31,7 +31,7 @@ import ch.njol.skript.lang.ExpressionType; @Name("Damage Cause") -@Description("The <a href='classes.html#damagecause'>damage cause</a> of a damage event. Please click on the link for more information.") +@Description("The <a href='./classes.html#damagecause'>damage cause</a> of a damage event. Please click on the link for more information.") @Examples("damage cause is lava, fire or burning") @Since("2.0") public class ExprDamageCause extends EventValueExpression<DamageCause> { diff --git a/src/main/java/ch/njol/skript/expressions/ExprDifference.java b/src/main/java/ch/njol/skript/expressions/ExprDifference.java index c4d4a0aba7b..944fe5c22ee 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprDifference.java +++ b/src/main/java/ch/njol/skript/expressions/ExprDifference.java @@ -47,7 +47,7 @@ * @author Peter Güttinger */ @Name("Difference") -@Description("The difference between two values, e.g. <a href='../classes.html#number'>numbers</a>, <a href='../classes/#date'>dates</a> or <a href='../classes/#time'>times</a>.") +@Description("The difference between two values, e.g. <a href='./classes.html#number'>numbers</a>, <a href='./classes/#date'>dates</a> or <a href='./classes/#time'>times</a>.") @Examples({"if difference between {command::%player%::lastuse} and now is smaller than a minute:", "\tmessage \"You have to wait a minute before using this command again!\""}) @Since("1.4") diff --git a/src/main/java/ch/njol/skript/expressions/ExprDurability.java b/src/main/java/ch/njol/skript/expressions/ExprDurability.java index ab2a3ff6016..d0b2d75d42e 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprDurability.java +++ b/src/main/java/ch/njol/skript/expressions/ExprDurability.java @@ -42,7 +42,7 @@ @Name("Data/Damage Value") @Description({"The data/damage value of an item/block. Data values of blocks are only supported on 1.12.2 and below.", "You usually don't need this expression as you can check and set items with aliases easily, ", - "but this expression can e.g. be used to \"add 1 to data of <item>\", e.g. for cycling through all wool colours."}) + "but this expression can e.g. be used to \"add 1 to data of <item>\", e.g. for cycling through all wool colors."}) @Examples({"set damage value of player's tool to 10", "set data value of target block of player to 3", "add 1 to the data value of the clicked block", diff --git a/src/main/java/ch/njol/skript/expressions/ExprEnchantmentOfferCost.java b/src/main/java/ch/njol/skript/expressions/ExprEnchantmentOfferCost.java index f1cfdba39ce..10b18fa6a86 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprEnchantmentOfferCost.java +++ b/src/main/java/ch/njol/skript/expressions/ExprEnchantmentOfferCost.java @@ -33,11 +33,13 @@ import ch.njol.skript.util.Experience; import ch.njol.util.coll.CollectionUtils; -@Name("Enchantment Offer Enchantment Cost") -@Description({"The cost of an enchantment offer. This is displayed to the right of an enchantment offer.", - " If the cost is changed, it will always be at least 1.", - " This changes how many levels are required to enchant, but does not change the number of levels removed.", - " To change the number of levels removed, use the enchant event."}) +@Name("Enchantment Offer Cost") +@Description({ + "The cost of an enchantment offer. This is displayed to the right of an enchantment offer.", + "If the cost is changed, it will always be at least 1.", + "This changes how many levels are required to enchant, but does not change the number of levels removed.", + "To change the number of levels removed, use the enchant event." +}) @Examples("set cost of enchantment offer 1 to 50") @Since("2.5") @RequiredPlugins("1.11 or newer") diff --git a/src/main/java/ch/njol/skript/expressions/ExprFireworkEffect.java b/src/main/java/ch/njol/skript/expressions/ExprFireworkEffect.java index de1ae98d468..18711fa6955 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprFireworkEffect.java +++ b/src/main/java/ch/njol/skript/expressions/ExprFireworkEffect.java @@ -38,8 +38,8 @@ @Name("Firework Effect") @Description("Represents a 'firework effect' which can be used in the <a href='effects.html#EffFireworkLaunch'>launch firework</a> effect.") @Examples({"launch flickering trailing burst firework colored blue and green at player", - "launch trailing flickering star coloured purple, yellow, blue, green and red fading to pink at target entity", - "launch ball large coloured red, purple and white fading to light green and black at player's location with duration 1"}) + "launch trailing flickering star colored purple, yellow, blue, green and red fading to pink at target entity", + "launch ball large colored red, purple and white fading to light green and black at player's location with duration 1"}) @Since("2.4") public class ExprFireworkEffect extends SimpleExpression<FireworkEffect> { diff --git a/src/main/java/ch/njol/skript/expressions/ExprHanging.java b/src/main/java/ch/njol/skript/expressions/ExprHanging.java index d4d66461f94..0061ae9b0c9 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprHanging.java +++ b/src/main/java/ch/njol/skript/expressions/ExprHanging.java @@ -37,7 +37,7 @@ import org.eclipse.jdt.annotation.Nullable; @Name("Hanging Entity/Remover") -@Description("Returns the hanging entity or remover in hanging <a href='/events.html#break_mine'>break</a> and <a href='/events.html#place'>place</a> events.") +@Description("Returns the hanging entity or remover in hanging <a href='./events.html#break_mine'>break</a> and <a href='./events.html#place'>place</a> events.") @Examples({"on break of item frame:", "\tif item of hanging entity is diamond pickaxe:", "\t\tcancel event", diff --git a/src/main/java/ch/njol/skript/expressions/ExprHealReason.java b/src/main/java/ch/njol/skript/expressions/ExprHealReason.java index 77be703000b..12dc0270976 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprHealReason.java +++ b/src/main/java/ch/njol/skript/expressions/ExprHealReason.java @@ -37,7 +37,7 @@ @Name("Heal Reason") -@Description("The <a href='../classes.html#healreason'>heal reason</a> of a heal event. Please click on the link for more information.") +@Description("The <a href='./classes.html#healreason'>heal reason</a> of a heal event. Please click on the link for more information.") @Examples({"on heal:", "\tif heal reason = satiated:", "\t\tsend \"You ate enough food and gained health back!\" to player"}) diff --git a/src/main/java/ch/njol/skript/expressions/ExprHotbarSlot.java b/src/main/java/ch/njol/skript/expressions/ExprHotbarSlot.java index 678c033d475..0c4e497ff46 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprHotbarSlot.java +++ b/src/main/java/ch/njol/skript/expressions/ExprHotbarSlot.java @@ -33,9 +33,10 @@ import ch.njol.skript.util.slot.Slot; @Name("Hotbar Slot") -@Description({"The slot number of the currently selected hotbar slot."}) +@Description({"The currently selected hotbar <a href='./classes.html#slot'>slot</a>. To retrieve its number use <a href='#ExprSlotIndex'>Slot Index</a> expression."}) @Examples({"message \"%player's current hotbar slot%\"", - "set player's selected hotbar slot to slot 4 of player"}) + "set player's selected hotbar slot to slot 4 of player", + "send \"index of player's current hotbar slot = 1\" # second slot from the left"}) @Since("2.2-dev36") public class ExprHotbarSlot extends SimplePropertyExpression<Player, Slot> { diff --git a/src/main/java/ch/njol/skript/expressions/ExprInventoryAction.java b/src/main/java/ch/njol/skript/expressions/ExprInventoryAction.java index 3b00bea9efb..0c8d13fc6f3 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprInventoryAction.java +++ b/src/main/java/ch/njol/skript/expressions/ExprInventoryAction.java @@ -31,7 +31,7 @@ import ch.njol.skript.lang.ExpressionType; @Name("Inventory Action") -@Description("The <a href='../classes.html#inventoryaction'>inventory action</a> of an inventory event. Please click on the link for more information.") +@Description("The <a href='./classes.html#inventoryaction'>inventory action</a> of an inventory event. Please click on the link for more information.") @Examples("inventory action is pickup all") @Since("2.2-dev16") public class ExprInventoryAction extends EventValueExpression<InventoryAction> { diff --git a/src/main/java/ch/njol/skript/expressions/ExprInventoryInfo.java b/src/main/java/ch/njol/skript/expressions/ExprInventoryInfo.java index 950e9b736f3..2dcce43ae9b 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprInventoryInfo.java +++ b/src/main/java/ch/njol/skript/expressions/ExprInventoryInfo.java @@ -39,9 +39,9 @@ import ch.njol.util.Kleenean; @Name("Inventory Holder/Viewers/Rows/Slots") -@Description("Gets the amount of rows/slots, viewers and holder of an inventory." + - "" + - "NOTE: 'Viewers' expression returns a list of players viewing the inventory. Note that a player is considered to be viewing their own inventory and internal crafting screen even when said inventory is not open.") +@Description({"Gets the amount of rows/slots, viewers and holder of an inventory.", + "", + "NOTE: 'Viewers' expression returns a list of players viewing the inventory. Note that a player is considered to be viewing their own inventory and internal crafting screen even when said inventory is not open."}) @Examples({"event-inventory's amount of rows", "holder of player's top inventory", "{_inventory}'s viewers"}) diff --git a/src/main/java/ch/njol/skript/expressions/ExprLocationAt.java b/src/main/java/ch/njol/skript/expressions/ExprLocationAt.java index a59d3adc919..dc516c77854 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprLocationAt.java +++ b/src/main/java/ch/njol/skript/expressions/ExprLocationAt.java @@ -40,7 +40,7 @@ * @author Peter Güttinger */ @Name("Location At") -@Description("Allows to create a <a href='../classes.html#location'>location</a> from three coordinates and a world.") +@Description("Allows to create a <a href='./classes.html#location'>location</a> from three coordinates and a world.") @Examples({"set {_loc} to the location at arg-1, arg-2, arg-3 of the world arg-4", "distance between the player and the location (0, 0, 0) is less than 200"}) @Since("2.0") diff --git a/src/main/java/ch/njol/skript/expressions/ExprLoopValue.java b/src/main/java/ch/njol/skript/expressions/ExprLoopValue.java index 5f5a728761b..f0c893fe44f 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprLoopValue.java +++ b/src/main/java/ch/njol/skript/expressions/ExprLoopValue.java @@ -58,7 +58,7 @@ "loop 10 times:", " message \"%11 - loop-number%\"", " wait a second", - "# generate a 10x10 floor made of randomly coloured wool below the player:", + "# generate a 10x10 floor made of randomly colored wool below the player:", "loop blocks from the block below the player to the block 10 east of the block below the player:", " loop blocks from the loop-block to the block 10 north of the loop-block:", " set loop-block-2 to any wool"}) diff --git a/src/main/java/ch/njol/skript/expressions/ExprName.java b/src/main/java/ch/njol/skript/expressions/ExprName.java index 2b22278a768..056691dc6ff 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprName.java +++ b/src/main/java/ch/njol/skript/expressions/ExprName.java @@ -67,7 +67,7 @@ "\t\t<ul>", "\t\t\t<li><strong>Name:</strong> The Minecraft account name of the player. Can't be changed, but 'display name' can be changed.</li>", "\t\t\t<li><strong>Display Name:</strong> The name of the player that is displayed in messages. " + - "This name can be changed freely and can include colour codes, and is shared among all plugins (e.g. chat plugins will use the display name).</li>", + "This name can be changed freely and can include color codes, and is shared among all plugins (e.g. chat plugins will use the display name).</li>", "\t\t</ul>", "\t</li>", "\t<li><strong>Entities</strong>", diff --git a/src/main/java/ch/njol/skript/expressions/ExprParse.java b/src/main/java/ch/njol/skript/expressions/ExprParse.java index d849688ef7f..0cf481c7bdf 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprParse.java +++ b/src/main/java/ch/njol/skript/expressions/ExprParse.java @@ -55,7 +55,7 @@ "and one that parses the text according to a pattern.", "If the given text could not be parsed, this expression will return nothing and the <a href='#ExprParseError'>parse error</a> will be set if some information is available.", "Some notes about parsing with a pattern:", - "- The pattern must be a <a href='../patterns/'>Skript pattern</a>, " + + "- The pattern must be a <a href='./patterns/'>Skript pattern</a>, " + "e.g. percent signs are used to define where to parse which types, e.g. put a %number% or %items% in the pattern if you expect a number or some items there.", "- You <i>have to</i> save the expression's value in a list variable, e.g. <code>set {parsed::*} to message parsed as \"...\"</code>.", "- The list variable will contain the parsed values from all %types% in the pattern in order. If a type was plural, e.g. %items%, the variable's value at the respective index will be a list variable," + diff --git a/src/main/java/ch/njol/skript/expressions/ExprSignText.java b/src/main/java/ch/njol/skript/expressions/ExprSignText.java index 6d7a2b28069..592420ed647 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprSignText.java +++ b/src/main/java/ch/njol/skript/expressions/ExprSignText.java @@ -44,7 +44,7 @@ * @author Peter Güttinger */ @Name("Sign Text") -@Description("A line of text on a sign. Can be changed, but remember that there is a 16 character limit per line (including colour codes that use 2 characters each).") +@Description("A line of text on a sign. Can be changed, but remember that there is a 16 character limit per line (including color codes that use 2 characters each).") @Examples({"on rightclick on sign:", " line 2 of the clicked block is \"[Heal]\":", " heal the player", diff --git a/src/main/java/ch/njol/skript/expressions/ExprSpectatorTarget.java b/src/main/java/ch/njol/skript/expressions/ExprSpectatorTarget.java index fb22281d74d..5dd6ca5f71d 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprSpectatorTarget.java +++ b/src/main/java/ch/njol/skript/expressions/ExprSpectatorTarget.java @@ -18,6 +18,13 @@ */ package ch.njol.skript.expressions; +import ch.njol.skript.classes.Changer; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.util.coll.CollectionUtils; import org.bukkit.GameMode; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; diff --git a/src/main/java/ch/njol/skript/expressions/ExprSubstring.java b/src/main/java/ch/njol/skript/expressions/ExprSubstring.java index 02e5dc551e0..bbf1da03796 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprSubstring.java +++ b/src/main/java/ch/njol/skript/expressions/ExprSubstring.java @@ -21,14 +21,15 @@ import java.util.ArrayList; import java.util.List; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Keywords; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import ch.njol.skript.Skript; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; @@ -42,6 +43,7 @@ @Name("Substring") @Description("Extracts part of a text. You can either get the first <x> characters, the last <x> characters, the character at index <x>, or the characters between indices <x> and <y>." + " The indices <x> and <y> should be between 1 and the <a href='#ExprLength'>length</a> of the text (other values will be fit into this range).") +@Keywords({"substring", "subtext"}) @Examples({"set {_s} to the first 5 characters of the text argument", "message \"%subtext of {_s} from characters 2 to (the length of {_s} - 1)%\" # removes the first and last character from {_s} and sends it to the player or console", "set {_characters::*} to characters at 1, 2 and 7 in player's display name", diff --git a/src/main/java/ch/njol/skript/expressions/ExprTimeState.java b/src/main/java/ch/njol/skript/expressions/ExprTimeState.java index 0a0126fd9b3..19ef77c2219 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTimeState.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTimeState.java @@ -37,7 +37,7 @@ * @author Peter Güttinger */ @Name("Former/Future State") -@Description({"Represents the value of an expression before an event happened or the value it will have directly after the event, e.g. the old or new level respectively in a <a href='../events.html#level_change'>level change event</a>.", +@Description({"Represents the value of an expression before an event happened or the value it will have directly after the event, e.g. the old or new level respectively in a <a href='./events.html#level_change'>level change event</a>.", "Note: The past, future and present states of an expression are sometimes called 'time states' of an expression.", "Note 2: If you don't specify whether to use the past or future state of an expression that has different values, its default value will be used which is usually the value after the event."}) @Examples({"on teleport:", diff --git a/src/main/java/ch/njol/skript/expressions/ExprTimes.java b/src/main/java/ch/njol/skript/expressions/ExprTimes.java index 714a4df9a44..8c8cad634fa 100755 --- a/src/main/java/ch/njol/skript/expressions/ExprTimes.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTimes.java @@ -18,17 +18,12 @@ */ package ch.njol.skript.expressions; -import java.util.Iterator; -import java.util.stream.IntStream; -import java.util.stream.LongStream; - -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - -import com.google.common.collect.Iterators; import ch.njol.skript.Skript; import ch.njol.skript.config.Node; -import ch.njol.skript.doc.NoDoc; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.Literal; @@ -37,8 +32,21 @@ import ch.njol.skript.lang.util.SimpleLiteral; import ch.njol.skript.log.SkriptLogger; import ch.njol.util.Kleenean; +import com.google.common.collect.Iterators; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.Iterator; +import java.util.stream.LongStream; -@NoDoc +@Name("X Times") +@Description({"Integers between 1 and X, used in loops to loop X times."}) +@Examples({ + "loop 20 times:", + "\tbroadcast \"%21 - loop-number% seconds left..\"", + "\twait 1 second" +}) +@Since("1.4.6") public class ExprTimes extends SimpleExpression<Long> { static { diff --git a/src/main/java/ch/njol/skript/hooks/Hook.java b/src/main/java/ch/njol/skript/hooks/Hook.java index 5ffe5b5275c..c0d415c2786 100644 --- a/src/main/java/ch/njol/skript/hooks/Hook.java +++ b/src/main/java/ch/njol/skript/hooks/Hook.java @@ -20,6 +20,7 @@ import java.io.IOException; +import ch.njol.skript.doc.Documentation; import org.bukkit.Bukkit; import org.bukkit.plugin.Plugin; @@ -45,15 +46,25 @@ public Hook() throws IOException { @SuppressWarnings("unchecked") final P p = (P) Bukkit.getPluginManager().getPlugin(getName()); plugin = p; - if (p == null) + if (p == null) { + if (Documentation.canGenerateUnsafeDocs()) { + loadClasses(); + if (Skript.logHigh()) + Skript.info(m_hooked.toString(getName())); + } return; + } + if (!init()) { Skript.error(m_hook_error.toString(p.getName())); return; } + loadClasses(); + if (Skript.logHigh()) Skript.info(m_hooked.toString(p.getName())); + return; } diff --git a/src/main/java/ch/njol/skript/hooks/VaultHook.java b/src/main/java/ch/njol/skript/hooks/VaultHook.java index 7c5eda8a8bf..8377e8bc1d8 100644 --- a/src/main/java/ch/njol/skript/hooks/VaultHook.java +++ b/src/main/java/ch/njol/skript/hooks/VaultHook.java @@ -20,6 +20,7 @@ import java.io.IOException; +import ch.njol.skript.doc.Documentation; import net.milkbowl.vault.Vault; import net.milkbowl.vault.chat.Chat; import net.milkbowl.vault.economy.Economy; @@ -54,14 +55,14 @@ protected boolean init() { return economy != null || chat != null || permission != null; } - @SuppressWarnings("null") @Override + @SuppressWarnings("null") protected void loadClasses() throws IOException { - if (economy != null) + if (economy != null || Documentation.canGenerateUnsafeDocs()) Skript.getAddonInstance().loadClasses(getClass().getPackage().getName() + ".economy"); - if (chat != null) + if (chat != null || (Documentation.canGenerateUnsafeDocs())) Skript.getAddonInstance().loadClasses(getClass().getPackage().getName() + ".chat"); - if (permission != null) + if (permission != null || (Documentation.canGenerateUnsafeDocs())) Skript.getAddonInstance().loadClasses(getClass().getPackage().getName() + ".permission"); } diff --git a/src/main/java/ch/njol/skript/hooks/chat/expressions/ExprPrefixSuffix.java b/src/main/java/ch/njol/skript/hooks/chat/expressions/ExprPrefixSuffix.java index b36fa02718e..b5883a10024 100644 --- a/src/main/java/ch/njol/skript/hooks/chat/expressions/ExprPrefixSuffix.java +++ b/src/main/java/ch/njol/skript/hooks/chat/expressions/ExprPrefixSuffix.java @@ -18,14 +18,11 @@ */ package ch.njol.skript.hooks.chat.expressions; -import org.bukkit.entity.Player; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.SimplePropertyExpression; import ch.njol.skript.hooks.VaultHook; @@ -33,17 +30,24 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.util.Utils; import ch.njol.util.Kleenean; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; /** * @author Peter Güttinger */ @Name("Prefix/Suffix") @Description("The prefix or suffix as defined in the server's chat plugin.") -@Examples({"on chat:", - " cancel event", - " broadcast \"%player's prefix%%player's display name%%player's suffix%: %message%\" to the player's world", - "set the player's prefix to \"[<red>Admin<reset>] \""}) +@Examples({ + "on chat:", + "\tcancel event", + "\tbroadcast \"%player's prefix%%player's display name%%player's suffix%: %message%\" to the player's world", + "", + "set the player's prefix to \"[<red>Admin<reset>] \"" +}) @Since("2.0") +@RequiredPlugins({"Vault", "a chat plugin that supports Vault"}) public class ExprPrefixSuffix extends SimplePropertyExpression<Player, String> { static { register(ExprPrefixSuffix.class, String.class, "[chat] (1¦prefix|2¦suffix)", "players"); diff --git a/src/main/java/ch/njol/skript/hooks/economy/classes/Money.java b/src/main/java/ch/njol/skript/hooks/economy/classes/Money.java index 7994c3c0113..0fe60da1612 100644 --- a/src/main/java/ch/njol/skript/hooks/economy/classes/Money.java +++ b/src/main/java/ch/njol/skript/hooks/economy/classes/Money.java @@ -44,13 +44,14 @@ public class Money { .user("money") .name("Money") .description("A certain amount of money. Please note that this differs from <a href='#number'>numbers</a> as it includes a currency symbol or name, but usually the two are interchangeable, e.g. you can both <code>add 100$ to the player's balance</code> and <code>add 100 to the player's balance</code>.") - .usage("<code><number> $</code> or <code>$ <number></code>, where '$' is your server's currency, e.g. '10 rupees' or '£5.00'") + .usage("<number> $ or $ <number>, where '$' is your server's currency, e.g. '10 rupees' or '£5.00'") .examples("add 10£ to the player's account", "remove Fr. 9.95 from the player's money", "set the victim's money to 0", "increase the attacker's balance by the level of the victim * 100") .since("2.0") .before("itemtype", "itemstack") + .requiredPlugins("Vault", "an economy plugin that supports Vault") .parser(new Parser<Money>() { @Override @Nullable diff --git a/src/main/java/ch/njol/skript/hooks/permission/expressions/ExprGroup.java b/src/main/java/ch/njol/skript/hooks/permission/expressions/ExprGroup.java index db0f0eae85b..71aebfc1db2 100644 --- a/src/main/java/ch/njol/skript/hooks/permission/expressions/ExprGroup.java +++ b/src/main/java/ch/njol/skript/hooks/permission/expressions/ExprGroup.java @@ -33,9 +33,7 @@ import ch.njol.util.Kleenean; import net.milkbowl.vault.permission.Permission; import org.bukkit.OfflinePlayer; -import org.bukkit.entity.Player; import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import java.util.ArrayList; diff --git a/src/main/java/ch/njol/skript/hooks/regions/classes/Region.java b/src/main/java/ch/njol/skript/hooks/regions/classes/Region.java index 05c44274782..ad6f1af6bcb 100644 --- a/src/main/java/ch/njol/skript/hooks/regions/classes/Region.java +++ b/src/main/java/ch/njol/skript/hooks/regions/classes/Region.java @@ -53,6 +53,7 @@ public abstract class Region implements YggdrasilExtendedSerializable { .after("string", "world", "offlineplayer", "player") .since("2.1") .user("regions?") + .requiredPlugins("Supported regions plugin") .parser(new Parser<Region>() { @Override @Nullable diff --git a/src/main/java/ch/njol/skript/hooks/regions/conditions/CondCanBuild.java b/src/main/java/ch/njol/skript/hooks/regions/conditions/CondCanBuild.java index 45bfcea47c8..ce76fbad733 100644 --- a/src/main/java/ch/njol/skript/hooks/regions/conditions/CondCanBuild.java +++ b/src/main/java/ch/njol/skript/hooks/regions/conditions/CondCanBuild.java @@ -18,15 +18,11 @@ */ package ch.njol.skript.hooks.regions.conditions; -import org.bukkit.Location; -import org.bukkit.entity.Player; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; 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.hooks.regions.RegionsPlugin; import ch.njol.skript.lang.Condition; @@ -35,21 +31,30 @@ import ch.njol.skript.util.Direction; import ch.njol.util.Checker; import ch.njol.util.Kleenean; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; /** * @author Peter Güttinger */ @Name("Can Build") -@Description({"Tests whether a player is allowed to build at a certain location.", - "This condition requires a supported <a href='../classes.html#region'>regions</a> plugin to be installed."}) -@Examples({"command /setblock <material>:", - " description: set the block at your crosshair to a different type", - " trigger:", - " player cannot build at the targeted block:", - " message \"You do not have permission to change blocks there!\"", - " stop", - " set the targeted block to argument"}) +@Description({ + "Tests whether a player is allowed to build at a certain location.", + "This condition requires a supported <a href='./classes.html#region'>regions</a> plugin to be installed." +}) +@Examples({ + "command /setblock <material>:", + "\tdescription: set the block at your crosshair to a different type", + "\ttrigger:", + "\t\tplayer cannot build at the targeted block:", + "\t\t\tmessage \"You do not have permission to change blocks there!\"", + "\t\t\tstop", + "\t\tset the targeted block to argument" +}) @Since("2.0") +@RequiredPlugins("Supported regions plugin") public class CondCanBuild extends Condition { static { Skript.registerCondition(CondCanBuild.class, diff --git a/src/main/java/ch/njol/skript/hooks/regions/conditions/CondIsMember.java b/src/main/java/ch/njol/skript/hooks/regions/conditions/CondIsMember.java index d665e08de17..b946bce5a30 100644 --- a/src/main/java/ch/njol/skript/hooks/regions/conditions/CondIsMember.java +++ b/src/main/java/ch/njol/skript/hooks/regions/conditions/CondIsMember.java @@ -18,14 +18,11 @@ */ package ch.njol.skript.hooks.regions.conditions; -import org.bukkit.OfflinePlayer; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; import ch.njol.skript.doc.Since; import ch.njol.skript.hooks.regions.classes.Region; import ch.njol.skript.lang.Condition; @@ -33,6 +30,9 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Checker; import ch.njol.util.Kleenean; +import org.bukkit.OfflinePlayer; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; /** * @author Peter Güttinger @@ -40,11 +40,14 @@ @Name("Is Member/Owner of Region") @Description({"Checks whether a player is a member or owner of a particular region.", "This condition requires a supported regions plugin to be installed."}) -@Examples({"on region enter:", - " player is the owner of the region", - " message \"Welcome back to %region%!\"", - " send \"%player% just entered %region%!\" to all members of the region"}) +@Examples({ + "on region enter:", + "\tplayer is the owner of the region", + "\tmessage \"Welcome back to %region%!\"", + "\tsend \"%player% just entered %region%!\" to all members of the region" +}) @Since("2.1") +@RequiredPlugins("Supported regions plugin") public class CondIsMember extends Condition { static { Skript.registerCondition(CondIsMember.class, diff --git a/src/main/java/ch/njol/skript/hooks/regions/conditions/CondRegionContains.java b/src/main/java/ch/njol/skript/hooks/regions/conditions/CondRegionContains.java index 2345fbe4292..5b65c53c2a3 100644 --- a/src/main/java/ch/njol/skript/hooks/regions/conditions/CondRegionContains.java +++ b/src/main/java/ch/njol/skript/hooks/regions/conditions/CondRegionContains.java @@ -18,14 +18,11 @@ */ package ch.njol.skript.hooks.regions.conditions; -import org.bukkit.Location; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; import ch.njol.skript.doc.Since; import ch.njol.skript.hooks.regions.classes.Region; import ch.njol.skript.lang.Condition; @@ -34,18 +31,27 @@ import ch.njol.skript.util.Direction; import ch.njol.util.Checker; import ch.njol.util.Kleenean; +import org.bukkit.Location; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; /** * @author Peter Güttinger */ @Name("Region Contains") -@Description({"Checks whether a location is contained in a particular <a href='../classes.html#region'>region</a>.", - "This condition requires a supported regions plugin to be installed."}) -@Examples({"player is in the region {regions::3}", - "on region enter:", - " region contains {flags.%world%.red}", - " message \"The red flag is near!\""}) +@Description({ + "Checks whether a location is contained in a particular <a href='./classes.html#region'>region</a>.", + "This condition requires a supported regions plugin to be installed." +}) +@Examples({ + "player is in the region {regions::3}", + "", + "on region enter:", + "\tregion contains {flags.%world%.red}", + "\tmessage \"The red flag is near!\"" +}) @Since("2.1") +@RequiredPlugins("Supported regions plugin") public class CondRegionContains extends Condition { static { Skript.registerCondition(CondRegionContains.class, diff --git a/src/main/java/ch/njol/skript/hooks/regions/events/EvtRegionBorder.java b/src/main/java/ch/njol/skript/hooks/regions/events/EvtRegionBorder.java index d29bfeff511..a267d02beca 100644 --- a/src/main/java/ch/njol/skript/hooks/regions/events/EvtRegionBorder.java +++ b/src/main/java/ch/njol/skript/hooks/regions/events/EvtRegionBorder.java @@ -54,11 +54,12 @@ public class EvtRegionBorder extends SelfRegisteringSkriptEvent { Skript.registerEvent("Region Enter/Leave", EvtRegionBorder.class, RegionBorderEvent.class, "(0¦enter[ing]|1¦leav(e|ing)|1¦exit[ing]) [of] ([a] region|[[the] region] %-regions%)", "region (0¦enter[ing]|1¦leav(e|ing)|1¦exit[ing])") - .description("Called when a player enters or leaves a <a href='../classes.html#region'>region</a>.", + .description("Called when a player enters or leaves a <a href='./classes.html#region'>region</a>.", "This event requires a supported regions plugin to be installed.") .examples("on region exit:", " message \"Leaving %region%.\"") - .since("2.1"); + .since("2.1") + .requiredPlugins("Supported regions plugin"); EventValues.registerEventValue(RegionBorderEvent.class, Region.class, new Getter<Region, RegionBorderEvent>() { @Override public Region get(final RegionBorderEvent e) { diff --git a/src/main/java/ch/njol/skript/hooks/regions/expressions/ExprBlocksInRegion.java b/src/main/java/ch/njol/skript/hooks/regions/expressions/ExprBlocksInRegion.java index 29510473b1a..7d473a7905c 100644 --- a/src/main/java/ch/njol/skript/hooks/regions/expressions/ExprBlocksInRegion.java +++ b/src/main/java/ch/njol/skript/hooks/regions/expressions/ExprBlocksInRegion.java @@ -18,19 +18,11 @@ */ package ch.njol.skript.hooks.regions.expressions; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.NoSuchElementException; - -import org.bukkit.block.Block; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; import ch.njol.skript.doc.Since; import ch.njol.skript.hooks.regions.classes.Region; import ch.njol.skript.lang.Expression; @@ -40,16 +32,29 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.iterator.ArrayIterator; import ch.njol.util.coll.iterator.EmptyIterator; +import org.bukkit.block.Block; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.NoSuchElementException; /** * @author Peter Güttinger */ @Name("Blocks in Region") -@Description({"All blocks in a <a href='../classes.html#region'>region</a>.", - "This expression requires a supported regions plugin to be installed."}) -@Examples({"loop all blocks in the region {arena.%{faction.%player%}%}:", - " clear the loop-block"}) +@Description({ + "All blocks in a <a href='./classes.html#region'>region</a>.", + "This expression requires a supported regions plugin to be installed." +}) +@Examples({ + "loop all blocks in the region {arena.%{faction.%player%}%}:", + "\tclear the loop-block" +}) @Since("2.1") +@RequiredPlugins("Supported regions plugin") public class ExprBlocksInRegion extends SimpleExpression<Block> { static { Skript.registerExpression(ExprBlocksInRegion.class, Block.class, ExpressionType.COMBINED, diff --git a/src/main/java/ch/njol/skript/hooks/regions/expressions/ExprMembersOfRegion.java b/src/main/java/ch/njol/skript/hooks/regions/expressions/ExprMembersOfRegion.java index e7356f2bbc7..08d1d5d0983 100644 --- a/src/main/java/ch/njol/skript/hooks/regions/expressions/ExprMembersOfRegion.java +++ b/src/main/java/ch/njol/skript/hooks/regions/expressions/ExprMembersOfRegion.java @@ -18,16 +18,11 @@ */ package ch.njol.skript.hooks.regions.expressions; -import java.util.ArrayList; - -import org.bukkit.OfflinePlayer; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; import ch.njol.skript.doc.Since; import ch.njol.skript.hooks.regions.RegionsPlugin; import ch.njol.skript.hooks.regions.classes.Region; @@ -36,16 +31,26 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; +import org.bukkit.OfflinePlayer; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.ArrayList; /** * @author Peter Güttinger */ @Name("Region Members & Owners") -@Description({"A list of members or owners of a <a href='../classes.html#region'>region</a>.", - "This expression requires a supported regions plugin to be installed."}) -@Examples({"on entering of a region:", - " message \"You're entering %region% whose owners are %owners of region%\"."}) +@Description({ + "A list of members or owners of a <a href='./classes.html#region'>region</a>.", + "This expression requires a supported regions plugin to be installed." +}) +@Examples({ + "on entering of a region:", + "\tmessage \"You're entering %region% whose owners are %owners of region%\"" +}) @Since("2.1") +@RequiredPlugins("Supported regions plugin") public class ExprMembersOfRegion extends SimpleExpression<OfflinePlayer> { static { Skript.registerExpression(ExprMembersOfRegion.class, OfflinePlayer.class, ExpressionType.PROPERTY, diff --git a/src/main/java/ch/njol/skript/hooks/regions/expressions/ExprRegion.java b/src/main/java/ch/njol/skript/hooks/regions/expressions/ExprRegion.java index 2821e905e11..0cd7f735bd9 100644 --- a/src/main/java/ch/njol/skript/hooks/regions/expressions/ExprRegion.java +++ b/src/main/java/ch/njol/skript/hooks/regions/expressions/ExprRegion.java @@ -18,28 +18,33 @@ */ package ch.njol.skript.hooks.regions.expressions; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.EventValueExpression; import ch.njol.skript.hooks.regions.classes.Region; import ch.njol.skript.lang.ExpressionType; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; /** * @author Peter Güttinger */ @Name("Region") -@Description({"The <a href='classes.html#region'>region</a> involved in an event.", - "This expression requires a supported regions plugin to be installed."}) -@Examples({"on region enter:", - " region is {forbidden region}", - " cancel the event"}) +@Description({ + "The <a href='./classes.html#region'>region</a> involved in an event.", + "This expression requires a supported regions plugin to be installed." +}) +@Examples({ + "on region enter:", + "\tregion is {forbidden region}", + "\tcancel the event" +}) @Since("2.1") +@RequiredPlugins("Supported regions plugin") public class ExprRegion extends EventValueExpression<Region> { static { Skript.registerExpression(ExprRegion.class, Region.class, ExpressionType.SIMPLE, "[the] [event-]region"); diff --git a/src/main/java/ch/njol/skript/hooks/regions/expressions/ExprRegionsAt.java b/src/main/java/ch/njol/skript/hooks/regions/expressions/ExprRegionsAt.java index c3a44e010e9..9cd5a542f88 100644 --- a/src/main/java/ch/njol/skript/hooks/regions/expressions/ExprRegionsAt.java +++ b/src/main/java/ch/njol/skript/hooks/regions/expressions/ExprRegionsAt.java @@ -18,16 +18,11 @@ */ package ch.njol.skript.hooks.regions.expressions; -import java.util.ArrayList; - -import org.bukkit.Location; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; import ch.njol.skript.doc.Since; import ch.njol.skript.hooks.regions.RegionsPlugin; import ch.njol.skript.hooks.regions.classes.Region; @@ -37,21 +32,31 @@ import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.skript.util.Direction; import ch.njol.util.Kleenean; +import org.bukkit.Location; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.ArrayList; /** * @author Peter Güttinger */ @Name("Regions At") -@Description({"All <a href='../classes.html#region'>regions</a> at a particular <a href='../classes/#location'>location</a>.", - "This expression requires a supported regions plugin to be installed."}) -@Examples({"On click on a sign:", - " line 1 of the clicked block is \"[region info]\"", - " set {_regions::*} to regions at the clicked block", - " if {_regions::*} is empty:", - " message \"No regions exist at this sign.\"", - " else:", - " message \"Regions containing this sign: <gold>%{_regions::*}%<r>.\""}) +@Description({ + "All <a href='./classes.html#region'>regions</a> at a particular <a href='./classes/#location'>location</a>.", + "This expression requires a supported regions plugin to be installed." +}) +@Examples({ + "On click on a sign:", + "\tline 1 of the clicked block is \"[region info]\"", + "\tset {_regions::*} to regions at the clicked block", + "\tif {_regions::*} is empty:", + "\t\tmessage \"No regions exist at this sign.\"", + "\telse:", + "\t\tmessage \"Regions containing this sign: <gold>%{_regions::*}%<r>.\"" +}) @Since("2.1") +@RequiredPlugins("Supported regions plugin") public class ExprRegionsAt extends SimpleExpression<Region> { static { Skript.registerExpression(ExprRegionsAt.class, Region.class, ExpressionType.PROPERTY, diff --git a/src/main/java/ch/njol/skript/lang/Expression.java b/src/main/java/ch/njol/skript/lang/Expression.java index c0b58f166b0..d7ce5095d69 100644 --- a/src/main/java/ch/njol/skript/lang/Expression.java +++ b/src/main/java/ch/njol/skript/lang/Expression.java @@ -18,6 +18,17 @@ */ package ch.njol.skript.lang; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Spliterators; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; +import org.eclipse.jdt.annotation.Nullable; + import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer; import ch.njol.skript.classes.Changer.ChangeMode; @@ -276,6 +287,20 @@ default public Stream<? extends T> stream(final Event e) { */ @Nullable public Class<?>[] acceptChange(ChangeMode mode); + + /** + * Tests all accepted change modes, and if so what type it expects the <code>delta</code> to be. + * @return A Map contains ChangeMode as the key and accepted types of that mode as the value + */ + default Map<ChangeMode, Class<?>[]> getAcceptedChangeModes() { + HashMap<ChangeMode, Class<?>[]> map = new HashMap<>(); + for (ChangeMode cm : ChangeMode.values()) { + Class<?>[] ac = acceptChange(cm); + if (ac != null) + map.put(cm, ac); + } + return map; + } /** * Changes the expression's value by the given amount. This will only be called on supported modes and with the desired <code>delta</code> type as returned by diff --git a/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java b/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java index 91ae4610622..1151a073378 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java +++ b/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java @@ -39,6 +39,8 @@ public final class SkriptEventInfo<E extends SkriptEvent> extends StructureInfo< @Nullable private String[] examples; @Nullable + private String[] keywords; + @Nullable private String since; @Nullable private String documentationID; @@ -112,6 +114,18 @@ public SkriptEventInfo<E> examples(final String... examples) { this.examples = examples; return this; } + + /** + * Only used for Skript's documentation. + * + * @param keywords + * @return This SkriptEventInfo object + */ + public SkriptEventInfo<E> keywords(final String... keywords) { + assert this.keywords == null; + this.keywords = keywords; + return this; + } /** * Only used for Skript's documentation. @@ -171,6 +185,11 @@ public String[] getDescription() { public String[] getExamples() { return examples; } + + @Nullable + public String[] getKeywords() { + return keywords; + } @Nullable public String getSince() { 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 78a9a5523e4..8ac316b1ab7 100644 --- a/src/main/java/ch/njol/skript/lang/function/Functions.java +++ b/src/main/java/ch/njol/skript/lang/function/Functions.java @@ -23,12 +23,15 @@ import ch.njol.skript.SkriptAddon; import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.config.SectionNode; -import org.skriptlang.skript.lang.script.Script; +import ch.njol.skript.lang.ParseContext; +import ch.njol.skript.lang.SkriptParser; +import ch.njol.skript.log.SkriptLogger; import ch.njol.skript.registrations.Classes; import ch.njol.skript.util.Utils; import ch.njol.util.NonNullPair; import ch.njol.util.StringUtils; import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.script.Script; import java.util.ArrayList; import java.util.Collection; @@ -159,8 +162,7 @@ public static Signature<?> parseSignature(String script, String name, String arg if (returnClass == null) return signError("Cannot recognise the type '" + returnType + "'"); } - - return new Signature<>(script, name, parameters.toArray(new Parameter[0]), local, (ClassInfo<Object>) returnClass, singleReturn); + return new Signature<>(script, name, parameters.toArray(new Parameter[0]), local, (ClassInfo<Object>) returnClass, singleReturn, null); } /** diff --git a/src/main/java/ch/njol/skript/lang/function/JavaFunction.java b/src/main/java/ch/njol/skript/lang/function/JavaFunction.java index 11f7a014cb0..59c0d9e4279 100644 --- a/src/main/java/ch/njol/skript/lang/function/JavaFunction.java +++ b/src/main/java/ch/njol/skript/lang/function/JavaFunction.java @@ -30,9 +30,9 @@ public abstract class JavaFunction<T> extends Function<T> { public JavaFunction(Signature<T> sign) { super(sign); } - + public JavaFunction(String name, Parameter<?>[] parameters, ClassInfo<T> returnType, boolean single) { - this(new Signature<>("none", name, parameters, false, returnType, single)); + this(new Signature<>("none", name, parameters, false, returnType, single, Thread.currentThread().getStackTrace()[3].getClassName())); } @Override diff --git a/src/main/java/ch/njol/skript/lang/function/Signature.java b/src/main/java/ch/njol/skript/lang/function/Signature.java index 396806dc7e3..a07825fd292 100644 --- a/src/main/java/ch/njol/skript/lang/function/Signature.java +++ b/src/main/java/ch/njol/skript/lang/function/Signature.java @@ -68,17 +68,33 @@ public class Signature<T> { * References (function calls) to function with this signature. */ final Collection<FunctionReference<?>> calls; - - Signature(String script, String name, Parameter<?>[] parameters, boolean local, @Nullable final ClassInfo<T> returnType, boolean single) { + + /** + * The class path for the origin of this signature. + */ + @Nullable + final String originClassPath; + + public Signature(String script, + String name, + Parameter<?>[] parameters, boolean local, + @Nullable ClassInfo<T> returnType, + boolean single, + @Nullable String originClassPath) { this.script = script; this.name = name; this.parameters = parameters; this.local = local; this.returnType = returnType; this.single = single; - + this.originClassPath = originClassPath; + calls = Collections.newSetFromMap(new WeakHashMap<>()); } + + public Signature(String script, String name, Parameter<?>[] parameters, boolean local, @Nullable ClassInfo<T> returnType, boolean single) { + this(script, name, parameters, local, returnType, single, null); + } public String getName() { return name; @@ -105,7 +121,11 @@ public ClassInfo<T> getReturnType() { public boolean isSingle() { return single; } - + + public String getOriginClassPath() { + return originClassPath; + } + /** * Gets maximum number of parameters that the function described by this * signature is able to take. diff --git a/src/main/java/ch/njol/skript/sections/SecConditional.java b/src/main/java/ch/njol/skript/sections/SecConditional.java index 8b983b35f24..cadc1e333ce 100644 --- a/src/main/java/ch/njol/skript/sections/SecConditional.java +++ b/src/main/java/ch/njol/skript/sections/SecConditional.java @@ -22,6 +22,10 @@ import ch.njol.skript.Skript; import ch.njol.skript.config.Node; import ch.njol.skript.config.SectionNode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; import ch.njol.skript.events.bukkit.SkriptParseEvent; import ch.njol.skript.lang.Condition; import ch.njol.skript.lang.Expression; @@ -42,6 +46,32 @@ import java.util.Iterator; import java.util.List; +@Name("Conditionals") +@Description({ + "Conditional sections", + "if: executed when its condition is true", + "else if: executed if all previous chained conditionals weren't executed, and its condition is true", + "else: executed if all previous chained conditionals weren't executed", + "", + "parse if: a special case of 'if' condition that its code will not be parsed if the condition is not true", + "else parse if: another special case of 'else if' condition that its code will not be parsed if all previous chained " + + "conditionals weren't executed, and its condition is true", +}) +@Examples({ + "if player's health is greater than or equal to 4:", + "\tsend \"Your health is okay so far but be careful!\"", + "", + "else if player's health is greater than 2:", + "\tsend \"You need to heal ASAP, your health is very low!\"", + "", + "else: # Less than 2 hearts", + "\tsend \"You are about to DIE if you don't heal NOW. You have only %player's health% heart(s)!\"", + "", + "parse if plugin \"SomePluginName\" is enabled: # parse if %condition%", + "\t# This code will only be executed if the condition used is met otherwise Skript will not parse this section therefore will not give any errors/info about this section", + "" +}) +@Since("1.0") @SuppressWarnings("NotNullFieldNotInitialized") public class SecConditional extends Section { diff --git a/src/main/java/ch/njol/skript/sections/SecLoop.java b/src/main/java/ch/njol/skript/sections/SecLoop.java index d1c937e8d41..1b3826f81ce 100644 --- a/src/main/java/ch/njol/skript/sections/SecLoop.java +++ b/src/main/java/ch/njol/skript/sections/SecLoop.java @@ -21,6 +21,10 @@ import ch.njol.skript.Skript; import ch.njol.skript.SkriptAPIException; import ch.njol.skript.config.SectionNode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.Section; import ch.njol.skript.lang.SkriptParser.ParseResult; @@ -39,6 +43,43 @@ import java.util.Map; import java.util.WeakHashMap; +@Name("Loop") +@Description({ + "Loop sections repeat their code with multiple values.", + "", + "A loop will loop through all elements of the given expression, e.g. all players, worlds, items, etc. " + + "The conditions & effects inside the loop will be executed for every of those elements, " + + "which can be accessed with ‘loop-<what>’, e.g. <code>send \"hello\" to loop-player</code>. " + + "When a condition inside a loop is not fulfilled the loop will start over with the next element of the loop. " + + "You can however use <code>stop loop</code> to exit the loop completely and resume code execution after the end of the loop.", + "", + "<b>Loopable Values</b>", + "All <a href=\"/expressions.html\">expressions</a> that represent more than one value, e.g. ‘all players’, ‘worlds’, " + + "etc., as well as list variables, can be looped. You can also use a list of expressions, e.g. <code>loop the victim " + + "and the attacker</code>, to execute the same code for only a few values.", + "", + "<b>List Variables</b>", + "When looping list variables, you can also use <code>loop-index</code> in addition to <code>loop-value</code> inside " + + "the loop. <code>loop-value</code> is the value of the currently looped variable, and <code>loop-index</code> " + + "is the last part of the variable's name (the part where the list variable has its asterisk *)." +}) +@Examples({ + "loop all players:", + "\tsend \"Hello %loop-player%!\" to loop-player", + "", + "loop items in player's inventory:", + "\tif loop-item is dirt:", + "\t\tset loop-item to air", + "", + "loop 10 times:", + "\tsend title \"%11 - loop-value%\" and subtitle \"seconds left until the game begins\" to player for 1 second # 10, 9, 8 etc.", + "\twait 1 second", + "", + "loop {Coins::*}:", + "\tset {Coins::%loop-index%} to loop-value + 5 # Same as \"add 5 to {Coins::%loop-index%}\" where loop-index is the uuid of " + + "the player and loop-value is the actually coins value such as 200" +}) +@Since("1.0") public class SecLoop extends Section { static { diff --git a/src/main/java/ch/njol/skript/sections/SecWhile.java b/src/main/java/ch/njol/skript/sections/SecWhile.java index c03ddfc9c9e..102d6b620f3 100644 --- a/src/main/java/ch/njol/skript/sections/SecWhile.java +++ b/src/main/java/ch/njol/skript/sections/SecWhile.java @@ -20,6 +20,10 @@ import ch.njol.skript.Skript; import ch.njol.skript.config.SectionNode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Condition; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.Section; @@ -31,6 +35,25 @@ import java.util.List; +@Name("While Loop") +@Description("While Loop sections are loops that will just keep repeating as long as a condition is met.") +@Examples({ + "while size of all players < 5:", + "\tsend \"More players are needed to begin the adventure\" to all players", + "\twait 5 seconds", + "", + "set {_counter} to 1", + "do while {_counter} > 1: # false but will increase {_counter} by 1 then get out", + "\tadd 1 to {_counter}", + "", + "# Be careful when using while loops with conditions that are almost ", + "# always true for a long time without using 'wait %timespan%' inside it, ", + "# otherwise it will probably hang and crash your server.", + "while player is online:", + "\tgive player 1 dirt", + "\twait 1 second # without using a delay effect the server will crash", +}) +@Since("2.0, 2.6 (do while)") public class SecWhile extends Section { static { From ce466f74fca5b7843e27e11b90835612e706c6fb Mon Sep 17 00:00:00 2001 From: Dylan <djlevy26@gmail.com> Date: Fri, 20 Jan 2023 20:18:01 -0500 Subject: [PATCH 196/619] add event value for BlockSpreadEvent.getSource(); (#5377) --- .../skript/expressions/ExprSourceBlock.java | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprSourceBlock.java diff --git a/src/main/java/ch/njol/skript/expressions/ExprSourceBlock.java b/src/main/java/ch/njol/skript/expressions/ExprSourceBlock.java new file mode 100644 index 00000000000..e120943c538 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprSourceBlock.java @@ -0,0 +1,84 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ + +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Events; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import org.bukkit.block.Block; +import org.bukkit.event.Event; +import org.bukkit.event.block.BlockSpreadEvent; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Source Block") +@Description("The source block in a spread event.") +@Events("Spread") +@Examples({ + "on spread:", + "\tif the source block is a grass block:", + "\t\tset the source block to a dirt block" +}) +@Since("INSERT VERSION") +public class ExprSourceBlock extends SimpleExpression<Block> { + + static { + Skript.registerExpression(ExprSourceBlock.class, Block.class, ExpressionType.SIMPLE, "[the] source block"); + } + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(BlockSpreadEvent.class)) { + Skript.error("The 'source block' is only usable in a spread event"); + return false; + } + return true; + } + + @Override + protected Block[] get(Event event) { + if (!(event instanceof BlockSpreadEvent)) + return new Block[0]; + return new Block[]{((BlockSpreadEvent) event).getSource()}; + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class<? extends Block> getReturnType() { + return Block.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the source block"; + } + +} From 0f80646de12eef0f7bc9a7b223c150443a246d64 Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Sat, 21 Jan 2023 05:23:50 +0300 Subject: [PATCH 197/619] Add support for LootGenerateEvent (#5375) --- .../classes/data/BukkitEventValues.java | 19 +++ .../ch/njol/skript/events/SimpleEvents.java | 16 +++ .../ch/njol/skript/expressions/ExprLoot.java | 134 ++++++++++++++++++ 3 files changed, 169 insertions(+) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprLoot.java 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 e1a63fd04c2..c71d7fe48f8 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -138,6 +138,7 @@ import org.bukkit.event.weather.LightningStrikeEvent; import org.bukkit.event.weather.WeatherEvent; import org.bukkit.event.world.ChunkEvent; +import org.bukkit.event.world.LootGenerateEvent; import org.bukkit.event.world.PortalCreateEvent; import org.bukkit.event.world.StructureGrowEvent; import org.bukkit.event.world.WorldEvent; @@ -1427,5 +1428,23 @@ public Egg get(PlayerEggThrowEvent event) { return event.getEgg(); } }, EventValues.TIME_NOW); + + // LootGenerateEvent + if (Skript.classExists("org.bukkit.event.world.LootGenerateEvent")) { + EventValues.registerEventValue(LootGenerateEvent.class, Entity.class, new Getter<Entity, LootGenerateEvent>() { + @Override + @Nullable + public Entity get(LootGenerateEvent event) { + return event.getEntity(); + } + }, EventValues.TIME_NOW); + EventValues.registerEventValue(LootGenerateEvent.class, Location.class, new Getter<Location, LootGenerateEvent>() { + @Override + @Nullable + public Location get(LootGenerateEvent event) { + return event.getLootContext().getLocation(); + } + }, EventValues.TIME_NOW); + } } } diff --git a/src/main/java/ch/njol/skript/events/SimpleEvents.java b/src/main/java/ch/njol/skript/events/SimpleEvents.java index bfeb2df00fb..cbb57d3dcf4 100644 --- a/src/main/java/ch/njol/skript/events/SimpleEvents.java +++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java @@ -95,6 +95,7 @@ import org.bukkit.event.world.ChunkLoadEvent; import org.bukkit.event.world.ChunkPopulateEvent; import org.bukkit.event.world.ChunkUnloadEvent; +import org.bukkit.event.world.LootGenerateEvent; import org.bukkit.event.world.PortalCreateEvent; import org.bukkit.event.world.SpawnChangeEvent; import org.bukkit.event.world.WorldInitEvent; @@ -648,6 +649,21 @@ public class SimpleEvents { "\tcancel the event") .since("INSERT VERSION"); } + if (Skript.classExists("org.bukkit.event.world.LootGenerateEvent")) { + Skript.registerEvent("Loot Generate", SimpleEvent.class, LootGenerateEvent.class, "loot generat(e|ing)") + .description( + "Called when a loot table of an inventory is generated in the world.", + "For example, when opening a shipwreck chest." + ) + .examples( + "on loot generate:", + "\tchance of %10", + "\tadd 64 diamonds", + "\tsend \"You hit the jackpot!!\"" + ) + .since("INSERT VERSION") + .requiredPlugins("MC 1.16+"); + } } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprLoot.java b/src/main/java/ch/njol/skript/expressions/ExprLoot.java new file mode 100644 index 00000000000..9a91c3a85e5 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprLoot.java @@ -0,0 +1,134 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.inventory.ItemStack; +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 org.bukkit.event.Event; +import org.bukkit.event.world.LootGenerateEvent; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.ArrayList; +import java.util.List; + +@Name("Loot") +@Description("The loot that will be generated in a 'loot generate' event.") +@Examples({ + "on loot generate:", + "\tchance of %10", + "\tadd 64 diamonds", + "\tsend \"You hit the jackpot!!\"" +}) +@Since("INSERT VERSION") +@RequiredPlugins("MC 1.16+") +public class ExprLoot extends SimpleExpression<ItemStack> { + + static { + if (Skript.classExists("org.bukkit.event.world.LootGenerateEvent")) + Skript.registerExpression(ExprLoot.class, ItemStack.class, ExpressionType.SIMPLE, "[the] loot"); + } + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(LootGenerateEvent.class)) { + Skript.error("The 'loot' expression can only be used in a 'loot generate' event"); + return false; + } + return true; + } + + @Override + @Nullable + protected ItemStack[] get(Event event) { + if (!(event instanceof LootGenerateEvent)) + return new ItemStack[0]; + return ((LootGenerateEvent) event).getLoot().toArray(new ItemStack[0]); + } + + @Override + @Nullable + public Class<?>[] acceptChange(ChangeMode mode) { + switch (mode) { + case ADD: + case REMOVE: + case SET: + case DELETE: + return CollectionUtils.array(ItemStack[].class); + default: + return null; + } + } + + @Override + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + if (!(event instanceof LootGenerateEvent)) + return; + LootGenerateEvent lootEvent = (LootGenerateEvent) event; + + List<ItemStack> items = null; + if (delta != null) { + items = new ArrayList<>(delta.length); + for (Object item : delta) + items.add((ItemStack) item); + } + + switch (mode) { + case ADD: + lootEvent.getLoot().addAll(items); + break; + case REMOVE: + lootEvent.getLoot().removeAll(items); + break; + case SET: + lootEvent.setLoot(items); + break; + case DELETE: + lootEvent.getLoot().clear(); + break; + } + } + + @Override + public boolean isSingle() { + return false; + } + + @Override + public Class<? extends ItemStack> getReturnType() { + return ItemStack.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the loot"; + } + +} From 5e7f601ecf267ca85a2875c76a40f3249f853b87 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Fri, 20 Jan 2023 23:47:48 -0500 Subject: [PATCH 198/619] Fix isCurrentEvent checks (#5372) --- .../njol/skript/lang/parser/ParserInstance.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java b/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java index 25687c5ba16..44f8958b8d0 100644 --- a/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java +++ b/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java @@ -225,8 +225,13 @@ public Class<? extends Event>[] getCurrentEvents() { * <br><br> * See also {@link #isCurrentEvent(Class[])} for checking with multiple argument classes */ - public boolean isCurrentEvent(@Nullable Class<? extends Event> event) { - return CollectionUtils.containsSuperclass(currentEvents, event); + public boolean isCurrentEvent(Class<? extends Event> event) { + for (Class<? extends Event> currentEvent : currentEvents) { + // check that current event is same or child of event we want + if (event.isAssignableFrom(currentEvent)) + return true; + } + return false; } /** @@ -243,7 +248,11 @@ public boolean isCurrentEvent(@Nullable Class<? extends Event> event) { */ @SafeVarargs public final boolean isCurrentEvent(Class<? extends Event>... events) { - return CollectionUtils.containsAnySuperclass(currentEvents, events); + for (Class<? extends Event> event : events) { + if (isCurrentEvent(event)) + return true; + } + return true; } // Section API From 3e915cb3c109d2e0e90edb48065c25e8475ec37e Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Fri, 20 Jan 2023 22:47:59 -0700 Subject: [PATCH 199/619] Fix getting random literal lists not being random (#5348) --- .../njol/skript/expressions/ExprRandom.java | 72 ++++++++++++------- .../skript/lang/util/ConvertedLiteral.java | 2 - .../regressions/5345-any-aliases-random.sk | 16 +++++ 3 files changed, 62 insertions(+), 28 deletions(-) create mode 100644 src/test/skript/tests/regressions/5345-any-aliases-random.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprRandom.java b/src/main/java/ch/njol/skript/expressions/ExprRandom.java index e451fb4cc6c..c3a87f7e957 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprRandom.java +++ b/src/main/java/ch/njol/skript/expressions/ExprRandom.java @@ -19,6 +19,10 @@ package ch.njol.skript.expressions; import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @@ -30,62 +34,78 @@ import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionList; import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.skript.util.LiteralUtils; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -/** - * @author Peter Güttinger - */ @Name("Random") @Description("Gets a random item out of a set, e.g. a random player out of all players online.") -@Examples({"give a diamond to a random player out of all players", - "give a random item out of all items to the player"}) +@Examples({ + "give a diamond to a random player out of all players", + "give a random item out of all items to the player" +}) @Since("1.4.9") public class ExprRandom extends SimpleExpression<Object> { + static { Skript.registerExpression(ExprRandom.class, Object.class, ExpressionType.COMBINED, "[a] random %*classinfo% [out] of %objects%"); } - - @SuppressWarnings("null") + private Expression<?> expr; - - @SuppressWarnings("unchecked") + @Override - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { - final Expression<?> expr = exprs[1].getConvertedExpression((((Literal<ClassInfo<?>>) exprs[0]).getSingle()).getC()); + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (LiteralUtils.hasUnparsedLiteral(exprs[1])) { + expr = LiteralUtils.defendExpression(exprs[1]); + if (expr instanceof ExpressionList) { + Class<?> type = (((Literal<ClassInfo<?>>) exprs[0]).getSingle()).getC(); + List<Expression<?>> list = Arrays.stream(((ExpressionList<?>) expr).getExpressions()) + .map(expression -> (Expression<?>) expression.getConvertedExpression(type)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + if (list.isEmpty()) { + Skript.error("There are no objects of type '" + exprs[0].toString() + "' in the list " + exprs[1].toString()); + return false; + } + expr = CollectionUtils.getRandom(list); + } + } else { + expr = exprs[1].getConvertedExpression((((Literal<ClassInfo<?>>) exprs[0]).getSingle()).getC()); + } if (expr == null) return false; - this.expr = expr; return true; } - + @Override - protected Object[] get(final Event e) { - final Object[] set = expr.getAll(e); + protected Object[] get(Event event) { + Object[] set = expr.getAll(event); if (set.length <= 1) return set; - final Object[] one = (Object[]) Array.newInstance(set.getClass().getComponentType(), 1); + Object[] one = (Object[]) Array.newInstance(set.getClass().getComponentType(), 1); one[0] = CollectionUtils.getRandom(set); return one; } - + @Override - public Class<? extends Object> getReturnType() { - return expr.getReturnType(); + public boolean isSingle() { + return true; } - + @Override - public String toString(final @Nullable Event e, final boolean debug) { - return "a random element out of " + expr.toString(e, debug); + public Class<? extends Object> getReturnType() { + return expr.getReturnType(); } - + @Override - public boolean isSingle() { - return true; + public String toString(@Nullable Event event, boolean debug) { + return "a random element out of " + expr.toString(event, debug); } - + } diff --git a/src/main/java/ch/njol/skript/lang/util/ConvertedLiteral.java b/src/main/java/ch/njol/skript/lang/util/ConvertedLiteral.java index 6f9d90e9d8f..2b11c39be68 100644 --- a/src/main/java/ch/njol/skript/lang/util/ConvertedLiteral.java +++ b/src/main/java/ch/njol/skript/lang/util/ConvertedLiteral.java @@ -34,7 +34,6 @@ import ch.njol.util.coll.iterator.ArrayIterator; /** - * @author Peter Güttinger * @see SimpleLiteral */ public class ConvertedLiteral<F, T> extends ConvertedExpression<F, T> implements Literal<T> { @@ -47,7 +46,6 @@ public ConvertedLiteral(final Literal<F> source, final T[] data, final Class<T> @Override @Nullable public T convert(final F f) { - assert false; return Converters.convert(f, to); } }, 0)); diff --git a/src/test/skript/tests/regressions/5345-any-aliases-random.sk b/src/test/skript/tests/regressions/5345-any-aliases-random.sk new file mode 100644 index 00000000000..0fb00edf38d --- /dev/null +++ b/src/test/skript/tests/regressions/5345-any-aliases-random.sk @@ -0,0 +1,16 @@ +function randomAliases(empty: boolean = true) :: item: + return random item out of any boots + +test "any aliases random": + assert (random itemtype out of 14, a diamond helmet, false and console) is a diamond helmet with "Could not grab the diamond helmet out of the list." + assert (random itemtype out of 14, a diamond helmet, a gold helmet, false and console) is any helmet with "Could not grab any helmet out of the list." + assert random item out of any boots is any boots with "Failed to return the same types as the list." + set {_list::*} to true, any boots and false + assert random number out of {_list::*} is not set with "Failed to error when finding incorrect type." + loop 50 times: + if randomAliases() is not randomAliases(): + stop + assert false is true with "The aliases were the same 50 times in a row" + +test "any number random": + assert (random number out of 47, a diamond helmet, false and console) is 47 with "Could not grab the number out of the list." From 495fd4f6851a3a968a1818a1aad7441fb0eb5540 Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Sat, 21 Jan 2023 08:54:54 +0300 Subject: [PATCH 200/619] Plural Event Values (#5168) --- .../skript/classes/data/BukkitClasses.java | 4 +- .../classes/data/BukkitEventValues.java | 168 +++++++++++++----- .../skript/classes/data/SkriptClasses.java | 2 +- .../expressions/ExprEventExpression.java | 36 ++-- .../base/EventValueExpression.java | 111 ++++++------ .../ch/njol/util/coll/CollectionUtils.java | 12 ++ 6 files changed, 223 insertions(+), 110 deletions(-) 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 7302fc5d386..8ca30c77843 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -886,8 +886,8 @@ public String toVariableNameString(InventoryHolder holder) { .since("1.0")); Classes.registerClass(new ClassInfo<>(ItemStack.class, "itemstack") - .user("item", "material") - .name("Item / Material") + .user("items?") + .name("Item") .description("An item, e.g. a stack of torches, a furnace, or a wooden sword of sharpness 2. " + "Unlike <a href='#itemtype'>item type</a> an item can only represent exactly one item (e.g. an upside-down cobblestone stair facing west), " + "while an item type can represent a whole range of items (e.g. any cobble stone stairs regardless of direction).", 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 c71d7fe48f8..07630e88a7f 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -31,6 +31,7 @@ import ch.njol.skript.util.BlockUtils; import ch.njol.skript.util.DelayedChangeBlock; import ch.njol.skript.util.Direction; +import ch.njol.skript.util.EnchantmentType; import ch.njol.skript.util.Getter; import ch.njol.skript.util.slot.InventorySlot; import ch.njol.skript.util.slot.Slot; @@ -180,17 +181,35 @@ public Block get(final StructureGrowEvent e) { return e.getLocation().getBlock(); } }, 0); + EventValues.registerEventValue(StructureGrowEvent.class, Block[].class, new Getter<Block[], StructureGrowEvent>() { + @Override + @Nullable + public Block[] get(StructureGrowEvent event) { + return event.getBlocks().stream() + .map(BlockState::getBlock) + .toArray(Block[]::new); + } + }, EventValues.TIME_NOW); EventValues.registerEventValue(StructureGrowEvent.class, Block.class, new Getter<Block, StructureGrowEvent>() { @Override @Nullable - public Block get(final StructureGrowEvent e) { - for (final BlockState bs : e.getBlocks()) { - if (bs.getLocation().equals(e.getLocation())) + public Block get(StructureGrowEvent event) { + for (final BlockState bs : event.getBlocks()) { + if (bs.getLocation().equals(event.getLocation())) return new BlockStateBlock(bs); } - return e.getLocation().getBlock(); + return event.getLocation().getBlock(); } - }, 1); + }, EventValues.TIME_FUTURE); + EventValues.registerEventValue(StructureGrowEvent.class, Block[].class, new Getter<Block[], StructureGrowEvent>() { + @Override + @Nullable + public Block[] get(StructureGrowEvent event) { + return event.getBlocks().stream() + .map(BlockStateBlock::new) + .toArray(Block[]::new); + } + }, EventValues.TIME_FUTURE); // WeatherEvent - not a WorldEvent (wtf ô_Ô) EventValues.registerEventValue(WeatherEvent.class, World.class, new Getter<World, WeatherEvent>() { @Override @@ -419,7 +438,14 @@ public Player get(final SignChangeEvent e) { return e.getPlayer(); } }, 0); - + EventValues.registerEventValue(SignChangeEvent.class, String[].class, new Getter<String[], SignChangeEvent>() { + @Override + @Nullable + public String[] get(SignChangeEvent event) { + return event.getLines(); + } + }, EventValues.TIME_NOW); + // === EntityEvents === EventValues.registerEventValue(EntityEvent.class, Entity.class, new Getter<Entity, EntityEvent>() { @Override @@ -467,6 +493,13 @@ public Projectile get(final EntityDamageByEntityEvent e) { } }, 0); // EntityDeathEvent + EventValues.registerEventValue(EntityDeathEvent.class, ItemStack[].class, new Getter<ItemStack[], EntityDeathEvent>() { + @Override + @Nullable + public ItemStack[] get(EntityDeathEvent event) { + return event.getDrops().toArray(new ItemStack[0]); + } + }, EventValues.TIME_NOW); EventValues.registerEventValue(EntityDeathEvent.class, Projectile.class, new Getter<Projectile, EntityDeathEvent>() { @Override @Nullable @@ -570,6 +603,14 @@ public Block get(final EntityChangeBlockEvent e) { return e.getBlock(); } }, 0); + // AreaEffectCloudApplyEvent + EventValues.registerEventValue(AreaEffectCloudApplyEvent.class, LivingEntity[].class, new Getter<LivingEntity[], AreaEffectCloudApplyEvent>() { + @Override + @Nullable + public LivingEntity[] get(AreaEffectCloudApplyEvent event) { + return event.getAffectedEntities().toArray(new LivingEntity[0]); + } + }, EventValues.TIME_NOW); EventValues.registerEventValue(AreaEffectCloudApplyEvent.class, PotionEffectType.class, new Getter<PotionEffectType, AreaEffectCloudApplyEvent>() { @Override @Nullable @@ -752,13 +793,13 @@ public Entity get(final PlayerInteractEntityEvent e) { @Override @Nullable public ItemStack get(final PlayerInteractEntityEvent e) { - EquipmentSlot hand = e.getHand(); - if (hand == EquipmentSlot.HAND) - return e.getPlayer().getInventory().getItemInMainHand(); - else if (hand == EquipmentSlot.OFF_HAND) - return e.getPlayer().getInventory().getItemInOffHand(); - else - return null; + EquipmentSlot hand = e.getHand(); + if (hand == EquipmentSlot.HAND) + return e.getPlayer().getInventory().getItemInMainHand(); + else if (hand == EquipmentSlot.OFF_HAND) + return e.getPlayer().getInventory().getItemInOffHand(); + else + return null; } }, 0); // PlayerInteractEvent @@ -807,27 +848,27 @@ public ItemStack get(PlayerItemDamageEvent event) { } }, 0); //PlayerItemMendEvent - EventValues.registerEventValue(PlayerItemMendEvent.class, Player.class, new Getter<Player, PlayerItemMendEvent>() { - @Override - @Nullable - public Player get(PlayerItemMendEvent e) { - return e.getPlayer(); - } - }, 0); - EventValues.registerEventValue(PlayerItemMendEvent.class, ItemStack.class, new Getter<ItemStack, PlayerItemMendEvent>() { - @Override - @Nullable - public ItemStack get(PlayerItemMendEvent e) { - return e.getItem(); - } - }, 0); - EventValues.registerEventValue(PlayerItemMendEvent.class, Entity.class, new Getter<Entity, PlayerItemMendEvent>() { - @Override - @Nullable - public Entity get(PlayerItemMendEvent e) { - return e.getExperienceOrb(); - } - }, 0); + EventValues.registerEventValue(PlayerItemMendEvent.class, Player.class, new Getter<Player, PlayerItemMendEvent>() { + @Override + @Nullable + public Player get(PlayerItemMendEvent e) { + return e.getPlayer(); + } + }, 0); + EventValues.registerEventValue(PlayerItemMendEvent.class, ItemStack.class, new Getter<ItemStack, PlayerItemMendEvent>() { + @Override + @Nullable + public ItemStack get(PlayerItemMendEvent e) { + return e.getItem(); + } + }, 0); + EventValues.registerEventValue(PlayerItemMendEvent.class, Entity.class, new Getter<Entity, PlayerItemMendEvent>() { + @Override + @Nullable + public Entity get(PlayerItemMendEvent e) { + return e.getExperienceOrb(); + } + }, 0); // --- HangingEvents --- @@ -939,6 +980,12 @@ public CommandSender get(final ServerCommandEvent e) { return e.getSender(); } }, 0); + EventValues.registerEventValue(CommandEvent.class, String[].class, new Getter<String[], CommandEvent>() { + @Override + public String[] get(CommandEvent event) { + return event.getArgs(); + } + }, EventValues.TIME_NOW); EventValues.registerEventValue(CommandEvent.class, CommandSender.class, new Getter<CommandSender, CommandEvent>() { @Override public CommandSender get(final CommandEvent e) { @@ -952,7 +999,7 @@ public World get(final CommandEvent e) { return e.getSender() instanceof Player ? ((Player) e.getSender()).getWorld() : null; } }, 0); - + // === ServerEvents === // Script load/unload event EventValues.registerEventValue(ScriptEvent.class, CommandSender.class, new Getter<CommandSender, ScriptEvent>() { @@ -1074,6 +1121,15 @@ public Player get(BlockFertilizeEvent event) { return event.getPlayer(); } }, 0); + EventValues.registerEventValue(BlockFertilizeEvent.class, Block[].class, new Getter<Block[], BlockFertilizeEvent>() { + @Nullable + @Override + public Block[] get(BlockFertilizeEvent event) { + return event.getBlocks().stream() + .map(BlockState::getBlock) + .toArray(Block[]::new); + } + }, EventValues.TIME_NOW); // PrepareItemCraftEvent EventValues.registerEventValue(PrepareItemCraftEvent.class, Slot.class, new Getter<Slot, PrepareItemCraftEvent>() { @Override @@ -1196,6 +1252,15 @@ public World get(final PortalCreateEvent e) { return e.getWorld(); } }, 0); + EventValues.registerEventValue(PortalCreateEvent.class, Block[].class, new Getter<Block[], PortalCreateEvent>() { + @Override + @Nullable + public Block[] get(PortalCreateEvent event) { + return event.getBlocks().stream() + .map(BlockState::getBlock) + .toArray(Block[]::new); + } + }, EventValues.TIME_NOW); if (Skript.methodExists(PortalCreateEvent.class, "getEntity")) { // Minecraft 1.14+ EventValues.registerEventValue(PortalCreateEvent.class, Entity.class, new Getter<Entity, PortalCreateEvent>() { @Override @@ -1222,6 +1287,18 @@ public ItemStack get(PlayerEditBookEvent event) { return book; } }, EventValues.TIME_FUTURE); + EventValues.registerEventValue(PlayerEditBookEvent.class, String[].class, new Getter<String[], PlayerEditBookEvent>() { + @Override + public String[] get(PlayerEditBookEvent event) { + return event.getPreviousBookMeta().getPages().toArray(new String[0]); + } + }, EventValues.TIME_PAST); + EventValues.registerEventValue(PlayerEditBookEvent.class, String[].class, new Getter<String[], PlayerEditBookEvent>() { + @Override + public String[] get(PlayerEditBookEvent event) { + return event.getNewBookMeta().getPages().toArray(new String[0]); + } + }, EventValues.TIME_FUTURE); //ItemDespawnEvent EventValues.registerEventValue(ItemDespawnEvent.class, Item.class, new Getter<Item, ItemDespawnEvent>() { @Override @@ -1334,12 +1411,12 @@ public FireworkEffect get(FireworkExplodeEvent e) { } }, 0); //PlayerRiptideEvent - EventValues.registerEventValue(PlayerRiptideEvent.class, ItemStack.class, new Getter<ItemStack, PlayerRiptideEvent>() { - @Override - public ItemStack get(PlayerRiptideEvent e) { - return e.getItem(); - } - }, 0); + EventValues.registerEventValue(PlayerRiptideEvent.class, ItemStack.class, new Getter<ItemStack, PlayerRiptideEvent>() { + @Override + public ItemStack get(PlayerRiptideEvent e) { + return e.getItem(); + } + }, 0); //PlayerArmorChangeEvent if (Skript.classExists("com.destroystokyo.paper.event.player.PlayerArmorChangeEvent")) { EventValues.registerEventValue(PlayerArmorChangeEvent.class, ItemStack.class, new Getter<ItemStack, PlayerArmorChangeEvent>() { @@ -1387,6 +1464,15 @@ public ItemStack get(EnchantItemEvent e) { return e.getItem(); } }, 0); + EventValues.registerEventValue(EnchantItemEvent.class, EnchantmentType[].class, new Getter<EnchantmentType[], EnchantItemEvent>() { + @Override + @Nullable + public EnchantmentType[] get(EnchantItemEvent event) { + return event.getEnchantsToAdd().entrySet().stream() + .map(entry -> new EnchantmentType(entry.getKey(), entry.getValue())) + .toArray(EnchantmentType[]::new); + } + }, EventValues.TIME_NOW); EventValues.registerEventValue(EnchantItemEvent.class, Block.class, new Getter<Block, EnchantItemEvent>() { @Override @Nullable 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 eb7ff0e418b..2f894ce5580 100644 --- a/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java @@ -181,7 +181,7 @@ public String toVariableNameString(final WeatherType o) { .serializer(new EnumSerializer<>(WeatherType.class))); Classes.registerClass(new ClassInfo<>(ItemType.class, "itemtype") - .user("item ?types?", "items", "materials") + .user("item ?types?", "materials?") .name("Item Type") .description("An item type is an alias, e.g. 'a pickaxe', 'all plants', etc., and can result in different items when added to an inventory, " + "and unlike <a href='#itemstack'>items</a> they are well suited for checking whether an inventory contains a certain item or whether a certain item is of a certain type.", diff --git a/src/main/java/ch/njol/skript/expressions/ExprEventExpression.java b/src/main/java/ch/njol/skript/expressions/ExprEventExpression.java index 73e2a8f95ec..c5d959970ac 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprEventExpression.java +++ b/src/main/java/ch/njol/skript/expressions/ExprEventExpression.java @@ -18,9 +18,6 @@ */ package ch.njol.skript.expressions; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.doc.NoDoc; @@ -30,7 +27,14 @@ import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.localization.Noun; +import ch.njol.skript.registrations.Classes; +import ch.njol.skript.util.Utils; import ch.njol.util.Kleenean; +import ch.njol.util.NonNullPair; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; /** * Provided for convenience: one can write 'event-world' instead of only 'world' to distinguish between the event-world and the loop-world. @@ -39,22 +43,26 @@ */ @NoDoc public class ExprEventExpression extends WrapperExpression<Object> { + static { Skript.registerExpression(ExprEventExpression.class, Object.class, ExpressionType.PROPERTY, "[the] event-%*classinfo%");// property so that it is parsed after most other expressions } - + @Override - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parser) { - @SuppressWarnings("unchecked") - final ClassInfo<?> ci = ((Literal<ClassInfo<?>>) exprs[0]).getSingle(); - final EventValueExpression<?> e = new EventValueExpression<Object>(ci.getC()); - setExpr(e); - return e.init(); + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) { + ClassInfo<?> classInfo = ((Literal<ClassInfo<?>>) exprs[0]).getSingle(); + Class<?> c = classInfo.getC(); + + boolean plural = Utils.getEnglishPlural(parser.expr).getSecond(); + EventValueExpression<?> eventValue = new EventValueExpression<>(plural ? CollectionUtils.arrayType(c) : c); + setExpr(eventValue); + return eventValue.init(); } - + @Override - public String toString(final @Nullable Event e, final boolean debug) { - return getExpr().toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return getExpr().toString(event, debug); } - + } diff --git a/src/main/java/ch/njol/skript/expressions/base/EventValueExpression.java b/src/main/java/ch/njol/skript/expressions/base/EventValueExpression.java index 001ce7e62c7..519e10c0e9e 100644 --- a/src/main/java/ch/njol/skript/expressions/base/EventValueExpression.java +++ b/src/main/java/ch/njol/skript/expressions/base/EventValueExpression.java @@ -65,83 +65,92 @@ public class EventValueExpression<T> extends SimpleExpression<T> implements DefaultExpression<T> { private final Class<? extends T> c; + private final Class<?> componentType; @Nullable private Changer<? super T> changer; private final Map<Class<? extends Event>, Getter<? extends T, ?>> getters = new HashMap<>(); + private final boolean single; - public EventValueExpression(final Class<? extends T> c) { + public EventValueExpression(Class<? extends T> c) { this(c, null); } - public EventValueExpression(final Class<? extends T> c, final @Nullable Changer<? super T> changer) { + public EventValueExpression(Class<? extends T> c, @Nullable Changer<? super T> changer) { assert c != null; this.c = c; this.changer = changer; + single = !c.isArray(); + componentType = single ? c : c.getComponentType(); } @Override @Nullable - protected T[] get(final Event e) { - final T o = getValue(e); - if (o == null) - return null; - @SuppressWarnings("unchecked") - final T[] one = (T[]) Array.newInstance(c, 1); - one[0] = o; - return one; - } - @SuppressWarnings("unchecked") + protected T[] get(Event event) { + T value = getValue(event); + if (value == null) + return (T[]) Array.newInstance(c, 0); + if (single) { + T[] one = (T[]) Array.newInstance(c, 1); + one[0] = value; + return one; + } + T[] dataArray = (T[]) value; + T[] array = (T[]) Array.newInstance(c.getComponentType(), ((T[]) value).length); + System.arraycopy(dataArray, 0, array, 0, array.length); + return array; + } + @Nullable - private <E extends Event> T getValue(final E e) { - if (getters.containsKey(e.getClass())) { - final Getter<? extends T, ? super E> g = (Getter<? extends T, ? super E>) getters.get(e.getClass()); - return g == null ? null : g.get(e); + @SuppressWarnings("unchecked") + private <E extends Event> T getValue(E event) { + if (getters.containsKey(event.getClass())) { + final Getter<? extends T, ? super E> g = (Getter<? extends T, ? super E>) getters.get(event.getClass()); + return g == null ? null : g.get(event); } for (final Entry<Class<? extends Event>, Getter<? extends T, ?>> p : getters.entrySet()) { - if (p.getKey().isAssignableFrom(e.getClass())) { - getters.put(e.getClass(), p.getValue()); - return p.getValue() == null ? null : ((Getter<? extends T, ? super E>) p.getValue()).get(e); + if (p.getKey().isAssignableFrom(event.getClass())) { + getters.put(event.getClass(), p.getValue()); + return p.getValue() == null ? null : ((Getter<? extends T, ? super E>) p.getValue()).get(event); } } - getters.put(e.getClass(), null); + getters.put(event.getClass(), null); return null; } @Override - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parser) { + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) { if (exprs.length != 0) throw new SkriptAPIException(this.getClass().getName() + " has expressions in its pattern but does not override init(...)"); return init(); } - @SuppressWarnings("null") @Override public boolean init() { final ParseLogHandler log = SkriptLogger.startParseLogHandler(); try { boolean hasValue = false; - final Class<? extends Event>[] es = getParser().getCurrentEvents(); - if (es == null) { + Class<? extends Event>[] events = getParser().getCurrentEvents(); + if (events == null) { assert false; return false; } - for (final Class<? extends Event> e : es) { - if (getters.containsKey(e)) { - hasValue = getters.get(e) != null; + for (Class<? extends Event> event : events) { + if (getters.containsKey(event)) { + hasValue = getters.get(event) != null; continue; } - final Getter<? extends T, ?> getter = EventValues.getEventValueGetter(e, c, getTime()); + Getter<? extends T, ?> getter = EventValues.getEventValueGetter(event, c, getTime()); if (getter != null) { - getters.put(e, getter); + getters.put(event, getter); hasValue = true; } } if (!hasValue) { - log.printError("There's no " + Classes.getSuperClassInfo(c).getName() + " in " + Utils.a(getParser().getCurrentEventName()) + " event"); + log.printError("There's no " + Classes.getSuperClassInfo(componentType).getName().toString(!single) + " in " + Utils.a(getParser().getCurrentEventName()) + " event"); return false; } log.printLog(); @@ -158,44 +167,42 @@ public Class<? extends T> getReturnType() { @Override public boolean isSingle() { - return true; + return single; } @Override - public String toString(final @Nullable Event e, final boolean debug) { - if (!debug || e == null) - return "event-" + Classes.getSuperClassInfo(c).getName(); - return Classes.getDebugMessage(getValue(e)); + public String toString(@Nullable Event event, boolean debug) { + if (!debug || event == null) + return "event-" + Classes.getSuperClassInfo(componentType).getName().toString(!single); + return Classes.getDebugMessage(getValue(event)); } - @SuppressWarnings("unchecked") @Override @Nullable - public Class<?>[] acceptChange(final ChangeMode mode) { - Changer<? super T> ch = changer; - if (ch == null) - changer = ch = (Changer<? super T>) Classes.getSuperClassInfo(c).getChanger(); - return ch == null ? null : ch.acceptChange(mode); + @SuppressWarnings("unchecked") + public Class<?>[] acceptChange(ChangeMode mode) { + if (changer == null) + changer = (Changer<? super T>) Classes.getSuperClassInfo(c).getChanger(); + return changer == null ? null : changer.acceptChange(mode); } @Override - public void change(final Event e, final @Nullable Object[] delta, final ChangeMode mode) { - final Changer<? super T> ch = changer; - if (ch == null) - throw new UnsupportedOperationException(); - ChangerUtils.change(ch, getArray(e), delta, mode); + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + if (changer == null) + throw new SkriptAPIException("The changer cannot be null"); + ChangerUtils.change(changer, getArray(event), delta, mode); } @Override - public boolean setTime(final int time) { - final Class<? extends Event>[] es = getParser().getCurrentEvents(); - if (es == null) { + public boolean setTime(int time) { + Class<? extends Event>[] events = getParser().getCurrentEvents(); + if (events == null) { assert false; return false; } - for (final Class<? extends Event> e : es) { - assert e != null; - if (EventValues.doesEventValueHaveTimeStates(e, c)) { + for (Class<? extends Event> event : events) { + assert event != null; + if (EventValues.doesEventValueHaveTimeStates(event, c)) { super.setTime(time); // Since the time was changed, we now need to re-initalize the getters we already got. START getters.clear(); diff --git a/src/main/java/ch/njol/util/coll/CollectionUtils.java b/src/main/java/ch/njol/util/coll/CollectionUtils.java index 21bad6f36df..908f245e7ff 100644 --- a/src/main/java/ch/njol/util/coll/CollectionUtils.java +++ b/src/main/java/ch/njol/util/coll/CollectionUtils.java @@ -22,6 +22,7 @@ import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; +import java.lang.reflect.Array; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -355,6 +356,17 @@ public static <E> Set<E> union(final @Nullable Set<E>... sets) { public static <T> T[] array(final T... array) { return array; } + + /** + * Returns a {@code Class} for an array type whose component type + * is described by this {@linkplain Class}. + * + * @return a {@code Class} describing the array type + */ + @SuppressWarnings("unchecked") + public static <T> Class<T[]> arrayType(Class<T> c) { + return (Class<T[]>) Array.newInstance(c, 0).getClass(); + } /** * Creates a permutation of all integers in the interval [start, end] From 078f65d396c645739f9d5fbdd15ed9cae019ac45 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Sat, 21 Jan 2023 01:45:15 -0500 Subject: [PATCH 201/619] Wacky Wednesday's #1: The Pickle API (#5076) --- .../skript/expressions/ExprSeaPickles.java | 152 ++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprSeaPickles.java diff --git a/src/main/java/ch/njol/skript/expressions/ExprSeaPickles.java b/src/main/java/ch/njol/skript/expressions/ExprSeaPickles.java new file mode 100644 index 00000000000..df5b3f1ff1a --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprSeaPickles.java @@ -0,0 +1,152 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.type.SeaPickle; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Sea Pickles") +@Description("An expression to obtain or modify data relating to the pickles of a sea pickle block.") +@Examples({ + "on block break:", + "\ttype of block is sea pickle", + "\tsend \"Wow! This stack of sea pickles contained %event-block's sea pickle count% pickles!\"", + "\tsend \"It could've contained a maximum of %event-block's maximum sea pickle count% pickles!\"", + "\tsend \"It had to have contained at least %event-block's minimum sea pickle count% pickles!\"", + "\tcancel event", + "\tset event-block's sea pickle count to event-block's maximum sea pickle count", + "\tsend \"This bad boy is going to hold so many pickles now!!\"" +}) +@Since("INSERT VERSION") +public class ExprSeaPickles extends SimplePropertyExpression<Block, Integer> { + + static { + register(ExprSeaPickles.class, Integer.class, "[:(min|max)[imum]] [sea] pickle(s| (count|amount))", "blocks"); + } + + private boolean minimum, maximum; + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + minimum = parseResult.hasTag("min"); + maximum = parseResult.hasTag("max"); + return super.init(exprs, matchedPattern, isDelayed, parseResult); + } + + @Override + @Nullable + public Integer convert(Block block) { + BlockData blockData = block.getBlockData(); + if (!(blockData instanceof SeaPickle)) + return null; + + SeaPickle pickleData = (SeaPickle) blockData; + + if (maximum) + return pickleData.getMaximumPickles(); + if (minimum) + return pickleData.getMinimumPickles(); + return pickleData.getPickles(); + } + + @Override + @Nullable + public Class<?>[] acceptChange(ChangeMode mode) { + if (minimum || maximum) + return null; + switch (mode) { + case SET: + case ADD: + case REMOVE: + case RESET: + case DELETE: + return CollectionUtils.array(Number.class); + default: + return null; + } + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + if (delta == null && mode != ChangeMode.RESET && mode != ChangeMode.DELETE) + return; + + int change = delta != null ? ((Number) delta[0]).intValue() : 0; + if (mode == ChangeMode.REMOVE) + change *= -1; + + for (Block block : getExpr().getArray(event)) { + // Obtain pickle data + BlockData blockData = block.getBlockData(); + if (!(blockData instanceof SeaPickle)) + return; + SeaPickle pickleData = (SeaPickle) blockData; + + int newPickles = change; + + // Calculate new pickles value + switch (mode) { + case ADD: + case REMOVE: + newPickles += pickleData.getPickles(); + case SET: + if (newPickles != 0) { // 0 = delete pickles + newPickles = Math.max(pickleData.getMinimumPickles(), newPickles); // Ensure value isn't too low + newPickles = Math.min(pickleData.getMaximumPickles(), newPickles); // Ensure value isn't too high + } + break; + case RESET: + newPickles = pickleData.getMinimumPickles(); + } + + // Update the block data + if (newPickles != 0) { + pickleData.setPickles(newPickles); + block.setBlockData(pickleData); + } else { // We are removing the pickles :( + block.setType(pickleData.isWaterlogged() ? Material.WATER : Material.AIR); + } + } + } + + @Override + public Class<? extends Integer> getReturnType() { + return Integer.class; + } + + @Override + protected String getPropertyName() { + return (maximum ? "maximum " : minimum ? "minimum " : "") + "sea pickle count"; + } + +} From 01ef1fc65d0726100ccf9b44671db56839881365 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Sat, 21 Jan 2023 05:17:06 -0700 Subject: [PATCH 202/619] Allow the readme.txt to be generated in lang folder (#5385) Co-authored-by: Kenzie <admin@moderocky.com> --- src/main/java/ch/njol/skript/Skript.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 9909763239b..73ba8839e81 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -425,8 +425,7 @@ public void onEnable() { saveTo = new File(scriptsFolder, fileName); } else if (populateLanguageFiles && e.getName().startsWith("lang/") - && e.getName().endsWith(".lang") - && !e.getName().endsWith("/default.lang")) { + && !e.getName().endsWith("default.lang")) { String fileName = e.getName().substring(e.getName().lastIndexOf("/") + 1); saveTo = new File(lang, fileName); } else if (e.getName().equals("config.sk")) { From 0c21d4253026cdf05ddc3078d616ec3c3c6a9235 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Tue, 24 Jan 2023 00:45:11 -0500 Subject: [PATCH 203/619] Rewrite Converter System (#4939) --- src/main/java/ch/njol/skript/Skript.java | 6 +- .../njol/skript/classes/ChainedConverter.java | 4 +- .../ch/njol/skript/classes/Converter.java | 4 +- .../classes/data/DefaultConverters.java | 402 +++++--------- .../java/ch/njol/skript/command/Commands.java | 7 +- .../ch/njol/skript/config/EnumParser.java | 2 +- .../java/ch/njol/skript/config/Option.java | 2 +- .../njol/skript/entity/FallingBlockData.java | 4 +- .../njol/skript/entity/ThrownPotionData.java | 2 +- .../ch/njol/skript/expressions/ExprBlock.java | 4 +- .../ch/njol/skript/expressions/ExprChunk.java | 2 +- .../njol/skript/expressions/ExprColoured.java | 2 +- .../skript/expressions/ExprDefaultValue.java | 4 +- .../njol/skript/expressions/ExprFilter.java | 4 +- .../njol/skript/expressions/ExprGameMode.java | 2 +- .../skript/expressions/ExprLightLevel.java | 2 +- .../skript/expressions/ExprLoopValue.java | 6 +- .../njol/skript/expressions/ExprMetadata.java | 4 +- .../skript/expressions/ExprPassenger.java | 2 +- .../ch/njol/skript/expressions/ExprRound.java | 2 +- .../njol/skript/expressions/ExprShooter.java | 2 +- .../njol/skript/expressions/ExprTarget.java | 2 +- .../njol/skript/expressions/ExprTernary.java | 4 +- .../njol/skript/expressions/ExprTypeOf.java | 2 +- .../njol/skript/expressions/ExprVehicle.java | 2 +- .../ch/njol/skript/expressions/ExprWorld.java | 2 +- .../ch/njol/skript/expressions/ExprXOf.java | 20 - .../expressions/base/PropertyExpression.java | 4 +- .../base/SimplePropertyExpression.java | 2 +- .../expressions/base/WrapperExpression.java | 4 +- .../skript/hooks/economy/classes/Money.java | 4 +- .../skript/hooks/regions/classes/Region.java | 2 +- .../java/ch/njol/skript/lang/Expression.java | 2 +- .../java/ch/njol/skript/lang/Variable.java | 4 +- .../lang/function/ExprFunctionCall.java | 4 +- .../lang/function/FunctionReference.java | 2 +- .../skript/lang/util/ConvertedExpression.java | 10 +- .../skript/lang/util/ConvertedLiteral.java | 6 +- .../skript/lang/util/SimpleExpression.java | 2 +- .../njol/skript/lang/util/SimpleLiteral.java | 4 +- .../ch/njol/skript/registrations/Classes.java | 24 +- .../njol/skript/registrations/Converters.java | 270 ++-------- .../skript/registrations/EventValues.java | 3 +- .../java/ch/njol/skript/util/FileUtils.java | 2 +- src/main/java/ch/njol/skript/util/Getter.java | 2 +- .../ch/njol/skript/variables/Variables.java | 2 +- .../lang/converter/ChainedConverter.java | 65 +++ .../skript/lang/converter/Converter.java | 61 +++ .../skript/lang/converter/ConverterInfo.java | 62 +++ .../skript/lang/converter/Converters.java | 499 ++++++++++++++++++ .../skript/lang/converter/package-info.java | 23 + ...kup for object to x converters is wrong.sk | 11 + .../tests/syntaxes/expressions/ExprXOf.sk | 6 + 53 files changed, 999 insertions(+), 582 deletions(-) create mode 100644 src/main/java/org/skriptlang/skript/lang/converter/ChainedConverter.java create mode 100644 src/main/java/org/skriptlang/skript/lang/converter/Converter.java create mode 100644 src/main/java/org/skriptlang/skript/lang/converter/ConverterInfo.java create mode 100644 src/main/java/org/skriptlang/skript/lang/converter/Converters.java create mode 100644 src/main/java/org/skriptlang/skript/lang/converter/package-info.java create mode 100644 src/test/skript/tests/regressions/5054-converter lookup for object to x converters is wrong.sk diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 73ba8839e81..1b86ff51497 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -22,7 +22,7 @@ import ch.njol.skript.bukkitutil.BurgerHelper; import ch.njol.skript.classes.ClassInfo; import org.skriptlang.skript.lang.comparator.Comparator; -import ch.njol.skript.classes.Converter; +import org.skriptlang.skript.lang.converter.Converter; import ch.njol.skript.classes.data.BukkitClasses; import ch.njol.skript.classes.data.BukkitEventValues; import ch.njol.skript.classes.data.DefaultComparators; @@ -60,7 +60,7 @@ import ch.njol.skript.log.Verbosity; import ch.njol.skript.registrations.Classes; import org.skriptlang.skript.lang.comparator.Comparators; -import ch.njol.skript.registrations.Converters; +import org.skriptlang.skript.lang.converter.Converters; import ch.njol.skript.registrations.EventValues; import ch.njol.skript.tests.runner.SkriptTestEvent; import ch.njol.skript.tests.runner.TestMode; @@ -1220,7 +1220,7 @@ public static void checkAcceptRegistrations() { private static void stopAcceptingRegistrations() { acceptRegistrations = false; - Converters.createMissingConverters(); + Converters.createChainedConverters(); Classes.onRegistrationsStop(); } diff --git a/src/main/java/ch/njol/skript/classes/ChainedConverter.java b/src/main/java/ch/njol/skript/classes/ChainedConverter.java index df0c38b2c7d..07bf100dd2e 100644 --- a/src/main/java/ch/njol/skript/classes/ChainedConverter.java +++ b/src/main/java/ch/njol/skript/classes/ChainedConverter.java @@ -20,7 +20,7 @@ import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.registrations.Converters; +import org.skriptlang.skript.lang.converter.Converters; /** * Used to chain convertes to build a single converter. This is automatically created when a new converter is added. @@ -31,7 +31,9 @@ * @param <T> same as Converter's <T> (to) * @see Converters#registerConverter(Class, Class, Converter) * @see Converter + * @deprecated Use {@link org.skriptlang.skript.lang.converter.Converter} */ +@Deprecated public final class ChainedConverter<F, M, T> implements Converter<F, T> { private final Converter<? super F, ? extends M> first; diff --git a/src/main/java/ch/njol/skript/classes/Converter.java b/src/main/java/ch/njol/skript/classes/Converter.java index 824ee740f5e..d2162d6eccf 100644 --- a/src/main/java/ch/njol/skript/classes/Converter.java +++ b/src/main/java/ch/njol/skript/classes/Converter.java @@ -26,7 +26,7 @@ import ch.njol.skript.lang.Debuggable; import ch.njol.skript.registrations.Classes; -import ch.njol.skript.registrations.Converters; +import org.skriptlang.skript.lang.converter.Converters; /** * Converts data from type to another. @@ -34,7 +34,9 @@ * @param <F> The accepted type of objects to convert <b>f</b>rom * @param <T> The type to convert <b>t</b>o * @see Converters#registerConverter(Class, Class, Converter) + * @deprecated Use {@link org.skriptlang.skript.lang.converter.Converter} */ +@Deprecated public interface Converter<F, T> { /** diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java b/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java index 305c7db0b04..1ddbd0b7cbd 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java @@ -18,6 +18,17 @@ */ package ch.njol.skript.classes.data; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.command.Commands; +import ch.njol.skript.entity.EntityData; +import ch.njol.skript.entity.EntityType; +import ch.njol.skript.entity.XpOrbData; +import ch.njol.skript.util.BlockInventoryHolder; +import ch.njol.skript.util.BlockUtils; +import ch.njol.skript.util.Direction; +import ch.njol.skript.util.EnchantmentType; +import ch.njol.skript.util.Experience; +import ch.njol.skript.util.slot.Slot; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; @@ -38,282 +49,130 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; import org.bukkit.util.Vector; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.Skript; -import ch.njol.skript.aliases.ItemType; -import ch.njol.skript.classes.Converter; -import ch.njol.skript.entity.EntityData; -import ch.njol.skript.entity.EntityType; -import ch.njol.skript.entity.XpOrbData; -import ch.njol.skript.registrations.Converters; -import ch.njol.skript.util.BlockInventoryHolder; -import ch.njol.skript.util.BlockUtils; -import ch.njol.skript.util.Direction; -import ch.njol.skript.util.EnchantmentType; -import ch.njol.skript.util.Experience; -import ch.njol.skript.util.slot.Slot; +import org.skriptlang.skript.lang.converter.Converter; +import org.skriptlang.skript.lang.converter.Converters; -/** - * @author Peter Güttinger - */ -@SuppressWarnings("rawtypes") public class DefaultConverters { public DefaultConverters() {} static { + // Integer - Long Converters.registerConverter(Integer.class, Long.class, Integer::longValue); // OfflinePlayer - PlayerInventory - Converters.registerConverter(OfflinePlayer.class, PlayerInventory.class, new Converter<OfflinePlayer, PlayerInventory>() { - @Override - @Nullable - public PlayerInventory convert(final OfflinePlayer p) { - if (!p.isOnline()) - return null; - Player online = p.getPlayer(); - assert online != null; - return online.getInventory(); - } - }, Converter.NO_COMMAND_ARGUMENTS); + Converters.registerConverter(OfflinePlayer.class, PlayerInventory.class, p -> { + if (!p.isOnline()) + return null; + Player online = p.getPlayer(); + assert online != null; + return online.getInventory(); + }, Commands.CONVERTER_NO_COMMAND_ARGUMENTS); + // OfflinePlayer - Player - Converters.registerConverter(OfflinePlayer.class, Player.class, new Converter<OfflinePlayer, Player>() { - @Override - @Nullable - public Player convert(final OfflinePlayer p) { - return p.getPlayer(); - } - }, Converter.NO_COMMAND_ARGUMENTS); - - // TODO improve handling of interfaces + Converters.registerConverter(OfflinePlayer.class, Player.class, OfflinePlayer::getPlayer, Commands.CONVERTER_NO_COMMAND_ARGUMENTS); + // CommandSender - Player - Converters.registerConverter(CommandSender.class, Player.class, new Converter<CommandSender, Player>() { - @Override - @Nullable - public Player convert(final CommandSender s) { - if (s instanceof Player) - return (Player) s; - return null; - } + Converters.registerConverter(CommandSender.class, Player.class, s -> { + if (s instanceof Player) + return (Player) s; + return null; }); + // BlockCommandSender - Block - Converters.registerConverter(BlockCommandSender.class, Block.class, new Converter<BlockCommandSender, Block>() { - @Override - @Nullable - public Block convert(final BlockCommandSender s) { - return s.getBlock(); - } - }); + Converters.registerConverter(BlockCommandSender.class, Block.class, BlockCommandSender::getBlock); + // Entity - Player - Converters.registerConverter(Entity.class, Player.class, new Converter<Entity, Player>() { - @Override - @Nullable - public Player convert(final Entity e) { - if (e instanceof Player) - return (Player) e; - return null; - } + Converters.registerConverter(Entity.class, Player.class, e -> { + if (e instanceof Player) + return (Player) e; + return null; }); + // Entity - LivingEntity // Entity->Player is used if this doesn't exist - Converters.registerConverter(Entity.class, LivingEntity.class, new Converter<Entity, LivingEntity>() { - @Override - @Nullable - public LivingEntity convert(final Entity e) { - if (e instanceof LivingEntity) - return (LivingEntity) e; - return null; - } + Converters.registerConverter(Entity.class, LivingEntity.class, e -> { + if (e instanceof LivingEntity) + return (LivingEntity) e; + return null; }); // Block - Inventory - Converters.registerConverter(Block.class, Inventory.class, new Converter<Block, Inventory>() { - @Override - @Nullable - public Inventory convert(final Block b) { - if (b.getState() instanceof InventoryHolder) - return ((InventoryHolder) b.getState()).getInventory(); - return null; - } - }, Converter.NO_COMMAND_ARGUMENTS); + Converters.registerConverter(Block.class, Inventory.class, b -> { + if (b.getState() instanceof InventoryHolder) + return ((InventoryHolder) b.getState()).getInventory(); + return null; + }, Commands.CONVERTER_NO_COMMAND_ARGUMENTS); // Entity - Inventory - Converters.registerConverter(Entity.class, Inventory.class, new Converter<Entity, Inventory>() { - @Override - @Nullable - public Inventory convert(final Entity e) { - if (e instanceof InventoryHolder) - return ((InventoryHolder) e).getInventory(); - return null; - } - }, Converter.NO_COMMAND_ARGUMENTS); + Converters.registerConverter(Entity.class, Inventory.class, e -> { + if (e instanceof InventoryHolder) + return ((InventoryHolder) e).getInventory(); + return null; + }, Commands.CONVERTER_NO_COMMAND_ARGUMENTS); // Block - ItemType - Converters.registerConverter(Block.class, ItemType.class, new Converter<Block, ItemType>() { - @Override - public ItemType convert(final Block b) { - return new ItemType(b); - } - }, Converter.NO_LEFT_CHAINING | Converter.NO_COMMAND_ARGUMENTS); - - // Location - Block -// Converters.registerConverter(Location.class, Block.class, new Converter<Location, Block>() { -// @Override -// public Block convert(final Location l) { -// return l.getBlock(); -// } -// }); - Converters.registerConverter(Block.class, Location.class, new Converter<Block, Location>() { - @Override - @Nullable - public Location convert(final Block b) { - return BlockUtils.getLocation(b); - } - }, Converter.NO_COMMAND_ARGUMENTS); + Converters.registerConverter(Block.class, ItemType.class, ItemType::new, Converter.NO_LEFT_CHAINING | Commands.CONVERTER_NO_COMMAND_ARGUMENTS); + + // Block - Location + Converters.registerConverter(Block.class, Location.class, BlockUtils::getLocation, Commands.CONVERTER_NO_COMMAND_ARGUMENTS); // Entity - Location - Converters.registerConverter(Entity.class, Location.class, new Converter<Entity, Location>() { - @Override - @Nullable - public Location convert(final Entity e) { - return e.getLocation(); - } - }, Converter.NO_COMMAND_ARGUMENTS); + Converters.registerConverter(Entity.class, Location.class, Entity::getLocation, Commands.CONVERTER_NO_COMMAND_ARGUMENTS); + // Entity - EntityData - Converters.registerConverter(Entity.class, EntityData.class, new Converter<Entity, EntityData>() { - @Override - public EntityData convert(final Entity e) { - return EntityData.fromEntity(e); - } - }, Converter.NO_COMMAND_ARGUMENTS | Converter.NO_RIGHT_CHAINING); + Converters.registerConverter(Entity.class, EntityData.class, EntityData::fromEntity, Commands.CONVERTER_NO_COMMAND_ARGUMENTS | Converter.NO_RIGHT_CHAINING); + // EntityData - EntityType - Converters.registerConverter(EntityData.class, EntityType.class, new Converter<EntityData, EntityType>() { - @Override - public EntityType convert(final EntityData data) { - return new EntityType(data, -1); - } - }); - - // Location - World -// Skript.registerConverter(Location.class, World.class, new Converter<Location, World>() { -// private final static long serialVersionUID = 3270661123492313649L; -// -// @Override -// public World convert(final Location l) { -// if (l == null) -// return null; -// return l.getWorld(); -// } -// }); + Converters.registerConverter(EntityData.class, EntityType.class, data -> new EntityType(data, -1)); // ItemType - ItemStack - Converters.registerConverter(ItemType.class, ItemStack.class, new Converter<ItemType, ItemStack>() { - @Override - @Nullable - public ItemStack convert(final ItemType i) { - return i.getRandom(); - } - }); - Converters.registerConverter(ItemStack.class, ItemType.class, new Converter<ItemStack, ItemType>() { - @Override - public ItemType convert(final ItemStack i) { - return new ItemType(i); - } - }); + Converters.registerConverter(ItemType.class, ItemStack.class, ItemType::getRandom); + Converters.registerConverter(ItemStack.class, ItemType.class, ItemType::new); // Experience - XpOrbData - Converters.registerConverter(Experience.class, XpOrbData.class, new Converter<Experience, XpOrbData>() { - @Override - public XpOrbData convert(final Experience e) { - return new XpOrbData(e.getXP()); - } - }); - Converters.registerConverter(XpOrbData.class, Experience.class, new Converter<XpOrbData, Experience>() { - @Override - public Experience convert(final XpOrbData e) { - return new Experience(e.getExperience()); - } - }); - -// // Item - ItemStack -// Converters.registerConverter(Item.class, ItemStack.class, new Converter<Item, ItemStack>() { -// @Override -// public ItemStack convert(final Item i) { -// return i.getItemStack(); -// } -// }); + Converters.registerConverter(Experience.class, XpOrbData.class, e -> new XpOrbData(e.getXP())); + Converters.registerConverter(XpOrbData.class, Experience.class, e -> new Experience(e.getExperience())); // Slot - ItemType - Converters.registerConverter(Slot.class, ItemType.class, new Converter<Slot, ItemType>() { - @Override - public ItemType convert(final Slot s) { - final ItemStack i = s.getItem(); - return new ItemType(i != null ? i : new ItemStack(Material.AIR, 1)); - } + Converters.registerConverter(Slot.class, ItemType.class, s -> { + ItemStack i = s.getItem(); + return new ItemType(i != null ? i : new ItemStack(Material.AIR, 1)); }); -// // Slot - Inventory -// Skript.addConverter(Slot.class, Inventory.class, new Converter<Slot, Inventory>() { -// @Override -// public Inventory convert(final Slot s) { -// if (s == null) -// return null; -// return s.getInventory(); -// } -// }); // Block - InventoryHolder - Converters.registerConverter(Block.class, InventoryHolder.class, new Converter<Block, InventoryHolder>() { - @Override - @Nullable - public InventoryHolder convert(final Block b) { - final BlockState s = b.getState(); - if (s instanceof InventoryHolder) - return (InventoryHolder) s; - return null; - } - }, Converter.NO_RIGHT_CHAINING | Converter.NO_COMMAND_ARGUMENTS); - - Converters.registerConverter(InventoryHolder.class, Block.class, new Converter<InventoryHolder, Block>() { - @Override - @Nullable - public Block convert(final InventoryHolder holder) { - if (holder instanceof BlockState) - return new BlockInventoryHolder((BlockState) holder); - if (holder instanceof DoubleChest) - return holder.getInventory().getLocation().getBlock(); - return null; - } + Converters.registerConverter(Block.class, InventoryHolder.class, b -> { + BlockState s = b.getState(); + if (s instanceof InventoryHolder) + return (InventoryHolder) s; + return null; + }, Converter.NO_RIGHT_CHAINING | Commands.CONVERTER_NO_COMMAND_ARGUMENTS); + Converters.registerConverter(InventoryHolder.class, Block.class, holder -> { + if (holder instanceof BlockState) + return new BlockInventoryHolder((BlockState) holder); + if (holder instanceof DoubleChest) + return holder.getInventory().getLocation().getBlock(); + return null; }); - - Converters.registerConverter(InventoryHolder.class, Entity.class, new Converter<InventoryHolder, Entity>() { - @Override - @Nullable - public Entity convert(InventoryHolder holder) { - if (holder instanceof Entity) - return (Entity) holder; - return null; - } + + // InventoryHolder - Entity + Converters.registerConverter(InventoryHolder.class, Entity.class, holder -> { + if (holder instanceof Entity) + return (Entity) holder; + return null; }); -// // World - Time -// Skript.registerConverter(World.class, Time.class, new Converter<World, Time>() { -// @Override -// public Time convert(final World w) { -// if (w == null) -// return null; -// return new Time((int) w.getTime()); -// } -// }); - // Enchantment - EnchantmentType - Converters.registerConverter(Enchantment.class, EnchantmentType.class, new Converter<Enchantment, EnchantmentType>() { - @Override - public EnchantmentType convert(final Enchantment e) { - return new EnchantmentType(e, -1); - } - }); - + Converters.registerConverter(Enchantment.class, EnchantmentType.class, e -> new EnchantmentType(e, -1)); + + // Vector - Direction + Converters.registerConverter(Vector.class, Direction.class, Direction::new); + + // EnchantmentOffer - EnchantmentType + Converters.registerConverter(EnchantmentOffer.class, EnchantmentType.class, eo -> new EnchantmentType(eo.getEnchantment(), eo.getEnchantmentLevel())); + + Converters.registerConverter(String.class, World.class, Bukkit::getWorld); + // // Entity - String (UUID) // Very slow, thus disabled for now // Converters.registerConverter(String.class, Entity.class, new Converter<String, Entity>() { // @@ -325,12 +184,12 @@ public EnchantmentType convert(final Enchantment e) { // if (p.getName().equals(f) || p.getUniqueId().toString().equals(f)) // return p; // } -// +// // return null; // } -// +// // }); - + // Number - Vector; DISABLED due to performance problems // Converters.registerConverter(Number.class, Vector.class, new Converter<Number, Vector>() { // @Override @@ -340,25 +199,54 @@ public EnchantmentType convert(final Enchantment e) { // } // }); - // Vector - Direction - Converters.registerConverter(Vector.class, Direction.class, new Converter<Vector, Direction>() { - @Override - @Nullable - public Direction convert(Vector vector) { - return new Direction(vector); - } - }); - - // EnchantmentOffer Converters - // EnchantmentOffer - EnchantmentType - Converters.registerConverter(EnchantmentOffer.class, EnchantmentType.class, new Converter<EnchantmentOffer, EnchantmentType>() { - @Nullable - @Override - public EnchantmentType convert(EnchantmentOffer eo) { - return new EnchantmentType(eo.getEnchantment(), eo.getEnchantmentLevel()); - } - }); +// // World - Time +// Skript.registerConverter(World.class, Time.class, new Converter<World, Time>() { +// @Override +// public Time convert(final World w) { +// if (w == null) +// return null; +// return new Time((int) w.getTime()); +// } +// }); + +// // Slot - Inventory +// Skript.addConverter(Slot.class, Inventory.class, new Converter<Slot, Inventory>() { +// @Override +// public Inventory convert(final Slot s) { +// if (s == null) +// return null; +// return s.getInventory(); +// } +// }); + +// // Item - ItemStack +// Converters.registerConverter(Item.class, ItemStack.class, new Converter<Item, ItemStack>() { +// @Override +// public ItemStack convert(final Item i) { +// return i.getItemStack(); +// } +// }); + + // Location - World +// Skript.registerConverter(Location.class, World.class, new Converter<Location, World>() { +// private final static long serialVersionUID = 3270661123492313649L; +// +// @Override +// public World convert(final Location l) { +// if (l == null) +// return null; +// return l.getWorld(); +// } +// }); + + // Location - Block +// Converters.registerConverter(Location.class, Block.class, new Converter<Location, Block>() { +// @Override +// public Block convert(final Location l) { +// return l.getBlock(); +// } +// }); - Converters.registerConverter(String.class, World.class, Bukkit::getWorld); } + } diff --git a/src/main/java/ch/njol/skript/command/Commands.java b/src/main/java/ch/njol/skript/command/Commands.java index d55b427d264..87801b2cfd1 100644 --- a/src/main/java/ch/njol/skript/command/Commands.java +++ b/src/main/java/ch/njol/skript/command/Commands.java @@ -75,7 +75,12 @@ public abstract class Commands { public final static ArgsMessage m_too_many_arguments = new ArgsMessage("commands.too many arguments"); public final static Message m_internal_error = new Message("commands.internal error"); public final static Message m_correct_usage = new Message("commands.correct usage"); - + + /** + * A Converter flag declaring that a Converter cannot be used for parsing command arguments. + */ + public static final int CONVERTER_NO_COMMAND_ARGUMENTS = 4; + private final static Map<String, ScriptCommand> commands = new HashMap<>(); @Nullable diff --git a/src/main/java/ch/njol/skript/config/EnumParser.java b/src/main/java/ch/njol/skript/config/EnumParser.java index c4da65c82d2..4fb52b133ff 100644 --- a/src/main/java/ch/njol/skript/config/EnumParser.java +++ b/src/main/java/ch/njol/skript/config/EnumParser.java @@ -23,7 +23,7 @@ import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; -import ch.njol.skript.classes.Converter; +import org.skriptlang.skript.lang.converter.Converter; /** * @author Peter Güttinger diff --git a/src/main/java/ch/njol/skript/config/Option.java b/src/main/java/ch/njol/skript/config/Option.java index 5a1741b6efe..13a9f113cf6 100644 --- a/src/main/java/ch/njol/skript/config/Option.java +++ b/src/main/java/ch/njol/skript/config/Option.java @@ -24,7 +24,7 @@ import ch.njol.skript.Skript; import ch.njol.skript.classes.ClassInfo; -import ch.njol.skript.classes.Converter; +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; diff --git a/src/main/java/ch/njol/skript/entity/FallingBlockData.java b/src/main/java/ch/njol/skript/entity/FallingBlockData.java index 46c8b18e5a5..b3dcfbbcadf 100644 --- a/src/main/java/ch/njol/skript/entity/FallingBlockData.java +++ b/src/main/java/ch/njol/skript/entity/FallingBlockData.java @@ -31,7 +31,7 @@ import ch.njol.skript.aliases.ItemData; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.bukkitutil.block.BlockCompat; -import ch.njol.skript.classes.Converter; +import org.skriptlang.skript.lang.converter.Converter; import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.localization.Adjective; @@ -39,7 +39,7 @@ import ch.njol.skript.localization.Message; import ch.njol.skript.localization.Noun; import ch.njol.skript.registrations.Classes; -import ch.njol.skript.registrations.Converters; +import org.skriptlang.skript.lang.converter.Converters; import ch.njol.util.coll.CollectionUtils; /** diff --git a/src/main/java/ch/njol/skript/entity/ThrownPotionData.java b/src/main/java/ch/njol/skript/entity/ThrownPotionData.java index 632f2dc9ff6..2f60014a310 100644 --- a/src/main/java/ch/njol/skript/entity/ThrownPotionData.java +++ b/src/main/java/ch/njol/skript/entity/ThrownPotionData.java @@ -37,7 +37,7 @@ import ch.njol.skript.localization.Language; import ch.njol.skript.localization.Noun; import ch.njol.skript.registrations.Classes; -import ch.njol.skript.registrations.Converters; +import org.skriptlang.skript.lang.converter.Converters; import ch.njol.util.coll.CollectionUtils; /** diff --git a/src/main/java/ch/njol/skript/expressions/ExprBlock.java b/src/main/java/ch/njol/skript/expressions/ExprBlock.java index 50b3d585f76..56ea8b4802d 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprBlock.java +++ b/src/main/java/ch/njol/skript/expressions/ExprBlock.java @@ -24,8 +24,8 @@ import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; -import ch.njol.skript.classes.Converter; -import ch.njol.skript.classes.Converter.ConverterInfo; +import org.skriptlang.skript.lang.converter.Converter; +import org.skriptlang.skript.lang.converter.ConverterInfo; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; diff --git a/src/main/java/ch/njol/skript/expressions/ExprChunk.java b/src/main/java/ch/njol/skript/expressions/ExprChunk.java index d82545200ed..666d5d5f9c7 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprChunk.java +++ b/src/main/java/ch/njol/skript/expressions/ExprChunk.java @@ -25,7 +25,7 @@ import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer.ChangeMode; -import ch.njol.skript.classes.Converter; +import org.skriptlang.skript.lang.converter.Converter; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; diff --git a/src/main/java/ch/njol/skript/expressions/ExprColoured.java b/src/main/java/ch/njol/skript/expressions/ExprColoured.java index 2ab501d5d9c..5736ec1c06d 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprColoured.java +++ b/src/main/java/ch/njol/skript/expressions/ExprColoured.java @@ -22,7 +22,7 @@ import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; -import ch.njol.skript.classes.Converter; +import org.skriptlang.skript.lang.converter.Converter; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; diff --git a/src/main/java/ch/njol/skript/expressions/ExprDefaultValue.java b/src/main/java/ch/njol/skript/expressions/ExprDefaultValue.java index ca8ff0cbc90..a146d615380 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprDefaultValue.java +++ b/src/main/java/ch/njol/skript/expressions/ExprDefaultValue.java @@ -27,7 +27,7 @@ import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser; import ch.njol.skript.lang.util.SimpleExpression; -import ch.njol.skript.registrations.Converters; +import org.skriptlang.skript.lang.converter.Converters; import ch.njol.skript.util.LiteralUtils; import ch.njol.skript.util.Utils; import ch.njol.util.Kleenean; @@ -85,7 +85,7 @@ protected T[] get(Event e) { Object[] first = this.first.getArray(e); Object values[] = first.length != 0 ? first : second.getArray(e); try { - return Converters.convertArray(values, types, superType); + return Converters.convert(values, types, superType); } catch (ClassCastException e1) { return (T[]) Array.newInstance(superType, 0); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprFilter.java b/src/main/java/ch/njol/skript/expressions/ExprFilter.java index 87ae73d5002..239b720e418 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprFilter.java +++ b/src/main/java/ch/njol/skript/expressions/ExprFilter.java @@ -30,7 +30,7 @@ import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; -import ch.njol.skript.registrations.Converters; +import org.skriptlang.skript.lang.converter.Converters; import ch.njol.skript.util.LiteralUtils; import ch.njol.skript.util.Utils; import ch.njol.util.Kleenean; @@ -217,7 +217,7 @@ protected T[] get(Event event) { } try { - return Converters.convertArray(new Object[]{current}, types, superType); + return Converters.convert(new Object[]{current}, types, superType); } catch (ClassCastException e1) { return (T[]) Array.newInstance(superType, 0); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprGameMode.java b/src/main/java/ch/njol/skript/expressions/ExprGameMode.java index e2954a31534..8e9dc4e9b17 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprGameMode.java +++ b/src/main/java/ch/njol/skript/expressions/ExprGameMode.java @@ -28,7 +28,7 @@ import ch.njol.skript.ScriptLoader; import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer.ChangeMode; -import ch.njol.skript.classes.Converter; +import org.skriptlang.skript.lang.converter.Converter; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; diff --git a/src/main/java/ch/njol/skript/expressions/ExprLightLevel.java b/src/main/java/ch/njol/skript/expressions/ExprLightLevel.java index 324f897b8fd..d8580bdb130 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprLightLevel.java +++ b/src/main/java/ch/njol/skript/expressions/ExprLightLevel.java @@ -24,7 +24,7 @@ import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; -import ch.njol.skript.classes.Converter; +import org.skriptlang.skript.lang.converter.Converter; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; diff --git a/src/main/java/ch/njol/skript/expressions/ExprLoopValue.java b/src/main/java/ch/njol/skript/expressions/ExprLoopValue.java index f0c893fe44f..4ef78dd68c6 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprLoopValue.java +++ b/src/main/java/ch/njol/skript/expressions/ExprLoopValue.java @@ -19,8 +19,8 @@ package ch.njol.skript.expressions; import ch.njol.skript.Skript; -import ch.njol.skript.classes.Converter; -import ch.njol.skript.classes.Converter.ConverterInfo; +import org.skriptlang.skript.lang.converter.Converter; +import org.skriptlang.skript.lang.converter.ConverterInfo; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; @@ -33,7 +33,7 @@ import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.skript.log.ErrorQuality; import ch.njol.skript.registrations.Classes; -import ch.njol.skript.registrations.Converters; +import org.skriptlang.skript.lang.converter.Converters; import ch.njol.skript.sections.SecLoop; import ch.njol.skript.util.Utils; import ch.njol.util.Kleenean; diff --git a/src/main/java/ch/njol/skript/expressions/ExprMetadata.java b/src/main/java/ch/njol/skript/expressions/ExprMetadata.java index 8cd697dc4a2..3cf5ccff8f0 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprMetadata.java +++ b/src/main/java/ch/njol/skript/expressions/ExprMetadata.java @@ -38,7 +38,7 @@ import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser; import ch.njol.skript.lang.util.SimpleExpression; -import ch.njol.skript.registrations.Converters; +import org.skriptlang.skript.lang.converter.Converters; import ch.njol.skript.util.Utils; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; @@ -101,7 +101,7 @@ protected T[] get(Event e) { } } try { - return Converters.convertArray(values.toArray(), types, superType); + return Converters.convert(values.toArray(), types, superType); } catch (ClassCastException e1) { return (T[]) Array.newInstance(superType, 0); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprPassenger.java b/src/main/java/ch/njol/skript/expressions/ExprPassenger.java index 1ecb1839e8f..fcb174224af 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprPassenger.java +++ b/src/main/java/ch/njol/skript/expressions/ExprPassenger.java @@ -31,7 +31,7 @@ import ch.njol.skript.Skript; import ch.njol.skript.bukkitutil.PassengerUtils; import ch.njol.skript.classes.Changer.ChangeMode; -import ch.njol.skript.classes.Converter; +import org.skriptlang.skript.lang.converter.Converter; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; diff --git a/src/main/java/ch/njol/skript/expressions/ExprRound.java b/src/main/java/ch/njol/skript/expressions/ExprRound.java index 2728b7a4cc1..7fc7cf7f349 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprRound.java +++ b/src/main/java/ch/njol/skript/expressions/ExprRound.java @@ -22,7 +22,7 @@ import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; -import ch.njol.skript.classes.Converter; +import org.skriptlang.skript.lang.converter.Converter; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; diff --git a/src/main/java/ch/njol/skript/expressions/ExprShooter.java b/src/main/java/ch/njol/skript/expressions/ExprShooter.java index d703aab7c14..b7589c9be2a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprShooter.java +++ b/src/main/java/ch/njol/skript/expressions/ExprShooter.java @@ -26,7 +26,7 @@ import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer.ChangeMode; -import ch.njol.skript.classes.Converter; +import org.skriptlang.skript.lang.converter.Converter; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; diff --git a/src/main/java/ch/njol/skript/expressions/ExprTarget.java b/src/main/java/ch/njol/skript/expressions/ExprTarget.java index c3852a873ee..0bdef493a37 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTarget.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTarget.java @@ -30,7 +30,7 @@ import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer.ChangeMode; -import ch.njol.skript.classes.Converter; +import org.skriptlang.skript.lang.converter.Converter; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; diff --git a/src/main/java/ch/njol/skript/expressions/ExprTernary.java b/src/main/java/ch/njol/skript/expressions/ExprTernary.java index b3f2058024c..3992f308623 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTernary.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTernary.java @@ -28,7 +28,7 @@ import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser; import ch.njol.skript.lang.util.SimpleExpression; -import ch.njol.skript.registrations.Converters; +import org.skriptlang.skript.lang.converter.Converters; import ch.njol.skript.util.LiteralUtils; import ch.njol.skript.util.Utils; import ch.njol.util.Kleenean; @@ -95,7 +95,7 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye protected T[] get(Event e) { Object[] values = condition.check(e) ? ifTrue.getArray(e) : ifFalse.getArray(e); try { - return Converters.convertArray(values, types, superType); + return Converters.convert(values, types, superType); } catch (ClassCastException e1) { return (T[]) Array.newInstance(superType, 0); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprTypeOf.java b/src/main/java/ch/njol/skript/expressions/ExprTypeOf.java index 4f901e90407..ed62d174d63 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTypeOf.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTypeOf.java @@ -26,7 +26,7 @@ import ch.njol.skript.entity.EntityData; import ch.njol.skript.expressions.base.SimplePropertyExpression; import ch.njol.skript.lang.util.ConvertedExpression; -import ch.njol.skript.registrations.Converters; +import org.skriptlang.skript.lang.converter.Converters; import org.bukkit.block.data.BlockData; import org.bukkit.inventory.Inventory; import org.bukkit.potion.PotionEffect; diff --git a/src/main/java/ch/njol/skript/expressions/ExprVehicle.java b/src/main/java/ch/njol/skript/expressions/ExprVehicle.java index ea0313ff896..d13e4a428d3 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVehicle.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVehicle.java @@ -28,7 +28,7 @@ import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer.ChangeMode; -import ch.njol.skript.classes.Converter; +import org.skriptlang.skript.lang.converter.Converter; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; diff --git a/src/main/java/ch/njol/skript/expressions/ExprWorld.java b/src/main/java/ch/njol/skript/expressions/ExprWorld.java index fbf71f3400c..127109e0b4a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprWorld.java +++ b/src/main/java/ch/njol/skript/expressions/ExprWorld.java @@ -28,7 +28,7 @@ import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer.ChangeMode; -import ch.njol.skript.classes.Converter; +import org.skriptlang.skript.lang.converter.Converter; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; diff --git a/src/main/java/ch/njol/skript/expressions/ExprXOf.java b/src/main/java/ch/njol/skript/expressions/ExprXOf.java index 1fc496ab22b..4f352936e9a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprXOf.java +++ b/src/main/java/ch/njol/skript/expressions/ExprXOf.java @@ -81,26 +81,6 @@ protected Object[] get(Event e, Object[] source) { }); } - @Override - @Nullable - @SuppressWarnings("unchecked") - public <R> Expression<? extends R> getConvertedExpression(Class<R>... to) { - if (CollectionUtils.containsSuperclass(to, getReturnType())) - return (Expression<? extends R>) this; - - if (!CollectionUtils.containsAnySuperclass(to, ItemStack.class, ItemType.class, EntityType.class)) - return null; - - Expression<? extends R> converted = getExpr().getConvertedExpression(to); - if (converted == null) - return null; - - ExprXOf exprXOf = new ExprXOf(); - exprXOf.setExpr(converted); - exprXOf.amount = amount; - return (Expression<? extends R>) exprXOf; - } - @Override public Class<?> getReturnType() { return getExpr().getReturnType(); diff --git a/src/main/java/ch/njol/skript/expressions/base/PropertyExpression.java b/src/main/java/ch/njol/skript/expressions/base/PropertyExpression.java index 30b37198fe3..994eb9fe1fa 100644 --- a/src/main/java/ch/njol/skript/expressions/base/PropertyExpression.java +++ b/src/main/java/ch/njol/skript/expressions/base/PropertyExpression.java @@ -21,13 +21,13 @@ import org.bukkit.event.Event; import ch.njol.skript.Skript; -import ch.njol.skript.classes.Converter; +import org.skriptlang.skript.lang.converter.Converter; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.SyntaxElement; import ch.njol.skript.lang.util.SimpleExpression; -import ch.njol.skript.registrations.Converters; +import org.skriptlang.skript.lang.converter.Converters; import ch.njol.util.Kleenean; /** diff --git a/src/main/java/ch/njol/skript/expressions/base/SimplePropertyExpression.java b/src/main/java/ch/njol/skript/expressions/base/SimplePropertyExpression.java index d855054781c..7b23e10d8d5 100644 --- a/src/main/java/ch/njol/skript/expressions/base/SimplePropertyExpression.java +++ b/src/main/java/ch/njol/skript/expressions/base/SimplePropertyExpression.java @@ -21,7 +21,7 @@ import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.classes.Converter; +import org.skriptlang.skript.lang.converter.Converter; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; diff --git a/src/main/java/ch/njol/skript/expressions/base/WrapperExpression.java b/src/main/java/ch/njol/skript/expressions/base/WrapperExpression.java index 672c71b03c9..652ff72fa1f 100644 --- a/src/main/java/ch/njol/skript/expressions/base/WrapperExpression.java +++ b/src/main/java/ch/njol/skript/expressions/base/WrapperExpression.java @@ -24,13 +24,13 @@ import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.classes.Changer.ChangeMode; -import ch.njol.skript.classes.Converter.ConverterInfo; +import org.skriptlang.skript.lang.converter.ConverterInfo; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.SyntaxElement; import ch.njol.skript.lang.util.ConvertedExpression; import ch.njol.skript.lang.util.SimpleExpression; -import ch.njol.skript.registrations.Converters; +import org.skriptlang.skript.lang.converter.Converters; import ch.njol.util.Kleenean; /** diff --git a/src/main/java/ch/njol/skript/hooks/economy/classes/Money.java b/src/main/java/ch/njol/skript/hooks/economy/classes/Money.java index 0fe60da1612..a9c759a32d9 100644 --- a/src/main/java/ch/njol/skript/hooks/economy/classes/Money.java +++ b/src/main/java/ch/njol/skript/hooks/economy/classes/Money.java @@ -25,13 +25,13 @@ import ch.njol.skript.classes.Arithmetic; import ch.njol.skript.classes.ClassInfo; import org.skriptlang.skript.lang.comparator.Comparator; -import ch.njol.skript.classes.Converter; +import org.skriptlang.skript.lang.converter.Converter; import ch.njol.skript.classes.Parser; import ch.njol.skript.hooks.VaultHook; import ch.njol.skript.lang.ParseContext; import ch.njol.skript.registrations.Classes; import org.skriptlang.skript.lang.comparator.Comparators; -import ch.njol.skript.registrations.Converters; +import org.skriptlang.skript.lang.converter.Converters; import ch.njol.util.StringUtils; import org.skriptlang.skript.lang.comparator.Relation; diff --git a/src/main/java/ch/njol/skript/hooks/regions/classes/Region.java b/src/main/java/ch/njol/skript/hooks/regions/classes/Region.java index ad6f1af6bcb..1377337ede1 100644 --- a/src/main/java/ch/njol/skript/hooks/regions/classes/Region.java +++ b/src/main/java/ch/njol/skript/hooks/regions/classes/Region.java @@ -26,7 +26,7 @@ import ch.njol.skript.lang.ParseContext; import ch.njol.skript.lang.VariableString; import ch.njol.skript.registrations.Classes; -import ch.njol.skript.registrations.Converters; +import org.skriptlang.skript.lang.converter.Converters; import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilExtendedSerializable; import org.bukkit.Bukkit; import org.bukkit.Location; diff --git a/src/main/java/ch/njol/skript/lang/Expression.java b/src/main/java/ch/njol/skript/lang/Expression.java index d7ce5095d69..dd649b6376e 100644 --- a/src/main/java/ch/njol/skript/lang/Expression.java +++ b/src/main/java/ch/njol/skript/lang/Expression.java @@ -33,7 +33,7 @@ import ch.njol.skript.classes.Changer; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.classes.Changer.ChangerUtils; -import ch.njol.skript.classes.Converter; +import org.skriptlang.skript.lang.converter.Converter; import ch.njol.skript.conditions.CondIsSet; import ch.njol.skript.lang.util.ConvertedExpression; import ch.njol.skript.lang.util.SimpleExpression; diff --git a/src/main/java/ch/njol/skript/lang/Variable.java b/src/main/java/ch/njol/skript/lang/Variable.java index ad319e8bacc..94bf8f44725 100644 --- a/src/main/java/ch/njol/skript/lang/Variable.java +++ b/src/main/java/ch/njol/skript/lang/Variable.java @@ -34,7 +34,7 @@ import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.skript.registrations.Classes; import org.skriptlang.skript.lang.comparator.Comparators; -import ch.njol.skript.registrations.Converters; +import org.skriptlang.skript.lang.converter.Converters; import ch.njol.skript.util.StringMode; import ch.njol.skript.util.Utils; import ch.njol.skript.variables.TypeHints; @@ -458,7 +458,7 @@ private T getConverted(Event e) { private T[] getConvertedArray(Event e) { assert list; - return Converters.convertArray((Object[]) get(e), types, superType); + return Converters.convert((Object[]) get(e), types, superType); } private void set(Event e, @Nullable Object value) { diff --git a/src/main/java/ch/njol/skript/lang/function/ExprFunctionCall.java b/src/main/java/ch/njol/skript/lang/function/ExprFunctionCall.java index e585c3521e3..09d9966c586 100644 --- a/src/main/java/ch/njol/skript/lang/function/ExprFunctionCall.java +++ b/src/main/java/ch/njol/skript/lang/function/ExprFunctionCall.java @@ -21,7 +21,7 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; -import ch.njol.skript.registrations.Converters; +import org.skriptlang.skript.lang.converter.Converters; import ch.njol.skript.util.Utils; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; @@ -59,7 +59,7 @@ public ExprFunctionCall(FunctionReference<?> function, Class<? extends T>[] expe protected T[] get(Event e) { Object[] returnValue = function.execute(e); function.resetReturnValue(); - return Converters.convertArray(returnValue, returnTypes, returnType); + return Converters.convert(returnValue, returnTypes, returnType); } @Override diff --git a/src/main/java/ch/njol/skript/lang/function/FunctionReference.java b/src/main/java/ch/njol/skript/lang/function/FunctionReference.java index 4d1dc27b40a..723b8987d9a 100644 --- a/src/main/java/ch/njol/skript/lang/function/FunctionReference.java +++ b/src/main/java/ch/njol/skript/lang/function/FunctionReference.java @@ -28,7 +28,7 @@ import ch.njol.skript.log.RetainingLogHandler; import ch.njol.skript.log.SkriptLogger; import ch.njol.skript.registrations.Classes; -import ch.njol.skript.registrations.Converters; +import org.skriptlang.skript.lang.converter.Converters; import ch.njol.skript.util.LiteralUtils; import ch.njol.util.StringUtils; import org.bukkit.event.Event; diff --git a/src/main/java/ch/njol/skript/lang/util/ConvertedExpression.java b/src/main/java/ch/njol/skript/lang/util/ConvertedExpression.java index 1e9fdbb1b5e..0d66cca4dc6 100644 --- a/src/main/java/ch/njol/skript/lang/util/ConvertedExpression.java +++ b/src/main/java/ch/njol/skript/lang/util/ConvertedExpression.java @@ -27,12 +27,12 @@ import ch.njol.skript.classes.Changer; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.classes.ClassInfo; -import ch.njol.skript.classes.Converter; -import ch.njol.skript.classes.Converter.ConverterInfo; +import org.skriptlang.skript.lang.converter.Converter; +import org.skriptlang.skript.lang.converter.ConverterInfo; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.registrations.Classes; -import ch.njol.skript.registrations.Converters; +import org.skriptlang.skript.lang.converter.Converters; import ch.njol.util.Checker; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; @@ -65,7 +65,7 @@ public ConvertedExpression(Expression<? extends F> source, Class<T> to, Converte this.source = source; this.to = to; - this.conv = info.converter; + this.conv = info.getConverter(); this.converterInfo = info; } @@ -95,7 +95,7 @@ public final boolean init(final Expression<?>[] vars, final int matchedPattern, public String toString(final @Nullable Event e, final boolean debug) { if (debug && e == null) return "(" + source.toString(e, debug) + " >> " + conv + ": " - + converterInfo.toString(e, true) + ")"; + + converterInfo + ")"; return source.toString(e, debug); } diff --git a/src/main/java/ch/njol/skript/lang/util/ConvertedLiteral.java b/src/main/java/ch/njol/skript/lang/util/ConvertedLiteral.java index 2b11c39be68..2765dd42a2e 100644 --- a/src/main/java/ch/njol/skript/lang/util/ConvertedLiteral.java +++ b/src/main/java/ch/njol/skript/lang/util/ConvertedLiteral.java @@ -24,11 +24,11 @@ import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.SkriptAPIException; -import ch.njol.skript.classes.Converter; -import ch.njol.skript.classes.Converter.ConverterInfo; +import org.skriptlang.skript.lang.converter.Converter; +import org.skriptlang.skript.lang.converter.ConverterInfo; import ch.njol.skript.lang.Literal; import ch.njol.skript.registrations.Classes; -import ch.njol.skript.registrations.Converters; +import org.skriptlang.skript.lang.converter.Converters; import ch.njol.util.Checker; import ch.njol.util.coll.CollectionUtils; import ch.njol.util.coll.iterator.ArrayIterator; diff --git a/src/main/java/ch/njol/skript/lang/util/SimpleExpression.java b/src/main/java/ch/njol/skript/lang/util/SimpleExpression.java index 98bda113eac..df057711941 100644 --- a/src/main/java/ch/njol/skript/lang/util/SimpleExpression.java +++ b/src/main/java/ch/njol/skript/lang/util/SimpleExpression.java @@ -30,7 +30,7 @@ import ch.njol.skript.classes.Changer; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.classes.ClassInfo; -import ch.njol.skript.classes.Converter; +import org.skriptlang.skript.lang.converter.Converter; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.registrations.Classes; diff --git a/src/main/java/ch/njol/skript/lang/util/SimpleLiteral.java b/src/main/java/ch/njol/skript/lang/util/SimpleLiteral.java index dbae50ae0c5..b15e2943c48 100644 --- a/src/main/java/ch/njol/skript/lang/util/SimpleLiteral.java +++ b/src/main/java/ch/njol/skript/lang/util/SimpleLiteral.java @@ -32,7 +32,7 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.UnparsedLiteral; import ch.njol.skript.registrations.Classes; -import ch.njol.skript.registrations.Converters; +import org.skriptlang.skript.lang.converter.Converters; import ch.njol.skript.util.StringMode; import ch.njol.skript.util.Utils; import ch.njol.util.Checker; @@ -139,7 +139,7 @@ public Class<T> getReturnType() { public <R> Literal<? extends R> getConvertedExpression(final Class<R>... to) { if (CollectionUtils.containsSuperclass(to, c)) return (Literal<? extends R>) this; - final R[] parsedData = Converters.convertArray(data, to, (Class<R>) Utils.getSuperType(to)); + final R[] parsedData = Converters.convert(data, to, (Class<R>) Utils.getSuperType(to)); if (parsedData.length != data.length) return null; return new ConvertedLiteral<>(this, parsedData, (Class<R>) Utils.getSuperType(to)); diff --git a/src/main/java/ch/njol/skript/registrations/Classes.java b/src/main/java/ch/njol/skript/registrations/Classes.java index 46a80c13c60..5f1bc476be9 100644 --- a/src/main/java/ch/njol/skript/registrations/Classes.java +++ b/src/main/java/ch/njol/skript/registrations/Classes.java @@ -37,6 +37,7 @@ import java.util.Set; import java.util.regex.Pattern; +import ch.njol.skript.command.Commands; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Chunk; @@ -46,8 +47,6 @@ import ch.njol.skript.SkriptAPIException; import ch.njol.skript.SkriptConfig; import ch.njol.skript.classes.ClassInfo; -import ch.njol.skript.classes.Converter; -import ch.njol.skript.classes.Converter.ConverterInfo; import ch.njol.skript.classes.Parser; import ch.njol.skript.classes.Serializer; import ch.njol.skript.lang.DefaultExpression; @@ -66,6 +65,9 @@ import ch.njol.yggdrasil.YggdrasilInputStream; import ch.njol.yggdrasil.YggdrasilOutputStream; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.skriptlang.skript.lang.converter.Converter; +import org.skriptlang.skript.lang.converter.ConverterInfo; +import org.skriptlang.skript.lang.converter.Converters; /** * @author Peter Güttinger @@ -475,14 +477,14 @@ public static <T> T parse(final String s, final Class<T> c, final ParseContext c log.printLog(); return t; } - for (final ConverterInfo<?, ?> conv : Converters.getConverters()) { - if (context == ParseContext.COMMAND && (conv.options & Converter.NO_COMMAND_ARGUMENTS) != 0) + for (final ConverterInfo<?, ?> conv : org.skriptlang.skript.lang.converter.Converters.getConverterInfo()) { + if (context == ParseContext.COMMAND && (conv.getFlags() & Commands.CONVERTER_NO_COMMAND_ARGUMENTS) != 0) continue; - if (c.isAssignableFrom(conv.to)) { + if (c.isAssignableFrom(conv.getTo())) { log.clear(); - final Object o = parseSimple(s, conv.from, context); + final Object o = parseSimple(s, conv.getFrom(), context); if (o != null) { - t = (T) ((Converter) conv.converter).convert(o); + t = (T) ((Converter) conv.getConverter()).convert(o); if (t != null) { log.printLog(); return t; @@ -515,13 +517,13 @@ public static <T> Parser<? extends T> getParser(final Class<T> to) { if (to.isAssignableFrom(ci.getC()) && ci.getParser() != null) return (Parser<? extends T>) ci.getParser(); } - for (final ConverterInfo<?, ?> conv : Converters.getConverters()) { - if (to.isAssignableFrom(conv.to)) { + for (final ConverterInfo<?, ?> conv : org.skriptlang.skript.lang.converter.Converters.getConverterInfo()) { + if (to.isAssignableFrom(conv.getTo())) { for (int i = classInfos.length - 1; i >= 0; i--) { final ClassInfo<?> ci = classInfos[i]; final Parser<?> parser = ci.getParser(); - if (conv.from.isAssignableFrom(ci.getC()) && parser != null) - return Classes.createConvertedParser(parser, (Converter<?, ? extends T>) conv.converter); + if (conv.getFrom().isAssignableFrom(ci.getC()) && parser != null) + return Classes.createConvertedParser(parser, (Converter<?, ? extends T>) conv.getConverter()); } } } diff --git a/src/main/java/ch/njol/skript/registrations/Converters.java b/src/main/java/ch/njol/skript/registrations/Converters.java index 87ee94655c2..ae5282eeb5c 100644 --- a/src/main/java/ch/njol/skript/registrations/Converters.java +++ b/src/main/java/ch/njol/skript/registrations/Converters.java @@ -18,35 +18,30 @@ */ package ch.njol.skript.registrations; -import java.lang.reflect.Array; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.Skript; -import ch.njol.skript.classes.ChainedConverter; import ch.njol.skript.classes.Converter; import ch.njol.skript.classes.Converter.ConverterInfo; -import ch.njol.skript.classes.Converter.ConverterUtils; -import ch.njol.util.Pair; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.List; +import java.util.stream.Collectors; /** * Contains all registered converters and allows operating with them. + * @deprecated Use {@link org.skriptlang.skript.lang.converter.Converters} */ +@Deprecated public abstract class Converters { private Converters() {} - private static List<ConverterInfo<?, ?>> converters = new ArrayList<>(50); - - @SuppressWarnings("null") - public static List<ConverterInfo<?, ?>> getConverters() { - return Collections.unmodifiableList(converters); + @SuppressWarnings("unchecked") + public static <F, T> List<ConverterInfo<?, ?>> getConverters() { + return org.skriptlang.skript.lang.converter.Converters.getConverterInfo().stream() + .map(unknownInfo -> { + org.skriptlang.skript.lang.converter.ConverterInfo<F, T> info = (org.skriptlang.skript.lang.converter.ConverterInfo<F, T>) unknownInfo; + return new ConverterInfo<>(info.getFrom(), info.getTo(), info.getConverter()::convert, info.getFlags()); + }) + .collect(Collectors.toList()); } /** @@ -56,62 +51,17 @@ private Converters() {} * @param to Type that the converter converts to. * @param converter Actual converter. */ - public static <F, T> void registerConverter(final Class<F> from, final Class<T> to, final Converter<F, T> converter) { + public static <F, T> void registerConverter(Class<F> from, Class<T> to, Converter<F, T> converter) { registerConverter(from, to, converter, 0); } - public static <F, T> void registerConverter(final Class<F> from, final Class<T> to, final Converter<F, T> converter, final int options) { - Skript.checkAcceptRegistrations(); - final ConverterInfo<F, T> info = new ConverterInfo<>(from, to, converter, options); - for (int i = 0; i < converters.size(); i++) { - final ConverterInfo<?, ?> info2 = converters.get(i); - if (info2.from.isAssignableFrom(from) && to.isAssignableFrom(info2.to)) { - converters.add(i, info); - return; - } - } - converters.add(info); + public static <F, T> void registerConverter(Class<F> from, Class<T> to, Converter<F, T> converter, int options) { + org.skriptlang.skript.lang.converter.Converters.registerConverter(from, to, converter::convert, options); } // REMIND how to manage overriding of converters? - shouldn't actually matter public static void createMissingConverters() { - for (int i = 0; i < converters.size(); i++) { - final ConverterInfo<?, ?> info = converters.get(i); - for (int j = 0; j < converters.size(); j++) {// not from j = i+1 since new converters get added during the loops - final ConverterInfo<?, ?> info2 = converters.get(j); - - // info -> info2 - if ((info.options & Converter.NO_RIGHT_CHAINING) == 0 && (info2.options & Converter.NO_LEFT_CHAINING) == 0 - && info2.from.isAssignableFrom(info.to) && !converterExistsSlow(info.from, info2.to)) { - converters.add(createChainedConverter(info, info2)); - // info2 -> info - } else if ((info.options & Converter.NO_LEFT_CHAINING) == 0 && (info2.options & Converter.NO_RIGHT_CHAINING) == 0 - && info.from.isAssignableFrom(info2.to) && !converterExistsSlow(info2.from, info.to)) { - converters.add(createChainedConverter(info2, info)); - } - } - } - } - - /** - * Checks if a converter between given classes exists. This iterates over - * all converters, which may take a while. - * @param from Type to convert from. - * @param to Type to convert to. - * @return If a converter exists. - */ - private static boolean converterExistsSlow(final Class<?> from, final Class<?> to) { - for (final ConverterInfo<?, ?> i : converters) { - if ((i.from.isAssignableFrom(from) || from.isAssignableFrom(i.from)) && (i.to.isAssignableFrom(to) || to.isAssignableFrom(i.to))) { - return true; - } - } - return false; - } - - @SuppressWarnings("unchecked") - private static <F, M, T> ConverterInfo<F, T> createChainedConverter(final ConverterInfo<?, ?> first, final ConverterInfo<?, ?> second) { - return new ConverterInfo<>(first, second, new ChainedConverter<>((Converter<F, M>) first.converter, (Converter<M, T>) second.converter), first.options | second.options); + org.skriptlang.skript.lang.converter.Converters.createChainedConverters(); } /** @@ -122,17 +72,9 @@ private static <F, M, T> ConverterInfo<F, T> createChainedConverter(final Conver * @param to * @return The converted value or null if no converter exists or the converter returned null for the given value. */ - @SuppressWarnings("unchecked") @Nullable public static <F, T> T convert(final @Nullable F o, final Class<T> to) { - if (o == null) - return null; - if (to.isInstance(o)) - return (T) o; - final Converter<? super F, ? extends T> conv = getConverter((Class<F>) o.getClass(), to); - if (conv == null) - return null; - return conv.convert(o); + return org.skriptlang.skript.lang.converter.Converters.convert(o, to); } /** @@ -144,22 +86,9 @@ public static <F, T> T convert(final @Nullable F o, final Class<T> to) { * @param to * @return The converted object */ - @SuppressWarnings("unchecked") @Nullable public static <F, T> T convert(final @Nullable F o, final Class<? extends T>[] to) { - if (o == null) - return null; - for (final Class<? extends T> t : to) - if (t.isInstance(o)) - return (T) o; - final Class<F> c = (Class<F>) o.getClass(); - for (final Class<? extends T> t : to) { - @SuppressWarnings("null") - final Converter<? super F, ? extends T> conv = getConverter(c, t); - if (conv != null) - return conv.convert(o); - } - return null; + return org.skriptlang.skript.lang.converter.Converters.convert(o, to); } /** @@ -170,21 +99,12 @@ public static <F, T> T convert(final @Nullable F o, final Class<? extends T>[] t * @param to * @return A T[] array without null elements */ - @SuppressWarnings("unchecked") @Nullable public static <T> T[] convertArray(final @Nullable Object[] o, final Class<T> to) { - assert to != null; - if (o == null) + T[] converted = org.skriptlang.skript.lang.converter.Converters.convert(o, to); + if (converted.length == 0) // no longer nullable with new converter classes return null; - if (to.isAssignableFrom(o.getClass().getComponentType())) - return (T[]) o; - final List<T> l = new ArrayList<>(o.length); - for (final Object e : o) { - final T c = convert(e, to); - if (c != null) - l.add(c); - } - return l.toArray((T[]) Array.newInstance(to, l.size())); + return converted; } /** @@ -195,25 +115,8 @@ public static <T> T[] convertArray(final @Nullable Object[] o, final Class<T> to * @param superType The component type of the returned array * @return The converted array */ - @SuppressWarnings("unchecked") public static <T> T[] convertArray(final @Nullable Object[] o, final Class<? extends T>[] to, final Class<T> superType) { - if (o == null) { - final T[] r = (T[]) Array.newInstance(superType, 0); - assert r != null; - return r; - } - for (final Class<? extends T> t : to) - if (t.isAssignableFrom(o.getClass().getComponentType())) - return (T[]) o; - final List<T> l = new ArrayList<>(o.length); - for (final Object e : o) { - final T c = convert(e, to); - if (c != null) - l.add(c); - } - final T[] r = l.toArray((T[]) Array.newInstance(superType, l.size())); - assert r != null; - return r; + return org.skriptlang.skript.lang.converter.Converters.convert(o, to, superType); } /** @@ -226,17 +129,8 @@ public static <T> T[] convertArray(final @Nullable Object[] o, final Class<? ext * @throws ClassCastException if one of {@code original}'s * elements cannot be converted to a {@code to} */ - @SuppressWarnings("unchecked") public static <T> T[] convertStrictly(Object[] original, Class<T> to) throws ClassCastException { - T[] end = (T[]) Array.newInstance(to, original.length); - for (int i = 0; i < original.length; i++) { - T converted = Converters.convert(original[i], to); - if (converted != null) - end[i] = converted; - else - throw new ClassCastException(); - } - return end; + return org.skriptlang.skript.lang.converter.Converters.convertStrictly(original, to); } /** @@ -248,14 +142,8 @@ public static <T> T[] convertStrictly(Object[] original, Class<T> to) throws Cla * @throws ClassCastException if {@code original} could not be converted to a {@code to} */ public static <T> T convertStrictly(Object original, Class<T> to) throws ClassCastException { - T converted = convert(original, to); - if (converted != null) - return converted; - else - throw new ClassCastException(); + return org.skriptlang.skript.lang.converter.Converters.convertStrictly(original, to); } - - private final static Map<Pair<Class<?>, Class<?>>, ConverterInfo<?, ?>> convertersCache = new HashMap<>(); /** * Tests whether a converter between the given classes exists. @@ -265,18 +153,11 @@ public static <T> T convertStrictly(Object original, Class<T> to) throws ClassCa * @return Whether a converter exists */ public static boolean converterExists(final Class<?> from, final Class<?> to) { - if (to.isAssignableFrom(from) || from.isAssignableFrom(to)) - return true; - return getConverter(from, to) != null; + return org.skriptlang.skript.lang.converter.Converters.converterExists(from, to); } public static boolean converterExists(final Class<?> from, final Class<?>... to) { - for (final Class<?> t : to) { - assert t != null; - if (converterExists(from, t)) - return true; - } - return false; + return org.skriptlang.skript.lang.converter.Converters.converterExists(from, to); } /** @@ -288,8 +169,11 @@ public static boolean converterExists(final Class<?> from, final Class<?>... to) */ @Nullable public static <F, T> Converter<? super F, ? extends T> getConverter(final Class<F> from, final Class<T> to) { - ConverterInfo<? super F, ? extends T> info = getConverterInfo(from, to); - return info != null ? info.converter : null; + org.skriptlang.skript.lang.converter.Converter<F, T> converter = + org.skriptlang.skript.lang.converter.Converters.getConverter(from, to); + if (converter == null) + return null; + return (Converter<F, T>) converter::convert; } /** @@ -299,74 +183,13 @@ public static boolean converterExists(final Class<?> from, final Class<?>... to) * @param to * @return The converter info or null if no converters were found. */ - @SuppressWarnings("unchecked") @Nullable public static <F, T> ConverterInfo<? super F, ? extends T> getConverterInfo(Class<F> from, Class<T> to) { - Pair<Class<?>, Class<?>> p = new Pair<>(from, to); - if (convertersCache.containsKey(p)) // can contain null to denote nonexistence of a converter - return (ConverterInfo<? super F, ? extends T>) convertersCache.get(p); - ConverterInfo<? super F, ? extends T> c = lookupConverterInfo(from, to); - convertersCache.put(p, c); - return c; - } - - @SuppressWarnings("unchecked") - @Nullable - private static <F, T> ConverterInfo<? super F, ? extends T> lookupConverterInfo(Class<F> from, Class<T> to) { - // Check for existing converters between two types first - for (final ConverterInfo<?, ?> conv : converters) { - if (conv.from.isAssignableFrom(from) && to.isAssignableFrom(conv.to)) { - return (ConverterInfo<? super F, ? extends T>) conv; - } - } - - // None found? Try the converters that have either from OR to not match - for (final ConverterInfo<?, ?> conv : converters) { - if (conv.from.isAssignableFrom(from) && conv.to.isAssignableFrom(to)) { - // from matches, but to doesn't exactly and needs to be filtered - - return new ConverterInfo<>(from, to, (Converter<F, T>) ConverterUtils - .createInstanceofConverter(conv.converter, to), 0); - } else if (from.isAssignableFrom(conv.from) && to.isAssignableFrom(conv.to)) { - // to matches, from doesn't exactly, and needs filtering - return new ConverterInfo<>(from, to, (Converter<F, T>) ConverterUtils - .createInstanceofConverter(conv), 0); - } - } - - // Begin accepting both to and from not exactly matching - for (final ConverterInfo<?, ?> conv : converters) { - if (from.isAssignableFrom(conv.from) && conv.to.isAssignableFrom(to)) { - return new ConverterInfo<>(from, to, (Converter<F, T>) ConverterUtils - .createDoubleInstanceofConverter(conv, to), 0); - } - } - - // No converter available - return null; - } - - @SuppressWarnings("unchecked") - @Nullable - private static <F, T> Converter<? super F, ? extends T> getConverter_i(final Class<F> from, final Class<T> to) { - for (final ConverterInfo<?, ?> conv : converters) { - if (conv.from.isAssignableFrom(from) && to.isAssignableFrom(conv.to)) { - return (Converter<? super F, ? extends T>) conv.converter; - } - } - for (final ConverterInfo<?, ?> conv : converters) { - if (conv.from.isAssignableFrom(from) && conv.to.isAssignableFrom(to)) { - return (Converter<? super F, ? extends T>) ConverterUtils.createInstanceofConverter(conv.converter, to); - } else if (from.isAssignableFrom(conv.from) && to.isAssignableFrom(conv.to)) { - return (Converter<? super F, ? extends T>) ConverterUtils.createInstanceofConverter(conv); - } - } - for (final ConverterInfo<?, ?> conv : converters) { - if (from.isAssignableFrom(conv.from) && conv.to.isAssignableFrom(to)) { - return (Converter<? super F, ? extends T>) ConverterUtils.createDoubleInstanceofConverter(conv, to); - } - } - return null; + org.skriptlang.skript.lang.converter.ConverterInfo<F, T> info = + org.skriptlang.skript.lang.converter.Converters.getConverterInfo(from, to); + if (info == null) + return null; + return new ConverterInfo<>(info.getFrom(), info.getTo(), info.getConverter()::convert, info.getFlags()); } /** @@ -376,25 +199,12 @@ public static boolean converterExists(final Class<?> from, final Class<?>... to) * @return The converted array * @throws ArrayStoreException if the given class is not a superclass of all objects returned by the converter */ - @SuppressWarnings("unchecked") public static <F, T> T[] convertUnsafe(final F[] from, final Class<?> to, final Converter<? super F, ? extends T> conv) { - return convert(from, (Class<T>) to, conv); + return org.skriptlang.skript.lang.converter.Converters.convertUnsafe(from, to, conv::convert); } public static <F, T> T[] convert(final F[] from, final Class<T> to, final Converter<? super F, ? extends T> conv) { - @SuppressWarnings("unchecked") - T[] ts = (T[]) Array.newInstance(to, from.length); - int j = 0; - for (int i = 0; i < from.length; i++) { - final F f = from[i]; - final T t = f == null ? null : conv.convert(f); - if (t != null) - ts[j++] = t; - } - if (j != ts.length) - ts = Arrays.copyOf(ts, j); - assert ts != null; - return ts; + return org.skriptlang.skript.lang.converter.Converters.convert(from, to, conv::convert); } } diff --git a/src/main/java/ch/njol/skript/registrations/EventValues.java b/src/main/java/ch/njol/skript/registrations/EventValues.java index 82089400a71..741502a022c 100644 --- a/src/main/java/ch/njol/skript/registrations/EventValues.java +++ b/src/main/java/ch/njol/skript/registrations/EventValues.java @@ -27,7 +27,8 @@ import com.google.common.collect.ImmutableList; import ch.njol.skript.Skript; -import ch.njol.skript.classes.Converter; +import org.skriptlang.skript.lang.converter.Converter; +import org.skriptlang.skript.lang.converter.Converters; import ch.njol.skript.expressions.base.EventValueExpression; import ch.njol.skript.util.Getter; diff --git a/src/main/java/ch/njol/skript/util/FileUtils.java b/src/main/java/ch/njol/skript/util/FileUtils.java index f476d1c01d4..c966ab8ddba 100644 --- a/src/main/java/ch/njol/skript/util/FileUtils.java +++ b/src/main/java/ch/njol/skript/util/FileUtils.java @@ -29,7 +29,7 @@ import java.util.ArrayList; import java.util.Collection; -import ch.njol.skript.classes.Converter; +import org.skriptlang.skript.lang.converter.Converter; /** * @author Peter Güttinger diff --git a/src/main/java/ch/njol/skript/util/Getter.java b/src/main/java/ch/njol/skript/util/Getter.java index 1b319628ad5..44de2746e70 100644 --- a/src/main/java/ch/njol/skript/util/Getter.java +++ b/src/main/java/ch/njol/skript/util/Getter.java @@ -20,7 +20,7 @@ import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.classes.Converter; +import org.skriptlang.skript.lang.converter.Converter; /** * Used to get a specific value from instances of some type. diff --git a/src/main/java/ch/njol/skript/variables/Variables.java b/src/main/java/ch/njol/skript/variables/Variables.java index a55e0df150b..aa3808b30e4 100644 --- a/src/main/java/ch/njol/skript/variables/Variables.java +++ b/src/main/java/ch/njol/skript/variables/Variables.java @@ -54,7 +54,7 @@ import ch.njol.skript.config.SectionNode; import ch.njol.skript.lang.Variable; import ch.njol.skript.registrations.Classes; -import ch.njol.skript.registrations.Converters; +import org.skriptlang.skript.lang.converter.Converters; import ch.njol.skript.variables.DatabaseStorage.Type; import ch.njol.skript.variables.SerializedVariable.Value; import ch.njol.util.Closeable; diff --git a/src/main/java/org/skriptlang/skript/lang/converter/ChainedConverter.java b/src/main/java/org/skriptlang/skript/lang/converter/ChainedConverter.java new file mode 100644 index 00000000000..ee6abc15c9e --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/converter/ChainedConverter.java @@ -0,0 +1,65 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.converter; + +import org.eclipse.jdt.annotation.Nullable; + +/** + * A Chained Converter is very similar to a regular {@link Converter}. + * They are used when it is not possible to directly convert from Type A to Type B. + * Instead, a "middle man" is used that makes it possible to convert from Type A to Type B. + * What this means is that Type A is converted to the "middle man" type. + * Then, that "middle man" type is converted to Type B. + * + * Of course, multiple Chained Converters can be chained together to provide far more conversion possibilities. + * In these cases, you might have, for example, another "middle man" between + * this Chained Converter's Type A and "middle man" + * + * By using conversion chains, Skript can convert an object into a different type that seemingly has no relation. + * + * @param <F> The type to convert from. + * @param <M> A middle type that is needed for converting 'from' to 'to'. + * 'from' will be converted to this type, and then this type will be converted to 'to'. + * @param <T> The type to convert to. + */ +final class ChainedConverter<F, M, T> implements Converter<F, T> { + + private final Converter<F, M> first; + private final Converter<M, T> second; + + ChainedConverter(Converter<F, M> first, Converter<M, T> second) { + this.first = first; + this.second = second; + } + + @Override + @Nullable + public T convert(F from) { + M middle = first.convert(from); + if (middle == null) + return null; + return second.convert(middle); + } + + @Override + public String toString() { + return "ChainedConverter{first=" + first + ",second=" + second + "}"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/converter/Converter.java b/src/main/java/org/skriptlang/skript/lang/converter/Converter.java new file mode 100644 index 00000000000..32bf8aa6c06 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/converter/Converter.java @@ -0,0 +1,61 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.converter; + +import org.eclipse.jdt.annotation.Nullable; + +/** + * Used to convert an object to a different type. + * + * @param <F> The type to convert from. + * @param <T> The type to convert to. + */ +@FunctionalInterface +public interface Converter<F, T> { + + /** + * A Converter flag declaring that this Converter may be chained in any way with another Converter. + */ + int ALL_CHAINING = 0; + + /** + * A Converter flag declaring that another Converter cannot be chained to this Converter. + * This means that this Converter must be the beginning of a chain. + */ + int NO_LEFT_CHAINING = 1; + + /** + * A Converter flag declaring this Converter cannot be chained to another Converter. + * This means that this Converter must be the end of a chain. + */ + int NO_RIGHT_CHAINING = 2; + + /** + * A Converter flag declaring that this Converter cannot be chained in any way with another Converter. + */ + int NO_CHAINING = NO_LEFT_CHAINING | NO_RIGHT_CHAINING; + + /** + * Converts an object using this Converter. + * @param from The object to convert. + * @return The converted object. + */ + @Nullable T convert(F from); + +} diff --git a/src/main/java/org/skriptlang/skript/lang/converter/ConverterInfo.java b/src/main/java/org/skriptlang/skript/lang/converter/ConverterInfo.java new file mode 100644 index 00000000000..8faa29c1419 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/converter/ConverterInfo.java @@ -0,0 +1,62 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.converter; + +/** + * Holds information about a {@link Converter}. + * + * @param <F> The type to convert from. + * @param <T> The type to convert to. + */ +public final class ConverterInfo<F, T> { + + private final Class<F> from; + private final Class<T> to; + private final Converter<F, T> converter; + private final int flags; + + public ConverterInfo(Class<F> from, Class<T> to, Converter<F, T> converter, int flags) { + this.from = from; + this.to = to; + this.converter = converter; + this.flags = flags; + } + + public Class<F> getFrom() { + return from; + } + + public Class<T> getTo() { + return to; + } + + public Converter<F, T> getConverter() { + return converter; + } + + public int getFlags() { + return flags; + } + + @Override + public String toString() { + return "ConverterInfo{from=" + from + ",to=" + to + ",converter=" + converter + ",flag=" + flags + "}"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/converter/Converters.java b/src/main/java/org/skriptlang/skript/lang/converter/Converters.java new file mode 100644 index 00000000000..23a4ddf5051 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/converter/Converters.java @@ -0,0 +1,499 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.converter; + +import ch.njol.skript.Skript; +import ch.njol.skript.SkriptAPIException; +import org.eclipse.jdt.annotation.Nullable; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * Converters are used to provide Skript with specific instructions for converting an object to a different type. + * @see #registerConverter(Class, Class, Converter) + */ +public final class Converters { + + private Converters() {} + + /** + * A List containing information for all registered converters. + */ + private static final List<ConverterInfo<?, ?>> CONVERTERS = Collections.synchronizedList(new ArrayList<>(50)); + + /** + * @return An unmodifiable, synchronized list containing all registered {@link ConverterInfo}s. + * When traversing this list, please refer to {@link Collections#synchronizedList(List)} to ensure that + * the list is properly traversed due to its synchronized status. + * Please note that this does not include any special Converters resolved by Skript during runtime. + * This method ONLY returns converters explicitly registered during registration. + * Thus, it is recommended to use {@link #getConverter(Class, Class)}. + */ + public static List<ConverterInfo<?, ?>> getConverterInfo() { + return Collections.unmodifiableList(CONVERTERS); + } + + /** + * A map for quickly access converters that have already been resolved. + * This is useful for skipping complex lookups that may require chaining. + */ + private static final Map<Integer, ConverterInfo<?, ?>> QUICK_ACCESS_CONVERTERS = Collections.synchronizedMap(new HashMap<>(50)); + + /** + * Registers a new Converter with Skript's collection of Converters. + * @param from The type to convert from. + * @param to The type to convert to. + * @param converter A Converter for converting objects of type 'from' to type 'to'. + */ + public static <F, T> void registerConverter(Class<F> from, Class<T> to, Converter<F, T> converter) { + registerConverter(from, to, converter, Converter.ALL_CHAINING); + } + + /** + * Registers a new Converter with Skript's collection of Converters. + * @param from The type to convert from. + * @param to The type to convert to. + * @param converter A Converter for converting objects of type 'from' to type 'to'. + * @param flags Flags to set for the Converter. Flags can be found under {@link Converter}. + */ + public static <F, T> void registerConverter(Class<F> from, Class<T> to, Converter<F, T> converter, int flags) { + Skript.checkAcceptRegistrations(); + + ConverterInfo<F, T> info = new ConverterInfo<>(from, to, converter, flags); + + if (exactConverterExists(from, to)) { + throw new SkriptAPIException( + "A Converter from '" + from + "' to '" + to + " already exists!" + ); + } + + CONVERTERS.add(info); + } + + /** + * This method is to be called after Skript has finished registration. + * It allows {@link ChainedConverter}s to be created so that Skript may do more complex conversions + * involving multiple converters. + */ + // TODO Find a better way of doing this that doesn't require a method to be called (probably requires better Registration API) + // REMIND how to manage overriding of converters? - shouldn't actually matter + @SuppressWarnings("unchecked") + public static <F, M, T> void createChainedConverters() { + for (int i = 0; i < CONVERTERS.size(); i++) { + + ConverterInfo<?, ?> unknownInfo1 = CONVERTERS.get(i); + for (int j = 0; j < CONVERTERS.size(); j++) { // Not from j = i+1 since new converters get added during the loops + + ConverterInfo<?, ?> unknownInfo2 = CONVERTERS.get(j); + + // chain info -> info2 + if ( + unknownInfo2.getFrom() != Object.class // Object can only exist at the beginning of a chain + && (unknownInfo1.getFlags() & Converter.NO_RIGHT_CHAINING) == 0 + && (unknownInfo2.getFlags() & Converter.NO_LEFT_CHAINING) == 0 + && unknownInfo2.getFrom().isAssignableFrom(unknownInfo1.getTo()) + && !exactConverterExists(unknownInfo1.getFrom(), unknownInfo2.getTo()) + ) { + ConverterInfo<F, M> info1 = (ConverterInfo<F, M>) unknownInfo1; + ConverterInfo<M, T> info2 = (ConverterInfo<M, T>) unknownInfo2; + + CONVERTERS.add(new ConverterInfo<>( + info1.getFrom(), + info2.getTo(), + new ChainedConverter<>(info1.getConverter(), info2.getConverter()), + info1.getFlags() | info2.getFlags() + )); + } + + // chain info2 -> info + else if ( + unknownInfo1.getFrom() != Object.class // Object can only exist at the beginning of a chain + && (unknownInfo1.getFlags() & Converter.NO_LEFT_CHAINING) == 0 + && (unknownInfo2.getFlags() & Converter.NO_RIGHT_CHAINING) == 0 + && unknownInfo1.getFrom().isAssignableFrom(unknownInfo2.getTo()) + && !exactConverterExists(unknownInfo2.getFrom(), unknownInfo1.getTo()) + ) { + ConverterInfo<M, T> info1 = (ConverterInfo<M, T>) unknownInfo1; + ConverterInfo<F, M> info2 = (ConverterInfo<F, M>) unknownInfo2; + + CONVERTERS.add(new ConverterInfo<>( + info2.getFrom(), + info1.getTo(), + new ChainedConverter<>(info2.getConverter(), info1.getConverter()), + info2.getFlags() | info1.getFlags() + )); + } + + } + + } + } + + /** + * Internal method. + * @return Whether a Converter exists that EXACTLY matches the provided types. + */ + private static boolean exactConverterExists(Class<?> from, Class<?> to) { + for (ConverterInfo<?, ?> info : CONVERTERS) { + if (from == info.getFrom() && to == info.getTo()) + return true; + } + return false; + } + + /** + * @return Whether a Converter capable of converting 'fromType' to 'toType' exists. + */ + public static boolean converterExists(Class<?> fromType, Class<?> toType) { + if (toType.isAssignableFrom(fromType) || fromType.isAssignableFrom(toType)) + return true; + return getConverter(fromType, toType) != null; + } + + /** + * @return Whether a Converter capable of converting 'fromType' to one of the provided 'toTypes' exists. + */ + public static boolean converterExists(Class<?> fromType, Class<?>... toTypes) { + for (Class<?> toType : toTypes) { + if (converterExists(fromType, toType)) + return true; + } + return false; + } + + /** + * A method for obtaining the ConverterInfo of a Converter that can convert + * an object of 'fromType' into an object of 'toType'. + * @param fromType The type to convert from. + * @param toType The type to convert to. + * @return The ConverterInfo of a Converter capable of converting an object of 'fromType' into an object of 'toType'. + * Will return null if no such Converter exists. + */ + @Nullable + @SuppressWarnings("unchecked") + public static <F, T> ConverterInfo<F, T> getConverterInfo(Class<F> fromType, Class<T> toType) { + int hash = Objects.hash(fromType, toType); + + ConverterInfo<F, T> info = (ConverterInfo<F, T>) QUICK_ACCESS_CONVERTERS.get(hash); + + if (info == null) { // Manual lookup + info = getConverter_i(fromType, toType); + QUICK_ACCESS_CONVERTERS.put(hash, info); + } + + return info; + } + + /** + * A method for obtaining a Converter that can convert an object of 'fromType' into an object of 'toType'. + * @param fromType The type to convert from. + * @param toType The type to convert to. + * @return A Converter capable of converting an object of 'fromType' into an object of 'toType'. + * Will return null if no such Converter exists. + */ + @Nullable + public static <F, T> Converter<F, T> getConverter(Class<F> fromType, Class<T> toType) { + ConverterInfo<F, T> info = getConverterInfo(fromType, toType); + return info != null ? info.getConverter() : null; + } + + /** + * The internal method for obtaining the ConverterInfo of a Converter that can convert + * an object of 'fromType' into an object of 'toType'. + * + * @param fromType The type to convert from. + * @param toType The type to convert to. + * @return The ConverterInfo of a Converter capable of converting an object of 'fromType' into an object of 'toType'. + * Will return null if no such Converter exists. + * + * @param <F> The type to convert from. + * @param <T> The type to convert to. + * @param <SubType> The 'fromType' for a Converter that may only convert certain objects of 'fromType' + * @param <ParentType> The 'toType' for a Converter that may only sometimes convert objects of 'fromType' + * into objects of 'toType' (e.g. the converted object may only share a parent with 'toType') + */ + @Nullable + @SuppressWarnings("unchecked") + private static <F, T extends ParentType, SubType extends F, ParentType> ConverterInfo<F, T> getConverter_i( + Class<F> fromType, + Class<T> toType + ) { + // Check for an exact match + for (ConverterInfo<?, ?> info : CONVERTERS) { + if (fromType == info.getFrom() && toType == info.getTo()) + return (ConverterInfo<F, T>) info; + } + + // Check for an almost perfect match + for (ConverterInfo<?, ?> info : CONVERTERS) { + if (info.getFrom().isAssignableFrom(fromType) && toType.isAssignableFrom(info.getTo())) + return (ConverterInfo<F, T>) info; + } + + // We don't want to create "maybe" converters for 'Object -> X' conversions + // Instead, we should just try and convert during runtime when we have a better idea of the fromType + if (fromType == Object.class) + return new ConverterInfo<>(fromType, toType, fromObject -> Converters.convert(fromObject, toType), 0); + + // Attempt to find converters that have either 'from' OR 'to' not exactly matching + for (ConverterInfo<?, ?> unknownInfo : CONVERTERS) { + if (unknownInfo.getFrom().isAssignableFrom(fromType) && unknownInfo.getTo().isAssignableFrom(toType)) { + ConverterInfo<F, ParentType> info = (ConverterInfo<F, ParentType>) unknownInfo; + + // 'to' doesn't exactly match and needs to be filtered + // Basically, this converter might convert 'F' into something that's shares a parent with 'T' + return new ConverterInfo<>(fromType, toType, fromObject -> { + Object converted = info.getConverter().convert(fromObject); + if (toType.isInstance(converted)) + return (T) converted; + return null; + }, 0); + + } else if (fromType.isAssignableFrom(unknownInfo.getFrom()) && toType.isAssignableFrom(unknownInfo.getTo())) { + ConverterInfo<SubType, T> info = (ConverterInfo<SubType, T>) unknownInfo; + + // 'from' doesn't exactly match and needs to be filtered + // Basically, this converter will only convert certain 'F' objects + return new ConverterInfo<>(fromType, toType, fromObject -> { + if (!info.getFrom().isInstance(fromType)) + return null; + return info.getConverter().convert((SubType) fromObject); + }, 0); + + } + } + + // At this point, accept both 'from' AND 'to' not exactly matching + for (ConverterInfo<?, ?> unknownInfo : CONVERTERS) { + if (fromType.isAssignableFrom(unknownInfo.getFrom()) && unknownInfo.getTo().isAssignableFrom(toType)) { + ConverterInfo<SubType, ParentType> info = (ConverterInfo<SubType, ParentType>) unknownInfo; + + // 'from' and 'to' both don't exactly match and need to be filtered + // Basically, this converter will only convert certain 'F' objects + // and some conversion results will only share a parent with 'T' + return new ConverterInfo<>(fromType, toType, fromObject -> { + if (!info.getFrom().isInstance(fromObject)) + return null; + Object converted = info.getConverter().convert((SubType) fromObject); + if (toType.isInstance(converted)) + return (T) converted; + return null; + }, 0); + + } + } + + // No converter available + return null; + } + + /** + * Standard method for converting an object into a different type. + * @param from The object to convert. + * @param toType The type that 'from' should be converted into. + * @return An object of 'toType', or null if 'from' couldn't be successfully converted. + */ + @Nullable + @SuppressWarnings("unchecked") + public static <From, To> To convert(@Nullable From from, Class<To> toType) { + if (from == null) + return null; + + if (toType.isInstance(from)) + return (To) from; + + Converter<From, To> converter = getConverter((Class<From>) from.getClass(), toType); + if (converter == null) + return null; + + return converter.convert(from); + } + + /** + * A method for converting an object into one of several provided types. + * @param from The object to convert. + * @param toTypes A list of types that should be tried for converting 'from'. + * @return An object of one of the provided 'toTypes', or null if 'from' couldn't successfully be converted. + */ + @Nullable + @SuppressWarnings("unchecked") + public static <From, To> To convert(@Nullable From from, Class<? extends To>[] toTypes) { + if (from == null) + return null; + + for (Class<? extends To> toType : toTypes) { + if (toType.isInstance(from)) + return (To) from; + } + + Class<From> fromType = (Class<From>) from.getClass(); + for (Class<? extends To> toType : toTypes) { + Converter<From, ? extends To> converter = getConverter(fromType, toType); + if (converter != null) + return converter.convert(from); + } + + return null; + } + + /** + * Standard method for bulk-conversion of objects into a different type. + * @param from The objects to convert. + * @param toType The type that 'from' should be converted into. + * @return Objects of 'toType'. Will return null if 'from' is null. + * Please note that the returned array may not be the same size as 'from'. + * This can happen if an object contained within 'from' is not successfully converted. + */ + @SuppressWarnings("unchecked") + public static <To> To[] convert(@Nullable Object[] from, Class<To> toType) { + //noinspection ConstantConditions + if (from == null) + return (To[]) Array.newInstance(toType, 0); + + if (toType.isAssignableFrom(from.getClass().getComponentType())) + return (To[]) from; + + List<To> converted = new ArrayList<>(from.length); + for (Object fromSingle : from) { + To convertedSingle = convert(fromSingle, toType); + if (convertedSingle != null) + converted.add(convertedSingle); + } + + return converted.toArray((To[]) Array.newInstance(toType, converted.size())); + } + + /** + * A method for bulk-conversion of objects into one of several provided types. + * @param from The objects to convert. + * @param toTypes A list of types that should be tried for converting each object. + * @param superType A parent type of all provided 'toTypes'. + * @return Objects of 'superType'. Will return any empty array if 'from' is null. + * Please note that the returned array may not be the same size as 'from'. + * This can happen if an object contained within 'from' is not successfully converted. + * And, of course, the returned array may contain objects of a different type. + */ + @SuppressWarnings("unchecked") + public static <To> To[] convert(@Nullable Object[] from, Class<? extends To>[] toTypes, Class<To> superType) { + //noinspection ConstantConditions + if (from == null) + return (To[]) Array.newInstance(superType, 0); + + Class<?> fromType = from.getClass().getComponentType(); + + for (Class<? extends To> toType : toTypes) { + if (toType.isAssignableFrom(fromType)) + return (To[]) from; + } + + List<To> converted = new ArrayList<>(from.length); + for (Object fromSingle : from) { + To convertedSingle = convert(fromSingle, toTypes); + if (convertedSingle != null) + converted.add(convertedSingle); + } + + return converted.toArray((To[]) Array.newInstance(superType, converted.size())); + } + + /** + * A method for bulk-converting objects of a specific type using a specific Converter. + * @param from The objects to convert. + * @param toType The type to convert into. + * @param converter The converter to use for conversion. + * @return Objects of 'toType'. + * Please note that the returned array may not be the same size as 'from'. + * This can happen if an object contained within 'from' is not successfully converted. + */ + @SuppressWarnings("unchecked") + public static <From, To> To[] convert(From[] from, Class<To> toType, Converter<? super From, ? extends To> converter) { + To[] converted = (To[]) Array.newInstance(toType, from.length); + + int j = 0; + for (From fromSingle : from) { + To convertedSingle = fromSingle == null ? null : converter.convert(fromSingle); + if (convertedSingle != null) + converted[j++] = convertedSingle; + } + + if (j != converted.length) + converted = Arrays.copyOf(converted, j); + + return converted; + } + + /** + * A method that guarantees an object of 'toType' is returned. + * @param from The object to convert. + * @param toType The type to convert into. + * @return An object of 'toType'. + * @throws ClassCastException If 'from' cannot be converted. + */ + public static <To> To convertStrictly(Object from, Class<To> toType) { + To converted = convert(from, toType); + if (converted == null) + throw new ClassCastException("Cannot convert '" + from + "' to an object of type '" + toType + "'"); + return converted; + } + + /** + * A method for bulk-conversion that guarantees objects of 'toType' are returned. + * @param from The object to convert. + * @param toType The type to convert into. + * @return Objects of 'toType'. The returned array will be the same size as 'from'. + * @throws ClassCastException If any of the provided objects cannot be converted. + */ + @SuppressWarnings("unchecked") + public static <To> To[] convertStrictly(Object[] from, Class<To> toType) { + To[] converted = (To[]) Array.newInstance(toType, from.length); + + for (int i = 0; i < from.length; i++) { + To convertedSingle = convert(from[i], toType); + if (convertedSingle == null) + throw new ClassCastException("Cannot convert '" + from[i] + "' to an object of type '" + toType + "'"); + converted[i] = convertedSingle; + } + + return converted; + } + + /** + * A method for bulk-converting objects of a specific type using a specific Converter. + * @param from The objects to convert. + * @param toType A superclass for all objects to be converted. + * @param converter The converter to use for conversion. + * @return Objects of 'toType'. + * Please note that the returned array may not be the same size as 'from'. + * This can happen if an object contained within 'from' is not successfully converted. + * @throws ArrayStoreException If 'toType' is not a superclass of all objects returned by the converter. + * @throws ClassCastException If 'toType' is not of 'T'. + */ + @SuppressWarnings("unchecked") + public static <From, To> To[] convertUnsafe(From[] from, Class<?> toType, Converter<? super From, ? extends To> converter) { + return convert(from, (Class<To>) toType, converter); + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/converter/package-info.java b/src/main/java/org/skriptlang/skript/lang/converter/package-info.java new file mode 100644 index 00000000000..e0e05b8a2f5 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/converter/package-info.java @@ -0,0 +1,23 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +@NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) +package org.skriptlang.skript.lang.converter; + +import org.eclipse.jdt.annotation.DefaultLocation; +import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/src/test/skript/tests/regressions/5054-converter lookup for object to x converters is wrong.sk b/src/test/skript/tests/regressions/5054-converter lookup for object to x converters is wrong.sk new file mode 100644 index 00000000000..7b1592f106c --- /dev/null +++ b/src/test/skript/tests/regressions/5054-converter lookup for object to x converters is wrong.sk @@ -0,0 +1,11 @@ +test "converter lookup for object to x converters is wrong": + + loop 1, 2, and 3: + assert loop-value + 1 > 1 with "loop-value is not greater than 1 (loop-value = '%loop-value%')" + + set {_hello::*} to bananas() + assert size of {_hello::*} > 0 with "reversed list didn't work ({_hello::*} = '%{_hello::*}%')" + +function bananas() :: numbers: + set {_hello::*} to 1 and 2 + return reversed {_hello::*} diff --git a/src/test/skript/tests/syntaxes/expressions/ExprXOf.sk b/src/test/skript/tests/syntaxes/expressions/ExprXOf.sk index fe21a443134..af08af80093 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprXOf.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprXOf.sk @@ -1,3 +1,7 @@ +function ExprXOf(c: number) :: item: + set {_gem} to diamond + return {_c} of {_gem} + test "X of": set {_x} to 5 assert 1 of {_x} is not set with "incorrect type failed" @@ -5,3 +9,5 @@ test "X of": assert 2 of {_y} is 2 zombie with "entity type amount failed" set {_z} to stone assert 2 of {_z} is 2 stone with "item failed" + + assert ExprXOf(2) is 2 diamonds with "function return failed - got '%ExprXOf(2)%'" From 76c462b701431df5029c139c42b5e98c070424d5 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Wed, 25 Jan 2023 10:57:15 -0500 Subject: [PATCH 204/619] Use keyword searching to improve parsing speeds (#5103) --- .../ch/njol/skript/lang/SkriptParser.java | 115 ++++++++++++++---- .../patterns/LiteralPatternElement.java | 14 ++- .../skript/patterns/TypePatternElement.java | 47 ++++++- 3 files changed, 148 insertions(+), 28 deletions(-) diff --git a/src/main/java/ch/njol/skript/lang/SkriptParser.java b/src/main/java/ch/njol/skript/lang/SkriptParser.java index 7bf5f7e52d2..2976ab179b9 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptParser.java +++ b/src/main/java/ch/njol/skript/lang/SkriptParser.java @@ -59,6 +59,7 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.MatchResult; @@ -1145,33 +1146,105 @@ public static String notOfType(final ClassInfo<?>... cs) { /** * Returns the next character in the expression, skipping strings, variables and parentheses (unless <tt>context</tt> is {@link ParseContext#COMMAND}). * - * @param expr The expression - * @param i The last index + * @param expr The expression to traverse. + * @param startIndex The index to start at. * @return The next index (can be expr.length()), or -1 if an invalid string, variable or bracket is found or if <tt>i >= expr.length()</tt>. * @throws StringIndexOutOfBoundsException if <tt>i < 0</tt> */ - public static int next(final String expr, final int i, final ParseContext context) { - if (i >= expr.length()) - return -1; - if (i < 0) - throw new StringIndexOutOfBoundsException(i); + public static int next(String expr, int startIndex, ParseContext context) { + if (startIndex < 0) + throw new StringIndexOutOfBoundsException(startIndex); if (context == ParseContext.COMMAND) - return i + 1; - final char c = expr.charAt(i); - if (c == '"') { - final int i2 = nextQuote(expr, i + 1); - return i2 < 0 ? -1 : i2 + 1; - } else if (c == '{') { - final int i2 = VariableString.nextVariableBracket(expr, i + 1); - return i2 < 0 ? -1 : i2 + 1; - } else if (c == '(') { - for (int j = i + 1; j >= 0 && j < expr.length(); j = next(expr, j, context)) { - if (expr.charAt(j) == ')') - return j + 1; - } + return startIndex + 1; + + int exprLength = expr.length(); + if (startIndex >= exprLength) + return -1; + + int j; + switch (expr.charAt(startIndex)) { + case '"': + j = nextQuote(expr, startIndex + 1); + return j < 0 ? -1 : j + 1; + case '{': + j = VariableString.nextVariableBracket(expr, startIndex + 1); + return j < 0 ? -1 : j + 1; + case '(': + for (j = startIndex + 1; j >= 0 && j < exprLength; j = next(expr, j, context)) { + if (expr.charAt(j) == ')') + return j + 1; + } + return -1; + default: + return startIndex + 1; + } + } + + /** + * Returns the next occurrence of the needle in the haystack. + * Similar to {@link #next(String, int, ParseContext)}, this method skips + * strings, variables and parentheses (unless <tt>context</tt> is {@link ParseContext#COMMAND}). + * + * @param haystack The string to search in. + * @param needle The string to search for. + * @param startIndex The index to start in within the haystack. + * @param caseSensitive Whether this search will be case-sensitive. + * @return The next index representing the first character of the needle. + * May return -1 if an invalid string, variable or bracket is found or if <tt>startIndex >= hatsack.length()</tt>. + * @throws StringIndexOutOfBoundsException if <tt>startIndex < 0</tt>. + */ + public static int nextOccurrence(String haystack, String needle, int startIndex, ParseContext parseContext, boolean caseSensitive) { + if (startIndex < 0) + throw new StringIndexOutOfBoundsException(startIndex); + if (parseContext == ParseContext.COMMAND) + return haystack.indexOf(needle, startIndex); + + int haystackLength = haystack.length(); + if (startIndex >= haystackLength) return -1; + + if (!caseSensitive) { + haystack = haystack.toLowerCase(Locale.ENGLISH); + needle = needle.toLowerCase(Locale.ENGLISH); } - return i + 1; + + char firstChar = needle.charAt(0); + boolean startsWithSpecialChar = firstChar == '"' || firstChar == '{' || firstChar == '('; + + while (startIndex < haystackLength) { + + char c = haystack.charAt(startIndex); + + if (startsWithSpecialChar) { // Early check before special character handling + if (haystack.startsWith(needle, startIndex)) + return startIndex; + } + + switch (c) { + case '"': + startIndex = nextQuote(haystack, startIndex + 1); + if (startIndex < 0) + return -1; + break; + case '{': + startIndex = VariableString.nextVariableBracket(haystack, startIndex + 1); + if (startIndex < 0) + return -1; + break; + case '(': + startIndex = next(haystack, startIndex, parseContext); // Use other function to skip to right after closing parentheses + if (startIndex < 0) + return -1; + break; + } + + if (haystack.startsWith(needle, startIndex)) + return startIndex; + + startIndex++; + } + + return -1; } private static final Map<String, SkriptPattern> patterns = new ConcurrentHashMap<>(); diff --git a/src/main/java/ch/njol/skript/patterns/LiteralPatternElement.java b/src/main/java/ch/njol/skript/patterns/LiteralPatternElement.java index 113a915c2b3..c4e7d23d9a1 100644 --- a/src/main/java/ch/njol/skript/patterns/LiteralPatternElement.java +++ b/src/main/java/ch/njol/skript/patterns/LiteralPatternElement.java @@ -45,11 +45,17 @@ public MatchResult match(String expr, MatchResult matchResult) { int exprIndex = matchResult.exprOffset; for (char c : literal) { - if (c == ' ') { - if (exprIndex == 0 || exprIndex == exprChars.length || (exprIndex > 0 && exprChars[exprIndex - 1] == ' ')) + if (c == ' ') { // spaces have special handling to account for extraneous spaces within lines + // ignore patterns leading or ending with spaces (or if we have multiple leading spaces) + if (exprIndex == 0 || exprIndex == exprChars.length) continue; - else if (exprChars[exprIndex] != ' ') - return null; + if (exprChars[exprIndex] == ' ') { // pattern is ' fly' and we were given ' fly' + exprIndex++; + continue; + } + if (exprChars[exprIndex - 1] == ' ') // pattern is ' fly' but we were given something like ' fly' or 'fly' + continue; + return null; } else if (exprIndex == exprChars.length || Character.toLowerCase(c) != Character.toLowerCase(exprChars[exprIndex])) return null; exprIndex++; diff --git a/src/main/java/ch/njol/skript/patterns/TypePatternElement.java b/src/main/java/ch/njol/skript/patterns/TypePatternElement.java index 418ff05cdf7..f3cbdb5f666 100644 --- a/src/main/java/ch/njol/skript/patterns/TypePatternElement.java +++ b/src/main/java/ch/njol/skript/patterns/TypePatternElement.java @@ -100,19 +100,47 @@ public static TypePatternElement fromString(String s, int expressionIndex) { @Nullable public MatchResult match(String expr, MatchResult matchResult) { int newExprOffset; + + String nextLiteral = null; + boolean nextLiteralIsWhitespace = false; + if (next == null) { newExprOffset = expr.length(); + } else if (next instanceof LiteralPatternElement) { + nextLiteral = next.toString(); + + nextLiteralIsWhitespace = nextLiteral.trim().isEmpty(); + + if (!nextLiteralIsWhitespace) { // Don't do this for literal patterns that are *only* whitespace - they have their own special handling + // trim trailing whitespace - it can cause issues with optional patterns following the literal + int nextLength = nextLiteral.length(); + for (int i = nextLength; i > 0; i--) { + if (nextLiteral.charAt(i - 1) != ' ') { + if (i != nextLength) + nextLiteral = nextLiteral.substring(0, i); + break; + } + } + } + + newExprOffset = SkriptParser.nextOccurrence(expr, nextLiteral, matchResult.exprOffset, matchResult.parseContext, false); + if (newExprOffset == -1 && nextLiteralIsWhitespace) { // We need to tread more carefully here + // This may be because the next PatternElement is optional or an empty choice (there may be other cases too) + nextLiteral = null; + newExprOffset = SkriptParser.next(expr, matchResult.exprOffset, matchResult.parseContext); + } } else { newExprOffset = SkriptParser.next(expr, matchResult.exprOffset, matchResult.parseContext); - if (newExprOffset == -1) - return null; } + if (newExprOffset == -1) + return null; + ExprInfo exprInfo = getExprInfo(); ParseLogHandler loopLogHandler = SkriptLogger.startParseLogHandler(); try { - for (; newExprOffset != -1; newExprOffset = SkriptParser.next(expr, newExprOffset, matchResult.parseContext)) { + while (newExprOffset != -1) { loopLogHandler.clear(); MatchResult matchResultCopy = matchResult.copy(); @@ -150,6 +178,19 @@ public MatchResult match(String expr, MatchResult matchResult) { expressionLogHandler.printError(); } } + + if (nextLiteral != null) { + int oldNewExprOffset = newExprOffset; + newExprOffset = SkriptParser.nextOccurrence(expr, nextLiteral, newExprOffset + 1, matchResult.parseContext, false); + if (newExprOffset == -1 && nextLiteralIsWhitespace) { + // This may be because the next PatternElement is optional or an empty choice (there may be other cases too) + // So, from this point on, we're going to go character by character + nextLiteral = null; + newExprOffset = SkriptParser.next(expr, oldNewExprOffset, matchResult.parseContext); + } + } else { + newExprOffset = SkriptParser.next(expr, newExprOffset, matchResult.parseContext); + } } } finally { if (!loopLogHandler.isStopped()) From 3e0e47fbc28f751cf8264d00b333cd2fecba0801 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Wed, 25 Jan 2023 23:07:20 -0700 Subject: [PATCH 205/619] Add deprecated methods to support old API for now (#5214) --- .../ch/njol/skript/doc/Documentation.java | 2 +- .../lang/function/FunctionReference.java | 9 +- .../njol/skript/lang/function/Functions.java | 93 ++++++++++++++----- 3 files changed, 76 insertions(+), 28 deletions(-) diff --git a/src/main/java/ch/njol/skript/doc/Documentation.java b/src/main/java/ch/njol/skript/doc/Documentation.java index 0ce241f705f..c40fe0e15e9 100644 --- a/src/main/java/ch/njol/skript/doc/Documentation.java +++ b/src/main/java/ch/njol/skript/doc/Documentation.java @@ -457,7 +457,7 @@ private static String validateHTML(@Nullable String html, final String baseURL) continue linkLoop; } } else if (s[0].equals("../functions/")) { - if (Functions.getFunction("" + s[1], null) != null) + if (Functions.getGlobalFunction("" + s[1]) != null) continue; } else { final int i = CollectionUtils.indexOf(urls, s[0].substring("../".length(), s[0].length() - 1)); diff --git a/src/main/java/ch/njol/skript/lang/function/FunctionReference.java b/src/main/java/ch/njol/skript/lang/function/FunctionReference.java index 723b8987d9a..fe0d355d082 100644 --- a/src/main/java/ch/njol/skript/lang/function/FunctionReference.java +++ b/src/main/java/ch/njol/skript/lang/function/FunctionReference.java @@ -123,7 +123,9 @@ public boolean validateFunction(boolean first) { function = null; SkriptLogger.setNode(node); Skript.debug("Validating function " + functionName); - Signature<?> sign = Functions.getSignature(functionName, script); + Signature<?> sign = Functions.getLocalSignature(functionName, script); + if (sign == null) + sign = Functions.getGlobalSignature(functionName); // Check if the requested function exists if (sign == null) { @@ -267,7 +269,10 @@ public boolean resetReturnValue() { protected T[] execute(Event e) { // If needed, acquire the function reference if (function == null) - function = (Function<? extends T>) Functions.getFunction(functionName, script); + function = (Function<? extends T>) Functions.getLocalFunction(functionName, script); + + if (function == null) + function = (Function<? extends T>) Functions.getGlobalFunction(functionName); if (function == null) { // It might be impossible to resolve functions in some cases! Skript.error("Couldn't resolve call for '" + functionName + "'."); 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 8ac316b1ab7..2a0f2087e7b 100644 --- a/src/main/java/ch/njol/skript/lang/function/Functions.java +++ b/src/main/java/ch/njol/skript/lang/function/Functions.java @@ -227,51 +227,94 @@ private static Signature<?> signError(String error) { /** * Gets a function, if it exists. Note that even if function exists in scripts, * it might not have been parsed yet. If you want to check for existence, - * then use {@link #getSignature(String, String)}. + * then use {@link #getGlobalSignature(String)}. + * + * @deprecated in favour of {@link #getGlobalFunction(String)} for proper name. + * @param name Name of function. + * @return Function, or null if it does not exist. + */ + @Deprecated + @Nullable + public static Function<?> getFunction(String name) { + return getGlobalFunction(name); + } + + /** + * Gets a function, if it exists. Note that even if function exists in scripts, + * it might not have been parsed yet. If you want to check for existence, + * then use {@link #getGlobalSignature(String)}. + * + * @param name Name of function. + * @return Function, or null if it does not exist. + */ + @Nullable + public static Function<?> getGlobalFunction(String name) { + Namespace namespace = globalFunctions.get(name); + if (namespace == null) + return null; + return namespace.getFunction(name, false); + } + + /** + * Gets a function, if it exists. Note that even if function exists in scripts, + * it might not have been parsed yet. If you want to check for existence, + * then use {@link #getLocalSignature(String, String)}. * * @param name Name of function. * @param script The script where the function is declared in. Used to get local functions. * @return Function, or null if it does not exist. */ @Nullable - public static Function<?> getFunction(String name, @Nullable String script) { + public static Function<?> getLocalFunction(String name, String script) { Namespace namespace = null; Function<?> function = null; - if (script != null) { - namespace = getScriptNamespace(script); - if (namespace != null) - function = namespace.getFunction(name); - } - if (namespace == null || function == null) { - namespace = globalFunctions.get(name); - if (namespace == null) - return null; - function = namespace.getFunction(name, false); - } + namespace = getScriptNamespace(script); + if (namespace != null) + function = namespace.getFunction(name); return function; } /** * Gets a signature of function with given name. + * + * @deprecated in favour of {@link #getGlobalSignature(String)} for proper name. + * @param name Name of function. + * @return Signature, or null if function does not exist. + */ + @Deprecated + @Nullable + public static Signature<?> getSignature(String name) { + return getGlobalSignature(name); + } + + /** + * Gets a signature of function with given name. + * + * @param name Name of function. + * @return Signature, or null if function does not exist. + */ + @Nullable + public static Signature<?> getGlobalSignature(String name) { + Namespace namespace = globalFunctions.get(name); + if (namespace == null) + return null; + return namespace.getSignature(name, false); + } + + /** + * Gets a signature of function with given name. + * * @param name Name of function. * @param script The script where the function is declared in. Used to get local functions. * @return Signature, or null if function does not exist. */ @Nullable - public static Signature<?> getSignature(String name, @Nullable String script) { + public static Signature<?> getLocalSignature(String name, String script) { Namespace namespace = null; Signature<?> signature = null; - if (script != null) { - namespace = getScriptNamespace(script); - if (namespace != null) - signature = namespace.getSignature(name); - } - if (namespace == null || signature == null) { - namespace = globalFunctions.get(name); - if (namespace == null) - return null; - signature = namespace.getSignature(name, false); - } + namespace = getScriptNamespace(script); + if (namespace != null) + signature = namespace.getSignature(name); return signature; } From 40fb4dcf4456d194aa6d329e5453285c38700771 Mon Sep 17 00:00:00 2001 From: TheCodingDuck <71715967+colton-boi@users.noreply.github.com> Date: Thu, 26 Jan 2023 02:57:52 -0500 Subject: [PATCH 206/619] Support for unequipping items for EffEquip (#5363) --- .../java/ch/njol/skript/effects/EffEquip.java | 170 ++++++++++-------- 1 file changed, 91 insertions(+), 79 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffEquip.java b/src/main/java/ch/njol/skript/effects/EffEquip.java index 454bfab267d..5f3e4ca907a 100644 --- a/src/main/java/ch/njol/skript/effects/EffEquip.java +++ b/src/main/java/ch/njol/skript/effects/EffEquip.java @@ -21,7 +21,6 @@ import org.bukkit.Material; import org.bukkit.entity.AbstractHorse; import org.bukkit.entity.ChestedHorse; -import org.bukkit.entity.Horse; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Llama; import org.bukkit.entity.Pig; @@ -29,7 +28,6 @@ import org.bukkit.entity.Steerable; import org.bukkit.event.Event; import org.bukkit.inventory.EntityEquipment; -import org.bukkit.inventory.HorseInventory; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.LlamaInventory; @@ -52,37 +50,52 @@ * @author Peter Güttinger */ @Name("Equip") -@Description("Equips an entity with some given armor. This will replace any armor that the entity is wearing.") -@Examples({"equip player with diamond helmet", - "equip player with all diamond armor"}) -@Since("1.0, INSERT VERSION (multiple entities)") +@Description("Equips or unequips an entity with some given armor. This will replace any armor that the entity is wearing.") +@Examples({ + "equip player with diamond helmet", + "equip player with all diamond armor", + "unequip diamond chestplate from player", + "unequip all armor from player", + "unequip player's armor" +}) +@Since("1.0, INSERT VERSION (multiple entities, unequip)") public class EffEquip extends Effect { static { Skript.registerEffect(EffEquip.class, "equip [%livingentities%] with %itemtypes%", - "make %livingentities% wear %itemtypes%"); + "make %livingentities% wear %itemtypes%", + "unequip %itemtypes% [from %livingentities%]", + "unequip %livingentities%'[s] (armor|equipment)" + ); } - @SuppressWarnings("null") + @SuppressWarnings("NotNullFieldNotInitialized") private Expression<LivingEntity> entities; - @SuppressWarnings("null") - private Expression<ItemType> types; + @Nullable + private Expression<ItemType> itemTypes; + + private boolean equip = true; - @SuppressWarnings({"unchecked", "null"}) @Override + @SuppressWarnings("unchecked") public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) { - entities = (Expression<LivingEntity>) exprs[0]; - types = (Expression<ItemType>) exprs[1]; + if (matchedPattern == 0 || matchedPattern == 1) { + entities = (Expression<LivingEntity>) exprs[0]; + itemTypes = (Expression<ItemType>) exprs[1]; + } else if (matchedPattern == 2) { + itemTypes = (Expression<ItemType>) exprs[0]; + entities = (Expression<LivingEntity>) exprs[1]; + equip = false; + } else if (matchedPattern == 3) { + entities = (Expression<LivingEntity>) exprs[0]; + equip = false; + } return true; } - private static final boolean SUPPORTS_HORSES = Skript.classExists("org.bukkit.entity.Horse"); - private static final boolean NEW_HORSES = Skript.classExists("org.bukkit.entity.AbstractHorse"); - private static final boolean SUPPORTS_LLAMAS = Skript.classExists("org.bukkit.entity.Llama"); private static final boolean SUPPORTS_STEERABLE = Skript.classExists("org.bukkit.entity.Steerable"); - private static final ItemType HELMET = Aliases.javaItemType("helmet"); private static final ItemType CHESTPLATE = Aliases.javaItemType("chestplate"); private static final ItemType LEGGINGS = Aliases.javaItemType("leggings"); private static final ItemType BOOTS = Aliases.javaItemType("boots"); @@ -91,95 +104,94 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye private static final ItemType CHEST = Aliases.javaItemType("chest"); private static final ItemType CARPET = Aliases.javaItemType("carpet"); + private static final ItemType[] ALL_EQUIPMENT = new ItemType[] {CHESTPLATE, LEGGINGS, BOOTS, HORSE_ARMOR, SADDLE, CHEST, CARPET}; + @Override - @SuppressWarnings("deprecation") protected void execute(Event event) { - ItemType[] ts = types.getArray(event); - for (LivingEntity en : entities.getArray(event)) { - if (SUPPORTS_STEERABLE && en instanceof Steerable) { - for (ItemType it : ts) { - if (SADDLE.isOfType(it.getMaterial())) { - ((Steerable) en).setSaddle(true); + ItemType[] itemTypes; + boolean unequipHelmet = false; + if (this.itemTypes != null) { + itemTypes = this.itemTypes.getArray(event); + } else { + itemTypes = ALL_EQUIPMENT; + unequipHelmet = true; + } + for (LivingEntity entity : entities.getArray(event)) { + if (SUPPORTS_STEERABLE && entity instanceof Steerable) { + for (ItemType itemType : itemTypes) { + if (SADDLE.isOfType(itemType.getMaterial())) { + ((Steerable) entity).setSaddle(equip); } } - } else if (en instanceof Pig) { - for (ItemType t : ts) { - if (t.isOfType(Material.SADDLE)) { - ((Pig) en).setSaddle(true); + } else if (entity instanceof Pig) { + for (ItemType itemType : itemTypes) { + if (itemType.isOfType(Material.SADDLE)) { + ((Pig) entity).setSaddle(equip); break; } } - continue; - } else if (SUPPORTS_LLAMAS && en instanceof Llama) { - LlamaInventory invi = ((Llama) en).getInventory(); - for (ItemType t : ts) { - for (ItemStack item : t.getAll()) { + } else if (entity instanceof Llama) { + LlamaInventory inv = ((Llama) entity).getInventory(); + for (ItemType itemType : itemTypes) { + for (ItemStack item : itemType.getAll()) { if (CARPET.isOfType(item)) { - invi.setDecor(item); + inv.setDecor(equip ? item : null); } else if (CHEST.isOfType(item)) { - ((Llama) en).setCarryingChest(true); + ((Llama) entity).setCarryingChest(equip); } } } - continue; - } else if (NEW_HORSES && en instanceof AbstractHorse) { + } else if (entity instanceof AbstractHorse) { // Spigot's API is bad, just bad... Abstract horse doesn't have horse inventory! - Inventory invi = ((AbstractHorse) en).getInventory(); - for (ItemType t : ts) { - for (ItemStack item : t.getAll()) { + Inventory inv = ((AbstractHorse) entity).getInventory(); + for (ItemType itemType : itemTypes) { + for (ItemStack item : itemType.getAll()) { if (SADDLE.isOfType(item)) { - invi.setItem(0, item); // Slot 0=saddle + inv.setItem(0, equip ? item : null); // Slot 0=saddle } else if (HORSE_ARMOR.isOfType(item)) { - invi.setItem(1, item); // Slot 1=armor - } else if (CHEST.isOfType(item) && en instanceof ChestedHorse) { - ((ChestedHorse) en).setCarryingChest(true); + inv.setItem(1, equip ? item : null); // Slot 1=armor + } else if (CHEST.isOfType(item) && entity instanceof ChestedHorse) { + ((ChestedHorse) entity).setCarryingChest(equip); } } } - continue; - } else if (SUPPORTS_HORSES && en instanceof Horse) { - HorseInventory invi = ((Horse) en).getInventory(); - for (ItemType t : ts) { - for (ItemStack item : t.getAll()) { - if (SADDLE.isOfType(item)) { - invi.setSaddle(item); - } else if (HORSE_ARMOR.isOfType(item)) { - invi.setArmor(item); - } else if (CHEST.isOfType(item)) { - ((Horse) en).setCarryingChest(true); + } else { + EntityEquipment equipment = entity.getEquipment(); + if (equipment == null) + continue; + for (ItemType itemType : itemTypes) { + for (ItemStack item : itemType.getAll()) { + if (CHESTPLATE.isOfType(item)) { + equipment.setChestplate(equip ? item : null); + } else if (LEGGINGS.isOfType(item)) { + equipment.setLeggings(equip ? item : null); + } else if (BOOTS.isOfType(item)) { + equipment.setBoots(equip ? item : null); + } else { + // Apply all other items to head, as all items will appear on a player's head + equipment.setHelmet(equip ? item : null); } } + if (unequipHelmet) { // Since players can wear any helmet, itemTypes won't have the item in the array every time + equipment.setHelmet(null); + } } - continue; - } - EntityEquipment equip = en.getEquipment(); - if (equip == null) - continue; - for (ItemType t : ts) { - for (ItemStack item : t.getAll()) { - // Blocks are visible in head slot, too - // TODO skulls; waiting for decoration aliases - if (HELMET.isOfType(item) || item.getType().isBlock()) - equip.setHelmet(item); - else if (CHESTPLATE.isOfType(item)) - equip.setChestplate(item); - else if (LEGGINGS.isOfType(item)) - equip.setLeggings(item); - else if (BOOTS.isOfType(item)) - equip.setBoots(item); - - // We have no idea where to equip other items - // User can set them to slot they need custom hats etc. - } + if (entity instanceof Player) + PlayerUtils.updateInventory((Player) entity); } - if (en instanceof Player) - PlayerUtils.updateInventory((Player) en); } } @Override public String toString(@Nullable Event event, boolean debug) { - return "equip " + entities.toString(event, debug) + " with " + types.toString(event, debug); + if (equip) { + assert itemTypes != null; + return "equip " + entities.toString(event, debug) + " with " + itemTypes.toString(event, debug); + } else if (itemTypes != null) { + return "unequip " + itemTypes.toString(event, debug) + " from " + entities.toString(event, debug); + } else { + return "unequip " + entities.toString(event, debug) + "'s equipment"; + } } } From 42710155e345dd221966809813621157a2f6e64f Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Thu, 26 Jan 2023 15:02:35 -0700 Subject: [PATCH 207/619] Fix assertion effect throwing errors when verbose is in debug (#5349) --- .../java/ch/njol/skript/tests/runner/EffAssert.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/ch/njol/skript/tests/runner/EffAssert.java b/src/main/java/ch/njol/skript/tests/runner/EffAssert.java index e86fe7127cb..04e4e9ce786 100644 --- a/src/main/java/ch/njol/skript/tests/runner/EffAssert.java +++ b/src/main/java/ch/njol/skript/tests/runner/EffAssert.java @@ -46,9 +46,9 @@ public class EffAssert extends Effect { Skript.registerEffect(EffAssert.class, "assert <.+> [(1¦to fail)] with %string%"); } - @SuppressWarnings("null") + @Nullable private Condition condition; - + @SuppressWarnings("null") private Expression<String> errorMsg; @@ -100,7 +100,10 @@ public TriggerItem walk(Event e) { } @Override - public String toString(@Nullable Event e, boolean debug) { - return "assert " + condition.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + if (condition == null) + return "assertion"; + return "assert " + condition.toString(event, debug); } + } From b2d6c8b122f2178503d38416e042ab5549e5f10b Mon Sep 17 00:00:00 2001 From: Shane Bee <shanebolenback@me.com> Date: Thu, 26 Jan 2023 17:43:23 -0800 Subject: [PATCH 208/619] EffCancelEvent - update player inventory after cancelling (#5362) --- src/main/java/ch/njol/skript/effects/EffCancelEvent.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/ch/njol/skript/effects/EffCancelEvent.java b/src/main/java/ch/njol/skript/effects/EffCancelEvent.java index 8518ecb7b98..e5b3ba4f2b5 100644 --- a/src/main/java/ch/njol/skript/effects/EffCancelEvent.java +++ b/src/main/java/ch/njol/skript/effects/EffCancelEvent.java @@ -18,10 +18,12 @@ */ package ch.njol.skript.effects; +import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.Event; import org.bukkit.event.Event.Result; import org.bukkit.event.block.BlockCanBuildEvent; +import org.bukkit.event.inventory.InventoryInteractEvent; import org.bukkit.event.player.PlayerDropItemEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerLoginEvent; @@ -92,6 +94,8 @@ public void execute(final Event e) { ((BlockCanBuildEvent) e).setBuildable(!cancel); } else if (e instanceof PlayerDropItemEvent) { PlayerUtils.updateInventory(((PlayerDropItemEvent) e).getPlayer()); + } else if (e instanceof InventoryInteractEvent) { + PlayerUtils.updateInventory(((Player) ((InventoryInteractEvent) e).getWhoClicked())); } } From e74c256a0e0d86ae6eec4f770788672d2998dc0d Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Fri, 27 Jan 2023 02:55:49 +0100 Subject: [PATCH 209/619] Optimize list parsing (#5133) --- .../ch/njol/skript/lang/SkriptParser.java | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/main/java/ch/njol/skript/lang/SkriptParser.java b/src/main/java/ch/njol/skript/lang/SkriptParser.java index 2976ab179b9..ec96022e262 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptParser.java +++ b/src/main/java/ch/njol/skript/lang/SkriptParser.java @@ -48,6 +48,7 @@ import ch.njol.util.StringUtils; import ch.njol.util.coll.CollectionUtils; import com.google.common.primitives.Booleans; +import org.bukkit.event.EventPriority; import org.bukkit.plugin.java.JavaPlugin; import org.eclipse.jdt.annotation.Nullable; import org.skriptlang.skript.lang.script.Script; @@ -608,8 +609,9 @@ else if (!hasSingular && hasPlural) * group 1 is null for ',', otherwise it's one of and/or/nor (not necessarily lowercase). */ @SuppressWarnings("null") - public final static Pattern listSplitPattern = Pattern.compile("\\s*,?\\s+(and|n?or)\\s+|\\s*,\\s*", Pattern.CASE_INSENSITIVE); - + public static final Pattern LIST_SPLIT_PATTERN = Pattern.compile("\\s*,?\\s+(and|n?or)\\s+|\\s*,\\s*", Pattern.CASE_INSENSITIVE); + public static final Pattern OR_PATTERN = Pattern.compile("\\sor\\s", Pattern.CASE_INSENSITIVE); + private final static String MULTIPLE_AND_OR = "List has multiple 'and' or 'or', will default to 'and'. Use brackets if you want to define multiple lists."; private final static String MISSING_AND_OR = "List is missing 'and' or 'or', defaulting to 'and'"; @@ -645,7 +647,7 @@ public final <T> Expression<? extends T> parseExpression(final Class<? extends T final List<int[]> pieces = new ArrayList<>(); { - final Matcher m = listSplitPattern.matcher(expr); + final Matcher m = LIST_SPLIT_PATTERN.matcher(expr); int i = 0, j = 0; for (; i >= 0 && i <= expr.length(); i = next(expr, i, context)) { if (i == expr.length() || m.region(i, expr.length()).lookingAt()) { @@ -676,9 +678,10 @@ public final <T> Expression<? extends T> parseExpression(final Class<? extends T log.printError(); return null; } - + + // `b` is the first piece included, `a` is the last outer: for (int b = 0; b < pieces.size();) { - for (int a = pieces.size() - b; a >= 1; a--) { + for (int a = 1; a <= pieces.size() - b; a++) { if (b == 0 && a == pieces.size()) // i.e. the whole expression - already tried to parse above continue; final int x = pieces.get(b)[0], y = pieces.get(b + a - 1)[1]; @@ -767,7 +770,7 @@ public final Expression<?> parseExpression(final ExprInfo vi) { final List<int[]> pieces = new ArrayList<>(); { - final Matcher m = listSplitPattern.matcher(expr); + final Matcher m = LIST_SPLIT_PATTERN.matcher(expr); int i = 0, j = 0; for (; i >= 0 && i <= expr.length(); i = next(expr, i, context)) { if (i == expr.length() || m.region(i, expr.length()).lookingAt()) { @@ -798,9 +801,17 @@ public final Expression<?> parseExpression(final ExprInfo vi) { log.printError(); return null; } - + + // Early check if this can be parsed as a list. + // The only case where multiple expressions are allowed, is when it is an 'or' list + if (!vi.isPlural[0] && !OR_PATTERN.matcher(expr).find()) { + log.printError(); + return null; + } + + // `b` is the first piece included, `a` is the last outer: for (int b = 0; b < pieces.size();) { - for (int a = pieces.size() - b; a >= 1; a--) { + for (int a = 1; a <= pieces.size() - b; a++) { if (b == 0 && a == pieces.size()) // i.e. the whole expression - already tried to parse above continue; final int x = pieces.get(b)[0], y = pieces.get(b + a - 1)[1]; @@ -836,11 +847,11 @@ public final Expression<?> parseExpression(final ExprInfo vi) { log.printError(); return null; } - + // Check if multiple values are accepted // If not, only 'or' lists are allowed // (both 'and' and potentially 'and' lists will not be accepted) - if (vi.isPlural[0] == false && !and.isFalse()) { + if (!vi.isPlural[0] && !and.isFalse()) { // List cannot be used in place of a single value here log.printError(); return null; @@ -1422,5 +1433,12 @@ private static ExprInfo createExprInfo(String s) throws IllegalArgumentException private static ParserInstance getParser() { return ParserInstance.get(); } + + /** + * @deprecated due to bad naming conventions, + * use {@link #LIST_SPLIT_PATTERN} instead. + */ + @Deprecated + public final static Pattern listSplitPattern = LIST_SPLIT_PATTERN; } From f7eb3d252c7e1f0afe8c00f56fb4f7a2c301c1a7 Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Sat, 28 Jan 2023 05:21:23 +0100 Subject: [PATCH 210/619] Fix/number converters (#5079) --- .../ch/njol/skript/classes/data/DefaultConverters.java | 10 +++++++--- .../skript/tests/regressions/4445-number-conversion.sk | 7 +++++++ 2 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 src/test/skript/tests/regressions/4445-number-conversion.sk diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java b/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java index 1ddbd0b7cbd..5fcd02a82b2 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java @@ -57,9 +57,13 @@ public class DefaultConverters { public DefaultConverters() {} static { - - // Integer - Long - Converters.registerConverter(Integer.class, Long.class, Integer::longValue); + // Number to subtypes converters + Converters.registerConverter(Number.class, Byte.class, Number::byteValue); + Converters.registerConverter(Number.class, Double.class, Number::doubleValue); + Converters.registerConverter(Number.class, Float.class, Number::floatValue); + Converters.registerConverter(Number.class, Integer.class, Number::intValue); + Converters.registerConverter(Number.class, Long.class, Number::longValue); + Converters.registerConverter(Number.class, Short.class, Number::shortValue); // OfflinePlayer - PlayerInventory Converters.registerConverter(OfflinePlayer.class, PlayerInventory.class, p -> { diff --git a/src/test/skript/tests/regressions/4445-number-conversion.sk b/src/test/skript/tests/regressions/4445-number-conversion.sk new file mode 100644 index 00000000000..3a7467743ce --- /dev/null +++ b/src/test/skript/tests/regressions/4445-number-conversion.sk @@ -0,0 +1,7 @@ +test "number conversion": + set {_i} to 3 + set {_j} to 2 + numberConversionFunction({_i} + {_j}) + +function numberConversionFunction(i: integer): + assert {_i} is 5 with "Number conversion failed, value: %{_i}%" From 1d6554ab8474fa404b4d0fc987d3c7f50c43659c Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Sat, 28 Jan 2023 16:30:05 +0100 Subject: [PATCH 211/619] Improve hashCode of Pair (#5407) --- src/main/java/ch/njol/util/Pair.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/njol/util/Pair.java b/src/main/java/ch/njol/util/Pair.java index d79f9ac1f7f..4af86f9d404 100644 --- a/src/main/java/ch/njol/util/Pair.java +++ b/src/main/java/ch/njol/util/Pair.java @@ -20,6 +20,7 @@ import java.io.Serializable; import java.util.Map.Entry; +import java.util.Objects; import org.eclipse.jdt.annotation.Nullable; @@ -96,9 +97,7 @@ public final boolean equals(final @Nullable Object obj) { */ @Override public final int hashCode() { - final T1 first = this.first; - final T2 second = this.second; - return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode()); + return Objects.hash(first, second); } @Override From 445ff870717a6a8eadeaccf9b533a36a352c8ed3 Mon Sep 17 00:00:00 2001 From: Fusezion <fusezionstream@gmail.com> Date: Mon, 30 Jan 2023 04:46:00 -0500 Subject: [PATCH 212/619] Fix Exmaples of ExprFormatDate.java (#5413) --- src/main/java/ch/njol/skript/expressions/ExprFormatDate.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprFormatDate.java b/src/main/java/ch/njol/skript/expressions/ExprFormatDate.java index 64f07c9561f..df7f32cf925 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprFormatDate.java +++ b/src/main/java/ch/njol/skript/expressions/ExprFormatDate.java @@ -47,7 +47,7 @@ "command /date:", "\ttrigger:", "\t\tsend \"Full date: %now formatted human-readable%\" to sender", - "\t\tsend \"Short date: %now formatted as \"\"yyyy-MM-dd\"\"%\" to sender" + "\t\tsend \"Short date: %now formatted as \"yyyy-MM-dd\"%\" to sender" }) @Since("2.2-dev31, INSERT VERSION (support variables in format)") public class ExprFormatDate extends PropertyExpression<Date, String> { From ff668189cfe163369ef013a8f93c43e196f8a986 Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Mon, 30 Jan 2023 15:09:14 +0100 Subject: [PATCH 213/619] Fix empty string parsing (#5415) --- .../java/ch/njol/skript/lang/SkriptParser.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main/java/ch/njol/skript/lang/SkriptParser.java b/src/main/java/ch/njol/skript/lang/SkriptParser.java index ec96022e262..d227e337811 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptParser.java +++ b/src/main/java/ch/njol/skript/lang/SkriptParser.java @@ -1155,23 +1155,28 @@ public static String notOfType(final ClassInfo<?>... cs) { } /** - * Returns the next character in the expression, skipping strings, variables and parentheses (unless <tt>context</tt> is {@link ParseContext#COMMAND}). + * Returns the next character in the expression, skipping strings, + * variables and parentheses + * (unless {@code context} is {@link ParseContext#COMMAND}). * * @param expr The expression to traverse. * @param startIndex The index to start at. - * @return The next index (can be expr.length()), or -1 if an invalid string, variable or bracket is found or if <tt>i >= expr.length()</tt>. - * @throws StringIndexOutOfBoundsException if <tt>i < 0</tt> + * @return The next index (can be expr.length()), or -1 if + * an invalid string, variable or bracket is found + * or if {@code startIndex >= expr.length()}. + * @throws StringIndexOutOfBoundsException if {@code startIndex < 0}. */ public static int next(String expr, int startIndex, ParseContext context) { if (startIndex < 0) throw new StringIndexOutOfBoundsException(startIndex); - if (context == ParseContext.COMMAND) - return startIndex + 1; int exprLength = expr.length(); if (startIndex >= exprLength) return -1; + if (context == ParseContext.COMMAND) + return startIndex + 1; + int j; switch (expr.charAt(startIndex)) { case '"': From 1b9b42397c1f388177b4fee562291b4b826c9573 Mon Sep 17 00:00:00 2001 From: Shane Bee <shanebolenback@me.com> Date: Mon, 30 Jan 2023 15:12:38 -0800 Subject: [PATCH 214/619] FishData - add tadpole (#5412) --- .../java/ch/njol/skript/entity/FishData.java | 45 ++++++++++++------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/src/main/java/ch/njol/skript/entity/FishData.java b/src/main/java/ch/njol/skript/entity/FishData.java index 2f06ff3e449..c3694a12f6b 100644 --- a/src/main/java/ch/njol/skript/entity/FishData.java +++ b/src/main/java/ch/njol/skript/entity/FishData.java @@ -18,12 +18,16 @@ */ package ch.njol.skript.entity; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.concurrent.ThreadLocalRandom; import org.bukkit.entity.Cod; import org.bukkit.entity.Fish; import org.bukkit.entity.PufferFish; import org.bukkit.entity.Salmon; +import org.bukkit.entity.Tadpole; import org.bukkit.entity.TropicalFish; import org.eclipse.jdt.annotation.Nullable; @@ -33,9 +37,14 @@ public class FishData extends EntityData<Fish> { + private static final boolean HAS_TADPOLE = Skript.classExists("org.bukkit.entity.Tadpole"); + private static final List<String> PATTERNS; + static { - register(FishData.class, "fish", Fish.class, 0, - "fish", "cod", "puffer fish", "salmon", "tropical fish"); + PATTERNS = new ArrayList<>(Arrays.asList("fish", "cod", "puffer fish", "salmon", "tropical fish")); + if (HAS_TADPOLE) + PATTERNS.add("tadpole"); + register(FishData.class, "fish", Fish.class, 0, PATTERNS.toArray(new String[0])); } private boolean init = true; @@ -53,7 +62,7 @@ public FishData(int pattern) { protected boolean init(Literal<?>[] exprs, int matchedPattern, ParseResult parseResult) { if (matchedPattern == 0) wildcard = true; - pattern = (matchedPattern == 0) ? ThreadLocalRandom.current().nextInt(1, 5) : matchedPattern; + pattern = (matchedPattern == 0) ? ThreadLocalRandom.current().nextInt(1, PATTERNS.size()) : matchedPattern; return true; } @@ -62,7 +71,7 @@ protected boolean init(@Nullable Class<? extends Fish> c, @Nullable Fish e) { int matchedPattern = getInitPattern(e); if (matchedPattern == 0) wildcard = true; - pattern = (matchedPattern == 0) ? ThreadLocalRandom.current().nextInt(1, 5) : matchedPattern; + pattern = (matchedPattern == 0) ? ThreadLocalRandom.current().nextInt(1, PATTERNS.size()) : matchedPattern; return true; } @@ -90,6 +99,8 @@ public Class<? extends Fish> getType() { return Salmon.class; case 4: return TropicalFish.class; + case 5: + return Tadpole.class; } return Fish.class; } @@ -105,31 +116,33 @@ protected int hashCode_i() { } @Override - protected boolean equals_i(EntityData<?> obj) { - return obj instanceof Fish ? (wildcard ? true : getPattern((Fish) obj) == pattern) : false; + protected boolean equals_i(EntityData<?> entityData) { + return entityData instanceof Fish ? (wildcard ? true : getPattern((Fish) entityData) == pattern) : false; } @Override - public boolean isSupertypeOf(EntityData<?> e) { - return e instanceof Fish ? (wildcard ? true : getPattern((Fish) e) == pattern) : false; + public boolean isSupertypeOf(EntityData<?> entityData) { + return entityData instanceof Fish ? (wildcard ? true : getPattern((Fish) entityData) == pattern) : false; } - private static int getInitPattern(@Nullable Fish f) { - if (f == null) + private static int getInitPattern(@Nullable Fish fish) { + if (fish == null) return 0; - else if (f instanceof Cod) + else if (fish instanceof Cod) return 1; - else if (f instanceof PufferFish) + else if (fish instanceof PufferFish) return 2; - else if (f instanceof Salmon) + else if (fish instanceof Salmon) return 3; - else if (f instanceof TropicalFish) + else if (fish instanceof TropicalFish) return 4; + else if (HAS_TADPOLE && fish instanceof Tadpole) + return 5; return 0; } - private int getPattern(@Nullable Fish f) { - int p = getInitPattern(f); + private int getPattern(@Nullable Fish fish) { + int p = getInitPattern(fish); return p == 0 ? pattern : p; } From e2e35c6e44311d50e5d5dbd84bd050330d87e5c1 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Tue, 31 Jan 2023 15:41:51 -0500 Subject: [PATCH 215/619] Deprecate SelfRegisteringSkriptEvent (#5140) --- .../classes/data/BukkitEventValues.java | 8 +- .../java/ch/njol/skript/events/EvtAtTime.java | 253 ++++++++---------- .../java/ch/njol/skript/events/EvtChat.java | 127 --------- .../skript/events/EvtExperienceSpawn.java | 175 ++++++------ .../java/ch/njol/skript/events/EvtMoveOn.java | 165 ++++++------ .../ch/njol/skript/events/EvtPeriodical.java | 151 +++++------ .../java/ch/njol/skript/events/EvtScript.java | 83 +++--- .../java/ch/njol/skript/events/EvtSkript.java | 108 ++++---- .../ch/njol/skript/events/SimpleEvents.java | 21 ++ .../events/util/PlayerChatEventHandler.java | 47 ---- .../njol/skript/events/util/package-info.java | 27 -- .../njol/skript/expressions/ExprMessage.java | 19 +- .../hooks/regions/events/EvtRegionBorder.java | 228 ++++++++-------- .../lang/SelfRegisteringSkriptEvent.java | 24 ++ .../java/ch/njol/skript/lang/SkriptEvent.java | 69 +++-- .../skript/lang/structure/Structure.java | 6 +- 16 files changed, 663 insertions(+), 848 deletions(-) delete mode 100644 src/main/java/ch/njol/skript/events/EvtChat.java delete mode 100644 src/main/java/ch/njol/skript/events/util/PlayerChatEventHandler.java delete mode 100644 src/main/java/ch/njol/skript/events/util/package-info.java 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 07630e88a7f..333eaa48d24 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -22,7 +22,6 @@ import ch.njol.skript.aliases.Aliases; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.command.CommandEvent; -import ch.njol.skript.events.EvtMoveOn; import ch.njol.skript.events.bukkit.ScriptEvent; import ch.njol.skript.events.bukkit.SkriptStartEvent; import ch.njol.skript.events.bukkit.SkriptStopEvent; @@ -835,11 +834,10 @@ public Entity get(final PlayerShearEntityEvent e) { // PlayerMoveEvent EventValues.registerEventValue(PlayerMoveEvent.class, Block.class, new Getter<Block, PlayerMoveEvent>() { @Override - @Nullable - public Block get(final PlayerMoveEvent e) { - return EvtMoveOn.getBlock(e); + public Block get(PlayerMoveEvent event) { + return event.getTo().clone().subtract(0, 0.5, 0).getBlock(); } - }, 0); + }, EventValues.TIME_NOW); // PlayerItemDamageEvent EventValues.registerEventValue(PlayerItemDamageEvent.class, ItemStack.class, new Getter<ItemStack, PlayerItemDamageEvent>() { @Override diff --git a/src/main/java/ch/njol/skript/events/EvtAtTime.java b/src/main/java/ch/njol/skript/events/EvtAtTime.java index 76a928d731d..17d37f00c5c 100644 --- a/src/main/java/ch/njol/skript/events/EvtAtTime.java +++ b/src/main/java/ch/njol/skript/events/EvtAtTime.java @@ -18,34 +18,30 @@ */ package ch.njol.skript.events; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map.Entry; - -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.SkriptEventHandler; import ch.njol.skript.events.bukkit.ScheduledEvent; import ch.njol.skript.lang.Literal; -import ch.njol.skript.lang.SelfRegisteringSkriptEvent; +import ch.njol.skript.lang.SkriptEvent; import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.lang.Trigger; import ch.njol.skript.registrations.Classes; import ch.njol.skript.util.Time; import ch.njol.util.Math2; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; + +public class EvtAtTime extends SkriptEvent implements Comparable<EvtAtTime> { -/** - * @author Peter Güttinger - */ -@SuppressFBWarnings("EQ_COMPARETO_USE_OBJECT_EQUALS") -public class EvtAtTime extends SelfRegisteringSkriptEvent implements Comparable<EvtAtTime> { static { Skript.registerEvent("*At Time", EvtAtTime.class, ScheduledEvent.class, "at %time% [in %worlds%]") .description("An event that occurs at a given <a href='./classes.html#time'>minecraft time</a> in every world or only in specific worlds.") @@ -53,147 +49,130 @@ public class EvtAtTime extends SelfRegisteringSkriptEvent implements Comparable< .since("1.3.4"); } - private final static int CHECKPERIOD = 10; - - private final static class EvtAtInfo { - public EvtAtInfo() {} - - int lastTick; // as Bukkit's scheduler is inconsistent this saves the exact tick when the events were last checked - int currentIndex; - ArrayList<EvtAtTime> list = new ArrayList<>(); + private static final int CHECK_PERIOD = 10; + + private static final Map<World, EvtAtInfo> TRIGGERS = new ConcurrentHashMap<>(); + + private static final class EvtAtInfo { + private int lastTick; // as Bukkit's scheduler is inconsistent this saves the exact tick when the events were last checked + private int currentIndex; + private final List<EvtAtTime> instances = new ArrayList<>(); } - - final static HashMap<World, EvtAtInfo> triggers = new HashMap<>(); - - @Nullable - private Trigger t; - int tick; - - @SuppressWarnings("null") - private transient World[] worlds; - /** - * null if all worlds - */ - @Nullable - private String[] worldNames = null; - - @SuppressWarnings({"unchecked", "null"}) + + private int tick; + + @SuppressWarnings("NotNullFieldNotInitialized") + private World[] worlds; + @Override - public boolean init(final Literal<?>[] args, final int matchedPattern, final ParseResult parser) { + @SuppressWarnings("unchecked") + public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult) { tick = ((Literal<Time>) args[0]).getSingle().getTicks(); worlds = args[1] == null ? Bukkit.getWorlds().toArray(new World[0]) : ((Literal<World>) args[1]).getAll(); - if (args[1] != null) { - worldNames = new String[worlds.length]; - for (int i = 0; i < worlds.length; i++) - worldNames[i] = worlds[i].getName(); - } return true; } - - private static int taskID = -1; - - private static void registerListener() { - if (taskID != -1) - return; - taskID = Bukkit.getScheduler().scheduleSyncRepeatingTask(Skript.getInstance(), new Runnable() { - @SuppressWarnings("null") - @Override - public void run() { - for (final Entry<World, EvtAtInfo> e : triggers.entrySet()) { - final EvtAtInfo i = e.getValue(); - final int tick = (int) e.getKey().getTime(); - if (i.lastTick == tick) // stupid Bukkit scheduler - continue; - if (i.lastTick + CHECKPERIOD * 2 < tick || i.lastTick > tick && i.lastTick - 24000 + CHECKPERIOD * 2 < tick) { // time changed, e.g. by a command or plugin - i.lastTick = Math2.mod(tick - CHECKPERIOD, 24000); - } - final boolean midnight = i.lastTick > tick; // actually 6:00 - if (midnight) - i.lastTick -= 24000; - final int startIndex = i.currentIndex; - while (true) { - final EvtAtTime next = i.list.get(i.currentIndex); - final int nextTick = midnight && next.tick > 12000 ? next.tick - 24000 : next.tick; - if (i.lastTick < nextTick && nextTick <= tick) { - next.execute(e.getKey()); - i.currentIndex++; - if (i.currentIndex == i.list.size()) - i.currentIndex = 0; - if (i.currentIndex == startIndex) // all events executed at once - break; - } else { - break; - } - } - i.lastTick = tick; - } - } - }, 0, CHECKPERIOD); - } - - void execute(final World w) { - final Trigger t = this.t; - if (t == null) { - assert false; - return; - } - final ScheduledEvent e = new ScheduledEvent(w); - SkriptEventHandler.logEventStart(e); - SkriptEventHandler.logTriggerEnd(t); - t.execute(e); - SkriptEventHandler.logTriggerEnd(t); - SkriptEventHandler.logEventEnd(); - } - + @Override - public void register(final Trigger t) { - this.t = t; - for (final World w : worlds) { - EvtAtInfo i = triggers.get(w); - if (i == null) { - triggers.put(w, i = new EvtAtInfo()); - i.lastTick = (int) w.getTime() - 1; + public boolean postLoad() { + for (World world : worlds) { + EvtAtInfo info = TRIGGERS.get(world); + if (info == null) { + TRIGGERS.put(world, info = new EvtAtInfo()); + info.lastTick = (int) world.getTime() - 1; } - i.list.add(this); - Collections.sort(i.list); + info.instances.add(this); + Collections.sort(info.instances); } registerListener(); + return true; } - + @Override - public void unregister(final Trigger t) { - assert t == this.t; - this.t = null; - final Iterator<EvtAtInfo> iter = triggers.values().iterator(); - while (iter.hasNext()) { - final EvtAtInfo i = iter.next(); - i.list.remove(this); - if (i.currentIndex >= i.list.size()) - i.currentIndex--; - if (i.list.isEmpty()) - iter.remove(); + public void unload() { + Iterator<EvtAtInfo> iterator = TRIGGERS.values().iterator(); + while (iterator.hasNext()) { + EvtAtInfo info = iterator.next(); + info.instances.remove(this); + if (info.currentIndex >= info.instances.size()) + info.currentIndex--; + if (info.instances.isEmpty()) + iterator.remove(); + } + + if (taskID == -1 && TRIGGERS.isEmpty()) { // Unregister Bukkit listener if possible + Bukkit.getScheduler().cancelTask(taskID); + taskID = -1; } - if (triggers.isEmpty()) - unregisterAll(); } - + + @Override + public boolean check(Event event) { + throw new UnsupportedOperationException(); + } + @Override - public void unregisterAll() { + public boolean isEventPrioritySupported() { + return false; + } + + private static int taskID = -1; + + private static void registerListener() { if (taskID != -1) - Bukkit.getScheduler().cancelTask(taskID); - t = null; - taskID = -1; - triggers.clear(); + return; + taskID = Bukkit.getScheduler().scheduleSyncRepeatingTask(Skript.getInstance(), () -> { + for (Entry<World, EvtAtInfo> entry : TRIGGERS.entrySet()) { + EvtAtInfo info = entry.getValue(); + int tick = (int) entry.getKey().getTime(); + + // Stupid Bukkit scheduler + if (info.lastTick == tick) + continue; + + // Check if time changed, e.g. by a command or plugin + if (info.lastTick + CHECK_PERIOD * 2 < tick || info.lastTick > tick && info.lastTick - 24000 + CHECK_PERIOD * 2 < tick) + info.lastTick = Math2.mod(tick - CHECK_PERIOD, 24000); + + boolean midnight = info.lastTick > tick; // actually 6:00 + if (midnight) + info.lastTick -= 24000; + + int startIndex = info.currentIndex; + while (true) { + EvtAtTime next = info.instances.get(info.currentIndex); + int nextTick = midnight && next.tick > 12000 ? next.tick - 24000 : next.tick; + + if (!(info.lastTick < nextTick && nextTick <= tick)) + break; + + // Execute our event + ScheduledEvent event = new ScheduledEvent(entry.getKey()); + SkriptEventHandler.logEventStart(event); + SkriptEventHandler.logTriggerEnd(next.trigger); + next.trigger.execute(event); + SkriptEventHandler.logTriggerEnd(next.trigger); + SkriptEventHandler.logEventEnd(); + + info.currentIndex++; + if (info.currentIndex == info.instances.size()) + info.currentIndex = 0; + if (info.currentIndex == startIndex) // All events executed at once + break; + } + + info.lastTick = tick; + } + }, 0, CHECK_PERIOD); } @Override - public String toString(final @Nullable Event e, final boolean debug) { + public String toString(@Nullable Event event, boolean debug) { return "at " + Time.toString(tick) + " in worlds " + Classes.toString(worlds, true); } @Override - public int compareTo(final @Nullable EvtAtTime e) { - return e == null ? tick : tick - e.tick; + public int compareTo(@Nullable EvtAtTime event) { + return event == null ? tick : tick - event.tick; } } diff --git a/src/main/java/ch/njol/skript/events/EvtChat.java b/src/main/java/ch/njol/skript/events/EvtChat.java deleted file mode 100644 index f80e4dea7d2..00000000000 --- a/src/main/java/ch/njol/skript/events/EvtChat.java +++ /dev/null @@ -1,127 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.events; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.concurrent.Callable; - -import org.bukkit.event.Event; -import org.bukkit.event.EventException; -import org.bukkit.event.Listener; -import org.bukkit.event.player.AsyncPlayerChatEvent; -import org.bukkit.event.player.PlayerChatEvent; -import org.bukkit.plugin.EventExecutor; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.Skript; -import ch.njol.skript.SkriptConfig; -import ch.njol.skript.SkriptEventHandler; -import ch.njol.skript.events.util.PlayerChatEventHandler; -import ch.njol.skript.lang.Literal; -import ch.njol.skript.lang.SelfRegisteringSkriptEvent; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.lang.Trigger; -import ch.njol.skript.util.Task; - -/** - * @author Peter Güttinger - */ -@SuppressWarnings("deprecation") -public class EvtChat extends SelfRegisteringSkriptEvent { - static { - Skript.registerEvent("Chat", EvtChat.class, PlayerChatEventHandler.usesAsyncEvent ? AsyncPlayerChatEvent.class : PlayerChatEvent.class, "chat") - .description("Called whenever a player chats. Use <a href='./expressions.html#ExprChatFormat'>chat format</a> to change message format, use <a href='./expressions.html#ExprChatRecipients'>chat recipients</a> to edit chat recipients.") - .examples("on chat:", - " if player has permission \"owner\":", - " set chat format to \"<red>[player]<light gray>: <light red>[message]\"", - " else if player has permission \"admin\":", - " set chat format to \"<light red>[player]<light gray>: <orange>[message]\"", - " else: #default message format", - " set chat format to \"<orange>[player]<light gray>: <white>[message]\"") - .since("1.4.1"); - } - - final static Collection<Trigger> triggers = new ArrayList<>(); - - private static boolean registeredExecutor = false; - private final static EventExecutor executor = new EventExecutor() { - - final void execute(final Event e) { - SkriptEventHandler.logEventStart(e); - for (final Trigger t : triggers) { - assert t != null : triggers; - SkriptEventHandler.logTriggerStart(t); - t.execute(e); - SkriptEventHandler.logTriggerEnd(t); - } - SkriptEventHandler.logEventEnd(); - } - - @Override - public void execute(final @Nullable Listener l, final @Nullable Event e) throws EventException { - if (e == null) - return; - if (!triggers.isEmpty()) { - if (e instanceof PlayerChatEvent || !e.isAsynchronous()) { - execute(e); - return; - } - Task.callSync(new Callable<Void>() { - @Override - @Nullable - public Void call() throws Exception { - execute(e); - return null; - } - }); - } - } - }; - - @Override - public boolean init(final Literal<?>[] args, final int matchedPattern, final ParseResult parser) { - return true; - } - - @Override - public String toString(final @Nullable Event e, final boolean debug) { - return "chat"; - } - - @Override - public void register(final Trigger t) { - triggers.add(t); - if (!registeredExecutor) { - PlayerChatEventHandler.registerChatEvent(SkriptConfig.defaultEventPriority.value(), executor, true); - registeredExecutor = true; - } - } - - @Override - public void unregister(final Trigger t) { - triggers.remove(t); - } - - @Override - public void unregisterAll() { - triggers.clear(); - } - -} diff --git a/src/main/java/ch/njol/skript/events/EvtExperienceSpawn.java b/src/main/java/ch/njol/skript/events/EvtExperienceSpawn.java index beb2299ceef..7b3f3f0a074 100644 --- a/src/main/java/ch/njol/skript/events/EvtExperienceSpawn.java +++ b/src/main/java/ch/njol/skript/events/EvtExperienceSpawn.java @@ -23,7 +23,7 @@ import ch.njol.skript.SkriptEventHandler; import ch.njol.skript.events.bukkit.ExperienceSpawnEvent; import ch.njol.skript.lang.Literal; -import ch.njol.skript.lang.SelfRegisteringSkriptEvent; +import ch.njol.skript.lang.SkriptEvent; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.Trigger; import ch.njol.skript.registrations.EventValues; @@ -32,6 +32,7 @@ import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.event.Event; +import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.block.BlockExpEvent; import org.bukkit.event.entity.EntityDeathEvent; @@ -41,109 +42,129 @@ import org.eclipse.jdt.annotation.Nullable; import java.util.ArrayList; -import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; -public class EvtExperienceSpawn extends SelfRegisteringSkriptEvent { +public class EvtExperienceSpawn extends SkriptEvent { static { Skript.registerEvent("Experience Spawn", EvtExperienceSpawn.class, ExperienceSpawnEvent.class, "[e]xp[erience] [orb] spawn", - "spawn of [a[n]] [e]xp[erience] [orb]") - .description("Called whenever experience is about to spawn. This is a helper event for easily being able to stop xp from spawning, as all you can currently do is cancel the event.", - "Please note that it's impossible to detect xp orbs spawned by plugins (including Skript) with Bukkit, thus make sure that you have no such plugins if you don't want any xp orbs to spawn. " - + "(Many plugins that only <i>change</i> the experience dropped by blocks or entities will be detected without problems though)") - .examples("on xp spawn:", - "\tworld is \"minigame_world\"", - "\tcancel event") - .since("2.0"); + "spawn of [a[n]] [e]xp[erience] [orb]" + ).description( + "Called whenever experience is about to spawn.", + "Please note that this event will not fire for xp orbs spawned by plugins (including Skript) with Bukkit." + ).examples( + "on xp spawn:", + "\tworld is \"minigame_world\"", + "\tcancel event" + ).since("2.0"); EventValues.registerEventValue(ExperienceSpawnEvent.class, Location.class, new Getter<Location, ExperienceSpawnEvent>() { @Override - public Location get(ExperienceSpawnEvent e) { - return e.getLocation(); + public Location get(ExperienceSpawnEvent event) { + return event.getLocation(); } - }, 0); + }, EventValues.TIME_NOW); EventValues.registerEventValue(ExperienceSpawnEvent.class, Experience.class, new Getter<Experience, ExperienceSpawnEvent>() { @Override - public Experience get(ExperienceSpawnEvent e) { - return new Experience(e.getSpawnedXP()); + public Experience get(ExperienceSpawnEvent event) { + return new Experience(event.getSpawnedXP()); } - }, 0); + }, EventValues.TIME_NOW); } + + private static final List<Trigger> TRIGGERS = Collections.synchronizedList(new ArrayList<>()); + + private static final AtomicBoolean REGISTERED_EXECUTORS = new AtomicBoolean(); + + private static final EventExecutor EXECUTOR = (listener, event) -> { + ExperienceSpawnEvent experienceEvent; + if (event instanceof BlockExpEvent) { + experienceEvent = new ExperienceSpawnEvent( + ((BlockExpEvent) event).getExpToDrop(), + ((BlockExpEvent) event).getBlock().getLocation().add(0.5, 0.5, 0.5) + ); + } else if (event instanceof EntityDeathEvent) { + experienceEvent = new ExperienceSpawnEvent( + ((EntityDeathEvent) event).getDroppedExp(), + ((EntityDeathEvent) event).getEntity().getLocation() + ); + } else if (event instanceof ExpBottleEvent) { + experienceEvent = new ExperienceSpawnEvent( + ((ExpBottleEvent) event).getExperience(), + ((ExpBottleEvent) event).getEntity().getLocation() + ); + } else if (event instanceof PlayerFishEvent) { + if (((PlayerFishEvent) event).getState() != PlayerFishEvent.State.CAUGHT_FISH) // There is no EXP + return; + experienceEvent = new ExperienceSpawnEvent( + ((PlayerFishEvent) event).getExpToDrop(), + ((PlayerFishEvent) event).getPlayer().getLocation() + ); + } else { + assert false; + return; + } + + SkriptEventHandler.logEventStart(event); + synchronized (TRIGGERS) { + for (Trigger trigger : TRIGGERS) { + SkriptEventHandler.logTriggerStart(trigger); + trigger.execute(experienceEvent); + SkriptEventHandler.logTriggerEnd(trigger); + } + } + SkriptEventHandler.logEventEnd(); + + if (experienceEvent.isCancelled()) + experienceEvent.setSpawnedXP(0); + + if (event instanceof BlockExpEvent) { + ((BlockExpEvent) event).setExpToDrop(experienceEvent.getSpawnedXP()); + } else if (event instanceof EntityDeathEvent) { + ((EntityDeathEvent) event).setDroppedExp(experienceEvent.getSpawnedXP()); + } else if (event instanceof ExpBottleEvent) { + ((ExpBottleEvent) event).setExperience(experienceEvent.getSpawnedXP()); + } else if (event instanceof PlayerFishEvent) { + ((PlayerFishEvent) event).setExpToDrop(experienceEvent.getSpawnedXP()); + } + }; @Override public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult) { return true; } - private static final Collection<Trigger> triggers = new ArrayList<>(); - @Override - public void register(Trigger t) { - triggers.add(t); - registerExecutor(); + public boolean postLoad() { + TRIGGERS.add(trigger); + if (REGISTERED_EXECUTORS.compareAndSet(false, true)) { + EventPriority priority = SkriptConfig.defaultEventPriority.value(); + //noinspection unchecked + for (Class<? extends Event> clazz : new Class[]{BlockExpEvent.class, EntityDeathEvent.class, ExpBottleEvent.class, PlayerFishEvent.class}) + Bukkit.getPluginManager().registerEvent(clazz, new Listener(){}, priority, EXECUTOR, Skript.getInstance(), true); + } + return true; } - + @Override - public void unregister(Trigger t) { - triggers.remove(t); + public void unload() { + TRIGGERS.remove(trigger); } - + @Override - public void unregisterAll() { - triggers.clear(); - } - - private static boolean registeredExecutor = false; - - @SuppressWarnings("unchecked") - private static void registerExecutor() { - if (registeredExecutor) - return; - for (Class<? extends Event> c : new Class[] {BlockExpEvent.class, EntityDeathEvent.class, ExpBottleEvent.class, PlayerFishEvent.class}) - Bukkit.getPluginManager().registerEvent(c, new Listener() {}, SkriptConfig.defaultEventPriority.value(), executor, Skript.getInstance(), true); - registeredExecutor = true; + public boolean check(Event event) { + throw new UnsupportedOperationException(); } - - private static final EventExecutor executor = (listener, e) -> { - ExperienceSpawnEvent es; - if (e instanceof BlockExpEvent) { - es = new ExperienceSpawnEvent(((BlockExpEvent) e).getExpToDrop(), ((BlockExpEvent) e).getBlock().getLocation().add(0.5, 0.5, 0.5)); - } else if (e instanceof EntityDeathEvent) { - es = new ExperienceSpawnEvent(((EntityDeathEvent) e).getDroppedExp(), ((EntityDeathEvent) e).getEntity().getLocation()); - } else if (e instanceof ExpBottleEvent) { - es = new ExperienceSpawnEvent(((ExpBottleEvent) e).getExperience(), ((ExpBottleEvent) e).getEntity().getLocation()); - } else if (e instanceof PlayerFishEvent) { - if (((PlayerFishEvent) e).getState() != PlayerFishEvent.State.CAUGHT_FISH) // There is no EXP - return; - es = new ExperienceSpawnEvent(((PlayerFishEvent) e).getExpToDrop(), ((PlayerFishEvent) e).getPlayer().getLocation()); - } else { - assert false; - return; - } - - SkriptEventHandler.logEventStart(e); - for (Trigger t : triggers) { - SkriptEventHandler.logTriggerStart(t); - t.execute(es); - SkriptEventHandler.logTriggerEnd(t); - } - SkriptEventHandler.logEventEnd(); - if (es.isCancelled()) - es.setSpawnedXP(0); - if (e instanceof BlockExpEvent) { - ((BlockExpEvent) e).setExpToDrop(es.getSpawnedXP()); - } else if (e instanceof EntityDeathEvent) { - ((EntityDeathEvent) e).setDroppedExp(es.getSpawnedXP()); - } else if (e instanceof ExpBottleEvent) { - ((ExpBottleEvent) e).setExperience(es.getSpawnedXP()); - } else if (e instanceof PlayerFishEvent) { - ((PlayerFishEvent) e).setExpToDrop(es.getSpawnedXP()); - } - }; + @Override + public boolean isEventPrioritySupported() { + return false; + } @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { return "experience spawn"; } diff --git a/src/main/java/ch/njol/skript/events/EvtMoveOn.java b/src/main/java/ch/njol/skript/events/EvtMoveOn.java index c61a0cf1284..52b552b0040 100644 --- a/src/main/java/ch/njol/skript/events/EvtMoveOn.java +++ b/src/main/java/ch/njol/skript/events/EvtMoveOn.java @@ -24,8 +24,9 @@ import ch.njol.skript.aliases.Aliases; import ch.njol.skript.aliases.ItemData; import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.bukkitutil.ItemUtils; import ch.njol.skript.lang.Literal; -import ch.njol.skript.lang.SelfRegisteringSkriptEvent; +import ch.njol.skript.lang.SkriptEvent; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.Trigger; import ch.njol.skript.registrations.Classes; @@ -40,62 +41,67 @@ import org.eclipse.jdt.annotation.Nullable; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; - -public class EvtMoveOn extends SelfRegisteringSkriptEvent { // TODO on jump +public class EvtMoveOn extends SkriptEvent { static { // Register EvtPressurePlate before EvtMoveOn, https://github.com/SkriptLang/Skript/issues/2555 new EvtPressurePlate(); + Skript.registerEvent("Move On", EvtMoveOn.class, PlayerMoveEvent.class, "(step|walk)[ing] (on|over) %*itemtypes%") - .description("Called when a player moves onto a certain type of block. Please note that using this event can cause lag if there are many players online.") - .examples("on walking on dirt or grass:", "on stepping on stone:") - .since("2.0"); + .description( + "Called when a player moves onto a certain type of block.", + "Please note that using this event can cause lag if there are many players online." + ).examples( + "on walking on dirt or grass:", + "on stepping on stone:" + ).since("2.0"); } - /** - * Actual fence blocks and fence gates. - */ + // Fence blocks and fence gates private static final ItemType FENCE_PART = Aliases.javaItemType("fence part"); - private static final HashMap<Material, List<Trigger>> ITEM_TYPE_TRIGGERS = new HashMap<>(); - - @SuppressWarnings("ConstantConditions") - private ItemType[] types = null; + private static final Map<Material, List<Trigger>> ITEM_TYPE_TRIGGERS = new ConcurrentHashMap<>(); - private static boolean registeredExecutor = false; - private static final EventExecutor executor = (l, event) -> { - PlayerMoveEvent e = (PlayerMoveEvent) event; - Location from = e.getFrom(), to = e.getTo(); + private static final AtomicBoolean REGISTERED_EXECUTOR = new AtomicBoolean(); + + private static final EventExecutor EXECUTOR = (listener, e) -> { + PlayerMoveEvent event = (PlayerMoveEvent) e; + Location from = event.getFrom(), to = event.getTo(); if (!ITEM_TYPE_TRIGGERS.isEmpty()) { Block block = getOnBlock(to); - if (block == null || block.getType() == Material.AIR) + if (block == null || ItemUtils.isAir(block.getType())) return; + Material id = block.getType(); - List<Trigger> ts = ITEM_TYPE_TRIGGERS.get(id); - if (ts == null) + List<Trigger> triggers = ITEM_TYPE_TRIGGERS.get(id); + if (triggers == null) return; + int y = getBlockY(to.getY(), id); - if (to.getWorld().equals(from.getWorld()) && to.getBlockX() == from.getBlockX() && to.getBlockZ() == from.getBlockZ() - && y == getBlockY(from.getY(), getOnBlock(from).getType()) && getOnBlock(from).getType() == id) - return; + if (to.getWorld().equals(from.getWorld()) && to.getBlockX() == from.getBlockX() && to.getBlockZ() == from.getBlockZ()) { + Block fromOnBlock = getOnBlock(from); + if (fromOnBlock != null && y == getBlockY(from.getY(), fromOnBlock.getType()) && fromOnBlock.getType() == id) + return; + } - SkriptEventHandler.logEventStart(e); - triggersLoop: for (Trigger t : ts) { - EvtMoveOn se = (EvtMoveOn) t.getEvent(); - for (ItemType i : se.types) { - if (i.isOfType(block)) { - SkriptEventHandler.logTriggerStart(t); - t.execute(e); - SkriptEventHandler.logTriggerEnd(t); - continue triggersLoop; + SkriptEventHandler.logEventStart(event); + for (Trigger trigger : triggers) { + for (ItemType type : ((EvtMoveOn) trigger.getEvent()).types) { + if (type.isOfType(block)) { + SkriptEventHandler.logTriggerStart(trigger); + trigger.execute(event); + SkriptEventHandler.logTriggerEnd(trigger); + break; } } } @@ -104,10 +110,10 @@ public class EvtMoveOn extends SelfRegisteringSkriptEvent { // TODO on jump }; @Nullable - private static Block getOnBlock(Location l) { - Block block = l.getWorld().getBlockAt(l.getBlockX(), (int) (Math.ceil(l.getY()) - 1), l.getBlockZ()); - if (block.getType() == Material.AIR && Math.abs((l.getY() - l.getBlockY()) - 0.5) < Skript.EPSILON) { // Fences - block = l.getWorld().getBlockAt(l.getBlockX(), l.getBlockY() - 1, l.getBlockZ()); + private static Block getOnBlock(Location location) { + Block block = location.getWorld().getBlockAt(location.getBlockX(), (int) (Math.ceil(location.getY()) - 1), location.getBlockZ()); + if (block.getType() == Material.AIR && Math.abs((location.getY() - location.getBlockY()) - 0.5) < Skript.EPSILON) { // Fences + block = location.getWorld().getBlockAt(location.getBlockX(), location.getBlockY() - 1, location.getBlockZ()); if (!FENCE_PART.isOfType(block)) return null; } @@ -119,76 +125,77 @@ private static int getBlockY(double y, Material id) { return (int) Math.floor(y) - 1; return (int) Math.ceil(y) - 1; } - - public static Block getBlock(PlayerMoveEvent e) { - return e.getTo().clone().subtract(0, 0.5, 0).getBlock(); - } + + @SuppressWarnings("NotNullFieldNotInitialized") + private ItemType[] types; @SuppressWarnings("unchecked") @Override public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult) { - Literal<? extends ItemType> l = (Literal<? extends ItemType>) args[0]; - if (l == null) + Literal<? extends ItemType> types = (Literal<? extends ItemType>) args[0]; + if (types == null) return false; - types = l.getAll(); - for (ItemType t : types) { - if (t.isAll()) { + this.types = types.getAll(); + + for (ItemType type : this.types) { + if (type.isAll()) { Skript.error("Can't use an 'on walk' event with an alias that matches all blocks"); return false; } - boolean hasBlock = false; - for (ItemData d : t) { - if (d.getType().isBlock() && d.getType() != Material.AIR) // don't allow air - hasBlock = true; - } - if (!hasBlock) { - Skript.error(t + " is not a block and can thus not be walked on"); - return false; + for (ItemData data : type) { // Check for illegal types + if (!data.getType().isBlock() || ItemUtils.isAir(data.getType())) { + Skript.error(type + " is not a block and can thus not be walked on"); + return false; + } } } + return true; } - + @Override - public void register(Trigger trigger) { + public boolean postLoad() { Set<Material> materialSet = new HashSet<>(); - for (ItemType t : types) { - for (ItemData d : t) { - if (!d.getType().isBlock()) - continue; - materialSet.add(d.getType()); - } + for (ItemType type : types) { // Get unique materials + for (ItemData data : type) + materialSet.add(data.getType()); } - for (Material material : materialSet) { - List<Trigger> ts = ITEM_TYPE_TRIGGERS.computeIfAbsent(material, k -> new ArrayList<>()); - ts.add(trigger); - } + for (Material material : materialSet) + ITEM_TYPE_TRIGGERS.computeIfAbsent(material, k -> new ArrayList<>()).add(trigger); - if (!registeredExecutor) { - Bukkit.getPluginManager().registerEvent(PlayerMoveEvent.class, new Listener() {}, SkriptConfig.defaultEventPriority.value(), executor, Skript.getInstance(), true); - registeredExecutor = true; + if (REGISTERED_EXECUTOR.compareAndSet(false, true)) { + Bukkit.getPluginManager().registerEvent( + PlayerMoveEvent.class, new Listener(){}, SkriptConfig.defaultEventPriority.value(), EXECUTOR, Skript.getInstance(), true + ); } + + return true; } @Override - public void unregister(Trigger t) { - Iterator<Entry<Material, List<Trigger>>> i2 = ITEM_TYPE_TRIGGERS.entrySet().iterator(); - while (i2.hasNext()) { - List<Trigger> ts = i2.next().getValue(); - ts.remove(t); - if (ts.isEmpty()) - i2.remove(); + public void unload() { + Iterator<Entry<Material, List<Trigger>>> iterator = ITEM_TYPE_TRIGGERS.entrySet().iterator(); + while (iterator.hasNext()) { + List<Trigger> triggers = iterator.next().getValue(); + triggers.remove(trigger); + if (triggers.isEmpty()) + iterator.remove(); } } @Override - public void unregisterAll() { - ITEM_TYPE_TRIGGERS.clear(); + public boolean check(Event event) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isEventPrioritySupported() { + return false; } @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { return "walk on " + Classes.toString(types, false); } diff --git a/src/main/java/ch/njol/skript/events/EvtPeriodical.java b/src/main/java/ch/njol/skript/events/EvtPeriodical.java index 2ef0590f388..79a7de9b051 100644 --- a/src/main/java/ch/njol/skript/events/EvtPeriodical.java +++ b/src/main/java/ch/njol/skript/events/EvtPeriodical.java @@ -18,132 +18,109 @@ */ package ch.njol.skript.events; -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.SkriptEventHandler; import ch.njol.skript.events.bukkit.ScheduledEvent; import ch.njol.skript.events.bukkit.ScheduledNoWorldEvent; import ch.njol.skript.lang.Literal; -import ch.njol.skript.lang.SelfRegisteringSkriptEvent; +import ch.njol.skript.lang.SkriptEvent; import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.lang.Trigger; import ch.njol.skript.util.Timespan; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +public class EvtPeriodical extends SkriptEvent { -/** - * @author Peter Güttinger - */ -public class EvtPeriodical extends SelfRegisteringSkriptEvent { static { Skript.registerEvent("*Periodical", EvtPeriodical.class, ScheduledNoWorldEvent.class, "every %timespan%") .description("An event that is called periodically.") - .examples("every 2 seconds:", - "every minecraft hour:", - "every tick: # can cause lag depending on the code inside the event", - "every minecraft days:") - .since("1.0"); + .examples( + "every 2 seconds:", + "every minecraft hour:", + "every tick: # can cause lag depending on the code inside the event", + "every minecraft days:" + ).since("1.0"); Skript.registerEvent("*Periodical", EvtPeriodical.class, ScheduledEvent.class, "every %timespan% in [world[s]] %worlds%") .description("An event that is called periodically.") - .examples("every 2 seconds in \"world\":", - "every minecraft hour in \"flatworld\":", - "every tick in \"world\": # can cause lag depending on the code inside the event", - "every minecraft days in \"plots\":") - .since("1.0") + .examples( + "every 2 seconds in \"world\":", + "every minecraft hour in \"flatworld\":", + "every tick in \"world\": # can cause lag depending on the code inside the event", + "every minecraft days in \"plots\":" + ).since("1.0") .documentationID("eventperiodical"); } - @SuppressWarnings("null") + @SuppressWarnings("NotNullFieldNotInitialized") private Timespan period; - - @Nullable - private Trigger t; - @Nullable + + @SuppressWarnings("NotNullFieldNotInitialized") private int[] taskIDs; - - @Nullable - private transient World[] worlds; - -// @Nullable -// private String[] worldNames; - - @SuppressWarnings("unchecked") + + private World @Nullable [] worlds; + @Override - public boolean init(final Literal<?>[] args, final int matchedPattern, final ParseResult parser) { + @SuppressWarnings("unchecked") + public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult) { period = ((Literal<Timespan>) args[0]).getSingle(); - if (args.length > 1 && args[1] != null) { + if (args.length > 1 && args[1] != null) worlds = ((Literal<World>) args[1]).getArray(); -// worldNames = new String[worlds.length]; -// for (int i = 0; i < worlds.length; i++) -// worldNames[i] = worlds[i].getName(); - } return true; } - - void execute(final @Nullable World w) { - final Trigger t = this.t; - if (t == null) { - assert false; - return; - } - final ScheduledEvent e = w == null ? new ScheduledNoWorldEvent() : new ScheduledEvent(w); - SkriptEventHandler.logEventStart(e); - SkriptEventHandler.logTriggerStart(t); - t.execute(e); - SkriptEventHandler.logTriggerEnd(t); - SkriptEventHandler.logEventEnd(); - } - - @SuppressWarnings("null") + @Override - public void register(final Trigger t) { - this.t = t; - int[] taskIDs; + public boolean postLoad() { + long ticks = period.getTicks_i(); + if (worlds == null) { - taskIDs = new int[] {Bukkit.getScheduler().scheduleSyncRepeatingTask(Skript.getInstance(), new Runnable() { - @Override - public void run() { - execute(null); - } - }, period.getTicks_i(), period.getTicks_i())}; + taskIDs = new int[]{ + Bukkit.getScheduler().scheduleSyncRepeatingTask( + Skript.getInstance(), () -> execute(null), ticks, ticks + ) + }; } else { taskIDs = new int[worlds.length]; for (int i = 0; i < worlds.length; i++) { - final World w = worlds[i]; - taskIDs[i] = Bukkit.getScheduler().scheduleSyncRepeatingTask(Skript.getInstance(), new Runnable() { - @Override - public void run() { - execute(w); - } - }, period.getTicks_i() - (w.getFullTime() % period.getTicks_i()), period.getTicks_i()); - assert worlds != null; // FindBugs + World world = worlds[i]; + taskIDs[i] = Bukkit.getScheduler().scheduleSyncRepeatingTask( + Skript.getInstance(), () -> execute(world), ticks - (world.getFullTime() % ticks), ticks + ); } } - this.taskIDs = taskIDs; + + return true; } - + @Override - public void unregister(final Trigger t) { - assert t == this.t; - this.t = null; - assert taskIDs != null; - for (final int taskID : taskIDs) + public void unload() { + for (int taskID : taskIDs) Bukkit.getScheduler().cancelTask(taskID); } - + @Override - public void unregisterAll() { - t = null; - assert taskIDs != null; - for (final int taskID : taskIDs) - Bukkit.getScheduler().cancelTask(taskID); + public boolean check(Event event) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isEventPrioritySupported() { + return false; } @Override - public String toString(final @Nullable Event e, final boolean debug) { + public String toString(@Nullable Event event, boolean debug) { return "every " + period; } + + private void execute(@Nullable World world) { + ScheduledEvent event = world == null ? new ScheduledNoWorldEvent() : new ScheduledEvent(world); + SkriptEventHandler.logEventStart(event); + SkriptEventHandler.logTriggerStart(trigger); + trigger.execute(event); + SkriptEventHandler.logTriggerEnd(trigger); + SkriptEventHandler.logEventEnd(); + } } diff --git a/src/main/java/ch/njol/skript/events/EvtScript.java b/src/main/java/ch/njol/skript/events/EvtScript.java index 4acb1badcab..e11450e5813 100644 --- a/src/main/java/ch/njol/skript/events/EvtScript.java +++ b/src/main/java/ch/njol/skript/events/EvtScript.java @@ -18,73 +18,73 @@ */ package ch.njol.skript.events; -import org.bukkit.Bukkit; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.events.bukkit.ScriptEvent; import ch.njol.skript.lang.Literal; -import ch.njol.skript.lang.SelfRegisteringSkriptEvent; +import ch.njol.skript.lang.SkriptEvent; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.Trigger; +import org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; -/** - * @author Peter Güttinger - */ -public class EvtScript extends SelfRegisteringSkriptEvent { +public class EvtScript extends SkriptEvent { static { Skript.registerEvent("Script Load/Unload", EvtScript.class, ScriptEvent.class, - "[(1¦async)] [script] (load|init|enable)", - "[(1¦async)] [script] (unload|stop|disable)" - ) - .description("Called directly after the trigger is loaded, or directly before the whole script is unloaded.", - "The keyword 'async' indicates the trigger can be ran asynchronously, ") - .examples("on load:", - " set {running::%script%} to true", + "[:async] [script] (load|init|enable)", + "[:async] [script] (unload|stop|disable)" + ).description( + "Called directly after the trigger is loaded, or directly before the whole script is unloaded.", + "The keyword 'async' indicates the trigger can be ran asynchronously, " + ).examples( + "on load:", + "\tset {running::%script%} to true", "on unload:", - " set {running::%script%} to false") - .since("2.0"); + "\tset {running::%script%} to false" + ).since("2.0"); } - private boolean asyncAllowed; + private boolean async; private boolean load; @Override - public boolean init(final Literal<?>[] args, final int matchedPattern, final ParseResult parser) { - asyncAllowed = parser.mark == 1; + public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult) { + async = parseResult.hasTag("async"); load = matchedPattern == 0; return true; } - - @Nullable - private Trigger t; - + @Override - public void register(final Trigger t) { - this.t = t; + public boolean postLoad() { if (load) - runTrigger(t, new ScriptEvent()); + runTrigger(trigger, new ScriptEvent()); + return true; } - + @Override - public void unregister(final Trigger t) { - assert t == this.t; + public void unload() { if (!load) - runTrigger(t, new ScriptEvent()); - this.t = null; + runTrigger(trigger, new ScriptEvent()); } - + + @Override + public boolean check(Event event) { + throw new UnsupportedOperationException(); + } + @Override - public void unregisterAll() { - if (!load && t != null) - runTrigger(t, new ScriptEvent()); - t = null; + public boolean isEventPrioritySupported() { + return false; } + @Override + public String toString(@Nullable Event event, boolean debug) { + return (async ? "async " : "") + "script " + (load ? "" : "un") + "load"; + } + private void runTrigger(Trigger trigger, Event event) { - if (asyncAllowed || Bukkit.isPrimaryThread()) { + if (async || Bukkit.isPrimaryThread()) { trigger.execute(event); } else { if (Skript.getInstance().isEnabled()) @@ -92,9 +92,4 @@ private void runTrigger(Trigger trigger, Event event) { } } - @Override - public String toString(final @Nullable Event e, final boolean debug) { - return (asyncAllowed ? "async " : "") + "script " + (load ? "" : "un") + "load"; - } - } diff --git a/src/main/java/ch/njol/skript/events/EvtSkript.java b/src/main/java/ch/njol/skript/events/EvtSkript.java index b2936aa9e7d..af1bc35e58b 100644 --- a/src/main/java/ch/njol/skript/events/EvtSkript.java +++ b/src/main/java/ch/njol/skript/events/EvtSkript.java @@ -18,82 +18,90 @@ */ package ch.njol.skript.events; -import java.util.ArrayList; -import java.util.Collection; - -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.events.bukkit.SkriptStartEvent; import ch.njol.skript.events.bukkit.SkriptStopEvent; import ch.njol.skript.lang.Literal; -import ch.njol.skript.lang.SelfRegisteringSkriptEvent; +import ch.njol.skript.lang.SkriptEvent; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.Trigger; import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class EvtSkript extends SkriptEvent { -/** - * @author Peter Güttinger - */ -public class EvtSkript extends SelfRegisteringSkriptEvent { static { - Skript.registerEvent("Server Start/Stop", EvtSkript.class, CollectionUtils.array(SkriptStartEvent.class, SkriptStopEvent.class), "(0¦server|1¦skript) (start|load|enable)", "(0¦server|1¦skript) (stop|unload|disable)") - .description("Called when the server starts or stops (actually, when Skript starts or stops, so a /reload will trigger these events as well).") - .examples("on skript start:", "on server stop:") - .since("2.0"); + Skript.registerEvent("Server Start/Stop", EvtSkript.class, CollectionUtils.array(SkriptStartEvent.class, SkriptStopEvent.class), + "(:server|skript) (start|load|enable)", "(:server|skript) (stop|unload|disable)" + ) + .description("Called when the server starts or stops (actually, when Skript starts or stops, so a /reload will trigger these events as well).") + .examples("on skript start:", "on server stop:") + .since("2.0"); + } + + private static final List<Trigger> START = Collections.synchronizedList(new ArrayList<>()); + private static final List<Trigger> STOP = Collections.synchronizedList(new ArrayList<>()); + + public static void onSkriptStart() { + Event event = new SkriptStartEvent(); + synchronized (START) { + for (Trigger trigger : START) + trigger.execute(event); + START.clear(); + } + } + + public static void onSkriptStop() { + Event event = new SkriptStopEvent(); + synchronized (STOP) { + for (Trigger trigger : STOP) + trigger.execute(event); + STOP.clear(); + } } private boolean isStart; @Override - public boolean init(final Literal<?>[] args, final int matchedPattern, final ParseResult parser) { + public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult) { isStart = matchedPattern == 0; - if (parser.mark == 0) { - Skript.warning("Server start/stop events are actually called when Skript is started or stopped. It is thus recommended to use 'on Skript start/stop' instead."); - } + if (parseResult.hasTag("server")) + Skript.warning( + "Server start/stop events are actually called when Skript is started or stopped." + + "It is thus recommended to use 'on Skript start/stop' instead." + ); return true; } - - private final static Collection<Trigger> start = new ArrayList<>(), stop = new ArrayList<>(); - - public static void onSkriptStart() { - final Event e = new SkriptStartEvent(); - for (final Trigger t : start) - t.execute(e); - } - - public static void onSkriptStop() { - final Event e = new SkriptStopEvent(); - for (final Trigger t : stop) - t.execute(e); + + @Override + public boolean postLoad() { + (isStart ? START : STOP).add(trigger); + return true; } - + @Override - public void register(final Trigger t) { - if (isStart) - start.add(t); - else - stop.add(t); + public void unload() { + (isStart ? START : STOP).remove(trigger); } - + @Override - public void unregister(final Trigger t) { - if (isStart) - start.remove(t); - else - stop.remove(t); + public boolean check(Event event) { + throw new UnsupportedOperationException(); } - + @Override - public void unregisterAll() { - start.clear(); - stop.clear(); + public boolean isEventPrioritySupported() { + return false; } @Override - public String toString(final @Nullable Event e, final boolean debug) { - return "on server " + (isStart ? "start" : "stop"); + public String toString(@Nullable Event event, boolean debug) { + return "on skript " + (isStart ? "start" : "stop"); } } diff --git a/src/main/java/ch/njol/skript/events/SimpleEvents.java b/src/main/java/ch/njol/skript/events/SimpleEvents.java index cbb57d3dcf4..bfbccf575c6 100644 --- a/src/main/java/ch/njol/skript/events/SimpleEvents.java +++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java @@ -60,6 +60,7 @@ import org.bukkit.event.inventory.InventoryOpenEvent; import org.bukkit.event.inventory.InventoryPickupItemEvent; import org.bukkit.event.inventory.PrepareAnvilEvent; +import org.bukkit.event.player.AsyncPlayerChatEvent; import org.bukkit.event.player.PlayerAnimationEvent; import org.bukkit.event.player.PlayerBedEnterEvent; import org.bukkit.event.player.PlayerBedLeaveEvent; @@ -649,6 +650,25 @@ public class SimpleEvents { "\tcancel the event") .since("INSERT VERSION"); } + + //noinspection deprecation + Skript.registerEvent("Chat", SimpleEvent.class, AsyncPlayerChatEvent.class, "chat") + .description( + "Called whenever a player chats.", + "Use <a href='./expressions.html#ExprChatFormat'>chat format</a> to change message format.", + "Use <a href='./expressions.html#ExprChatRecipients'>chat recipients</a> to edit chat recipients." + ) + .examples( + "on chat:", + "\tif player has permission \"owner\":", + "\t\tset chat format to \"<red>[player]<light gray>: <light red>[message]\"", + "\telse if player has permission \"admin\":", + "\t\tset chat format to \"<light red>[player]<light gray>: <orange>[message]\"", + "\telse: #default message format", + "\t\tset chat format to \"<orange>[player]<light gray>: <white>[message]\"" + ) + .since("1.4.1"); + if (Skript.classExists("org.bukkit.event.world.LootGenerateEvent")) { Skript.registerEvent("Loot Generate", SimpleEvent.class, LootGenerateEvent.class, "loot generat(e|ing)") .description( @@ -664,6 +684,7 @@ public class SimpleEvents { .since("INSERT VERSION") .requiredPlugins("MC 1.16+"); } + } } diff --git a/src/main/java/ch/njol/skript/events/util/PlayerChatEventHandler.java b/src/main/java/ch/njol/skript/events/util/PlayerChatEventHandler.java deleted file mode 100644 index 3c50b464bf6..00000000000 --- a/src/main/java/ch/njol/skript/events/util/PlayerChatEventHandler.java +++ /dev/null @@ -1,47 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.events.util; - -import org.bukkit.Bukkit; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.player.AsyncPlayerChatEvent; -import org.bukkit.event.player.PlayerChatEvent; -import org.bukkit.plugin.EventExecutor; - -import ch.njol.skript.Skript; - -/** - * @author Peter Güttinger - */ -@SuppressWarnings("deprecation") -public abstract class PlayerChatEventHandler { - - private PlayerChatEventHandler() {} - - public final static boolean usesAsyncEvent = Skript.classExists("org.bukkit.event.player.AsyncPlayerChatEvent"); - - public static void registerChatEvent(final EventPriority priority, final EventExecutor executor, final boolean ignoreCancelled) { - if (Skript.classExists("org.bukkit.event.player.AsyncPlayerChatEvent")) - Bukkit.getPluginManager().registerEvent(AsyncPlayerChatEvent.class, new Listener() {}, priority, executor, Skript.getInstance(), ignoreCancelled); - else - Bukkit.getPluginManager().registerEvent(PlayerChatEvent.class, new Listener() {}, priority, executor, Skript.getInstance(), ignoreCancelled); - } - -} diff --git a/src/main/java/ch/njol/skript/events/util/package-info.java b/src/main/java/ch/njol/skript/events/util/package-info.java deleted file mode 100644 index d3d69131004..00000000000 --- a/src/main/java/ch/njol/skript/events/util/package-info.java +++ /dev/null @@ -1,27 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -/** - * @author Peter Güttinger - */ -@NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) -package ch.njol.skript.events.util; - -import org.eclipse.jdt.annotation.DefaultLocation; -import org.eclipse.jdt.annotation.NonNullByDefault; - diff --git a/src/main/java/ch/njol/skript/expressions/ExprMessage.java b/src/main/java/ch/njol/skript/expressions/ExprMessage.java index 9074e018e72..a3b54f43c4c 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprMessage.java +++ b/src/main/java/ch/njol/skript/expressions/ExprMessage.java @@ -22,7 +22,6 @@ import org.bukkit.event.entity.EntityDeathEvent; import org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.event.player.AsyncPlayerChatEvent; -import org.bukkit.event.player.PlayerChatEvent; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerKickEvent; import org.bukkit.event.player.PlayerQuitEvent; @@ -35,7 +34,6 @@ import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; -import ch.njol.skript.events.util.PlayerChatEventHandler; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; @@ -72,22 +70,15 @@ public class ExprMessage extends SimpleExpression<String> { @SuppressWarnings("unchecked") private static enum MessageType { - CHAT("chat", "[chat( |-)]message", PlayerChatEventHandler.usesAsyncEvent ? AsyncPlayerChatEvent.class : PlayerChatEvent.class) { + CHAT("chat", "[chat( |-)]message", AsyncPlayerChatEvent.class) { @Override - @Nullable - String get(final Event e) { - if (PlayerChatEventHandler.usesAsyncEvent) - return ((AsyncPlayerChatEvent) e).getMessage(); - else - return ((PlayerChatEvent) e).getMessage(); + String get(Event event) { + return ((AsyncPlayerChatEvent) event).getMessage(); } @Override - void set(final Event e, final String message) { - if (PlayerChatEventHandler.usesAsyncEvent) - ((AsyncPlayerChatEvent) e).setMessage(message); - else - ((PlayerChatEvent) e).setMessage(message); + void set(Event event, String message) { + ((AsyncPlayerChatEvent) event).setMessage(message); } }, JOIN("join", "(join|log[ ]in)( |-)message", PlayerJoinEvent.class) { diff --git a/src/main/java/ch/njol/skript/hooks/regions/events/EvtRegionBorder.java b/src/main/java/ch/njol/skript/hooks/regions/events/EvtRegionBorder.java index a267d02beca..06ec33a810c 100644 --- a/src/main/java/ch/njol/skript/hooks/regions/events/EvtRegionBorder.java +++ b/src/main/java/ch/njol/skript/hooks/regions/events/EvtRegionBorder.java @@ -18,15 +18,21 @@ */ package ch.njol.skript.hooks.regions.events; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Set; - +import ch.njol.skript.Skript; +import ch.njol.skript.SkriptConfig; +import ch.njol.skript.hooks.regions.RegionsPlugin; +import ch.njol.skript.hooks.regions.classes.Region; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptEvent; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.Trigger; +import ch.njol.skript.registrations.EventValues; +import ch.njol.skript.util.Getter; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.event.Event; -import org.bukkit.event.EventException; +import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerPortalEvent; @@ -34,149 +40,141 @@ import org.bukkit.plugin.EventExecutor; import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.Skript; -import ch.njol.skript.SkriptConfig; -import ch.njol.skript.hooks.regions.RegionsPlugin; -import ch.njol.skript.hooks.regions.classes.Region; -import ch.njol.skript.lang.Literal; -import ch.njol.skript.lang.SelfRegisteringSkriptEvent; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.lang.Trigger; -import ch.njol.skript.registrations.EventValues; -import ch.njol.skript.util.Getter; -import ch.njol.util.Checker; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +public class EvtRegionBorder extends SkriptEvent { -/** - * @author Peter Güttinger - */ -public class EvtRegionBorder extends SelfRegisteringSkriptEvent { static { Skript.registerEvent("Region Enter/Leave", EvtRegionBorder.class, RegionBorderEvent.class, - "(0¦enter[ing]|1¦leav(e|ing)|1¦exit[ing]) [of] ([a] region|[[the] region] %-regions%)", - "region (0¦enter[ing]|1¦leav(e|ing)|1¦exit[ing])") - .description("Called when a player enters or leaves a <a href='./classes.html#region'>region</a>.", - "This event requires a supported regions plugin to be installed.") - .examples("on region exit:", - " message \"Leaving %region%.\"") - .since("2.1") + "(:enter[ing]|leav(e|ing)|exit[ing]) [of] ([a] region|[[the] region] %-regions%)", + "region (:enter[ing]|leav(e|ing)|exit[ing])") + .description( + "Called when a player enters or leaves a <a href='./classes.html#region'>region</a>.", + "This event requires a supported regions plugin to be installed." + ).examples( + "on region exit:", + "\tmessage \"Leaving %region%.\"" + ).since("2.1") .requiredPlugins("Supported regions plugin"); EventValues.registerEventValue(RegionBorderEvent.class, Region.class, new Getter<Region, RegionBorderEvent>() { @Override - public Region get(final RegionBorderEvent e) { + public Region get(RegionBorderEvent e) { return e.getRegion(); } - }, 0); + }, EventValues.TIME_NOW); EventValues.registerEventValue(RegionBorderEvent.class, Player.class, new Getter<Player, RegionBorderEvent>() { @Override - public Player get(final RegionBorderEvent e) { + public Player get(RegionBorderEvent e) { return e.getPlayer(); } - }, 0); + }, EventValues.TIME_NOW); + } + + // Even WorldGuard doesn't have events, and this way all region plugins are supported for sure. + private final static EventExecutor EXECUTOR = new EventExecutor() { + @Nullable + Event last = null; + + @Override + public void execute(@Nullable Listener listener, Event event) { + if (event == last) + return; + last = event; + + PlayerMoveEvent moveEvent = (PlayerMoveEvent) event; + Location to = moveEvent.getTo(); + Location from = moveEvent.getFrom(); + + if (to.equals(from)) + return; + + Set<? extends Region> oldRegions = RegionsPlugin.getRegionsAt(from); + Set<? extends Region> newRegions = RegionsPlugin.getRegionsAt(to); + + for (Region oldRegion : oldRegions) { + if (!newRegions.contains(oldRegion)) + callEvent(oldRegion, moveEvent, false); + } + + for (Region newRegion : newRegions) { + if (!oldRegions.contains(newRegion)) + callEvent(newRegion, moveEvent, true); + } + } + }; + + private static void callEvent(Region region, PlayerMoveEvent event, boolean enter) { + RegionBorderEvent regionEvent = new RegionBorderEvent(region, event.getPlayer(), enter); + regionEvent.setCancelled(event.isCancelled()); + synchronized (TRIGGERS) { + for (Trigger trigger : TRIGGERS) { + if (((EvtRegionBorder) trigger.getEvent()).applies(regionEvent)) + trigger.execute(regionEvent); + } + } + event.setCancelled(regionEvent.isCancelled()); } + + private static final List<Trigger> TRIGGERS = Collections.synchronizedList(new ArrayList<>()); + + private static final AtomicBoolean REGISTERED_EXECUTORS = new AtomicBoolean(); private boolean enter; @Nullable private Literal<Region> regions; - - @SuppressWarnings("unchecked") + @Override - public boolean init(final Literal<?>[] args, final int matchedPattern, final ParseResult parseResult) { - enter = parseResult.mark == 0; + @SuppressWarnings("unchecked") + public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult) { + enter = parseResult.hasTag("enter"); regions = args.length == 0 ? null : (Literal<Region>) args[0]; return true; } - + @Override - public String toString(final @Nullable Event e, final boolean debug) { - final Literal<Region> r = regions; - return (enter ? "enter" : "leave") + " of " + (r == null ? "a region" : r.toString(e, debug)); + public boolean postLoad() { + TRIGGERS.add(trigger); + if (REGISTERED_EXECUTORS.compareAndSet(false, true)) { + EventPriority priority = SkriptConfig.defaultEventPriority.value(); + Bukkit.getPluginManager().registerEvent(PlayerMoveEvent.class, new Listener(){}, priority, EXECUTOR, Skript.getInstance(), true); + Bukkit.getPluginManager().registerEvent(PlayerTeleportEvent.class, new Listener(){}, priority, EXECUTOR, Skript.getInstance(), true); + Bukkit.getPluginManager().registerEvent(PlayerPortalEvent.class, new Listener(){}, priority, EXECUTOR, Skript.getInstance(), true); + } + return true; } - - private final static Collection<Trigger> triggers = new ArrayList<>(); - + @Override - public void register(final Trigger t) { - triggers.add(t); - register(); + public void unload() { + TRIGGERS.remove(trigger); } - + @Override - public void unregister(final Trigger t) { - triggers.remove(t); + public boolean check(Event event) { + throw new UnsupportedOperationException(); } - + + @Override + public boolean isEventPrioritySupported() { + return false; + } + @Override - public void unregisterAll() { - triggers.clear(); + public String toString(@Nullable Event event, boolean debug) { + return (enter ? "enter" : "leave") + " of " + (regions == null ? "a region" : regions.toString(event, debug)); } - private boolean applies(final Event e) { - assert e instanceof RegionBorderEvent; - if (enter != ((RegionBorderEvent) e).isEntering()) + private boolean applies(RegionBorderEvent event) { + if (enter != event.isEntering()) return false; - final Literal<Region> r = regions; - if (r == null) + if (regions == null) return true; - final Region re = ((RegionBorderEvent) e).getRegion(); - return r.check(e, new Checker<Region>() { - @Override - public boolean check(final Region r) { - return r.equals(re); - } - }); - } - - static void callEvent(final Region r, final PlayerMoveEvent me, final boolean enter) { - final Player p = me.getPlayer(); - assert p != null; - final RegionBorderEvent e = new RegionBorderEvent(r, p, enter); - e.setCancelled(me.isCancelled()); - for (final Trigger t : triggers) { - if (((EvtRegionBorder) t.getEvent()).applies(e)) - t.execute(e); - } - me.setCancelled(e.isCancelled()); - } - - // even WorldGuard doesn't have events, and this way all region plugins are supported for sure. - private final static EventExecutor ee = new EventExecutor() { - @Nullable - Event last = null; - - @SuppressWarnings("null") - @Override - public void execute(final @Nullable Listener listener, final Event event) throws EventException { - if (event == last) - return; - last = event; - final PlayerMoveEvent e = (PlayerMoveEvent) event; - final Location to = e.getTo(), from = e.getFrom(); - if (to != null && to.equals(from)) - return; - //if (to.getWorld().equals(from.getWorld()) && to.distanceSquared(from) < 2) - // return; - final Set<? extends Region> oldRs = RegionsPlugin.getRegionsAt(from), newRs = RegionsPlugin.getRegionsAt(to); - for (final Region r : oldRs) { - if (!newRs.contains(r)) - callEvent(r, e, false); - } - for (final Region r : newRs) { - if (!oldRs.contains(r)) - callEvent(r, e, true); - } - } - }; - - private static boolean registered = false; - - private static void register() { - if (registered) - return; - Bukkit.getPluginManager().registerEvent(PlayerMoveEvent.class, new Listener() {}, SkriptConfig.defaultEventPriority.value(), ee, Skript.getInstance(), true); - Bukkit.getPluginManager().registerEvent(PlayerTeleportEvent.class, new Listener() {}, SkriptConfig.defaultEventPriority.value(), ee, Skript.getInstance(), true); - Bukkit.getPluginManager().registerEvent(PlayerPortalEvent.class, new Listener() {}, SkriptConfig.defaultEventPriority.value(), ee, Skript.getInstance(), true); - registered = true; + Region region = event.getRegion(); + return regions.check(event, r -> r.equals(region)); } } diff --git a/src/main/java/ch/njol/skript/lang/SelfRegisteringSkriptEvent.java b/src/main/java/ch/njol/skript/lang/SelfRegisteringSkriptEvent.java index 24816203653..00b9f607620 100644 --- a/src/main/java/ch/njol/skript/lang/SelfRegisteringSkriptEvent.java +++ b/src/main/java/ch/njol/skript/lang/SelfRegisteringSkriptEvent.java @@ -23,12 +23,20 @@ import java.util.Objects; +/** + * @deprecated Regular {@link org.skriptlang.skript.lang.structure.Structure} methods should be used. + * See individual methods for their equivalents. + */ +@Deprecated public abstract class SelfRegisteringSkriptEvent extends SkriptEvent { /** * This method is called after the whole trigger is loaded for events that fire themselves. * * @param t the trigger to register to this event + * @deprecated This method's functionality can be replaced by overriding {@link #postLoad()}. + * Normally, that method would register the parsed trigger with {@link ch.njol.skript.SkriptEventHandler}. + * A reference to the {@link Trigger} is available through {@link #trigger}. */ public abstract void register(Trigger t); @@ -36,6 +44,9 @@ public abstract class SelfRegisteringSkriptEvent extends SkriptEvent { * This method is called to unregister this event registered through {@link #register(Trigger)}. * * @param t the same trigger which was registered for this event + * @deprecated This method's functionality can be replaced by overriding {@link #unload()}. + * Normally, that method would unregister the parsed trigger with {@link ch.njol.skript.SkriptEventHandler}. + * A reference to the {@link Trigger} is available through {@link #trigger}. */ public abstract void unregister(Trigger t); @@ -43,6 +54,8 @@ public abstract class SelfRegisteringSkriptEvent extends SkriptEvent { * This method is called to unregister all events registered through {@link #register(Trigger)}. * This is called on all registered events, thus it can also only unregister the * event it is called on. + * @deprecated This method should no longer be used. + * Each trigger should be unregistered through {@link #unregister(Trigger)}. */ public abstract void unregisterAll(); @@ -54,6 +67,17 @@ public boolean load() { return load; } + @Override + public boolean postLoad() { + register(trigger); + return true; + } + + @Override + public void unload() { + unregister(trigger); + } + @Override public final boolean check(Event e) { throw new UnsupportedOperationException(); diff --git a/src/main/java/ch/njol/skript/lang/SkriptEvent.java b/src/main/java/ch/njol/skript/lang/SkriptEvent.java index 263b4ade193..e150e8699d8 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptEvent.java +++ b/src/main/java/ch/njol/skript/lang/SkriptEvent.java @@ -56,10 +56,11 @@ public abstract class SkriptEvent extends Structure { @Nullable protected EventPriority eventPriority; private SkriptEventInfo<?> skriptEventInfo; - @Nullable - private List<TriggerItem> items; - @Nullable - private Trigger trigger; + + /** + * The Trigger containing this SkriptEvent's code. + */ + protected Trigger trigger; @Override public final boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult, EntryContainer entryContainer) { @@ -101,6 +102,16 @@ public final boolean init(Literal<?>[] args, int matchedPattern, ParseResult par */ public abstract boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult); + /** + * This method handles the loading of the Structure's syntax elements. + * Only override this method if you know what you are doing! + */ + @Override + public boolean preLoad() { + // Implemented just for javadoc + return super.preLoad(); + } + /** * This method handles the loading of the Structure's syntax elements. * Only override this method if you know what you are doing! @@ -119,7 +130,13 @@ public boolean load() { try { getParser().setCurrentEvent(skriptEventInfo.getName().toLowerCase(Locale.ENGLISH), eventClasses); - items = ScriptLoader.loadItems(source); + @Nullable List<TriggerItem> items = ScriptLoader.loadItems(source); + Script script = getParser().getCurrentScript(); + + trigger = new Trigger(script, expr, this, items); + int lineNumber = getEntryContainer().getSource().getLine(); + trigger.setLineNumber(lineNumber); // Set line number for debugging + trigger.setDebugLabel(script + ": line " + lineNumber); } finally { getParser().deleteCurrentEvent(); } @@ -133,28 +150,7 @@ public boolean load() { */ @Override public boolean postLoad() { - getParser().setCurrentEvent(skriptEventInfo.getName().toLowerCase(Locale.ENGLISH), getEventClasses()); - - Script script = getParser().getCurrentScript(); - - try { - assert items != null; // This method will only be called if 'load' was successful, meaning 'items' will be set - trigger = new Trigger(script, expr, this, items); - int lineNumber = getEntryContainer().getSource().getLine(); - trigger.setLineNumber(lineNumber); // Set line number for debugging - trigger.setDebugLabel(script + ": line " + lineNumber); - } finally { - getParser().deleteCurrentEvent(); - } - - if (this instanceof SelfRegisteringSkriptEvent) { - ((SelfRegisteringSkriptEvent) this).register(trigger); - } else { - SkriptEventHandler.registerBukkitEvents(trigger, getEventClasses()); - } - - getParser().deleteCurrentEvent(); - + SkriptEventHandler.registerBukkitEvents(trigger, getEventClasses()); return true; } @@ -164,14 +160,17 @@ public boolean postLoad() { */ @Override public void unload() { - if (trigger == null) - return; + SkriptEventHandler.unregisterBukkitEvents(trigger); + } - if (this instanceof SelfRegisteringSkriptEvent) { - ((SelfRegisteringSkriptEvent) this).unregister(trigger); - } else { - SkriptEventHandler.unregisterBukkitEvents(trigger); - } + /** + * This method handles the unregistration of this event with Skript and Bukkit. + * Only override this method if you know what you are doing! + */ + @Override + public void postUnload() { + // Implemented just for javadoc + super.postUnload(); } @Override @@ -184,7 +183,7 @@ public Priority getPriority() { * will only be called for events this SkriptEvent is registered for. * @return true if this is SkriptEvent is represented by the Bukkit Event or false if not */ - public abstract boolean check(Event e); + public abstract boolean check(Event event); /** * Script loader checks this before loading items in event. If false is diff --git a/src/main/java/org/skriptlang/skript/lang/structure/Structure.java b/src/main/java/org/skriptlang/skript/lang/structure/Structure.java index bf7afe348c8..8e0a0d9cddb 100644 --- a/src/main/java/org/skriptlang/skript/lang/structure/Structure.java +++ b/src/main/java/org/skriptlang/skript/lang/structure/Structure.java @@ -25,11 +25,9 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.ParseContext; -import ch.njol.skript.lang.SelfRegisteringSkriptEvent; import ch.njol.skript.lang.SkriptParser; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.SyntaxElement; -import ch.njol.skript.lang.Trigger; import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.skript.log.ParseLogHandler; import ch.njol.skript.log.SkriptLogger; @@ -151,12 +149,12 @@ public boolean postLoad() { } /** - * Called when this structure is unloaded, similar to {@link SelfRegisteringSkriptEvent#unregister(Trigger)}. + * Called when this structure is unloaded. */ public void unload() { } /** - * Called when this structure is unloaded, similar to {@link SelfRegisteringSkriptEvent#unregister(Trigger)}. + * Called when this structure is unloaded. * This method is primarily designed for Structures that wish to execute actions after * most other Structures have finished unloading. */ From a052b1e22a88515e6af18b1bfd5ebd06d955cfe2 Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Tue, 31 Jan 2023 21:50:36 +0100 Subject: [PATCH 216/619] Make SimpleExpression copy the received array before returning it (#5329) --- .../njol/skript/expressions/base/PropertyExpression.java | 5 ++++- .../java/ch/njol/skript/lang/util/SimpleExpression.java | 7 ++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/base/PropertyExpression.java b/src/main/java/ch/njol/skript/expressions/base/PropertyExpression.java index 994eb9fe1fa..6f2d2d7ae98 100644 --- a/src/main/java/ch/njol/skript/expressions/base/PropertyExpression.java +++ b/src/main/java/ch/njol/skript/expressions/base/PropertyExpression.java @@ -30,6 +30,8 @@ import org.skriptlang.skript.lang.converter.Converters; import ch.njol.util.Kleenean; +import java.util.Arrays; + /** * Represents an expression which represents a property of another one. Remember to set the expression with {@link #setExpr(Expression)} in * {@link SyntaxElement#init(Expression[], int, Kleenean, ParseResult) init()}. @@ -75,7 +77,8 @@ protected final T[] get(final Event e) { @Override public final T[] getAll(final Event e) { - return get(e, expr.getAll(e)); + T[] result = get(e, expr.getAll(e)); + return Arrays.copyOf(result, result.length); } /** diff --git a/src/main/java/ch/njol/skript/lang/util/SimpleExpression.java b/src/main/java/ch/njol/skript/lang/util/SimpleExpression.java index df057711941..6874b1d2b1a 100644 --- a/src/main/java/ch/njol/skript/lang/util/SimpleExpression.java +++ b/src/main/java/ch/njol/skript/lang/util/SimpleExpression.java @@ -19,6 +19,7 @@ package ch.njol.skript.lang.util; import java.lang.reflect.Array; +import java.util.Arrays; import java.util.Iterator; import org.bukkit.event.Event; @@ -84,7 +85,7 @@ public T[] getAll(final Event e) { if (t != null) numNonNull++; if (numNonNull == all.length) - return all; + return Arrays.copyOf(all, all.length); final T[] r = (T[]) Array.newInstance(getReturnType(), numNonNull); assert r != null; int i = 0; @@ -113,7 +114,7 @@ public final T[] getArray(final Event e) { if (!getAnd()) { if (all.length == 1 && all[0] != null) - return all; + return Arrays.copyOf(all, 1); int rand = Utils.random(0, numNonNull); final T[] one = (T[]) Array.newInstance(getReturnType(), 1); for (final T t : all) { @@ -129,7 +130,7 @@ public final T[] getArray(final Event e) { } if (numNonNull == all.length) - return all; + return Arrays.copyOf(all, all.length); final T[] r = (T[]) Array.newInstance(getReturnType(), numNonNull); assert r != null; int i = 0; From 334c6171b1cbea6a32d96d9a37082f6219865006 Mon Sep 17 00:00:00 2001 From: CJYKK <59359590+CJYKK@users.noreply.github.com> Date: Wed, 1 Feb 2023 14:52:10 +0800 Subject: [PATCH 217/619] Add Simplified Chinese Translation (#5231) --- src/main/resources/config.sk | 2 +- .../resources/lang/simplifiedchinese.lang | 190 ++++++++++++++++++ 2 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/lang/simplifiedchinese.lang diff --git a/src/main/resources/config.sk b/src/main/resources/config.sk index 3e0ad76479c..9b19ad0bc5f 100644 --- a/src/main/resources/config.sk +++ b/src/main/resources/config.sk @@ -31,7 +31,7 @@ # ==== General Options ==== language: english -# Which language to use. Currently English, German, Korean, French and Polish are included in the download, but custom languages can be created as well. +# Which language to use. Currently English, German, Korean, French, Polish and Simplified Chinese (simplifiedchinese.lang) are included in the download, but custom languages can be created as well. # Please note that not everything can be translated yet, i.e. parts of Skript will still be english if you use another language. # If you want to translate Skript to your language please read the readme.txt located in the /lang/ folder in the jar # (open the jar as zip or rename it to Skript.zip to access it) diff --git a/src/main/resources/lang/simplifiedchinese.lang b/src/main/resources/lang/simplifiedchinese.lang new file mode 100644 index 00000000000..aa1900b22ae --- /dev/null +++ b/src/main/resources/lang/simplifiedchinese.lang @@ -0,0 +1,190 @@ +# Default Simplified Chinese 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: <none> + +# -- Skript -- +skript: + copyright: ~ created by & © Peter Güttinger aka Njol ~ + prefix: <gray>[<gold>Skript<gray>] # not used + quotes error: 无效的引号使用(")。如果你想在"引号文本"中使用引号,双写它们("")。 + brackets error: 括号的数量或位置无效。请确保每一个左括号都有一个相应的右括号。 + invalid reload: Skript只能通过Bukkit的“/reload”或Skript的“/skript reload”命令来重新加载。 + no scripts: 没有发现任何脚本,也许你应该写一些 ;) + no errors: 所有脚本都已加载,没有错误。 + scripts loaded: 已加载%s个脚本,包括%s个结构和%s个命令(%s内) + finished loading: 已完成加载。 + +# -- Skript command -- +skript command: + usage: 用法: + help: + description: Skript的主命令 + help: 显示此帮助信息。使用“/skript reload/enable/disable/update”来获取更多信息。 + reload: + description: 重新加载某个特定的脚本、所有的脚本、配置或所有的内容。 + all: 重新加载配置、所有alias和所有脚本 + config: 重新加载主配置 + aliases: 重新加载alias配置(aliases-english.zip或插件jar) + scripts: 重新加载所有脚本 + <script>: 重新加载某个特定的脚本或某个文件夹的脚本 + enable: + description: 启用所有脚本或某个特定的脚本 + all: 启用所有脚本 + <script>: 启用某个特定的脚本或某个文件夹的脚本 + disable: + description: 禁用所有脚本或某个特定的脚本 + all: 禁用所有脚本 + <script>: 禁用某个特定的脚本或某个文件夹的脚本 + update: + description: 检查更新,阅读更新日志,或下载最新版本的Skript + check: 检查新版本 + changes: 列出自当前版本以来的所有变化 + download: 下载最新的版本 + info: 打印一个带有Skript的别名和文档链接的信息 + gen-docs: 使用插件文件夹中的doc-templates生成文档 + test: 用于运行内部的Skript测试 + + invalid script: 无法在scripts文件夹中找到脚本<grey>“<gold>%s<grey>”<red>! + invalid folder: 无法在scripts文件夹中找到文件夹<grey>“<gold>%s<grey>”<red>! + reload: + warning line info: <gold><bold>第%s行:<gray>(%s)<reset>\n + error line info: <light red><bold>第%s行:<gray>(%s)<reset>\n + reloading: 正在重新加载<gold>%s<reset>… + reloaded: <lime>已成功重新加载<gold>%s<lime>。<gray>(<gold>%2$sms<gray>) + error: <light red>在重新加载<gold>%1$s<light red>时遇到了<gold>%2$s<light red>个错误!<gray>(<gold>%3$sms<gray>) + script disabled: <gold>%s<reset>已禁用。使用<gray>/<gold>skript <cyan>enable <red>%s<reset>来启用它。 + warning details: <yellow> %s<reset>\n + error details: <light red> %s<reset>\n + other details: <white> %s<reset>\n + line details: <gold> 行:<gray>%s<reset>\n <reset> + + config, aliases and scripts: 配置、其它配置和所有脚本 + scripts: 所有脚本 + main config: 主配置 + aliases: 其它配置 + script: <gold>%s<reset> + scripts in folder: <gold>%s<reset>中的所有脚本 + x scripts in folder: <gold>%1$s<reset>中的<gold>%2$s<reset>个脚本 + empty folder: <gold>%s<reset>不包含任何已启用的脚本。 + enable: + all: + enabling: 正在启用所有已禁用的脚本… + enabled: 已成功启用并解析了所有先前禁用的脚本。 + error: 在解析禁用的脚本时遇到了%s个错误! + io error: 无法加载一个或多个脚本 - 一些脚本可能已被重命名,并在服务器重新启动时启用:%s + single: + already enabled: <gold>%s<reset>已经启用了!如果它已被更改,使用<gray>/<gold>skript <cyan>reload <red>%s<reset>来重新加载。 + enabling: 正在启用<gold>%s<reset>… + enabled: 已成功启用并解析<gold>%s<reset>。 + error: 在解析<gold>%1$s<red>时遇到了%2$s个错误! + io error: 无法启用<gold>%s<red>:%s + folder: + empty: <gold>%s<reset>不包含任何已禁用的脚本。 + enabling: 正在启用<gold>%1$s<reset>中的<gold>%2$s<reset>个脚本… + enabled: 已成功启用并解析<gold>%1$s<reset>中的<gold>%2$s<reset>个先前禁用的脚本。 + error: 在解析<gold>%1$s<red>时遇到了%2$s个错误! + io error: 在启用<gold>%s<red>中的一个或多个脚本时出错(有些脚本可能会在服务器重新启动时启用):%s + disable: + all: + disabled: 已成功禁用所有脚本。 + io error: 无法重命名一个或多个脚本 - 一些脚本可能已被重命名,并在服务器重新启动时禁用:%s + single: + already disabled: <gold>%s<reset>已经被禁用了! + disabled: 已成功禁用<gold>%s<reset>。 + io error: 无法重命名<gold>%s<red>当你重新启动服务器时,它会再次启用:%s + folder: + empty: <gold>%s<reset>不包含任何已启用的脚本。 + disabled: 已成功禁用了<gold>%1$s<reset>中的<gold>%2$s<reset>的脚本。 + io error: 在禁用<gold>%s<red>中的一个或多个脚本时出错(有些脚本可能会在服务器重新启动时禁用):%s + update: + # check/download: see Updater + changes: + # multiple versions: + # title: <gold>%s<r> update¦ has¦s have¦ been released since this version (<gold>%s<r>) of Skript: + # footer: To show the changelog of a version type <gold>/skript update changes <version><reset> + # invalid version: No changelog for the version <gold>%s<red> available + title: <bold><cyan>%s<reset>(%s) + next page: <grey>第%s/%s页。使用<gold>/skript update changes %s<gray>查看下一页(提示:使用上箭头键) + info: + aliases: Skript的alias可以在这里找到:<aqua>https://github.com/SkriptLang/skript-aliases + documentation: Skript的文档可以在这里找到:<aqua>https://docs.skriptlang.org/ + tutorials: Skript的教程可以在这里找到:<aqua>https://docs.skriptlang.org/tutorials + version: Skript版本:<aqua>%s + server: 服务器版本:<aqua>%s + addons: 已安装的Skript插件:<aqua>%s + dependencies: 已安装的依赖:<aqua>%s + +# -- Updater -- +updater: + not started: Skript还没有检查最新版本。使用<gold>/skript update check<reset>来检查。 + checking: 正在检查最新版的Skript… + check in progress: 新版本的检查已经在进行中了。 + updater disabled: 更新程序已禁用,未能检查Skript的最新版本。 + check error: <red>检查最新版的Skript时出现了错误:<light red>%s + running latest version: 你目前正在运行最新的Skript稳定版本。 + running latest version (beta): 你目前运行的是Skript的<i>测试<r>版本,没有新的<i>稳定<r>版可用。请注意,你必须手动更新到较新的测试版! + update available: 新版本的Skript可用:<gold>%s<reset>(你目前正在运行<gold>%s<reset>)。 + downloading: 正在下载Skript<gold>%s<reset>… + download in progress: 最新版的下载已经在进行中了。 + download error: <red>下载最新版的Skript时出现了错误:<light red>%s + downloaded: 最新版本的Skript已下载完成! 重新启动服务器或使用/reload来应用更改。 + internal error: 在检查Skript的最新版本时发生了一个内部错误。请查看服务器日志以了解详情。 + custom version: 你目前正在运行一个自定义的Skript版本。不会自动安装更新。 + nightly build: 你目前正在运行Skript的测试版。不会自动安装更新。 + +# -- Commands -- +commands: + no permission message: 你没有必要的权限来使用这个命令 + cooldown message: 你使用此命令的频率过高,请稍后再试。 + executable by players: 这个命令只能由玩家使用 + executable by console: 这个命令只能在控制台使用 + correct usage: 正确的用法: + invalid argument: 无效的参数<gray>“%s<gray>”<reset>。允许的是: + too many arguments: 这个命令只能接受单个的%2$s。 + internal error: 在试图执行此命令时发生了一个内部错误。 + no player starts with: 没有名字以“%s”开头的玩家在线 + multiple players start with: 有多个名字以“%s”开头的玩家在线 + +# -- Hooks -- +hooks: + hooked: 成功与%s连接 + error: 无法连接到%1$s。这可能是因为Skript不支持已安装的%1$s版本。 + +# -- Aliases -- +aliases: + # Errors and warnings + empty string: ''不是一个物品类型 + invalid item type: “%s”不是一个物品类型 + empty name: 一个alias必须具有名称 + brackets error: 括号使用无效 + not enough brackets: 在字符%s(“%s”)处打开的括号必须被关闭 + too many brackets: 字符%s(“%s”)关闭了一个不存在的部分 + unknown variation: 变量%s没有被定义过 + missing aliases: 以下Minecraft ID没有任何alias: + empty alias: alias没有定义任何Minecraft ID或标签 + invalid minecraft id: Minecraft ID %s无效 + useless variation: 变量没有指定任何Minecraft ID或标签,所以它是无用的 + invalid tags: 指定的标签没有在有效的JSON中定义 + unexpected section: 这里不允许有字符串 + invalid variation section: 一个部分应该是变量,但%s不是有效的变量名 + outside section: alias必须放在字符串内 + + # Other messages + loaded x aliases from: 加载了%s个英语alias(来自%s) + loaded x aliases: 总共加载了%s个英语alias + +# -- Time -- +time: + errors: + 24 hours: 一天只有24小时 + 12 hours: 12小时制下不允许使用超过12小时 + 60 minutes: 一个小时只有60分钟 + +# -- IO Exceptions -- +io exceptions: + unknownhostexception: 无法连接到%s + accessdeniedexception: 拒绝访问%s From 3b1e576890bfca5e6daf42db0eacb31a8847f412 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Wed, 1 Feb 2023 02:42:28 -0700 Subject: [PATCH 218/619] Add Japanese Translation (#5418) * Add Japanese Translation (cherry picked from commit 0e9c2c1bba4af606abe850ed1c8997bc30a34bcc) * Proofread Japanese Translation (cherry picked from commit ee4dde57e63b27d92f0f97e2e42188ee43208144) * Capitalize Japanese in config.sk (cherry picked from commit 5d39b5a5c9868f72fdbd4856b287fffa22088835) * Fix Japanese Translation (cherry picked from commit 0541caa6b8cffa3e97bcf3c9fd4666bab27cf47e) * Fix Japanese Translation File (cherry picked from commit d67886a91f6d94399ef111587b2fe70e596fd969) --------- Co-authored-by: faketuna <faketuna.st@gmail.com> Co-authored-by: rilyhugu <rori1855@gmail.com> --- src/main/resources/config.sk | 4 +- src/main/resources/lang/japanese.lang | 190 ++++++++++++++++++++++++++ 2 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 src/main/resources/lang/japanese.lang diff --git a/src/main/resources/config.sk b/src/main/resources/config.sk index 9b19ad0bc5f..6212e21d47a 100644 --- a/src/main/resources/config.sk +++ b/src/main/resources/config.sk @@ -31,12 +31,12 @@ # ==== General Options ==== language: english -# Which language to use. Currently English, German, Korean, French, Polish and Simplified Chinese (simplifiedchinese.lang) are included in the download, but custom languages can be created as well. +# Which language to use. Currently English, German, Korean, French, Polish, Japanese and Simplified Chinese +# are included in the download, but custom languages can be created as well. Use the name in lowercase and no spaces as the value. # Please note that not everything can be translated yet, i.e. parts of Skript will still be english if you use another language. # If you want to translate Skript to your language please read the readme.txt located in the /lang/ folder in the jar # (open the jar as zip or rename it to Skript.zip to access it) - check for new version: true # Whether Skript should check for whether a new version of Skript is available when the server starts. # If this is set to true Skript will announce a new version to all players with the "skript.admin" permission. diff --git a/src/main/resources/lang/japanese.lang b/src/main/resources/lang/japanese.lang new file mode 100644 index 00000000000..64fa4c0d000 --- /dev/null +++ b/src/main/resources/lang/japanese.lang @@ -0,0 +1,190 @@ +# Default Japanese 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: <none> + +# -- Skript -- +skript: + copyright: ~ created by & © Peter Güttinger aka Njol ~ + prefix: <gray>[<gold>Skript<gray>] # not used + quotes error: クオーテーション(")の使い方が正しくありません。 クオーテーションをテキストに対して使用したい場合 "このように" 2つの "" で囲ってください。 + brackets error: セクションが正しく閉じられていません。 カッコの配置か量を見直してみてください。 + invalid reload: Skriptの再読み込みは Bukkit側の '/reload' もしくは Skript側の '/skript reload' でのみ再読み込みが可能です。 + no scripts: スクリプトが見つかりませんでした。 新しく書いてみてはいかがでしょうか。 + no errors: 全てのスクリプトはエラー無しでロードされました。 + scripts loaded: %s 個のスクリプトが読み込まれ %s 個の構造体が %s 個の中から読み込まれました。 + finished loading: 読み込みが完了しました。 + +# -- Skript command -- +skript command: + usage: 使用方法: + help: + description: Skriptのメインコマンドです。 + help: このメッセージを表示します。 '/skript reload/enable/disable/update' を使用してより詳細な情報を入手できます。 + reload: + description: 指定されたスクリプト, 全てのスクリプト, コンフィグ, または全てを再読み込みします。 + all: コンフィグ, 全てのエイリアス設定, 全てのスクリプトを再読み込みします。 + config: コンフィグを再読み込みします。 + aliases: 全てのエイリアス設定を再読み込みします。 (aliases-english.zip もしくは plugin jar) + scripts: 全てのスクリプトを再読み込みします。 + <script>: 特定のスクリプト、もしくはフォルダ内のスクリプトを再読み込みします。 + enable: + description: 指定されたスクリプト、もしくは全てのスクリプトを有効化します。 + all: 全てのスクリプトを有効化します。 + <script>: 特定のスクリプト、もしくはフォルダ内のスクリプトを有効化します。 + disable: + description: 指定されたスクリプト、もしくは全てのスクリプトを無効化します。 + all: 全てのスクリプトを無効化します。 + <script>: 特定のスクリプト、もしくはフォルダ内のスクリプトを無効化します。 + update: + description: アップデートのチェック, 変更履歴を見る, もしくは最新バージョンのSkriptをダウンロードします。 + check: 新しいバージョンが存在するか確認します。 + changes: 現在のバージョンとの変更点を表示します。 + download: 最新バージョンをダウンロードします。 + info: Skriptのエイリアスとドキュメントをリンク付きで表示します。 + gen-docs: pluginフォルダ内にテンプレートを使用してドキュメントを生成します。 + test: 内部テストを実行するために使用します。 + + invalid script: 指定されたスクリプト <grey>'<gold>%s<grey>'<red> はscriptsフォルダ内では見つかりませんでした! + invalid folder: 指定されたフォルダ <grey>'<gold>%s<grey>'<red> はscriptsフォルダ内では見つかりませんでした! + reload: + warning line info: <gold><bold>%s行目:<gray> (%s)<reset>\n + error line info: <light red><bold>%s行目:<gray> (%s)<reset>\n + reloading: <gold>%s<reset>を読み込み中... + reloaded: <gold>%s<lime>の読み込みに成功しました。<gray>(<gold>%2$sms<gray>) + error: <gold>%2$s <light red>個のエラーが<gold>%1$s<light red>の読み込み中に発見されました! <gray>(<gold>%3$sms<gray>) + script disabled: <gold>%s<reset>は現在無効化されています。<gray>/<gold>skript <cyan>enable <red>%s<reset>を使用して有効化できます。 + warning details: <yellow> %s<reset>\n + error details: <light red> %s<reset>\n + other details: <white> %s<reset>\n + line details: <gold> 該当部分: <gray>%s<reset>\n <reset> + + config, aliases and scripts: コンフィグ, エイリアスと全てのスクリプト + scripts: 全てのスクリプト + main config: コンフィグ + aliases: エイリアス + script: <gold>%s<reset> + scripts in folder: <gold>%s<reset>フォルダ内の全てのスクリプト + x scripts in folder: <gold>%1$s<reset>フォルダ内の<gold>%2$s<reset>個のスクリプト + empty folder: 有効化されたスクリプトが<gold>%s<reset>フォルダ内にありませんでした。 + enable: + all: + enabling: 無効化された全てのスクリプトを有効化しています... + enabled: 全ての無効化されていたスクリプトの解析・有効化に成功しました。 + error: 解析中に%s個のエラーが見つかりました! + io error: 一つもしくは複数のスクリプトがロードできませんでした。 一部のスクリプトはファイル名が変更された可能性があります。 またサーバー再起動時に有効化されます: %s + single: + already enabled: <gold>%s<reset>はすでに有効化されています! もし変更を反映したい場合は<gray>/<gold>skript <cyan>reload <red>%s<reset>を使用して再読み込みします。 + enabling: <gold>%s<reset>を有効化しています... + enabled: <gold>%s<reset>の解析・有効化に成功しました。 + error: <gold>%1$s<red>を解析中に%2$s個のエラーが見つかりました! + io error: <gold>%s<red>は有効化できませんでした。: %s + folder: + empty: <gold>%s<reset>フォルダ内に無効化されたスクリプトが見つかりませんでした。 + enabling: <gold>%1$s<reset>フォルダ内の<gold>%2$s<reset>個のスクリプトを有効化しています... + enabled: <gold>%1$s<reset>フォルダ内にある無効化されていた<gold>%2$s<reset>の解析・有効化に成功しました。 + error: <gold>%1$s<red>フォルダ内のスクリプトを解析中に%2$s個のエラーが見つかりました! + io error: <gold>%s<red>フォルダ内の一つもしくは複数のスクリプトのロード時にエラーが発生しました。 (一部のスクリプトはサーバー再起動時に有効になる場合があります): %s + disable: + all: + disabled: 全てのスクリプトの無効化に成功しました。 + io error: 一つもしくは複数のスクリプトのファイル名を変更できませんでした。 一部のスクリプトはファイル名が変更された可能性があります。 またサーバー再起動時に無効化されます: %s + single: + already disabled: <gold>%s<reset>はすでに無効化されています! + disabled: <gold>%s<reset>の無効化に成功しました。 + io error: <gold>%s<red>のファイル名の変更ができませんでした。 サーバー再起動時に再度有効化されます: %s + folder: + empty: <gold>%s<reset>フォルダ内に有効化されたスクリプトが見つかりませんでした。 + disabled: <gold>%1$s<reset>フォルダ内の<gold>%2$s<reset>個のスクリプトが無効化されました。 + io error: <gold>%s<red>フォルダ内の一つもしくは複数のスクリプトのファイル名を変更できませんでした。 (一部のスクリプトはサーバー再起動時に無効化される場合があります): %s + update: + # check/download: Upadater をご確認ください + changes: + # multiple versions: + # title: <gold>%s<r>個のアップデートが 現在のSkriptバージョン (<gold>%s<r>) に存在します: + # footer: このバージョンの変更履歴を確認したい場合は<gold>/skript update changes <version><reset>を実行してください。 + # invalid version: バージョン<gold>%s<red>の変更履歴はありません。 + title: <bold><cyan>%s<reset> (%s) + next page: <grey>(%s/%s) <gold>/skript update changes %s<gray> で次のページを表示します。 (ヒント: 上矢印キーを押すと良いかもしれません) + info: + aliases: Skriptのエイリアス一覧はこちらからご覧いただけます: <aqua>https://github.com/SkriptLang/skript-aliases + documentation: Skriptのドキュメントはこちらからご覧いただけます: <aqua>https://docs.skriptlang.org/ + tutorials: Skriptのチュートリアルはこちらからご覧いただけます: <aqua>https://docs.skriptlang.org/tutorials + version: Skriptのバージョン: <aqua>%s + server: サーバーのバージョン: <aqua>%s + addons: インストール済みのSkriptアドオン: <aqua>%s + dependencies: インストール済みの依存関係: <aqua>%s + +# -- Updater -- +updater: + not started: Skriptは最新バージョンの確認をしていません。 確認をするには<gold>/skript update check<reset>を使用してください。 + checking: Skriptの最新バージョンを確認中... + check in progress: 最新バージョンは現在確認中です。 + updater disabled: 更新が無効化されているため、Skriptの最新バージョンの確認は行われませんでした。 + check error: <red>Skriptの最新バージョンの確認中にエラーが発生しました:<light red> %s + running latest version: 最新安定版のSkriptが動作しています。 + running latest version (beta): <i>ベータ版<r>のSkriptが動作しています。 また新しい<i>安定版<r>のSkriptはありません。 新しいベータ版へのアップデートは手動で行う必要があるためご注意ください! + update available: 新しいバージョンのSkriptが存在します: <gold>%s<reset> (現在は<gold>%s<reset>が動作しています。) + downloading: Skript <gold>%s<reset>をダウンロード中... + download in progress: Skriptの最新バージョンは現在ダウンロード中です。 + download error: <red>Skriptの最新バージョンをダウンロード中にエラーが発生しました:<light red> %s + downloaded: Skriptの最新バージョンがダウンロードされました! サーバーを再起動、または/reloadを使用し更新を適用してください。 + internal error: 内部エラーが発生したためSkriptの最新バージョンの確認に失敗しました。 詳細はサーバーログをご確認ください。 + custom version: カスタマイズされたSkriptが動作しています。 アップデートは自動的にインストールされません。 + nightly build: 開発版のSkriptが動作しています。 アップデートは自動的にインストールされません。 + +# -- Commands -- +commands: + no permission message: このコマンドを使用するための権限がありません。 + cooldown message: このコマンドを使用しすぎたため現在使用できません、時間を開けて再度お試しください。 + executable by players: このコマンドはプレイヤーのみ実行できます。 + executable by console: このコマンドはコンソールのみ実行できます。 + correct usage: 使用方法: + invalid argument: 引数<gray>'%s<gray>'<reset>は使用できません。 使用可能な引数: + too many arguments: このコマンドは引数%2$sのみ使用できます。 + internal error: このコマンドを実行中に内部エラーが発生しました。 + no player starts with: '%s'で始まるプレイヤーが見つかりませんでした。 + multiple players start with: '%s'で始まるプレイヤーが複数存在します。 + +# -- Hooks -- +hooks: + hooked: %sのフックに成功しました。 + error: %1$sのフックに失敗しました。 Skriptが%1$sのバージョンをサポートしていない場合に発生する可能性があります。 + +# -- Aliases -- +aliases: + # Errors and warnings + empty string: ''はitem typeではありません。 + invalid item type: '%s'はitem typeではありません。 + empty name: エイリアスは名前を付ける必要があります。 + brackets error: カッコの使い方が間違っています。 + not enough brackets: %s ('%s')で始まったセクションは閉じる必要があります。 + too many brackets: %s ('%s')は存在しないセクションを閉じています。 + unknown variation: %sは定義されていません。 + missing aliases: 以下のIDのエイリアスは定義されていません: + empty alias: %sは空の値です。 + invalid minecraft id: %sは有効なMinecraft IDではありません。 + useless variation: VariationにMinecraft IDまたはタグが指定されていないため使用できません。 + invalid tags: 指定されたタグは有効なJSONに定義されていません。 + unexpected section: このセクションはここでは使用できません。 + invalid variation section: このセクションはvariationセクションです。 ですが%sは有効なvariation名ではありません。 + outside section: エイリアスはセクションに配置する必要があります。 + + # Other messages + loaded x aliases from: %s のエイリアスを %s からロードしました。 + loaded x aliases: %s 個のエイリアスをロードしました。 + +# -- Time -- +time: + errors: + 24 hours: 1日は24時間しかありません。 + 12 hours: 12時間制では12時間以上を指定できません + 60 minutes: 1時間は60分しかありません + +# -- IO Exceptions -- +io exceptions: + unknownhostexception: %s への接続に失敗しました + accessdeniedexception: %s へのアクセスが拒否されました From 4f054e436eb1179f65c8d1d6f3bfef74c54d6e58 Mon Sep 17 00:00:00 2001 From: Kiip <25848425+kiip1@users.noreply.github.com> Date: Wed, 1 Feb 2023 16:29:41 +0100 Subject: [PATCH 219/619] Fix uncoloured expression (#5224) --- .../njol/skript/util/chat/ChatMessages.java | 57 +++++++++++-------- .../syntaxes/expressions/ExprColoured.sk | 5 ++ 2 files changed, 37 insertions(+), 25 deletions(-) create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprColoured.sk diff --git a/src/main/java/ch/njol/skript/util/chat/ChatMessages.java b/src/main/java/ch/njol/skript/util/chat/ChatMessages.java index 88b90c62766..8a80f65929e 100644 --- a/src/main/java/ch/njol/skript/util/chat/ChatMessages.java +++ b/src/main/java/ch/njol/skript/util/chat/ChatMessages.java @@ -18,6 +18,16 @@ */ package ch.njol.skript.util.chat; +import ch.njol.skript.Skript; +import ch.njol.skript.SkriptAddon; +import ch.njol.skript.localization.Language; +import ch.njol.skript.localization.LanguageChangeListener; +import ch.njol.skript.util.Utils; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import net.md_5.bungee.api.ChatColor; +import org.eclipse.jdt.annotation.Nullable; + import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -29,17 +39,6 @@ import java.util.Set; import java.util.regex.Pattern; -import org.eclipse.jdt.annotation.Nullable; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import ch.njol.skript.Skript; -import ch.njol.skript.SkriptAddon; -import ch.njol.skript.localization.Language; -import ch.njol.skript.localization.LanguageChangeListener; -import ch.njol.skript.util.Utils; -import net.md_5.bungee.api.ChatColor; - /** * Handles parsing chat messages. */ @@ -573,25 +572,33 @@ public static void registerAddonCode(@Nullable SkriptAddon addon, @Nullable Chat registerChatCode(code); } + private static final Pattern HEX_COLOR_PATTERN = Pattern.compile("[§&]x"); + private static final Pattern ANY_COLOR_PATTERN = Pattern.compile("(?i)[&§][0-9a-folkrnm]"); + /** * Strips all styles from given string. * @param text String to strip styles from. * @return A string without styles. */ public static String stripStyles(String text) { - List<MessageComponent> components = parse(text); - StringBuilder sb = new StringBuilder(); - for (MessageComponent component : components) { // This also strips bracket tags ex. <red> <ttp:..> etc. - sb.append(component.text); - } - String plain = sb.toString(); - - if (Utils.HEX_SUPPORTED) // Strip '§x', '&x' - plain = plain.replaceAll("[§&]x", ""); - - plain = plain.replaceAll("(?i)[&§][0-9a-folkrnm]", ""); // strips colors & or § (ex. &5) - - assert plain != null; - return plain; + String previous; + String result = text; + do { + previous = result; + + List<MessageComponent> components = parse(result); + StringBuilder builder = new StringBuilder(); + for (MessageComponent component : components) { // This also strips bracket tags ex. <red> <ttp:..> etc. + builder.append(component.text); + } + String plain = builder.toString(); + + if (Utils.HEX_SUPPORTED) // Strip '§x', '&x' + plain = HEX_COLOR_PATTERN.matcher(plain).replaceAll(""); + + result = ANY_COLOR_PATTERN.matcher(plain).replaceAll(""); // strips colors & or § (ex. &5) + } while (!previous.equals(result)); + + return result; } } diff --git a/src/test/skript/tests/syntaxes/expressions/ExprColoured.sk b/src/test/skript/tests/syntaxes/expressions/ExprColoured.sk new file mode 100644 index 00000000000..91a9a02a485 --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprColoured.sk @@ -0,0 +1,5 @@ +test "(un)coloured": + assert uncoloured "<red>B" is "B" with "Tag strip failed" + assert uncoloured "<red<red<red>>>B" is "B" with "Recursive tag strip failed" + assert uncoloured "&aB" is "B" with "Colour strip failed" + assert uncoloured "&&aaB" is "B" with "Recursive colour strip failed" From 67339e395418017f69af40087eb9bf24b9f04ef0 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Thu, 2 Feb 2023 01:31:48 -0500 Subject: [PATCH 220/619] Improve ItemMeta comparisons (#5374) --- .../java/ch/njol/skript/aliases/ItemData.java | 46 +++++++------------ 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/src/main/java/ch/njol/skript/aliases/ItemData.java b/src/main/java/ch/njol/skript/aliases/ItemData.java index 8b7f4d261a0..faaa8c76169 100644 --- a/src/main/java/ch/njol/skript/aliases/ItemData.java +++ b/src/main/java/ch/njol/skript/aliases/ItemData.java @@ -29,7 +29,6 @@ import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilExtendedSerializable; import org.bukkit.Bukkit; import org.bukkit.Material; -import org.bukkit.OfflinePlayer; import org.bukkit.block.Block; import org.bukkit.block.BlockState; import org.bukkit.enchantments.Enchantment; @@ -37,9 +36,6 @@ import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.inventory.meta.PotionMeta; -import org.bukkit.inventory.meta.SkullMeta; -import org.bukkit.potion.PotionData; import org.eclipse.jdt.annotation.Nullable; import java.io.IOException; @@ -50,6 +46,7 @@ import java.nio.file.Paths; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Objects; import java.util.Set; @@ -371,8 +368,7 @@ private boolean hasFlag(int flag) { /** * Compares {@link ItemMeta}s for {@link #matchAlias(ItemData)}. - * Note that this does NOT compare everything; only the most - * important bits. + * Note that this method assumes the metas are coming from similar types. * @param first Meta of this item. * @param second Meta of given item. * @return Match quality of metas. @@ -417,30 +413,22 @@ private static MatchQuality compareItemMetas(ItemMeta first, ItemMeta second) { if (!newQuality.isBetter(quality)) quality = newQuality; } - - // Potion data - if (second instanceof PotionMeta) { - if (!(first instanceof PotionMeta)) { - return MatchQuality.DIFFERENT; // Second is a potion, first is clearly not - } - // Compare potion type, including extended and level 2 attributes - PotionData ourPotion = ((PotionMeta) first).getBasePotionData(); - PotionData theirPotion = ((PotionMeta) second).getBasePotionData(); - return !Objects.equals(ourPotion, theirPotion) ? MatchQuality.SAME_MATERIAL : quality; - } - // Skull owner - if (second instanceof SkullMeta) { - if (!(first instanceof SkullMeta)) { - return MatchQuality.DIFFERENT; // Second is a skull, first is clearly not - } - // Compare skull owners - OfflinePlayer ourOwner = ((SkullMeta) first).getOwningPlayer(); - OfflinePlayer theirOwner = ((SkullMeta) second).getOwningPlayer(); - return !Objects.equals(ourOwner, theirOwner) ? MatchQuality.SAME_MATERIAL : quality; - } - - return quality; + // awful but we have to make these values the same so that they don't matter for comparison + second = second.clone(); // don't actually change it + + second.setDisplayName(ourName); // set our name + second.setLore(ourLore); // set our lore + for (Enchantment theirEnchant : theirEnchants.keySet()) // remove their enchants + second.removeEnchant(theirEnchant); + for (Entry<Enchantment, Integer> ourEnchant : ourEnchants.entrySet()) // add our enchants + second.addEnchant(ourEnchant.getKey(), ourEnchant.getValue(), true); + for (ItemFlag theirFlag : theirFlags) // remove their flags + second.removeItemFlags(theirFlag); + for (ItemFlag ourFlag : ourFlags) // add our flags + second.addItemFlags(ourFlag); + + return first.equals(second) ? quality : MatchQuality.SAME_MATERIAL; } /** From 1f20b98f5a7476b2a1caec298f72b3e360c170f7 Mon Sep 17 00:00:00 2001 From: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> Date: Thu, 2 Feb 2023 03:06:32 -0600 Subject: [PATCH 221/619] Automatically generate and push docs for nightly editions (#5383) --- .github/workflows/docs.yml | 58 +++++++++++++++++++ build.gradle | 8 +++ .../ch/njol/skript/doc/Documentation.java | 6 +- 3 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/docs.yml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000000..908e03bb81c --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,58 @@ +name: Nightly Docs + +on: + push: + branches: + - master + - 'dev/**' + +jobs: + docs: + if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + path: skript + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'adopt' + cache: gradle + - name: Grant execute permission for gradlew + run: chmod +x ./skript/gradlew + - name: Checkout skript-docs + uses: actions/checkout@v3 + with: + repository: 'SkriptLang/skript-docs' + path: skript-docs + ssh-key: ${{ secrets.DOCS_DEPLOY_KEY }} + - name: Setup documentation environment + env: + DOCS_DEPLOY_KEY: ${{ secrets.DOCS_DEPLOY_KEY }} + run: | + eval `ssh-agent` + DOC_DIR="${GITHUB_WORKSPACE}/skript-docs/docs/nightly/${GITHUB_REF#refs/heads/}/" + echo "DOC_DIR=${DOC_DIR}" >> $GITHUB_ENV + echo "SKRIPT_DOCS_TEMPLATE_DIR=${GITHUB_WORKSPACE}/skript-docs/doc-templates" >> $GITHUB_ENV + echo "SKRIPT_DOCS_OUTPUT_DIR=${DOC_DIR}" >> $GITHUB_ENV + echo "$DOCS_DEPLOY_KEY" | tr -d '\r' | ssh-add - > /dev/null + mkdir ~/.ssh + ssh-keyscan www.github.com >> ~/.ssh/known_hosts + - name: Build Skript and generate docs + run: | + cd ./skript + ./gradlew clean genDocs javadoc + mv "${GITHUB_WORKSPACE}/skript/build/docs/javadoc" "${DOC_DIR}/javadocs" + cd .. + - name: Push nightly docs + if: success() + run: | + cd ./skript-docs + git config user.email "nightlydocs@skriptlang.org" + git config user.name "Nightly Doc Bot" + git add docs/nightly + git commit -m "Update nightly docs" + git push origin main diff --git a/build.gradle b/build.gradle index c1bbdbd2945..0c4ff493238 100644 --- a/build.gradle +++ b/build.gradle @@ -135,6 +135,14 @@ license { exclude('**/*.json') // JSON files do not have headers } +javadoc { + 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 { diff --git a/src/main/java/ch/njol/skript/doc/Documentation.java b/src/main/java/ch/njol/skript/doc/Documentation.java index c40fe0e15e9..3916d5f1d5c 100644 --- a/src/main/java/ch/njol/skript/doc/Documentation.java +++ b/src/main/java/ch/njol/skript/doc/Documentation.java @@ -80,11 +80,13 @@ public static boolean canGenerateUnsafeDocs() { } public static File getDocsTemplateDirectory() { - return DOCS_TEMPLATE_DIRECTORY; + String environmentTemplateDir = System.getenv("SKRIPT_DOCS_TEMPLATE_DIR"); + return environmentTemplateDir == null ? DOCS_TEMPLATE_DIRECTORY : new File(environmentTemplateDir); } public static File getDocsOutputDirectory() { - return DOCS_OUTPUT_DIRECTORY; + String environmentOutputDir = System.getenv("SKRIPT_DOCS_OUTPUT_DIR"); + return environmentOutputDir == null ? DOCS_OUTPUT_DIRECTORY : new File(environmentOutputDir); } public static void generate() { From b9ba626241d2363ffb74492ce1d404b186dbc0e6 Mon Sep 17 00:00:00 2001 From: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> Date: Thu, 2 Feb 2023 14:02:05 -0600 Subject: [PATCH 222/619] Fix issue with nightly docs job not working when javadocs dir is already present (#5420) --- .github/workflows/docs.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 908e03bb81c..3bbdc60b52f 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -34,10 +34,11 @@ jobs: DOCS_DEPLOY_KEY: ${{ secrets.DOCS_DEPLOY_KEY }} run: | eval `ssh-agent` - DOC_DIR="${GITHUB_WORKSPACE}/skript-docs/docs/nightly/${GITHUB_REF#refs/heads/}/" + DOC_DIR="${GITHUB_WORKSPACE}/skript-docs/docs/nightly/${GITHUB_REF#refs/heads/}" + rm -r ${DOC_DIR}/* || true echo "DOC_DIR=${DOC_DIR}" >> $GITHUB_ENV echo "SKRIPT_DOCS_TEMPLATE_DIR=${GITHUB_WORKSPACE}/skript-docs/doc-templates" >> $GITHUB_ENV - echo "SKRIPT_DOCS_OUTPUT_DIR=${DOC_DIR}" >> $GITHUB_ENV + echo "SKRIPT_DOCS_OUTPUT_DIR=${DOC_DIR}/" >> $GITHUB_ENV echo "$DOCS_DEPLOY_KEY" | tr -d '\r' | ssh-add - > /dev/null mkdir ~/.ssh ssh-keyscan www.github.com >> ~/.ssh/known_hosts @@ -54,5 +55,5 @@ jobs: git config user.email "nightlydocs@skriptlang.org" git config user.name "Nightly Doc Bot" git add docs/nightly - git commit -m "Update nightly docs" + git commit -m "Update nightly docs" || (echo "Nothing to push!" && exit 0) git push origin main From 34038dcb51f593ca7a0bcad71977d8f3de6b2050 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Thu, 2 Feb 2023 13:16:12 -0700 Subject: [PATCH 223/619] Stop using String for enum serialization (#4930) --- .../njol/skript/classes/EnumSerializer.java | 28 +++- .../skript/classes/YggdrasilSerializer.java | 22 ++- .../skript/classes/data/SkriptClasses.java | 125 +++--------------- .../ch/njol/skript/entity/EntityType.java | 26 +--- 4 files changed, 58 insertions(+), 143 deletions(-) diff --git a/src/main/java/ch/njol/skript/classes/EnumSerializer.java b/src/main/java/ch/njol/skript/classes/EnumSerializer.java index e88a655bf2a..d0c32ab039c 100644 --- a/src/main/java/ch/njol/skript/classes/EnumSerializer.java +++ b/src/main/java/ch/njol/skript/classes/EnumSerializer.java @@ -18,6 +18,8 @@ */ package ch.njol.skript.classes; +import java.io.StreamCorruptedException; + import org.eclipse.jdt.annotation.Nullable; import ch.njol.yggdrasil.ClassResolver; @@ -25,21 +27,22 @@ /** * Mainly kept for backwards compatibility, but also serves as {@link ClassResolver} for enums. - * - * @author Peter Güttinger */ public class EnumSerializer<T extends Enum<T>> extends Serializer<T> { private final Class<T> c; - public EnumSerializer(final Class<T> c) { + public EnumSerializer(Class<T> c) { this.c = c; } + /** + * Enum serialization has been using String serialization since Skript (INSERT VERSION) + */ @Override @Deprecated @Nullable - public T deserialize(final String s) { + public T deserialize(String s) { try { return Enum.valueOf(c, s); } catch (final IllegalArgumentException e) { @@ -59,12 +62,23 @@ public boolean canBeInstantiated() { } @Override - public Fields serialize(final T t) { - throw new IllegalStateException(); // not used + public Fields serialize(T e) { + Fields fields = new Fields(); + fields.putPrimitive("name", e.name()); + return fields; + } + + @Override + public T deserialize(Fields fields) { + try { + return Enum.valueOf(c, fields.getAndRemovePrimitive("name", String.class)); + } catch (IllegalArgumentException | StreamCorruptedException e) { + return null; + } } @Override - public void deserialize(final T o, final Fields f) { + public void deserialize(T o, Fields f) { assert false; } diff --git a/src/main/java/ch/njol/skript/classes/YggdrasilSerializer.java b/src/main/java/ch/njol/skript/classes/YggdrasilSerializer.java index de43cd79820..5fab3d52342 100644 --- a/src/main/java/ch/njol/skript/classes/YggdrasilSerializer.java +++ b/src/main/java/ch/njol/skript/classes/YggdrasilSerializer.java @@ -26,25 +26,41 @@ import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilExtendedSerializable; /** - * @author Peter Güttinger + * Serializer that allows Yggdrasil to automatically serialize classes that extend YggdrasilSerializable. */ public class YggdrasilSerializer<T extends YggdrasilSerializable> extends Serializer<T> { @Override - public Fields serialize(final T o) throws NotSerializableException { + public final Fields serialize(final T o) throws NotSerializableException { if (o instanceof YggdrasilExtendedSerializable) return ((YggdrasilExtendedSerializable) o).serialize(); return new Fields(o); } @Override - public void deserialize(final T o, final Fields f) throws StreamCorruptedException, NotSerializableException { + public final void deserialize(final T o, final Fields f) throws StreamCorruptedException, NotSerializableException { if (o instanceof YggdrasilExtendedSerializable) ((YggdrasilExtendedSerializable) o).deserialize(f); else f.setFields(o); } + /** + * Deserialises an object from a string returned by this serializer or an earlier version thereof. + * <p> + * This method should only return null if the input is invalid (i.e. not produced by {@link #serialize(Object)} or an older version of that method) + * <p> + * This method must only be called from Bukkit's main thread if {@link #mustSyncDeserialization()} returned true. + * + * @param s + * @return The deserialised object or null if the input is invalid. An error message may be logged to specify the cause. + */ + @Deprecated + @Override + public T deserialize(String s) { + return null; + } + @Override public boolean mustSyncDeserialization() { return false; diff --git a/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java b/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java index 2f894ce5580..5966704ece4 100644 --- a/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java @@ -18,6 +18,15 @@ */ package ch.njol.skript.classes.data; +import java.io.StreamCorruptedException; +import java.util.Locale; +import java.util.regex.Pattern; + +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemStack; +import org.eclipse.jdt.annotation.Nullable; + import ch.njol.skript.Skript; import ch.njol.skript.aliases.Aliases; import ch.njol.skript.aliases.ItemData; @@ -55,14 +64,6 @@ import ch.njol.skript.util.visual.VisualEffect; import ch.njol.skript.util.visual.VisualEffects; import ch.njol.yggdrasil.Fields; -import org.bukkit.Material; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.inventory.ItemStack; -import org.eclipse.jdt.annotation.Nullable; - -import java.io.StreamCorruptedException; -import java.util.Locale; -import java.util.regex.Pattern; /** * @author Peter Güttinger @@ -268,23 +269,7 @@ public String toString(final Time t, final int flags) { public String toVariableNameString(final Time o) { return "time:" + o.getTicks(); } - }).serializer(new YggdrasilSerializer<Time>() { -// return "" + t.getTicks(); - @Override - @Nullable - public Time deserialize(final String s) { - try { - return new Time(Integer.parseInt(s)); - } catch (final NumberFormatException e) { - return null; - } - } - - @Override - public boolean mustSyncDeserialization() { - return false; - } - })); + }).serializer(new YggdrasilSerializer<>())); Classes.registerClass(new ClassInfo<>(Timespan.class, "timespan") .user("time ?spans?") @@ -322,23 +307,7 @@ public String toString(final Timespan t, final int flags) { public String toVariableNameString(final Timespan o) { return "timespan:" + o.getMilliSeconds(); } - }).serializer(new YggdrasilSerializer<Timespan>() { -// return "" + t.getMilliSeconds(); - @Override - @Nullable - public Timespan deserialize(final String s) { - try { - return new Timespan(Long.parseLong(s)); - } catch (final NumberFormatException e) { - return null; - } - } - - @Override - public boolean mustSyncDeserialization() { - return false; - } - }) + }).serializer(new YggdrasilSerializer<>()) .math(Timespan.class, new Arithmetic<Timespan, Timespan>() { @Override public Timespan difference(final Timespan t1, final Timespan t2) { @@ -419,21 +388,7 @@ public String toString(final Timeperiod o, final int flags) { public String toVariableNameString(final Timeperiod o) { return "timeperiod:" + o.start + "-" + o.end; } - }).serializer(new YggdrasilSerializer<Timeperiod>() { -// return t.start + "-" + t.end; - @Override - @Nullable - public Timeperiod deserialize(final String s) { - final String[] split = s.split("-"); - if (split.length != 2) - return null; - try { - return new Timeperiod(Integer.parseInt(split[0]), Integer.parseInt(split[1])); - } catch (final NumberFormatException e) { - return null; - } - } - })); + }).serializer(new YggdrasilSerializer<>())); Classes.registerClass(new ClassInfo<>(Date.class, "date") .user("dates?") @@ -445,18 +400,8 @@ public Timeperiod deserialize(final String s) { "subtract a day from {_yesterday}", "# now {_yesterday} represents the date 24 hours before now") .since("1.4") - .serializer(new YggdrasilSerializer<Date>() { -// return "" + d.getTimestamp(); - @Override - @Nullable - public Date deserialize(final String s) { - try { - return new Date(Long.parseLong(s)); - } catch (final NumberFormatException e) { - return null; - } - } - }).math(Timespan.class, new Arithmetic<Date, Timespan>() { + .serializer(new YggdrasilSerializer<>()) + .math(Timespan.class, new Arithmetic<Date, Timespan>() { @Override public Timespan difference(final Date first, final Date second) { return first.difference(second); @@ -522,15 +467,7 @@ public String toVariableNameString(final Direction o) { return o.toString(); } }) - .serializer(new YggdrasilSerializer<Direction>() { -// return o.serialize(); - @Override - @Deprecated - @Nullable - public Direction deserialize(final String s) { - return Direction.deserialize(s); - } - })); + .serializer(new YggdrasilSerializer<>())); Classes.registerClass(new ClassInfo<>(Slot.class, "slot") .user("(inventory )?slots?") @@ -731,24 +668,7 @@ public String toVariableNameString(final EnchantmentType o) { return o.toString(); } }) - .serializer(new YggdrasilSerializer<EnchantmentType>() { -// return o.getType().getId() + ":" + o.getLevel(); - @Override - @Nullable - public EnchantmentType deserialize(final String s) { - final String[] split = s.split(":"); - if (split.length != 2) - return null; - try { - final Enchantment ench = EnchantmentUtils.getByKey(split[0]); - if (ench == null) - return null; - return new EnchantmentType(ench, Integer.parseInt(split[1])); - } catch (final NumberFormatException e) { - return null; - } - } - })); + .serializer(new YggdrasilSerializer<>())); Classes.registerClass(new ClassInfo<>(Experience.class, "experience") .user("experience ?(points?)?") @@ -785,18 +705,7 @@ public String toVariableNameString(final Experience xp) { } }) - .serializer(new YggdrasilSerializer<Experience>() { -// return "" + xp; - @Override - @Nullable - public Experience deserialize(final String s) { - try { - return new Experience(Integer.parseInt(s)); - } catch (final NumberFormatException e) { - return null; - } - } - })); + .serializer(new YggdrasilSerializer<>())); Classes.registerClass(new ClassInfo<>(VisualEffect.class, "visualeffect") .name("Visual Effect") diff --git a/src/main/java/ch/njol/skript/entity/EntityType.java b/src/main/java/ch/njol/skript/entity/EntityType.java index d9ce332e6a9..b5174caebc4 100644 --- a/src/main/java/ch/njol/skript/entity/EntityType.java +++ b/src/main/java/ch/njol/skript/entity/EntityType.java @@ -61,31 +61,7 @@ public String toVariableNameString(final EntityType t) { return "entitytype:" + t.toString(); } }) - .serializer(new YggdrasilSerializer<EntityType>() { -// return t.amount + "*" + EntityData.serializer.serialize(t.data); - @Override - @Deprecated - @Nullable - public EntityType deserialize(final String s) { - final String[] split = s.split("\\*", 2); - if (split.length != 2) - return null; - @SuppressWarnings("null") - final EntityData<?> d = EntityData.serializer.deserialize(split[1]); - if (d == null) - return null; - try { - return new EntityType(d, Integer.parseInt(split[0])); - } catch (final NumberFormatException e) { - return null; - } - } - - @Override - public boolean mustSyncDeserialization() { - return false; - } - })); + .serializer(new YggdrasilSerializer<>())); } public int amount = -1; From 1349d86d1c2c2df9b4731ddcc39b55220dd89062 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Fri, 3 Feb 2023 20:58:45 -0500 Subject: [PATCH 224/619] Fix expression list checking (#5423) --- .../ch/njol/skript/lang/ExpressionList.java | 18 +++++++++--------- .../tests/misc/expression list checking.sk | 3 +++ 2 files changed, 12 insertions(+), 9 deletions(-) create mode 100644 src/test/skript/tests/misc/expression list checking.sk diff --git a/src/main/java/ch/njol/skript/lang/ExpressionList.java b/src/main/java/ch/njol/skript/lang/ExpressionList.java index 6ec5ef8c54c..8a6a26fc909 100644 --- a/src/main/java/ch/njol/skript/lang/ExpressionList.java +++ b/src/main/java/ch/njol/skript/lang/ExpressionList.java @@ -148,22 +148,22 @@ public boolean isSingle() { } @Override - public boolean check(Event e, Checker<? super T> c, boolean negated) { - return negated ^ check(e, c); - } - - @Override - public boolean check(Event e, Checker<? super T> c) { + public boolean check(Event event, Checker<? super T> checker, boolean negated) { for (Expression<? extends T> expr : expressions) { - boolean b = expr.check(e, c); - if (and && !b) + boolean result = expr.check(event, checker) ^ negated; + if (and && !result) return false; - if (!and && b) + if (!and && result) return true; } return and; } + @Override + public boolean check(Event event, Checker<? super T> checker) { + return check(event, checker, false); + } + @SuppressWarnings("unchecked") @Override @Nullable diff --git a/src/test/skript/tests/misc/expression list checking.sk b/src/test/skript/tests/misc/expression list checking.sk new file mode 100644 index 00000000000..450e7926d5d --- /dev/null +++ b/src/test/skript/tests/misc/expression list checking.sk @@ -0,0 +1,3 @@ +test "expression list checking": + assert "" or {_empty} is empty with "both values aren't empty, but one should be" + assert "" or {_empty} is not empty with "both values are empty, but one shouldn't be" From bf643bca62909e705479fd4925e80c5d7ff02e79 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Sat, 4 Feb 2023 05:32:23 +0300 Subject: [PATCH 225/619] =?UTF-8?q?=F0=9F=92=A3=20Remove=20docs=20folder?= =?UTF-8?q?=20(#5376)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/README.md | 35 - docs/assets/Logo/Skript Logo Circular.png | Bin 19415 -> 0 bytes docs/assets/Logo/Skript Logo.jpg | Bin 30413 -> 0 bytes docs/assets/Logo/Skript Logo.png | Bin 10738 -> 0 bytes docs/assets/icon.png | Bin 19415 -> 0 bytes docs/assets/light-off.svg | 70 -- docs/assets/light-on.svg | 114 -- docs/assets/search.svg | 173 --- docs/classes.html | 5 - docs/conditions.html | 5 - docs/css/styles.css | 1197 --------------------- docs/docs.html | 5 - docs/docs.json | 33 - docs/effects.html | 5 - docs/events.html | 5 - docs/expressions.html | 5 - docs/functions.html | 12 - docs/index.html | 65 -- docs/js/functions.js | 353 ------ docs/js/main.js | 408 ------- docs/js/theme-switcher.js | 34 - docs/sections.html | 5 - docs/template.html | 51 - docs/templates/desc_full.html | 59 - docs/templates/desc_full.json | 11 - docs/templates/desc_nav.html | 1 - docs/templates/navbar.html | 35 - docs/templates/pattern_element.html | 1 - docs/templates/pattern_element.json | 1 - docs/text.html | 337 ------ docs/tutorials.html | 16 - 31 files changed, 3041 deletions(-) delete mode 100644 docs/README.md delete mode 100644 docs/assets/Logo/Skript Logo Circular.png delete mode 100644 docs/assets/Logo/Skript Logo.jpg delete mode 100644 docs/assets/Logo/Skript Logo.png delete mode 100644 docs/assets/icon.png delete mode 100644 docs/assets/light-off.svg delete mode 100644 docs/assets/light-on.svg delete mode 100644 docs/assets/search.svg delete mode 100644 docs/classes.html delete mode 100644 docs/conditions.html delete mode 100644 docs/css/styles.css delete mode 100644 docs/docs.html delete mode 100644 docs/docs.json delete mode 100644 docs/effects.html delete mode 100644 docs/events.html delete mode 100644 docs/expressions.html delete mode 100644 docs/functions.html delete mode 100644 docs/index.html delete mode 100644 docs/js/functions.js delete mode 100644 docs/js/main.js delete mode 100644 docs/js/theme-switcher.js delete mode 100644 docs/sections.html delete mode 100644 docs/template.html delete mode 100644 docs/templates/desc_full.html delete mode 100644 docs/templates/desc_full.json delete mode 100644 docs/templates/desc_nav.html delete mode 100644 docs/templates/navbar.html delete mode 100644 docs/templates/pattern_element.html delete mode 100644 docs/templates/pattern_element.json delete mode 100644 docs/text.html delete mode 100644 docs/tutorials.html diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 30f465c13e5..00000000000 --- a/docs/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# Skript Documentation Templates - -Skript's features are documented directly in it's Java code. But we still need - -1. Clear tutorials, not just "you can check the syntax pattern" -2. Examples explained, if needed - -When generating final result, each HTML file is surrounded by template.html, -which provides head element, navigation bar and so on. - -## Template Patterns - -Patterns have syntax of ${pattern_here}. For example, ${skript.version} is replaced with -current Skript version. Please see below for more... - -You can also include other files by using ${include <filename>}. Just please make -sure that those included files don't have tags which are not allowed in position -where include is called. - -## Pattern Reference -``` -skript.* - Information of Skript -version - Skript's version -include <filename> - Load given file and place them here -generate <expressions/effects/events/types/functions> <loop template file> - Generated reference -content - In template.html, marks the point where other file is placed -``` - -## Generating the documentation - -1. You will need to create a directory named `doc-templates` in `plugins/Skript/`, and copy everything from [here](https://github.com/SkriptLang/Skript/tree/master/docs) into that directory. -2. Execute the command `/sk gen-docs`. -3. The `docs/` directory will be created _(if not created already)_ in `plugins/Skript` containing the website's files. -4. Open `index.html` and browse the documentation. -5. _(Optionally)_ Add this system property `-Dskript.forceregisterhooks` in your server startup script (before the -jar property) to force generating hooks docs. \ No newline at end of file diff --git a/docs/assets/Logo/Skript Logo Circular.png b/docs/assets/Logo/Skript Logo Circular.png deleted file mode 100644 index 8c93b07d381c4529ff4ceeec49474e64748b25d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19415 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%0Ap6ArCC$+~Ly+g0r(y}^*LL^R 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 diff --git a/docs/assets/Logo/Skript Logo.jpg b/docs/assets/Logo/Skript Logo.jpg deleted file mode 100644 index 2a1221730d2c990dd0305d09d116319af80edbe1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30413 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 diff --git a/docs/assets/Logo/Skript Logo.png b/docs/assets/Logo/Skript Logo.png deleted file mode 100644 index d3965b280fea418bdc79e2d4997492d83fb036c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10738 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 T2=B`OD|4^49X{u~Gm-xXo%vj~ diff --git a/docs/assets/icon.png b/docs/assets/icon.png deleted file mode 100644 index 8c93b07d381c4529ff4ceeec49474e64748b25d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19415 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%0Ap6ArCC$+~Ly+g0r(y}^*LL^R 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 diff --git a/docs/assets/light-off.svg b/docs/assets/light-off.svg deleted file mode 100644 index 1b7db07723c..00000000000 --- a/docs/assets/light-off.svg +++ /dev/null @@ -1,70 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> -<svg version="1.1" id="Layer_2_00000120519223585702451540000006968771831968122805_" - xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 148.2 149.7" - style="enable-background:new 0 0 148.2 149.7;" xml:space="preserve"> -<style type="text/css"> - .st0{fill:none;stroke:#FFFFFF;stroke-width:2;} -</style> -<g> - <g> - <path d="M89.7,126.8l-30.9,0c-1.2,0-2.2,1.1-2.2,2.6c0,1.4,1,2.6,2.2,2.6l30.9-0.1c1.2,0,2.2-1.1,2.2-2.5 - C91.9,128,90.9,126.8,89.7,126.8L89.7,126.8z"/> - <path d="M89.7,134L58.7,134c-1.2,0-2.2,1.2-2.2,2.6c0,1.4,1,2.6,2.2,2.6H62l3.3,6.8c0,0,1.6,2.2,2.4,2.4c0.8,0.2,10.3,1.3,13.3,0 - c0,0,1.4-0.4,2.6-2.6c0.9-1.7,2.7-5.1,3.5-6.6h2.5c1.2,0,2.2-1.2,2.2-2.6C91.9,135.1,90.9,134,89.7,134z"/> - <path d="M89.7,119.7l-31,0c-1.2,0-2.2,1.2-2.2,2.6c0,1.4,1,2.6,2.2,2.5l30.9,0c1.2,0,2.2-1.2,2.2-2.6 - C91.9,120.8,90.9,119.7,89.7,119.7L89.7,119.7z"/> - <g> - <path d="M84.7,74.7c0.3-0.6,0.4-1.1,0.2-1.4c-0.2-0.3-0.4-0.5-0.6-0.5c-0.5,0-1.1,0.6-1.4,1.2c-0.4,0.9-0.4,2-0.2,2.9 - C83.7,76.3,84.4,75.5,84.7,74.7L84.7,74.7z"/> - <path d="M71.4,73.5c0.2-0.5,0.3-1.3,0.1-1.7c0-0.1-0.1-0.1-0.2-0.1c-0.1,0-0.2,0-0.5,0.1c-0.6,0.2-1.1,0.8-1.4,1.4 - c-0.6,1.2-0.6,2.4-0.2,3.4c0.1-0.1,0.2-0.2,0.2-0.3C70.2,75.4,71,74.5,71.4,73.5L71.4,73.5z"/> - <path d="M61.6,87.3c4.2,9,7.8,19.1,11.1,30.7h4.7c3-10.9,6.4-20.5,10.2-29.1c-3.1-1.4-5.5-4.1-6.8-7.6c-2.1,0.9-4.3,1.2-6.6,1 - c-1.9-0.2-3.6-0.7-4.9-1.4C68.1,82.4,65.1,85.6,61.6,87.3L61.6,87.3z"/> - <path d="M54.6,81.6c-0.1,0-0.1,0-0.2,0c-0.4,0-0.8,0.2-0.9,0.5c-0.2,0.7,0.2,1.3,0.5,1.7c0.8,1,2.2,1.7,3.3,1.7 - c0.1,0,0.2,0,0.3,0c-0.2-0.5-0.5-1-0.8-1.5C56.2,82.9,55.5,81.8,54.6,81.6L54.6,81.6z"/> - <path d="M94.5,82.5L94.5,82.5c-0.1,0-0.4,0.1-1.2,1.3l-0.1,0.1c-0.5,0.9-0.9,1.8-1.4,2.8c1-0.1,1.9-0.5,2.4-1 - c0.5-0.5,0.7-1.1,0.6-1.9C94.8,82.9,94.6,82.6,94.5,82.5z"/> - <path d="M74,27.9c-23,0-41.7,18.7-41.6,41.8c0,16.8,10,31.2,24.3,37.8v8.1c0,1.4,1.1,2.5,2.5,2.5h10.4 - c-3.2-11.3-6.8-21.1-10.8-29.8c-2.3,0.5-4.4-0.1-6.2-1.7c-1.2-1.1-2.3-2.9-2.2-4.7c0.1-1.1,0.7-2,1.6-2.6 - c0.7-0.4,1.4-0.7,2.2-0.7c2.3,0,4.1,2.1,5,3.9c0.3,0.7,0.7,1.3,1,2c2.4-1.1,4.8-3.1,6.8-5.6c-0.6-0.8-0.9-1.7-1.1-2.8 - c-0.2-1.8,0.6-4.4,2-6c1.5-1.7,3.3-2,5-0.8c0.9,0.7,1.5,1.5,1.6,2.5c0.3,2.3-1.7,4.7-3.1,6.4c-0.1,0.1-0.1,0.2-0.2,0.2 - c0.4,0.2,0.8,0.3,1.3,0.4c2.5,0.5,4.8,0.5,6.4-0.2c0.3-0.1,0.6-0.2,0.9-0.4c-0.1-0.6-0.2-1.2-0.3-1.8c-0.2-2.6,0.5-4.5,2.2-5.9 - c0.7-0.6,1.6-0.9,2.5-0.9c0.9,0,1.6,0.3,2.2,0.9c1,1,1.4,2.2,1.3,3.4c-0.2,2.5-2.5,4.4-3.5,5.1c-0.3,0.2-0.6,0.4-0.9,0.6 - c0.8,2.1,2.3,5.2,5.3,6.4c0.6-1.3,1.2-2.6,1.9-3.8c0-0.1,0.1-0.2,0.2-0.2c0.5-0.8,2-2.7,3.7-2.7c0.6,0,1.2,0.2,1.8,0.7 - c1.7,1.5,1.8,4,1.1,5.8c-0.8,2.3-3.1,3.6-6,3.7c-0.3,0-0.6,0-0.9,0c-3.8,8.3-7.1,17.6-10.1,28.3h8.7c1.4,0,2.5-1.1,2.5-2.5v-8.1 - c14.3-6.6,24.2-21.1,24.2-37.9C115.8,46.4,97.1,27.8,74,27.9L74,27.9z"/> - </g> - </g> - <g> - <path class="st0" d="M89.7,126.8l-30.9,0c-1.2,0-2.2,1.1-2.2,2.6c0,1.4,1,2.6,2.2,2.6l30.9-0.1c1.2,0,2.2-1.1,2.2-2.5 - C91.9,128,90.9,126.8,89.7,126.8L89.7,126.8z"/> - <path class="st0" d="M89.7,134L58.7,134c-1.2,0-2.2,1.2-2.2,2.6c0,1.4,1,2.6,2.2,2.6H62l3.3,6.8c0,0,1.6,2.2,2.4,2.4 - c0.8,0.2,10.3,1.3,13.3,0c0,0,1.4-0.4,2.6-2.6c0.9-1.7,2.7-5.1,3.5-6.6h2.5c1.2,0,2.2-1.2,2.2-2.6C91.9,135.1,90.9,134,89.7,134z" - /> - <path class="st0" d="M89.7,119.7l-31,0c-1.2,0-2.2,1.2-2.2,2.6c0,1.4,1,2.6,2.2,2.5l30.9,0c1.2,0,2.2-1.2,2.2-2.6 - C91.9,120.8,90.9,119.7,89.7,119.7L89.7,119.7z"/> - <g> - <path class="st0" d="M84.7,74.7c0.3-0.6,0.4-1.1,0.2-1.4c-0.2-0.3-0.4-0.5-0.6-0.5c-0.5,0-1.1,0.6-1.4,1.2 - c-0.4,0.9-0.4,2-0.2,2.9C83.7,76.3,84.4,75.5,84.7,74.7L84.7,74.7z"/> - <path class="st0" d="M71.4,73.5c0.2-0.5,0.3-1.3,0.1-1.7c0-0.1-0.1-0.1-0.2-0.1c-0.1,0-0.2,0-0.5,0.1c-0.6,0.2-1.1,0.8-1.4,1.4 - c-0.6,1.2-0.6,2.4-0.2,3.4c0.1-0.1,0.2-0.2,0.2-0.3C70.2,75.4,71,74.5,71.4,73.5L71.4,73.5z"/> - <path class="st0" d="M61.6,87.3c4.2,9,7.8,19.1,11.1,30.7h4.7c3-10.9,6.4-20.5,10.2-29.1c-3.1-1.4-5.5-4.1-6.8-7.6 - c-2.1,0.9-4.3,1.2-6.6,1c-1.9-0.2-3.6-0.7-4.9-1.4C68.1,82.4,65.1,85.6,61.6,87.3L61.6,87.3z"/> - <path class="st0" d="M54.6,81.6c-0.1,0-0.1,0-0.2,0c-0.4,0-0.8,0.2-0.9,0.5c-0.2,0.7,0.2,1.3,0.5,1.7c0.8,1,2.2,1.7,3.3,1.7 - c0.1,0,0.2,0,0.3,0c-0.2-0.5-0.5-1-0.8-1.5C56.2,82.9,55.5,81.8,54.6,81.6L54.6,81.6z"/> - <path class="st0" d="M94.5,82.5L94.5,82.5c-0.1,0-0.4,0.1-1.2,1.3l-0.1,0.1c-0.5,0.9-0.9,1.8-1.4,2.8c1-0.1,1.9-0.5,2.4-1 - c0.5-0.5,0.7-1.1,0.6-1.9C94.8,82.9,94.6,82.6,94.5,82.5z"/> - <path class="st0" d="M74,27.9c-23,0-41.7,18.7-41.6,41.8c0,16.8,10,31.2,24.3,37.8v8.1c0,1.4,1.1,2.5,2.5,2.5h10.4 - c-3.2-11.3-6.8-21.1-10.8-29.8c-2.3,0.5-4.4-0.1-6.2-1.7c-1.2-1.1-2.3-2.9-2.2-4.7c0.1-1.1,0.7-2,1.6-2.6 - c0.7-0.4,1.4-0.7,2.2-0.7c2.3,0,4.1,2.1,5,3.9c0.3,0.7,0.7,1.3,1,2c2.4-1.1,4.8-3.1,6.8-5.6c-0.6-0.8-0.9-1.7-1.1-2.8 - c-0.2-1.8,0.6-4.4,2-6c1.5-1.7,3.3-2,5-0.8c0.9,0.7,1.5,1.5,1.6,2.5c0.3,2.3-1.7,4.7-3.1,6.4c-0.1,0.1-0.1,0.2-0.2,0.2 - c0.4,0.2,0.8,0.3,1.3,0.4c2.5,0.5,4.8,0.5,6.4-0.2c0.3-0.1,0.6-0.2,0.9-0.4c-0.1-0.6-0.2-1.2-0.3-1.8c-0.2-2.6,0.5-4.5,2.2-5.9 - c0.7-0.6,1.6-0.9,2.5-0.9c0.9,0,1.6,0.3,2.2,0.9c1,1,1.4,2.2,1.3,3.4c-0.2,2.5-2.5,4.4-3.5,5.1c-0.3,0.2-0.6,0.4-0.9,0.6 - c0.8,2.1,2.3,5.2,5.3,6.4c0.6-1.3,1.2-2.6,1.9-3.8c0-0.1,0.1-0.2,0.2-0.2c0.5-0.8,2-2.7,3.7-2.7c0.6,0,1.2,0.2,1.8,0.7 - c1.7,1.5,1.8,4,1.1,5.8c-0.8,2.3-3.1,3.6-6,3.7c-0.3,0-0.6,0-0.9,0c-3.8,8.3-7.1,17.6-10.1,28.3h8.7c1.4,0,2.5-1.1,2.5-2.5v-8.1 - c14.3-6.6,24.2-21.1,24.2-37.9C115.8,46.4,97.1,27.8,74,27.9L74,27.9z"/> - </g> - </g> -</g> -</svg> diff --git a/docs/assets/light-on.svg b/docs/assets/light-on.svg deleted file mode 100644 index 601ccea1f8e..00000000000 --- a/docs/assets/light-on.svg +++ /dev/null @@ -1,114 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> -<svg version="1.1" id="Layer_2_00000120519223585702451540000006968771831968122805_" - xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 148.2 149.7" - style="enable-background:new 0 0 148.2 149.7;" xml:space="preserve"> -<style type="text/css"> - .st0{fill:#F7F8F8;} - .st1{fill:#ED9E00;} - .st2{fill:#F8BF39;} - .st3{fill:none;stroke:#1D1D1B;stroke-width:2;} -</style> -<g> - <g> - <path class="st0" d="M89.7,126.8l-30.9,0c-1.2,0-2.2,1.1-2.2,2.6c0,1.4,1,2.6,2.2,2.6l30.9-0.1c1.2,0,2.2-1.1,2.2-2.5 - C91.9,128,90.9,126.8,89.7,126.8L89.7,126.8z"/> - <path class="st0" d="M89.7,134L58.7,134c-1.2,0-2.2,1.2-2.2,2.6c0,1.4,1,2.6,2.2,2.6H62l3.3,6.8c0,0,1.6,2.2,2.4,2.4 - c0.8,0.2,10.3,1.3,13.3,0c0,0,1.4-0.4,2.6-2.6c0.9-1.7,2.7-5.1,3.5-6.6h2.5c1.2,0,2.2-1.2,2.2-2.6C91.9,135.1,90.9,134,89.7,134z" - /> - <path class="st0" d="M89.7,119.7l-31,0c-1.2,0-2.2,1.2-2.2,2.6c0,1.4,1,2.6,2.2,2.5l30.9,0c1.2,0,2.2-1.2,2.2-2.6 - C91.9,120.8,90.9,119.7,89.7,119.7L89.7,119.7z"/> - <g> - <path class="st1" d="M131.4,95.2c-2.6-1.3-5.2-2.6-7.8-3.9c-1.5-0.8-3.6-0.5-4.5,1.2c0,0,0,0,0,0c-0.8,1.5-0.5,3.6,1.2,4.5 - c2.6,1.3,5.2,2.6,7.8,3.9c1.5,0.8,3.7,0.4,4.5-1.2c0.3-0.6,0.5-1.2,0.4-1.9C133,96.7,132.5,95.7,131.4,95.2L131.4,95.2z"/> - <path class="st1" d="M29.1,92.5c-0.8-1.7-3-1.9-4.5-1.2c-2.6,1.3-5.2,2.6-7.8,3.9c-1.1,0.5-1.5,1.6-1.6,2.6 - c0,0.7,0.1,1.3,0.4,1.9c0.8,1.6,3,2,4.5,1.2c2.6-1.3,5.2-2.6,7.8-3.9C29.6,96.2,29.9,94.1,29.1,92.5 - C29.1,92.6,29.1,92.5,29.1,92.5z"/> - <path class="st1" d="M133.9,53.9c-2.9,0.6-5.7,1.2-8.6,1.8c-0.8,0.2-1.4,0.7-1.8,1.3c-0.2,0.3-0.3,0.6-0.4,1 - c-0.1,0.6-0.2,1.2,0,1.7c0,0,0,0.1,0,0.1c0.3,1.6,1.5,2.3,2.8,2.4c0.4,0.1,0.8,0,1.2-0.1c2.8-0.6,5.7-1.3,8.5-1.9 - C139.8,59.3,138,53.1,133.9,53.9L133.9,53.9z"/> - <path class="st1" d="M25.1,59.4c0.3-1.5-0.4-3.1-2.3-3.5c-2.8-0.6-5.7-1.3-8.5-1.9c-1.7-0.4-3.6,0.5-4,2.3c0,0,0,0.1,0,0.1 - c-0.4,1.7,0.5,3.6,2.3,4c2.8,0.6,5.7,1.2,8.6,1.8C23.3,62.7,24.8,61.1,25.1,59.4z"/> - <path class="st1" d="M115.3,19.7C115.3,19.7,115.3,19.7,115.3,19.7c-1.3-1.1-3.5-1.5-4.7,0c-1.8,2.3-3.6,4.6-5.4,6.8 - c-1.1,1.4-1.3,3.3,0,4.6c0.1,0.1,0.2,0.1,0.2,0.2c1.2,1,3.3,1.1,4.4-0.2c1.8-2.3,3.7-4.5,5.5-6.8 - C116.4,23.1,116.8,20.9,115.3,19.7L115.3,19.7z"/> - <path class="st1" d="M42.9,26.6c-1.8-2.3-3.6-4.6-5.4-6.8c-1.2-1.5-3.4-1-4.6,0c0,0,0,0-0.1,0.1c-1.5,1.2-1,3.4,0,4.6 - c1.8,2.3,3.7,4.5,5.5,6.8c0.6,0.8,1.6,1,2.5,0.9c0.8-0.1,1.6-0.4,2.1-1c0.5-0.6,0.8-1.2,0.9-1.8C44,28.4,43.6,27.4,42.9,26.6 - L42.9,26.6z"/> - <path class="st1" d="M74.1,5.1C74.1,5.1,74.1,5.1,74.1,5.1c-1.8,0-3.3,1.5-3.3,3.2c0,2.9,0,5.8,0,8.7c0,1.8,1.5,3.3,3.3,3.3 - c0,0,0,0,0.1,0c0.9,0,1.7-0.3,2.2-0.8c0.7-0.5,1.1-1.3,1.1-2.4c0-2.9,0-5.8,0-8.7C77.4,6.6,75.9,5.1,74.1,5.1L74.1,5.1z"/> - </g> - <g> - <path class="st2" d="M84.7,74.7c0.3-0.6,0.4-1.1,0.2-1.4c-0.2-0.3-0.4-0.5-0.6-0.5c-0.5,0-1.1,0.6-1.4,1.2 - c-0.4,0.9-0.4,2-0.2,2.9C83.7,76.3,84.4,75.5,84.7,74.7L84.7,74.7z"/> - <path class="st2" d="M71.4,73.5c0.2-0.5,0.3-1.3,0.1-1.7c0-0.1-0.1-0.1-0.2-0.1c-0.1,0-0.2,0-0.5,0.1c-0.6,0.2-1.1,0.8-1.4,1.4 - c-0.6,1.2-0.6,2.4-0.2,3.4c0.1-0.1,0.2-0.2,0.2-0.3C70.2,75.4,71,74.5,71.4,73.5L71.4,73.5z"/> - <path class="st2" d="M61.6,87.3c4.2,9,7.8,19.1,11.1,30.7h4.7c3-10.9,6.4-20.5,10.2-29.1c-3.1-1.4-5.5-4.1-6.8-7.6 - c-2.1,0.9-4.3,1.2-6.6,1c-1.9-0.2-3.6-0.7-4.9-1.4C68.1,82.4,65.1,85.6,61.6,87.3L61.6,87.3z"/> - <path class="st2" d="M54.6,81.6c-0.1,0-0.1,0-0.2,0c-0.4,0-0.8,0.2-0.9,0.5c-0.2,0.7,0.2,1.3,0.5,1.7c0.8,1,2.2,1.7,3.3,1.7 - c0.1,0,0.2,0,0.3,0c-0.2-0.5-0.5-1-0.8-1.5C56.2,82.9,55.5,81.8,54.6,81.6L54.6,81.6z"/> - <path class="st2" d="M94.5,82.5L94.5,82.5c-0.1,0-0.4,0.1-1.2,1.3l-0.1,0.1c-0.5,0.9-0.9,1.8-1.4,2.8c1-0.1,1.9-0.5,2.4-1 - c0.5-0.5,0.7-1.1,0.6-1.9C94.8,82.9,94.6,82.6,94.5,82.5z"/> - <path class="st2" d="M74,27.9c-23,0-41.7,18.7-41.6,41.8c0,16.8,10,31.2,24.3,37.8v8.1c0,1.4,1.1,2.5,2.5,2.5h10.4 - c-3.2-11.3-6.8-21.1-10.8-29.8c-2.3,0.5-4.4-0.1-6.2-1.7c-1.2-1.1-2.3-2.9-2.2-4.7c0.1-1.1,0.7-2,1.6-2.6 - c0.7-0.4,1.4-0.7,2.2-0.7c2.3,0,4.1,2.1,5,3.9c0.3,0.7,0.7,1.3,1,2c2.4-1.1,4.8-3.1,6.8-5.6c-0.6-0.8-0.9-1.7-1.1-2.8 - c-0.2-1.8,0.6-4.4,2-6c1.5-1.7,3.3-2,5-0.8c0.9,0.7,1.5,1.5,1.6,2.5c0.3,2.3-1.7,4.7-3.1,6.4c-0.1,0.1-0.1,0.2-0.2,0.2 - c0.4,0.2,0.8,0.3,1.3,0.4c2.5,0.5,4.8,0.5,6.4-0.2c0.3-0.1,0.6-0.2,0.9-0.4c-0.1-0.6-0.2-1.2-0.3-1.8c-0.2-2.6,0.5-4.5,2.2-5.9 - c0.7-0.6,1.6-0.9,2.5-0.9c0.9,0,1.6,0.3,2.2,0.9c1,1,1.4,2.2,1.3,3.4c-0.2,2.5-2.5,4.4-3.5,5.1c-0.3,0.2-0.6,0.4-0.9,0.6 - c0.8,2.1,2.3,5.2,5.3,6.4c0.6-1.3,1.2-2.6,1.9-3.8c0-0.1,0.1-0.2,0.2-0.2c0.5-0.8,2-2.7,3.7-2.7c0.6,0,1.2,0.2,1.8,0.7 - c1.7,1.5,1.8,4,1.1,5.8c-0.8,2.3-3.1,3.6-6,3.7c-0.3,0-0.6,0-0.9,0c-3.8,8.3-7.1,17.6-10.1,28.3h8.7c1.4,0,2.5-1.1,2.5-2.5v-8.1 - c14.3-6.6,24.2-21.1,24.2-37.9C115.8,46.4,97.1,27.8,74,27.9L74,27.9z"/> - </g> - </g> - <g> - <path class="st3" d="M89.7,126.8l-30.9,0c-1.2,0-2.2,1.1-2.2,2.6c0,1.4,1,2.6,2.2,2.6l30.9-0.1c1.2,0,2.2-1.1,2.2-2.5 - C91.9,128,90.9,126.8,89.7,126.8L89.7,126.8z"/> - <path class="st3" d="M89.7,134L58.7,134c-1.2,0-2.2,1.2-2.2,2.6c0,1.4,1,2.6,2.2,2.6H62l3.3,6.8c0,0,1.6,2.2,2.4,2.4 - c0.8,0.2,10.3,1.3,13.3,0c0,0,1.4-0.4,2.6-2.6c0.9-1.7,2.7-5.1,3.5-6.6h2.5c1.2,0,2.2-1.2,2.2-2.6C91.9,135.1,90.9,134,89.7,134z" - /> - <path class="st3" d="M89.7,119.7l-31,0c-1.2,0-2.2,1.2-2.2,2.6c0,1.4,1,2.6,2.2,2.5l30.9,0c1.2,0,2.2-1.2,2.2-2.6 - C91.9,120.8,90.9,119.7,89.7,119.7L89.7,119.7z"/> - <g> - <path class="st3" d="M131.4,95.2c-2.6-1.3-5.2-2.6-7.8-3.9c-1.5-0.8-3.6-0.5-4.5,1.2c0,0,0,0,0,0c-0.8,1.5-0.5,3.6,1.2,4.5 - c2.6,1.3,5.2,2.6,7.8,3.9c1.5,0.8,3.7,0.4,4.5-1.2c0.3-0.6,0.5-1.2,0.4-1.9C133,96.7,132.5,95.7,131.4,95.2L131.4,95.2z"/> - <path class="st3" d="M29.1,92.5c-0.8-1.7-3-1.9-4.5-1.2c-2.6,1.3-5.2,2.6-7.8,3.9c-1.1,0.5-1.5,1.6-1.6,2.6 - c0,0.7,0.1,1.3,0.4,1.9c0.8,1.6,3,2,4.5,1.2c2.6-1.3,5.2-2.6,7.8-3.9C29.6,96.2,29.9,94.1,29.1,92.5 - C29.1,92.6,29.1,92.5,29.1,92.5z"/> - <path class="st3" d="M133.9,53.9c-2.9,0.6-5.7,1.2-8.6,1.8c-0.8,0.2-1.4,0.7-1.8,1.3c-0.2,0.3-0.3,0.6-0.4,1 - c-0.1,0.6-0.2,1.2,0,1.7c0,0,0,0.1,0,0.1c0.3,1.6,1.5,2.3,2.8,2.4c0.4,0.1,0.8,0,1.2-0.1c2.8-0.6,5.7-1.3,8.5-1.9 - C139.8,59.3,138,53.1,133.9,53.9L133.9,53.9z"/> - <path class="st3" d="M25.1,59.4c0.3-1.5-0.4-3.1-2.3-3.5c-2.8-0.6-5.7-1.3-8.5-1.9c-1.7-0.4-3.6,0.5-4,2.3c0,0,0,0.1,0,0.1 - c-0.4,1.7,0.5,3.6,2.3,4c2.8,0.6,5.7,1.2,8.6,1.8C23.3,62.7,24.8,61.1,25.1,59.4z"/> - <path class="st3" d="M115.3,19.7C115.3,19.7,115.3,19.7,115.3,19.7c-1.3-1.1-3.5-1.5-4.7,0c-1.8,2.3-3.6,4.6-5.4,6.8 - c-1.1,1.4-1.3,3.3,0,4.6c0.1,0.1,0.2,0.1,0.2,0.2c1.2,1,3.3,1.1,4.4-0.2c1.8-2.3,3.7-4.5,5.5-6.8 - C116.4,23.1,116.8,20.9,115.3,19.7L115.3,19.7z"/> - <path class="st3" d="M42.9,26.6c-1.8-2.3-3.6-4.6-5.4-6.8c-1.2-1.5-3.4-1-4.6,0c0,0,0,0-0.1,0.1c-1.5,1.2-1,3.4,0,4.6 - c1.8,2.3,3.7,4.5,5.5,6.8c0.6,0.8,1.6,1,2.5,0.9c0.8-0.1,1.6-0.4,2.1-1c0.5-0.6,0.8-1.2,0.9-1.8C44,28.4,43.6,27.4,42.9,26.6 - L42.9,26.6z"/> - <path class="st3" d="M74.1,5.1C74.1,5.1,74.1,5.1,74.1,5.1c-1.8,0-3.3,1.5-3.3,3.2c0,2.9,0,5.8,0,8.7c0,1.8,1.5,3.3,3.3,3.3 - c0,0,0,0,0.1,0c0.9,0,1.7-0.3,2.2-0.8c0.7-0.5,1.1-1.3,1.1-2.4c0-2.9,0-5.8,0-8.7C77.4,6.6,75.9,5.1,74.1,5.1L74.1,5.1z"/> - </g> - <g> - <path class="st3" d="M84.7,74.7c0.3-0.6,0.4-1.1,0.2-1.4c-0.2-0.3-0.4-0.5-0.6-0.5c-0.5,0-1.1,0.6-1.4,1.2 - c-0.4,0.9-0.4,2-0.2,2.9C83.7,76.3,84.4,75.5,84.7,74.7L84.7,74.7z"/> - <path class="st3" d="M71.4,73.5c0.2-0.5,0.3-1.3,0.1-1.7c0-0.1-0.1-0.1-0.2-0.1c-0.1,0-0.2,0-0.5,0.1c-0.6,0.2-1.1,0.8-1.4,1.4 - c-0.6,1.2-0.6,2.4-0.2,3.4c0.1-0.1,0.2-0.2,0.2-0.3C70.2,75.4,71,74.5,71.4,73.5L71.4,73.5z"/> - <path class="st3" d="M61.6,87.3c4.2,9,7.8,19.1,11.1,30.7h4.7c3-10.9,6.4-20.5,10.2-29.1c-3.1-1.4-5.5-4.1-6.8-7.6 - c-2.1,0.9-4.3,1.2-6.6,1c-1.9-0.2-3.6-0.7-4.9-1.4C68.1,82.4,65.1,85.6,61.6,87.3L61.6,87.3z"/> - <path class="st3" d="M54.6,81.6c-0.1,0-0.1,0-0.2,0c-0.4,0-0.8,0.2-0.9,0.5c-0.2,0.7,0.2,1.3,0.5,1.7c0.8,1,2.2,1.7,3.3,1.7 - c0.1,0,0.2,0,0.3,0c-0.2-0.5-0.5-1-0.8-1.5C56.2,82.9,55.5,81.8,54.6,81.6L54.6,81.6z"/> - <path class="st3" d="M94.5,82.5L94.5,82.5c-0.1,0-0.4,0.1-1.2,1.3l-0.1,0.1c-0.5,0.9-0.9,1.8-1.4,2.8c1-0.1,1.9-0.5,2.4-1 - c0.5-0.5,0.7-1.1,0.6-1.9C94.8,82.9,94.6,82.6,94.5,82.5z"/> - <path class="st3" d="M74,27.9c-23,0-41.7,18.7-41.6,41.8c0,16.8,10,31.2,24.3,37.8v8.1c0,1.4,1.1,2.5,2.5,2.5h10.4 - c-3.2-11.3-6.8-21.1-10.8-29.8c-2.3,0.5-4.4-0.1-6.2-1.7c-1.2-1.1-2.3-2.9-2.2-4.7c0.1-1.1,0.7-2,1.6-2.6 - c0.7-0.4,1.4-0.7,2.2-0.7c2.3,0,4.1,2.1,5,3.9c0.3,0.7,0.7,1.3,1,2c2.4-1.1,4.8-3.1,6.8-5.6c-0.6-0.8-0.9-1.7-1.1-2.8 - c-0.2-1.8,0.6-4.4,2-6c1.5-1.7,3.3-2,5-0.8c0.9,0.7,1.5,1.5,1.6,2.5c0.3,2.3-1.7,4.7-3.1,6.4c-0.1,0.1-0.1,0.2-0.2,0.2 - c0.4,0.2,0.8,0.3,1.3,0.4c2.5,0.5,4.8,0.5,6.4-0.2c0.3-0.1,0.6-0.2,0.9-0.4c-0.1-0.6-0.2-1.2-0.3-1.8c-0.2-2.6,0.5-4.5,2.2-5.9 - c0.7-0.6,1.6-0.9,2.5-0.9c0.9,0,1.6,0.3,2.2,0.9c1,1,1.4,2.2,1.3,3.4c-0.2,2.5-2.5,4.4-3.5,5.1c-0.3,0.2-0.6,0.4-0.9,0.6 - c0.8,2.1,2.3,5.2,5.3,6.4c0.6-1.3,1.2-2.6,1.9-3.8c0-0.1,0.1-0.2,0.2-0.2c0.5-0.8,2-2.7,3.7-2.7c0.6,0,1.2,0.2,1.8,0.7 - c1.7,1.5,1.8,4,1.1,5.8c-0.8,2.3-3.1,3.6-6,3.7c-0.3,0-0.6,0-0.9,0c-3.8,8.3-7.1,17.6-10.1,28.3h8.7c1.4,0,2.5-1.1,2.5-2.5v-8.1 - c14.3-6.6,24.2-21.1,24.2-37.9C115.8,46.4,97.1,27.8,74,27.9L74,27.9z"/> - </g> - </g> -</g> -</svg> diff --git a/docs/assets/search.svg b/docs/assets/search.svg deleted file mode 100644 index 9895c46a2d2..00000000000 --- a/docs/assets/search.svg +++ /dev/null @@ -1,173 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> -<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" - viewBox="0 0 249.1 281.9" style="enable-background:new 0 0 249.1 281.9;" xml:space="preserve"> -<style type="text/css"> - .st0{fill:#434041;} - .st1{fill:#9FD3D7;} - .st2{opacity:0.5;fill:#FFFFFF;enable-background:new ;} - .st3{opacity:0.5;} - .st4{fill:#FFFFFF;} - .st5{opacity:0.8;} - .st6{fill:#737277;} - .st7{fill:#F19800;} - .st8{fill:#525154;} -</style> -<g id="Paquete_de_vectores:_motivo_de_puntos_15"> -</g> -<g id="Paquete_de_vectores:_motivo_de_puntos_15_1_"> -</g> -<g id="Paquete_de_vectores:_motivo_de_puntos_03"> -</g> -<rect x="147" y="154.5" transform="matrix(0.7762 -0.6305 0.6305 0.7762 -73.2279 136.4001)" width="17" height="33.6"/> -<path class="st0" d="M20.7,151.7c32.4,39.9,91.1,45.9,130.9,13.6c39.9-32.4,45.9-91.1,13.6-130.9C132.9-5.5,74.1-11.5,34.3,20.8 - S-11.6,111.9,20.7,151.7z M30.4,143.9C2.3,109.4,7.6,58.5,42.1,30.5C76.6,2.4,127.5,7.7,155.5,42.2c28.1,34.5,22.8,85.4-11.7,113.4 - C109.3,183.7,58.4,178.4,30.4,143.9z"/> -<g> - <path class="st1" d="M30.4,143.8c28,34.5,78.9,39.7,113.4,11.7s39.7-78.9,11.7-113.4S76.6,2.4,42.1,30.4S2.3,109.3,30.4,143.8z"/> - <g> - <path class="st2" d="M49,37.1c24.5-19.9,52.5-26.1,62.4-13.9c10,12.2-1.9,38.3-26.4,58.2s-52.5,26.1-62.4,13.9 - C12.6,83.1,24.5,57,49,37.1z"/> - <g class="st3"> - <g> - <g> - <path class="st4" d="M68.6,152.2c7.4,3.7,15.6,5.9,23.8,6.6c16.4,1.4,33.2-3.8,45.9-14.2c12.8-10.2,21.3-25.6,23.3-42 - c1-8.2,0.5-16.6-1.6-24.7c-2-8.1-5.8-15.8-10.7-22.7c5.8,6.3,10.2,13.9,13,22.1s3.8,17,3.1,25.7c-0.7,8.7-3.2,17.3-7.3,25.1 - c-4.2,7.8-9.9,14.7-16.7,20.3c-6.8,5.5-14.7,9.7-23.2,12.1c-8.4,2.4-17.3,3.1-26,2s-17.1-3.9-24.6-8.3 - c-7.4-4.4-13.9-10.3-18.9-17.2C54.4,143.2,61.2,148.5,68.6,152.2z"/> - </g> - </g> - <g> - <g> - <path class="st4" d="M78.7,144.5c5.9,2.9,12.3,4.6,18.8,5.1c12.9,1,26-3.1,35.9-11.2c10-8.1,16.6-20,18.3-32.9 - c0.9-6.4,0.5-13-1.1-19.4c-1.5-6.4-4.4-12.5-8.2-18c4.7,4.9,8.2,10.9,10.5,17.4c2.2,6.5,3.2,13.5,2.6,20.4 - c-0.5,6.9-2.6,13.8-5.8,20c-3.2,6.1-7.8,11.7-13.2,16.1c-5.5,4.4-11.8,7.8-18.6,9.7c-6.8,1.8-13.9,2.4-20.8,1.5 - s-13.6-3.2-19.5-6.7c-5.8-3.5-11-8.3-14.8-13.8C67.4,137.6,72.8,141.7,78.7,144.5z"/> - </g> - </g> - </g> - <g class="st5"> - <path class="st4" d="M101.1,30.7c6.5-5.2,16-4.3,21.2,2.2s4.3,16-2.2,21.2c-6.5,5.2-16,4.3-21.2-2.2 - C93.6,45.5,94.6,36,101.1,30.7z"/> - <path class="st4" d="M134.5,42.2c2.9-2.3,7.1-1.9,9.4,1s1.9,7.1-1,9.4s-7.1,1.9-9.4-1C131.2,48.7,131.6,44.6,134.5,42.2z"/> - </g> - </g> -</g> -<g> - <path class="st0" d="M153.3,188.4c-3-4.1-3.6-8.6-1.2-10.2c3.9-2.6,7.5-5.6,10.9-8.9c2-2,6.3-0.5,9.7,3.3 - c24.5,27.6,49,55.3,73.6,83c3.4,3.9,3.9,9.5,1,12.3c-4.9,4.7-10.3,9.1-15.9,13c-3.4,2.4-8.8,0.7-11.8-3.4 - C197.4,247.8,175.3,218.1,153.3,188.4z"/> - <path class="st6" d="M165.9,179.3c-0.9-1.1-0.8-2.7,0.3-3.6l0,0c1.1-0.9,2.7-0.8,3.6,0.3l72.5,82.8c0.9,1.1,0.8,2.7-0.3,3.6l0,0 - c-1.1,0.9-2.7,0.8-3.6-0.3L165.9,179.3z"/> -</g> -<rect x="-904.2" y="-25.8" class="st7" width="425.2" height="425.2"/> -<g> - <rect x="-733.1" y="184.5" transform="matrix(0.6305 -0.7762 0.7762 0.6305 -414.7179 -476.2191)" width="51.1" height="25.9"/> - <path class="st0" d="M-577.4,37.3C-617.2,5-675.9,11-708.3,50.9c-32.3,39.8-26.3,98.5,13.6,130.9c39.8,32.3,98.5,26.3,130.9-13.6 - C-531.5,128.4-537.6,69.7-577.4,37.3z M-686.9,172.2c-34.5-28-39.8-78.9-11.7-113.4c28-34.5,78.9-39.8,113.4-11.7 - c34.5,28,39.8,78.9,11.7,113.4C-601.5,194.9-652.4,200.2-686.9,172.2z"/> - <g> - <path class="st1" d="M-585.2,46.9c-34.5-28-85.4-22.8-113.4,11.7S-721.4,144-686.9,172s85.4,22.8,113.4-11.7 - C-545.4,125.9-550.7,75-585.2,46.9z"/> - <g> - <path class="st2" d="M-565.7,111.8c-9.9,12.2-37.9,6-62.4-13.9s-36.4-46-26.4-58.2c9.9-12.2,37.9-6,62.4,13.9 - C-567.6,73.5-555.7,99.6-565.7,111.8z"/> - <g class="st3"> - <g> - <g> - <path class="st4" d="M-591.8,153.5c-5,6.9-11.5,12.8-18.9,17.2c-7.5,4.4-15.9,7.2-24.6,8.3s-17.6,0.4-26-2 - c-8.5-2.4-16.4-6.6-23.2-12.1c-6.8-5.6-12.5-12.5-16.7-20.3c-4.1-7.8-6.6-16.4-7.3-25.1c-0.7-8.7,0.3-17.5,3.1-25.7 - s7.2-15.8,13-22.1c-4.9,6.9-8.7,14.6-10.7,22.7c-2.1,8.1-2.6,16.5-1.6,24.7c2,16.4,10.5,31.8,23.3,42 - c12.7,10.4,29.5,15.6,45.9,14.2c8.2-0.7,16.4-2.9,23.8-6.6C-604.3,165-597.5,159.8-591.8,153.5z"/> - </g> - </g> - <g> - <g> - <path class="st4" d="M-605.9,149.3c-3.8,5.5-9,10.3-14.8,13.8c-5.9,3.5-12.6,5.8-19.5,6.7c-6.9,0.9-14,0.3-20.8-1.5 - c-6.8-1.9-13.1-5.3-18.6-9.7c-5.4-4.4-10-10-13.2-16.1c-3.2-6.2-5.3-13.1-5.8-20c-0.6-6.9,0.4-13.9,2.6-20.4 - c2.3-6.5,5.8-12.5,10.5-17.4c-3.8,5.5-6.7,11.6-8.2,18c-1.6,6.4-2,13-1.1,19.4c1.7,12.9,8.3,24.8,18.3,32.9 - c9.9,8.1,23,12.2,35.9,11.2c6.5-0.5,12.9-2.2,18.8-5.1C-615.9,158.2-610.5,154.2-605.9,149.3z"/> - </g> - </g> - </g> - <g class="st5"> - <path class="st4" d="M-642,68.5c-5.2,6.5-14.7,7.4-21.2,2.2c-6.5-5.2-7.4-14.7-2.2-21.2s14.7-7.4,21.2-2.2 - C-637.7,52.5-636.7,62-642,68.5z"/> - <path class="st4" d="M-676.6,68.1c-2.3,2.9-6.5,3.3-9.4,1s-3.3-6.5-1-9.4s6.5-3.3,9.4-1C-674.7,61.1-674.3,65.3-676.6,68.1z"/> - </g> - </g> - </g> - <g> - <path class="st0" d="M-804.8,358.8c-4.6,6.3-12.8,8.8-18,5.2c-8.6-5.9-16.7-12.5-24.2-19.7c-4.5-4.3-3.7-12.8,1.5-18.7 - c37.3-42.1,74.6-84.2,111.9-126.2c5.2-5.8,11.7-8,14.8-5c5.2,5,10.7,9.5,16.6,13.5c3.5,2.4,2.7,9.2-1.9,15.5 - C-737.7,268.6-771.2,313.7-804.8,358.8z"/> - <path class="st6" d="M-833.5,335.5c-1.4,1.6-3.9,1.8-5.5,0.4l0,0c-1.6-1.4-1.8-3.9-0.4-5.5l110.1-125.8c1.4-1.6,3.9-1.8,5.5-0.4 - l0,0c1.6,1.4,1.8,3.9,0.4,5.5L-833.5,335.5z"/> - </g> -</g> -<g> - <g> - <g> - <path class="st8" d="M-700.3,292.9h6.2v2.2c0,2.1,0.3,3.5,0.8,4.2s1.4,1.1,2.7,1.1s2.3-0.3,3-1s1-1.7,1-3c0-1-0.2-1.9-0.7-2.6 - s-1.5-1.6-3.1-2.8l-3.2-2.3c-2.8-1.9-4.5-3.5-5.3-4.7c-0.7-1.2-1.1-2.8-1.1-4.7c0-2.8,0.8-5,2.5-6.6s4-2.4,6.9-2.4 - c3.1,0,5.5,0.8,7.1,2.5c1.7,1.7,2.5,4.1,2.5,7.3c0,0.3,0,0.6,0,0.7c0,0.2,0,0.3,0,0.5h-5.9v-0.9c0-1.8-0.3-3.1-0.9-3.9 - c-0.6-0.8-1.6-1.2-2.9-1.2c-1,0-1.8,0.3-2.4,0.9c-0.6,0.6-0.9,1.4-0.9,2.4c0,1.4,1.3,3,3.8,4.7h0.1l3.4,2.3 - c2.4,1.6,4.1,3.2,5,4.6s1.3,3.3,1.3,5.5c0,3-0.9,5.4-2.6,7c-1.7,1.7-4.2,2.5-7.5,2.5c-3.4,0-5.9-0.9-7.5-2.6s-2.5-4.4-2.5-8 - c0-0.4,0-1,0.1-1.9v0.2H-700.3z"/> - <path class="st8" d="M-675.2,304.6v-33.5h17.2v5.6h-11v7.7h10.2v5.3H-669v9.2h11.3v5.7L-675.2,304.6L-675.2,304.6z"/> - <path class="st8" d="M-655.6,304.6l7.5-33.5h8.4l7.6,33.5h-6.7l-1.7-8.4h-7.2l-1.6,8.4H-655.6z M-646.7,291.2h5.3l-2.7-14 - L-646.7,291.2z"/> - <path class="st8" d="M-628.8,304.6v-33.5h11.2c3.4,0,5.8,0.7,7.3,2.1s2.2,3.6,2.2,6.7c0,2.2-0.4,3.9-1.2,5.1 - c-0.8,1.3-2,2-3.5,2.2c3,0.8,4.5,3.3,4.6,7.5c0,0.2,0,0.3,0,0.4l0.2,4.4c0.1,1.3,0.2,2.3,0.5,3c0.3,0.8,0.7,1.4,1.2,2h-7.4 - c-0.2-0.6-0.3-1.2-0.4-2c-0.1-0.8-0.1-1.8-0.2-3.2l-0.1-3.5v-0.9c0-1.9-0.4-3.3-1.2-4s-2.5-1.1-5-1.1h-2.1v14.7L-628.8,304.6 - L-628.8,304.6z M-622.8,285.3h3.6c1.8,0,3.1-0.4,3.8-1.1c0.7-0.7,1.1-2,1.1-3.9c0-1.7-0.4-2.8-1.2-3.5s-2.2-1-4.2-1h-3.1 - L-622.8,285.3L-622.8,285.3z"/> - <path class="st8" d="M-589.8,292.7h6.1c0,0.1,0,0.4,0,0.6c0,0.3,0,0.5,0,0.7c0,3.9-0.8,6.7-2.4,8.6c-1.6,1.9-4.1,2.8-7.5,2.8 - c-1.6,0-3-0.2-4.1-0.6c-1.1-0.4-2.1-1.1-3-1.9c-1.1-1.2-1.9-2.8-2.4-4.8s-0.7-5.6-0.7-10.6c0-3.4,0.1-6.2,0.4-8.2 - s0.7-3.6,1.3-4.6c0.9-1.5,2-2.6,3.3-3.3c1.3-0.7,3-1,5.1-1c3.4,0,5.9,0.9,7.4,2.7c1.5,1.8,2.3,4.6,2.3,8.5h-6v-0.9 - c0-1.8-0.3-3.2-0.9-4.1c-0.6-0.9-1.5-1.3-2.7-1.3c-1.5,0-2.5,0.6-3.1,1.8c-0.5,1.2-0.8,4.6-0.8,10.2c0,6,0.3,9.7,0.8,11 - s1.5,2,3,2c1.4,0,2.4-0.5,2.9-1.5c0.6-1,0.8-2.9,0.8-5.7v-0.4H-589.8z"/> - <path class="st8" d="M-578.7,304.6v-33.5h6.2v13.1h8.5v-13.1h6.2v33.5h-6.2v-15.2h-8.5v15.2H-578.7z"/> - </g> - </g> - <g> - <g> - <path class="st8" d="M-694.6,320.4l-2.4-10.8h2.2l1.6,8.7l1.5-8.7h2.1l-2.3,10.8H-694.6z"/> - <path class="st8" d="M-684.8,320.4v-10.8h5.6v1.8h-3.6v2.5h3.3v1.7h-3.3v3h3.6v1.8H-684.8z"/> - <path class="st8" d="M-669.8,316.6h2c0,0,0,0.1,0,0.2s0,0.2,0,0.2c0,1.3-0.3,2.2-0.8,2.8c-0.5,0.6-1.3,0.9-2.4,0.9 - c-0.5,0-1-0.1-1.3-0.2c-0.4-0.1-0.7-0.3-1-0.6c-0.4-0.4-0.6-0.9-0.8-1.5c-0.2-0.7-0.2-1.8-0.2-3.4c0-1.1,0-2,0.1-2.7 - s0.2-1.2,0.4-1.5c0.3-0.5,0.7-0.9,1.1-1.1c0.4-0.2,1-0.3,1.7-0.3c1.1,0,1.9,0.3,2.4,0.9s0.8,1.5,0.8,2.8h-2v-0.4 - c0-0.6-0.1-1-0.3-1.3s-0.5-0.4-0.9-0.4c-0.5,0-0.8,0.2-1,0.6s-0.3,1.5-0.3,3.3c0,2,0.1,3.1,0.3,3.6c0.2,0.4,0.5,0.6,1,0.6 - s0.8-0.2,0.9-0.5c0.2-0.3,0.3-0.9,0.3-1.8V316.6z"/> - <path class="st8" d="M-661.1,320.4v-9.1h-2.3v-1.8h6.7v1.8h-2.3v9.1H-661.1z"/> - <path class="st8" d="M-652.3,315c0-1.1,0.1-2.1,0.2-2.8c0.1-0.7,0.3-1.2,0.5-1.6c0.3-0.5,0.7-0.8,1.1-1.1c0.4-0.2,1-0.3,1.7-0.3 - s1.3,0.1,1.7,0.3c0.4,0.2,0.8,0.6,1.1,1.1c0.2,0.4,0.4,0.9,0.5,1.6s0.2,1.6,0.2,2.7s-0.1,2.1-0.2,2.7s-0.3,1.2-0.5,1.6 - c-0.3,0.5-0.7,0.8-1.1,1.1c-0.4,0.2-1,0.3-1.7,0.3s-1.3-0.1-1.7-0.3c-0.4-0.2-0.8-0.6-1.1-1.1c-0.2-0.3-0.4-0.9-0.5-1.6 - C-652.2,317.1-652.3,316.2-652.3,315z M-650.2,315c0,1.8,0.1,2.9,0.3,3.4s0.6,0.7,1.2,0.7c0.6,0,0.9-0.2,1.1-0.7 - c0.2-0.5,0.3-1.6,0.3-3.4s-0.1-2.9-0.3-3.4s-0.6-0.7-1.1-0.7c-0.6,0-0.9,0.2-1.1,0.7C-650.1,312.1-650.2,313.2-650.2,315z"/> - <path class="st8" d="M-640,320.4v-10.8h3.6c1.1,0,1.9,0.2,2.4,0.7c0.5,0.4,0.7,1.2,0.7,2.2c0,0.7-0.1,1.3-0.4,1.7 - c-0.3,0.4-0.6,0.6-1.1,0.7c1,0.3,1.5,1.1,1.5,2.4c0,0.1,0,0.1,0,0.1l0.1,1.4c0,0.4,0.1,0.7,0.2,1c0.1,0.2,0.2,0.5,0.4,0.6h-2.4 - c-0.1-0.2-0.1-0.4-0.1-0.6s0-0.6-0.1-1v-1.1v-0.3c0-0.6-0.1-1.1-0.4-1.3c-0.3-0.2-0.8-0.4-1.6-0.4h-0.7v4.8h-2.1V320.4z - M-638,314.2h1.2c0.6,0,1-0.1,1.2-0.4c0.2-0.2,0.3-0.7,0.3-1.3c0-0.5-0.1-0.9-0.4-1.1c-0.2-0.2-0.7-0.3-1.4-0.3h-1v3.1H-638z"/> - <path class="st8" d="M-620.8,320.4v-10.8h3.2c0.7,0,1.2,0.1,1.6,0.2c0.4,0.1,0.7,0.3,1,0.5c0.4,0.4,0.6,0.8,0.8,1.4 - c0.2,0.6,0.2,1.4,0.2,2.6c0,1.5-0.1,2.6-0.2,3.3c-0.1,0.6-0.3,1.2-0.5,1.6s-0.6,0.8-1,0.9c-0.4,0.2-1.2,0.3-2.3,0.3H-620.8z - M-618.9,318.8h0.9c0.5,0,0.8,0,1-0.1s0.3-0.2,0.4-0.3c0.2-0.3,0.3-0.7,0.4-1.2c0.1-0.5,0.1-1.3,0.1-2.4s0-1.8-0.1-2.3 - c-0.1-0.5-0.3-0.8-0.5-1.1c-0.1-0.1-0.3-0.2-0.5-0.3c-0.2,0-0.5-0.1-0.9-0.1h-0.9L-618.9,318.8L-618.9,318.8z"/> - <path class="st8" d="M-608.8,320.4v-10.8h5.6v1.8h-3.6v2.5h3.3v1.7h-3.3v3h3.6v1.8H-608.8z"/> - <path class="st8" d="M-598.2,316.6h2v0.7c0,0.7,0.1,1.1,0.2,1.4c0.2,0.2,0.5,0.4,0.9,0.4c0.4,0,0.8-0.1,1-0.3 - c0.2-0.2,0.3-0.5,0.3-1c0-0.3-0.1-0.6-0.2-0.8c-0.2-0.2-0.5-0.5-1-0.9l-1-0.7c-0.9-0.6-1.5-1.1-1.7-1.5s-0.4-0.9-0.4-1.5 - c0-0.9,0.3-1.6,0.8-2.1s1.3-0.8,2.2-0.8c1,0,1.8,0.3,2.3,0.8s0.8,1.3,0.8,2.4c0,0.1,0,0.2,0,0.2c0,0.1,0,0.1,0,0.1h-1.9v-0.3 - c0-0.6-0.1-1-0.3-1.3s-0.5-0.4-1-0.4c-0.3,0-0.6,0.1-0.8,0.3c-0.2,0.2-0.3,0.5-0.3,0.8c0,0.5,0.4,1,1.2,1.5l0,0l1.1,0.8 - c0.8,0.5,1.3,1,1.6,1.5s0.4,1.1,0.4,1.8c0,1-0.3,1.7-0.8,2.3c-0.6,0.5-1.4,0.8-2.4,0.8c-1.1,0-1.9-0.3-2.4-0.8 - c-0.5-0.6-0.8-1.4-0.8-2.6C-598.3,317.1-598.2,316.9-598.2,316.6L-598.2,316.6z"/> - <path class="st8" d="M-586.5,320.4v-10.8h2v10.8H-586.5z"/> - <path class="st8" d="M-573.7,320.4l-0.1-1.2c-0.2,0.5-0.5,0.9-0.8,1.1c-0.4,0.2-0.9,0.3-1.5,0.3c-1.1,0-1.9-0.4-2.4-1.3 - s-0.7-2.4-0.7-4.7c0-1.1,0.1-1.9,0.2-2.6c0.1-0.6,0.3-1.1,0.5-1.5c0.3-0.4,0.7-0.8,1.1-1c0.5-0.2,1-0.3,1.7-0.3 - c1.2,0,2,0.3,2.6,0.8c0.6,0.5,0.8,1.4,0.8,2.6v0.3h-1.9v-0.1c0-0.6-0.1-1.1-0.4-1.5c-0.2-0.3-0.6-0.5-1.1-0.5 - c-0.6,0-1,0.2-1.2,0.7s-0.3,1.6-0.3,3.6c0,1.5,0.1,2.6,0.3,3.1c0.2,0.5,0.6,0.8,1.1,0.8s0.9-0.2,1.1-0.5c0.2-0.4,0.3-1,0.3-1.9 - v-0.4h-1.4v-1.7h3.3v5.9L-573.7,320.4L-573.7,320.4z"/> - <path class="st8" d="M-567,320.4v-10.8h2.1l2.8,7.6l-0.1-7.6h1.9v10.8h-2.1l-2.8-7.6l0.1,7.6H-567z"/> - </g> - </g> -</g> -</svg> diff --git a/docs/classes.html b/docs/classes.html deleted file mode 100644 index 8685ad71de0..00000000000 --- a/docs/classes.html +++ /dev/null @@ -1,5 +0,0 @@ -<h1 id="nav-title">Types</h1> - -<div id="content"> - ${generate classes desc_full.html} -</div> \ No newline at end of file diff --git a/docs/conditions.html b/docs/conditions.html deleted file mode 100644 index 091bdf2d1c4..00000000000 --- a/docs/conditions.html +++ /dev/null @@ -1,5 +0,0 @@ -<h1 id="nav-title">Conditions</h1> - -<div id="content"> - ${generate conditions desc_full.html} -</div> diff --git a/docs/css/styles.css b/docs/css/styles.css deleted file mode 100644 index 22ae63678e0..00000000000 --- a/docs/css/styles.css +++ /dev/null @@ -1,1197 +0,0 @@ -:root { - --font-color: #f7f7f7; - --bg-color: #1e1e1e; - - --search-color: white; - --search-bg-color: rgb(15, 15, 15); - --search-ver-bg-color: rgb(51, 51, 51); - - --table-row-color: rgba(38, 38, 38, 0.3); - --table-row2-color: rgba(0, 0, 0, 0.3); - --table-side-color: rgba(114, 114, 114, 0.1); - --table-side2-color: rgba(116, 116, 116, 0.05); - --table-desc-color: rgba(0, 0, 0, 0.45); - - --code-font-color: white; - --code-bg-color: rgba(99, 99, 99, 0.2); - - --sidebar-items-bg: rgba(0, 0, 0, .2); - - --element-bottom-border: rgba(255, 255, 255, .2); - - --link-color: #ffad32; - --link-hover-color: #dd8500; - --link-visited-color: #ff9800; - - --table-shadow: 0 0 20px rgba(0, 0, 0, .4); - - --box-font-color: black; - --box-inner-font-color: white; - --box-bg: rgba(0, 0, 0, 0.4) !important; - --red-box-font-color: #1e1e1e; - --red-box-bg: rgba(0, 0, 0, 0.4); - - --syntax-item-shadow: 0px 0px 15px rgba(0, 0, 0, .5); - - --wrapper-bg-color: transparent; - - --scrollbar-track-bg-color: rgba(255, 255, 255, 0.2); - --scrollbar-thumb-bg-color: rgba(255, 255, 255, 0.4); - - --inline-code-bg-color: #3e3e3e9e; -} - -[data-theme="white"] { - --font-color: black; - --font-secondary-color: rgba(0, 0, 0, .65); - --bg-color: #f5f5f5; - --table-color: white; - - --search-color: black; - --search-bg-color: white; - --search-ver-bg-color: rgba(253, 253, 253, 0.7); - - --table-row-color: rgba(0, 0, 0, 0.02); - --table-row2-color: rgba(0, 0, 0, 0.05); - --table-side-color: rgb(224 224 224 / 60%); - --table-side2-color: rgb(202 202 202 / 45%); - --table-desc-color: rgb(211 211 211 / 40%); - - --code-font-color: white; - --code-bg-color: rgb(41, 41, 41); - - --sidebar-items-bg: rgba(0, 0, 0, 0.07); - - --element-bottom-border: rgba(0, 0, 0, 0.1); - - --link-color: #bb7000; - --link-hover-color: #dd8500; - --link-visited-color: #ff9800; - - --table-shadow: 0 0 20px rgba(0, 0, 0, 0.15); - - --box-font-color: black; - --box-inner-font-color: white; - --box-bg: rgb(41, 41, 41) !important; - --red-box-font-color: white; - --red-box-bg: rgba(0, 0, 0, 0.1); - - --syntax-item-shadow: 0px 0px 15px rgba(0, 0, 0, .2); - - --wrapper-bg-color: white; - - --scrollbar-track-bg-color: rgba(0, 0, 0, 0.03); - --scrollbar-thumb-bg-color: rgba(48, 48, 51, 0.4); - - --inline-code-bg-color: #a9a9a99e; - - /* transition: all 1s; */ -} - -::selection { - background-color: #ff9800; - color: black; -} - -html { - scroll-behavior: smooth; -} - -::-webkit-scrollbar { - width: 6px; - height: 6px; -} - -::-webkit-scrollbar-track { - background-color: var(--scrollbar-track-bg-color); - border-radius: 10px; -} - -::-webkit-scrollbar-thumb { - background-color: var(--scrollbar-thumb-bg-color); - border-radius: 10px; -} - -* { - margin: 0px; - padding: 0px; -} - -body { - font-family: "Poppins", sans-serif; - background-color: var(--bg-color); - display: grid; - /* - grid-template-rows: 100vh 100vh; - grid-template-columns: minmax(15em, 18em) minmax(80%, 100%); - */ - grid-template-columns: 16vw 84vw; - grid-template-rows: 55px 94vh; - overflow: hidden; -} - -.main-page { - grid-template-columns: 16vw 84vw; -} - -a { - color: var(--link-color); - transition: all 0.1s; - text-decoration: none; -} - -a:hover { - color: var(--link-hover-color); -} - -a:visited { - color: var(--link-visited-color); -} - -.item-wrapper { - box-shadow: var(--syntax-item-shadow); - border-left: 5px solid rgb(255, 152, 0); - margin: 35px 0; - background-color: var(--wrapper-bg-color) -} - -.type-Event { - border-left: 5px solid rgb(28, 181, 152); -} - -.type-Event .item-type { - color: rgb(28, 181, 152); -} - -.type-Event .item-examples p { - background-color: rgb(28, 181, 152); -} - -.type-Effect { - border-left: 5px solid rgb(139, 255, 0); -} - -.type-Effect .item-type { - color: rgb(139, 255, 0); -} - -.type-Effect .item-examples p { - background-color: rgb(139, 255, 0); -} - -.type-Expression { - border-left: 5px solid rgb(255, 152, 0); -} - -.type-Expression .item-type { - color: rgb(255, 152, 0); -} - -.type-Expression .item-examples p { - background-color: rgb(255, 152, 0); -} - -.type-Condition { - border-left: 5px solid rgb(181, 28, 28); -} - -.type-Condition .item-type { - color: rgb(181, 28, 28); -} - -.type-Condition .item-examples p { - background-color: rgb(181, 28, 28); - color: white; -} - -.type-Section { - border-left: 5px solid rgb(181, 28, 121); -} - -.type-Section .item-type { - color: rgb(181, 28, 121); -} - -.type-Section .item-examples p { - background-color: rgb(181, 28, 121); - color: white; -} - -.type-EffectSection { - border-left: 5px solid rgb(199, 0, 255); -} - -.type-EffectSection .item-type { - color: rgb(199, 0, 255); -} - -.type-EffectSection .item-examples p { - background-color: rgb(199, 0, 255); - color: white; -} - -.type-Type { - border-left: 5px solid rgb(0, 100, 255); -} - -.type-Type .item-type { - color: rgb(0, 100, 255); -} - -.type-Type .item-examples p { - background-color: rgb(0, 100, 255); - color: white; -} - -.type-Function { - border-left: 5px solid rgb(128, 0, 255); -} - -.type-Function .item-type { - color: rgb(128, 0, 255); -} - -.type-Function .item-examples p { - background-color: rgb(128, 0, 255); - color: white; -} - -/* Pattern right section list items */ - -.item-details:nth-child(1) ul li:nth-child(odd) { - background-color: var(--table-row-color); - color: var(--font-color); - border-left: 2px solid rgb(255, 152, 0); -} - -.item-details:nth-child(1) ul li:nth-child(even) { - background-color: var(--table-row2-color); - color: var(--font-color); - border-left: 1px solid rgb(255, 152, 0); -} - -.no-list-style { - list-style: none; -} - -#global-navigation { - grid-row-start: 1; - grid-row-end: 1; - grid-column-start: 2; - position: sticky; - top: 0em; - background-color: #292929; - padding: 15px; - z-index: 100; - bottom: 0px; - display: flex; -} - -#global-navigation li { - display: inline; -} - -#global-navigation a { - text-decoration: none; - color: #ffffff; - padding: 10px; - padding-bottom: 16px; - transition: all 0.1s; -} - -/* Active tab for menu items */ -#global-navigation a:hover, -.active-tab { - color: #ffc107; - /*border-bottom: 3px solid #ff9800;*/ - box-shadow: inset 0 -3px 0 #ff9800; -} - -/* Active syntax */ -div.active-syntax { - border-left: 5px solid #ff4e4e; -} - -div.active-syntax .item-examples p { - background-color: #ff4e4e; - color: white; -} - -.item-examples p { - cursor: pointer; -} - -.example-details-closed:before { - content: '▶ '; -} - -.example-details-opened:before { - content: '▼ '; -} - -/* Active tab for sub menus */ -.menu-subtabs .active-tab { - color: #ffc107 !important; - /* border-left: 3px solid #ff9800; */ - background: #3a3a3a; - box-shadow: unset; -} - -#side-nav { - grid-row-start: 1; - grid-row-end: 1; - grid-column-start: 1; - grid-column-end: 1; - position: sticky; - top: 60px; - height: max-content; -} - -#nav-title { - grid-row-start: 1; - grid-row-end: 1; - grid-column-start: 1; - grid-column-end: 1; - position: fixed; - border-left: solid 3px #ff9800; - text-decoration: none; - background-color: #353535; - font-size: 1.5vw; - font-weight: bold; - color: #ff9800; - height: 35px; - padding: 10px; - padding-left: 25px; - z-index: 50; - transition: color 0.2s; - width: 100%; -} - -/* #nav-title { - font-size: clamp(1.25em, 1.35vw, 2em); -} */ - -#nav-title:hover { - color: rgba(255, 152, 0, 0.9); -} - -#nav-contents { - /* height: calc(100vh - 60px); */ - height: 93vh; - /* Fix the TOP css of #side-nav */ - width: 100%; - overflow: scroll; - overflow-x: hidden; - position: sticky; -} - -#nav-contents>a { - display: block; - font-size: 18px; - background-color: var(--sidebar-items-bg); - text-decoration: none; - color: var(--font-color); - margin: 6px 10px; - word-wrap: break-word; - padding: 5px 15px; - transition: background-color 0.1s, font 0.1s; - word-break: break-word; -} - -#side-nav a:focus { - font-weight: bold; -} - -#nav-contents a:hover, -.active-item { - color: var(--font-color); - background-color: rgba(0, 0, 0, 0.08); - border-left: solid 3px #ff9800; - /* font-weight: bold; */ -} - -#content { - grid-row-start: 1; - grid-row-end: 1; - grid-column-start: 2; - padding-left: 30px; - position: relative; - /* top: 110px; */ - margin-top: 110px; - height: calc(100vh - 110px); - /* Fixed the TOP CSS 110px due to body having overflow hidden and top = 110px so the last 110px is hidden, this will fix it */ - /* padding: calc(0.22em + 30px); */ - /* When applied it will break the padding when an anchor is clicked */ - padding-right: 30px; - overflow-x: hidden; - overflow-y: scroll; -} - -#content.no-left-panel { - grid-column-start: 1; - grid-column-end: none; - padding: 0 8vw; - margin-left: unset; -} - -#content-no-docs { - grid-row-start: 1; - grid-row-end: 1; - grid-column-start: 2; - margin-left: 30px; - position: relative; - top: 55px; - height: calc(100vh - 55px); - /* Fixed the TOP CSS 55px due to body having overflow hidden and top = 55px so the last 55px is hidden, this will fix it */ - padding: 0.22em; - padding-right: 30px; - overflow-x: hidden; - overflow-y: scroll; -} - -#content-no-docs.no-left-panel { - grid-column-start: 1; - grid-column-end: none; - padding: 0 8vw; - margin-left: unset; -} - -#side-nav.no-left-panel { - display: none; -} - -div p { - padding: 10px; -} - -table { - width: 100%; - border-collapse: collapse; -} - -.item-title { - font-size: 14px; - font-weight: bold; - padding-bottom: 10px; - padding-top: 0.5em; - padding-left: 10px; - color: var(--font-color); -} - -.item-title>a { - text-decoration: none; - font-size: 100%; -} - -.item-type { - font-size: 16px; - padding: 10px; - color: var(--font-color); - padding: 10px; - padding-right: 20px; - float: right; -} - -#search-icon { - position: fixed; - right: 1vw; - top: 60px; - cursor: pointer; -} - -.new-element { - background: red; - color: white; - font-size: 12px; - padding: 3px; - border-radius: 5px; - margin-left: 10px; - cursor: pointer; - display: inline; - position: relative; - top: -2px; -} - -.item-content { - padding-bottom: 25px; - border-bottom: 1px solid var(--element-bottom-border); - content-visibility: auto; -} - -.item-table-label { - background-color: #deb887; - text-align: center; - font-weight: bold; - color: var(--font-color); - width: 8em; - padding: 0.3em; -} - -.item-content .item-details:nth-child(odd) td:nth-child(2) { - background-color: var(--table-row-color); - color: var(--font-color); -} - -.item-content .item-details:nth-child(even) td:nth-child(2) { - background-color: var(--table-row2-color); - color: var(--font-color); -} - - -/* OVERRIDE */ - -.item-content .item-details:nth-child(1) td:nth-child(1) { - /* Patterns */ - background-color: var(--table-side-color); -} - -/* .item-content .item-details:nth-child(1) td:nth-child(2) { - background-color: var(--table-side2-color); -} */ - - -/* OVERRIDE */ - -.item-content .item-details:nth-child(odd) .item-table-label { - background-color: var(--table-side-color); -} - -.item-content .item-details:nth-child(even) .item-table-label { - background-color: var(--table-side2-color); -} - -.item-details { - border-collapse: collapse; - width: 80vw; - max-width: 80vw; -} - -.item-content tr:nth-child(1n + 2) td:nth-child(2) { - /* 1n+2 will choose all elements excpet the first */ - padding: 8px; - font-weight: 500; - color: var(--font-secondary-color); -} - -.noleftpad { - padding-left: 0em !important; -} - -td ul { - padding: 0em; - list-style-type: none; -} - -.item-description { - padding: 15px; - background-color: var(--table-desc-color); - color: var(--font-color); -} - -.item-description>p { - margin-top: 0.7em; -} - -.skript-code-block { - padding: 6px; - font-family: "Source Code Pro", monospace; -} - -.item-examples { - /* margin: 25px 10px 0 10px; */ - margin-top: 25px; -} - -.item-examples p { - background-color: rgb(255, 152, 0); - /* border-left: 3px solid rgba(255, 152, 0, 0.5); */ - padding: 5px; - color: var(--box-font-color); - width: max-content; - margin-top: 25px; -} - -.item-examples .skript-code-block { - background-color: var(--code-bg-color); - visibility: visible; - font-family: "Source Code Pro", monospace; - font-weight: 400; -} - -.skript-code-block>a { - text-decoration: none; -} - -.item-examples>.skript-code-block { - /* border-left: solid 3px #ff9800; */ - padding: 30px; - font-size: 0.9em; - color: var(--code-font-color); - display: none; -} - -.box-title { - background-color: #ff9800; - width: max-content; - padding: 5px; - padding-right: 7px; - margin-top: 20px; - color: var(--box-font-color); -} - -.box { - border-left: 3px solid #ff9800; - padding: 15px; - background-color: var(--box-bg); - margin-bottom: 10px; - color: var(--box-inner-font-color); -} - -.box-title-red { - background-color: #ff4e4e; - width: max-content; - padding: 5px; - padding-right: 7px; - margin-top: 20px; - color: var(--red-box-font-color); - font-weight: bold; -} - -.box-red { - border-left: 3px solid #ff4e4e; - padding: 15px; - background-color: var(--red-box-bg); - color: var(--font-color); - margin-bottom: 10px; -} - - - -@media (max-width: 1200px) { - body { - grid-template-columns: 20% minmax(80%, 100%); - } - - #global-navigation li { - white-space: nowrap; - } - - #global-navigation { - display: flex; - } - - #global-navigation { - grid-column-start: 1; - grid-column-end: none; - } - - #global-navigation>a { - padding: 10px; - padding-top: 0.1em; - padding-bottom: 0.1em; - } - - #nav-title { - display: none; - } - - span .search-bar-version { - /* More specific to override the other */ - left: calc(20% + 150px) !important; - } - - input#search-bar { - left: 20%; - } - - #search-version { - left: 20% !important; - } -} - - -@media (max-width: 1024px) { - body { - grid-template-columns: 20% minmax(80%, 100%); - } - - #global-navigation>a { - padding: 10px; - padding-top: 0.1em; - padding-bottom: 0.1em; - } -} - -@media (max-width: 768px) { - body { - grid-template-columns: 20% minmax(80%, 100%); - } - - #global-navigation { - display: flex; - flex-wrap: wrap; - } - - #nav-contents>a { - font-size: 12px; - } - - .item-description { - font-size: 14px; - } - - .item-table-label { - width: 5em; - } - - #cookies-bar { - flex-direction: column; - } -} - -@media (max-width: 550px) { - body { - grid-template-columns: 0 100%; - } - - #global-navigation { - /* +2px due to 18px padding botton not 16px */ - height: calc(4.1em + 2px); - } - - #nav-contents { - display: none; - } - - #content { - padding-top: 0px !important; - top: 50px; - margin-left: 10px; - height: calc(100vh - 160px); - /* !important to override home */ - } - - img#theme-switch { - width: 45px; - } - - #global-navigation>li { - padding-bottom: 18px; - } - - input#search-bar { - top: calc(4.1em + 24px) !important; - left: 0; - width: 100% !important; - } - - a#search-icon { - top: calc(4.1em + 38px); - right: 3vw; - } - - #search-bar-after { - top: calc(4.1em + 94px) !important; - } - - span .search-bar-version { - /* More specific to override the other */ - left: 150px !important; - } - - #search-version { - top: calc(4.1em + 24px) !important; - left: 0% !important; - } - - #global-navigation>a { - padding: 10px; - padding-top: 0.1em; - padding-bottom: 0.1em; - } - - div.item-content { - max-width: 100vw; - } - - div.grid-container { - grid-template-columns: initial; - } - - .box.code { - height: 300px; - font-size: 14px; - } - - p.item-type { - display: none; - } -} - -.title { - margin-top: 32px; - color: var(--font-color); -} - -.subtitle { - padding-left: 20px !important; - color: var(--font-color); -} - -p, h1, h2, h3, h4, h5 { - color: var(--font-color); -} - -.left-margin { - margin-left: 20px !important; -} - -.colors-table { - width: 75%; - margin: 32px auto; - border-collapse: collapse; - font-size: 0.9em; - font-family: "Poppins", sans-serif; - box-shadow: var(--table-shadow); - color: var(--font-color); - /*border-radius: 5px;*/ -} - -.colors-table th, -.colors-table td { - padding: 12px 15px; -} - -.colors-table tbody tr { - border-bottom: 1px solid rgba(0, 0, 0, .1); -} - -.colors-table tbody tr:nth-of-type(even) { - background-color: rgba(0, 0, 0, 0.02); -} - -.colors-table tbody tr:last-of-type { - border-bottom: 2px solid #ff9800; -} - -ol.custom-list { - margin-left: 16px; - list-style: none; - counter-reset: my-awesome-counter; -} - -ol.custom-list li { - counter-increment: my-awesome-counter; -} - -ol.custom-list li::before { - content: counter(my-awesome-counter) ". "; - font-weight: bold; -} - -code { - font-family: "Source Code Pro", monospace; - background-color: var(--inline-code-bg-color); - padding: 0px 5px; - border-radius: 5px; -} - -pre { - font-family: "Source Code Pro", monospace; -} - -pre code { /* override styling for code blocks */ - font-family: "Source Code Pro", monospace; - background-color: transparent; - padding: 0; - border-radius: 0; -} - -#notification-box { - background-color: rgb(36, 36, 36); - border-radius: 15px; - box-shadow: 0px 3px 10px 0px rgba(0, 0, 0, .2); - text-align: center; - color: rgb(40, 236, 40); - font-weight: bold; - transition: all .2s; - /*transition-delay: .3s;*/ - opacity: 0; - position: fixed; - left: 50%; - top: 92%; - padding: 10px; - transform: translate(-50%, -50%); -} - -.activate-notification { - opacity: 1 !important; - transform: translateY(-20px); -} - -.grid-container { - width: 100%; - display: inline-grid; - grid-template-columns: calc(33% - 1.7%) calc(33% - 1.7%) calc(33% - 1.7%); - grid-column-gap: 3%; - overflow: hidden; -} - -.link { - display: inline-block; -} - -.padding { - padding: 32px 0; -} - -.bottom-padding { - padding-bottom: 32px; -} - -.bottom-padding { - padding-bottom: 32px; -} - -.top-padding { - padding-top: 32px; -} - -.top-padding-2 { - padding-top: 64px; -} - -#search-version { - padding: 12px; - border: none; - font-size: 18px; - font-weight: bold; - position: fixed; - background: var(--search-ver-bg-color); - width: 150px; - left: 16vw; - top: 55px; - box-shadow: 0 3px 5px 0 rgba(0, 0, 0, .05); - color: var(--search-color); - /* border-bottom: 2px solid rgba(255, 152, 0, 0.9); */ -} - -#search-bar { - padding: 12px; - border: none; - font-size: 18px; - position: fixed; - background: var(--search-bg-color); - width: calc(84vw); - left: 16vw; - top: 55px; - box-shadow: 0 3px 5px 0 rgba(0, 0, 0, .05); - color: var(--search-color); -} - -.search-bar-version { - left: calc(16vw + 150px) !important; -} - -#search-bar:focus { - outline: none; -} - -#search-bar-after { - padding: 3px; - font-size: 12px; - position: fixed; - background: var(--search-bg-color); - color: var(--search-color); - box-shadow: 0 3px 5px 0 rgba(0, 0, 0, .05); - right: 15px; - top: 100px; - text-align: right; -} - -.pre { - white-space: pre; -} - -.pre-wrap { - white-space: pre-wrap; -} - -#theme-switch { - position: fixed; - right: 40px; - bottom: 30px; - text-align: right; - display: inline-block; - width: 60px; -} - -#theme-switch:hover { - cursor: pointer; -} - -.link-icon { - margin-left: 5px; -} - - -.menu-tab { - color: white; - font-size: 16px; - border: none; -} - -.menu-subtabs { - display: none; - position: absolute; - top: 55px; - background-color: #292929; - min-width: 120px; - box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); - z-index: 9; - transition: all 3s ease-out; - opacity: 0; - transform: translateY(-10px); -} - -/* Links inside the dropdown */ -.menu-subtabs a { - color: white; - background: #292929; - padding: 12px 16px; - text-decoration: none; - display: block; -} - -/* Change color of dropdown links on hover */ -.menu-subtabs a:hover { - background-color: #3a3a3a; -} - -/* Show the dropdown menu on hover */ -.menu-tab:hover .menu-subtabs { - transition: all 3s ease-out; - display: block; - opacity: 1; - transform: translateY(0px); -} - -/* Keep the bottom border when hover over the sub-tabs */ -.menu-tab:hover .menu-tab-item { - box-shadow: inset 0 -3px 0 #ff9800; -} - -/* Hover over submenu items */ -#global-navigation .menu-subtabs a:hover { - box-shadow: unset; - /* border-left: 3px solid #ff9800; */ - background: rgba(0, 0, 0, .1); -} - -/* Syntax Highlighting */ -.sk-cond, -.sk-loop { - color: #a52e91; - font-weight: bold; -} - -.sk-eff { - color: rgb(255, 174, 0); -} - -.sk-expr { - color: rgb(255, 174, 25); -} - -.sk-event { - color: #c23838; -} - -.sk-command { - color: #c27715; -} - -.sk-arg-type { - color: #dbab28; -} - -.sk-loop-value { - color: #ff6600; -} - -.sk-false { - color: #b10000; - font-weight: bold; -} - -.sk-true { - color: #20b628; - font-weight: bold; -} - -.sk-operators { - font-weight: bold; -} - -.sk-var { - color: #e74040; -} - -.sk-string [class*="sk-"], -.sk-string { - /* More specific = higher priority to override other css in strings and comments */ - color: #34a52a !important; - font-weight: unset; -} - -.sk-comment, -.sk-comment [class*="sk-"] { - color: #696969 !important; - font-weight: unset; -} - -.sk-function { - color: #e873ff; - font-weight: bold; -} - -.sk-timespan { - color: #ff5967; - font-style: italic; -} - -.new-tab { - color: rgb(255, 91, 91) !important; - font-weight: 600; -} - -#cookies-bar { - position: sticky; - margin: 0 -8vw; - bottom: 0; - background-color: #353535; - color: white; - padding: 10px; - z-index: 100; - display: flex; - align-items: center; - justify-content: center; -} - -#cookies-accept { - padding: 5px 10px; - border: unset; - background-color: rgb(29, 167, 29); - margin: 0 5px; - cursor: pointer; - color: white; -} - -#cookies-deny { - padding: 5px 10px; - border: unset; - background-color: rgb(167, 29, 29); - margin: 0 5px; - cursor: pointer; - color: white; -} \ No newline at end of file diff --git a/docs/docs.html b/docs/docs.html deleted file mode 100644 index f54970f30af..00000000000 --- a/docs/docs.html +++ /dev/null @@ -1,5 +0,0 @@ -<h1 id="nav-title">All Elements</h1> - -<div id="content"> - ${generate docs desc_full.html} -</div> diff --git a/docs/docs.json b/docs/docs.json deleted file mode 100644 index a3fc012341e..00000000000 --- a/docs/docs.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "skriptVersion" : "${skript.version}", - - "classes" : [ - ${generate classes desc_full.json} - {"end" : true} - ], - - "conditions" : [ - ${generate conditions desc_full.json} - {"end" : true} - ], - - "effects" : [ - ${generate effects desc_full.json} - {"end" : true} - ], - - "events" : [ - ${generate events desc_full.json} - {"end" : true} - ], - - "expressions" : [ - ${generate expressions desc_full.json} - {"end" : true} - ], - - "functions" : [ - ${generate functions desc_full.json} - {"end" : true} - ] -} diff --git a/docs/effects.html b/docs/effects.html deleted file mode 100644 index 3f2811fd076..00000000000 --- a/docs/effects.html +++ /dev/null @@ -1,5 +0,0 @@ -<h1 id="nav-title">Effects</h1> - -<div id="content"> - ${generate effects desc_full.html} -</div> \ No newline at end of file diff --git a/docs/events.html b/docs/events.html deleted file mode 100644 index 119fd0e558b..00000000000 --- a/docs/events.html +++ /dev/null @@ -1,5 +0,0 @@ -<h1 id="nav-title">Events</h1> - -<div id="content"> - ${generate events desc_full.html} -</div> diff --git a/docs/expressions.html b/docs/expressions.html deleted file mode 100644 index 7eca87e5f64..00000000000 --- a/docs/expressions.html +++ /dev/null @@ -1,5 +0,0 @@ -<h1 id="nav-title">Expressions</h1> - -<div id="content"> - ${generate expressions desc_full.html} -</div> \ No newline at end of file diff --git a/docs/functions.html b/docs/functions.html deleted file mode 100644 index f018161fc7a..00000000000 --- a/docs/functions.html +++ /dev/null @@ -1,12 +0,0 @@ -<h1 id="nav-title">Functions</h1> - -<div id="content"> - - <p class="box-title-red">Note:</p> - <p class="box-red" style="margin-bottom: 25px;"> - These functions are defined by Skript. You may also create your own functions! - Tutorial for doing so is planned, but right now you need to seek it elsewhere. - </p> - - ${generate functions desc_full.html} -</div> diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 16d3bfc923a..00000000000 --- a/docs/index.html +++ /dev/null @@ -1,65 +0,0 @@ -<h1 id="nav-title">Documentation</h1> - -<div id="content-no-docs" class="no-left-panel" style="display: inline-block;"> -<div style="height: 32px;"></div> <!-- Space --> -<p> <span style="font-size: 60px; font-weight: bold;">Skript</span> is (surprise, surprise) a scripting plugin for the Bukkit platform. It is easy to use for - simple tasks, but you can also create really complex things with it. The syntax of Skript is close to - English, but it is still not magic. While you might succeed with experimentation for simple tasks, for - anything more complex you will need some guidance. </p> -<p> This is Skript's documentation. You will find all supported features of the plugin here, along with some - useful examples. We don't have tutorials yet, but you can find good ones using whatever search engine you - prefer. </p> - -<p class="box-title">Quick Look</p> -<pre class="box code" style="height: 460px; overflow: auto;"><code class="bash"> -command /sethome:<br/> - permission: skript.home # Permission required for this command<br/> - description: Set your home # Description of this command<br/> - executable by: players # Console won't be able to run this command<br/> - trigger: # The actual trigger/code that will run when someone do /sethome<br/> - # Set a unique variable to sender's location<br/> - set {home::%uuid of player%} to location of player<br/> - # Send a message to the sender<br/> - message "Set your home to <grey>%location of player%<reset>"<br/> -<br/> -command /home:<br/> - permission: skript.home<br/> - description: Teleport yourself to your home<br/> - trigger:<br/> - # Check if that variable we used in /sethome has been set (in other words, if player ever ran /sethome)<br/> - if {home::%uuid of player%} is not set:<br/> - message "You have not set your home yet!"<br/> - stop trigger # stop the code here, lines below won't run<br/> - # Teleport the player to their home<br/> - teleport player to {home::%uuid of player%}<br/> - send "&aYou have been teleported."<br/> -</code></pre> - -<div id="info" class="grid-container padding"> - <div class="grid-item"> - <p class="box-title">Latest Stable Version</p> - <p class="box placeholder"><a class="link" href="https://github.com/SkriptLang/Skript/releases/tag/${stable-version}" target="_blank">Skript ${stable-version}</a></p> - </div> - <div class="grid-item"> - <p class="box-title">Latest Version</p> - <p class="box placeholder"><a class="link" href="https://github.com/SkriptLang/Skript/releases/tag/${latest-version}" target="_blank">Skript ${latest-version}</a></p> - </div> - <div class="grid-item"> - <p class="box-title">Contributors</p> - <p class="box placeholder"><a class="link" href="https://github.com/SkriptLang/Skript/graphs/contributors" target="_blank">${contributors-size} Contributors</a></p> - </div> -</div> - -<p class="box"> Found something incorrect in this documentation? Please report it to the <a - href="https://github.com/SkriptLang/Skript/issues">issue tracker</a>. </p> -<p class="box"> <strong>We are looking for docs authors!</strong> Currently, the only documentation is generated - automatically. It would be nice to have some hand-written content such as tutorials on the docs as well. For - example, currently we don't have a tutorial on how to use loops here; This makes it harder for newcomers to - learn. Check <a href="https://github.com/SkriptLang/Skript/issues/611">this issue</a> for more details and - if you're interested in helping out. </p> - - -<div style="padding-top: 64px;"></div> <!-- Space --> -<p style="font-size: 14px; text-align: center;" class="placeholder"><a href="https://github.com/SkriptLang/Skript/">SkriptLang Team</a> • - Site developed by <a href="https://github.com/AyhamAl-Ali">Ayham Al-Ali</a> • Site Version <b>${site-version}</b> • Generated on <b>${skript.build.date}</b></p> -<div style="padding-top: 16px;"></div> <!-- Space --> \ No newline at end of file diff --git a/docs/js/functions.js b/docs/js/functions.js deleted file mode 100644 index d6f785ee7d9..00000000000 --- a/docs/js/functions.js +++ /dev/null @@ -1,353 +0,0 @@ -let isCookiesAccepted = getCookie("cookies-accepted") === "true"; - -// <> Cookies -function setCookie(cname, cvalue, exdays, force = false) { - if (!isCookiesAccepted && !force) - return; - - const d = new Date(); - d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000)); - let expires = "expires=" + d.toUTCString(); - document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/; SameSite=None; Secure"; -} - -function getCookie(cname) { - let name = cname + "="; - let ca = document.cookie.split(';'); - for (let i = 0; i < ca.length; i++) { - let c = ca[i]; - while (c.charAt(0) == ' ') { - c = c.substring(1); - } - if (c.indexOf(name) == 0) { - return c.substring(name.length, c.length); - } - } - return ""; -} - -// Cookies </> - -// <> localStorage - -/** - * Set the value of local storage item - * @param {string} item id - * @param {object} value - * @param {double} exdays time in days - */ -function setStorageItem(item, value, exdays, force = false) { - if (!isCookiesAccepted && !force) - return; - - const d = new Date(); - d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000)); - localStorage.setItem(item, value + "; " + d.getTime()); -} - -/** - * Remove a local storage item - * @param {string} item the id of the item - */ -function removeStorageItem(item) { - localStorage.removeItem(item) -} - -/** - * Get local storage item (value & time if has one) - * @param {string} item the item id - * @param {boolean} noExpireationCheck whether to check for expiration time and remove the item - * @returns the item object - */ -function getStorageItem(item, noExpireationCheck = false) { - let result = localStorage.getItem(item); - if (!result) - return null; - - if (!noExpireationCheck) { - if (isStorageItemExpired(result)) { - removeStorageItem(item); - return null; - } - } - result = result.split("; ")[0]; - return result; -} - -/** - * Get local storage item value after split at ';' - * @param {string} item the id of th item - * @returns the item value - */ -function getStorageItemValue(item) { - let result = localStorage.getItem(item); - if (!result) - return null; - return result.split("; ")[0]; -} - -/** - * @param {string} string the value of the item not the item id (the value without splitting) - * @returns the expiration date - */ -function getStorageItemExpiration(value) { - let expires = localStorage.getItem(value).split("; ")[1]; - if (!expires) { // item with no expiration date - return null; - } - return new Date(expires); -} - -/** - * - * @param string the value of the item not the item id (the value without splitting) - * @returns whether expired or not - */ -function isStorageItemExpired(value) { - let expires = parseInt(value.split("; ")[1]); - if (!expires) { // item with no expiration date - return null; - } - return new Date(expires) < new Date(); -} - -// localStorage </> - - - -function getApiValue(element, placeholder, apiPathName, callback, noReplace = false) { - let placeholderName = placeholder.replace("ghapi-", ""); - let cv = getStorageItem(placeholder); // cached value - if (noReplace) { - if (cv) { - callback(cv, true); - } else { - $.getJSON(ghAPI + `/${apiPathName}`, (data) => { - let value = callback(data, false); - setStorageItem(placeholder, value, 0.2); - }) - } - return; - } - - let innerHTML = element.innerHTML; - if (innerHTML.includes(`\${${placeholderName}}`)) { - if (cv) { - element.innerHTML = element.innerHTML.replaceAll(`\${${placeholderName}}`, cv); - } else { - $.getJSON(ghAPI + `/${apiPathName}`, (data) => { - let value = callback(data, false); - element.innerHTML = element.innerHTML.replaceAll(`\${${placeholderName}}`, value); - setStorageItem(placeholder, value, 0.2); - }) - } - } -} - -// Copy texts/links to clipboard -function copyToClipboard(value) { - setTimeout(() => { - let cb = document.body.appendChild(document.createElement("input")); - cb.value = value; - cb.focus(); - cb.select(); - document.execCommand('copy'); - cb.parentNode.removeChild(cb); - }, 50) -} - -// Show notification -function showNotification(text, bgColor, color) { - var noti = document.body.appendChild(document.createElement("span")); - noti.id = "notification-box"; - - setTimeout(() => { - noti.textContent = text; - if (bgColor) - noti.styles.backgroundColor = bgColor; - if (color) - noti.styles.backgroundColor = color; - noti.classList.add("activate-notification"); - setTimeout(() => { - noti.classList.remove("activate-notification"); - setTimeout(() => { - noti.parentNode.removeChild(noti); - }, 200); - }, 1500); - }, 50); -} - -// <> Magic Text (&k) -function getRandomChar() { - chars = "ÂÃÉÊÐÑÙÚÛÜéêëãòóôēĔąĆćŇň1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ~!@#$%^&*()-=_+{}["; - return chars.charAt(Math.floor(Math.random() * chars.length) + 1) -} - -function magicTextGen(element) { - var msg = element.textContent; - var length = msg.length; - - setInterval(() => { - var newMsg = ""; - for (i = 0; i <= length; i++) { - newMsg += getRandomChar(msg.charAt(i)); - } - element.textContent = newMsg; - - }, 30) -} - -function renderMagicText() { - document.querySelectorAll('.magic-text').forEach((e) => { - magicTextGen(e); - }) -} -// Magic Text (&k) </> - -// Replace Github placeholders -function replacePlaceholders(element) { - let innerHTML = element.innerHTML; - if (innerHTML.includes("${latest-version}")) { - getApiValue(element, "ghapi-latest-version", "releases", (data) => { - return data[0]["tag_name"]; - }); - } - - if (innerHTML.includes("${latest-version-changelog}")) { - getApiValue(element, "ghapi-latest-version-changelog", "releases", (data) => { - return data["body"].replaceAll("\\r\\n", "<br>"); - }); - } - - if (innerHTML.includes("${stable-version}")) { - getApiValue(element, "ghapi-stable-version", "releases/latest", (data) => { - return data["tag_name"]; - }); - } - - if (innerHTML.includes("${stable-version-changelog}")) { - getApiValue(element, "ghapi-stable-version-changelog", "releases/latest", (data) => { - return data["body"].replaceAll("\\r\\n", "<br>"); - }); - } - - if (innerHTML.includes("${latest-issue-")) { - getApiValue(element, "ghapi-latest-issue-user", "issues?per_page=1", (data) => { - return data[0]["user"]["login"]; - }); - getApiValue(element, "ghapi-latest-issue-title", "issues?per_page=1", (data) => { - return data[0]["title"]; - }); - getApiValue(element, "ghapi-latest-issue-date", "issues?per_page=1", (data) => { - return data[0]["created_at"]; - }); - } - - if (innerHTML.includes("${latest-pull-")) { - getApiValue(element, "ghapi-latest-pull-user", "pulls?per_page=1", (data) => { - return data[0]["user"]["login"]; - }); - getApiValue(element, "ghapi-latest-pull-title", "pulls?per_page=1", (data) => { - return data[0]["title"]; - }); - getApiValue(element, "ghapi-latest-pull-date", "pulls?per_page=1", (data) => { - return data[0]["created_at"]; - }); - } - - if (innerHTML.includes("${site-version}")) { - element.innerHTML = element.innerHTML.replaceAll("${site-version}", siteVersion); - } - - if (innerHTML.includes("${contributors-size}")) { - getApiValue(element, "ghapi-contributors-size", "contributors?per_page=500", (data) => { - return data.length; - }); - } -} - -// <> Syntax Highlighting - -// ORDER MATTERS!! -// All regexes must be sorrouneded with () to be able to use group 1 as the whole match since Js doesn't have group 0 -// Example: .+ = X -// Example: (.+) = ✓ -var patterns = []; // [REGEX, CLASS] - -function registerSyntax(regexString, flags, clazz) { - try { - regex = new RegExp(regexString, flags); - patterns.push([regex, clazz]); - } catch (error) { - console.warn(`Either your browser doesn't support this regex or the regex is incorrect (${regexString}):` + error); - } -} - -registerSyntax("((?<!#)#(?!#).*)", "gi", "sk-comment") // Must be first, : must be before :: -registerSyntax("(\\:|\\:\\:)", "gi", "sk-var") -registerSyntax("((?<!href=)\\\".+?\\\")", "gi", "sk-string") // before others to not edit non skript code -// registerSyntax("\\b(add|give|increase|set|make|remove( all| every|)|subtract|reduce|delete|clear|reset|send|broadcast|wait|halt|create|(dis)?enchant|shoot|rotate|reload|enable|(re)?start|teleport|feed|heal|hide|kick|(IP(-| )|un|)ban|break|launch|leash|force|message|close|show|reveal|cure|poison|spawn)(?=[ <])\\b", "gi", "sk-eff") // better to be off since it can't be much improved due to how current codes are made (can't detect \\s nor \\t) -registerSyntax("\\b(on (?=.+\\:))", "gi", "sk-event") -registerSyntax("\\b((parse )?if|else if|else|(do )?while|loop(?!-)|return|continue( loop|)|at)\\b", "gi", "sk-cond") -registerSyntax("\\b((|all )player(s|)|victim|attacker|sender|loop-player|shooter|uuid of |'s uuid|(location of |'s location)|console)\\b", "gi", "sk-expr") -registerSyntax("\\b((loop|event)-\\w+)\\b", "gi", "sk-loop-value") -registerSyntax("\\b(contains?|(has|have|is|was|were|are|does)(n't| not|)|can('t| ?not|))\\b", "gi", "sk-cond") -registerSyntax("\\b(command \\/.+(?=.*?:))", "gi", "sk-command") -registerSyntax("(<.+?>)", "gi", "sk-arg-type") -registerSyntax("\\b(true)\\b", "gi", "sk-true") -registerSyntax("\\b(stop( (the |)|)(trigger|server|loop|)|cancel( event)?|false)\\b", "gi", "sk-false") -registerSyntax("({|})", "gi", "sk-var") -registerSyntax("(\\w+?(?=\\(.*?\\)))", "gi", "sk-function") -registerSyntax("((\\d+?(\\.\\d+?)? |a |)(|minecraft |mc |real |rl |irl )(tick|second|minute|hour|day)s?)", "gi", "sk-timespan") -registerSyntax("\\b(now)\\b", "gi", "sk-timespan") - -function highlightElement(element) { - - let lines = element.innerHTML.split("<br>") - - for (let j = 0; j < lines.length; j++) { - Loop2: for (let i = 0; i < patterns.length; i++) { - let match; - let regex = patterns[i][0]; - let oldLine = lines[j]; - - while ((match = regex.exec(oldLine)) != null) { - lines[j] = lines[j].replaceAll(regex, `<span class='${patterns[i][1]}'>$1</span>`) - if (regex.lastIndex == 0) // Break after it reaches the end of exec count to avoid inf loop - continue Loop2; - } - } - } - element.innerHTML = lines.join("<br>") -} -// Syntax Highlighting </> - -// Offset page's scroll - Used for anchor scroll correction -function offsetAnchor(event, id) { // event can be null - let content = document.querySelector("#content"); - let element = document.getElementById(id); - - if (content && element) { - if (event != null) - event.preventDefault(); - content.scroll(0, element.offsetTop - 25); // Should be less than the margin in .item-wrapper so it doesn't show the part of the previous .item-wrapper - } -} - -// <> Toggle a specific doc element block - -// Active syntax -var lastActiveSyntaxID; -function toggleSyntax(elementID) { - let element = document.getElementById(elementID) - if (!element) - return - - if (lastActiveSyntaxID != null) - document.getElementById(lastActiveSyntaxID).classList.remove("active-syntax"); - - element.classList.add("active-syntax"); - lastActiveSyntaxID = elementID; -} - -// Toggle a specific doc element block </> \ No newline at end of file diff --git a/docs/js/main.js b/docs/js/main.js deleted file mode 100644 index 0b48675990d..00000000000 --- a/docs/js/main.js +++ /dev/null @@ -1,408 +0,0 @@ -const siteVersion = "2.2.0"; // site version is different from skript version -const ghAPI = "https://api.github.com/repos/SkriptLang/Skript"; - -// ID Scroll -const links = document.querySelectorAll("div.item-wrapper"); -const contents = document.querySelector("#content"); - -lastActiveSideElement = null; -navContents = document.getElementById("nav-contents"); - -if (contents) { - setTimeout(() => { - contents.addEventListener('scroll', (e) => { - links.forEach((ha) => { - const rect = ha.getBoundingClientRect(); - if (rect.top > 0 && rect.top < 350) { - // const location = window.location.toString().split("#")[0]; - // history.replaceState(null, null, location + "#" + ha.id); // Not needed since lastActiveSideElement + causes history spam - - if (lastActiveSideElement != null) { - lastActiveSideElement.classList.remove("active-item"); - } - - lastActiveSideElement = document.querySelectorAll(`#nav-contents a[href="#${ha.id}"]`)[0]; - if (lastActiveSideElement != null) { - lastActiveSideElement.classList.add("active-item"); - navContents.scroll(0, lastActiveSideElement.offsetTop - 100); - } - } - }); - })}, 50); // respect auto hash scroll -} - - -// Active Tab -const pageLink = window.location.toString().replaceAll(/(.*)\/(.+?).html(.*)/gi, '$2'); -if (pageLink === "" || pageLink == window.location.toString()) // home page - when there is no `.+?.html` pageLink will = windown.location due to current regex - document.querySelectorAll('#global-navigation a[href="index.html"]')[0].classList.add("active-tab"); -else - document.querySelectorAll(`#global-navigation a[href="${pageLink}.html"]`)[0].classList.add("active-tab"); - - -// No Left Panel -for (e in {"content-no-docs": 0, "content": 1}) { - let noLeftPanel = document.querySelectorAll(`#${e}.no-left-panel`)[0]; - if (noLeftPanel != null) - document.querySelectorAll('#side-nav')[0].classList.add('no-left-panel'); -} - -// Magic Text -renderMagicText(); - -// Anchor scroll correction -document.querySelectorAll(".link-icon").forEach((e) => { - e.addEventListener("click", (event) => { - let id = e.getAttribute("href").replace("#", ""); - if (id != "" && id != null) { - // offsetAnchor(event, id); - event.preventDefault(); - toggleSyntax(id); - } - }); -}) - -// Open description/pattern links in same tab rather than scrolling because hash links uses search bar -document.querySelectorAll(".item-wrapper a:not(.link-icon)").forEach((e) => { - e.addEventListener("click", (event) => { - event.preventDefault(); - window.open(e.href); - }); -}) - - -// Anchor click copy link -const currentPageLink = window.location.toString().replaceAll(/(.+?.html)(.*)/gi, '$1'); -document.querySelectorAll(".item-title > a").forEach((e) => { - e.addEventListener("click", (event) => { - copyToClipboard(window.location.toString().split(/[?#]/g)[0] + "?search=#" + e.parentElement.parentElement.id); - showNotification("✅ Link copied successfully.") - }); -}) - -// New element label click -document.querySelectorAll(".new-element").forEach((e) => { - e.addEventListener("click", (event) => { - searchNow("is:new"); - }); -}) - -// <> Search Bar -const versionComparePattern = /.*?(\d\.\d(?:\.\d|))(\+|-|).*/gi; -const versionPattern = / ?v(?:ersion|):(\d\.\d(?:\.\d|-(?:beta|alpha|dev)\d*|))(\+|-|)/gi; -const typePattern = / ?t(?:ype|):(condition|expression|type|effect|event|section|effectsection|function)/gi; -const newPattern = / ?is:(new)/gi; -const resultsFoundText = "result(s) found"; - -function versionCompare(base, target) { // Return -1, 0, 1 - base = base.replaceAll(versionComparePattern, "$1").replaceAll(/[^0-9]/gi, ""); - target = target.replaceAll(versionComparePattern, "$1").replaceAll(/[^0-9]/gi, ""); - - base = parseInt(base) < 100 ? parseInt(base) * 10 : parseInt(base); // convert ten's to hundred's to fix (2.5.1+ not triggering 2.6 by converting 26 -> 260) - target = parseInt(target) < 100 ? parseInt(target) * 10 : parseInt(target); - - if (target > base) - return 1 - if (target == base) - return 0 - if (target < base) - return -1 -} - -var searchBar; -var searchIcon; - -// Load search link -var linkParams = new URLSearchParams(window.location.href.replace("+", "%2B").split("?")[1]) // URLSearchParams decode '+' as space while encodeURI keeps + as is -if (linkParams && linkParams.get("search")) { - setTimeout(() => { - searchNow(linkParams.get("search")) // anchor link sometimes appear after the search param so filter it - }, 20) // Until searchBar is loaded -} else { - // Search the hash value if available - requestedElementID = window.location.hash; - if (requestedElementID != undefined && requestedElementID != "") { - setTimeout(() => { - searchNow(requestedElementID); - }, 20) // Until searchBar is loaded - } -} - -var content = document.getElementById("content"); -if (content) { - let isNewPage = linkParams.get("isNew") != null; - content.insertAdjacentHTML('afterbegin', `<a id="search-icon" ${isNewPage ? 'class="search-icon-new"' : ""} title="Copy the search link."><img style="width: 28px;" src="./assets/search.svg"></a>`); - content.insertAdjacentHTML('afterbegin', `<span><input id="search-bar" ${isNewPage ? 'class="search-bar-version"' : ""} type="text" placeholder="Search the docs 🔍" title="Available Filters: Version: v:2.5.3 v:2.2+ v:2.4- Type: t:expression t:condition etc. New: is:new"><span id="search-bar-after" style="display: none;">0 ${resultsFoundText}</span></span>`); - searchBar = document.getElementById("search-bar"); - searchIcon = document.getElementById("search-icon"); - - if (isNewPage) { - let tags = [] - let options = "<select id='search-version' name='versions' id='versions' onchange='checkVersionFilter()'></select>" - content.insertAdjacentHTML('afterbegin', `<span>${options}</span>`); - options = document.getElementById("search-version"); - - getApiValue(null, "skript-versions", "tags?per_page=83&page=2", (data, isCached) => { // 83 and page 2 matters to filter dev branches (temporary solution) - if (isCached) - data = data.split(","); - - for (let i = 0; i < data.length; i++) { - let tag; - if (isCached) { - tag = data[i]; - } else { - tag = data[i]["name"]; - } - tags.push(tag.replaceAll(/(.*)-(dev|beta|alpha).*/gi, "$1")); - } - - tags = [...new Set(tags)] // remove duplicates - - for (let i = 0; i < tags.length; i++) { - let option = document.createElement('option') - option.value = tags[i] - option.textContent = "Since v" + tags[i] - options.appendChild(option) - } - - if (!linkParams.get("search") && !window.location.href.match(/.*?#.+/)) - searchNow(`v:${tags[0]}+`) - - return tags; - }, true) - - } -} else { - content = document.getElementById("content-no-docs") -} - -// Copy search link -if (searchIcon) { - searchIcon.addEventListener('click', (event) => { - let link = window.location.href.split(/[?#]/g)[0] // link without search param - link += `?search=${encodeURI(searchBar.value)}` - copyToClipboard(link) - showNotification("✅ Search link copied.") - }) -} - -// Used when selecting a version from the dropdown -function checkVersionFilter() { - let el = document.getElementById("search-version") - if (el) { - searchNow(`v:${el.value}+`) - } -} - -function searchNow(value = "") { - if (value != "") // Update searchBar value - searchBar.value = value; - - let allElements = document.querySelectorAll(".item-wrapper"); - let searchValue = searchBar.value; - let count = 0; // Check if any matches found - let pass; - - // version - let version = ""; - let versionAndUp = false; - let versionAndDown = false; - if (searchValue.match(versionPattern)) { - let verExec = versionPattern.exec(searchValue); - version = verExec[1]; - if (verExec.length > 2) { - versionAndUp = verExec[2] == "+" == true; - versionAndDown = verExec[2] == "-" == true; - } - searchValue = searchValue.replaceAll(versionPattern, "") // Don't include filters in the search - } - - // Type - let filterType; - if (searchValue.match(typePattern)) { - filterType = typePattern.exec(searchValue)[1]; - searchValue = searchValue.replaceAll(typePattern, "") - } - - // News - let filterNew; - if (searchValue.match(newPattern)) { - filterNew = newPattern.exec(searchValue)[1] == "new"; - searchValue = searchValue.replaceAll(newPattern, "") - } - - searchValue = searchValue.replaceAll(/( ){2,}/gi, " ") // Filter duplicate spaces - searchValue = searchValue.replaceAll(/[^a-zA-Z0-9 #_]/gi, ""); // Filter none alphabet and digits to avoid regex errors - - allElements.forEach((e) => { - let patterns = document.querySelectorAll(`#${e.id} .item-details .skript-code-block`); - for (let i = 0; i < patterns.length; i++) { // Search in the patterns for better results - let pattern = patterns[i]; - let regex = new RegExp(searchValue, "gi") - let name = document.querySelectorAll(`#${e.id} .item-title h1`)[0].textContent // Syntax Name - let desc = document.querySelectorAll(`#${e.id} .item-description`)[0].textContent // Syntax Desc - let keywords = e.getAttribute("data-keywords") - let id = e.id // Syntax ID - let filtersFound = false; - - // Version check - let versionFound; - if (version != "") { - versionFound = versionCompare(version, document.querySelectorAll(`#${e.id} .item-details:nth-child(2) td:nth-child(2)`)[0].textContent) == 0; - - if (versionAndUp || versionAndDown) { - let versions = document.querySelectorAll(`#${e.id} .item-details:nth-child(2) td:nth-child(2)`)[0].textContent.split(","); - for (const v in versions) { // split on ',' without space in case some version didn't have space and versionCompare will handle it - if (versionAndUp) { - if (versionCompare(version, versions[v]) == 1) { - versionFound = true; - break; // Performance - } - } else if (versionAndDown) { - if (versionCompare(version, versions[v]) == -1) { - versionFound = true; - break; // Performance - } - } - } - } - } else { - versionFound = true; - } - - let filterNewFound = true; - if (filterNew) { - filterNewFound = document.querySelector(`#${e.id} .item-title .new-element`) != null - } - - let filterTypeFound = true; - let filterTypeEl = document.querySelector(`#${e.id} .item-title .item-type`); - if (filterType) { - filterTypeFound = filterType.toLowerCase() === filterTypeEl.textContent.toLowerCase() - } - - if (filterNewFound && versionFound && filterTypeFound) - filtersFound = true - - if ((regex.test(pattern.textContent.replaceAll("[ ]", " ")) || regex.test(name) || - regex.test(desc) || regex.test(keywords) || "#" + id == searchValue || searchValue == "") && filtersFound) { // Replacing '[ ]' will improve some searching cases such as 'off[ ]hand' - pass = true - break; // Performance - } - } - - // Filter - let sideNavItem = document.querySelectorAll(`#nav-contents a[href="#${e.id}"]`); // Since we have new addition filter we need to loop this - if (pass) { - e.style.display = null; - if (sideNavItem) - sideNavItem.forEach(e => { - e.style.display = null; - }) - count++; - } else { - e.style.display = "none"; - if (sideNavItem) - sideNavItem.forEach(e => { - e.style.display = "none"; - }) - } - - pass = false; // Reset - }) - - searchResultBox = document.getElementById("search-bar-after"); - if (count > 0 && (version != "" || searchValue != "" || filterType || filterNew)) { - searchResultBox.textContent = `${count} ${resultsFoundText}` - searchResultBox.style.display = null; - } else { - searchResultBox.style.display = "none"; - } - - if (count == 0) { - if (document.getElementById("no-matches") == null) - document.getElementById("content").insertAdjacentHTML('beforeend', '<p id="no-matches" style="text-align: center;">No matches found.</p>'); - } else { - if (document.getElementById("no-matches") != null) - document.getElementById("no-matches").remove(); - } - - count = 0; // reset -} - -if (searchBar) { - searchBar.focus() // To easily search after page loading without the need to click - searchBar.addEventListener('keydown', (event) => { - setTimeout(() => { // Important to actually get the value after typing or deleting + better performance - searchNow(); - }, 100); - }); -} -// Search Bar </> - -// <> Placeholders -// To save performance we use the class "placeholder" on the wrapper element of elements that contains the placeholder -// To only select those elements and replace their innerHTML -document.querySelectorAll(".placeholder").forEach(e => { - replacePlaceholders(e); -}); -// Placeholders </> - -// <> Syntax Highlighting -document.addEventListener("DOMContentLoaded", function (event) { - setTimeout(() => { - document.querySelectorAll('.item-examples .skript-code-block').forEach(el => { - highlightElement(el); - }); - document.querySelectorAll('pre code').forEach(el => { - highlightElement(el); - }); - document.querySelectorAll('.box.skript-code-block').forEach(el => { - highlightElement(el); - }); - }, 100); -}); -// Syntax Highlighting </> - - -// <> Example Collapse -var examples = document.querySelectorAll(".item-examples p"); -if (examples) { - setTimeout(() => { - examples.forEach(e => { - let pElement = e; - let divElement = e.parentElement.children[1]; - pElement.addEventListener("click", ev => { - if (pElement.classList.contains("example-details-opened")) { - pElement.classList.remove("example-details-opened"); - pElement.classList.add("example-details-closed"); - divElement.style.display = "none"; - } else { - pElement.classList.remove("example-details-closed"); - pElement.classList.add("example-details-opened"); - divElement.style.display = "block"; - } - }) - }) - }, 50) -} -// Example Collapse </> - -// <> Cookies Accecpt -if (!isCookiesAccepted) { - content.insertAdjacentHTML('beforeend', `<div id="cookies-bar"> <p> We use cookies and local storage to enhance your browsing experience and store github related statistics. By clicking "Accept", you consent to our use of cookies and local storage. </p><div style="padding: 10px; white-space: nowrap;"> <button id="cookies-accept">Accept</button> <button id="cookies-deny">Deny</button> </div></div>`); -} - -let cookiesBar = document.querySelector("#cookies-bar");; -let cookiesAccept = document.querySelector("#cookies-accept"); -let cookiesDeny = document.querySelector("#cookies-deny"); -if (cookiesAccept && cookiesDeny) { - cookiesAccept.addEventListener('click', () => { - setCookie('cookies-accepted', true, 99, true); - cookiesBar.remove(); - }); - cookiesDeny.addEventListener('click', () => { - cookiesBar.remove(); - }); -} -// Cookies Accecpt </> \ No newline at end of file diff --git a/docs/js/theme-switcher.js b/docs/js/theme-switcher.js deleted file mode 100644 index 9e6314187fb..00000000000 --- a/docs/js/theme-switcher.js +++ /dev/null @@ -1,34 +0,0 @@ -// -// This file is made to fix theme flicker at load due to 'defer' in main.js loading -// - -// Auto load DarkMode from cookies -if (getCookie("darkMode") == "false") { - document.body.setAttribute('data-theme', 'white') - document.body.insertAdjacentHTML('beforeend', `<img style="z-index: 99;" src="./assets/light-on.svg" id="theme-switch">`); -} else { - document.body.insertAdjacentHTML('beforeend', `<img style="z-index: 99;" src="./assets/light-off.svg" id="theme-switch">`); - - // Auto load from system theme - // const darkThemeMq = window.matchMedia("(prefers-color-scheme: dark)"); - // if (darkThemeMq.matches) { - // document.body.removeAttribute('data-theme'); - // } else { - // document.body.setAttribute('data-theme', 'white') - // } - } - -setTimeout(() => { - var themeSwitcher = document.getElementById('theme-switch'); - themeSwitcher.addEventListener('click', (event) => { - if (document.body.getAttribute("data-theme") == null) { - document.body.setAttribute('data-theme', 'white'); - event.target.src = "./assets/light-on.svg"; - setCookie("darkMode", "false", 99); - } else { - event.target.src = "./assets/light-off.svg"; - document.body.removeAttribute('data-theme'); - setCookie("darkMode", "true", 99); - } - }); -}, 500); // For some reason this wouldn't work in index.html (only) unless some delay is added o.O \ No newline at end of file diff --git a/docs/sections.html b/docs/sections.html deleted file mode 100644 index 679e7adae44..00000000000 --- a/docs/sections.html +++ /dev/null @@ -1,5 +0,0 @@ -<h1 id="nav-title">Sections</h1> - -<div id="content"> - ${generate sections desc_full.html} -</div> diff --git a/docs/template.html b/docs/template.html deleted file mode 100644 index 2dbe1bf6c4a..00000000000 --- a/docs/template.html +++ /dev/null @@ -1,51 +0,0 @@ -<!doctype html> -<html lang="en"> -<head> - <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=0.8, maximum-scale=1.0"> - - <meta name="description" content="Skript is a Bukkit plugin which allows server admins to customize their server easily, but without the hassle of programming a plugin or asking/paying someone to program a plugin for them. - SkriptLang/Skript"> - - <link rel="icon" type="image/png" href="./assets/icon.png"> - - <meta property="og:type" content="website"> - <meta property="og:title" content="Skript Documentation"> - <meta property="og:site_name" content="Skript Documentation"> - <meta property="og:description" content="Skript is a Bukkit plugin which allows server admins to customize their server easily, but without the hassle of programming a plugin or asking/paying someone to program a plugin for them."> - <meta property="og:image" content="https://skriptlang.github.io/Skript/assets/icon.png"> - <meta property="og:url" content="https://skriptlang.github.io/Skript/"> - <meta name="theme-color" content="#ff9800"> - - <title>Skript Documentation - ${skript.version}</title> - - <link href="css/styles.css" rel="stylesheet"> - - <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js" defer></script> - - <script src="./js/functions.js"></script> - <script src="./js/main.js" defer></script> - - <link rel="preconnect" href="https://fonts.gstatic.com"> - <link href="https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;0,900;1,200;1,300;1,400;1,500;1,600;1,700;1,900&display=swap" rel="stylesheet"> - - <link rel="preconnect" href="https://fonts.gstatic.com"> - <link href="https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap" rel="stylesheet"> - - <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.15.4/css/all.css" integrity="sha384-DyZ88mC6Up2uqS4h/KRgHuoeGwBcD4Ng9SiP4dIRy0EXTlnuz47vAwmeGwVChigm" crossorigin="anonymous"> - -</head> -<body> - <!-- loaded inside body without defer to fix theme flicker --> - <script src="./js/theme-switcher.js"></script> - - ${include navbar.html} - <nav id="side-nav"> - <div id="nav-contents"> - ${generate ${pagename} desc_nav.html} - </div> - </nav> - - ${content} - -</body> -</html> diff --git a/docs/templates/desc_full.html b/docs/templates/desc_full.html deleted file mode 100644 index 2a82c110f95..00000000000 --- a/docs/templates/desc_full.html +++ /dev/null @@ -1,59 +0,0 @@ -<div class="item-wrapper type-${element.type}" id="${element.id}" data-keywords="${element.keywords}"> - <div class="item-title"> - <h1 style="display: inline-block">${element.name}</h1> <a href="#${element.id}" class="link-icon">🔗</a> ${if new-element} <p class="new-element">New</p> ${end} - <p class="item-type">${element.type}</p> - </div> - <div class="item-content"> - <table> - <tr class="item-details"> - <td class="item-table-label">Patterns:</td> - <td class="noleftpad"> - <ul> - ${generate element.patterns pattern_element.html} - </ul> - </td> - </tr> - - <tr class="item-details"> - <td class="item-table-label">Since:</td> - <td>${element.since}</td> - </tr> - - ${if events} - <tr class="item-details"> - <td class="item-table-label">Usable in events:</td> - <td>${element.events}</td> - </tr> - - ${end} ${if required-plugins} - <tr class="item-details"> - <td class="item-table-label">Requirements:</td> - <td>${element.required-plugins}</td> - </tr> - - ${end} ${if by-addon} - <tr class="item-details"> - <td class="item-table-label">By Addon:</td> - <td>${element.by-addon}</td> - </tr> - ${end} ${if return-type} - <tr class="item-details"> - <td class="item-table-label">Return Type:</td> - <td><a href="classes.html#${element.return-type-linkcheck}">${element.return-type}</a></td> - </tr> - ${end} - </table> - - <div class="item-description"> - ${element.desc} - </div> - - <div class="item-examples"> - <p class="example-details-closed">Examples:</p> - - <div class="skript-code-block"> - ${element.examples} - </div> - </div> - </div> -</div> diff --git a/docs/templates/desc_full.json b/docs/templates/desc_full.json deleted file mode 100644 index 9acb3793baf..00000000000 --- a/docs/templates/desc_full.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "id" : "${element.id}", - "name" : "${element.name}", - "patterns" : [ - ${generate element.patterns-safe pattern_element.json} - "pattern_end" - ], - "since" : "${element.since}", - "description" : "${element.desc-safe}", - "examples" : "${element.examples-safe}" -}, diff --git a/docs/templates/desc_nav.html b/docs/templates/desc_nav.html deleted file mode 100644 index 8a749025b54..00000000000 --- a/docs/templates/desc_nav.html +++ /dev/null @@ -1 +0,0 @@ -<a href="#${element.id}">${element.name}</a> diff --git a/docs/templates/navbar.html b/docs/templates/navbar.html deleted file mode 100644 index 6d34dc12128..00000000000 --- a/docs/templates/navbar.html +++ /dev/null @@ -1,35 +0,0 @@ -<ul id="global-navigation"> - <li><a href="index.html">Home</a></li> - - <div class="menu-tab"> - <li><a class="menu-tab-item" href="docs.html">Docs <i class="fas fa-caret-down"></i></a></li> - <div class="menu-subtabs"> - <a href="events.html">Events</a> - <a href="conditions.html">Conditions</a> - <a href="sections.html">Sections</a> - <a href="effects.html">Effects</a> - <a href="expressions.html">Expressions</a> - <a href="classes.html">Types</a> - <a href="functions.html">Functions</a> - </div> - </div> - - <li><a href="docs.html?isNew" class="new-tab">New</a></li> - - <div class="menu-tab"> - <li><a class="menu-tab-item" href="tutorials.html">Tutorials <i class="fas fa-caret-down"></i></a></li> - <div class="menu-subtabs"> - <a href="text.html">Text</a> - </div> - </div> - - <div class="menu-tab"> - <li><a class="menu-tab-item" href="#">Dev Tools <i class="fas fa-caret-down"></i></a></li> - <div class="menu-subtabs"> - <a href="javadocs/" target="_blank">Javadocs</a> - </div> - </div> - - <li><a href="https://github.com/SkriptLang/Skript/" target="_blank" style="font-weight: bold;">GitHub</a></li> - <li style="margin-left: auto;"><a style="font-weight: bold; color: #ff9800" href="https://github.com/SkriptLang/Skript/releases/tag/${skript.version}" target="_blank">v${skript.version}</a></li> -</ul> diff --git a/docs/templates/pattern_element.html b/docs/templates/pattern_element.html deleted file mode 100644 index 40bdd4dcfc3..00000000000 --- a/docs/templates/pattern_element.html +++ /dev/null @@ -1 +0,0 @@ -<li class="skript-code-block">${element.pattern}</li> diff --git a/docs/templates/pattern_element.json b/docs/templates/pattern_element.json deleted file mode 100644 index 391cc6257da..00000000000 --- a/docs/templates/pattern_element.json +++ /dev/null @@ -1 +0,0 @@ -"${element.pattern}", diff --git a/docs/text.html b/docs/text.html deleted file mode 100644 index bf7aa0c2009..00000000000 --- a/docs/text.html +++ /dev/null @@ -1,337 +0,0 @@ -<h1 id="nav-title">Text in Scripts</h1> - -<div id="content-no-docs" class="no-left-panel" style="padding-top: 32px;"> - <h2 class="title">Text (String)</h2> - <p class="subtitle"> - Skript allows you to write pieces of text (programmers usually call them strings) in the scripts. This is done by putting the text inside double quotes, as follows: - </p> - <div class="box skript-code-block left-margin">"this is text"</div> - <p class="subtitle"> - Simple, isn't it? If an effect, expression, condition, trigger or function accepts something of type text or string, you can use this format to write it right there! - </p> - <h2 class="title">Formatting Text</h2> - <p class="subtitle"> - But isn't just text a bit boring? Worry not, as Minecraft has support for colors, styles and other formatting options in chat. Most of the options also work with item and entity names. - </p> - <h2 class="title">Colors</h2> - <p class="subtitle"> - Minecraft has 16 pre-set color codes to be used in text. Skript supports them in two different ways: - </p> - <p class="box left-margin"> - Color name tags, for example <b><red></b><br/> Minecraft color codes, like <b>§c</b>; using <b>&</b> works, too - </p> - <p class="subtitle"> - Here's a table of all colors, including both Skript names and color codes: - </p> - <table class="colors-table"> - <tr style="font-weight: 700"> - <td>Color</td> - <td>Code</td> - <td>Name</td> - <td>Alternative Names</td> - </tr> - <tr> - <td style=" - background-color: #000000; - color: #ffffff; - width: 10px; - height: 10px; - "></td> - <td>§0</td> - <td>black</td> - <td></td> - </tr> - <tr> - <td style=" - background-color: #0000aa; - color: #ffffff; - width: 10px; - height: 10px; - "></td> - <td>§1</td> - <td>blue</td> - <td>dark blue</td> - </tr> - <tr> - <td style=" - background-color: #00aa00; - color: #ffffff; - width: 10px; - height: 10px; - "></td> - <td>§2</td> - <td>green</td> - <td>dark green</td> - </tr> - <tr> - <td style=" - background-color: #00aaaa; - color: #ffffff; - width: 10px; - height: 10px; - "></td> - <td>§3</td> - <td>cyan</td> - <td>aqua, dark cyan, dark aqua, dark turquoise, dark turquois</td> - </tr> - <tr> - <td style=" - background-color: #aa0000; - color: #ffffff; - width: 10px; - height: 10px; - "></td> - <td>§4</td> - <td>red</td> - <td>dark red</td> - </tr> - <tr> - <td style=" - background-color: #aa00aa; - color: #ffffff; - width: 10px; - height: 10px; - "></td> - <td>§5</td> - <td>purple</td> - <td>dark purple</td> - </tr> - <tr> - <td style=" - background-color: #ffaa00; - color: #000000; - width: 10px; - height: 10px; - "></td> - <td>§6</td> - <td>orange</td> - <td>orange, gold, dark yellow</td> - </tr> - <tr> - <td style=" - background-color: #aaaaaa; - color: #000000; - width: 10px; - height: 10px; - "></td> - <td>§7</td> - <td>grey</td> - <td>light grey, gray, light gray, silver</td> - </tr> - <tr> - <td style=" - background-color: #555555; - color: #ffffff; - width: 10px; - height: 10px; - "></td> - <td>§8</td> - <td>dark gray</td> - <td>dark grey</td> - </tr> - <tr> - <td style=" - background-color: #5555ff; - color: #ffffff; - width: 10px; - height: 10px; - "></td> - <td>§9</td> - <td>light blue</td> - <td>light blue, indigo</td> - </tr> - <tr> - <td style=" - background-color: #55ff55; - color: #000000; - width: 10px; - height: 10px; - "></td> - <td>§a</td> - <td>light green</td> - <td>lime, lime green</td> - </tr> - <tr> - <td style=" - background-color: #55ffff; - color: #000000; - width: 10px; - height: 10px; - "></td> - <td>§b</td> - <td>light cyan</td> - <td>light aqua, turquoise, turquois, light blue</td> - </tr> - <tr> - <td style=" - background-color: #ff5555; - color: #000000; - width: 10px; - height: 10px; - "></td> - <td>§c</td> - <td>light red</td> - <td>pink</td> - </tr> - <tr> - <td style=" - background-color: #ff55ff; - color: #000000; - width: 10px; - height: 10px; - "></td> - <td>§d</td> - <td>magenta</td> - <td>light purple</td> - </tr> - <tr> - <td style=" - background-color: #ffff55; - color: #000000; - width: 10px; - height: 10px; - "></td> - <td>§e</td> - <td>yellow</td> - <td>light yellow</td> - </tr> - <tr> - <td style=" - background-color: #ffffff; - color: #000000; - width: 10px; - height: 10px; - "></td> - <td>§f</td> - <td>white</td> - <td></td> - </tr> - </table> - <p class="subtitle"> - In <b>Minecraft 1.16</b>, support was added for - <b>6-digit</b> hexadecimal colors to specify custom colors other than the 16 default color codes. A new tag can be used to format with these colors. The tag looks like this: - </p> - <div class="box skript-code-block left-margin"><##hex code></div> - <p class="subtitle">Here's what the tag would look like when used in a script:</p> - <div class="box skript-code-block left-margin"> - send "<##123456>Hey %player%!" to player - </div> - <p class="subtitle"> - For information not related to Skript, see - <a href="https://minecraft.gamepedia.com/Formatting_codes#Color_codes">Minecraft Wiki page</a - > - concerning colors. Note that depending on Skript configuration, color - codes may do more than just change color of text after them. By default, - for backwards compatibility, they clear following styles: magic, bold, - strikethrough, underlined, italic. Other styles are not affected, and - this feature can be toggled of in config.sk. - </p> - <h2 class="title">Other Styles</h2> - <p class="subtitle"> - Minecraft also has various other styles available. The following are - available everywhere, including item and entity names: - </p> - - <table class="colors-table"> - <tr style="font-weight: 700"> - <td>Code</td> - <td>Name</td> - <td>Alternative Names</td> - </tr> - <tr> - <td>§k</td> - <td>magic <span class="magic-text" style="position: absolute;">test</span></td> - <td>obfuscated</td> - </tr> - <tr> - <td>§l</td> - <td style="font-weight: bold">bold</td> - <td>b</td> - </tr> - <tr> - <td>§m</td> - <td style="text-decoration: line-through">strikethrough</td> - <td>strike, s</td> - </tr> - <tr> - <td>§n</td> - <td style="text-decoration: underline">underlined</td> - <td>underline, u</td> - </tr> - <tr> - <td>§o</td> - <td style="font-style: italic">italic</td> - <td>italics, i</td> - </tr> - <tr> - <td>§r</td> - <td>reset</td> - <td>r</td> - </tr> - </table> - <p class="subtitle"> - If it wasn't clear from the table, §r clears all other formatting and - colors. You'll probably use it quite often when sending chat messages - from scripts. - </p> - <p class="subtitle"> - Skript also supports certain newer features, which are only available in - chat. Those do not have formatting codes, so you must use tags for them. - </p> - <table class="colors-table"> - <tr style="font-weight: 700"> - <td>Name</td> - <td>Alternative Names</td> - <td>Description</td> - </tr> - <tr> - <td>link</td> - <td>open url, url</td> - <td>Opens a link when player clicks on text</td> - </tr> - <tr> - <td>run command</td> - <td>command, cmd</td> - <td>Makes player execute a chat command when they click on text</td> - </tr> - <tr> - <td>suggest command</td> - <td>sgt</td> - <td>Adds a command to chat prompt of player when clicked</td> - </tr> - <tr> - <td>tooltip</td> - <td>show text, ttp</td> - <td>Shows a tooltip when player hovers over text with their mouse</td> - </tr> - <tr> - <td>font</td> - <td>f</td> - <td>Change the font of the text (1.16+)</td> - </tr> - <tr> - <td>insertion</td> - <td>insert, ins</td> - <td>Will append a text at player's current cursor in chat input only while holding SHIFT.</td> - </tr> - </table> - <p class="subtitle">All of these styles require a parameter, in format</p> - <div class="box skript-code-block left-margin"> - <name:parameter> - </div> - <p class="subtitle"> - For link, parameter must be either http or https url if you want clients - to recognize it. For others, it can be any text you'd like (you can make - player run invalid commands if you wish). -</p> - <h2 class="title">Text and Variables</h2> - <p class="subtitle"> - Variable names are text, but obviously formatting that text does no - good. However, everything else you can do for text, you can do for - variable names. A guide about this is coming... some day. - </p> - <p style="padding-bottom: 20px"> - Guide written by <a href="https://github.com/bensku">bensku</a>. - </p> - <div style="padding-top: 32px;"></div> <!-- Space --> -</div> \ No newline at end of file diff --git a/docs/tutorials.html b/docs/tutorials.html deleted file mode 100644 index d4e74bb75c6..00000000000 --- a/docs/tutorials.html +++ /dev/null @@ -1,16 +0,0 @@ -<h1 id="nav-title">Tutorials</h1> -<div id="content-no-docs" class="no-left-panel" style="margin-top: 40px"> - <p class="box-title-red">Note:</p> - <div class="box-red" style="height: max-content"> - <strong>Skript Tutorials are coming soon.</strong> - <br><br> - <ol class="custom-list"> - <li>Loops</li> - <li>Commands</li> - <li>Functions</li> - <li>Variables</li> - <li>Visual effects</li> - </ol> - <br> - </div> -</div> \ No newline at end of file From 8a96dcbbafb6812e19cf09a03bef75451c435c82 Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Sat, 4 Feb 2023 05:41:39 +0300 Subject: [PATCH 226/619] Add copy to clipboard tag (#5353) --- src/main/java/ch/njol/skript/util/Utils.java | 1 + src/main/java/ch/njol/skript/util/chat/ChatMessages.java | 2 ++ .../java/ch/njol/skript/util/chat/MessageComponent.java | 4 +++- src/main/java/ch/njol/skript/util/chat/SkriptChatCode.java | 7 +++++++ src/main/resources/lang/default.lang | 1 + 5 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/util/Utils.java b/src/main/java/ch/njol/skript/util/Utils.java index 7598fb83b74..18adcfdaf4c 100644 --- a/src/main/java/ch/njol/skript/util/Utils.java +++ b/src/main/java/ch/njol/skript/util/Utils.java @@ -447,6 +447,7 @@ public static CompletableFuture<ByteArrayDataInput> sendPluginMessage(Player pla final static Map<String, String> 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() { diff --git a/src/main/java/ch/njol/skript/util/chat/ChatMessages.java b/src/main/java/ch/njol/skript/util/chat/ChatMessages.java index 8a80f65929e..dbe03940c76 100644 --- a/src/main/java/ch/njol/skript/util/chat/ChatMessages.java +++ b/src/main/java/ch/njol/skript/util/chat/ChatMessages.java @@ -95,6 +95,8 @@ public void onLanguageChange() { Skript.debug("Parsing message style lang files"); for (SkriptChatCode code : SkriptChatCode.values()) { assert code != null; + if (code == SkriptChatCode.copy_to_clipboard && !Utils.COPY_SUPPORTED) + continue; registerChatCode(code); } diff --git a/src/main/java/ch/njol/skript/util/chat/MessageComponent.java b/src/main/java/ch/njol/skript/util/chat/MessageComponent.java index a6b750cba6b..725114cca05 100644 --- a/src/main/java/ch/njol/skript/util/chat/MessageComponent.java +++ b/src/main/java/ch/njol/skript/util/chat/MessageComponent.java @@ -103,7 +103,9 @@ public enum Action { suggest_command, - change_page; + change_page, + + copy_to_clipboard; public final String spigotName; diff --git a/src/main/java/ch/njol/skript/util/chat/SkriptChatCode.java b/src/main/java/ch/njol/skript/util/chat/SkriptChatCode.java index f34ede0dff2..f8324e123ea 100644 --- a/src/main/java/ch/njol/skript/util/chat/SkriptChatCode.java +++ b/src/main/java/ch/njol/skript/util/chat/SkriptChatCode.java @@ -125,6 +125,13 @@ public void updateComponent(MessageComponent component, String param) { } }, + copy_to_clipboard(true) { + @Override + public void updateComponent(MessageComponent component, String param) { + component.clickEvent = new ClickEvent(ClickEvent.Action.copy_to_clipboard, param); + } + }, + // hoverEvent show_text(true) { diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 915fe646839..662a7e7587e 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -405,6 +405,7 @@ chat styles: run_command: run command, command, cmd suggest_command: suggest command, sgt change_page: change page + copy_to_clipboard: copy to clipboard, copy, clipboard show_text: tooltip, show text, ttp font: font, f insertion: insertion, insert, ins From 4923c62f9f70f284f2a27e06e01f52282398f1a3 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Sat, 4 Feb 2023 06:00:20 +0300 Subject: [PATCH 227/619] =?UTF-8?q?=F0=9F=9B=A0=20Fix=20a=20forgotten=20Ty?= =?UTF-8?q?po=20in=20Docs=20Bot=20(#5426)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 3bbdc60b52f..e41c4bf7692 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -53,7 +53,7 @@ jobs: run: | cd ./skript-docs git config user.email "nightlydocs@skriptlang.org" - git config user.name "Nightly Doc Bot" + git config user.name "Nightly Docs Bot" git add docs/nightly git commit -m "Update nightly docs" || (echo "Nothing to push!" && exit 0) git push origin main From 3b9c157e0f81d2119d8e94bf6cf82e3a01b7132d Mon Sep 17 00:00:00 2001 From: sovdee <ethansovde@gmail.com> Date: Fri, 3 Feb 2023 22:26:53 -0500 Subject: [PATCH 228/619] Create CondIsPreferredTool.java (#5397) --- .../conditions/CondIsPreferredTool.java | 101 ++++++++++++++++++ .../conditions/CondIsPreferredTool.sk | 25 +++++ 2 files changed, 126 insertions(+) create mode 100644 src/main/java/ch/njol/skript/conditions/CondIsPreferredTool.java create mode 100644 src/test/skript/tests/syntaxes/conditions/CondIsPreferredTool.sk diff --git a/src/main/java/ch/njol/skript/conditions/CondIsPreferredTool.java b/src/main/java/ch/njol/skript/conditions/CondIsPreferredTool.java new file mode 100644 index 00000000000..c03fcc798cd --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondIsPreferredTool.java @@ -0,0 +1,101 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.conditions; + +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Condition; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; +import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Is Preferred Tool") +@Description( + "Checks whether an item is the preferred tool for a block. A preferred tool is one that will drop the block's item " + + "when used. For example, a wooden pickaxe is a preferred tool for grass and stone blocks, but not for iron ore." +) +@Examples({ + "on left click:", + "\tevent-block is set", + "\tif player's tool is the preferred tool for event-block:", + "\t\tbreak event-block naturally using player's tool", + "\telse:", + "\t\tcancel event" +}) +@Since("INSERT VERSION") +@RequiredPlugins("1.16.5+, Paper 1.19.2+ (blockdata)") +public class CondIsPreferredTool extends Condition { + + static { + String types = "blocks"; + if (Skript.methodExists(BlockData.class, "isPreferredTool", ItemStack.class)) + types += "/blockdatas"; + + Skript.registerCondition(CondIsPreferredTool.class, + "%itemtypes% (is|are) %" + types + "%'s preferred tool[s]", + "%itemtypes% (is|are) [the|a] preferred tool[s] (for|of) %" + types + "%", + "%itemtypes% (is|are)(n't| not) %" + types + "%'s preferred tool[s]", + "%itemtypes% (is|are)(n't| not) [the|a] preferred tool[s] (for|of) %" + types + "%" + ); + } + + private Expression<ItemType> items; + private Expression<?> blocks; + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + setNegated(matchedPattern >= 2); + items = (Expression<ItemType>) exprs[0]; + blocks = exprs[1]; + return true; + } + + @Override + public boolean check(Event event) { + for (Object block : blocks.getArray(event)){ + if (block instanceof Block) { + if (!items.check(event, (item) -> ((Block) block).isPreferredTool(item.getRandom()), isNegated())) + return false; + } else if (block instanceof BlockData) { + if (!items.check(event, (item) -> ((BlockData) block).isPreferredTool(item.getRandom()), isNegated())) + return false; + } else { + // invalid type + return false; + } + } + // all checks passed + return true; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return items.toString(event, debug) + " is the preferred tool for " + blocks.toString(event, debug); + } +} diff --git a/src/test/skript/tests/syntaxes/conditions/CondIsPreferredTool.sk b/src/test/skript/tests/syntaxes/conditions/CondIsPreferredTool.sk new file mode 100644 index 00000000000..bbb3687e438 --- /dev/null +++ b/src/test/skript/tests/syntaxes/conditions/CondIsPreferredTool.sk @@ -0,0 +1,25 @@ +test "preferred tool blockdata" when running minecraft "1.19.2": + assert wooden pickaxe is preferred tool for stone with "failed wooden pickaxe for stone blockdata" + assert wooden pickaxe is preferred tool for grass with "failed wooden pickaxe for grass blockdata" + assert wooden pickaxe is not preferred tool for obsidian with "failed wooden pickaxe for obsidian blockdata" + + assert diamond pickaxe is preferred tool for obsidian with "failed diamond pickaxe for obsidian blockdata" + + assert wooden axe is not preferred tool for stone with "failed wooden axe for stone blockdata" + assert wooden axe is preferred tool for grass with "failed wooden axe for grass blockdata" + +test "preferred tool" when running minecraft "1.16.5": + set {_block} to block at location(0,0,0, "world") + set {_temp} to {_block}'s type + set block at {_block} to grass + assert wooden axe is preferred tool for {_block} with "failed wooden axe for grass" + assert wooden pickaxe is preferred tool for {_block} with "failed wooden pickaxe for grass" + assert diamond pickaxe is preferred tool for {_block} with "failed diamond pickaxe for grass" + + set block at {_block} to obsidian + assert wooden axe is not preferred tool for {_block} with "failed wooden axe for obsidian" + assert wooden pickaxe is not preferred tool for {_block} with "failed wooden pickaxe for obsidian" + assert diamond pickaxe is preferred tool for {_block} with "failed diamond pickaxe for obsidian" + + # leave no trace + set block at {_block} to {_temp} From 4d0a5cf171574187d385a57ea985922b64417f78 Mon Sep 17 00:00:00 2001 From: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> Date: Fri, 3 Feb 2023 21:58:05 -0600 Subject: [PATCH 229/619] Add method exists condition for testing (#5370) --- .../skript/tests/runner/CondMethodExists.java | 123 ++++++++++++++++++ .../syntaxes/conditions/CondMethodExists.sk | 10 ++ 2 files changed, 133 insertions(+) create mode 100644 src/main/java/ch/njol/skript/tests/runner/CondMethodExists.java create mode 100644 src/test/skript/tests/syntaxes/conditions/CondMethodExists.sk diff --git a/src/main/java/ch/njol/skript/tests/runner/CondMethodExists.java b/src/main/java/ch/njol/skript/tests/runner/CondMethodExists.java new file mode 100644 index 00000000000..8a3b5e04061 --- /dev/null +++ b/src/main/java/ch/njol/skript/tests/runner/CondMethodExists.java @@ -0,0 +1,123 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.tests.runner; + +import ch.njol.skript.conditions.base.PropertyCondition; +import org.apache.commons.lang.StringUtils; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Name("Method Exists") +@Description("Checks if a method exists") +@Examples("if method \"org.bukkit.Bukkit#getPluginCommand(java.lang.String)") +@Since("INSERT VERSION") +public class CondMethodExists extends PropertyCondition<String> { + + private final static Pattern SIGNATURE_PATTERN = Pattern.compile("(?<class>.+)#(?<name>.+)\\((?<params>.*)\\)"); + + static { + Skript.registerCondition(CondMethodExists.class, "method[s] %strings% [dont:do(esn't|n't)] exist[s]"); + } + + @SuppressWarnings("NotNullFieldNotInitialized") + private Expression<String> signatures; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + signatures = (Expression<String>) exprs[0]; + setExpr(signatures); + setNegated(parseResult.hasTag("dont")); + return true; + } + + @Override + public boolean check(String signature) { + Matcher sigMatcher = SIGNATURE_PATTERN.matcher(signature); + if (!sigMatcher.matches()) + return false; + + try { + Class<?> clazz = Class.forName(sigMatcher.group("class")); + List<Class<?>> parameters = new ArrayList<>(); + String rawParameters = sigMatcher.group("params"); + if (!StringUtils.isBlank(rawParameters)) { + for (String parameter : rawParameters.split(",")) { + parameters.add(parseClass(parameter.trim())); + } + } + return Skript.methodExists(clazz, sigMatcher.group("name"), parameters.toArray(new Class[0])); + } catch (ClassNotFoundException exception) { + return false; + } + } + + @Override + protected String getPropertyName() { + return "method exists"; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "method " + signatures.toString(event, debug) + " exists"; + } + + private static Class<?> parseClass(String clazz) throws ClassNotFoundException { + if (clazz.endsWith("[]")) { + Class<?> baseClass = parseClass(clazz.substring(0, clazz.length() - 2)); + return Array.newInstance(baseClass, 0).getClass(); + } + switch (clazz) { + case "byte": + return byte.class; + case "short": + return short.class; + case "int": + return int.class; + case "long": + return long.class; + case "float": + return float.class; + case "double": + return double.class; + case "boolean": + return boolean.class; + case "char": + return char.class; + default: + return Class.forName(clazz); + } + } + +} diff --git a/src/test/skript/tests/syntaxes/conditions/CondMethodExists.sk b/src/test/skript/tests/syntaxes/conditions/CondMethodExists.sk new file mode 100644 index 00000000000..e34bac7daac --- /dev/null +++ b/src/test/skript/tests/syntaxes/conditions/CondMethodExists.sk @@ -0,0 +1,10 @@ +test "method exists": + assert method "java.lang.String##length()" exists with "Existing method was not found" + assert method "java.lang.String##notReal()" doesn't exist with "Fake method was found" + assert method "java.lang.SuperFakeClass##notReal()" doesn't exist with "Fake class was found" + assert method "java.lang.String##indexOf(java.lang.String)" exists with "Existing method with parameter was not found" + assert method "java.lang.String##replace(java.lang.CharSequence,java.lang.CharSequence)" exists with "Existing method with multiple parameters not found" + assert method "java.lang.String##charAt(int)" exists with "Existing method with primitive parameter not found" + assert method "java.lang.String##substring(int,int)" exists with "Existing method with multiple primitive parameters not found" + assert method "java.lang.String##valueOf(char[])" exists with "Existing method with primitive array parameter not found" + assert method "java.lang.Runtime##exec(java.lang.String[],java.lang.String[])" exists with "Existing method with array parameter not found" From 10ef98d582911c1a2b3ab62435e8ee5376b2c014 Mon Sep 17 00:00:00 2001 From: coffeeRequired <106232282+cooffeeRequired@users.noreply.github.com> Date: Sat, 4 Feb 2023 11:13:57 +0100 Subject: [PATCH 230/619] It checks if the item is stackable. (#5381) --- .../skript/conditions/CondIsStackable.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/main/java/ch/njol/skript/conditions/CondIsStackable.java diff --git a/src/main/java/ch/njol/skript/conditions/CondIsStackable.java b/src/main/java/ch/njol/skript/conditions/CondIsStackable.java new file mode 100644 index 00000000000..f419e0ba2dd --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondIsStackable.java @@ -0,0 +1,52 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.conditions; + +import org.bukkit.inventory.ItemStack; +import ch.njol.skript.conditions.base.PropertyCondition; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; + +@Name("Is Stackable") +@Description("Checks whether an item is stackable.") +@Examples({ + "diamond axe is stackable", + "birch wood is stackable", + "torch is stackable" +}) +@Since("INSERT VERSION") +public class CondIsStackable extends PropertyCondition<ItemStack> { + + static { + register(CondIsStackable.class, "stackable", "itemstacks"); + } + + @Override + public boolean check(ItemStack item) { + return item.getMaxStackSize() > 1; + } + + @Override + protected String getPropertyName() { + return "stackable"; + } + +} From 671338c801725beb4d4012dfe04f6abbca2e25bf Mon Sep 17 00:00:00 2001 From: Fusezion <fusezionstream@gmail.com> Date: Sat, 4 Feb 2023 10:20:26 -0500 Subject: [PATCH 231/619] Update README.md (#5410) --- README.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dcbd171e4b0..bdbbcab8316 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ This Github fork of Skript is based on Mirreski's improvements which was built on Njol's original Skript. ## Requirements -Skript requires **Spigot** to work. You heard it right, Bukkit does *not* work. +Skript requires **Spigot** to work. You heard it right, **CraftBukkit** does *not* work. **Paper**, which is a fork of Spigot, is recommended; it is required for some parts of Skript to be available. @@ -31,7 +31,11 @@ Please see our [contribution guidelines](https://github.com/SkriptLang/Skript/bl before reporting issues. ## Help Us Test -We have an [official Discord community](https://discord.gg/ZPsZAg6ygu) for testing Skript's new features and releases. +Wanting to help test Skript's new features and releases? +You can head on over to our [Official Testing Discord](https://discord.gg/ZPsZAg6ygu), and whenever we start testing new features/releases you will be the first to know. + +Please note this is not a help Discord. +If you require assistance with how to use Skript please check out the [Relevant Links](https://github.com/SkriptLang/Skript#relevant-links) section for a list of available resources to assist you. ## A Note About Add-ons We don't support add-ons here, even though some of Skript developers have also @@ -168,7 +172,8 @@ Or, if you use Maven: ## Relevant Links * [skUnity forums](https://forums.skunity.com) -* [Add-on releases at skUnity](https://forums.skunity.com/forums/addon-releases) +* [skUnity addon releases](https://forums.skunity.com/forums/addon-releases) +* [skUnity Discord invite](https://discord.gg/0l3WlzBPKX7WNjkf) * [Skript Chat Discord invite](https://discord.gg/0lx4QhQvwelCZbEX) * [Skript Hub](https://skripthub.net) * [Original Skript at Bukkit](https://dev.bukkit.org/bukkit-plugins/skript) (inactive) From fc344b3144af36dd527904a100ba35f2b032d937 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Mon, 6 Feb 2023 19:42:00 +0300 Subject: [PATCH 232/619] =?UTF-8?q?=F0=9F=9A=80=20Add=20a=20cover=20to=20R?= =?UTF-8?q?EADME.md=20(#5427)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/assets/Cover.jpg | Bin 0 -> 135367 bytes README.md | 6 +++++- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 .github/assets/Cover.jpg diff --git a/.github/assets/Cover.jpg b/.github/assets/Cover.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a77391fd6fed95aa723028b69172f83a9effbf8f GIT binary patch literal 135367 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 literal 0 HcmV?d00001 diff --git a/README.md b/README.md index bdbbcab8316..a353f8b6225 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ + + +--- + # Skript [](https://travis-ci.org/SkriptLang/Skript) -Skript is a plugin for Paper/Spigot, which allows server owners and other people +**Skript** is a Minecraft plugin for Paper/Spigot, which allows server owners and other people to modify their servers without learning Java. It can also be useful if you *do* know Java; some tasks are quicker to do with Skript, and so it can be used for prototyping etc. From 33c49f8cf866d0a598b215bb06534782badace37 Mon Sep 17 00:00:00 2001 From: Shane Bee <shanebolenback@me.com> Date: Tue, 7 Feb 2023 11:05:46 -0800 Subject: [PATCH 233/619] ExprPlayerViewDistance - re-enable this expression (#5333) --- .../expressions/ExprPlayerViewDistance.java | 118 ++++++++++-------- 1 file changed, 66 insertions(+), 52 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprPlayerViewDistance.java b/src/main/java/ch/njol/skript/expressions/ExprPlayerViewDistance.java index 3fc9a387f4d..9fcbe8b2d79 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprPlayerViewDistance.java +++ b/src/main/java/ch/njol/skript/expressions/ExprPlayerViewDistance.java @@ -25,42 +25,41 @@ import ch.njol.skript.doc.Name; import ch.njol.skript.doc.RequiredPlugins; import ch.njol.skript.doc.Since; -import ch.njol.skript.expressions.base.PropertyExpression; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.SkriptParser; -import ch.njol.util.Kleenean; +import ch.njol.skript.expressions.base.SimplePropertyExpression; import ch.njol.util.coll.CollectionUtils; +import org.apache.commons.lang.NotImplementedException; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @Name("View Distance") -@Description("The view distance of a player. Can be changed.") -@Examples({"set view distance of player to 10", "set {_view} to view distance of player", - "reset view distance of all players", "add 2 to view distance of player"}) -@RequiredPlugins("Paper 1.9-1.13.2") +@Description({ + "The view distance of a player as set by the server. Can be changed.", + "NOTE: This is the view distance sent by the server to the player.", + "This has nothing to do with client side view distance settings", + "NOTE: This may not work on some versions (such as MC 1.14.x).", + "The return value in this case will be the view distance set in system.properties." +}) +@Examples({ + "set view distance of player to 10", "set {_view} to view distance of player", + "reset view distance of all players", "add 2 to view distance of player" +}) +@RequiredPlugins("Paper") @Since("2.4") -public class ExprPlayerViewDistance extends PropertyExpression<Player, Long> { - +public class ExprPlayerViewDistance extends SimplePropertyExpression<Player, Integer> { + static { - // Not supported on 1.14 yet - if (Skript.methodExists(Player.class, "getViewDistance") && !Skript.isRunningMinecraft(1, 14)) - register(ExprPlayerViewDistance.class, Long.class, "view distance[s]", "players"); - } - - @Override - @SuppressWarnings({"unchecked", "null"}) - public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { - setExpr((Expression<Player>) exprs[0]); - return true; + if (Skript.methodExists(Player.class, "getViewDistance")) + register(ExprPlayerViewDistance.class, Integer.class, "view distance[s]", "players"); } - + @Override - protected Long[] get(Event e, Player[] source) { - return get(source, player -> (long) player.getViewDistance()); + @Nullable + public Integer convert(Player player) { + return getViewDistance(player); } - + @Override @Nullable public Class<?>[] acceptChange(ChangeMode mode) { @@ -74,40 +73,55 @@ public Class<?>[] acceptChange(ChangeMode mode) { } return null; } - + @Override - public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { int distance = delta == null ? 0 : ((Number) delta[0]).intValue(); - switch (mode) { - case DELETE: - case SET: - for (Player player : getExpr().getArray(e)) - player.setViewDistance(distance); - break; - case ADD: - for (Player player : getExpr().getArray(e)) - player.setViewDistance(player.getViewDistance() + distance); - break; - case REMOVE: - for (Player player : getExpr().getArray(e)) - player.setViewDistance(player.getViewDistance() - distance); - break; - case RESET: - for (Player player : getExpr().getArray(e)) - player.setViewDistance(Bukkit.getServer().getViewDistance()); - default: - assert false; + for (Player player : getExpr().getArray(event)) { + int oldDistance = getViewDistance(player); + switch (mode) { + case DELETE: + case SET: + setViewDistance(player, distance); + break; + case ADD: + setViewDistance(player, oldDistance + distance); + break; + case REMOVE: + setViewDistance(player, oldDistance - distance); + break; + case RESET: + setViewDistance(player, Bukkit.getViewDistance()); + default: + assert false; + } + } + } + + private static int getViewDistance(Player player) { + try { + return player.getViewDistance(); + } catch (NotImplementedException ignore) { + return Bukkit.getViewDistance(); } } - + + private static void setViewDistance(Player player, int distance) { + try { + player.setViewDistance(distance); + } catch (NotImplementedException ignore) { + Skript.error("'player view distance' is not available on your server version. This is NOT a Skript bug."); + } + } + @Override - public Class<? extends Long> getReturnType() { - return Long.class; + public Class<? extends Integer> getReturnType() { + return Integer.class; } - + @Override - public String toString(@Nullable Event e, boolean debug) { - return "the view distance of " + getExpr().toString(e, debug); + protected String getPropertyName() { + return "view distance"; } - + } From 97b44661b4ba164c0a63fedef7ad8c7382435213 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Wed, 8 Feb 2023 01:07:50 +0300 Subject: [PATCH 234/619] =?UTF-8?q?=F0=9F=9A=80=20Update=20docs=20link=20i?= =?UTF-8?q?n=20README=20(#5433)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a353f8b6225..edc1bdb9596 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ versions will be supported as soon as possible. You can find the downloads for each version with their release notes in the [releases page](https://github.com/SkriptLang/Skript/releases). ## Documentation -Documentation is available [here](https://skriptlang.github.io/Skript) for the +Documentation is available [here](https://docs.skriptlang.org/) for the latest version of Skript. ## Reporting Issues From e8b6d3900a17c0c4be10c17ff8b037aeece5df9c Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Thu, 9 Feb 2023 21:32:30 +0300 Subject: [PATCH 235/619] Get all the values of a classinfo (#5077) --- .../ch/njol/skript/classes/ClassInfo.java | 46 ++++- .../skript/classes/data/BukkitClasses.java | 7 + .../skript/classes/data/SkriptClasses.java | 9 + .../ch/njol/skript/entity/EntityData.java | 20 +- .../ch/njol/skript/expressions/ExprItems.java | 151 +++++++++++++++ .../ch/njol/skript/expressions/ExprSets.java | 175 +++++------------- .../ch/njol/skript/registrations/Classes.java | 3 + 7 files changed, 279 insertions(+), 132 deletions(-) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprItems.java diff --git a/src/main/java/ch/njol/skript/classes/ClassInfo.java b/src/main/java/ch/njol/skript/classes/ClassInfo.java index a7c09a76493..3dce029edb5 100644 --- a/src/main/java/ch/njol/skript/classes/ClassInfo.java +++ b/src/main/java/ch/njol/skript/classes/ClassInfo.java @@ -20,10 +20,14 @@ import java.util.Arrays; import java.util.HashSet; +import java.util.Iterator; import java.util.Set; +import java.util.function.Supplier; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; +import ch.njol.skript.SkriptAPIException; +import ch.njol.util.coll.iterator.ArrayIterator; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; @@ -60,7 +64,10 @@ public class ClassInfo<T> implements Debuggable { @Nullable private Changer<? super T> changer = null; - + + @Nullable + private Supplier<Iterator<T>> supplier = null; + @Nullable private Serializer<? super T> serializer = null; @Nullable @@ -162,7 +169,33 @@ public ClassInfo<T> defaultExpression(final DefaultExpression<T> defaultExpressi this.defaultExpression = defaultExpression; return this; } - + + + /** + * Used for dynamically getting all the possible values of a class + * + * @param supplier The supplier of the values + * @return This ClassInfo object + * @see ClassInfo#supplier(Object[]) + */ + public ClassInfo<T> supplier(Supplier<Iterator<T>> supplier) { + if (this.supplier != null) + throw new SkriptAPIException("supplier of this class is already set"); + this.supplier = supplier; + return this; + } + + /** + * Used for getting all the possible constants of a class + * + * @param values The array of the values + * @return This ClassInfo object + * @see ClassInfo#supplier(Supplier) + */ + public ClassInfo<T> supplier(T[] values) { + return supplier(() -> new ArrayIterator<>(values)); + } + public ClassInfo<T> serializer(final Serializer<? super T> serializer) { assert this.serializer == null; if (serializeAs != null) @@ -336,7 +369,14 @@ public Pattern[] getUserInputPatterns() { public Changer<? super T> getChanger() { return changer; } - + + @Nullable + public Supplier<Iterator<T>> getSupplier() { + if (supplier == null && c.isEnum()) + supplier = () -> new ArrayIterator<>(c.getEnumConstants()); + return supplier; + } + @Nullable public Serializer<? super T> getSerializer() { return serializer; 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 8ca30c77843..f8621ea7bd8 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -28,6 +28,7 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; +import ch.njol.util.coll.iterator.ArrayIterator; import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.classes.ConfigurationSerializer; import ch.njol.skript.classes.EnumClassInfo; @@ -899,6 +900,9 @@ public String toVariableNameString(InventoryHolder holder) { "{_item} is a torch") .since("1.0") .after("number") + .supplier(() -> Arrays.stream(Material.values()) + .map(ItemStack::new) + .iterator()) .parser(new Parser<ItemStack>() { @Override @Nullable @@ -1027,6 +1031,7 @@ protected boolean canBeInstantiated() { "apply potion of speed 2 to the player for 60 seconds", "remove invisibility from the victim") .since("") + .supplier(PotionEffectType.values()) .parser(new Parser<PotionEffectType>() { @Override @Nullable @@ -1188,6 +1193,7 @@ public boolean mustSyncDeserialization() { .examples("") .since("1.4.6") .before("enchantmenttype") + .supplier(Enchantment.values()) .parser(new Parser<Enchantment>() { @Override @Nullable @@ -1424,6 +1430,7 @@ public String toVariableNameString(FireworkEffect effect) { .usage(Arrays.stream(GameRule.values()).map(GameRule::getName).collect(Collectors.joining(", "))) .since("2.5") .requiredPlugins("Minecraft 1.13 or newer") + .supplier(GameRule.values()) .parser(new Parser<GameRule>() { @Override @Nullable 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 5966704ece4..184aa1d219c 100644 --- a/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java @@ -19,6 +19,7 @@ package ch.njol.skript.classes.data; import java.io.StreamCorruptedException; +import java.util.Iterator; import java.util.Locale; import java.util.regex.Pattern; @@ -65,6 +66,8 @@ import ch.njol.skript.util.visual.VisualEffects; import ch.njol.yggdrasil.Fields; +import java.util.Arrays; + /** * @author Peter Güttinger */ @@ -73,6 +76,7 @@ public class SkriptClasses { public SkriptClasses() {} static { + //noinspection unchecked Classes.registerClass(new ClassInfo<>(ClassInfo.class, "classinfo") .user("types?") .name("Type") @@ -86,6 +90,7 @@ public SkriptClasses() {} "kill the loop-entity") .since("2.0") .after("entitydata", "entitytype", "itemtype") + .supplier(() -> (Iterator) Classes.getClassInfos().iterator()) .parser(new Parser<ClassInfo>() { @Override @Nullable @@ -198,6 +203,9 @@ public String toVariableNameString(final WeatherType o) { .since("1.0") .before("itemstack", "entitydata", "entitytype") .after("number", "integer", "long", "time") + .supplier(() -> Arrays.stream(Material.values()) + .map(ItemType::new) + .iterator()) .parser(new Parser<ItemType>() { @Override @Nullable @@ -595,6 +603,7 @@ public String toVariableNameString(Slot o) { "set the color of the block to green", "message \"You're holding a <%color of tool%>%color of tool%<reset> wool block\"") .since("") + .supplier(SkriptColor.values()) .parser(new Parser<Color>() { @Override @Nullable diff --git a/src/main/java/ch/njol/skript/entity/EntityData.java b/src/main/java/ch/njol/skript/entity/EntityData.java index f797c61d7f8..8adb5f4043f 100644 --- a/src/main/java/ch/njol/skript/entity/EntityData.java +++ b/src/main/java/ch/njol/skript/entity/EntityData.java @@ -41,6 +41,7 @@ import ch.njol.skript.registrations.Classes; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; +import ch.njol.util.coll.iterator.SingleItemIterator; import ch.njol.yggdrasil.Fields; import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilExtendedSerializable; import org.bukkit.Bukkit; @@ -60,6 +61,7 @@ import java.util.Iterator; import java.util.List; import java.util.regex.Pattern; +import java.util.stream.Collectors; /** * @author Peter Güttinger @@ -78,6 +80,8 @@ public abstract class EntityData<E extends Entity> implements SyntaxElement, Ygg private static final Pattern REGEX_PATTERN = Pattern.compile("[a-zA-Z -]+"); + private static final List<EntityData> ALL_ENTITY_DATAS = new ArrayList<>(); + public static Serializer<EntityData> serializer = new Serializer<EntityData>() { @Override public Fields serialize(final EntityData o) throws NotSerializableException { @@ -157,6 +161,7 @@ public boolean mustSyncDeserialization() { .since("1.3") .defaultExpression(new SimpleLiteral<EntityData>(new SimpleEntityData(Entity.class), true)) .before("entitytype") + .supplier(ALL_ENTITY_DATAS::iterator) .parser(new Parser<EntityData>() { @Override public String toString(final EntityData d, final int flags) { @@ -175,7 +180,20 @@ public String toVariableNameString(final EntityData o) { } }).serializer(serializer)); } - + + public static void onRegistrationStop() { + infos.forEach(info -> { + if (SimpleEntityData.class.equals(info.getElementClass())) { + ALL_ENTITY_DATAS.addAll(Arrays.stream(info.codeNames) + .map(input -> SkriptParser.parseStatic(input, new SingleItemIterator<>(info), null)) + .collect(Collectors.toList()) + ); + } else { + ALL_ENTITY_DATAS.add(SkriptParser.parseStatic(info.codeName, new SingleItemIterator<>(info), null)); + } + }); + } + private final static class EntityDataInfo<T extends EntityData<?>> extends SyntaxElementInfo<T> implements LanguageChangeListener { final String codeName; final String[] codeNames; diff --git a/src/main/java/ch/njol/skript/expressions/ExprItems.java b/src/main/java/ch/njol/skript/expressions/ExprItems.java new file mode 100644 index 00000000000..06d099bc7b4 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprItems.java @@ -0,0 +1,151 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.skript.util.Utils; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.iterator.IteratorIterable; +import com.google.common.collect.Iterables; +import org.bukkit.Material; +import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.stream.StreamSupport; + +@Name("Items") +@Description("Items or blocks of a specific type, useful for looping.") +@Examples({ + "loop items of type ore and log:", + "\tblock contains loop-item", + "\tmessage \"Theres at least one %loop-item% in this block\"", + "drop all blocks at the player # drops one of every block at the player" +}) +@Since("1.0 pre-5") +public class ExprItems extends SimpleExpression<ItemType> { + + private static final ItemType[] ALL_BLOCKS = Arrays.stream(Material.values()) + .filter(Material::isBlock) + .map(ItemType::new) + .toArray(ItemType[]::new); + + static { + Skript.registerExpression(ExprItems.class, ItemType.class, ExpressionType.COMBINED, + "[all [[of] the]|the] block[[ ]type]s", + "every block[[ ]type]", + "[all [[of] the]|the|every] block[s] of type[s] %itemtypes%", + "[all [[of] the]|the|every] item[s] of type[s] %itemtypes%" + ); + } + + @Nullable + private Expression<ItemType> itemTypeExpr; + private boolean items; + private ItemType[] buffer = null; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + items = matchedPattern == 3; + itemTypeExpr = matchedPattern == 0 || matchedPattern == 1 ? null : (Expression<ItemType>) exprs[0]; + if (itemTypeExpr instanceof Literal) { + for (ItemType itemType : ((Literal<ItemType>) itemTypeExpr).getAll()) + itemType.setAll(true); + } + return true; + } + + @Override + @Nullable + protected ItemType[] get(Event event) { + if (buffer != null) + return buffer; + List<ItemType> items = new ArrayList<>(); + iterator(event).forEachRemaining(items::add); + ItemType[] itemTypes = items.toArray(new ItemType[0]); + if (itemTypeExpr instanceof Literal) + buffer = itemTypes; + return itemTypes; + } + + @Override + @Nullable + @SuppressWarnings("unchecked") + public Iterator<ItemType> iterator(Event event) { + if (!items && itemTypeExpr == null) + return Arrays.stream(ALL_BLOCKS) + .map(ItemType::clone) + .iterator(); + + Iterable<ItemStack> itemStackIterable = Iterables.concat(itemTypeExpr.stream(event) + .map(ItemType::getAll) + .toArray(Iterable[]::new)); + + if (items) { + return StreamSupport.stream(itemStackIterable.spliterator(), false) + .map(ItemType::new) + .iterator(); + } else { + return StreamSupport.stream(itemStackIterable.spliterator(), false) + .filter(itemStack -> itemStack.getType().isBlock()) + .map(ItemType::new) + .iterator(); + } + } + + @Override + public Class<? extends ItemType> getReturnType() { + return ItemType.class; + } + + @Override + public boolean isSingle() { + return false; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "all of the " + (items ? "items" : "blocks") + (itemTypeExpr != null ? " of type " + itemTypeExpr.toString(event, debug) : ""); + } + + @Override + public boolean isLoopOf(String string) { + if (items) { + return string.equals("item"); + } else { + return string.equals("block"); + } + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprSets.java b/src/main/java/ch/njol/skript/expressions/ExprSets.java index 0bc5e7243f3..1652354d1e4 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprSets.java +++ b/src/main/java/ch/njol/skript/expressions/ExprSets.java @@ -18,18 +18,8 @@ */ package ch.njol.skript.expressions; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.NoSuchElementException; - -import org.bukkit.Material; -import org.bukkit.event.Event; -import org.bukkit.inventory.ItemStack; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; -import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; @@ -39,152 +29,81 @@ import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; -import ch.njol.skript.util.Color; -import ch.njol.skript.util.SkriptColor; +import ch.njol.skript.util.Utils; import ch.njol.util.Kleenean; -import ch.njol.util.NullableChecker; -import ch.njol.util.coll.iterator.ArrayIterator; -import ch.njol.util.coll.iterator.CheckedIterator; -import ch.njol.util.coll.iterator.IteratorIterable; +import com.google.common.collect.Lists; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.Iterator; +import java.util.List; +import java.util.function.Supplier; @Name("Sets") -@Description("Collection sets of items or blocks of a specific type or colours, useful for looping.") +@Description("Returns a list of all the values of a type; useful for looping.") @Examples({ - "loop items of type ore and log:", - "\tblock contains loop-item", - "\tmessage \"Theres at least one %loop-item% in this block\"", - "drop all blocks at the player # drops one of every block at the player" + "loop all attribute types:", + "\tset loop-value attribute of player to 10", + "\tmessage \"Set attribute %loop-value% to 10!\"" }) -@Since("<i>unknown</i> (before 1.4.2), INSERT VERSION (colors)") +@Since("INSERT VERSION") public class ExprSets extends SimpleExpression<Object> { static { Skript.registerExpression(ExprSets.class, Object.class, ExpressionType.COMBINED, - "[(all [[of] the]|the|every)] item(s|[ ]types)", "[(all [[of] the]|the)] items of type[s] %itemtypes%", - "[(all [[of] the]|the|every)] block(s|[ ]types)", "[(all [[of] the]|the)] blocks of type[s] %itemtypes%", - "([all [[of] the]] colo[u]rs|(the|every) colo[u]r)"); + "[all [[of] the]|the|every] %*classinfo%" + ); } + private ClassInfo<?> classInfo; @Nullable - private Expression<ItemType> types; - private int pattern = -1; + private Supplier<? extends Iterator<?>> supplier; - @SuppressWarnings("unchecked") @Override - public boolean init(Expression<?>[] vars, int matchedPattern, Kleenean isDelayed, ParseResult parser) { - if (vars.length > 0) - types = (Expression<ItemType>) vars[0]; - pattern = matchedPattern; - if (types instanceof Literal) { - for (ItemType type : ((Literal<ItemType>) types).getAll()) - type.setAll(true); + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) { + // This check makes sure that "color" is not a valid pattern, and the type the user inputted has to be plural, unless it's "every %classinfo%" + boolean plural = Utils.getEnglishPlural(parser.expr).getSecond(); + if (!plural && !parser.expr.startsWith("every")) + return false; + + classInfo = ((Literal<ClassInfo<?>>) exprs[0]).getSingle(); + supplier = classInfo.getSupplier(); + if (supplier == null) { + Skript.error("You cannot get all values of type '" + classInfo.getName().getSingular() + "'"); + return false; } return true; } - private Object[] buffer = null; - @Override protected Object[] get(Event event) { - if (buffer != null) - return buffer; - List<Object> elements = new ArrayList<>(); - for (Object element : new IteratorIterable<>(iterator(event))) - elements.add(element); - if (types instanceof Literal) - return buffer = elements.toArray(); - return elements.toArray(); + assert supplier != null; + Iterator<?> iterator = supplier.get(); + List<?> elements = Lists.newArrayList(iterator); + return elements.toArray(new Object[0]); } @Override @Nullable - public Iterator<Object> iterator(Event event) { - if (pattern == 4) - return new ArrayIterator<>(SkriptColor.values()); - if (pattern == 0 || pattern == 3) - return new Iterator<Object>() { - - private final Iterator<Material> iter = new ArrayIterator<>(Material.values()); - - @Override - public boolean hasNext() { - return iter.hasNext(); - } - - @Override - public ItemStack next() { - return new ItemStack(iter.next()); - } - - @Override - public void remove() {} - - }; - Iterator<ItemType> it = new ArrayIterator<>(types.getArray(event)); - if (!it.hasNext()) - return null; - Iterator<Object> iter; - iter = new Iterator<Object>() { - - Iterator<ItemStack> current = it.next().getAll().iterator(); - - @Override - public boolean hasNext() { - while (!current.hasNext() && it.hasNext()) { - current = it.next().getAll().iterator(); - } - return current.hasNext(); - } - - @Override - public ItemStack next() { - if (!hasNext()) - throw new NoSuchElementException(); - return current.next(); - } - - @Override - public void remove() {} - - }; - - return new CheckedIterator<Object>(iter, new NullableChecker<Object>() { - @Override - public boolean check(@Nullable Object ob) { - if (ob == null) - return false; - if (ob instanceof ItemStack) - if (!((ItemStack) ob).getType().isBlock()) - return false; - return true; - } - }); - } - - @Override - public Class<? extends Object> getReturnType() { - if (pattern == 4) - return Color.class; - return ItemStack.class; - } - - @Override - public String toString(@Nullable Event event, boolean debug) { - if (event == null) - return "sets of data. Pattern " + pattern; - if (pattern == 4) - return "colours"; - return (pattern < 2 ? "blocks" : "items") + (types != null ? " of type" + (types.isSingle() ? "" : "s") + " " + types.toString(event, debug) : ""); + public Iterator<?> iterator(Event event) { + assert supplier != null; + return supplier.get(); } - + @Override public boolean isSingle() { return false; } - + @Override - public boolean isLoopOf(String s) { - return pattern == 4 && (s.equalsIgnoreCase("color") || s.equalsIgnoreCase("colour"))|| pattern >= 2 && s.equalsIgnoreCase("block") || pattern < 2 && s.equalsIgnoreCase("item"); + public Class<?> getReturnType() { + return classInfo.getC(); } - + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "all of the " + classInfo.getName().getPlural(); + } + } diff --git a/src/main/java/ch/njol/skript/registrations/Classes.java b/src/main/java/ch/njol/skript/registrations/Classes.java index 5f1bc476be9..afbfbe825ba 100644 --- a/src/main/java/ch/njol/skript/registrations/Classes.java +++ b/src/main/java/ch/njol/skript/registrations/Classes.java @@ -38,6 +38,7 @@ import java.util.regex.Pattern; import ch.njol.skript.command.Commands; +import ch.njol.skript.entity.EntityData; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Chunk; @@ -128,6 +129,8 @@ public static void onRegistrationsStop() { if (s != null) Variables.yggdrasil.registerClassResolver(s); } + + EntityData.onRegistrationStop(); } /** From 7f74c54d13d0c611d952db881c278712484b4561 Mon Sep 17 00:00:00 2001 From: sovdee <ethansovde@gmail.com> Date: Sun, 12 Feb 2023 03:39:10 -0500 Subject: [PATCH 236/619] Create ExprBreakSpeed.java (#5399) --- .../skript/expressions/ExprBreakSpeed.java | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprBreakSpeed.java diff --git a/src/main/java/ch/njol/skript/expressions/ExprBreakSpeed.java b/src/main/java/ch/njol/skript/expressions/ExprBreakSpeed.java new file mode 100644 index 00000000000..2c59e593fef --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprBreakSpeed.java @@ -0,0 +1,101 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.ArrayList; + +@Name("Block Break Speed") +@Description( + "Gets the speed at which the given player would break this block, taking into account tools, potion effects, " + + "whether or not the player is in water, enchantments, etc. The returned value is the amount of progress made in " + + "breaking the block each tick. When the total breaking progress reaches 1.0, the block is broken. Note that the " + + "break speed can change in the course of breaking a block, e.g. if a potion effect is applied or expires, or the " + + "player jumps/enters water.") +@Examples({ + "on left click using diamond pickaxe:", + "\tevent-block is set", + "\tsend \"Break Speed: %break speed for player%\" to player" +}) +@Since("INSERT VERSION") +@RequiredPlugins("1.17+") +public class ExprBreakSpeed extends SimpleExpression<Float> { + + static { + if (Skript.methodExists(Block.class, "getBreakSpeed", Player.class)) { + Skript.registerExpression(ExprBreakSpeed.class, Float.class, ExpressionType.COMBINED, + "[the] break speed[s] [of %blocks%] [for %players%]", + "%block%'[s] break speed[s] [for %players%]" + ); + } + } + + private Expression<Block> blocks; + private Expression<Player> players; + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + blocks = (Expression<Block>) exprs[0]; + players = (Expression<Player>) exprs[1]; + return true; + } + + @Override + @Nullable + protected Float[] get(Event event) { + ArrayList<Float> speeds = new ArrayList<>(); + for (Block block : this.blocks.getArray(event)) { + for (Player player : this.players.getArray(event)) { + speeds.add(block.getBreakSpeed(player)); + } + } + + return speeds.toArray(new Float[0]); + } + + @Override + public boolean isSingle() { + return blocks.isSingle() && players.isSingle(); + } + + @Override + public Class<? extends Float> getReturnType() { + return Float.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "break speed of " + blocks.toString(event, debug) + " for " + players.toString(event, debug); + } +} From 608c09e1d4e14df47dffb62f3d3a30d035a14539 Mon Sep 17 00:00:00 2001 From: Kiip <25848425+kiip1@users.noreply.github.com> Date: Mon, 13 Feb 2023 23:36:37 +0100 Subject: [PATCH 237/619] Improve sound effects (#5249) --- .../skript/classes/data/BukkitClasses.java | 3 +- .../ch/njol/skript/effects/EffPlaySound.java | 264 +++++++----------- .../ch/njol/skript/effects/EffStopSound.java | 153 +++++----- 3 files changed, 191 insertions(+), 229 deletions(-) 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 f8621ea7bd8..513c97b63e5 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -1395,8 +1395,7 @@ public String toVariableNameString(FireworkEffect effect) { .name("Sound Category") .description("The category of a sound, they are used for sound options of Minecraft. " + "See the <a href='effects.html#EffPlaySound'>play sound</a> and <a href='effects.html#EffStopSound'>stop sound</a> effects.") - .since("2.4") - .requiredPlugins("Minecraft 1.11 or newer")); + .since("2.4")); if (Skript.classExists("org.bukkit.entity.Panda$Gene")) { Classes.registerClass(new EnumClassInfo<>(Gene.class, "gene", "genes") diff --git a/src/main/java/ch/njol/skript/effects/EffPlaySound.java b/src/main/java/ch/njol/skript/effects/EffPlaySound.java index 8375dafb18d..2d0d5cc0e5b 100644 --- a/src/main/java/ch/njol/skript/effects/EffPlaySound.java +++ b/src/main/java/ch/njol/skript/effects/EffPlaySound.java @@ -18,27 +18,26 @@ */ package ch.njol.skript.effects; -import java.util.Locale; -import java.util.regex.Pattern; - -import org.bukkit.Location; -import org.bukkit.Sound; -import org.bukkit.SoundCategory; -import org.bukkit.World; -import org.bukkit.entity.Player; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.RequiredPlugins; import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Effect; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; +import org.bukkit.Location; +import org.bukkit.Sound; +import org.bukkit.SoundCategory; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Locale; +import java.util.regex.Pattern; @Name("Play Sound") @Description({"Plays a sound at given location for everyone or just for given players, or plays a sound to specified players. " + @@ -47,186 +46,139 @@ "are supported. Playing resource pack sounds are supported too. The sound category is 'master' by default. ", "", "Please note that sound names can get changed in any Minecraft or Spigot version, or even removed from Minecraft itself."}) -@Examples({"play sound \"block.note_block.pling\" # It is block.note.pling in 1.12.2", - "play sound \"entity.experience_orb.pickup\" with volume 0.5 to the player", - "play sound \"custom.music.1\" in jukebox category at {speakerBlock}"}) +@Examples({ + "play sound \"block.note_block.pling\" # It is block.note.pling in 1.12.2", + "play sound \"entity.experience_orb.pickup\" with volume 0.5 to the player", + "play sound \"custom.music.1\" in jukebox category at {speakerBlock}" +}) @Since("2.2-dev28, 2.4 (sound categories)") -@RequiredPlugins("Minecraft 1.11+ (sound categories)") public class EffPlaySound extends Effect { - private static final boolean SOUND_CATEGORIES_EXIST = Skript.classExists("org.bukkit.SoundCategory"); - private static final Pattern SOUND_VALID_PATTERN = Pattern.compile("[a-z0-9\\/:._-]+"); // Minecraft only accepts these characters + private static final Pattern KEY_PATTERN = Pattern.compile("([a-z0-9._-]+:)?[a-z0-9/._-]+"); static { - if (SOUND_CATEGORIES_EXIST) { - Skript.registerEffect(EffPlaySound.class, - "play sound[s] %strings% [(in|from) %-soundcategory%] " + - "[(at|with) volume %-number%] [(and|at|with) pitch %-number%] at %locations% [(to|for) %-players%]", - "play sound[s] %strings% [(in|from) %-soundcategory%] " + - "[(at|with) volume %-number%] [(and|at|with) pitch %-number%] [(to|for) %players%] [(at|from) %-locations%]"); - } else { - Skript.registerEffect(EffPlaySound.class, - "play sound[s] %strings% [(at|with) volume %-number%] " + - "[(and|at|with) pitch %-number%] at %locations% [(to|for) %-players%]", - "play sound[s] %strings% [(at|with) volume %-number%] " + - "[(and|at|with) pitch %-number%] [(to|for) %players%] [(at|from) %-locations%]"); - } + Skript.registerEffect(EffPlaySound.class, + "play sound[s] %strings% [(in|from) %-soundcategory%] " + + "[(at|with) volume %-number%] [(and|at|with) pitch %-number%] at %locations% [(to|for) %-players%]", + "play sound[s] %strings% [(in|from) %-soundcategory%] " + + "[(at|with) volume %-number%] [(and|at|with) pitch %-number%] [(to|for) %players%] [(at|from) %-locations%]" + ); } - @SuppressWarnings("null") + @SuppressWarnings("NotNullFieldNotInitialized") private Expression<String> sounds; @Nullable private Expression<SoundCategory> category; @Nullable - private Expression<Number> volume, pitch; + private Expression<Number> volume; + @Nullable + private Expression<Number> pitch; @Nullable private Expression<Location> locations; @Nullable private Expression<Player> players; @Override - @SuppressWarnings({"unchecked", "null"}) + @SuppressWarnings("unchecked") public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { sounds = (Expression<String>) exprs[0]; - if (SOUND_CATEGORIES_EXIST) { - category = (Expression<SoundCategory>) exprs[1]; - volume = (Expression<Number>) exprs[2]; - pitch = (Expression<Number>) exprs[3]; - if (matchedPattern == 0) { - locations = (Expression<Location>) exprs[4]; - players = (Expression<Player>) exprs[5]; - } else { - players = (Expression<Player>) exprs[4]; - locations = (Expression<Location>) exprs[5]; - } + category = (Expression<SoundCategory>) exprs[1]; + volume = (Expression<Number>) exprs[2]; + pitch = (Expression<Number>) exprs[3]; + if (matchedPattern == 0) { + locations = (Expression<Location>) exprs[4]; + players = (Expression<Player>) exprs[5]; } else { - volume = (Expression<Number>) exprs[1]; - pitch = (Expression<Number>) exprs[2]; - if (matchedPattern == 0) { - locations = (Expression<Location>) exprs[3]; - players = (Expression<Player>) exprs[4]; - } else { - players = (Expression<Player>) exprs[3]; - locations = (Expression<Location>) exprs[4]; - } + players = (Expression<Player>) exprs[4]; + locations = (Expression<Location>) exprs[5]; } + return true; } @Override - @SuppressWarnings("null") - protected void execute(Event e) { - Object category = null; - if (SOUND_CATEGORIES_EXIST) { - category = SoundCategory.MASTER; - if (this.category != null) { - category = this.category.getSingle(e); - if (category == null) - return; - } - } - float volume = 1, pitch = 1; - if (this.volume != null) { - Number volumeNumber = this.volume.getSingle(e); - if (volumeNumber == null) - return; - volume = volumeNumber.floatValue(); - } - if (this.pitch != null) { - Number pitchNumber = this.pitch.getSingle(e); - if (pitchNumber == null) - return; - pitch = pitchNumber.floatValue(); - } + protected void execute(Event event) { + SoundCategory category = this.category == null ? SoundCategory.MASTER : this.category.getOptionalSingle(event) + .orElse(SoundCategory.MASTER); + float volume = this.volume == null ? 1 : this.volume.getOptionalSingle(event) + .orElse(1) + .floatValue(); + float pitch = this.pitch == null ? 1 : this.pitch.getOptionalSingle(event) + .orElse(1) + .floatValue(); + if (players != null) { if (locations == null) { - for (Player p : players.getArray(e)) - playSound(p, p.getLocation(), sounds.getArray(e), (SoundCategory) category, volume, pitch); - } else { - for (Player p : players.getArray(e)) { - for (Location location : locations.getArray(e)) - playSound(p, location, sounds.getArray(e), (SoundCategory) category, volume, pitch); - } - } - } else { - if (locations != null) { - for (Location location : locations.getArray(e)) - playSound(location, sounds.getArray(e), (SoundCategory) category, volume, pitch); - } - } - } - - private static void playSound(Player p, Location location, String[] sounds, SoundCategory category, float volume, float pitch) { - for (String sound : sounds) { - Sound soundEnum = null; - try { - soundEnum = Sound.valueOf(sound.toUpperCase(Locale.ENGLISH)); - } catch (IllegalArgumentException ignored) {} - if (SOUND_CATEGORIES_EXIST) { - if (soundEnum == null) { - sound = sound.toLowerCase(Locale.ENGLISH); - if (!SOUND_VALID_PATTERN.matcher(sound).matches()) - continue; - p.playSound(location, sound, category, volume, pitch); - } else { - p.playSound(location, soundEnum, category, volume, pitch); + for (Player player : players.getArray(event)) { + SoundReceiver.play(Player::playSound, Player::playSound, player, + player.getLocation(), sounds.getArray(event), category, volume, pitch); } } else { - if (soundEnum == null) { - sound = sound.toLowerCase(Locale.ENGLISH); - if (!SOUND_VALID_PATTERN.matcher(sound).matches()) - continue; - p.playSound(location, sound, volume, pitch); - } else { - p.playSound(location, soundEnum, volume, pitch); + for (Player player : players.getArray(event)) { + for (Location location : locations.getArray(event)) { + SoundReceiver.play(Player::playSound, Player::playSound, player, + location, sounds.getArray(event), category, volume, pitch); + } } } - } - } - - private static void playSound(Location location, String[] sounds, SoundCategory category, float volume, float pitch) { - World w = location.getWorld(); - for (String sound : sounds) { - Sound soundEnum = null; - try { - soundEnum = Sound.valueOf(sound.toUpperCase(Locale.ENGLISH)); - } catch (IllegalArgumentException ignored) {} - if (SOUND_CATEGORIES_EXIST) { - if (soundEnum == null) { - sound = sound.toLowerCase(Locale.ENGLISH); - if (!SOUND_VALID_PATTERN.matcher(sound).matches()) - continue; - w.playSound(location, sound, category, volume, pitch); - } else { - w.playSound(location, soundEnum, category, volume, pitch); - } - } else { - if (soundEnum == null) { - sound = sound.toLowerCase(Locale.ENGLISH); - if (!SOUND_VALID_PATTERN.matcher(sound).matches()) - continue; - w.playSound(location, sound, volume, pitch); - } else { - w.playSound(location, soundEnum, volume, pitch); - } + } else if (locations != null) { + for (Location location : locations.getArray(event)) { + SoundReceiver.play(World::playSound, World::playSound, location.getWorld(), + location, sounds.getArray(event), category, volume, pitch); } } } @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { + StringBuilder builder = new StringBuilder() + .append("play sound ") + .append(sounds.toString(event, debug)); + + if (category != null) + builder.append(" in ").append(category.toString(event, debug)); + + if (volume != null) + builder.append(" with volume ").append(volume.toString(event, debug)); + + if (pitch != null) + builder.append(" with pitch ").append(pitch.toString(event, debug)); + if (locations != null) - return "play sound " + sounds.toString(e, debug) + - (category != null ? " in " + category.toString(e, debug) : "") + - (volume != null ? " at volume " + volume.toString(e, debug) : "") + - (pitch != null ? " at pitch " + pitch.toString(e, debug) : "") + - (locations != null ? " at " + locations.toString(e, debug) : "") + - (players != null ? " for " + players.toString(e, debug) : ""); - else - return "play sound " + sounds.toString(e, debug) + - (volume != null ? " at volume " + volume.toString(e, debug) : "") + - (pitch != null ? " at pitch " + pitch.toString(e, debug) : "") + - (players != null ? " to " + players.toString(e, debug) : ""); + builder.append(" at ").append(locations.toString(event, debug)); + + if (players != null) + builder.append(" to ").append(players.toString(event, debug)); + + return builder.toString(); + } + + @FunctionalInterface + private interface SoundReceiver<T, S> { + void play( + @NotNull T receiver, @NotNull Location location, @NotNull S sound, + @NotNull SoundCategory category, float volume, float pitch + ); + + static <T> void play( + @NotNull SoundReceiver<T, String> stringReceiver, + @NotNull SoundReceiver<T, Sound> soundReceiver, + @NotNull T receiver, @NotNull Location location, @NotNull String[] sounds, + @NotNull SoundCategory category, float volume, float pitch + ) { + for (String sound : sounds) { + try { + Sound enumSound = Sound.valueOf(sound.toUpperCase(Locale.ENGLISH)); + soundReceiver.play(receiver, location, enumSound, category, volume, pitch); + continue; + } catch (IllegalArgumentException ignored) {} + + sound = sound.toLowerCase(Locale.ENGLISH); + if (!KEY_PATTERN.matcher(sound).matches()) + continue; + + stringReceiver.play(receiver, location, sound, category, volume, pitch); + } + } } - } diff --git a/src/main/java/ch/njol/skript/effects/EffStopSound.java b/src/main/java/ch/njol/skript/effects/EffStopSound.java index 9039f1f07da..2f4da1c7861 100644 --- a/src/main/java/ch/njol/skript/effects/EffStopSound.java +++ b/src/main/java/ch/njol/skript/effects/EffStopSound.java @@ -18,14 +18,6 @@ */ package ch.njol.skript.effects; -import java.util.Locale; - -import org.bukkit.Sound; -import org.bukkit.SoundCategory; -import org.bukkit.entity.Player; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; @@ -36,97 +28,116 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; +import org.bukkit.Sound; +import org.bukkit.SoundCategory; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.Locale; +import java.util.regex.Pattern; @Name("Stop Sound") -@Description({"Stops a sound from playing to the specified players. Both Minecraft sound names and " + - "<a href=\"https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Sound.html\">Spigot sound names</a> " + - "are supported. Resource pack sounds are supported too. The sound category is 'master' by default. " + - "A sound can't be stopped from a different category. ", - "", - "Please note that sound names can get changed in any Minecraft or Spigot version, or even removed from Minecraft itself."}) -@Examples({"stop sound \"block.chest.open\" for the player", - "stop playing sounds \"ambient.underwater.loop\" and \"ambient.underwater.loop.additions\" to the player"}) -@Since("2.4") -@RequiredPlugins("Minecraft 1.10.2+, Minecraft 1.11+ (sound categories)") +@Description({ + "Stops specific or all sounds from playing to a group of players. Both Minecraft sound names and " + + "<a href=\"https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Sound.html\">Spigot sound names</a> " + + "are supported. Resource pack sounds are supported too. The sound category is 'master' by default. " + + "A sound can't be stopped from a different category. ", + "", + "Please note that sound names can get changed in any Minecraft or Spigot version, or even removed from Minecraft itself." +}) +@Examples({ + "stop sound \"block.chest.open\" for the player", + "stop playing sounds \"ambient.underwater.loop\" and \"ambient.underwater.loop.additions\" to the player", + "stop all sound for all players", + "stop sound in record category" +}) +@Since("2.4, INSERT VERSION (stop all sounds)") +@RequiredPlugins("MC 1.17.1 (stop all sounds)") public class EffStopSound extends Effect { + + private static final boolean STOP_ALL_SUPPORTED = Skript.methodExists(Player.class, "stopAllSounds"); + private static final Pattern KEY_PATTERN = Pattern.compile("([a-z0-9._-]+:)?[a-z0-9/._-]+"); - private static final boolean SOUND_CATEGORIES_EXIST = Skript.classExists("org.bukkit.SoundCategory"); - + static { - if (Skript.methodExists(Player.class, "stopSound", String.class)) { - if (SOUND_CATEGORIES_EXIST) { - Skript.registerEffect(EffStopSound.class, - "stop sound[s] %strings% [(in|from) %-soundcategory%] [(from playing to|for) %players%]", - "stop playing sound[s] %strings% [(in|from) %-soundcategory%] [(to|for) %players%]"); - } else { - Skript.registerEffect(EffStopSound.class, - "stop sound[s] %strings% [(in|from) %-soundcategory%] [(from playing to|for) %players%]", - "stop playing sound[s] %strings% [(in|from) %-soundcategory%] [(to|for) %players%]"); - } - } + String stopPattern = STOP_ALL_SUPPORTED ? "(all:all sound[s]|sound[s] %strings%)" : "sound[s] %strings%"; + + Skript.registerEffect(EffStopSound.class, + "stop " + stopPattern + " [(in|from) %-soundcategory%] [(from playing to|for) %players%]", + "stop playing sound[s] %strings% [(in|from) %-soundcategory%] [(to|for) %players%]" + ); } - - @SuppressWarnings("null") + + @SuppressWarnings("NotNullFieldNotInitialized") private Expression<String> sounds; @Nullable private Expression<SoundCategory> category; - @SuppressWarnings("null") + @SuppressWarnings("NotNullFieldNotInitialized") private Expression<Player> players; + + private boolean allSounds; @Override - @SuppressWarnings({"unchecked", "null"}) + @SuppressWarnings("unchecked") public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - sounds = (Expression<String>) exprs[0]; - if (SOUND_CATEGORIES_EXIST) { + allSounds = parseResult.hasTag("all"); + if (allSounds) { + category = (Expression<SoundCategory>) exprs[0]; + players = (Expression<Player>) exprs[1]; + } else { + sounds = (Expression<String>) exprs[0]; category = (Expression<SoundCategory>) exprs[1]; players = (Expression<Player>) exprs[2]; - } else { - players = (Expression<Player>) exprs[1]; } + return true; } @Override - protected void execute(Event e) { - Object category = null; - if (SOUND_CATEGORIES_EXIST) { - category = SoundCategory.MASTER; - if (this.category != null) { - category = this.category.getSingle(e); - if (category == null) - return; - } - } - for (String sound : sounds.getArray(e)) { - Sound soundEnum = null; - try { - soundEnum = Sound.valueOf(sound.toUpperCase(Locale.ENGLISH)); - } catch (IllegalArgumentException ignored) {} - if (soundEnum == null) { - if (SOUND_CATEGORIES_EXIST) { - for (Player p : players.getArray(e)) - p.stopSound(sound, (SoundCategory) category); - } else { - for (Player p : players.getArray(e)) - p.stopSound(sound); + protected void execute(Event event) { + // All sounds pattern wants explicitly defined master category + SoundCategory category = this.category == null ? null : this.category.getOptionalSingle(event) + .orElse(allSounds ? null : SoundCategory.MASTER); + Player[] targets = players.getArray(event); + + if (allSounds) { + if (category == null) { + for (Player player : targets) { + player.stopAllSounds(); } } else { - if (SOUND_CATEGORIES_EXIST) { - for (Player p : players.getArray(e)) - p.stopSound(soundEnum, (SoundCategory) category); - } else { - for (Player p : players.getArray(e)) - p.stopSound(soundEnum); + for (Player player : targets) { + player.stopSound(category); + } + } + } else { + for (String sound : sounds.getArray(event)) { + try { + Sound soundEnum = Sound.valueOf(sound.toUpperCase(Locale.ENGLISH)); + for (Player player : targets) { + player.stopSound(soundEnum, category); + } + + continue; + } catch (IllegalArgumentException ignored) { } + + sound = sound.toLowerCase(Locale.ENGLISH); + if (!KEY_PATTERN.matcher(sound).matches()) + continue; + + for (Player player : targets) { + player.stopSound(sound, category); } } } } @Override - public String toString(@Nullable Event e, boolean debug) { - return "stop sound " + sounds.toString(e, debug) + - (category != null ? " in " + category.toString(e, debug) : "") + - " from playing to " + players.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return (allSounds ? "stop all sounds " : "stop sound " + sounds.toString(event, debug)) + + (category != null ? " in " + category.toString(event, debug) : "") + + " from playing to " + players.toString(event, debug); } } From 0670656cc44816ee91c219c55206629b73c485c4 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Wed, 15 Feb 2023 16:54:40 -0700 Subject: [PATCH 238/619] Add a default expression register to property expression (#5272) --- .../expressions/base/PropertyExpression.java | 67 +++++++++++-------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/base/PropertyExpression.java b/src/main/java/ch/njol/skript/expressions/base/PropertyExpression.java index 6f2d2d7ae98..d6c40b28e38 100644 --- a/src/main/java/ch/njol/skript/expressions/base/PropertyExpression.java +++ b/src/main/java/ch/njol/skript/expressions/base/PropertyExpression.java @@ -19,6 +19,7 @@ package ch.njol.skript.expressions.base; import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; import org.skriptlang.skript.lang.converter.Converter; @@ -36,65 +37,77 @@ * Represents an expression which represents a property of another one. Remember to set the expression with {@link #setExpr(Expression)} in * {@link SyntaxElement#init(Expression[], int, Kleenean, ParseResult) init()}. * - * @author Peter Güttinger * @see SimplePropertyExpression * @see #register(Class, Class, String, String) */ public abstract class PropertyExpression<F, T> extends SimpleExpression<T> { - + /** * Registers an expression as {@link ExpressionType#PROPERTY} with the two default property patterns "property of %types%" and "%types%'[s] property" * - * @param c - * @param type - * @param property The name of the property - * @param fromType Should be plural but doesn't have to be + * @param c the PropertyExpression class being registered. + * @param type the main expression type the property is based off of. + * @param property the name of the property. + * @param fromType should be plural to support multiple objects but doesn't have to be. */ - public static <T> void register(final Class<? extends Expression<T>> c, final Class<T> type, final String property, final String fromType) { + public static <T> void register(Class<? extends Expression<T>> c, Class<T> type, String property, String fromType) { Skript.registerExpression(c, type, ExpressionType.PROPERTY, "[the] " + property + " of %" + fromType + "%", "%" + fromType + "%'[s] " + property); } - - @SuppressWarnings("null") + + /** + * Registers an expression as {@link ExpressionType#PROPERTY} with the two default property patterns "property [of %types%]" and "%types%'[s] property" + * This method also makes the expression type optional to force a default expression on the property expression. + * + * @param c the PropertyExpression class being registered. + * @param type the main expression type the property is based off of. + * @param property the name of the property. + * @param fromType should be plural to support multiple objects but doesn't have to be. + */ + public static <T> void registerDefault(Class<? extends Expression<T>> c, Class<T> type, String property, String fromType) { + Skript.registerExpression(c, type, ExpressionType.PROPERTY, "[the] " + property + " [of %" + fromType + "%]", "%" + fromType + "%'[s] " + property); + } + + @Nullable private Expression<? extends F> expr; - + /** * Sets the expression this expression represents a property of. No reference to the expression should be kept. * * @param expr */ - protected final void setExpr(final Expression<? extends F> expr) { + protected final void setExpr(Expression<? extends F> expr) { this.expr = expr; } - + public final Expression<? extends F> getExpr() { return expr; } - + @Override - protected final T[] get(final Event e) { - return get(e, expr.getArray(e)); + protected final T[] get(Event event) { + return get(event, expr.getArray(event)); } - + @Override - public final T[] getAll(final Event e) { - T[] result = get(e, expr.getAll(e)); + public final T[] getAll(Event event) { + T[] result = get(event, expr.getAll(event)); return Arrays.copyOf(result, result.length); } - + /** * Converts the given source object(s) to the correct type. * <p> * Please note that the returned array must neither be null nor contain any null elements! * - * @param e - * @param source + * @param event the event involved at the time of runtime calling. + * @param source the array of the objects from the expressions. * @return An array of the converted objects, which may contain less elements than the source array, but must not be null. * @see Converters#convert(Object[], Class, Converter) */ - protected abstract T[] get(Event e, F[] source); - + protected abstract T[] get(Event event, F[] source); + /** - * @param source + * @param source the array of the objects from the expressions. * @param converter must return instances of {@link #getReturnType()} * @return An array containing the converted values * @throws ArrayStoreException if the converter returned invalid values @@ -109,16 +122,16 @@ protected T[] get(final F[] source, final Converter<? super F, ? extends T> conv public final boolean isSingle() { return expr.isSingle(); } - + @Override public final boolean getAnd() { return expr.getAnd(); } - + @Override public Expression<? extends T> simplify() { expr = expr.simplify(); return this; } - + } From 0f774b2700894d9c5e0a1546661dc8afe3dd714c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Feb 2023 17:32:20 -0700 Subject: [PATCH 239/619] Bump org.bstats:bstats-bukkit from 3.0.0 to 3.0.1 (#5444) Bumps [org.bstats:bstats-bukkit](https://github.com/Bastian/bStats-Metrics) from 3.0.0 to 3.0.1. - [Release notes](https://github.com/Bastian/bStats-Metrics/releases) - [Commits](https://github.com/Bastian/bStats-Metrics/compare/v3.0.0...v3.0.1) --- updated-dependencies: - dependency-name: org.bstats:bstats-bukkit dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 0c4ff493238..84bd6feba8d 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,7 @@ allprojects { dependencies { shadow group: 'io.papermc', name: 'paperlib', version: '1.0.8' - shadow group: 'org.bstats', name: 'bstats-bukkit', version: '3.0.0' + shadow group: 'org.bstats', name: 'bstats-bukkit', version: '3.0.1' implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.19.3-R0.1-SNAPSHOT' implementation group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.700' implementation group: 'com.google.code.findbugs', name: 'findbugs', version: '3.0.1' From a13e401ca0c610d6aa632069be5b8f993080633f Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Wed, 15 Feb 2023 22:35:06 -0700 Subject: [PATCH 240/619] Fix default variables (#5317) --- .../java/ch/njol/skript/lang/Variable.java | 126 +++--- .../ch/njol/skript/lang/VariableString.java | 377 ++++++++++-------- .../ch/njol/skript/registrations/Classes.java | 21 +- .../skript/structures/StructVariables.java | 132 +++++- .../ch/njol/skript/variables/Variables.java | 6 +- .../skript/tests/misc/default variables.sk | 28 ++ 6 files changed, 455 insertions(+), 235 deletions(-) create mode 100644 src/test/skript/tests/misc/default variables.sk diff --git a/src/main/java/ch/njol/skript/lang/Variable.java b/src/main/java/ch/njol/skript/lang/Variable.java index 94bf8f44725..ed40f8b15ea 100644 --- a/src/main/java/ch/njol/skript/lang/Variable.java +++ b/src/main/java/ch/njol/skript/lang/Variable.java @@ -18,6 +18,27 @@ */ package ch.njol.skript.lang; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NoSuchElementException; +import java.util.TreeMap; + +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.comparator.Comparators; +import org.skriptlang.skript.lang.comparator.Relation; +import org.skriptlang.skript.lang.converter.Converters; +import org.skriptlang.skript.lang.script.Script; +import org.skriptlang.skript.lang.script.ScriptWarning; + import ch.njol.skript.Skript; import ch.njol.skript.SkriptAPIException; import ch.njol.skript.SkriptConfig; @@ -26,15 +47,11 @@ import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.classes.Changer.ChangerUtils; import ch.njol.skript.classes.ClassInfo; -import org.skriptlang.skript.lang.comparator.Relation; -import org.skriptlang.skript.lang.script.Script; -import org.skriptlang.skript.lang.script.ScriptWarning; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.skript.registrations.Classes; -import org.skriptlang.skript.lang.comparator.Comparators; -import org.skriptlang.skript.lang.converter.Converters; +import ch.njol.skript.structures.StructVariables.DefaultVariables; import ch.njol.skript.util.StringMode; import ch.njol.skript.util.Utils; import ch.njol.skript.variables.TypeHints; @@ -46,31 +63,18 @@ import ch.njol.util.coll.CollectionUtils; import ch.njol.util.coll.iterator.EmptyIterator; import ch.njol.util.coll.iterator.SingleItemIterator; -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.entity.Player; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; -import java.lang.reflect.Array; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.NoSuchElementException; -import java.util.TreeMap; - -/** - * @author Peter Güttinger - */ public class Variable<T> implements Expression<T> { private final static String SINGLE_SEPARATOR_CHAR = ":"; public final static String SEPARATOR = SINGLE_SEPARATOR_CHAR + SINGLE_SEPARATOR_CHAR; public final static String LOCAL_VARIABLE_TOKEN = "_"; + /** + * Script this variable was created in. + */ + private final Script script; + /** * The name of this variable, excluding the local variable token, but including the list variable token '::*'. */ @@ -92,6 +96,12 @@ private Variable(VariableString name, Class<? extends T>[] types, boolean local, assert name.isSimple() || name.getMode() == StringMode.VARIABLE_NAME; + ParserInstance parser = getParser(); + if (!parser.isActive()) + throw new IllegalStateException("Variable attempted to use constructor without a ParserInstance present at execution."); + + this.script = parser.getCurrentScript(); + this.local = local; this.list = list; @@ -181,14 +191,17 @@ public static <T> Variable<T> newInstance(String name, Class<? extends T>[] type boolean isPlural = name.endsWith(SEPARATOR + "*"); ParserInstance parser = ParserInstance.get(); - Script currentScript = parser.isActive() ? parser.getCurrentScript() : null; - if (currentScript != null - && !SkriptConfig.disableVariableStartingWithExpressionWarnings.value() + if (!parser.isActive()) { + Skript.error("A variable must only be created during parsing time."); + return null; + } + Script currentScript = parser.getCurrentScript(); + if (!SkriptConfig.disableVariableStartingWithExpressionWarnings.value() && !currentScript.suppressesWarning(ScriptWarning.VARIABLE_STARTS_WITH_EXPRESSION) && (isLocal ? name.substring(LOCAL_VARIABLE_TOKEN.length()) : name).startsWith("%")) { Skript.warning("Starting a variable's name with an expression is discouraged ({" + name + "}). " + "You could prefix it with the script's name: " + - "{" + StringUtils.substring(currentScript.getConfig().getFileName(), 0, -3) + "." + name + "}"); + "{" + StringUtils.substring(currentScript.getConfig().getFileName(), 0, -3) + SEPARATOR + name + "}"); } // Check for local variable type hints @@ -286,34 +299,56 @@ public String toString() { } @Override + @SuppressWarnings("unchecked") public <R> Variable<R> getConvertedExpression(Class<R>... to) { return new Variable<>(name, to, local, list, this); } /** * Gets the value of this variable as stored in the variables map. + * This method also checks against default variables. */ @Nullable - public Object getRaw(Event e) { - String n = name.toString(e); - if (n.endsWith(Variable.SEPARATOR + "*") != list) // prevents e.g. {%expr%} where "%expr%" ends with "::*" from returning a Map - return null; - Object val = !list ? convertIfOldPlayer(n, e, Variables.getVariable(n, e, local)) : Variables.getVariable(n, e, local); - if (val == null) - return Variables.getVariable((local ? LOCAL_VARIABLE_TOKEN : "") + name.getDefaultVariableName(), e, false); - return val; + public Object getRaw(Event event) { + DefaultVariables data = script.getData(DefaultVariables.class); + if (data != null) + data.enterScope(); + try { + String name = this.name.toString(event); + + // prevents e.g. {%expr%} where "%expr%" ends with "::*" from returning a Map + if (name.endsWith(Variable.SEPARATOR + "*") != list) + return null; + Object value = !list ? convertIfOldPlayer(name, event, Variables.getVariable(name, event, local)) : Variables.getVariable(name, event, local); + if (value != null) + return value; + + // Check for default variables if value is still null. + if (data == null || !data.hasDefaultVariables()) + return null; + + for (String typeHint : this.name.getDefaultVariableNames(name, event)) { + value = Variables.getVariable(typeHint, event, false); + if (value != null) + return value; + } + } finally { + if (data != null) + data.exitScope(); + } + return null; } - @SuppressWarnings("unchecked") @Nullable - private Object get(Event e) { - Object val = getRaw(e); + @SuppressWarnings("unchecked") + private Object get(Event event) { + Object val = getRaw(event); if (!list) return val; if (val == null) return Array.newInstance(types[0], 0); List<Object> l = new ArrayList<>(); - String name = StringUtils.substring(this.name.toString(e), 0, -1); + String name = StringUtils.substring(this.name.toString(event), 0, -1); for (Entry<String, ?> v : ((Map<String, ?>) val).entrySet()) { if (v.getKey() != null && v.getValue() != null) { Object o; @@ -322,7 +357,7 @@ private Object get(Event e) { else o = v.getValue(); if (o != null) - l.add(convertIfOldPlayer(name + v.getKey(), e, o)); + l.add(convertIfOldPlayer(name + v.getKey(), event, o)); } } return l.toArray(); @@ -333,16 +368,17 @@ private Object get(Event e) { * because the player object inside the variable will be a (kinda) dead variable * as a new player object has been created by the server. */ - @Nullable Object convertIfOldPlayer(String key, Event event, @Nullable Object t){ - if(SkriptConfig.enablePlayerVariableFix.value() && t != null && t instanceof Player){ - Player p = (Player) t; - if(!p.isValid() && p.isOnline()){ + @Nullable + Object convertIfOldPlayer(String key, Event event, @Nullable Object object) { + if (SkriptConfig.enablePlayerVariableFix.value() && object != null && object instanceof Player) { + Player p = (Player) object; + if (!p.isValid() && p.isOnline()) { Player player = Bukkit.getPlayer(p.getUniqueId()); Variables.setVariable(key, player, event, local); return player; } } - return t; + return object; } public Iterator<Pair<String, Object>> variablesIterator(Event e) { diff --git a/src/main/java/ch/njol/skript/lang/VariableString.java b/src/main/java/ch/njol/skript/lang/VariableString.java index 6eea122a90e..feb9085ba78 100644 --- a/src/main/java/ch/njol/skript/lang/VariableString.java +++ b/src/main/java/ch/njol/skript/lang/VariableString.java @@ -18,16 +18,32 @@ */ package ch.njol.skript.lang; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; + +import org.bukkit.ChatColor; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.script.Script; + +import com.google.common.collect.Lists; + import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.expressions.ExprColoured; import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.skript.lang.util.ConvertedExpression; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.skript.log.BlockingLogHandler; import ch.njol.skript.log.RetainingLogHandler; import ch.njol.skript.log.SkriptLogger; import ch.njol.skript.registrations.Classes; +import ch.njol.skript.structures.StructVariables.DefaultVariables; import ch.njol.skript.util.StringMode; import ch.njol.skript.util.Utils; import ch.njol.skript.util.chat.ChatMessages; @@ -37,35 +53,29 @@ import ch.njol.util.StringUtils; import ch.njol.util.coll.CollectionUtils; import ch.njol.util.coll.iterator.SingleItemIterator; -import org.bukkit.ChatColor; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; /** * Represents a string that may contain expressions, and is thus "variable". - * - * @author Peter Güttinger */ public class VariableString implements Expression<String> { - + + private final Script script; private final String orig; - + @Nullable private final Object[] string; + @Nullable private Object[] stringUnformatted; private final boolean isSimple; + @Nullable private final String simple; + @Nullable private final String simpleUnformatted; private final StringMode mode; - + /** * Message components that this string consists of. Only simple parts have * been evaluated here. @@ -74,22 +84,30 @@ public class VariableString implements Expression<String> { /** * Creates a new VariableString which does not contain variables. - * @param s Content for string. + * + * @param input Content for string. */ - private VariableString(String s) { - isSimple = true; - simpleUnformatted = s.replace("%%", "%"); // This doesn't contain variables, so this wasn't done in newInstance! - simple = Utils.replaceChatStyles(simpleUnformatted); + private VariableString(String input) { + this.isSimple = true; + this.simpleUnformatted = input.replace("%%", "%"); // This doesn't contain variables, so this wasn't done in newInstance! + this.simple = Utils.replaceChatStyles(simpleUnformatted); - orig = simple; - string = null; - mode = StringMode.MESSAGE; + this.orig = simple; + this.string = null; + this.mode = StringMode.MESSAGE; - components = new MessageComponent[] {ChatMessages.plainText(simpleUnformatted)}; + ParserInstance parser = getParser(); + if (!parser.isActive()) + throw new IllegalStateException("VariableString attempted to use constructor without a ParserInstance present at execution."); + + this.script = parser.getCurrentScript(); + + this.components = new MessageComponent[] {ChatMessages.plainText(simpleUnformatted)}; } - + /** * Creates a new VariableString which contains variables. + * * @param orig Original string (unparsed). * @param string Objects, some of them are variables. * @param mode String mode. @@ -98,105 +116,45 @@ private VariableString(String orig, Object[] string, StringMode mode) { this.orig = orig; this.string = new Object[string.length]; this.stringUnformatted = new Object[string.length]; - + + ParserInstance parser = getParser(); + if (!parser.isActive()) + throw new IllegalStateException("VariableString attempted to use constructor without a ParserInstance present at execution."); + + this.script = parser.getCurrentScript(); + // Construct unformatted string and components List<MessageComponent> components = new ArrayList<>(string.length); for (int i = 0; i < string.length; i++) { - Object o = string[i]; - if (o instanceof String) { - this.string[i] = Utils.replaceChatStyles((String) o); - components.addAll(ChatMessages.parse((String) o)); + Object object = string[i]; + if (object instanceof String) { + this.string[i] = Utils.replaceChatStyles((String) object); + components.addAll(ChatMessages.parse((String) object)); } else { - this.string[i] = o; + this.string[i] = object; components.add(null); // Not known parse-time } - + // For unformatted string, don't format stuff - this.stringUnformatted[i] = o; + this.stringUnformatted[i] = object; } this.components = components.toArray(new MessageComponent[0]); - + this.mode = mode; - - isSimple = false; - simple = null; - simpleUnformatted = null; + + this.isSimple = false; + this.simple = null; + this.simpleUnformatted = null; } - + /** * Prints errors */ @Nullable - public static VariableString newInstance(String s) { - return newInstance(s, StringMode.MESSAGE); - } - - /** - * Attempts to properly quote a string (e.g. double the double quotations). - * Please note that the string itself will not be surrounded with double quotations. - * @param string The string to properly quote. - * @return The input where all double quotations outside of expressions have been doubled. - */ - public static String quote(String string) { - StringBuilder fixed = new StringBuilder(); - boolean inExpression = false; - for (char c : string.toCharArray()) { - if (c == '%') // If we are entering an expression, quotes should NOT be doubled - inExpression = !inExpression; - if (!inExpression && c == '"') - fixed.append('"'); - fixed.append(c); - } - return fixed.toString(); + public static VariableString newInstance(String input) { + return newInstance(input, StringMode.MESSAGE); } - /** - * Tests whether a string is correctly quoted, i.e. only has doubled double quotes in it. - * Singular double quotes are only allowed between percentage signs. - * - * @param s The string - * @param withQuotes Whether s must be surrounded by double quotes or not - * @return Whether the string is quoted correctly - */ - public static boolean isQuotedCorrectly(String s, boolean withQuotes) { - if (withQuotes && (!s.startsWith("\"") || !s.endsWith("\"") || s.length() < 2)) - return false; - boolean quote = false; - boolean percentage = false; - for (int i = withQuotes ? 1 : 0; i < (withQuotes ? s.length() - 1 : s.length()); i++) { - if (percentage) { - if (s.charAt(i) == '%') - percentage = false; - - continue; - } - - if (quote && s.charAt(i) != '"') - return false; - - if (s.charAt(i) == '"') { - quote = !quote; - } else if (s.charAt(i) == '%') { - percentage = true; - } - } - return !quote; - } - - /** - * Removes quoted quotes from a string. - * - * @param s The string - * @param surroundingQuotes Whether the string has quotes at the start & end that should be removed - * @return The string with double quotes replaced with signle ones and optionally with removed surrounding quotes. - */ - public static String unquote(String s, boolean surroundingQuotes) { - assert isQuotedCorrectly(s, surroundingQuotes); - if (surroundingQuotes) - return s.substring(1, s.length() - 1).replace("\"\"", "\""); - return s.replace("\"\"", "\""); - } - /** * Creates an instance of VariableString by parsing given string. * Prints errors and returns null if it is somehow invalid. @@ -213,7 +171,7 @@ public static VariableString newInstance(String orig, StringMode mode) { Skript.error("The percent sign is used for expressions (e.g. %player%). To insert a '%' type it twice: %%."); return null; } - + // We must not parse color codes yet, as JSON support would be broken :( String s; if (mode != StringMode.VARIABLE_NAME) { @@ -235,9 +193,8 @@ public static VariableString newInstance(String orig, StringMode mode) { } else { s = orig; } - + List<Object> string = new ArrayList<>(n / 2 + 2); // List of strings and expressions - int c = s.indexOf('%'); if (c != -1) { if (c != 0) @@ -300,11 +257,11 @@ public static VariableString newInstance(String orig, StringMode mode) { // Only one string, no variable parts string.add(s); } - + // Check if this isn't actually variable string, and return if (string.size() == 1 && string.get(0) instanceof String) return new VariableString(s); - + Object[] sa = string.toArray(); if (string.size() == 1 && string.get(0) instanceof Expression && ((Expression<?>) string.get(0)).getReturnType() == String.class && @@ -316,6 +273,71 @@ public static VariableString newInstance(String orig, StringMode mode) { return new VariableString(orig, sa, mode); } + /** + * Attempts to properly quote a string (e.g. double the double quotations). + * Please note that the string itself will not be surrounded with double quotations. + * @param string The string to properly quote. + * @return The input where all double quotations outside of expressions have been doubled. + */ + public static String quote(String string) { + StringBuilder fixed = new StringBuilder(); + boolean inExpression = false; + for (char c : string.toCharArray()) { + if (c == '%') // If we are entering an expression, quotes should NOT be doubled + inExpression = !inExpression; + if (!inExpression && c == '"') + fixed.append('"'); + fixed.append(c); + } + return fixed.toString(); + } + + /** + * Tests whether a string is correctly quoted, i.e. only has doubled double quotes in it. + * Singular double quotes are only allowed between percentage signs. + * + * @param s The string + * @param withQuotes Whether s must be surrounded by double quotes or not + * @return Whether the string is quoted correctly + */ + public static boolean isQuotedCorrectly(String s, boolean withQuotes) { + if (withQuotes && (!s.startsWith("\"") || !s.endsWith("\"") || s.length() < 2)) + return false; + boolean quote = false; + boolean percentage = false; + if (withQuotes) + s = s.substring(1, s.length() - 1); + for (char c : s.toCharArray()) { + if (percentage) { + if (c == '%') + percentage = false; + continue; + } + if (quote && c != '"') + return false; + if (c == '"') { + quote = !quote; + } else if (c == '%') { + percentage = true; + } + } + return !quote; + } + + /** + * Removes quoted quotes from a string. + * + * @param s The string + * @param surroundingQuotes Whether the string has quotes at the start & end that should be removed + * @return The string with double quotes replaced with signle ones and optionally with removed surrounding quotes. + */ + public static String unquote(String s, boolean surroundingQuotes) { + assert isQuotedCorrectly(s, surroundingQuotes); + if (surroundingQuotes) + return s.substring(1, s.length() - 1).replace("\"\"", "\""); + return s.replace("\"\"", "\""); + } + /** * Copied from {@code SkriptParser#nextBracket(String, char, char, int, boolean)}, but removed escaping & returns -1 on error. * @@ -367,36 +389,6 @@ public static VariableString[] makeStringsFromQuoted(List<String> args) { return strings; } - /** - * Parses all expressions in the string and returns it. - * If this is a simple string, the event may be null. - * - * @param e Event to pass to the expressions. - * @return The input string with all expressions replaced. - */ - public String toString(@Nullable Event e) { - if (isSimple) { - assert simple != null; - return simple; - } - - if (e == null) { - throw new IllegalArgumentException("Event may not be null in non-simple VariableStrings!"); - } - - Object[] string = this.string; - assert string != null; - StringBuilder b = new StringBuilder(); - for (Object o : string) { - if (o instanceof Expression<?>) { - b.append(Classes.toString(((Expression<?>) o).getArray(e), true, mode)); - } else { - b.append(o); - } - } - return b.toString(); - } - /** * Parses all expressions in the string and returns it. * Does not parse formatting codes! @@ -527,61 +519,116 @@ private static ChatColor getLastColor(CharSequence s) { public String toString() { return toString(null, false); } - + + /** + * Parses all expressions in the string and returns it. + * If this is a simple string, the event may be null. + * + * @param event Event to pass to the expressions. + * @return The input string with all expressions replaced. + */ + public String toString(@Nullable Event event) { + if (isSimple) { + assert simple != null; + return simple; + } + if (event == null) + throw new IllegalArgumentException("Event may not be null in non-simple VariableStrings!"); + + Object[] string = this.string; + assert string != null; + StringBuilder builder = new StringBuilder(); + List<Class<?>> types = new ArrayList<>(); + for (Object object : string) { + if (object instanceof Expression<?>) { + Object[] objects = ((Expression<?>) object).getArray(event); + if (objects != null && objects.length > 0) + types.add(objects[0].getClass()); + builder.append(Classes.toString(objects, true, mode)); + } else { + builder.append(object); + } + } + String complete = builder.toString(); + if (mode == StringMode.VARIABLE_NAME && !types.isEmpty()) { + DefaultVariables data = script.getData(DefaultVariables.class); + if (data != null) + data.add(complete, types.toArray(new Class<?>[0])); + } + return complete; + } + /** - * Use {@link #toString(Event)} to get the actual string + * Use {@link #toString(Event)} to get the actual string. This method is for debugging. */ @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { if (isSimple) { assert simple != null; return '"' + simple + '"'; } Object[] string = this.string; assert string != null; - StringBuilder b = new StringBuilder("\""); - for (Object o : string) { - if (o instanceof Expression) { - b.append("%").append(((Expression<?>) o).toString(e, debug)).append("%"); + StringBuilder builder = new StringBuilder("\""); + for (Object object : string) { + if (object instanceof Expression) { + builder.append("%").append(((Expression<?>) object).toString(event, debug)).append("%"); } else { - b.append(o); + builder.append(object); } } - b.append('"'); - return b.toString(); + builder.append('"'); + return builder.toString(); } - - public String getDefaultVariableName() { + + /** + * Builds all possible default variable type hints based on the super type of the expression. + * + * @return List<String> of all possible super class code names. + */ + public List<String> getDefaultVariableNames(String variableName, Event event) { if (isSimple) { assert simple != null; - return simple; + return Lists.newArrayList(simple, "object"); } Object[] string = this.string; assert string != null; - StringBuilder b = new StringBuilder(); - for (Object o : string) { - if (o instanceof Expression) { - b.append("<") - .append(Classes.getSuperClassInfo(((Expression<?>) o).getReturnType()).getCodeName()) - .append(">"); - } else { - b.append(o); + List<StringBuilder> typeHints = Lists.newArrayList(new StringBuilder()); + DefaultVariables data = script.getData(DefaultVariables.class); + assert data != null : "default variables not present in current script"; + + // Represents the index of which expression in a variable string, example name::%entity%::%object% the index of 0 will be entity. + int hintIndex = 0; + for (Object object : string) { + if (!(object instanceof Expression)) { + typeHints.forEach(builder -> builder.append(object)); + continue; + } + StringBuilder[] current = typeHints.toArray(new StringBuilder[0]); + for (ClassInfo<?> classInfo : Classes.getAllSuperClassInfos(data.get(variableName)[hintIndex])) { + for (StringBuilder builder : current) { + String hint = builder.toString() + "<" + classInfo.getCodeName() + ">"; + typeHints.add(new StringBuilder(hint)); + typeHints.remove(builder); + } } + hintIndex++; } - return b.toString(); + return typeHints.stream().map(builder -> builder.toString()).collect(Collectors.toList()); } - + public boolean isSimple() { return isSimple; } - + public StringMode getMode() { return mode; } - + public VariableString setMode(StringMode mode) { if (this.mode == mode || isSimple) return this; + @SuppressWarnings("resource") BlockingLogHandler h = new BlockingLogHandler().start(); try { VariableString vs = newInstance(orig, mode); diff --git a/src/main/java/ch/njol/skript/registrations/Classes.java b/src/main/java/ch/njol/skript/registrations/Classes.java index afbfbe825ba..8f48370db56 100644 --- a/src/main/java/ch/njol/skript/registrations/Classes.java +++ b/src/main/java/ch/njol/skript/registrations/Classes.java @@ -301,7 +301,7 @@ public static <T> ClassInfo<T> getExactClassInfo(final @Nullable Class<T> c) { * @param c * @return The closest superclass's info */ - @SuppressWarnings({"unchecked", "null"}) + @SuppressWarnings("unchecked") public static <T> ClassInfo<? super T> getSuperClassInfo(final Class<T> c) { assert c != null; checkAllowClassInfoInteraction(); @@ -318,6 +318,25 @@ public static <T> ClassInfo<? super T> getSuperClassInfo(final Class<T> c) { assert false; return null; } + + /** + * Gets all the class info of the given class in closest order to ending on object. This list will never be empty unless <tt>c</tt> is null. + * + * @param c the class to check if assignable from + * @return The closest list of superclass infos + */ + @SuppressWarnings("unchecked") + public static <T> List<ClassInfo<? super T>> getAllSuperClassInfos(Class<T> c) { + assert c != null; + checkAllowClassInfoInteraction(); + List<ClassInfo<? super T>> list = new ArrayList<>(); + for (ClassInfo<?> ci : getClassInfos()) { + if (ci.getC().isAssignableFrom(c)) { + list.add((ClassInfo<? super T>) ci); + } + } + return list; + } /** * Gets a class by its code name diff --git a/src/main/java/ch/njol/skript/structures/StructVariables.java b/src/main/java/ch/njol/skript/structures/StructVariables.java index c5154d8d0b9..d0d49c5a8c7 100644 --- a/src/main/java/ch/njol/skript/structures/StructVariables.java +++ b/src/main/java/ch/njol/skript/structures/StructVariables.java @@ -18,6 +18,26 @@ */ package ch.njol.skript.structures; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Deque; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; +import org.skriptlang.skript.lang.converter.Converters; +import org.skriptlang.skript.lang.entry.EntryContainer; +import org.skriptlang.skript.lang.script.Script; +import org.skriptlang.skript.lang.script.ScriptData; +import org.skriptlang.skript.lang.structure.Structure; + +import com.google.common.collect.ImmutableList; + import ch.njol.skript.Skript; import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.config.EntryNode; @@ -30,21 +50,14 @@ import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.ParseContext; import ch.njol.skript.lang.SkriptParser.ParseResult; -import org.skriptlang.skript.lang.entry.EntryContainer; -import org.skriptlang.skript.lang.structure.Structure; +import ch.njol.skript.lang.Variable; import ch.njol.skript.log.ParseLogHandler; import ch.njol.skript.log.SkriptLogger; import ch.njol.skript.registrations.Classes; -import ch.njol.skript.registrations.Converters; import ch.njol.skript.variables.Variables; import ch.njol.util.NonNullPair; import ch.njol.util.StringUtils; -import org.bukkit.event.Event; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; +import ch.njol.util.coll.CollectionUtils; @Name("Variables") @Description({ @@ -53,9 +66,12 @@ }) @Examples({ "variables:", - "\t{joins} = 0", + "\t{joins} = 0", + "\t{balance::%player%} = 0", "on join:", - "\tadd 1 to {joins}" + "\tadd 1 to {joins}", + "\tmessage \"Your balance is %{balance::%player%}%\"", + "" }) @Since("1.0") public class StructVariables extends Structure { @@ -66,16 +82,71 @@ public class StructVariables extends Structure { Skript.registerStructure(StructVariables.class, "variables"); } - private final List<NonNullPair<String, Object>> variables = new ArrayList<>(); + public static class DefaultVariables implements ScriptData { + + private final Deque<Map<String, Class<?>[]>> hints = new ArrayDeque<>(); + private final List<NonNullPair<String, Object>> variables; + + public DefaultVariables(Collection<NonNullPair<String, Object>> variables) { + this.variables = ImmutableList.copyOf(variables); + } + + @SuppressWarnings("unchecked") + public void add(String variable, Class<?>... hints) { + if (hints == null || hints.length <= 0) + return; + if (CollectionUtils.containsAll(hints, Object.class)) // Ignore useless type hint + return; + this.hints.getFirst().put(variable, hints); + } + + public void enterScope() { + hints.push(new HashMap<>()); + } + + public void exitScope() { + hints.pop(); + } + + /** + * Returns the type hints of a variable. + * Can be null if no type hint was saved. + * + * @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<String, Class<?>[]> map : hints) { + Class<?>[] hints = map.get(variable); + if (hints != null && hints.length > 0) + return hints; + } + return null; + } + + public boolean hasDefaultVariables() { + return !variables.isEmpty(); + } + + /** + * @return an unmodifiable list of all the default variables registered for the script. + */ + @Unmodifiable + public List<NonNullPair<String, Object>> getVariables() { + return variables; + } + } @Override public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult, EntryContainer entryContainer) { - // TODO allow to make these override existing variables SectionNode node = entryContainer.getSource(); node.convertToEntries(0, "="); + + List<NonNullPair<String, Object>> variables = new ArrayList<>(); for (Node n : node) { if (!(n instanceof EntryNode)) { - Skript.error("Invalid line in variables section"); + Skript.error("Invalid line in variables structure"); continue; } @@ -83,9 +154,18 @@ public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResu if (name.startsWith("{") && name.endsWith("}")) name = name.substring(1, name.length() - 1); - String var = name; + if (name.startsWith(Variable.LOCAL_VARIABLE_TOKEN)) { + Skript.error("'" + name + "' cannot be a local variable in default variables structure"); + continue; + } + + if (name.contains("<") || name.contains(">")) { + Skript.error("'" + name + "' cannot have symbol '<' or '>' within the definition"); + continue; + } - name = StringUtils.replaceAll(name, "%(.+)?%", m -> { + String var = name; + name = StringUtils.replaceAll(name, "%(.+?)%", m -> { if (m.group(1).contains("{") || m.group(1).contains("}") || m.group(1).contains("%")) { Skript.error("'" + var + "' is not a valid name for a default variable"); return null; @@ -134,33 +214,41 @@ public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResu continue; } } - variables.add(new NonNullPair<>(name, o)); } + getParser().getCurrentScript().addData(new DefaultVariables(variables)); return true; } @Override public boolean load() { - for (NonNullPair<String, Object> pair : variables) { + DefaultVariables data = getParser().getCurrentScript().getData(DefaultVariables.class); + for (NonNullPair<String, Object> pair : data.getVariables()) { String name = pair.getKey(); - Object o = pair.getValue(); - if (Variables.getVariable(name, null, false) != null) continue; - Variables.setVariable(name, o, null, false); + Variables.setVariable(name, pair.getValue(), null, false); } return true; } + @Override + public void postUnload() { + Script script = getParser().getCurrentScript(); + DefaultVariables data = script.getData(DefaultVariables.class); + for (NonNullPair<String, Object> pair : data.getVariables()) + Variables.setVariable(pair.getKey(), null, null, false); + script.removeData(DefaultVariables.class); + } + @Override public Priority getPriority() { return PRIORITY; } @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { return "variables"; } diff --git a/src/main/java/ch/njol/skript/variables/Variables.java b/src/main/java/ch/njol/skript/variables/Variables.java index aa3808b30e4..6d9706d93f4 100644 --- a/src/main/java/ch/njol/skript/variables/Variables.java +++ b/src/main/java/ch/njol/skript/variables/Variables.java @@ -36,13 +36,13 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.regex.Pattern; -import ch.njol.skript.log.SkriptLogger; import org.bukkit.Bukkit; import org.bukkit.configuration.serialization.ConfigurationSerializable; import org.bukkit.configuration.serialization.ConfigurationSerialization; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.converter.Converters; import ch.njol.skript.Skript; import ch.njol.skript.SkriptAPIException; @@ -53,8 +53,8 @@ import ch.njol.skript.config.Node; import ch.njol.skript.config.SectionNode; import ch.njol.skript.lang.Variable; +import ch.njol.skript.log.SkriptLogger; import ch.njol.skript.registrations.Classes; -import org.skriptlang.skript.lang.converter.Converters; import ch.njol.skript.variables.DatabaseStorage.Type; import ch.njol.skript.variables.SerializedVariable.Value; import ch.njol.util.Closeable; @@ -313,6 +313,8 @@ static Lock getReadLock() { * Returns the internal value of the requested variable. * <p> * <b>Do not modify the returned value!</b> + * <p> + * This does not take into consideration default variables. You must use get methods from {@link ch.njol.skript.lang.Variable} * * @param name * @return an Object for a normal Variable or a Map<String, Object> for a list variable, or null if the variable is not set. diff --git a/src/test/skript/tests/misc/default variables.sk b/src/test/skript/tests/misc/default variables.sk new file mode 100644 index 00000000000..75006862736 --- /dev/null +++ b/src/test/skript/tests/misc/default variables.sk @@ -0,0 +1,28 @@ + +variables: + {testing variables1::%entity%} = 1337 + {testing variables2::%object%} = 1337 + {testing variables3::%entity%::%object%} = 1337 + +on test "default variables": + spawn a pig at spawn of world "world" + assert {testing variables1::%last spawned pig%} is set with "default variable 1 was not set" + assert {testing variables1::%last spawned pig%} is 1337 with "default variable 1 failed: Value with pig = %{testing variables1::%last spawned pig%}%" + + set {_string} to "empty string" + set {_local} to {_string} + assert {_local} is {_string} with "Local variable failed to copy another local variable." + + assert {testing variables2::%{_string}%} is set with "default variable 2 was not set" + assert {testing variables2::%{_string}%} is 1337 with "default variable 2 failed: Value with string = %{testing variables2::%{_string}%}%" + + assert {testing variables3::%last spawned pig%::%{_string}%} is set with "default variable 3 was not set" + assert {testing variables3::%last spawned pig%::%{_string}%} is 1337 with "default variable 3 failed: Value with entity and string = %{testing variables3::%last spawned pig%::%{_string}%}%" + + delete last spawned pig + delete {testing variables1::*}, {testing variables2::*} and {testing variables3::*} + + assert {testing variables1::*}, {testing variables2::*} and {testing variables3::*} are not set with "Failed to delete default variables." + +on script unload: + delete {testing variables1::*}, {testing variables2::*} and {testing variables3::*} From f939b78f75d6f20268e79ea4c6b620e9bc4e82f0 Mon Sep 17 00:00:00 2001 From: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> Date: Thu, 16 Feb 2023 01:44:39 -0600 Subject: [PATCH 241/619] Allow using alises when parsing BlockData (#5436) --- src/main/java/ch/njol/skript/util/BlockUtils.java | 15 +++++++++++++-- src/test/skript/tests/misc/blockdata parser.sk | 5 +++++ 2 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 src/test/skript/tests/misc/blockdata parser.sk diff --git a/src/main/java/ch/njol/skript/util/BlockUtils.java b/src/main/java/ch/njol/skript/util/BlockUtils.java index 92a7675951a..f9292b6b46b 100644 --- a/src/main/java/ch/njol/skript/util/BlockUtils.java +++ b/src/main/java/ch/njol/skript/util/BlockUtils.java @@ -18,6 +18,7 @@ */ package ch.njol.skript.util; +import ch.njol.skript.aliases.Aliases; import ch.njol.skript.aliases.ItemData; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.bukkitutil.block.BlockCompat; @@ -105,8 +106,18 @@ public static BlockData createBlockData(String dataString) { try { return Bukkit.createBlockData(data.startsWith("minecraft:") ? data : "minecraft:" + data); - } catch (IllegalArgumentException ignore) { - return null; + } catch (IllegalArgumentException ignored) { + try { + // we use the original dataString param here as we want the alias before modifications + String alias = dataString.substring(0, dataString.lastIndexOf("[")); + data = data.substring(data.lastIndexOf("[")); + ItemType type = Aliases.parseItemType(alias); + if (type == null) + return null; + return Bukkit.createBlockData(type.getMaterial(), data); + } catch (IllegalArgumentException | StringIndexOutOfBoundsException alsoIgnored) { + return null; + } } } diff --git a/src/test/skript/tests/misc/blockdata parser.sk b/src/test/skript/tests/misc/blockdata parser.sk new file mode 100644 index 00000000000..04f76866326 --- /dev/null +++ b/src/test/skript/tests/misc/blockdata parser.sk @@ -0,0 +1,5 @@ +aliases: + test alias = wheat + +test "blockdata parser using aliases": + assert wheat[age=7] is test alias[age=7] with "Parsing blockdata using an alias gave a different result" From 5105ec954144cb639c7fd118971a0a2afac9b402 Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Thu, 16 Feb 2023 13:44:35 +0300 Subject: [PATCH 242/619] Fix Function NPE (#5449) --- .../lang/function/FunctionReference.java | 11 ++---- .../njol/skript/lang/function/Functions.java | 38 ++++++++++++++++++ .../njol/skript/lang/function/Namespace.java | 39 +++++++++---------- 3 files changed, 60 insertions(+), 28 deletions(-) diff --git a/src/main/java/ch/njol/skript/lang/function/FunctionReference.java b/src/main/java/ch/njol/skript/lang/function/FunctionReference.java index fe0d355d082..a12bd8f735f 100644 --- a/src/main/java/ch/njol/skript/lang/function/FunctionReference.java +++ b/src/main/java/ch/njol/skript/lang/function/FunctionReference.java @@ -123,10 +123,8 @@ public boolean validateFunction(boolean first) { function = null; SkriptLogger.setNode(node); Skript.debug("Validating function " + functionName); - Signature<?> sign = Functions.getLocalSignature(functionName, script); - if (sign == null) - sign = Functions.getGlobalSignature(functionName); - + Signature<?> sign = Functions.getSignature(functionName, script); + // Check if the requested function exists if (sign == null) { if (first) { @@ -269,10 +267,7 @@ public boolean resetReturnValue() { protected T[] execute(Event e) { // If needed, acquire the function reference if (function == null) - function = (Function<? extends T>) Functions.getLocalFunction(functionName, script); - - if (function == null) - function = (Function<? extends T>) Functions.getGlobalFunction(functionName); + function = (Function<? extends T>) Functions.getFunction(functionName, script); if (function == null) { // It might be impossible to resolve functions in some cases! Skript.error("Couldn't resolve call for '" + functionName + "'."); 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 2a0f2087e7b..bc19f2bbf72 100644 --- a/src/main/java/ch/njol/skript/lang/function/Functions.java +++ b/src/main/java/ch/njol/skript/lang/function/Functions.java @@ -274,6 +274,26 @@ public static Function<?> getLocalFunction(String name, String script) { return function; } + /** + * Gets a local function, if it doesn't exist it'll fall back to a global function, + * if it exists. Note that even if function exists in scripts, + * it might not have been parsed yet. If you want to check for existence, + * then use {@link #getSignature(String, String)}. + * + * @param name Name of function. + * @param script The script where the function is declared in. Used to get local functions. + * @return Function, or null if it does not exist. + */ + @Nullable + public static Function<?> getFunction(String name, @Nullable String script) { + if (script == null) + return getGlobalFunction(name); + Function<?> function = getLocalFunction(name, script); + if (function == null) + return getGlobalFunction(name); + return function; + } + /** * Gets a signature of function with given name. * @@ -318,6 +338,24 @@ public static Signature<?> getLocalSignature(String name, String script) { return signature; } + /** + * Gets a signature of local function with the given name, if no signature was found, + * it will fall back to a global function. + * + * @param name Name of function. + * @param script The script where the function is declared in. Used to get local functions. + * @return Signature, or null if function does not exist. + */ + @Nullable + public static Signature<?> getSignature(String name, @Nullable String script) { + if (script == null) + return getGlobalSignature(name); + Signature<?> signature = getLocalSignature(name, script); + if (signature == null) + return getGlobalSignature(name); + return signature; + } + @Nullable public static Namespace getScriptNamespace(String script) { return namespaces.get(new Namespace.Key(Namespace.Origin.SCRIPT, script)); diff --git a/src/main/java/ch/njol/skript/lang/function/Namespace.java b/src/main/java/ch/njol/skript/lang/function/Namespace.java index fecbaf07998..7834e7f8899 100644 --- a/src/main/java/ch/njol/skript/lang/function/Namespace.java +++ b/src/main/java/ch/njol/skript/lang/function/Namespace.java @@ -21,6 +21,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import org.eclipse.jdt.annotation.Nullable; @@ -50,46 +51,44 @@ public enum Origin { public static class Key { private final Origin origin; - - private final String name; - public Key(Origin origin, String name) { + @Nullable + private final String scriptName; + + public Key(Origin origin, @Nullable String scriptName) { super(); this.origin = origin; - this.name = name; + this.scriptName = scriptName; } public Origin getOrigin() { return origin; } - - public String getName() { - return name; + + @Nullable + public String getScriptName() { + return scriptName; } @Override public int hashCode() { - int prime = 31; - int result = 1; - result = prime * result + name.hashCode(); - result = prime * result + origin.hashCode(); + int result = origin.hashCode(); + result = 31 * result + (scriptName != null ? scriptName.hashCode() : 0); return result; } @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) + public boolean equals(Object object) { + if (this == object) return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - Key other = (Key) obj; - if (!name.equals(other.name)) + if (object == null || getClass() != object.getClass()) return false; + + Key other = (Key) object; + if (origin != other.origin) return false; - return true; + return Objects.equals(scriptName, other.scriptName); } } From 58ba16311b136de3e0a675991b7e7c5eb5da5ef6 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Thu, 16 Feb 2023 08:42:40 -0500 Subject: [PATCH 243/619] Further Structure API Enhancements (#5321) Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Co-authored-by: TheLimeGlass <seantgrover@gmail.com> --- .../java/ch/njol/skript/ScriptLoader.java | 106 +++++++++--------- .../java/ch/njol/skript/SkriptCommand.java | 11 +- .../java/ch/njol/skript/aliases/Aliases.java | 8 -- .../skript/lang/parser/ParserInstance.java | 22 ++-- .../njol/skript/structures/StructAliases.java | 6 - .../njol/skript/structures/StructCommand.java | 10 +- .../njol/skript/structures/StructOptions.java | 71 +++++------- .../skriptlang/skript/lang/script/Script.java | 78 +++++++++---- .../skript/lang/script/ScriptEvent.java | 64 +++++++++++ .../lang/script/ScriptEventHandler.java | 46 -------- .../syntaxes/structures/StructCommand.sk | 18 +++ 11 files changed, 246 insertions(+), 194 deletions(-) create mode 100644 src/main/java/org/skriptlang/skript/lang/script/ScriptEvent.java delete mode 100644 src/main/java/org/skriptlang/skript/lang/script/ScriptEventHandler.java create mode 100644 src/test/skript/tests/syntaxes/structures/StructCommand.sk diff --git a/src/main/java/ch/njol/skript/ScriptLoader.java b/src/main/java/ch/njol/skript/ScriptLoader.java index fdca5b339ce..7c48fd5a104 100644 --- a/src/main/java/ch/njol/skript/ScriptLoader.java +++ b/src/main/java/ch/njol/skript/ScriptLoader.java @@ -23,21 +23,19 @@ import ch.njol.skript.config.SectionNode; import ch.njol.skript.config.SimpleNode; import ch.njol.skript.events.bukkit.PreScriptLoadEvent; -import ch.njol.skript.log.SkriptLogger; -import org.skriptlang.skript.lang.script.Script; import ch.njol.skript.lang.Section; import ch.njol.skript.lang.SkriptParser; import ch.njol.skript.lang.Statement; import ch.njol.skript.lang.TriggerItem; import ch.njol.skript.lang.TriggerSection; import ch.njol.skript.lang.parser.ParserInstance; -import org.skriptlang.skript.lang.structure.Structure; import ch.njol.skript.lang.util.ContextlessEvent; import ch.njol.skript.log.CountingLogHandler; import ch.njol.skript.log.LogEntry; import ch.njol.skript.log.RetainingLogHandler; +import ch.njol.skript.log.SkriptLogger; import ch.njol.skript.sections.SecLoop; -import ch.njol.skript.structures.StructOptions; +import ch.njol.skript.structures.StructOptions.OptionsData; import ch.njol.skript.util.ExceptionUtils; import ch.njol.skript.util.SkriptColor; import ch.njol.skript.util.Task; @@ -51,6 +49,8 @@ import org.bukkit.Bukkit; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.script.Script; +import org.skriptlang.skript.lang.structure.Structure; import java.io.File; import java.io.FileFilter; @@ -60,7 +60,6 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; @@ -467,7 +466,7 @@ public static CompletableFuture<ScriptInfo> loadScripts(File file, OpenCloseable * @param openCloseable An {@link OpenCloseable} that will be called before and after * each individual script load (see {@link #makeFuture(Supplier, OpenCloseable)}). */ - public static CompletableFuture<ScriptInfo> loadScripts(Collection<File> files, OpenCloseable openCloseable) { + public static CompletableFuture<ScriptInfo> loadScripts(Set<File> files, OpenCloseable openCloseable) { return loadScripts(files.stream() .sorted() .map(ScriptLoader::loadStructures) @@ -493,7 +492,7 @@ private static CompletableFuture<ScriptInfo> loadScripts(List<Config> configs, O ScriptInfo scriptInfo = new ScriptInfo(); - List<Script> scripts = new ArrayList<>(); + List<NonNullPair<Script, List<Structure>>> scripts = new ArrayList<>(); List<CompletableFuture<Void>> scriptInfoFutures = new ArrayList<>(); for (Config config : configs) { @@ -501,10 +500,9 @@ private static CompletableFuture<ScriptInfo> loadScripts(List<Config> configs, O throw new NullPointerException(); CompletableFuture<Void> future = makeFuture(() -> { - Script script = new Script(config); - ScriptInfo info = loadScript(script); - scripts.add(script); - scriptInfo.add(info); + NonNullPair<Script, List<Structure>> pair = loadScript(config); + scripts.add(pair); + scriptInfo.add(new ScriptInfo(1, pair.getSecond().size())); return null; }, openCloseable); @@ -521,13 +519,13 @@ private static CompletableFuture<ScriptInfo> loadScripts(List<Config> configs, O openCloseable.open(); scripts.stream() - .flatMap(script -> { // Flatten each entry down to a stream of Config-Structure pairs - return script.getStructures().stream() - .map(structure -> new NonNullPair<>(script, structure)); + .flatMap(pair -> { // Flatten each entry down to a stream of Script-Structure pairs + return pair.getSecond().stream() + .map(structure -> new NonNullPair<>(pair, structure)); }) .sorted(Comparator.comparing(pair -> pair.getSecond().getPriority())) .forEach(pair -> { - Script script = pair.getFirst(); + Script script = pair.getFirst().getFirst(); Structure structure = pair.getSecond(); parser.setActive(script); @@ -536,11 +534,11 @@ private static CompletableFuture<ScriptInfo> loadScripts(List<Config> configs, O try { if (!structure.preLoad()) - script.getStructures().remove(structure); + pair.getFirst().getSecond().remove(structure); } catch (Exception e) { //noinspection ThrowableNotThrown Skript.exception(e, "An error occurred while trying to load a Structure."); - script.getStructures().remove(structure); + pair.getFirst().getSecond().remove(structure); } }); @@ -550,9 +548,9 @@ private static CompletableFuture<ScriptInfo> loadScripts(List<Config> configs, O // However, this is not possible right now as reworks in multiple areas will be needed. // For example, the "Commands" class still uses a static list for currentArguments that is cleared between loads. // Until these reworks happen, limiting main loading to asynchronous (not parallel) is the only choice we have. - for (Script script : scripts) { - parser.setActive(script); - script.getStructures().removeIf(structure -> { + for (NonNullPair<Script, List<Structure>> pair : scripts) { + parser.setActive(pair.getFirst()); + pair.getSecond().removeIf(structure -> { parser.setCurrentStructure(structure); parser.setNode(structure.getEntryContainer().getSource()); try { @@ -567,9 +565,9 @@ private static CompletableFuture<ScriptInfo> loadScripts(List<Config> configs, O parser.setInactive(); - for (Script script : scripts) { - parser.setActive(script); - script.getStructures().removeIf(structure -> { + for (NonNullPair<Script, List<Structure>> pair : scripts) { + parser.setActive(pair.getFirst()); + pair.getSecond().removeIf(structure -> { parser.setCurrentStructure(structure); parser.setNode(structure.getEntryContainer().getSource()); try { @@ -595,22 +593,16 @@ private static CompletableFuture<ScriptInfo> loadScripts(List<Config> configs, O } /** - * Loads one script. Only for internal use, as this doesn't register/update event handlers. - * @param script The script to be loaded. - * @return Statistics for the script loaded. + * Creates a script and loads the provided config into it. + * @param config The config to load into a script. + * @return The script that was loaded. */ // Whenever you call this method, make sure to also call PreScriptLoadEvent - private static ScriptInfo loadScript(@Nullable Script script) { - if (script == null) { // Something bad happened, hopefully got logged to console - return new ScriptInfo(); - } + private static NonNullPair<Script, List<Structure>> loadScript(Config config) { - // Track what is loaded - ScriptInfo scriptInfo = new ScriptInfo(); - scriptInfo.files = 1; // Loading one script - - Config config = script.getConfig(); ParserInstance parser = getParser(); + List<Structure> structures = new ArrayList<>(); + Script script = new Script(config, structures); parser.setActive(script); try { @@ -642,13 +634,13 @@ private static ScriptInfo loadScript(@Nullable Script script) { if (structure == null) continue; - script.getStructures().add(structure); - - scriptInfo.structures++; + structures.add(structure); } - if (Skript.logHigh()) - Skript.info("loaded " + scriptInfo.structures + " structure" + (scriptInfo.structures == 1 ? "" : "s") + " from '" + config.getFileName() + "'"); + if (Skript.logHigh()) { + int count = structures.size(); + Skript.info("loaded " + count + " structure" + (count == 1 ? "" : "s") + " from '" + config.getFileName() + "'"); + } } } catch (Exception e) { //noinspection ThrowableNotThrown @@ -681,7 +673,7 @@ private static ScriptInfo loadScript(@Nullable Script script) { } } - return scriptInfo; + return new NonNullPair<>(script, structures); } /* @@ -802,8 +794,13 @@ public static ScriptInfo unloadScripts(Set<Script> scripts) { ParserInstance parser = getParser(); ScriptInfo info = new ScriptInfo(); - scripts = new HashSet<>(scripts); // Don't modify the list we were provided with + // ensure unloaded scripts are not being loaded + for (Script script : scripts) { + if (!loadedScripts.contains(script)) + throw new SkriptAPIException("The script at '" + script.getConfig().getPath() + "' is not loaded!"); + } + // initial unload stage for (Script script : scripts) { parser.setActive(script); for (Structure structure : script.getStructures()) @@ -812,6 +809,7 @@ public static ScriptInfo unloadScripts(Set<Script> scripts) { parser.setInactive(); + // finish unloading + data collection for (Script script : scripts) { List<Structure> structures = script.getStructures(); @@ -819,11 +817,11 @@ public static ScriptInfo unloadScripts(Set<Script> scripts) { info.structures += structures.size(); parser.setActive(script); - for (Structure structure : script.getStructures()) + for (Structure structure : structures) structure.postUnload(); - structures.clear(); parser.setInactive(); + script.clearData(); loadedScripts.remove(script); // We just unloaded it, so... File scriptFile = script.getConfig().getFile(); assert scriptFile != null; @@ -883,14 +881,18 @@ public static CompletableFuture<ScriptInfo> reloadScripts(Set<Script> scripts, O /** * Replaces options in a string. - * Options are gotten from {@link ch.njol.skript.structures.StructOptions#getOptions(Script)}. + * Options are obtained from a {@link Script}'s {@link OptionsData}. + * Example: <code>script.getData(OptionsData.class)</code> */ // TODO this system should eventually be replaced with a more generalized "node processing" system - public static String replaceOptions(String s) { + public static String replaceOptions(String string) { ParserInstance parser = getParser(); if (!parser.isActive()) // getCurrentScript() is not safe to use - return s; - return StructOptions.replaceOptions(parser.getCurrentScript(), s); + return string; + OptionsData optionsData = parser.getCurrentScript().getData(OptionsData.class); + if (optionsData == null) + return string; + return optionsData.replaceOptions(string); } /** @@ -1082,12 +1084,12 @@ public static int loadedTriggers() { */ @Deprecated static void loadScripts() { - unloadScripts(loadedScripts); + unloadScripts(getLoadedScripts()); loadScripts(Skript.getInstance().getScriptsFolder(), OpenCloseable.EMPTY).join(); } /** - * @deprecated Callers should not be using configs. Use {@link #loadScripts(Collection, OpenCloseable)}. + * @deprecated Callers should not be using configs. Use {@link #loadScripts(Set, OpenCloseable)}. */ @Deprecated public static ScriptInfo loadScripts(List<Config> configs) { @@ -1095,7 +1097,7 @@ public static ScriptInfo loadScripts(List<Config> configs) { } /** - * @deprecated Callers should not be using configs. Use {@link #loadScripts(Collection, OpenCloseable)}. + * @deprecated Callers should not be using configs. Use {@link #loadScripts(Set, OpenCloseable)}. * @see RetainingLogHandler */ @Deprecated @@ -1109,7 +1111,7 @@ public static ScriptInfo loadScripts(List<Config> configs, List<LogEntry> logOut } /** - * @deprecated Callers should not be using configs. Use {@link #loadScripts(Collection, OpenCloseable)}. + * @deprecated Callers should not be using configs. Use {@link #loadScripts(Set, OpenCloseable)}. */ @Deprecated public static ScriptInfo loadScripts(Config... configs) { diff --git a/src/main/java/ch/njol/skript/SkriptCommand.java b/src/main/java/ch/njol/skript/SkriptCommand.java index b0bed1c8000..ac926d14262 100644 --- a/src/main/java/ch/njol/skript/SkriptCommand.java +++ b/src/main/java/ch/njol/skript/SkriptCommand.java @@ -47,10 +47,11 @@ import java.io.File; import java.io.FileFilter; import java.io.IOException; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; public class SkriptCommand implements CommandExecutor { @@ -254,7 +255,7 @@ else if (args[0].equalsIgnoreCase("enable")) { } }); } else { - Collection<File> scriptFiles; + Set<File> scriptFiles; try { scriptFiles = toggleFiles(scriptFile, true); } catch (IOException e) { @@ -321,7 +322,7 @@ else if (args[0].equalsIgnoreCase("disable")) { } else { ScriptLoader.unloadScripts(ScriptLoader.getScripts(scriptFile)); - Collection<File> scripts; + Set<File> scripts; try { scripts = toggleFiles(scriptFile, false); } catch (IOException e) { @@ -515,10 +516,10 @@ private static File toggleFile(File file, boolean enable) throws IOException { ); } - private static Collection<File> toggleFiles(File folder, boolean enable) throws IOException { + private static Set<File> toggleFiles(File folder, boolean enable) throws IOException { FileFilter filter = enable ? ScriptLoader.getDisabledScriptsFilter() : ScriptLoader.getLoadedScriptsFilter(); - List<File> changed = new ArrayList<>(); + Set<File> changed = new HashSet<>(); for (File file : folder.listFiles()) { if (file.isDirectory()) { changed.addAll(toggleFiles(file, enable)); diff --git a/src/main/java/ch/njol/skript/aliases/Aliases.java b/src/main/java/ch/njol/skript/aliases/Aliases.java index 58aa0c9faeb..898f90e8eba 100644 --- a/src/main/java/ch/njol/skript/aliases/Aliases.java +++ b/src/main/java/ch/njol/skript/aliases/Aliases.java @@ -596,14 +596,6 @@ public static ScriptAliases createScriptAliases(Script script) { return aliases; } - /** - * Clears any stored custom aliases for the provided Script. - * @param script The script to clear aliases for. - */ - public static void clearScriptAliases(Script script) { - script.removeData(ScriptAliases.class); - } - /** * Internal method for obtaining ScriptAliases. Checks {@link ParserInstance#isActive()}. * @return The obtained aliases, or null if the script has no custom aliases. diff --git a/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java b/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java index 44f8958b8d0..f0b1f274a55 100644 --- a/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java +++ b/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java @@ -28,7 +28,7 @@ import ch.njol.skript.lang.TriggerSection; import ch.njol.skript.lang.util.ContextlessEvent; import ch.njol.skript.log.HandlerList; -import ch.njol.skript.structures.StructOptions; +import ch.njol.skript.structures.StructOptions.OptionsData; import ch.njol.util.Kleenean; import ch.njol.util.Validate; import ch.njol.util.coll.CollectionUtils; @@ -36,6 +36,7 @@ import org.eclipse.jdt.annotation.Nullable; import org.jetbrains.annotations.NotNull; import org.skriptlang.skript.lang.script.Script; +import org.skriptlang.skript.lang.script.ScriptEvent; import org.skriptlang.skript.lang.structure.Structure; import java.util.ArrayList; @@ -113,9 +114,11 @@ private void setCurrentScript(@Nullable Script currentScript) { // "Script" events if (previous != null) - previous.getEventHandlers().forEach(eventHandler -> eventHandler.whenMadeInactive(currentScript)); + previous.getEvents(ScriptEvent.ScriptInactiveEvent.class) + .forEach(eventHandler -> eventHandler.onInactive(currentScript)); if (currentScript != null) - currentScript.getEventHandlers().forEach(eventHandler -> eventHandler.whenMadeActive(previous)); + currentScript.getEvents(ScriptEvent.ScriptActiveEvent.class) + .forEach(eventHandler -> eventHandler.onActive(previous)); } /** @@ -252,7 +255,7 @@ public final boolean isCurrentEvent(Class<? extends Event>... events) { if (isCurrentEvent(event)) return true; } - return true; + return false; } // Section API @@ -421,7 +424,7 @@ protected final ParserInstance getParser() { } /** - * @deprecated See {@link org.skriptlang.skript.lang.script.ScriptEventHandler}. + * @deprecated See {@link ScriptEvent}. */ @Deprecated public void onCurrentScriptChange(@Nullable Config currentScript) { } @@ -481,16 +484,17 @@ private List<? extends Data> getDataInstances() { // Deprecated API /** - * @deprecated Use {@link ch.njol.skript.structures.StructOptions#getOptions(Script)} instead. + * @deprecated Use {@link Script#getData(Class)} instead. The {@link OptionsData} class should be obtained. + * Example: <code>script.getData(OptionsData.class)</code> */ @Deprecated public HashMap<String, String> getCurrentOptions() { if (!isActive()) return new HashMap<>(0); - HashMap<String, String> options = StructOptions.getOptions(getCurrentScript()); - if (options == null) + OptionsData data = getCurrentScript().getData(OptionsData.class); + if (data == null) return new HashMap<>(0); - return options; + return new HashMap<>(data.getOptions()); // ensure returned map is modifiable } /** diff --git a/src/main/java/ch/njol/skript/structures/StructAliases.java b/src/main/java/ch/njol/skript/structures/StructAliases.java index 7086e044601..8d09fcf802d 100644 --- a/src/main/java/ch/njol/skript/structures/StructAliases.java +++ b/src/main/java/ch/njol/skript/structures/StructAliases.java @@ -64,12 +64,6 @@ public boolean load() { return true; } - @Override - public void unload() { - // Unload aliases when this Script is unloaded - Aliases.clearScriptAliases(getParser().getCurrentScript()); - } - @Override public Priority getPriority() { return PRIORITY; diff --git a/src/main/java/ch/njol/skript/structures/StructCommand.java b/src/main/java/ch/njol/skript/structures/StructCommand.java index 22ea200ca08..c389c62d7bd 100644 --- a/src/main/java/ch/njol/skript/structures/StructCommand.java +++ b/src/main/java/ch/njol/skript/structures/StructCommand.java @@ -24,9 +24,9 @@ import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.classes.Parser; import ch.njol.skript.command.Argument; -import ch.njol.skript.command.CommandEvent; import ch.njol.skript.command.Commands; import ch.njol.skript.command.ScriptCommand; +import ch.njol.skript.command.ScriptCommandEvent; import ch.njol.skript.config.SectionNode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; @@ -97,7 +97,7 @@ public class StructCommand extends Structure { .addEntry("description", "", true) .addEntry("prefix", null, true) .addEntry("permission", "", true) - .addEntryData(new VariableStringEntryData("permission message", null, true, CommandEvent.class)) + .addEntryData(new VariableStringEntryData("permission message", null, true, ScriptCommandEvent.class)) .addEntryData(new KeyValueEntryData<List<String>>("aliases", new ArrayList<>(), true) { private final Pattern pattern = Pattern.compile("\\s*,\\s*/?"); @@ -132,9 +132,9 @@ protected Integer getValue(String value) { } }) .addEntryData(new LiteralEntryData<>("cooldown", null, true, Timespan.class)) - .addEntryData(new VariableStringEntryData("cooldown message", null, true, CommandEvent.class)) + .addEntryData(new VariableStringEntryData("cooldown message", null, true, ScriptCommandEvent.class)) .addEntry("cooldown bypass", null, true) - .addEntryData(new VariableStringEntryData("cooldown storage", null, true, StringMode.VARIABLE_NAME, CommandEvent.class)) + .addEntryData(new VariableStringEntryData("cooldown storage", null, true, StringMode.VARIABLE_NAME, ScriptCommandEvent.class)) .addSection("trigger", false) .unexpectedEntryMessage(key -> "Unexpected entry '" + key + "'. Check that it's spelled correctly, and ensure that you have put all code into a trigger." @@ -155,7 +155,7 @@ public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResu @Override @SuppressWarnings("unchecked") public boolean load() { - getParser().setCurrentEvent("command", CommandEvent.class); + getParser().setCurrentEvent("command", ScriptCommandEvent.class); EntryContainer entryContainer = getEntryContainer(); diff --git a/src/main/java/ch/njol/skript/structures/StructOptions.java b/src/main/java/ch/njol/skript/structures/StructOptions.java index ef5efa21063..070422979ed 100644 --- a/src/main/java/ch/njol/skript/structures/StructOptions.java +++ b/src/main/java/ch/njol/skript/structures/StructOptions.java @@ -28,14 +28,14 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptParser.ParseResult; -import org.skriptlang.skript.lang.script.Script; -import org.skriptlang.skript.lang.script.ScriptData; -import org.skriptlang.skript.lang.entry.EntryContainer; -import org.skriptlang.skript.lang.structure.Structure; import ch.njol.util.StringUtils; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.lang.entry.EntryContainer; +import org.skriptlang.skript.lang.script.ScriptData; +import org.skriptlang.skript.lang.structure.Structure; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; @@ -113,44 +113,33 @@ public String toString(@Nullable Event e, boolean debug) { return "options"; } - /** - * A method to obtain all options registered within a Script. - * @param script The Script to obtain options from. - * @return The options of this Script, or null if there are none. - */ - @Nullable - public static HashMap<String, String> getOptions(Script script) { - OptionsData optionsData = script.getData(OptionsData.class); - return optionsData != null ? optionsData.options : null; - } - - /** - * Replaces all options in the provided String using the options of the provided Script. - * @param script The Script to obtain options from. - * @param string The String to replace options in. - * @return A String with all options replaced, or the original String if the provided Script has no options. - */ - public static String replaceOptions(Script script, String string) { - Map<String, String> options = getOptions(script); - if (options == null) - return string; - - String replaced = StringUtils.replaceAll(string, "\\{@(.+?)\\}", m -> { - String option = options.get(m.group(1)); - if (option == null) { - Skript.error("undefined option " + m.group()); - return m.group(); - } - return Matcher.quoteReplacement(option); - }); - - assert replaced != null; - return replaced; - } - - private static final class OptionsData implements ScriptData { + public static final class OptionsData implements ScriptData { + + private final Map<String, String> options = new HashMap<>(); + + /** + * Replaces all options in the provided String using the options of this data. + * @param string The String to replace options in. + * @return A String with all options replaced, or the original String if the provided Script has no options. + */ + @SuppressWarnings("ConstantConditions") // no way to get null as callback does not return null anywhere + public String replaceOptions(String string) { + return StringUtils.replaceAll(string, "\\{@(.+?)\\}", m -> { + String option = options.get(m.group(1)); + if (option == null) { + Skript.error("undefined option " + m.group()); + return m.group(); + } + return Matcher.quoteReplacement(option); + }); + } - public final HashMap<String, String> options = new HashMap<>(15); + /** + * @return An unmodifiable version of this data's option mappings. + */ + public Map<String, String> getOptions() { + return Collections.unmodifiableMap(options); + } } diff --git a/src/main/java/org/skriptlang/skript/lang/script/Script.java b/src/main/java/org/skriptlang/skript/lang/script/Script.java index 01f9d20c632..2bcb4e412f6 100644 --- a/src/main/java/org/skriptlang/skript/lang/script/Script.java +++ b/src/main/java/org/skriptlang/skript/lang/script/Script.java @@ -20,9 +20,9 @@ import ch.njol.skript.config.Config; import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Unmodifiable; import org.skriptlang.skript.lang.structure.Structure; -import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -30,6 +30,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; +import java.util.stream.Collectors; /** * Scripts are the primary container of all code. @@ -41,29 +42,31 @@ public final class Script { private final Config config; - private final List<Structure> structures = new ArrayList<>(); + private final List<Structure> structures; /** * Creates a new Script to be used across the API. - * Only one script should be created per Config. A loaded script may be obtained through {@link ch.njol.skript.ScriptLoader}. - * @param config The Config containing the contents of this script. + * Only one Script should be created per Config. A loaded Script may be obtained through {@link ch.njol.skript.ScriptLoader}. + * @param config The Config containing the contents of this Script. + * @param structures The list of Structures contained in this Script. */ - public Script(Config config) { + public Script(Config config, List<Structure> structures) { this.config = config; + this.structures = structures; } /** - * @return The Config representing the structure of this script. + * @return The Config representing the structure of this Script. */ public Config getConfig() { return config; } /** - * @return A list of all Structures within this Script. + * @return An unmodifiable list of all Structures within this Script. */ public List<Structure> getStructures() { - return structures; + return Collections.unmodifiableList(structures); } // Warning Suppressions @@ -71,14 +74,14 @@ public List<Structure> getStructures() { private final Set<ScriptWarning> suppressedWarnings = new HashSet<>(ScriptWarning.values().length); /** - * @param warning Suppresses the provided warning for this script. + * @param warning Suppresses the provided warning for this Script. */ public void suppressWarning(ScriptWarning warning) { suppressedWarnings.add(warning); } /** - * @param warning Allows the provided warning for this script. + * @param warning Allows the provided warning for this Script. */ public void allowWarning(ScriptWarning warning) { suppressedWarnings.remove(warning); @@ -86,7 +89,7 @@ public void allowWarning(ScriptWarning warning) { /** * @param warning The warning to check. - * @return Whether this script suppresses the provided warning. + * @return Whether this Script suppresses the provided warning. */ public boolean suppressesWarning(ScriptWarning warning) { return suppressedWarnings.contains(warning); @@ -96,6 +99,13 @@ public boolean suppressesWarning(ScriptWarning warning) { private final Map<Class<? extends ScriptData>, ScriptData> scriptData = new ConcurrentHashMap<>(5); + /** + * Clears the data stored for this script. + */ + public void clearData() { + scriptData.clear(); + } + /** * Adds new ScriptData to this Script's data map. * @param data The data to add. @@ -137,29 +147,53 @@ public <Value extends ScriptData> Value getData(Class<? extends Value> dataType, // Script Events - private final Set<ScriptEventHandler> eventHandlers = new HashSet<>(5); + private final Set<ScriptEvent> eventHandlers = new HashSet<>(5); /** - * Adds the provided event handler to this Script. - * @param eventHandler The event handler to add. + * Adds the provided event to this Script. + * @param event The event to add. */ - public void addEventHandler(ScriptEventHandler eventHandler) { - eventHandlers.add(eventHandler); + public void registerEvent(ScriptEvent event) { + eventHandlers.add(event); } /** - * Removes the provided event handler from this Script. - * @param eventHandler The event handler to remove. + * Adds the provided event to this Script. + * @param eventType The type of event being added. This is useful for registering the event through lambdas. + * @param event The event to add. */ - public void removeEventHandler(ScriptEventHandler eventHandler) { - eventHandlers.remove(eventHandler); + public <T extends ScriptEvent> void registerEvent(Class<T> eventType, T event) { + eventHandlers.add(event); } /** - * @return An unmodifiable set of all event handlers. + * Removes the provided event from this Script. + * @param event The event to remove. */ - public Set<ScriptEventHandler> getEventHandlers() { + public void unregisterEvent(ScriptEvent event) { + eventHandlers.remove(event); + } + + /** + * @return An unmodifiable set of all events. + */ + @Unmodifiable + public Set<ScriptEvent> getEvents() { return Collections.unmodifiableSet(eventHandlers); } + /** + * @param type The type of events to get. + * @return An unmodifiable set of all events of the specified type. + */ + @Unmodifiable + @SuppressWarnings("unchecked") + public <T extends ScriptEvent> Set<T> getEvents(Class<T> type) { + return Collections.unmodifiableSet( + (Set<T>) eventHandlers.stream() + .filter(event -> type.isAssignableFrom(event.getClass())) + .collect(Collectors.toSet()) + ); + } + } diff --git a/src/main/java/org/skriptlang/skript/lang/script/ScriptEvent.java b/src/main/java/org/skriptlang/skript/lang/script/ScriptEvent.java new file mode 100644 index 00000000000..2b647374140 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/script/ScriptEvent.java @@ -0,0 +1,64 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.script; + +import ch.njol.skript.lang.parser.ParserInstance; +import org.eclipse.jdt.annotation.Nullable; + +/** + * A ScriptEvent is used for listening to and performing actions for different Script events. + * @see Script#registerEvent(ScriptEvent) + */ +public interface ScriptEvent { + + /** + * A ScriptEvent that is called when a Script is made active in a {@link ParserInstance}. + */ + @FunctionalInterface + interface ScriptActiveEvent extends ScriptEvent { + + /** + * Called when this Script is made active in a {@link ParserInstance}. + * + * @param oldScript The Script that was just made inactive. + * Null if the {@link ParserInstance} handling this Script + * was not {@link ParserInstance#isActive()} (it is becoming active). + */ + void onActive(@Nullable Script oldScript); + + } + + /** + * A ScriptEvent that is called when a Script is made inactive in a {@link ParserInstance}. + */ + @FunctionalInterface + interface ScriptInactiveEvent extends ScriptEvent { + + /** + * Called when this Script is made inactive in a {@link ParserInstance}. + * + * @param newScript The Script that will be made active after this one is completely inactive. + * Null if the {@link ParserInstance} handling this Script + * will not be {@link ParserInstance#isActive()} (will become inactive). + */ + void onInactive(@Nullable Script newScript); + + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/script/ScriptEventHandler.java b/src/main/java/org/skriptlang/skript/lang/script/ScriptEventHandler.java deleted file mode 100644 index 5362077216e..00000000000 --- a/src/main/java/org/skriptlang/skript/lang/script/ScriptEventHandler.java +++ /dev/null @@ -1,46 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package org.skriptlang.skript.lang.script; - -import ch.njol.skript.lang.parser.ParserInstance; -import org.eclipse.jdt.annotation.Nullable; - -/** - * A ScriptEventHandler is used for listening to and performing actions for different Script events. - * @see Script#addEventHandler(ScriptEventHandler) - */ -public abstract class ScriptEventHandler { - - /** - * Called when this Script is made active in a {@link ParserInstance}. - * - * @param oldScript The Script that was just made inactive. - * Null if the {@link ParserInstance} handling this Script was not {@link ParserInstance#isActive()}. - */ - public void whenMadeActive(@Nullable Script oldScript) { } - - /** - * Called when this Script is made inactive in a {@link ParserInstance}. - * - * @param newScript The Script that will be made active after this one is completely inactive. - * Null if the {@link ParserInstance} handling this Script will be not {@link ParserInstance#isActive()}. - */ - public void whenMadeInactive(@Nullable Script newScript) { } - -} diff --git a/src/test/skript/tests/syntaxes/structures/StructCommand.sk b/src/test/skript/tests/syntaxes/structures/StructCommand.sk new file mode 100644 index 00000000000..b15168b0996 --- /dev/null +++ b/src/test/skript/tests/syntaxes/structures/StructCommand.sk @@ -0,0 +1,18 @@ +test "commands": + execute command "skriptcommand taco" + execute command "/somecommand burrito is tasty" + +command skriptcommand <text> [<text>] [<itemtype = %dirt block named "steve"%>]: + trigger: + set {_arg1} to arg-1 + assert {_arg1} is "taco" with "arg-1 test failed (got '%{_arg1}%')" + set {_arg2} to arg-2 + assert {_arg2} is not set with "arg-2 test failed (got '%{_arg2}%')" + set {_arg3} to arg-3 + assert {_arg3} is a dirt block named "steve" with "arg-3 test failed (got '%{_arg3}%')" + +command //somecommand [<text>]: + trigger: + set {_arg1} to arg-1 + if {_arg1} is set: + assert {_arg1} is "burrito is tasty" with "arg-1 is 'burrito is tasty' test failed (got '%{_arg1}%')" From ec70cf0b89afcced615cf2f4518421c9092db7a2 Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Thu, 16 Feb 2023 23:08:11 +0300 Subject: [PATCH 244/619] Add Value Within expression (#5198) --- .../skript/expressions/ExprValueWithin.java | 117 ++++++++++++++++++ .../syntaxes/expressions/ExprValueWithin.sk | 8 ++ 2 files changed, 125 insertions(+) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprValueWithin.java create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprValueWithin.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprValueWithin.java b/src/main/java/ch/njol/skript/expressions/ExprValueWithin.java new file mode 100644 index 00000000000..df5d88d6571 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprValueWithin.java @@ -0,0 +1,117 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.Changer; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.WrapperExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.UnparsedLiteral; +import ch.njol.skript.registrations.Classes; +import ch.njol.skript.util.Utils; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Value Within") +@Description( + "Gets the value within objects. Usually used with variables to get the value they store rather than the variable itself, " + + "or with lists to get the values of a type." +) +@Examples({ + "set {_entity} to a random entity out of all entities", + "delete entity within {_entity} # This deletes the entity itself and not the value stored in the variable", + "", + "set {_list::*} to \"something\", 10, \"test\" and a zombie", + "broadcast the strings within {_list::*} # \"something\", \"test\"" +}) +@Since("INSERT VERSION") +public class ExprValueWithin extends WrapperExpression<Object> { + + static { + Skript.registerExpression(ExprValueWithin.class, Object.class, ExpressionType.COMBINED, "[the] (%-*classinfo%|value[:s]) (within|in) %~objects%"); + } + + @Nullable + private ClassInfo<?> classInfo; + + @Nullable + @SuppressWarnings("rawtypes") + private Changer changer; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + boolean plural; + if (exprs[0] != null) { + UnparsedLiteral unparsedLiteral = (UnparsedLiteral) exprs[0].getSource(); + String input = unparsedLiteral.getData(); + plural = Utils.getEnglishPlural(input).getSecond(); + } else { + plural = parseResult.hasTag("s"); + } + + if (plural == exprs[1].isSingle()) { + if (plural) { + Skript.error("You cannot get multiple elements of a single value"); + } else { + Skript.error(exprs[1].toString(null, false) + " may contain more than one " + (classInfo == null ? "value" : classInfo.getName())); + } + return false; + } + + classInfo = exprs[0] == null ? null : ((Literal<ClassInfo<?>>) exprs[0]).getSingle(); + Expression<?> expr = classInfo == null ? exprs[1] : exprs[1].getConvertedExpression(classInfo.getC()); + if (expr == null) + return false; + setExpr(expr); + return true; + } + + @Override + public Class<?> @Nullable [] acceptChange(ChangeMode mode) { + changer = Classes.getSuperClassInfo(getReturnType()).getChanger(); + if (changer == null) + return null; + return changer.acceptChange(mode); + } + + @Override + @SuppressWarnings("unchecked") + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + if (changer == null) + throw new UnsupportedOperationException(); + changer.change(getArray(event), delta, mode); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return (classInfo == null ? "value" : classInfo.toString(event, debug)) + " within " + getExpr(); + } + +} diff --git a/src/test/skript/tests/syntaxes/expressions/ExprValueWithin.sk b/src/test/skript/tests/syntaxes/expressions/ExprValueWithin.sk new file mode 100644 index 00000000000..5519c77b59a --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprValueWithin.sk @@ -0,0 +1,8 @@ +test "single value in variable": + set {_num} to 1337 + assert number within {_num} is 1337 with "number within variable failed" + assert string within {_num} is not set with "wrong type within variable returned a value" + +test "multiple values in list": + set {_values::*} to "something", 10, "test" and a zombie + assert size of (strings within {_values::*}) is 2 with "strings within list failed" From fce8119215a013b0ce4dcd34c7a97394203a9e38 Mon Sep 17 00:00:00 2001 From: Kiip <25848425+kiip1@users.noreply.github.com> Date: Thu, 16 Feb 2023 22:36:43 +0100 Subject: [PATCH 245/619] Remove buildbot (#5454) --- buildbot-compile.sh | 11 ----------- updaterepo.sh | 6 ------ 2 files changed, 17 deletions(-) delete mode 100755 buildbot-compile.sh delete mode 100755 updaterepo.sh diff --git a/buildbot-compile.sh b/buildbot-compile.sh deleted file mode 100755 index 9d314893d5f..00000000000 --- a/buildbot-compile.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash -# Compiles Skript with some special settings used to produce unstable builds - -# Version identifier: 2.2, date YYMMDD, git head -SKRIPT_VER="2.2-$(date +%y%m%d)-git-$(git rev-parse --short HEAD)" -export SKRIPT_VERSION=$SKRIPT_VER - -./gradlew clean build # Clean all, then build - -# Write name for upload script in my server to use -echo "$SKRIPT_VER" >buildbot-upload-version.txt diff --git a/updaterepo.sh b/updaterepo.sh deleted file mode 100755 index d0235955e1b..00000000000 --- a/updaterepo.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -./gradlew publish -cd ../ItemAPI/repo #This is due to me having strange file structure... -git add ch -git commit -m "Update Skript to latest source version" -git push repo master From 2f283d4e3e9239bc13450e4f63e3792060b56f10 Mon Sep 17 00:00:00 2001 From: sovdee <ethansovde@gmail.com> Date: Thu, 16 Feb 2023 16:43:33 -0500 Subject: [PATCH 246/619] Add EffKnockback (#5391) --- .../ch/njol/skript/effects/EffKnockback.java | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 src/main/java/ch/njol/skript/effects/EffKnockback.java diff --git a/src/main/java/ch/njol/skript/effects/EffKnockback.java b/src/main/java/ch/njol/skript/effects/EffKnockback.java new file mode 100644 index 00000000000..8cd464c2a1c --- /dev/null +++ b/src/main/java/ch/njol/skript/effects/EffKnockback.java @@ -0,0 +1,89 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.effects; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.util.Direction; +import ch.njol.util.Kleenean; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Event; +import org.bukkit.util.Vector; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Knockback") +@Description("Apply the same velocity as a knockback to living entities in a direction. Mechanics such as knockback resistance will be factored in.") +@Examples({ + "knockback player north", + "knock victim (vector from attacker to victim) with strength 10" +}) +@Since("INSERT VERSION") +@RequiredPlugins("Paper 1.19.2+") +public class EffKnockback extends Effect { + + static { + if (Skript.methodExists(LivingEntity.class, "knockback", double.class, double.class, double.class)) + Skript.registerEffect(EffKnockback.class, "(apply knockback to|knock[back]) %livingentities% [%direction%] [with (strength|force) %-number%]"); + } + + private Expression<LivingEntity> entities; + private Expression<Direction> direction; + @Nullable + private Expression<Number> strength; + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + entities = (Expression<LivingEntity>) exprs[0]; + direction = (Expression<Direction>) exprs[1]; + strength = (Expression<Number>) exprs[2]; + return true; + } + + @Override + protected void execute(Event event) { + Direction direction = this.direction.getSingle(event); + if (direction == null) + return; + + double strength = this.strength != null ? this.strength.getOptionalSingle(event).orElse(1).doubleValue() : 1.0; + + for (LivingEntity livingEntity : entities.getArray(event)) { + Vector directionVector = direction.getDirection(livingEntity); + // Flip the direction, because LivingEntity#knockback() takes the direction of the source of the knockback, + // not the direction of the actual knockback. + directionVector.multiply(-1); + livingEntity.knockback(strength, directionVector.getX(), directionVector.getZ()); + // ensure velocity is sent to client + livingEntity.setVelocity(livingEntity.getVelocity()); + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "knockback " + entities.toString(event, debug) + " " + direction.toString(event, debug) + " with strength " + (strength != null ? strength.toString(event, debug) : "1"); + } + +} From 11b0d43eec20869e66bc93f01c75c7bf7e6cad20 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Fri, 17 Feb 2023 18:10:27 -0700 Subject: [PATCH 247/619] Add rounding by decimal placement (#5295) --- .../skript/classes/data/DefaultFunctions.java | 32 ++++++++++++------- .../skript/tests/syntaxes/functions/round.sk | 16 ++++++++++ 2 files changed, 36 insertions(+), 12 deletions(-) create mode 100644 src/test/skript/tests/syntaxes/functions/round.sk diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java b/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java index 8cf2087c3fb..adc71b82041 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java @@ -18,6 +18,8 @@ */ package ch.njol.skript.classes.data; +import java.math.BigDecimal; +import java.math.RoundingMode; import java.util.Calendar; import ch.njol.skript.lang.function.FunctionEvent; @@ -41,10 +43,6 @@ import ch.njol.util.coll.CollectionUtils; import org.eclipse.jdt.annotation.Nullable; -/** - * @author Peter Güttinger - */ -@SuppressWarnings("null") public class DefaultFunctions { private static String str(double n) { @@ -68,16 +66,26 @@ public Long[] executeSimple(Object[][] params) { .examples("floor(2.34) = 2", "floor(2) = 2", "floor(2.99) = 2") .since("2.2")); - Functions.registerFunction(new SimpleJavaFunction<Long>("round", numberParam, DefaultClasses.LONG, true) { + Functions.registerFunction(new SimpleJavaFunction<Number>("round", new Parameter[] {new Parameter<>("n", DefaultClasses.NUMBER, true, null), new Parameter<>("d", DefaultClasses.NUMBER, true, new SimpleLiteral<Number>(0, false))}, DefaultClasses.NUMBER, true) { @Override - public Long[] executeSimple(Object[][] params) { + public Number[] executeSimple(Object[][] params) { if (params[0][0] instanceof Long) return new Long[] {(Long) params[0][0]}; - return new Long[] {Math2.round(((Number) params[0][0]).doubleValue())}; + double value = ((Number) params[0][0]).doubleValue(); + int placement = ((Number) params[1][0]).intValue(); + if (placement == 0) + return new Long[] {Math2.round(value)}; + if (placement >= 0) { + BigDecimal decimal = new BigDecimal(Double.toString(value)); + decimal = decimal.setScale(placement, RoundingMode.HALF_UP); + return new Double[] {decimal.doubleValue()}; + } + long rounded = Math2.round(value); + return new Double[] {(int) Math2.round(rounded * Math.pow(10.0, placement)) / Math.pow(10.0, placement)}; } - }.description("Rounds a number, i.e. returns the closest integer to the argument.") + }.description("Rounds a number, i.e. returns the closest integer to the argument. Place a second argument to define the decimal placement.") .examples("round(2.34) = 2", "round(2) = 2", "round(2.99) = 3", "round(2.5) = 3") - .since("2.2")); + .since("2.2, INSERT VERSION (decimal placement)")); Functions.registerFunction(new SimpleJavaFunction<Long>("ceil", numberParam, DefaultClasses.LONG, true) { @Override @@ -431,13 +439,13 @@ public Vector[] executeSimple(Object[][] params) { public Long[] executeSimple(Object[][] params) { long level = (long) params[0][0]; long exp; - if (level <= 0) { + if (level <= 0) { exp = 0; } else if (level >= 1 && level <= 15) { exp = level * level + 6 * level; } else if (level >= 16 && level <= 30) { // Truncating decimal parts probably works - exp = (int) (2.5 * level * level - 40.5 * level + 360); - } else { // Half experience points do not exist, anyway + exp = (int) (2.5 * level * level - 40.5 * level + 360); + } else { // Half experience points do not exist, anyway exp = (int) (4.5 * level * level - 162.5 * level + 2220); } diff --git a/src/test/skript/tests/syntaxes/functions/round.sk b/src/test/skript/tests/syntaxes/functions/round.sk new file mode 100644 index 00000000000..07ccbedb95b --- /dev/null +++ b/src/test/skript/tests/syntaxes/functions/round.sk @@ -0,0 +1,16 @@ +test "rounding function": + assert round(1.5) is 2 with "first assertion round failed" + assert round(1.49) is 1 with "second assertion round failed" + assert round(1.534, 2) is 1.53 with "third assertion round failed" + assert round(1.536, 2) is 1.54 with "fourth assertion round failed" + assert round(1.5367623123456789, 5) is 1.53676 with "fifth assertion round failed" + assert round(1.5, 1) is 1.5 with "double comparing failed" #because 1.50 should still be 1.5 + assert round(1.5, 1) is 1.50 with "tricking assertion round failed" + assert round(1.5, 2) is 1.500 with "second tricking assertion round failed" + assert round(1.500000, 3) is 1.5000 with "third tricking assertion round failed" + assert round(-11.535, 2) is -11.54 with "fourth tricking assertion round failed" + + assert round(111.111, -1) is 110.0 with "first negative assertion round failed" + assert round(15.1, -1) is 20.0 with "second negative assertion round failed" + assert round(1044.5, -1) is 1050.0 with "third negative assertion round failed" + assert round(-1044.5, -2) is -1000.0 with "fourth negative assertion round failed" From e636c03886f7dc503ea3c6d74e89cc7c88e05deb Mon Sep 17 00:00:00 2001 From: sovdee <ethansovde@gmail.com> Date: Sat, 18 Feb 2023 07:34:48 -0500 Subject: [PATCH 248/619] Adds PlayerInventorySlotChangeEvent (#5354) --- .../classes/data/BukkitEventValues.java | 32 +++++++++++++++++++ .../ch/njol/skript/events/SimpleEvents.java | 14 +++++++- 2 files changed, 45 insertions(+), 1 deletion(-) 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 333eaa48d24..314fd98bab7 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -38,6 +38,7 @@ import com.destroystokyo.paper.event.entity.ProjectileCollideEvent; import com.destroystokyo.paper.event.player.PlayerArmorChangeEvent; import io.papermc.paper.event.entity.EntityMoveEvent; +import io.papermc.paper.event.player.PlayerInventorySlotChangeEvent; import io.papermc.paper.event.player.PlayerTradeEvent; import org.bukkit.Bukkit; import org.bukkit.Chunk; @@ -1425,6 +1426,37 @@ public ItemStack get(PlayerArmorChangeEvent e) { } }, 0); } + //PlayerInventorySlotChangeEvent + if (Skript.classExists("io.papermc.paper.event.player.PlayerInventorySlotChangeEvent")) { + EventValues.registerEventValue(PlayerInventorySlotChangeEvent.class, ItemStack.class, new Getter<ItemStack, PlayerInventorySlotChangeEvent>() { + @Override + @Nullable + public ItemStack get(PlayerInventorySlotChangeEvent event) { + return event.getNewItemStack(); + } + }, EventValues.TIME_NOW); + EventValues.registerEventValue(PlayerInventorySlotChangeEvent.class, ItemStack.class, new Getter<ItemStack, PlayerInventorySlotChangeEvent>() { + @Override + @Nullable + public ItemStack get(PlayerInventorySlotChangeEvent event) { + return event.getOldItemStack(); + } + }, EventValues.TIME_PAST); + EventValues.registerEventValue(PlayerInventorySlotChangeEvent.class, Slot.class, new Getter<Slot, PlayerInventorySlotChangeEvent>() { + @Override + @Nullable + public Slot get(PlayerInventorySlotChangeEvent event) { + PlayerInventory inv = event.getPlayer().getInventory(); + int slotIndex = event.getSlot(); + // Not all indices point to inventory slots. Equipment, for example + if (slotIndex >= 36) { + return new ch.njol.skript.util.slot.EquipmentSlot(event.getPlayer(), slotIndex); + } else { + return new InventorySlot(inv, slotIndex); + } + } + }, EventValues.TIME_NOW); + } //PrepareItemEnchantEvent EventValues.registerEventValue(PrepareItemEnchantEvent.class, Player.class, new Getter<Player, PrepareItemEnchantEvent>() { @Override diff --git a/src/main/java/ch/njol/skript/events/SimpleEvents.java b/src/main/java/ch/njol/skript/events/SimpleEvents.java index bfbccf575c6..ad2d1f116b2 100644 --- a/src/main/java/ch/njol/skript/events/SimpleEvents.java +++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java @@ -19,6 +19,7 @@ package ch.njol.skript.events; import com.destroystokyo.paper.event.block.AnvilDamagedEvent; +import io.papermc.paper.event.player.PlayerInventorySlotChangeEvent; import org.bukkit.event.block.BlockCanBuildEvent; import org.bukkit.event.block.BlockDamageEvent; import org.bukkit.event.block.BlockFertilizeEvent; @@ -650,6 +651,18 @@ public class SimpleEvents { "\tcancel the event") .since("INSERT VERSION"); } + + if (Skript.classExists("io.papermc.paper.event.player.PlayerInventorySlotChangeEvent")) { + Skript.registerEvent("Inventory Slot Change", SimpleEvent.class, PlayerInventorySlotChangeEvent.class, "[player] inventory slot chang(e|ing)") + .description("Called when a slot in a player's inventory is changed.", "Warning: setting the event-slot to a new item can result in an infinite loop.") + .requiredPlugins("Paper 1.19.2+") + .examples( + "on inventory slot change:", + "\tif event-item is a diamond:", + "\t\tsend \"You obtained a diamond!\" to player" + ) + .since("INSERT VERSION"); + } //noinspection deprecation Skript.registerEvent("Chat", SimpleEvent.class, AsyncPlayerChatEvent.class, "chat") @@ -684,7 +697,6 @@ public class SimpleEvents { .since("INSERT VERSION") .requiredPlugins("MC 1.16+"); } - } } From 6cfb45dd6227ac1f7c92fd501ff2a4d22af97baa Mon Sep 17 00:00:00 2001 From: DelayedGaming <72163224+DelayedGaming@users.noreply.github.com> Date: Sat, 18 Feb 2023 20:53:56 +0800 Subject: [PATCH 249/619] Adds PlayerDeepSleepEvent (#5448) --- .../ch/njol/skript/events/SimpleEvents.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/njol/skript/events/SimpleEvents.java b/src/main/java/ch/njol/skript/events/SimpleEvents.java index ad2d1f116b2..1873933b05c 100644 --- a/src/main/java/ch/njol/skript/events/SimpleEvents.java +++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java @@ -19,6 +19,7 @@ package ch.njol.skript.events; import com.destroystokyo.paper.event.block.AnvilDamagedEvent; +import io.papermc.paper.event.player.PlayerDeepSleepEvent; import io.papermc.paper.event.player.PlayerInventorySlotChangeEvent; import org.bukkit.event.block.BlockCanBuildEvent; import org.bukkit.event.block.BlockDamageEvent; @@ -651,7 +652,6 @@ public class SimpleEvents { "\tcancel the event") .since("INSERT VERSION"); } - if (Skript.classExists("io.papermc.paper.event.player.PlayerInventorySlotChangeEvent")) { Skript.registerEvent("Inventory Slot Change", SimpleEvent.class, PlayerInventorySlotChangeEvent.class, "[player] inventory slot chang(e|ing)") .description("Called when a slot in a player's inventory is changed.", "Warning: setting the event-slot to a new item can result in an infinite loop.") @@ -663,7 +663,6 @@ public class SimpleEvents { ) .since("INSERT VERSION"); } - //noinspection deprecation Skript.registerEvent("Chat", SimpleEvent.class, AsyncPlayerChatEvent.class, "chat") .description( @@ -681,7 +680,6 @@ public class SimpleEvents { "\t\tset chat format to \"<orange>[player]<light gray>: <white>[message]\"" ) .since("1.4.1"); - if (Skript.classExists("org.bukkit.event.world.LootGenerateEvent")) { Skript.registerEvent("Loot Generate", SimpleEvent.class, LootGenerateEvent.class, "loot generat(e|ing)") .description( @@ -697,6 +695,20 @@ public class SimpleEvents { .since("INSERT VERSION") .requiredPlugins("MC 1.16+"); } + if (Skript.classExists("io.papermc.paper.event.player.PlayerDeepSleepEvent")) { + Skript.registerEvent("Player Deep Sleep", SimpleEvent.class, PlayerDeepSleepEvent.class, "[player] deep sleep[ing]") + .description( + "Called when a player has slept long enough to count as passing the night/storm.", + "Cancelling this event will prevent the player from being counted as deeply sleeping unless they exit and re-enter the bed." + ) + .examples( + "on player deep sleeping:", + "\tsend \"Zzzz..\" to player" + ) + .since("INSERT VERSION") + .requiredPlugins("Paper 1.16+"); + } + } } From be0c831fecac4e27dd777b3e01ec47a3170ed273 Mon Sep 17 00:00:00 2001 From: DelayedGaming <72163224+DelayedGaming@users.noreply.github.com> Date: Sat, 18 Feb 2023 21:01:03 +0800 Subject: [PATCH 250/619] Event Player Enter Chunk (#5447) --- .../classes/data/DefaultConverters.java | 4 ++ .../skript/events/EvtPlayerChunkEnter.java | 57 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 src/main/java/ch/njol/skript/events/EvtPlayerChunkEnter.java diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java b/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java index 5fcd02a82b2..a8b2688e94e 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java @@ -30,6 +30,7 @@ import ch.njol.skript.util.Experience; import ch.njol.skript.util.slot.Slot; import org.bukkit.Bukkit; +import org.bukkit.Chunk; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.OfflinePlayer; @@ -177,6 +178,9 @@ public DefaultConverters() {} Converters.registerConverter(String.class, World.class, Bukkit::getWorld); + // Location - Chunk + Converters.registerConverter(Location.class, Chunk.class, Location::getChunk); + // // Entity - String (UUID) // Very slow, thus disabled for now // Converters.registerConverter(String.class, Entity.class, new Converter<String, Entity>() { // diff --git a/src/main/java/ch/njol/skript/events/EvtPlayerChunkEnter.java b/src/main/java/ch/njol/skript/events/EvtPlayerChunkEnter.java new file mode 100644 index 00000000000..ada1484deae --- /dev/null +++ b/src/main/java/ch/njol/skript/events/EvtPlayerChunkEnter.java @@ -0,0 +1,57 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.events; + +import ch.njol.skript.Skript; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptEvent; +import ch.njol.skript.lang.SkriptParser.ParseResult; + +import org.bukkit.event.Event; +import org.bukkit.event.player.PlayerMoveEvent; + +import org.eclipse.jdt.annotation.Nullable; + +public class EvtPlayerChunkEnter extends SkriptEvent { + + static { + Skript.registerEvent("Player Chunk Enters", EvtPlayerChunkEnter.class, PlayerMoveEvent.class, "player (enter[s] [a] chunk|chunk enter[ing])") + .description("Called when a player enters a chunk. Note that this event is based on 'player move' event, and may be called frequent internally.") + .examples( + "on player enters a chunk:", + "\tsend \"You entered a chunk: %past event-chunk% -> %event-chunk%!\" to player" + ).since("INSERT VERSION"); + } + + @Override + public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult) { + return true; + } + + @Override + public boolean check(Event event) { + return ((PlayerMoveEvent) event).getFrom().getChunk() != ((PlayerMoveEvent) event).getTo().getChunk(); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "player enter chunk"; + } + +} From 695a94eb56b7ea50c461afac07c7634103fdd47b Mon Sep 17 00:00:00 2001 From: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> Date: Mon, 20 Feb 2023 13:41:39 -0600 Subject: [PATCH 251/619] Update Expression#stream to specify Stream elements should be non-null (#5200) --- src/main/java/ch/njol/skript/lang/Expression.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/njol/skript/lang/Expression.java b/src/main/java/ch/njol/skript/lang/Expression.java index dd649b6376e..413c9103e10 100644 --- a/src/main/java/ch/njol/skript/lang/Expression.java +++ b/src/main/java/ch/njol/skript/lang/Expression.java @@ -43,8 +43,10 @@ import ch.njol.util.Checker; import org.bukkit.event.Event; import org.bukkit.inventory.ItemStack; +import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; +import java.util.Arrays; import java.util.Iterator; import java.util.Optional; import java.util.Spliterators; @@ -114,10 +116,10 @@ default Optional<T> getOptionalSingle(Event e) { * Gets a non-null stream of this expression's values. * * @param e The event - * @return A non-null stream of this expression's values + * @return A non-null stream of this expression's non-null values */ - default public Stream<? extends T> stream(final Event e) { - Iterator<? extends T> iter = iterator(e); + default public Stream<@NonNull ? extends T> stream(Event event) { + Iterator<? extends T> iter = iterator(event); if (iter == null) { return Stream.empty(); } From 2abab676b3884947c1621a0bcaf028b35d9bbafa Mon Sep 17 00:00:00 2001 From: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> Date: Mon, 20 Feb 2023 19:39:41 -0600 Subject: [PATCH 252/619] Update security guidelines to point to new reporting page (#5469) --- security.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/security.md b/security.md index cd5dad1f445..839409cc93d 100644 --- a/security.md +++ b/security.md @@ -3,15 +3,12 @@ See also [code conventions](code-conventions.md); there are a few guidelines about security of added code there. ## Reporting security issues -Security issues may be reported to core team members privately e.g. on Discord. -Note that this applies *only* to security issues; everything else should still -be posted to issue tracker. +Security issues may be reported via the GitHub private vulnerability reporting feature [here](https://github.com/SkriptLang/Skript/security/advisories/new). +Note that this applies *only* to security issues; everything else should still be posted to issue tracker. -Publicly posting security issues is also allowed, because not everyone has or -wants a Discord account. We may add other channels for private reports in -future. +Please avoid publicly posting or discussing security issues that don't have a fix available yet. ## Team guidelines Everyone with push access must use two-factor authentication for their Github accounts. Should their account still be compromised, other team members should -be immediately notified via Discord. \ No newline at end of file +be immediately notified via Discord. From ffe150a49d29c92cf439b1122ed810a94b9368f8 Mon Sep 17 00:00:00 2001 From: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> Date: Tue, 21 Feb 2023 05:11:20 -0600 Subject: [PATCH 253/619] Fix double space in toString of ExprPermissions (#5470) --- src/main/java/ch/njol/skript/expressions/ExprPermissions.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprPermissions.java b/src/main/java/ch/njol/skript/expressions/ExprPermissions.java index 0d808427f99..21d7c1736a6 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprPermissions.java +++ b/src/main/java/ch/njol/skript/expressions/ExprPermissions.java @@ -82,7 +82,7 @@ public Class<String> getReturnType() { @Override public String toString(@Nullable Event event, boolean debug) { - return "permissions " + " of " + players.toString(event, debug); + return "permissions of " + players.toString(event, debug); } } From 950e6f5fc0d5b3f6c4501176b1468b80e0d62ef4 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Thu, 23 Feb 2023 06:33:23 -0500 Subject: [PATCH 254/619] Script handling improvements (#5466) --- .../java/ch/njol/skript/ScriptLoader.java | 12 ++++++--- .../java/ch/njol/skript/command/Commands.java | 2 +- .../njol/skript/expressions/ExprScript.java | 14 ++++++++--- .../ch/njol/skript/lang/VariableString.java | 25 +++++++++---------- .../skript/lang/parser/ParserInstance.java | 7 ++++-- .../skriptlang/skript/lang/script/Script.java | 3 +++ src/test/skript/tests/misc/effect commands.sk | 4 +++ 7 files changed, 43 insertions(+), 24 deletions(-) create mode 100644 src/test/skript/tests/misc/effect commands.sk diff --git a/src/main/java/ch/njol/skript/ScriptLoader.java b/src/main/java/ch/njol/skript/ScriptLoader.java index 7c48fd5a104..f32be7414e3 100644 --- a/src/main/java/ch/njol/skript/ScriptLoader.java +++ b/src/main/java/ch/njol/skript/ScriptLoader.java @@ -599,6 +599,8 @@ private static CompletableFuture<ScriptInfo> loadScripts(List<Config> configs, O */ // Whenever you call this method, make sure to also call PreScriptLoadEvent private static NonNullPair<Script, List<Structure>> loadScript(Config config) { + if (config.getFile() == null) + throw new IllegalArgumentException("A config must have a file to be loaded."); ParserInstance parser = getParser(); List<Structure> structures = new ArrayList<>(); @@ -791,15 +793,16 @@ private static Config loadStructure(InputStream source, String name) { * This data is calculated by using {@link ScriptInfo#add(ScriptInfo)}. */ public static ScriptInfo unloadScripts(Set<Script> scripts) { - ParserInstance parser = getParser(); - ScriptInfo info = new ScriptInfo(); - - // ensure unloaded scripts are not being loaded + // ensure unloaded scripts are not being unloaded for (Script script : scripts) { if (!loadedScripts.contains(script)) throw new SkriptAPIException("The script at '" + script.getConfig().getPath() + "' is not loaded!"); + if (script.getConfig().getFile() == null) + throw new IllegalArgumentException("A script must have a file to be unloaded."); } + ParserInstance parser = getParser(); + // initial unload stage for (Script script : scripts) { parser.setActive(script); @@ -810,6 +813,7 @@ public static ScriptInfo unloadScripts(Set<Script> scripts) { parser.setInactive(); // finish unloading + data collection + ScriptInfo info = new ScriptInfo(); for (Script script : scripts) { List<Structure> structures = script.getStructures(); diff --git a/src/main/java/ch/njol/skript/command/Commands.java b/src/main/java/ch/njol/skript/command/Commands.java index 87801b2cfd1..90a3f5d740a 100644 --- a/src/main/java/ch/njol/skript/command/Commands.java +++ b/src/main/java/ch/njol/skript/command/Commands.java @@ -158,7 +158,7 @@ public void onPlayerCommand(final PlayerCommandPreprocessEvent e) { public void onServerCommand(final ServerCommandEvent e) { if (e.getCommand() == null || e.getCommand().isEmpty() || e.isCancelled()) return; - if (SkriptConfig.enableEffectCommands.value() && e.getCommand().startsWith(SkriptConfig.effectCommandToken.value())) { + if ((Skript.testing() || SkriptConfig.enableEffectCommands.value()) && e.getCommand().startsWith(SkriptConfig.effectCommandToken.value())) { if (handleEffectCommand(e.getSender(), e.getCommand())) { e.setCancelled(true); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprScript.java b/src/main/java/ch/njol/skript/expressions/ExprScript.java index 4ca07457c85..a74529108fa 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprScript.java +++ b/src/main/java/ch/njol/skript/expressions/ExprScript.java @@ -26,6 +26,7 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.parser.ParserInstance; import org.skriptlang.skript.lang.script.Script; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; @@ -52,12 +53,17 @@ public class ExprScript extends SimpleExpression<String> { ); } - @SuppressWarnings("NotNullFieldNotIntialized") + @SuppressWarnings("NotNullFieldNotInitialized") private String name; @Override public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - String name = getParser().getCurrentScript().getConfig().getFileName(); + ParserInstance parser = getParser(); + if (!parser.isActive()) { + Skript.error("You can't use the script expression outside of scripts!"); + return false; + } + String name = parser.getCurrentScript().getConfig().getFileName(); if (name.contains(".")) name = name.substring(0, name.lastIndexOf('.')); this.name = name; @@ -65,7 +71,7 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye } @Override - protected String[] get(Event e) { + protected String[] get(Event event) { return new String[]{name}; } @@ -80,7 +86,7 @@ public Class<? extends String> getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { return "the script's name"; } diff --git a/src/main/java/ch/njol/skript/lang/VariableString.java b/src/main/java/ch/njol/skript/lang/VariableString.java index feb9085ba78..ddd636814d7 100644 --- a/src/main/java/ch/njol/skript/lang/VariableString.java +++ b/src/main/java/ch/njol/skript/lang/VariableString.java @@ -59,6 +59,7 @@ */ public class VariableString implements Expression<String> { + @Nullable private final Script script; private final String orig; @@ -97,10 +98,7 @@ private VariableString(String input) { this.mode = StringMode.MESSAGE; ParserInstance parser = getParser(); - if (!parser.isActive()) - throw new IllegalStateException("VariableString attempted to use constructor without a ParserInstance present at execution."); - - this.script = parser.getCurrentScript(); + this.script = parser.isActive() ? parser.getCurrentScript() : null; this.components = new MessageComponent[] {ChatMessages.plainText(simpleUnformatted)}; } @@ -116,12 +114,9 @@ private VariableString(String orig, Object[] string, StringMode mode) { this.orig = orig; this.string = new Object[string.length]; this.stringUnformatted = new Object[string.length]; - - ParserInstance parser = getParser(); - if (!parser.isActive()) - throw new IllegalStateException("VariableString attempted to use constructor without a ParserInstance present at execution."); - this.script = parser.getCurrentScript(); + ParserInstance parser = getParser(); + this.script = parser.isActive() ? parser.getCurrentScript() : null; // Construct unformatted string and components List<MessageComponent> components = new ArrayList<>(string.length); @@ -550,7 +545,7 @@ public String toString(@Nullable Event event) { } } String complete = builder.toString(); - if (mode == StringMode.VARIABLE_NAME && !types.isEmpty()) { + if (script != null && mode == StringMode.VARIABLE_NAME && !types.isEmpty()) { DefaultVariables data = script.getData(DefaultVariables.class); if (data != null) data.add(complete, types.toArray(new Class<?>[0])); @@ -587,18 +582,22 @@ public String toString(@Nullable Event event, boolean debug) { * @return List<String> of all possible super class code names. */ public List<String> getDefaultVariableNames(String variableName, Event event) { + if (script == null || mode != StringMode.VARIABLE_NAME) { + return Lists.newArrayList(); + } + if (isSimple) { assert simple != null; return Lists.newArrayList(simple, "object"); } - Object[] string = this.string; - assert string != null; + List<StringBuilder> typeHints = Lists.newArrayList(new StringBuilder()); DefaultVariables data = script.getData(DefaultVariables.class); assert data != null : "default variables not present in current script"; // Represents the index of which expression in a variable string, example name::%entity%::%object% the index of 0 will be entity. int hintIndex = 0; + assert string != null; for (Object object : string) { if (!(object instanceof Expression)) { typeHints.forEach(builder -> builder.append(object)); @@ -614,7 +613,7 @@ public List<String> getDefaultVariableNames(String variableName, Event event) { } hintIndex++; } - return typeHints.stream().map(builder -> builder.toString()).collect(Collectors.toList()); + return typeHints.stream().map(StringBuilder::toString).collect(Collectors.toList()); } public boolean isSimple() { diff --git a/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java b/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java index f0b1f274a55..9824e069acc 100644 --- a/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java +++ b/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java @@ -39,6 +39,7 @@ import org.skriptlang.skript.lang.script.ScriptEvent; import org.skriptlang.skript.lang.structure.Structure; +import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -532,8 +533,10 @@ public void deleteCurrentSkriptEvent() { public void setCurrentScript(@Nullable Config currentScript) { if (currentScript == null) return; - //noinspection ConstantConditions - shouldn't be null - Script script = ScriptLoader.getScript(currentScript.getFile()); + File file = currentScript.getFile(); + if (file == null) + return; + Script script = ScriptLoader.getScript(file); if (script != null) setActive(script); } diff --git a/src/main/java/org/skriptlang/skript/lang/script/Script.java b/src/main/java/org/skriptlang/skript/lang/script/Script.java index 2bcb4e412f6..888f53d71c5 100644 --- a/src/main/java/org/skriptlang/skript/lang/script/Script.java +++ b/src/main/java/org/skriptlang/skript/lang/script/Script.java @@ -20,6 +20,7 @@ import ch.njol.skript.config.Config; import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Unmodifiable; import org.skriptlang.skript.lang.structure.Structure; @@ -50,6 +51,7 @@ public final class Script { * @param config The Config containing the contents of this Script. * @param structures The list of Structures contained in this Script. */ + @ApiStatus.Internal public Script(Config config, List<Structure> structures) { this.config = config; this.structures = structures; @@ -65,6 +67,7 @@ public Config getConfig() { /** * @return An unmodifiable list of all Structures within this Script. */ + @Unmodifiable public List<Structure> getStructures() { return Collections.unmodifiableList(structures); } diff --git a/src/test/skript/tests/misc/effect commands.sk b/src/test/skript/tests/misc/effect commands.sk new file mode 100644 index 00000000000..b05dd1ed730 --- /dev/null +++ b/src/test/skript/tests/misc/effect commands.sk @@ -0,0 +1,4 @@ +test "effect commands": + + # assume if something goes wrong we'll get an exception + execute console command "!broadcast ""effect commands test""" From dd38bf31e1bb4c66446ef26faccb1412456c55e8 Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Sat, 25 Feb 2023 11:55:19 +0300 Subject: [PATCH 255/619] Fix plural event values exception (#5475) fix exception --- .../expressions/base/EventValueExpression.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/base/EventValueExpression.java b/src/main/java/ch/njol/skript/expressions/base/EventValueExpression.java index 519e10c0e9e..a08757d33e2 100644 --- a/src/main/java/ch/njol/skript/expressions/base/EventValueExpression.java +++ b/src/main/java/ch/njol/skript/expressions/base/EventValueExpression.java @@ -74,7 +74,7 @@ public class EventValueExpression<T> extends SimpleExpression<T> implements Defa public EventValueExpression(Class<? extends T> c) { this(c, null); } - + public EventValueExpression(Class<? extends T> c, @Nullable Changer<? super T> changer) { assert c != null; this.c = c; @@ -89,14 +89,14 @@ public EventValueExpression(Class<? extends T> c, @Nullable Changer<? super T> c protected T[] get(Event event) { T value = getValue(event); if (value == null) - return (T[]) Array.newInstance(c, 0); + return (T[]) Array.newInstance(componentType, 0); if (single) { T[] one = (T[]) Array.newInstance(c, 1); one[0] = value; return one; } T[] dataArray = (T[]) value; - T[] array = (T[]) Array.newInstance(c.getComponentType(), ((T[]) value).length); + T[] array = (T[]) Array.newInstance(componentType, dataArray.length); System.arraycopy(dataArray, 0, array, 0, array.length); return array; } @@ -161,8 +161,9 @@ public boolean init() { } @Override + @SuppressWarnings("unchecked") public Class<? extends T> getReturnType() { - return c; + return (Class<? extends T>) componentType; } @Override @@ -176,13 +177,13 @@ public String toString(@Nullable Event event, boolean debug) { return "event-" + Classes.getSuperClassInfo(componentType).getName().toString(!single); return Classes.getDebugMessage(getValue(event)); } - + @Override @Nullable @SuppressWarnings("unchecked") public Class<?>[] acceptChange(ChangeMode mode) { if (changer == null) - changer = (Changer<? super T>) Classes.getSuperClassInfo(c).getChanger(); + changer = (Changer<? super T>) Classes.getSuperClassInfo(componentType).getChanger(); return changer == null ? null : changer.acceptChange(mode); } From 86b7e8532eeb0dad7331c26966a2b6097ee5ff5c Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Wed, 1 Mar 2023 10:53:10 +0300 Subject: [PATCH 256/619] Fix Variables in Effect Commands (#5479) fix variables in effect commands --- .../java/ch/njol/skript/lang/Variable.java | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/main/java/ch/njol/skript/lang/Variable.java b/src/main/java/ch/njol/skript/lang/Variable.java index ed40f8b15ea..0b3fc39aa1e 100644 --- a/src/main/java/ch/njol/skript/lang/Variable.java +++ b/src/main/java/ch/njol/skript/lang/Variable.java @@ -73,6 +73,7 @@ public class Variable<T> implements Expression<T> { /** * Script this variable was created in. */ + @Nullable private final Script script; /** @@ -97,10 +98,8 @@ private Variable(VariableString name, Class<? extends T>[] types, boolean local, assert name.isSimple() || name.getMode() == StringMode.VARIABLE_NAME; ParserInstance parser = getParser(); - if (!parser.isActive()) - throw new IllegalStateException("Variable attempted to use constructor without a ParserInstance present at execution."); - this.script = parser.getCurrentScript(); + this.script = parser.isActive() ? parser.getCurrentScript() : null; this.local = local; this.list = list; @@ -191,12 +190,9 @@ public static <T> Variable<T> newInstance(String name, Class<? extends T>[] type boolean isPlural = name.endsWith(SEPARATOR + "*"); ParserInstance parser = ParserInstance.get(); - if (!parser.isActive()) { - Skript.error("A variable must only be created during parsing time."); - return null; - } - Script currentScript = parser.getCurrentScript(); - if (!SkriptConfig.disableVariableStartingWithExpressionWarnings.value() + Script currentScript = parser.isActive() ? parser.getCurrentScript() : null; + if (currentScript != null + && !SkriptConfig.disableVariableStartingWithExpressionWarnings.value() && !currentScript.suppressesWarning(ScriptWarning.VARIABLE_STARTS_WITH_EXPRESSION) && (isLocal ? name.substring(LOCAL_VARIABLE_TOKEN.length()) : name).startsWith("%")) { Skript.warning("Starting a variable's name with an expression is discouraged ({" + name + "}). " + @@ -213,7 +209,7 @@ public static <T> Variable<T> newInstance(String name, Class<? extends T>[] type assert type != null; if (type.isAssignableFrom(hint)) { // Hint matches, use variable with exactly correct type - return new Variable<>(vs, CollectionUtils.array(type), isLocal, isPlural, null); + return new Variable<>(vs, CollectionUtils.array(type), true, isPlural, null); } } @@ -221,16 +217,16 @@ public static <T> Variable<T> newInstance(String name, Class<? extends T>[] type for (Class<? extends T> type : types) { if (Converters.converterExists(hint, type)) { // Hint matches, even though converter is needed - return new Variable<>(vs, CollectionUtils.array(type), isLocal, isPlural, null); + return new Variable<>(vs, CollectionUtils.array(type), true, isPlural, null); } // Special cases if (type.isAssignableFrom(World.class) && hint.isAssignableFrom(String.class)) { // String->World conversion is weird spaghetti code - return new Variable<>(vs, types, isLocal, isPlural, null); + return new Variable<>(vs, types, true, isPlural, null); } else if (type.isAssignableFrom(Player.class) && hint.isAssignableFrom(String.class)) { // String->Player conversion is not available at this point - return new Variable<>(vs, types, isLocal, isPlural, null); + return new Variable<>(vs, types, true, isPlural, null); } } @@ -310,7 +306,7 @@ public <R> Variable<R> getConvertedExpression(Class<R>... to) { */ @Nullable public Object getRaw(Event event) { - DefaultVariables data = script.getData(DefaultVariables.class); + DefaultVariables data = script == null ? null : script.getData(DefaultVariables.class); if (data != null) data.enterScope(); try { From 9ead35ef48fdcf83016bf8449d611f100fe36b22 Mon Sep 17 00:00:00 2001 From: Dylan <djlevy26@gmail.com> Date: Tue, 7 Mar 2023 23:43:35 -0500 Subject: [PATCH 257/619] added event value and experience change event (#5389) --- .../skript/events/EvtExperienceChange.java | 78 +++++++++++++++++++ .../skript/expressions/ExprExperience.java | 59 ++++++++------ 2 files changed, 113 insertions(+), 24 deletions(-) create mode 100644 src/main/java/ch/njol/skript/events/EvtExperienceChange.java diff --git a/src/main/java/ch/njol/skript/events/EvtExperienceChange.java b/src/main/java/ch/njol/skript/events/EvtExperienceChange.java new file mode 100644 index 00000000000..9f184b4606a --- /dev/null +++ b/src/main/java/ch/njol/skript/events/EvtExperienceChange.java @@ -0,0 +1,78 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.events; + +import ch.njol.skript.Skript; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptEvent; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.registrations.EventValues; +import ch.njol.skript.util.Experience; +import ch.njol.skript.util.Getter; +import org.bukkit.event.Event; +import org.bukkit.event.player.PlayerExpChangeEvent; +import org.eclipse.jdt.annotation.Nullable; + +public class EvtExperienceChange extends SkriptEvent { + + static { + Skript.registerEvent("Experience Change", EvtExperienceChange.class, PlayerExpChangeEvent.class, "[player] (level progress|[e]xp|experience) (change|update|:increase|:decrease)") + .description("Called when a player's experience changes.") + .examples( + "on level progress change:", + "\tset {_xp} to event-experience", + "\tbroadcast \"%{_xp}%\"" + ) + .since("INSERT VERSION"); + EventValues.registerEventValue(PlayerExpChangeEvent.class, Experience.class, new Getter<Experience, PlayerExpChangeEvent>() { + @Override + @Nullable + public Experience get(PlayerExpChangeEvent event) { + return new Experience(event.getAmount()); + } + }, EventValues.TIME_NOW); + } + + private static final int ANY = 0, UP = 1, DOWN = 2; + private int mode = ANY; + + @Override + public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult) { + if (parseResult.hasTag("increase")) { + mode = UP; + } else if (parseResult.hasTag("decrease")) { + mode = DOWN; + } + return true; + } + + @Override + public boolean check(Event event) { + if (mode == ANY) + return true; + PlayerExpChangeEvent expChangeEvent = (PlayerExpChangeEvent) event; + return mode == UP ? expChangeEvent.getAmount() > 0 : expChangeEvent.getAmount() < 0; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "player level progress " + (mode == ANY ? "change" : mode == UP ? "increase" : "decrease"); + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprExperience.java b/src/main/java/ch/njol/skript/expressions/ExprExperience.java index f3e2591e08f..febf5526d2e 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprExperience.java +++ b/src/main/java/ch/njol/skript/expressions/ExprExperience.java @@ -20,6 +20,7 @@ import org.bukkit.event.Event; import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.player.PlayerExpChangeEvent; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; @@ -49,8 +50,8 @@ "on break of diamond ore:", "\tif tool of player = diamond pickaxe:", "\t\tadd 100 to dropped experience"}) -@Since("2.1, 2.5.3 (block break event)") -@Events({"experience spawn", "break / mine"}) +@Since("2.1, 2.5.3 (block break event), INSERT VERSION (experience change event)") +@Events({"experience spawn", "break / mine", "experience change"}) public class ExprExperience extends SimpleExpression<Experience> { static { Skript.registerExpression(ExprExperience.class, Experience.class, ExpressionType.SIMPLE, "[the] (spawned|dropped|) [e]xp[erience] [orb[s]]"); @@ -58,8 +59,8 @@ public class ExprExperience extends SimpleExpression<Experience> { @Override public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { - if (!getParser().isCurrentEvent(ExperienceSpawnEvent.class, BlockBreakEvent.class)) { - Skript.error("The experience expression can only be used in experience spawn and block break events"); + if (!getParser().isCurrentEvent(ExperienceSpawnEvent.class, BlockBreakEvent.class, PlayerExpChangeEvent.class)) { + Skript.error("The experience expression can only be used in experience spawn, block break and player experience change events"); return false; } return true; @@ -72,6 +73,8 @@ protected Experience[] get(final Event e) { return new Experience[] {new Experience(((ExperienceSpawnEvent) e).getSpawnedXP())}; else if (e instanceof BlockBreakEvent) return new Experience[] {new Experience(((BlockBreakEvent) e).getExpToDrop())}; + else if (e instanceof PlayerExpChangeEvent) + return new Experience[] {new Experience(((PlayerExpChangeEvent) e).getAmount())}; else return new Experience[0]; } @@ -95,27 +98,31 @@ public Class<?>[] acceptChange(final ChangeMode mode) { @Override public void change(final Event e, final @Nullable Object[] delta, final ChangeMode mode) { - double d; - if (e instanceof ExperienceSpawnEvent) - d = ((ExperienceSpawnEvent) e).getSpawnedXP(); - else if (e instanceof BlockBreakEvent) - d = ((BlockBreakEvent) e).getExpToDrop(); - else + double eventExp; + if (e instanceof ExperienceSpawnEvent) { + eventExp = ((ExperienceSpawnEvent) e).getSpawnedXP(); + } else if (e instanceof BlockBreakEvent) { + eventExp = ((BlockBreakEvent) e).getExpToDrop(); + } else if (e instanceof PlayerExpChangeEvent) { + eventExp = ((PlayerExpChangeEvent) e).getAmount(); + } else { return; - - if (delta != null) - for (final Object o : delta) { - final double v = o instanceof Experience ? ((Experience) o).getXP() : ((Number) o).doubleValue(); + } + if (delta == null) { + eventExp = 0; + } else { + for (Object obj : delta) { + double value = obj instanceof Experience ? ((Experience) obj).getXP() : ((Number) obj).doubleValue(); switch (mode) { case ADD: - d += v; + eventExp += value; break; case SET: - d = v; + eventExp = value; break; case REMOVE: case REMOVE_ALL: - d -= v; + eventExp -= value; break; case RESET: case DELETE: @@ -123,14 +130,18 @@ else if (e instanceof BlockBreakEvent) break; } } - else - d = 0; + } + - d = Math.max(0, Math.round(d)); - if (e instanceof ExperienceSpawnEvent) - ((ExperienceSpawnEvent) e).setSpawnedXP((int) d); - else - ((BlockBreakEvent) e).setExpToDrop((int) d); + eventExp = Math.max(0, Math.round(eventExp)); + int roundedEventExp = (int) eventExp; + if (e instanceof ExperienceSpawnEvent) { + ((ExperienceSpawnEvent) e).setSpawnedXP(roundedEventExp); + } else if (e instanceof BlockBreakEvent) { + ((BlockBreakEvent) e).setExpToDrop(roundedEventExp); + } else if (e instanceof PlayerExpChangeEvent) { + ((PlayerExpChangeEvent) e).setAmount(roundedEventExp); + } } @Override From e7ea29f1478fef1d0e1bf071b53410fce6ea8194 Mon Sep 17 00:00:00 2001 From: sovdee <ethansovde@gmail.com> Date: Wed, 8 Mar 2023 03:15:17 -0500 Subject: [PATCH 258/619] Add Support for Entities/Chunks/Worlds/Blocks in CondIsWithin (#5371) --- .../njol/skript/conditions/CondIsInWorld.java | 80 --------- .../njol/skript/conditions/CondIsWithin.java | 161 ++++++++++++++++++ .../conditions/CondIsWithinLocation.java | 79 --------- .../tests/syntaxes/conditions/CondIsWithin.sk | 56 ++++++ 4 files changed, 217 insertions(+), 159 deletions(-) delete mode 100644 src/main/java/ch/njol/skript/conditions/CondIsInWorld.java create mode 100644 src/main/java/ch/njol/skript/conditions/CondIsWithin.java delete mode 100644 src/main/java/ch/njol/skript/conditions/CondIsWithinLocation.java create mode 100644 src/test/skript/tests/syntaxes/conditions/CondIsWithin.sk diff --git a/src/main/java/ch/njol/skript/conditions/CondIsInWorld.java b/src/main/java/ch/njol/skript/conditions/CondIsInWorld.java deleted file mode 100644 index 8b84895b574..00000000000 --- a/src/main/java/ch/njol/skript/conditions/CondIsInWorld.java +++ /dev/null @@ -1,80 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.conditions; - -import org.bukkit.World; -import org.bukkit.entity.Entity; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.conditions.base.PropertyCondition; -import ch.njol.skript.conditions.base.PropertyCondition.PropertyType; -import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Examples; -import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.Since; -import ch.njol.skript.lang.Condition; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.util.Kleenean; - -/** - * @author Peter Güttinger - */ -@Name("Is in World") -@Description("Checks whether an entity is in a specific world.") -@Examples({"player is in \"world\"", - "argument isn't in world \"world_nether\"", - "the player is in the world of the victim"}) -@Since("1.4") -public class CondIsInWorld extends Condition { - - static { - PropertyCondition.register(CondIsInWorld.class, "in [[the] world[s]] %worlds%", "entities"); - } - - @SuppressWarnings("null") - private Expression<Entity> entities; - @SuppressWarnings("null") - private Expression<World> worlds; - - @SuppressWarnings({"unchecked", "null"}) - @Override - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { - entities = (Expression<Entity>) exprs[0]; - worlds = (Expression<World>) exprs[1]; - setNegated(matchedPattern == 1); - return true; - } - - @Override - public boolean check(final Event e) { - return entities.check(e, - entity -> worlds.check(e, - world -> entity.getWorld() == world - ), isNegated()); - } - - @Override - public String toString(final @Nullable Event e, final boolean debug) { - return PropertyCondition.toString(this, PropertyType.BE, e, debug, entities, - "in the " + (worlds.isSingle() ? "world " : "worlds ") + worlds.toString(e, debug)); - } - -} diff --git a/src/main/java/ch/njol/skript/conditions/CondIsWithin.java b/src/main/java/ch/njol/skript/conditions/CondIsWithin.java new file mode 100644 index 00000000000..fee755ec03b --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondIsWithin.java @@ -0,0 +1,161 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.conditions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Condition; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.util.AABB; +import ch.njol.util.Kleenean; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.event.Event; +import org.bukkit.util.BoundingBox; +import org.bukkit.util.Vector; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Is Within") +@Description({ + "Whether a location is within something else. The \"something\" can be a block, an entity, a chunk, a world, " + + "or a cuboid formed by two other locations.", + "Note that using the <a href='conditions.html#CondCompare'>is between</a> condition will refer to a straight line " + + "between locations, while this condition will refer to the cuboid between locations." +}) +@Examples({ + "if player's location is within {_loc1} and {_loc2}:", + "\tsend \"You are in a PvP zone!\" to player", + "", + "if player is in world(\"world\"):", + "\tsend \"You are in the overworld!\" to player", + "", + "if attacker's location is inside of victim:", + "\tcancel event", + "\tsend \"Back up!\" to attacker and victim", +}) +@Since("INSERT VERSION") +@RequiredPlugins("MC 1.17+ (within block)") +public class CondIsWithin extends Condition { + + static { + String validTypes = "entity/chunk/world"; + if (Skript.methodExists(Block.class, "getCollisionShape")) + validTypes += "/block"; + + Skript.registerCondition(CondIsWithin.class, + "%locations% (is|are) within %location% and %location%", + "%locations% (isn't|is not|aren't|are not) within %location% and %location%", + "%locations% (is|are) (within|in[side [of]]) %" + validTypes + "%", + "%locations% (isn't|is not|aren't|are not) (within|in[side [of]]) %" + validTypes + "%" + ); + } + + private Expression<Location> locsToCheck, loc1, loc2; + private Expression<?> area; + private boolean withinLocations; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + setNegated(matchedPattern % 2 == 1); + locsToCheck = (Expression<Location>) exprs[0]; + if (matchedPattern <= 1) { + // within two locations + withinLocations = true; + loc1 = (Expression<Location>) exprs[1]; + loc2 = (Expression<Location>) exprs[2]; + } else { + // within an entity/block/chunk/world + withinLocations = false; + area = exprs[1]; + } + return true; + } + + @Override + public boolean check(Event event) { + // within two locations + if (withinLocations) { + Location one = loc1.getSingle(event); + Location two = loc2.getSingle(event); + if (one == null || two == null || one.getWorld() != two.getWorld()) + return false; + AABB box = new AABB(one, two); + return locsToCheck.check(event, box::contains, isNegated()); + } + + // else, within an entity/block/chunk/world + Object area = this.area.getSingle(event); + if (area == null) + return false; + + // Entities + if (area instanceof Entity) { + BoundingBox box = ((Entity) area).getBoundingBox(); + return locsToCheck.check(event, (loc) -> box.contains(loc.toVector()), isNegated()); + } + + // Blocks + if (area instanceof Block) { + for (BoundingBox box : ((Block) area).getCollisionShape().getBoundingBoxes()) { + // getCollisionShape().getBoundingBoxes() returns a list of bounding boxes relative to the block's position, + // so we need to subtract the block position from each location + Vector blockVector = ((Block) area).getLocation().toVector(); + if (!locsToCheck.check(event, (loc) -> box.contains(loc.toVector().subtract(blockVector)), isNegated())) { + return false; + } + } + // if all locations are within the block, return true + return true; + } + + // Chunks + if (area instanceof Chunk) { + return locsToCheck.check(event, (loc) -> loc.getChunk() == area, isNegated()); + } + + // Worlds + if (area instanceof World) { + return locsToCheck.check(event, (loc) -> loc.getWorld() == area, isNegated()); + } + + // fall-back + return false; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + String str = locsToCheck.toString(event, debug) + " is within "; + if (withinLocations) { + str += loc1.toString(event, debug) + " and " + loc2.toString(event, debug); + } else { + str += area.toString(event, debug); + } + return str; + } + +} diff --git a/src/main/java/ch/njol/skript/conditions/CondIsWithinLocation.java b/src/main/java/ch/njol/skript/conditions/CondIsWithinLocation.java deleted file mode 100644 index b607ba19f48..00000000000 --- a/src/main/java/ch/njol/skript/conditions/CondIsWithinLocation.java +++ /dev/null @@ -1,79 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.conditions; - -import ch.njol.skript.Skript; -import ch.njol.skript.conditions.base.PropertyCondition; -import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Examples; -import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.Since; -import ch.njol.skript.lang.Condition; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.util.AABB; -import ch.njol.util.Kleenean; -import org.bukkit.Location; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - -@Name("Is Within Location") -@Description({ - "Whether a location is within two other locations forming a cuboid.", - "Using the <a href='conditions.html#CondCompare'>is between</a> condition will refer to a straight line between locations." -}) -@Examples({ - "if player's location is within {_loc1} and {_loc2}:", - "\tsend \"You are in a PvP zone!\" to player" -}) -@Since("INSERT VERSION") -public class CondIsWithinLocation extends Condition { - - static { - PropertyCondition.register(CondIsWithinLocation.class, "within %location% and %location%", "locations"); - } - - private Expression<Location> locsToCheck, loc1, loc2; - - @Override - @SuppressWarnings("unchecked") - public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - setNegated(matchedPattern == 1); - locsToCheck = (Expression<Location>) exprs[0]; - loc1 = (Expression<Location>) exprs[1]; - loc2 = (Expression<Location>) exprs[2]; - return true; - } - - @Override - public boolean check(Event event) { - Location one = loc1.getSingle(event); - Location two = loc2.getSingle(event); - if (one == null || two == null || one.getWorld() != two.getWorld()) - return false; - AABB box = new AABB(one, two); - return locsToCheck.check(event, box::contains, isNegated()); - } - - @Override - public String toString(@Nullable Event event, boolean debug) { - return locsToCheck.toString(event, debug) + " is within " + loc1.toString(event, debug) + " and " + loc2.toString(event, debug); - } - -} diff --git a/src/test/skript/tests/syntaxes/conditions/CondIsWithin.sk b/src/test/skript/tests/syntaxes/conditions/CondIsWithin.sk new file mode 100644 index 00000000000..a0c4339f556 --- /dev/null +++ b/src/test/skript/tests/syntaxes/conditions/CondIsWithin.sk @@ -0,0 +1,56 @@ +test "within condition" when running minecraft "1.17": + # two locations + set {_loc1} to location(0, 0, 0, "world") + set {_loc2} to location(20, 20, 20, "world") + assert location(10, 10, 10, "world") is within {_loc1} and {_loc2} with "failed within two locs ##1" + assert location(10, -10, 10, "world") is not within {_loc1} and {_loc2} with "failed within two locs ##2" + + # chunks + set {_chunk1} to chunk at {_loc1} + assert location(10, 10, 10, "world") is within {_chunk1} with "failed within chunk ##1" + assert location(-10, 10, -10, "world") is not within {_chunk1} with "failed within chunk ##2" + + # worlds + assert location(10, 10, 10, "world") is within world("world") with "failed within world ##1" + + # blocks + set block at {_loc1} to stone + assert {_loc1} is within block at {_loc1} with "failed within block ##1" + assert {_loc2} is not within block at {_loc1} with "failed within block ##2" + # special case, non-full blocks + set block at {_loc1} to lime carpet + assert {_loc1} is within block at {_loc1} with "failed within block ##3" + assert ({_loc1} ~ vector(0,0.3,0)) is not within block at {_loc1} with "failed within block ##4" + + # entities + set {_loc} to spawn of world "world" + spawn a pig at {_loc} + set {_pig} to last spawned entity + assert {_loc} is within {_pig} with "failed within entity ##1" + assert {_loc1} is not within {_pig} with "failed within entity ##2" + + delete random entity of {_pig} + +test "within condition" when running below minecraft "1.17": + # two locations + set {_loc1} to location(0, 0, 0, "world") + set {_loc2} to location(20, 20, 20, "world") + assert location(10, 10, 10, "world") is within {_loc1} and {_loc2} with "failed within two locs ##1" + assert location(10, -10, 10, "world") is not within {_loc1} and {_loc2} with "failed within two locs ##2" + + # chunks + set {_chunk1} to chunk at {_loc1} + assert location(10, 10, 10, "world") is within {_chunk1} with "failed within chunk ##1" + assert location(-10, 10, -10, "world") is not within {_chunk1} with "failed within chunk ##2" + + # worlds + assert location(10, 10, 10, "world") is within world("world") with "failed within world ##1" + + # entities + set {_loc} to spawn of world "world" + spawn a pig at {_loc} + set {_pig} to last spawned entity + assert {_loc} is within {_pig} with "failed within entity ##1" + assert {_loc1} is not within {_pig} with "failed within entity ##2" + + delete random entity of {_pig} From 2334e0a91548c63208fe1be2e0734fec55254639 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Wed, 8 Mar 2023 01:47:16 -0700 Subject: [PATCH 259/619] Adds back the JUnit testing system (#4979) --- .github/workflows/junit-17-builds.yml | 27 ++ .github/workflows/junit-8-builds.yml | 27 ++ .gitignore | 3 +- build.gradle | 80 ++++-- src/main/java/ch/njol/skript/Skript.java | 230 +++++++++++------- src/main/java/ch/njol/skript/SkriptAddon.java | 115 +++------ .../java/ch/njol/skript/SkriptCommand.java | 6 +- .../skript/SkriptCommandTabCompleter.java | 2 +- .../{tests => test}/platform/Environment.java | 75 +++++- .../platform/PlatformMain.java | 41 ++-- .../platform}/package-info.java | 2 +- .../runner/CondMethodExists.java | 3 +- .../runner/CondMinecraftVersion.java | 2 +- .../{tests => test}/runner/EffAssert.java | 41 ++-- .../skript/test/runner/EffObjectives.java | 122 ++++++++++ .../{tests => test}/runner/EvtTestCase.java | 2 +- .../skript/test/runner/ExprJUnitTest.java | 71 ++++++ .../skript/test/runner/SkriptJUnitTest.java | 154 ++++++++++++ .../runner/SkriptTestEvent.java | 2 +- .../{tests => test}/runner/TestFunctions.java | 2 +- .../{tests => test}/runner/TestMode.java | 9 +- .../{tests => test}/runner/TestTracker.java | 26 +- .../{tests => test/runner}/package-info.java | 2 +- .../{tests => test/utils}/TestResults.java | 2 +- .../platform => test/utils}/package-info.java | 4 +- src/main/java/ch/njol/skript/util/Utils.java | 90 ++++++- .../skript/variables/FlatFileStorageTest.java | 65 +++++ .../test/tests/aliases/AliasesTest.java | 59 +++++ .../test/tests/aliases/package-info.java | 24 ++ .../test/tests/classes/ClassesTest.java | 74 ++++++ .../test/tests/classes/package-info.java | 24 ++ .../skript/test/tests/config/NodeTest.java | 58 +++++ .../test/tests/config/package-info.java | 24 ++ .../test/tests/localization/NounTest.java | 65 +++++ .../test/tests/localization/UtilsPlurals.java | 64 +++++ .../test/tests/localization/package-info.java | 24 ++ .../tests/regression/SimpleJUnitTest.java | 68 ++++++ .../test/tests/regression/package-info.java | 24 ++ .../test/tests/syntaxes/package-info.java | 24 ++ .../skript/test/tests/utils/UtilsTest.java | 69 ++++++ .../skript/test/tests/utils/package-info.java | 24 ++ src/test/skript/README.md | 5 + src/test/skript/tests/junit/README.md | 8 + .../skript/tests/junit/SimpleJUnitTest.sk | 16 ++ 44 files changed, 1582 insertions(+), 277 deletions(-) create mode 100644 .github/workflows/junit-17-builds.yml create mode 100644 .github/workflows/junit-8-builds.yml rename src/main/java/ch/njol/skript/{tests => test}/platform/Environment.java (78%) rename src/main/java/ch/njol/skript/{tests => test}/platform/PlatformMain.java (81%) rename src/main/java/ch/njol/skript/{tests/runner => test/platform}/package-info.java (96%) rename src/main/java/ch/njol/skript/{tests => test}/runner/CondMethodExists.java (97%) rename src/main/java/ch/njol/skript/{tests => test}/runner/CondMinecraftVersion.java (98%) rename src/main/java/ch/njol/skript/{tests => test}/runner/EffAssert.java (77%) create mode 100644 src/main/java/ch/njol/skript/test/runner/EffObjectives.java rename src/main/java/ch/njol/skript/{tests => test}/runner/EvtTestCase.java (98%) create mode 100644 src/main/java/ch/njol/skript/test/runner/ExprJUnitTest.java create mode 100644 src/main/java/ch/njol/skript/test/runner/SkriptJUnitTest.java rename src/main/java/ch/njol/skript/{tests => test}/runner/SkriptTestEvent.java (96%) rename src/main/java/ch/njol/skript/{tests => test}/runner/TestFunctions.java (98%) rename src/main/java/ch/njol/skript/{tests => test}/runner/TestMode.java (91%) rename src/main/java/ch/njol/skript/{tests => test}/runner/TestTracker.java (90%) rename src/main/java/ch/njol/skript/{tests => test/runner}/package-info.java (96%) rename src/main/java/ch/njol/skript/{tests => test/utils}/TestResults.java (98%) rename src/main/java/ch/njol/skript/{tests/platform => test/utils}/package-info.java (92%) create mode 100644 src/test/java/ch/njol/skript/variables/FlatFileStorageTest.java create mode 100644 src/test/java/org/skriptlang/skript/test/tests/aliases/AliasesTest.java create mode 100644 src/test/java/org/skriptlang/skript/test/tests/aliases/package-info.java create mode 100644 src/test/java/org/skriptlang/skript/test/tests/classes/ClassesTest.java create mode 100644 src/test/java/org/skriptlang/skript/test/tests/classes/package-info.java create mode 100644 src/test/java/org/skriptlang/skript/test/tests/config/NodeTest.java create mode 100644 src/test/java/org/skriptlang/skript/test/tests/config/package-info.java create mode 100644 src/test/java/org/skriptlang/skript/test/tests/localization/NounTest.java create mode 100644 src/test/java/org/skriptlang/skript/test/tests/localization/UtilsPlurals.java create mode 100644 src/test/java/org/skriptlang/skript/test/tests/localization/package-info.java create mode 100644 src/test/java/org/skriptlang/skript/test/tests/regression/SimpleJUnitTest.java create mode 100644 src/test/java/org/skriptlang/skript/test/tests/regression/package-info.java create mode 100644 src/test/java/org/skriptlang/skript/test/tests/syntaxes/package-info.java create mode 100644 src/test/java/org/skriptlang/skript/test/tests/utils/UtilsTest.java create mode 100644 src/test/java/org/skriptlang/skript/test/tests/utils/package-info.java create mode 100644 src/test/skript/tests/junit/README.md create mode 100644 src/test/skript/tests/junit/SimpleJUnitTest.sk diff --git a/.github/workflows/junit-17-builds.yml b/.github/workflows/junit-17-builds.yml new file mode 100644 index 00000000000..eca1ebb9706 --- /dev/null +++ b/.github/workflows/junit-17-builds.yml @@ -0,0 +1,27 @@ +name: JUnit (MC 1.17+) + +on: + push: + branches: + - master + - 'dev/**' + pull_request: + +jobs: + build: + if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'adopt' + cache: gradle + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Build Skript and run JUnit + run: ./gradlew clean JUnitJava17 diff --git a/.github/workflows/junit-8-builds.yml b/.github/workflows/junit-8-builds.yml new file mode 100644 index 00000000000..93971c79624 --- /dev/null +++ b/.github/workflows/junit-8-builds.yml @@ -0,0 +1,27 @@ +name: JUnit (MC 1.13-1.16) + +on: + push: + branches: + - master + - 'dev/**' + pull_request: + +jobs: + build: + if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'adopt' + cache: gradle + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Build Skript and run JUnit + run: ./gradlew clean JUnitJava8 diff --git a/.gitignore b/.gitignore index 477d2c72451..fbddca8fd46 100755 --- a/.gitignore +++ b/.gitignore @@ -220,5 +220,6 @@ gradle-app.setting # End of https://www.toptal.com/developers/gitignore/api/intellij+all,gradle,java,eclipse,git -# Skript test runners +# TODO remove this in the future after some time since https://github.com/SkriptLang/Skript/pull/4979 +# This ensures developers don't upload their old existing test_runners/ folder. test_runners/ diff --git a/build.gradle b/build.gradle index 84bd6feba8d..fff7d4c2e69 100644 --- a/build.gradle +++ b/build.gradle @@ -11,6 +11,10 @@ plugins { id 'java' } +configurations { + testImplementation.extendsFrom testShadow +} + allprojects { repositories { mavenCentral() @@ -31,7 +35,11 @@ dependencies { implementation group: 'net.milkbowl.vault', name: 'Vault', version: '1.7.1', { exclude group: 'org.bstats', module: 'bstats-bukkit' } + implementation fileTree(dir: 'lib', include: '*.jar') + + testShadow group: 'junit', name: 'junit', version: '4.12' + testShadow group: 'org.easymock', name: 'easymock', version: '4.2' } compileJava.options.encoding = 'UTF-8' @@ -47,6 +55,12 @@ task checkAliases { } } +task testJar(type: ShadowJar) { + dependsOn(compileTestJava, licenseTest) + archiveName 'Skript-JUnit.jar' + from sourceSets.test.output, sourceSets.main.output, project.configurations.testShadow +} + task jar(overwrite: true, type: ShadowJar) { dependsOn checkAliases archiveName jarName ? 'Skript.jar' : jarName @@ -64,7 +78,7 @@ task relocateShadowJar(type: ConfigureShadowRelocation) { task sourceJar(type: Jar) { from sourceSets.main.allJava - archiveClassifier = "sources" + archiveClassifier = 'sources' } tasks.withType(ShadowJar) { @@ -106,8 +120,8 @@ processResources { publishing { publications { maven(MavenPublication) { - groupId "com.github.SkriptLang" - artifactId "Skript" + groupId 'com.github.SkriptLang' + artifactId 'Skript' version project.version artifact sourceJar artifact tasks.jar @@ -116,11 +130,11 @@ publishing { repositories { maven { - name = "repo" - url = "https://repo.skriptlang.org/releases" + name = 'repo' + url = 'https://repo.skriptlang.org/releases' credentials { - username = System.getenv("MAVEN_USERNAME") - password = System.getenv("MAVEN_PASSWORD") + username = System.getenv('MAVEN_USERNAME') + password = System.getenv('MAVEN_PASSWORD') } } } @@ -167,9 +181,15 @@ tasks.register('testNaming') { } // Create a test task with given name, environments dir/file, dev mode and java version. -void createTestTask(String name, String environments, boolean devMode, int javaVersion, boolean genDocs) { +void createTestTask(String name, String desc, String environment, boolean devMode, int javaVersion, boolean genDocs, boolean junit) { tasks.register(name, JavaExec) { - dependsOn nightlyRelease, testNaming + description = desc; + dependsOn licenseTest + if (junit) { + dependsOn testJar + } else { + dependsOn nightlyRelease, testNaming + } javaLauncher = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(javaVersion) } @@ -178,18 +198,19 @@ void createTestTask(String name, String environments, boolean devMode, int javaV } group = 'execution' classpath = files([ - 'build' + File.separator + 'libs' + File.separator + 'Skript-nightly.jar', + 'build' + File.separator + 'libs' + File.separator + (junit ? 'Skript-JUnit.jar' : 'Skript-nightly.jar'), project.configurations.runtimeClasspath.find { it.name.startsWith('gson') }, sourceSets.main.runtimeClasspath ]) - main = 'ch.njol.skript.tests.platform.PlatformMain' + main = 'ch.njol.skript.test.platform.PlatformMain' args = [ - 'test_runners', - 'src/test/skript/tests', + 'build/test_runners', + junit ? 'src/test/skript/tests/junit' : 'src/test/skript/tests', 'src/test/resources/runner_data', - environments, + environment, devMode, - genDocs + genDocs, + junit ] } } @@ -199,20 +220,31 @@ def latestJava = 17 def oldestJava = 8 tasks.withType(JavaCompile).configureEach { - options.compilerArgs += ["-source", "" + oldestJava, "-target", "" + oldestJava] + options.compilerArgs += ['-source', '' + oldestJava, '-target', '' + oldestJava] +} + +createTestTask('JUnitQuick', 'Runs JUnit tests on one environment being the latest supported Java and Minecraft.', 'src/test/skript/environments/' + latestEnv, false, latestJava, false, true) +createTestTask('JUnitJava17', 'Runs JUnit tests on all Java 17 environments.', 'src/test/skript/environments/java17', false, latestJava, false, true) +createTestTask('JUnitJava8', 'Runs JUnit tests on all Java 8 environments.', 'src/test/skript/environments/java8', false, oldestJava, false, true) +tasks.register('JUnit') { + description = 'Runs JUnit tests on all environments.' + dependsOn JUnitJava8, JUnitJava17 } // Register different Skript testing tasks -createTestTask('quickTest', 'src/test/skript/environments/' + latestEnv, false, latestJava, false) -createTestTask('skriptTestJava17', 'src/test/skript/environments/java17', false, latestJava, false) -createTestTask('skriptTestJava8', 'src/test/skript/environments/java8', false, oldestJava, false) -createTestTask('skriptTestDev', 'src/test/skript/environments/' + (project.property('testEnv') == null +createTestTask('quickTest', 'Runs tests on one environment being the latest supported Java and Minecraft.', 'src/test/skript/environments/' + latestEnv, false, latestJava, false, false) +createTestTask('skriptTestJava17', 'Runs tests on all Java 17 environments.', 'src/test/skript/environments/java17', false, latestJava, false, false) +createTestTask('skriptTestJava8', 'Runs tests on all Java 8 environments.', 'src/test/skript/environments/java8', false, oldestJava, false, false) +createTestTask('skriptTestDev', 'Runs testing server and uses \'system.in\' for command input, stop server to finish.', 'src/test/skript/environments/' + (project.property('testEnv') == null ? latestEnv : project.property('testEnv') + '.json'), true, Integer.parseInt(project.property('testEnvJavaVersion') == null - ? latestJava : project.property('testEnvJavaVersion')), false) -tasks.register('skriptTest') {dependsOn skriptTestJava8, skriptTestJava17} -createTestTask('genDocs', 'src/test/skript/environments/' + (project.property('testEnv') == null + ? latestJava : project.property('testEnvJavaVersion')), false, false) +tasks.register('skriptTest') { + description = 'Runs tests on all environments.' + dependsOn skriptTestJava8, skriptTestJava17 +} +createTestTask('genDocs', 'Generates the Skript documentation website html files.', 'src/test/skript/environments/' + (project.property('testEnv') == null ? latestEnv : project.property('testEnv') + '.json'), false, Integer.parseInt(project.property('testEnvJavaVersion') == null - ? latestJava : project.property('testEnvJavaVersion')), true) + ? latestJava : project.property('testEnvJavaVersion')), true, false) // Build flavor configurations task githubResources(type: ProcessResources) { diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 1b86ff51497..251c4d9afe3 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -18,6 +18,71 @@ */ package ch.njol.skript; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.Thread.UncaughtExceptionHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.logging.Filter; +import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; + +import org.bstats.bukkit.Metrics; +import org.bstats.charts.SimplePie; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.Server; +import org.bukkit.command.CommandSender; +import org.bukkit.command.PluginCommand; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.server.PluginDisableEvent; +import org.bukkit.event.server.ServerCommandEvent; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.plugin.java.JavaPlugin; +import org.eclipse.jdt.annotation.Nullable; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.skriptlang.skript.lang.entry.EntryValidator; +import org.skriptlang.skript.lang.script.Script; +import org.skriptlang.skript.lang.structure.Structure; +import org.skriptlang.skript.lang.structure.StructureInfo; + +import com.google.common.collect.Lists; +import com.google.gson.Gson; + import ch.njol.skript.aliases.Aliases; import ch.njol.skript.bukkitutil.BurgerHelper; import ch.njol.skript.classes.ClassInfo; @@ -62,9 +127,11 @@ import org.skriptlang.skript.lang.comparator.Comparators; import org.skriptlang.skript.lang.converter.Converters; import ch.njol.skript.registrations.EventValues; -import ch.njol.skript.tests.runner.SkriptTestEvent; -import ch.njol.skript.tests.runner.TestMode; -import ch.njol.skript.tests.runner.TestTracker; +import ch.njol.skript.test.runner.EffObjectives; +import ch.njol.skript.test.runner.SkriptJUnitTest; +import ch.njol.skript.test.runner.SkriptTestEvent; +import ch.njol.skript.test.runner.TestMode; +import ch.njol.skript.test.runner.TestTracker; import ch.njol.skript.timings.SkriptTimings; import ch.njol.skript.update.ReleaseManifest; import ch.njol.skript.update.ReleaseStatus; @@ -87,66 +154,6 @@ import ch.njol.util.StringUtils; import ch.njol.util.coll.iterator.CheckedIterator; import ch.njol.util.coll.iterator.EnumerationIterable; -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; -import org.bukkit.Server; -import org.bukkit.command.CommandSender; -import org.bukkit.command.PluginCommand; -import org.bukkit.entity.Player; -import org.bukkit.event.Event; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerCommandPreprocessEvent; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.event.server.PluginDisableEvent; -import org.bukkit.event.server.ServerCommandEvent; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.PluginDescriptionFile; -import org.bukkit.plugin.java.JavaPlugin; -import org.eclipse.jdt.annotation.Nullable; -import org.skriptlang.skript.lang.entry.EntryValidator; -import org.skriptlang.skript.lang.script.Script; -import org.skriptlang.skript.lang.structure.Structure; -import org.skriptlang.skript.lang.structure.StructureInfo; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.lang.Thread.UncaughtExceptionHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.logging.Filter; -import java.util.logging.Level; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.zip.ZipEntry; -import java.util.zip.ZipException; -import java.util.zip.ZipFile; // TODO meaningful error if someone uses an %expression with percent signs% outside of text or a variable @@ -354,7 +361,6 @@ public static void disableHookRegistration(Class<? extends Hook<?>>... hooks) { * The folder containing all Scripts. * Never reference this field directly. Use {@link #getScriptsFolder()}. */ - @SuppressWarnings("NotNullFieldNotInitialized") private File scriptsFolder; /** @@ -583,7 +589,7 @@ public void run() { info("Preparing Skript for testing..."); tainted = true; try { - getAddonInstance().loadClasses("ch.njol.skript", "tests"); + getAddonInstance().loadClasses("ch.njol.skript.test.runner"); } catch (IOException e) { Skript.exception("Failed to load testing environment."); Bukkit.getServer().shutdown(); @@ -637,26 +643,19 @@ protected void afterErrors() { // Skript initialization done debug("Early init done"); - Bukkit.getScheduler().runTaskLater(Skript.this, () -> { - if (TestMode.ENABLED) { // Ignore late init (scripts, etc.) in test mode + if (TestMode.ENABLED) { + Bukkit.getScheduler().runTaskLater(Skript.this, () -> info("Skript testing environment enabled, starting soon..."), 1); + // Ignore late init (scripts, etc.) in test mode + Bukkit.getScheduler().runTaskLater(Skript.this, () -> { + // Delay is in Minecraft ticks. + long shutdownDelay = 0; if (TestMode.GEN_DOCS) { Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "skript gen-docs"); - String results = new Gson().toJson(TestTracker.collectResults()); - try { - Files.write(TestMode.RESULTS_FILE, results.getBytes(StandardCharsets.UTF_8)); - } catch (IOException e) { - Skript.exception(e, "Failed to write test results."); - } - // Delay server shutdown to stop the server from crashing because the current tick takes a long time due to all the tests - Bukkit.getScheduler().runTaskLater(Skript.this, () -> { - Bukkit.getServer().shutdown(); - }, 5); - return; - } - if (TestMode.DEV_MODE) { // Run tests NOW! + } else if (TestMode.DEV_MODE) { // Developer controlled environment. info("Test development mode enabled. Test scripts are at " + TestMode.TEST_DIR); + return; } else { - info("Running all tests from " + TestMode.TEST_DIR); + info("Loading all tests from " + TestMode.TEST_DIR); // Treat parse errors as fatal testing failure CountingLogHandler errorCounter = new CountingLogHandler(Level.SEVERE); @@ -670,8 +669,6 @@ protected void afterErrors() { } Bukkit.getPluginManager().callEvent(new SkriptTestEvent()); - - info("Collecting results to " + TestMode.RESULTS_FILE); if (errorCounter.getCount() > 0) { TestTracker.testStarted("parse scripts"); TestTracker.testFailed(errorCounter.getCount() + " error(s) found"); @@ -680,22 +677,71 @@ protected void afterErrors() { TestTracker.testStarted("run scripts"); TestTracker.testFailed("exception was thrown during execution"); } + if (TestMode.JUNIT) { + SkriptLogger.setVerbosity(Verbosity.DEBUG); + info("Running all JUnit tests..."); + long milliseconds = 0, tests = 0, fails = 0, ignored = 0, size = 0; + try { + List<Class<?>> classes = Lists.newArrayList(Utils.getClasses(Skript.getInstance(), "org.skriptlang.skript.test", "tests")); + // Test that requires package access. This is only present when compiling with src/test. + classes.add(Class.forName("ch.njol.skript.variables.FlatFileStorageTest")); + size = classes.size(); + for (Class<?> clazz : classes) { + // Reset class SkriptJUnitTest which stores test requirements. + String test = clazz.getName(); + SkriptJUnitTest.setCurrentJUnitTest(test); + SkriptJUnitTest.setShutdownDelay(0); + + info("Running JUnit test '" + test + "'"); + Result junit = JUnitCore.runClasses(clazz); + TestTracker.testStarted("JUnit: '" + test + "'"); + + // Collect all data from the current JUnit test. + shutdownDelay = Math.max(shutdownDelay, SkriptJUnitTest.getShutdownDelay()); + tests += junit.getRunCount(); + milliseconds += junit.getRunTime(); + ignored += junit.getIgnoreCount(); + fails += junit.getFailureCount(); + + // If JUnit failures are present, add them to the TestTracker. + junit.getFailures().forEach(failure -> { + String message = failure.getMessage() == null ? "" : " " + failure.getMessage(); + TestTracker.testFailed("'" + test + "': " + message); + Skript.exception(failure.getException(), "JUnit test '" + failure.getTestHeader() + " failed."); + }); + SkriptJUnitTest.clearJUnitTest(); + } + } catch (IOException e) { + Skript.exception(e, "Failed to execute JUnit runtime tests."); + } catch (ClassNotFoundException e) { + // Should be the Skript test jar gradle task. + assert false : "Class 'ch.njol.skript.variables.FlatFileStorageTest' was not found."; + } + if (ignored > 0) + Skript.warning("There were " + ignored + " ignored test cases! This can mean they are not properly setup in order in that class!"); + + info("Completed " + tests + " JUnit tests in " + size + " classes with " + fails + " failures in " + milliseconds + " milliseconds."); + } + } + double display = shutdownDelay / 20; + info("Testing done, shutting down the server in " + display + " second" + (display <= 1D ? "" : "s") + "..."); + // Delay server shutdown to stop the server from crashing because the current tick takes a long time due to all the tests + Bukkit.getScheduler().runTaskLater(Skript.this, () -> { + if (TestMode.JUNIT && !EffObjectives.isJUnitComplete()) + TestTracker.testFailed(EffObjectives.getFailedObjectivesString()); + + info("Collecting results to " + TestMode.RESULTS_FILE); String results = new Gson().toJson(TestTracker.collectResults()); try { Files.write(TestMode.RESULTS_FILE, results.getBytes(StandardCharsets.UTF_8)); } catch (IOException e) { Skript.exception(e, "Failed to write test results."); } - info("Testing done, shutting down the server."); - // Delay server shutdown to stop the server from crashing because the current tick takes a long time due to all the tests - Bukkit.getScheduler().runTaskLater(Skript.this, () -> { - Bukkit.getServer().shutdown(); - }, 5); - } - return; - } - }, 100); + Bukkit.getServer().shutdown(); + }, shutdownDelay); + }, 100); + } final long vld = System.currentTimeMillis() - vls; if (logNormal()) diff --git a/src/main/java/ch/njol/skript/SkriptAddon.java b/src/main/java/ch/njol/skript/SkriptAddon.java index d32f90d1a5c..c117f51cb9a 100644 --- a/src/main/java/ch/njol/skript/SkriptAddon.java +++ b/src/main/java/ch/njol/skript/SkriptAddon.java @@ -18,35 +18,27 @@ */ package ch.njol.skript; -import ch.njol.skript.localization.Language; -import ch.njol.skript.util.Utils; -import ch.njol.skript.util.Version; -import ch.njol.util.coll.iterator.EnumerationIterable; -import org.bukkit.plugin.java.JavaPlugin; -import org.eclipse.jdt.annotation.Nullable; - import java.io.File; import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.bukkit.plugin.java.JavaPlugin; +import org.eclipse.jdt.annotation.Nullable; + +import ch.njol.skript.localization.Language; +import ch.njol.skript.util.Utils; +import ch.njol.skript.util.Version; + /** * Utility class for Skript addons. Use {@link Skript#registerAddon(JavaPlugin)} to create a SkriptAddon instance for your plugin. - * - * @author Peter Güttinger */ public final class SkriptAddon { - + public final JavaPlugin plugin; public final Version version; private final String name; - + /** * Package-private constructor. Use {@link Skript#registerAddon(JavaPlugin)} to get a SkriptAddon for your plugin. * @@ -67,16 +59,16 @@ public final class SkriptAddon { } version = v; } - + @Override public final String toString() { return name; } - + public String getName() { return name; } - + /** * Loads classes of the plugin by package. Useful for registering many syntax elements like Skript does it. * @@ -87,51 +79,13 @@ public String getName() { * @return This SkriptAddon */ public SkriptAddon loadClasses(String basePackage, String... subPackages) throws IOException { - assert subPackages != null; - JarFile jar = new JarFile(getFile()); - for (int i = 0; i < subPackages.length; i++) - subPackages[i] = subPackages[i].replace('.', '/') + "/"; - basePackage = basePackage.replace('.', '/') + "/"; - try { - List<String> classNames = new ArrayList<>(); - - for (JarEntry e : new EnumerationIterable<>(jar.entries())) { - if (e.getName().startsWith(basePackage) && e.getName().endsWith(".class")) { - boolean load = subPackages.length == 0; - for (String sub : subPackages) { - if (e.getName().startsWith(sub, basePackage.length())) { - load = true; - break; - } - } - - if (load) - classNames.add(e.getName().replace('/', '.').substring(0, e.getName().length() - ".class".length())); - } - } - - classNames.sort(String::compareToIgnoreCase); - - for (String c : classNames) { - try { - Class.forName(c, true, plugin.getClass().getClassLoader()); - } catch (ClassNotFoundException ex) { - Skript.exception(ex, "Cannot load class " + c + " from " + this); - } catch (ExceptionInInitializerError err) { - Skript.exception(err.getCause(), this + "'s class " + c + " generated an exception while loading"); - } - } - } finally { - try { - jar.close(); - } catch (IOException e) {} - } + Utils.getClasses(plugin, basePackage, subPackages); return this; } - + @Nullable private String languageFileDirectory = null; - + /** * Makes Skript load language files from the specified directory, e.g. "lang" or "skript lang" if you have a lang folder yourself. Localised files will be read from the * plugin's jar and the plugin's data folder, but the default English file is only taken from the jar and <b>must</b> exist! @@ -149,40 +103,27 @@ public SkriptAddon setLanguageFileDirectory(String directory) { Language.loadDefault(this); return this; } - + @Nullable public String getLanguageFileDirectory() { return languageFileDirectory; } - + @Nullable - private File file = null; - + private File file; + /** - * @return The jar file of the plugin. The first invocation of this method uses reflection to invoke the protected method {@link JavaPlugin#getFile()} to get the plugin's jar - * file. The file is then cached and returned upon subsequent calls to this method to reduce usage of reflection. + * The first invocation of this method uses reflection to invoke the protected method {@link JavaPlugin#getFile()} to get the plugin's jar file. + * The file is then cached and returned upon subsequent calls to this method to reduce usage of reflection. + * Only nullable if there was an exception thrown. + * + * @return The jar file of the plugin. */ @Nullable public File getFile() { - if (file != null) - return file; - try { - final Method getFile = JavaPlugin.class.getDeclaredMethod("getFile"); - getFile.setAccessible(true); - file = (File) getFile.invoke(plugin); - return file; - } catch (final NoSuchMethodException e) { - Skript.outdatedError(e); - } catch (final IllegalArgumentException e) { - Skript.outdatedError(e); - } catch (final IllegalAccessException e) { - assert false; - } catch (final SecurityException e) { - throw new RuntimeException(e); - } catch (final InvocationTargetException e) { - throw new RuntimeException(e.getCause()); - } - return null; + if (file == null) + file = Utils.getFile(plugin); + return file; } - + } diff --git a/src/main/java/ch/njol/skript/SkriptCommand.java b/src/main/java/ch/njol/skript/SkriptCommand.java index ac926d14262..7f01e3ff331 100644 --- a/src/main/java/ch/njol/skript/SkriptCommand.java +++ b/src/main/java/ch/njol/skript/SkriptCommand.java @@ -27,9 +27,9 @@ import ch.njol.skript.localization.PluralizingArgsMessage; import ch.njol.skript.log.RedirectingLogHandler; import ch.njol.skript.log.TimingLogHandler; -import ch.njol.skript.tests.runner.SkriptTestEvent; -import ch.njol.skript.tests.runner.TestMode; -import ch.njol.skript.tests.runner.TestTracker; +import ch.njol.skript.test.runner.SkriptTestEvent; +import ch.njol.skript.test.runner.TestMode; +import ch.njol.skript.test.runner.TestTracker; import ch.njol.skript.util.ExceptionUtils; import ch.njol.skript.util.FileUtils; import ch.njol.skript.util.SkriptColor; diff --git a/src/main/java/ch/njol/skript/SkriptCommandTabCompleter.java b/src/main/java/ch/njol/skript/SkriptCommandTabCompleter.java index 177a8e319ea..7f01510e6db 100644 --- a/src/main/java/ch/njol/skript/SkriptCommandTabCompleter.java +++ b/src/main/java/ch/njol/skript/SkriptCommandTabCompleter.java @@ -18,7 +18,7 @@ */ package ch.njol.skript; -import ch.njol.skript.tests.runner.TestMode; +import ch.njol.skript.test.runner.TestMode; import ch.njol.util.StringUtils; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; diff --git a/src/main/java/ch/njol/skript/tests/platform/Environment.java b/src/main/java/ch/njol/skript/test/platform/Environment.java similarity index 78% rename from src/main/java/ch/njol/skript/tests/platform/Environment.java rename to src/main/java/ch/njol/skript/test/platform/Environment.java index 763a9d94f90..cd7fb24d3f3 100644 --- a/src/main/java/ch/njol/skript/tests/platform/Environment.java +++ b/src/main/java/ch/njol/skript/test/platform/Environment.java @@ -16,13 +16,15 @@ * * Copyright Peter Güttinger, SkriptLang team and contributors */ -package ch.njol.skript.tests.platform; +package ch.njol.skript.test.platform; -import ch.njol.skript.tests.TestResults; import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; + +import ch.njol.skript.test.utils.TestResults; + import org.eclipse.jdt.annotation.Nullable; import java.io.File; @@ -43,6 +45,11 @@ */ public class Environment { + /** + * Time before the process is killed if there was a stack stace etc. + */ + private static final int TIMEOUT = 5 * 60_000; // 5 minutes. + private static final Gson gson = new Gson(); /** @@ -239,11 +246,11 @@ public TestResults runTests(Path runnerRoot, Path testsRoot, boolean devMode, bo args.addAll(Arrays.asList(commandLine)); Process process = new ProcessBuilder(args) - .directory(env.toFile()) - .redirectOutput(Redirect.INHERIT) - .redirectError(Redirect.INHERIT) - .redirectInput(Redirect.INHERIT) - .start(); + .directory(env.toFile()) + .redirectOutput(Redirect.INHERIT) + .redirectError(Redirect.INHERIT) + .redirectInput(Redirect.INHERIT) + .start(); // When we exit, try to make them exit too Runtime.getRuntime().addShutdownHook(new Thread(() -> { @@ -263,7 +270,7 @@ public void run() { System.exit(1); } } - }, 8 * 60_000); // 8 minutes. + }, TIMEOUT); } int code = process.waitFor(); @@ -278,4 +285,56 @@ public void run() { return results; } + @Nullable + public TestResults runJUnit(Path runnerRoot, Path testsRoot, String... jvmArgs) throws IOException, InterruptedException { + Path env = runnerRoot.resolve(name); + Path resultsPath = env.resolve("test_results.json"); + Files.deleteIfExists(resultsPath); + List<String> args = new ArrayList<>(); + args.add(System.getProperty("java.home") + File.separator + "bin" + File.separator + "java"); + args.add("-ea"); + args.add("-Dskript.testing.enabled=true"); + args.add("-Dskript.testing.dir=" + testsRoot); + args.add("-Dskript.testing.junit=true"); + args.add("-Dskript.testing.results=test_results.json"); + args.add("-Ddisable.watchdog=true"); + args.addAll(Arrays.asList(jvmArgs)); + args.addAll(Arrays.asList(commandLine)); + + Process process = new ProcessBuilder(args) + .directory(env.toFile()) + .redirectOutput(Redirect.INHERIT) + .redirectError(Redirect.INHERIT) + .redirectInput(Redirect.INHERIT) + .start(); + + // When we exit, try to make them exit too + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + if (process.isAlive()) { + process.destroy(); + } + })); + + new Timer("runner watchdog", true).schedule(new TimerTask() { + + @Override + public void run() { + if (process.isAlive()) { + System.err.println("Test environment is taking too long, failing..."); + System.exit(1); + } + } + }, TIMEOUT); + int code = process.waitFor(); + if (code != 0) + throw new IOException("environment returned with code " + code); + + // Read test results + if (!Files.exists(resultsPath)) + return null; + TestResults results = new Gson().fromJson(new String(Files.readAllBytes(resultsPath)), TestResults.class); + assert results != null; + return results; + } + } diff --git a/src/main/java/ch/njol/skript/tests/platform/PlatformMain.java b/src/main/java/ch/njol/skript/test/platform/PlatformMain.java similarity index 81% rename from src/main/java/ch/njol/skript/tests/platform/PlatformMain.java rename to src/main/java/ch/njol/skript/test/platform/PlatformMain.java index 0098f97200e..29f09c30a15 100644 --- a/src/main/java/ch/njol/skript/tests/platform/PlatformMain.java +++ b/src/main/java/ch/njol/skript/test/platform/PlatformMain.java @@ -16,7 +16,7 @@ * * Copyright Peter Güttinger, SkriptLang team and contributors */ -package ch.njol.skript.tests.platform; +package ch.njol.skript.test.platform; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -39,7 +39,7 @@ import com.google.gson.GsonBuilder; import com.google.gson.JsonSyntaxException; -import ch.njol.skript.tests.TestResults; +import ch.njol.skript.test.utils.TestResults; import ch.njol.util.NonNullPair; /** @@ -62,6 +62,7 @@ public static void main(String... args) throws IOException, InterruptedException assert envsRoot != null; boolean devMode = "true".equals(args[4]); boolean genDocs = "true".equals(args[5]); + boolean junit = "true".equals(args[6]); // Load environments List<Environment> envs; @@ -80,17 +81,22 @@ public static void main(String... args) throws IOException, InterruptedException } System.out.println("Test environments: " + String.join(", ", envs.stream().map(Environment::getName).collect(Collectors.toList()))); - - Set<String> allTests = new HashSet<>(); + Map<String, List<NonNullPair<Environment, String>>> failures = new HashMap<>(); - + Set<String> allTests = new HashSet<>(); + boolean docsFailed = false; // Run tests and collect the results envs.sort(Comparator.comparing(Environment::getName)); for (Environment env : envs) { System.out.println("Starting testing on " + env.getName()); env.initialize(dataRoot, runnerRoot, false); - TestResults results = env.runTests(runnerRoot, testsRoot, devMode, genDocs, "-Xmx5G"); + TestResults results = null; + if (junit) { + results = env.runJUnit(runnerRoot, testsRoot, "-Xmx5G"); + } else { + results = env.runTests(runnerRoot, testsRoot, devMode, genDocs, "-Xmx5G"); + } if (results == null) { if (devMode) { // Nothing to report, it's the dev mode environment. @@ -131,24 +137,29 @@ public static void main(String... args) throws IOException, InterruptedException Collections.sort(succeeded); List<String> failNames = new ArrayList<>(failures.keySet()); Collections.sort(failNames); - + // All succeeded tests in a single line - System.out.printf("%s Results %s%n", StringUtils.repeat("-", 25), StringUtils.repeat("-", 25)); - System.out.println("Tested environments: " + String.join(", ", + StringBuilder output = new StringBuilder(String.format("%s Results %s%n", StringUtils.repeat("-", 25), StringUtils.repeat("-", 25))); + output.append("\nTested environments: " + String.join(", ", envs.stream().map(Environment::getName).collect(Collectors.toList()))); - System.out.println("\nSucceeded: " + String.join(", ", succeeded)); + output.append("\nSucceeded:\n " + String.join((junit ? "\n " : ", "), succeeded)); + if (!failNames.isEmpty()) { // More space for failed tests, they're important - System.err.println("Failed:"); + output.append("\nFailed:"); for (String failed : failNames) { List<NonNullPair<Environment, String>> errors = failures.get(failed); - System.err.println(" " + failed + " (on " + errors.size() + " environments)"); + output.append(" " + failed + " (on " + errors.size() + " environment" + (errors.size() == 1 ? "" : "s") + ")"); for (NonNullPair<Environment, String> error : errors) { - System.err.println(" " + error.getSecond() + " (on " + error.getFirst().getName() + ")"); + output.append(" " + error.getSecond() + " (on " + error.getFirst().getName() + ")"); } } - System.exit(failNames.size()); // Error code to indicate how many tests failed + output.append(String.format("%n%n%s", StringUtils.repeat("-", 60))); + System.err.print(output.toString()); + System.exit(failNames.size()); // Error code to indicate how many tests failed. + return; } - System.out.printf("%n%s", StringUtils.repeat("-", 60)); + output.append(String.format("%n%n%s", StringUtils.repeat("-", 60))); + System.out.print(output.toString()); } } diff --git a/src/main/java/ch/njol/skript/tests/runner/package-info.java b/src/main/java/ch/njol/skript/test/platform/package-info.java similarity index 96% rename from src/main/java/ch/njol/skript/tests/runner/package-info.java rename to src/main/java/ch/njol/skript/test/platform/package-info.java index 8d276f66853..6ea91784b5f 100644 --- a/src/main/java/ch/njol/skript/tests/runner/package-info.java +++ b/src/main/java/ch/njol/skript/test/platform/package-info.java @@ -20,7 +20,7 @@ * Support for script-based testing. */ @NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) -package ch.njol.skript.tests.runner; +package ch.njol.skript.test.platform; import org.eclipse.jdt.annotation.DefaultLocation; import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/src/main/java/ch/njol/skript/tests/runner/CondMethodExists.java b/src/main/java/ch/njol/skript/test/runner/CondMethodExists.java similarity index 97% rename from src/main/java/ch/njol/skript/tests/runner/CondMethodExists.java rename to src/main/java/ch/njol/skript/test/runner/CondMethodExists.java index 8a3b5e04061..17d821ad6aa 100644 --- a/src/main/java/ch/njol/skript/tests/runner/CondMethodExists.java +++ b/src/main/java/ch/njol/skript/test/runner/CondMethodExists.java @@ -16,7 +16,7 @@ * * Copyright Peter Güttinger, SkriptLang team and contributors */ -package ch.njol.skript.tests.runner; +package ch.njol.skript.test.runner; import ch.njol.skript.conditions.base.PropertyCondition; import org.apache.commons.lang.StringUtils; @@ -50,7 +50,6 @@ public class CondMethodExists extends PropertyCondition<String> { Skript.registerCondition(CondMethodExists.class, "method[s] %strings% [dont:do(esn't|n't)] exist[s]"); } - @SuppressWarnings("NotNullFieldNotInitialized") private Expression<String> signatures; @Override diff --git a/src/main/java/ch/njol/skript/tests/runner/CondMinecraftVersion.java b/src/main/java/ch/njol/skript/test/runner/CondMinecraftVersion.java similarity index 98% rename from src/main/java/ch/njol/skript/tests/runner/CondMinecraftVersion.java rename to src/main/java/ch/njol/skript/test/runner/CondMinecraftVersion.java index 4aaafa405f7..bc5f1436349 100644 --- a/src/main/java/ch/njol/skript/tests/runner/CondMinecraftVersion.java +++ b/src/main/java/ch/njol/skript/test/runner/CondMinecraftVersion.java @@ -16,7 +16,7 @@ * * Copyright Peter Güttinger, SkriptLang team and contributors */ -package ch.njol.skript.tests.runner; +package ch.njol.skript.test.runner; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; diff --git a/src/main/java/ch/njol/skript/tests/runner/EffAssert.java b/src/main/java/ch/njol/skript/test/runner/EffAssert.java similarity index 77% rename from src/main/java/ch/njol/skript/tests/runner/EffAssert.java rename to src/main/java/ch/njol/skript/test/runner/EffAssert.java index 04e4e9ce786..4f78665295d 100644 --- a/src/main/java/ch/njol/skript/tests/runner/EffAssert.java +++ b/src/main/java/ch/njol/skript/test/runner/EffAssert.java @@ -16,20 +16,19 @@ * * Copyright Peter Güttinger, SkriptLang team and contributors */ -package ch.njol.skript.tests.runner; +package ch.njol.skript.test.runner; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.Since; +import ch.njol.skript.doc.NoDoc; import ch.njol.skript.lang.Condition; import ch.njol.skript.lang.Effect; import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.SkriptParser; +import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.TriggerItem; import ch.njol.skript.log.ParseLogHandler; import ch.njol.skript.log.SkriptLogger; @@ -37,8 +36,7 @@ @Name("Assert") @Description("Assert that condition is true. Test fails when it is not.") -@Examples("") -@Since("2.5") +@NoDoc public class EffAssert extends Effect { static { @@ -49,14 +47,12 @@ public class EffAssert extends Effect { @Nullable private Condition condition; - @SuppressWarnings("null") private Expression<String> errorMsg; - private boolean shouldFail; - @SuppressWarnings({"null", "unchecked"}) @Override - public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { String conditionString = parseResult.regexes.get(0).group(); errorMsg = (Expression<String>) exprs[0]; shouldFail = parseResult.mark != 0; @@ -65,9 +61,8 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye try { condition = Condition.parse(conditionString, "Can't understand this condition: " + conditionString); - if (shouldFail) { + if (shouldFail) return true; - } if (condition == null) { logHandler.printError(); @@ -82,18 +77,22 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye } @Override - protected void execute(Event e) {} - + protected void execute(Event event) {} + @Nullable @Override - public TriggerItem walk(Event e) { - if (shouldFail && condition == null) { + public TriggerItem walk(Event event) { + if (shouldFail && condition == null) return getNext(); - } - - if (condition.check(e) == shouldFail) { - String msg = errorMsg.getSingle(e); - TestTracker.testFailed(msg != null ? msg : "assertation failed"); + + if (condition.check(event) == shouldFail) { + String message = errorMsg.getSingle(event); + assert message != null; // Should not happen, developer needs to fix test. + if (SkriptJUnitTest.getCurrentJUnitTest() != null) { + TestTracker.junitTestFailed(SkriptJUnitTest.getCurrentJUnitTest(), message); + } else { + TestTracker.testFailed(message); + } return null; } return getNext(); diff --git a/src/main/java/ch/njol/skript/test/runner/EffObjectives.java b/src/main/java/ch/njol/skript/test/runner/EffObjectives.java new file mode 100644 index 00000000000..9a2d9a0e4ae --- /dev/null +++ b/src/main/java/ch/njol/skript/test/runner/EffObjectives.java @@ -0,0 +1,122 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.test.runner; + +import java.util.Arrays; +import java.util.List; + +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Multimap; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.NoDoc; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; + +@Name("Objectives") +@Description("An effect to setup required objectives for JUnit tests to complete.") +@NoDoc +public class EffObjectives extends Effect { + + static { + if (TestMode.ENABLED) + Skript.registerEffect(EffObjectives.class, + "ensure [[junit] test] %string% completes [(objective|trigger)[s]] %strings%", + "complete [(objective|trigger)[s]] %strings% (for|on) [[junit] test] %string%" + ); + } + + private static final Multimap<String, String> requirements = HashMultimap.create(); + private static final Multimap<String, String> completeness = HashMultimap.create(); + + private Expression<String> junit, objectives; + private boolean setup; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + objectives = (Expression<String>) exprs[matchedPattern ^ 1]; + junit = (Expression<String>) exprs[matchedPattern]; + setup = matchedPattern == 0; + return true; + } + + @Override + protected void execute(Event event) { + String junit = this.junit.getSingle(event); + assert junit != null; + String[] objectives = this.objectives.getArray(event); + assert objectives.length > 0; + if (setup) { + requirements.putAll(junit, Lists.newArrayList(objectives)); + } else { + completeness.putAll(junit, Lists.newArrayList(objectives)); + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + if (setup) + return "ensure junit test " + junit.toString(event, debug) + " completes objectives " + objectives.toString(event, debug); + return "complete objectives " + objectives.toString(event, debug) + " on junit test " + junit.toString(event, debug); + } + + /** + * Check if the currently running JUnit test has passed all + * it's required objectives that the script test setup. + * + * @return boolean true if the test passed. + */ + public static boolean isJUnitComplete() { + if (requirements.isEmpty()) + return true; + if (completeness.isEmpty() && !requirements.isEmpty()) + return false; + return completeness.equals(requirements); + } + + /** + * Returns an array string containing all the objectives that the current + * JUnit test failed to accomplish in the given time. + * + * @return + */ + public static String getFailedObjectivesString() { + StringBuilder builder = new StringBuilder(); + for (String test : requirements.keySet()) { + if (!completeness.containsKey(test)) { + builder.append("JUnit test '" + test + "' didn't complete any objectives."); + continue; + } + List<String> failures = Lists.newArrayList(requirements.get(test)); + failures.removeAll(completeness.get(test)); + builder.append("JUnit test '" + test + "' failed objectives: " + Arrays.toString(failures.toArray(new String[0]))); + } + return builder.toString(); + } + +} diff --git a/src/main/java/ch/njol/skript/tests/runner/EvtTestCase.java b/src/main/java/ch/njol/skript/test/runner/EvtTestCase.java similarity index 98% rename from src/main/java/ch/njol/skript/tests/runner/EvtTestCase.java rename to src/main/java/ch/njol/skript/test/runner/EvtTestCase.java index 82809604a19..eb6bbd93b4f 100644 --- a/src/main/java/ch/njol/skript/tests/runner/EvtTestCase.java +++ b/src/main/java/ch/njol/skript/test/runner/EvtTestCase.java @@ -16,7 +16,7 @@ * * Copyright Peter Güttinger, SkriptLang team and contributors */ -package ch.njol.skript.tests.runner; +package ch.njol.skript.test.runner; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; diff --git a/src/main/java/ch/njol/skript/test/runner/ExprJUnitTest.java b/src/main/java/ch/njol/skript/test/runner/ExprJUnitTest.java new file mode 100644 index 00000000000..0f85a98e8aa --- /dev/null +++ b/src/main/java/ch/njol/skript/test/runner/ExprJUnitTest.java @@ -0,0 +1,71 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.test.runner; + +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.NoDoc; +import ch.njol.skript.lang.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; + +@Name("JUnit Test Name") +@Description("Returns the currently running JUnit test name otherwise nothing.") +@NoDoc +public class ExprJUnitTest extends SimpleExpression<String> { + + static { + if (TestMode.ENABLED) + Skript.registerExpression(ExprJUnitTest.class, String.class, ExpressionType.SIMPLE, "[the] [current[[ly] running]] junit test [name]"); + } + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + return true; + } + + @Override + @Nullable + protected String[] get(Event event) { + return CollectionUtils.array(SkriptJUnitTest.getCurrentJUnitTest()); + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class<? extends String> getReturnType() { + return String.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "current junit test"; + } + +} diff --git a/src/main/java/ch/njol/skript/test/runner/SkriptJUnitTest.java b/src/main/java/ch/njol/skript/test/runner/SkriptJUnitTest.java new file mode 100644 index 00000000000..ed9fb6d026e --- /dev/null +++ b/src/main/java/ch/njol/skript/test/runner/SkriptJUnitTest.java @@ -0,0 +1,154 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.test.runner; + +import org.bukkit.Bukkit; +import org.bukkit.GameRule; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Pig; +import org.junit.After; +import org.junit.Before; + +import ch.njol.skript.Skript; + +/** + * Class that helps the JUnit test communicate with Skript. + */ +public abstract class SkriptJUnitTest { + + static { + World world = Bukkit.getWorlds().get(0); + world.setGameRule(GameRule.MAX_ENTITY_CRAMMING, 1000); + world.setGameRule(GameRule.DO_WEATHER_CYCLE, false); + // Natural entity spawning + world.setGameRule(GameRule.DO_MOB_SPAWNING, false); + world.setGameRule(GameRule.MOB_GRIEFING, false); + + if (Skript.isRunningMinecraft(1, 15)) { + world.setGameRule(GameRule.DO_PATROL_SPAWNING, false); + world.setGameRule(GameRule.DO_TRADER_SPAWNING, false); + world.setGameRule(GameRule.DISABLE_RAIDS, false); + } + } + + /** + * Used for getting the currently running JUnit test name. + */ + private static String currentJUnitTest; + + private static long delay = 0; + + /** + * The delay this JUnit test is requiring to run. + * Do note this is global to all other tests. The most delay is the final waiting time. + * + * @return the delay in Minecraft ticks this junit test is requiring to run for. + */ + public static long getShutdownDelay() { + return delay; + } + + /** + * @param delay Set the delay in Minecraft ticks for this test to run. + */ + public static void setShutdownDelay(long delay) { + SkriptJUnitTest.delay = delay; + } + + @Before + @After + public final void cleanup() { + getTestWorld().getEntities().forEach(Entity::remove); + setBlock(Material.AIR); + } + + /** + * @return the test world. + */ + protected World getTestWorld() { + return Bukkit.getWorlds().get(0); + } + + /** + * @return the testing location at the spawn of the testing world. + */ + protected Location getTestLocation() { + return getTestWorld().getSpawnLocation().add(0, 1, 0); + } + + /** + * Spawns a testing pig at the spawn location of the testing world. + * + * @return Pig that has been spawned. + */ + protected Pig spawnTestPig() { + if (delay <= 0D) + delay = 1; // A single tick allows the piggy to spawn before server shutdown. + return (Pig) getTestWorld().spawnEntity(getTestLocation(), EntityType.PIG); + } + + /** + * Set the type of the block at the testing location. + * + * @param material The material to set the block to. + * @return the Block after it has been updated. + */ + protected Block setBlock(Material material) { + Block block = getBlock(); + block.setType(material); + return block; + } + + /** + * Return the main block for testing in the getTestLocation(); + * + * @return the Block after it has been updated. + */ + protected Block getBlock() { + return getTestWorld().getSpawnLocation().add(10, 1, 0).getBlock(); + } + + /** + * Get the currently running JUnit test name. + */ + public static String getCurrentJUnitTest() { + return currentJUnitTest; + } + + /** + * Used internally. + */ + public static void setCurrentJUnitTest(String currentJUnitTest) { + SkriptJUnitTest.currentJUnitTest = currentJUnitTest; + } + + /** + * Used internally. + */ + public static void clearJUnitTest() { + SkriptJUnitTest.currentJUnitTest = null; + setShutdownDelay(0); + } + +} diff --git a/src/main/java/ch/njol/skript/tests/runner/SkriptTestEvent.java b/src/main/java/ch/njol/skript/test/runner/SkriptTestEvent.java similarity index 96% rename from src/main/java/ch/njol/skript/tests/runner/SkriptTestEvent.java rename to src/main/java/ch/njol/skript/test/runner/SkriptTestEvent.java index be7bf978857..558a7ae458e 100644 --- a/src/main/java/ch/njol/skript/tests/runner/SkriptTestEvent.java +++ b/src/main/java/ch/njol/skript/test/runner/SkriptTestEvent.java @@ -16,7 +16,7 @@ * * Copyright Peter Güttinger, SkriptLang team and contributors */ -package ch.njol.skript.tests.runner; +package ch.njol.skript.test.runner; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; diff --git a/src/main/java/ch/njol/skript/tests/runner/TestFunctions.java b/src/main/java/ch/njol/skript/test/runner/TestFunctions.java similarity index 98% rename from src/main/java/ch/njol/skript/tests/runner/TestFunctions.java rename to src/main/java/ch/njol/skript/test/runner/TestFunctions.java index 21f31a52b80..fe9a434cebe 100644 --- a/src/main/java/ch/njol/skript/tests/runner/TestFunctions.java +++ b/src/main/java/ch/njol/skript/test/runner/TestFunctions.java @@ -16,7 +16,7 @@ * * Copyright Peter Güttinger, SkriptLang team and contributors */ -package ch.njol.skript.tests.runner; +package ch.njol.skript.test.runner; import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.lang.function.Functions; diff --git a/src/main/java/ch/njol/skript/tests/runner/TestMode.java b/src/main/java/ch/njol/skript/test/runner/TestMode.java similarity index 91% rename from src/main/java/ch/njol/skript/tests/runner/TestMode.java rename to src/main/java/ch/njol/skript/test/runner/TestMode.java index d4612da0b4a..9374269f710 100644 --- a/src/main/java/ch/njol/skript/tests/runner/TestMode.java +++ b/src/main/java/ch/njol/skript/test/runner/TestMode.java @@ -16,7 +16,7 @@ * * Copyright Peter Güttinger, SkriptLang team and contributors */ -package ch.njol.skript.tests.runner; +package ch.njol.skript.test.runner; import java.io.File; import java.nio.file.Path; @@ -24,7 +24,7 @@ import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.tests.TestResults; +import ch.njol.skript.test.utils.TestResults; /** * Static utilities for Skript's 'test mode'. @@ -63,6 +63,11 @@ public class TestMode { */ public static final Path RESULTS_FILE = ENABLED ? Paths.get(System.getProperty(ROOT + "results")) : null; + /** + * If this test is for JUnits on the server. + */ + public static final boolean JUNIT = "true".equals(System.getProperty(ROOT + "junit")); + /** * In development mode, file that was last run. */ diff --git a/src/main/java/ch/njol/skript/tests/runner/TestTracker.java b/src/main/java/ch/njol/skript/test/runner/TestTracker.java similarity index 90% rename from src/main/java/ch/njol/skript/tests/runner/TestTracker.java rename to src/main/java/ch/njol/skript/test/runner/TestTracker.java index a80ca913814..fb6b2a0df40 100644 --- a/src/main/java/ch/njol/skript/tests/runner/TestTracker.java +++ b/src/main/java/ch/njol/skript/test/runner/TestTracker.java @@ -16,7 +16,7 @@ * * Copyright Peter Güttinger, SkriptLang team and contributors */ -package ch.njol.skript.tests.runner; +package ch.njol.skript.test.runner; import java.util.HashMap; import java.util.HashSet; @@ -25,50 +25,54 @@ import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.tests.TestResults; +import ch.njol.skript.test.utils.TestResults; /** * Tracks failed and succeeded tests. */ public class TestTracker { - + /** * Started tests. */ private static final Set<String> startedTests = new HashSet<>(); - + /** * Failed tests to failure assert messages. */ private static final Map<String, String> failedTests = new HashMap<>(); - + @Nullable private static String currentTest; - + public static void testStarted(String name) { startedTests.add(name); currentTest = name; } - + public static void testFailed(String msg) { failedTests.put(currentTest, msg); } - + + public static void junitTestFailed(String junit, String msg) { + failedTests.put(junit, msg); + } + public static Map<String, String> getFailedTests() { return new HashMap<>(failedTests); } - + public static Set<String> getSucceededTests() { Set<String> tests = new HashSet<>(startedTests); tests.removeAll(failedTests.keySet()); return tests; } - + public static TestResults collectResults() { TestResults results = new TestResults(getSucceededTests(), getFailedTests(), TestMode.docsFailed); startedTests.clear(); failedTests.clear(); return results; } - + } diff --git a/src/main/java/ch/njol/skript/tests/package-info.java b/src/main/java/ch/njol/skript/test/runner/package-info.java similarity index 96% rename from src/main/java/ch/njol/skript/tests/package-info.java rename to src/main/java/ch/njol/skript/test/runner/package-info.java index bee5911b214..0111f884a88 100644 --- a/src/main/java/ch/njol/skript/tests/package-info.java +++ b/src/main/java/ch/njol/skript/test/runner/package-info.java @@ -20,7 +20,7 @@ * Support for script-based testing. */ @NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) -package ch.njol.skript.tests; +package ch.njol.skript.test.runner; import org.eclipse.jdt.annotation.DefaultLocation; import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/src/main/java/ch/njol/skript/tests/TestResults.java b/src/main/java/ch/njol/skript/test/utils/TestResults.java similarity index 98% rename from src/main/java/ch/njol/skript/tests/TestResults.java rename to src/main/java/ch/njol/skript/test/utils/TestResults.java index ecc9045f0b2..53586491992 100644 --- a/src/main/java/ch/njol/skript/tests/TestResults.java +++ b/src/main/java/ch/njol/skript/test/utils/TestResults.java @@ -16,7 +16,7 @@ * * Copyright Peter Güttinger, SkriptLang team and contributors */ -package ch.njol.skript.tests; +package ch.njol.skript.test.utils; import java.util.Map; import java.util.Set; diff --git a/src/main/java/ch/njol/skript/tests/platform/package-info.java b/src/main/java/ch/njol/skript/test/utils/package-info.java similarity index 92% rename from src/main/java/ch/njol/skript/tests/platform/package-info.java rename to src/main/java/ch/njol/skript/test/utils/package-info.java index eef7a9713b0..846a1258239 100644 --- a/src/main/java/ch/njol/skript/tests/platform/package-info.java +++ b/src/main/java/ch/njol/skript/test/utils/package-info.java @@ -17,10 +17,10 @@ * Copyright Peter Güttinger, SkriptLang team and contributors */ /** - * Support for script-based testing. + * Utils for script-based testing. */ @NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) -package ch.njol.skript.tests.platform; +package ch.njol.skript.test.utils; import org.eclipse.jdt.annotation.DefaultLocation; import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/src/main/java/ch/njol/skript/util/Utils.java b/src/main/java/ch/njol/skript/util/Utils.java index 18adcfdaf4c..12255d4d8d6 100644 --- a/src/main/java/ch/njol/skript/util/Utils.java +++ b/src/main/java/ch/njol/skript/util/Utils.java @@ -18,6 +18,11 @@ */ package ch.njol.skript.util; +import java.io.File; +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; @@ -25,12 +30,16 @@ import java.util.Random; import java.util.concurrent.CompletableFuture; import java.util.function.Predicate; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Stream; import org.bukkit.Bukkit; import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.messaging.Messenger; import org.bukkit.plugin.messaging.PluginMessageListener; import org.eclipse.jdt.annotation.Nullable; @@ -51,6 +60,7 @@ import ch.njol.util.Pair; import ch.njol.util.StringUtils; import ch.njol.util.coll.CollectionUtils; +import ch.njol.util.coll.iterator.EnumerationIterable; import net.md_5.bungee.api.ChatColor; /** @@ -149,7 +159,85 @@ public static Pair<String, Integer> getAmount(String s) { // } // return new AmountResponse(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. <tt>"ch.njol.skript"</tt>. + * @param subPackages Which subpackages of the base package should be loaded, e.g. <tt>"expressions", "conditions", "effects"</tt>. Subpackages of these packages will be loaded + * as well. Use an empty array to load all subpackages of the base package. + * @throws IOException If some error occurred attempting to read the plugin's jar file. + * @return This SkriptAddon + */ + public static Class<?>[] getClasses(Plugin plugin, String basePackage, String... subPackages) throws IOException { + assert subPackages != null; + JarFile jar = new JarFile(getFile(plugin)); + for (int i = 0; i < subPackages.length; i++) + subPackages[i] = subPackages[i].replace('.', '/') + "/"; + basePackage = basePackage.replace('.', '/') + "/"; + List<Class<?>> classes = new ArrayList<>(); + try { + List<String> classNames = new ArrayList<>(); + + for (JarEntry e : new EnumerationIterable<>(jar.entries())) { + if (e.getName().startsWith(basePackage) && e.getName().endsWith(".class") && !e.getName().endsWith("package-info.class")) { + boolean load = subPackages.length == 0; + for (String sub : subPackages) { + if (e.getName().startsWith(sub, basePackage.length())) { + load = true; + break; + } + } + + if (load) + classNames.add(e.getName().replace('/', '.').substring(0, e.getName().length() - ".class".length())); + } + } + + classNames.sort(String::compareToIgnoreCase); + + for (String c : classNames) { + try { + classes.add(Class.forName(c, true, plugin.getClass().getClassLoader())); + } catch (ClassNotFoundException ex) { + Skript.exception(ex, "Cannot load class " + c); + } catch (ExceptionInInitializerError err) { + Skript.exception(err.getCause(), "class " + c + " generated an exception while loading"); + } + } + } finally { + try { + jar.close(); + } catch (IOException e) {} + } + return classes.toArray(new Class<?>[classes.size()]); + } + + /** + * 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 + public static File getFile(Plugin plugin) { + try { + Method getFile = JavaPlugin.class.getDeclaredMethod("getFile"); + getFile.setAccessible(true); + return (File) getFile.invoke(plugin); + } catch (NoSuchMethodException e) { + Skript.outdatedError(e); + } catch (IllegalArgumentException e) { + Skript.outdatedError(e); + } catch (IllegalAccessException e) { + assert false; + } catch (SecurityException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e.getCause()); + } + return null; + } + private final static String[][] plurals = { {"fe", "ves"},// most -f words' plurals can end in -fs as well as -ves diff --git a/src/test/java/ch/njol/skript/variables/FlatFileStorageTest.java b/src/test/java/ch/njol/skript/variables/FlatFileStorageTest.java new file mode 100644 index 00000000000..3868358459c --- /dev/null +++ b/src/test/java/ch/njol/skript/variables/FlatFileStorageTest.java @@ -0,0 +1,65 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.variables; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; + +import org.junit.Test; + +public class FlatFileStorageTest { + + @Test + public void testHexCoding() { + byte[] bytes = {-0x80, -0x50, -0x01, 0x00, 0x01, 0x44, 0x7F}; + String string = "80B0FF0001447F"; + assertEquals(string, FlatFileStorage.encode(bytes)); + assert Arrays.equals(bytes, FlatFileStorage.decode(string)) : Arrays.toString(bytes) + " != " + Arrays.toString(FlatFileStorage.decode(string)); + } + + @Test + public void testSplitCSV() { + String[][] vs = { + {"", ""}, + {",", "", ""}, + {",,", "", "", ""}, + {"a", "a"}, + {"a,", "a", ""}, + {",a", "", "a"}, + {",a,", "", "a", ""}, + {" , a , ", "", "a", ""}, + {"a,b,c", "a", "b", "c"}, + {" a , b , c ", "a", "b", "c"}, + + {"\"\"", ""}, + {"\",\"", ","}, + {"\"\"\"\"", "\""}, + {"\" \"", " "}, + {"a, \"\"\"\", b, \", c\", d", "a", "\"", "b", ", c", "d"}, + {"a, \"\"\", b, \", c", "a", "\", b, ", "c"}, + + {"\"\t\0\"", "\t\0"}, + }; + for (String[] v : vs) { + assert Arrays.equals(Arrays.copyOfRange(v, 1, v.length), FlatFileStorage.splitCSV(v[0])) : v[0] + ": " + Arrays.toString(Arrays.copyOfRange(v, 1, v.length)) + " != " + Arrays.toString(FlatFileStorage.splitCSV(v[0])); + } + } + +} diff --git a/src/test/java/org/skriptlang/skript/test/tests/aliases/AliasesTest.java b/src/test/java/org/skriptlang/skript/test/tests/aliases/AliasesTest.java new file mode 100644 index 00000000000..c2ad1a61918 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/aliases/AliasesTest.java @@ -0,0 +1,59 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.test.tests.aliases; + +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.LeatherArmorMeta; +import org.junit.Test; + +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.registrations.Classes; + +public class AliasesTest { + + @Test + public void test() { + ItemStack itemstack = new ItemStack(Material.LEATHER_CHESTPLATE, 6); + ItemMeta meta = itemstack.getItemMeta(); + assert meta instanceof LeatherArmorMeta; + LeatherArmorMeta leather = (LeatherArmorMeta) meta; + leather.setColor(Color.LIME); + itemstack.setItemMeta(leather); + ItemType itemType = new ItemType(itemstack); + assert itemType.equals(new ItemType(itemstack)); + + itemstack = new ItemStack(Material.LEATHER_CHESTPLATE, 2); + meta = itemstack.getItemMeta(); + assert meta instanceof LeatherArmorMeta; + leather = (LeatherArmorMeta) meta; + leather.setColor(Color.RED); + itemstack.setItemMeta(leather); + assert !itemType.equals(new ItemType(itemstack)); + + // Contains assert inside serialize method too, Njol mentioned this. + assert Classes.serialize(itemType) != null; + // This doesn't work anymore since Njol added this. + //assert Classes.serialize(itemType).equals(Classes.serialize(itemType)); + assert !Classes.serialize(itemType).equals(Classes.serialize(new ItemType(itemstack))); + } + +} diff --git a/src/test/java/org/skriptlang/skript/test/tests/aliases/package-info.java b/src/test/java/org/skriptlang/skript/test/tests/aliases/package-info.java new file mode 100644 index 00000000000..9abed714d99 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/aliases/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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +@NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) +package org.skriptlang.skript.test.tests.aliases; + +import org.eclipse.jdt.annotation.DefaultLocation; +import org.eclipse.jdt.annotation.NonNullByDefault; + diff --git a/src/test/java/org/skriptlang/skript/test/tests/classes/ClassesTest.java b/src/test/java/org/skriptlang/skript/test/tests/classes/ClassesTest.java new file mode 100644 index 00000000000..385078a5701 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/classes/ClassesTest.java @@ -0,0 +1,74 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.test.tests.classes; + +import org.bukkit.GameMode; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Snowball; +import org.bukkit.event.entity.EntityDamageEvent.DamageCause; +import org.bukkit.event.inventory.InventoryType; +import org.junit.Test; + +import ch.njol.skript.entity.CreeperData; +import ch.njol.skript.entity.EntityType; +import ch.njol.skript.entity.SimpleEntityData; +import ch.njol.skript.entity.ThrownPotionData; +import ch.njol.skript.entity.WolfData; +import ch.njol.skript.entity.XpOrbData; +import ch.njol.skript.registrations.Classes; +import ch.njol.skript.util.Date; +import ch.njol.skript.util.Direction; +import ch.njol.skript.util.Experience; +import ch.njol.skript.util.SkriptColor; +import ch.njol.skript.util.StructureType; +import ch.njol.skript.util.Time; +import ch.njol.skript.util.Timeperiod; +import ch.njol.skript.util.Timespan; +import ch.njol.skript.util.WeatherType; + +public class ClassesTest { + + @Test + public void serializationTest() { + Object[] random = { + // Java + (byte) 127, (short) 2000, -1600000, 1L << 40, -1.5f, 13.37, + "String", + + // Skript + SkriptColor.BLACK, StructureType.RED_MUSHROOM, WeatherType.THUNDER, + new Date(System.currentTimeMillis()), new Timespan(1337), new Time(12000), new Timeperiod(1000, 23000), + new Experience(15), new Direction(0, Math.PI, 10), new Direction(new double[] {0, 1, 0}), + new EntityType(new SimpleEntityData(HumanEntity.class), 300), + new CreeperData(), + new SimpleEntityData(Snowball.class), + new ThrownPotionData(), + new WolfData(), + new XpOrbData(50), + + // Bukkit - simple classes only + GameMode.ADVENTURE, InventoryType.CHEST, DamageCause.FALL, + + // there is also at least one variable for each class on my test server which are tested whenever the server shuts down. + }; + for (Object o : random) + Classes.serialize(o); // includes a deserialisation test + } + +} diff --git a/src/test/java/org/skriptlang/skript/test/tests/classes/package-info.java b/src/test/java/org/skriptlang/skript/test/tests/classes/package-info.java new file mode 100644 index 00000000000..27c021a95b0 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/classes/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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +@NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) +package org.skriptlang.skript.test.tests.classes; + +import org.eclipse.jdt.annotation.DefaultLocation; +import org.eclipse.jdt.annotation.NonNullByDefault; + diff --git a/src/test/java/org/skriptlang/skript/test/tests/config/NodeTest.java b/src/test/java/org/skriptlang/skript/test/tests/config/NodeTest.java new file mode 100644 index 00000000000..e24d28405f2 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/config/NodeTest.java @@ -0,0 +1,58 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.test.tests.config; + +import static org.junit.Assert.assertArrayEquals; + +import org.junit.Test; + +import ch.njol.skript.config.Node; +import ch.njol.util.NonNullPair; + +public class NodeTest { + + @Test + public void splitLineTest() { + String[][] data = { + {"", "", ""}, + {"ab", "ab", ""}, + {"ab#", "ab", "#"}, + {"ab##", "ab#", ""}, + {"ab###", "ab#", "#"}, + {"#ab", "", "#ab"}, + {"ab#cd", "ab", "#cd"}, + {"ab##cd", "ab#cd", ""}, + {"ab###cd", "ab#", "#cd"}, + {"######", "###", ""}, + {"#######", "###", "#"}, + {"#### # ####", "## ", "# ####"}, + {"##### ####", "##", "# ####"}, + {"#### #####", "## ##", "#"}, + {"#########", "####", "#"}, + {"a##b#c##d#e", "a#b", "#c##d#e"}, + {" a ## b # c ## d # e ", " a # b ", "# c ## d # e "}, + }; + for (String[] d : data) { + NonNullPair<String, String> p = Node.splitLine(d[0]); + assertArrayEquals(d[0], new String[] {d[1], d[2]}, new String[] {p.getFirst(), p.getSecond()}); + } + + } + +} diff --git a/src/test/java/org/skriptlang/skript/test/tests/config/package-info.java b/src/test/java/org/skriptlang/skript/test/tests/config/package-info.java new file mode 100644 index 00000000000..f32c2844ba2 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/config/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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +@NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) +package org.skriptlang.skript.test.tests.config; + +import org.eclipse.jdt.annotation.DefaultLocation; +import org.eclipse.jdt.annotation.NonNullByDefault; + diff --git a/src/test/java/org/skriptlang/skript/test/tests/localization/NounTest.java b/src/test/java/org/skriptlang/skript/test/tests/localization/NounTest.java new file mode 100644 index 00000000000..0c9cd31de11 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/localization/NounTest.java @@ -0,0 +1,65 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.test.tests.localization; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import ch.njol.skript.localization.Noun; +import ch.njol.util.NonNullPair; + +public class NounTest { + + @Test + public void testGetPlural() { + String[][] tests = { + {"a", "a", "a"}, + {"a¦b", "a", "ab"}, + {"a¦b¦c", "ab", "ac"}, + {"a¦b¦c¦d", "abd", "acd"}, + {"a¦b¦c¦d¦e", "abd", "acde"}, + {"a¦b¦c¦d¦e¦f", "abde", "acdf"}, + {"a¦b¦c¦d¦e¦f¦g", "abdeg", "acdfg"}, + }; + for (String[] test : tests) { + NonNullPair<String, String> p = Noun.getPlural(test[0]); + assertEquals(test[1], p.getFirst()); + assertEquals(test[2], p.getSecond()); + } + } + + @Test + public void testNormalizePluralMarkers() { + String[][] tests = { + {"a", "a"}, + {"a¦b", "a¦¦b¦"}, + {"a¦b¦c", "a¦b¦c¦"}, + {"a¦b¦c¦d", "a¦b¦c¦d"}, + {"a¦b¦c¦d¦e", "a¦b¦c¦d¦¦e¦"}, + {"a¦b¦c¦d¦e¦f", "a¦b¦c¦d¦e¦f¦"}, + {"a¦b¦c¦d¦e¦f¦g", "a¦b¦c¦d¦e¦f¦g"}, + }; + for (String[] test : tests) { + assertEquals(test[1], Noun.normalizePluralMarkers(test[0])); + assertEquals(test[1] + "@x", Noun.normalizePluralMarkers(test[0] + "@x")); + } + } + +} diff --git a/src/test/java/org/skriptlang/skript/test/tests/localization/UtilsPlurals.java b/src/test/java/org/skriptlang/skript/test/tests/localization/UtilsPlurals.java new file mode 100644 index 00000000000..716f11e2d26 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/localization/UtilsPlurals.java @@ -0,0 +1,64 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.test.tests.localization; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import ch.njol.skript.util.Utils; + +public class UtilsPlurals { + + /** + * Testing method {@link Utils#getEnglishPlural(String)} + */ + @Test + public void testPlural() { + String[][] strings = { + {"house", "houses"}, + {"cookie", "cookies"}, + {"creeper", "creepers"}, + {"cactus", "cacti"}, + {"rose", "roses"}, + {"dye", "dyes"}, + {"name", "names"}, + {"ingot", "ingots"}, + {"derp", "derps"}, + {"sheep", "sheep"}, + {"choir", "choirs"}, + {"man", "men"}, + {"child", "children"}, + {"hoe", "hoes"}, + {"toe", "toes"}, + {"hero", "heroes"}, + {"kidney", "kidneys"}, + {"anatomy", "anatomies"}, + {"axe", "axes"}, + {"elf", "elfs"}, + {"knife", "knives"}, + {"shelf", "shelfs"}, + }; + for (String[] s : strings) { + assertEquals(s[1], Utils.toEnglishPlural(s[0])); + assertEquals(s[0], Utils.getEnglishPlural(s[1]).getFirst()); + } + } + +} diff --git a/src/test/java/org/skriptlang/skript/test/tests/localization/package-info.java b/src/test/java/org/skriptlang/skript/test/tests/localization/package-info.java new file mode 100644 index 00000000000..bfc35002b56 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/localization/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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +@NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) +package org.skriptlang.skript.test.tests.localization; + +import org.eclipse.jdt.annotation.DefaultLocation; +import org.eclipse.jdt.annotation.NonNullByDefault; + diff --git a/src/test/java/org/skriptlang/skript/test/tests/regression/SimpleJUnitTest.java b/src/test/java/org/skriptlang/skript/test/tests/regression/SimpleJUnitTest.java new file mode 100644 index 00000000000..0f8f78b33b7 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/regression/SimpleJUnitTest.java @@ -0,0 +1,68 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.test.tests.regression; + +import org.bukkit.entity.Pig; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import ch.njol.skript.test.runner.SkriptJUnitTest; + +/** + * This class is a simple example JUnit. + * A piggy will spawn and get damaged by 100. + * The script under skript/tests/SimpleJUnitTest.sk will do all the assertion using the damage event. + * + * Methods exist in {@link ch.njol.skript.test.runner.SkriptJUnitTest} to simplify repetitive tasks. + */ +public class SimpleJUnitTest extends SkriptJUnitTest { + + private Pig piggy; + + /** + * In the static method of the class, you can utilize methods in SkriptJUnitTest that allow you + * to control how this JUnit test will interact with the server. + */ + static { + // Set the delay to 1 tick. This allows the piggy to be spawned into the world. + setShutdownDelay(1); + } + + @Before + @SuppressWarnings("deprecation") + public void spawnPig() { + piggy = spawnTestPig(); + piggy.setCustomName("Simple JUnit Test"); + } + + @Test + public void testDamage() { // Try to have more descriptive method names other than 'test()' + piggy.damage(100); + } + + @After + public void clearPiggy() { + // Remember to cleanup your test. This is an example method. + // Skript does clean up your JUnit test if it extends SkriptJUnitTest for; + // - Entities + // - Block (using getTestBlock) + } + +} diff --git a/src/test/java/org/skriptlang/skript/test/tests/regression/package-info.java b/src/test/java/org/skriptlang/skript/test/tests/regression/package-info.java new file mode 100644 index 00000000000..8225d91b19e --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/regression/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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +@NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) +package org.skriptlang.skript.test.tests.regression; + +import org.eclipse.jdt.annotation.DefaultLocation; +import org.eclipse.jdt.annotation.NonNullByDefault; + diff --git a/src/test/java/org/skriptlang/skript/test/tests/syntaxes/package-info.java b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/package-info.java new file mode 100644 index 00000000000..1a7edcf6847 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +@NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) +package org.skriptlang.skript.test.tests.syntaxes; + +import org.eclipse.jdt.annotation.DefaultLocation; +import org.eclipse.jdt.annotation.NonNullByDefault; + diff --git a/src/test/java/org/skriptlang/skript/test/tests/utils/UtilsTest.java b/src/test/java/org/skriptlang/skript/test/tests/utils/UtilsTest.java new file mode 100644 index 00000000000..592ea5c9fa8 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/utils/UtilsTest.java @@ -0,0 +1,69 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.test.tests.utils; + +import static org.junit.Assert.assertEquals; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.UnknownHostException; +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.junit.Test; + +import ch.njol.skript.util.Utils; + +/** + * Test methods from the Utils class. + */ +public class UtilsTest { + + /** + * Testing method {@link Utils#getSuperType(Class...)} + */ + @Test + public void testSuperClass() { + Class<?>[][] classes = { + {Object.class, Object.class}, + {String.class, String.class}, + {String.class, Object.class, Object.class}, + {Object.class, String.class, Object.class}, + {String.class, String.class, String.class}, + {Object.class, String.class, Object.class, String.class, Object.class}, + {Double.class, Integer.class, Number.class}, + {UnknownHostException.class, FileNotFoundException.class, IOException.class}, + {SortedMap.class, TreeMap.class, SortedMap.class}, + {LinkedList.class, ArrayList.class, AbstractList.class}, + {List.class, Set.class, Collection.class}, + {ArrayList.class, Set.class, Collection.class}, + }; + for (Class<?>[] cs : classes) { + assertEquals(cs[cs.length - 1], Utils.getSuperType(Arrays.copyOf(cs, cs.length - 1))); + } + } + +} diff --git a/src/test/java/org/skriptlang/skript/test/tests/utils/package-info.java b/src/test/java/org/skriptlang/skript/test/tests/utils/package-info.java new file mode 100644 index 00000000000..a02dc136ec8 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/utils/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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +@NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) +package org.skriptlang.skript.test.tests.utils; + +import org.eclipse.jdt.annotation.DefaultLocation; +import org.eclipse.jdt.annotation.NonNullByDefault; + diff --git a/src/test/skript/README.md b/src/test/skript/README.md index 3955abe012a..77f80457f3e 100644 --- a/src/test/skript/README.md +++ b/src/test/skript/README.md @@ -78,9 +78,14 @@ Use Gradle to launch a test development server: gradlew clean skriptTestDev --console=plain ``` +Note: adding the tag `clean` will clear the build directory, making Skript generate a new server each time. +Don't include the `clean` tag if you want to keep the same server folder around each test. + The server launched will be running at localhost:25565. You can use console as normal, though there is some lag due to Gradle. If you're having trouble, try without <code>--console=plain</code>. +Server files are located at <code>build/test_runners</code>. + To run individual test files, use <code>/sk test \<file\></code>. To run last used file again, just use <code>/sk test</code>. diff --git a/src/test/skript/tests/junit/README.md b/src/test/skript/tests/junit/README.md new file mode 100644 index 00000000000..029b1a5c184 --- /dev/null +++ b/src/test/skript/tests/junit/README.md @@ -0,0 +1,8 @@ +# JUnit testing system +This folder is for scripts that will load and be present for the Skript JUnit tests. +This allows a test to listen for events or persist during JUnit tests. + +An example would be checking the damage of an entity. You would write a test script +and have the on damage event inside that script with assertion checking. +Then in the JUnit Java class, you would perform some action that would damage the entity. +That test script then catches that event and any errors will be included in the final results. diff --git a/src/test/skript/tests/junit/SimpleJUnitTest.sk b/src/test/skript/tests/junit/SimpleJUnitTest.sk new file mode 100644 index 00000000000..25a048ae372 --- /dev/null +++ b/src/test/skript/tests/junit/SimpleJUnitTest.sk @@ -0,0 +1,16 @@ +on script load: + # Setup our objective for this script test to complete with the JUnit test. + ensure junit test "org.skriptlang.skript.test.tests.regression.SimpleJUnitTest" completes "piggy died" + +on damage of pig: + # Check that this is indeed our correct test to match with the JUnit test we want. + junit test is "org.skriptlang.skript.test.tests.regression.SimpleJUnitTest" + + # Using the JUnit name is not required, just another example. + assert custom name of victim is "Simple JUnit Test" with "piggy was not the same" + + # Remember the damage was 100 but Skript represents it by hearts so it's 50. + assert damage is 50 with "damage was not 50" + + # Tell our objective that our runtime objective has been completed which was an entity damage event. + complete objective "piggy died" for junit test "org.skriptlang.skript.test.tests.regression.SimpleJUnitTest" From 1a6f4c1253d5bb53925a8b7c2d5b7fb3ca53a310 Mon Sep 17 00:00:00 2001 From: Fusezion <fusezionstream@gmail.com> Date: Thu, 9 Mar 2023 10:39:17 -0500 Subject: [PATCH 260/619] Support .lang Skript Prefix (#5437) --- src/main/java/ch/njol/skript/Skript.java | 30 +++++++------------ .../ch/njol/skript/localization/Message.java | 19 +++++++++--- src/main/resources/lang/english.lang | 2 +- src/main/resources/lang/french.lang | 2 +- src/main/resources/lang/german.lang | 2 +- src/main/resources/lang/japanese.lang | 2 +- src/main/resources/lang/korean.lang | 2 +- src/main/resources/lang/polish.lang | 2 +- .../resources/lang/simplifiedchinese.lang | 2 +- 9 files changed, 33 insertions(+), 30 deletions(-) diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 251c4d9afe3..5ade760813d 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -150,7 +150,6 @@ import ch.njol.util.Closeable; import ch.njol.util.Kleenean; import ch.njol.util.NullableChecker; -import ch.njol.util.OpenCloseable; import ch.njol.util.StringUtils; import ch.njol.util.coll.iterator.CheckedIterator; import ch.njol.util.coll.iterator.EnumerationIterable; @@ -1837,22 +1836,15 @@ static void logEx(final String... lines) { for (final String line : lines) SkriptLogger.LOGGER.severe(EXCEPTION_PREFIX + line); } - - public static String SKRIPT_PREFIX = ChatColor.GRAY + "[" + ChatColor.GOLD + "Skript" + ChatColor.GRAY + "]" + ChatColor.RESET + " "; - -// static { -// Language.addListener(new LanguageChangeListener() { -// @Override -// public void onLanguageChange() { -// final String s = Language.get_("skript.prefix"); -// if (s != null) -// SKRIPT_PREFIX = Utils.replaceEnglishChatStyles(s) + ChatColor.RESET + " "; -// } -// }); -// } - + + private static final Message SKRIPT_PREFIX_MESSAGE = new Message("skript.prefix"); + + public static String getSkriptPrefix() { + return SKRIPT_PREFIX_MESSAGE.getValueOrDefault("<grey>[<gold>Skript<grey>] <reset>"); + } + public static void info(final CommandSender sender, final String info) { - sender.sendMessage(SKRIPT_PREFIX + Utils.replaceEnglishChatStyles(info)); + sender.sendMessage(Utils.replaceEnglishChatStyles(getSkriptPrefix() + info)); } /** @@ -1861,11 +1853,11 @@ public static void info(final CommandSender sender, final String info) { * @see #adminBroadcast(String) */ public static void broadcast(final String message, final String permission) { - Bukkit.broadcast(SKRIPT_PREFIX + Utils.replaceEnglishChatStyles(message), permission); + Bukkit.broadcast(Utils.replaceEnglishChatStyles(getSkriptPrefix() + message), permission); } public static void adminBroadcast(final String message) { - Bukkit.broadcast(SKRIPT_PREFIX + Utils.replaceEnglishChatStyles(message), "skript.admin"); + broadcast(message, "skript.admin"); } /** @@ -1879,7 +1871,7 @@ public static void message(final CommandSender sender, final String info) { } public static void error(final CommandSender sender, final String error) { - sender.sendMessage(SKRIPT_PREFIX + ChatColor.DARK_RED + Utils.replaceEnglishChatStyles(error)); + sender.sendMessage(Utils.replaceEnglishChatStyles(getSkriptPrefix() + ChatColor.DARK_RED + error)); } /** diff --git a/src/main/java/ch/njol/skript/localization/Message.java b/src/main/java/ch/njol/skript/localization/Message.java index 3cce636f90b..8060de4474c 100644 --- a/src/main/java/ch/njol/skript/localization/Message.java +++ b/src/main/java/ch/njol/skript/localization/Message.java @@ -49,12 +49,12 @@ public class Message { firstChange = false; }); } - + public final String key; @Nullable private String value; boolean revalidate = true; - + public Message(final String key) { this.key = "" + key.toLowerCase(Locale.ENGLISH); messages.add(this); @@ -62,7 +62,7 @@ public Message(final String key) { if (Skript.testing() && Language.isInitialized() && !Language.keyExists(this.key)) Language.missingEntryError(this.key); } - + /** * @return The value of this message in the current language */ @@ -82,7 +82,18 @@ public final String getValue() { validate(); return value; } - + + /** + * Gets the text this Message refers to. If value is null returns a default value. + * + * @param defaultValue The string this method refers to if value is null + * @return This message's value or default value if null + */ + public final String getValueOrDefault(String defaultValue) { + validate(); + return value == null ? defaultValue : value; + } + /** * Checks whether this value is set in the current language or the english default. * diff --git a/src/main/resources/lang/english.lang b/src/main/resources/lang/english.lang index 80b1846fa0c..17225590e3b 100644 --- a/src/main/resources/lang/english.lang +++ b/src/main/resources/lang/english.lang @@ -9,7 +9,7 @@ none: <none> # -- Skript -- skript: copyright: ~ created by & © Peter Güttinger aka Njol ~ - prefix: <gray>[<gold>Skript<gray>] # not used + prefix: <gray>[<gold>Skript<gray>] <reset> quotes error: Invalid use of quotes ("). If you want to use quotes in "quoted text", double them: "". brackets error: Invalid amount or placement of brackets. Please make sure that each opening bracket has a corresponding closing bracket. invalid reload: Skript may only be reloaded by either Bukkit's '/reload' or Skript's '/skript reload' command. diff --git a/src/main/resources/lang/french.lang b/src/main/resources/lang/french.lang index d2ec47c381e..9a548ba8587 100644 --- a/src/main/resources/lang/french.lang +++ b/src/main/resources/lang/french.lang @@ -9,7 +9,7 @@ none: <none> # -- Skript -- skript: copyright: ~ créé par & © Peter Güttinger alias Njol ~ - prefix: <gray>[<gold>Skript<gray>] # not used + prefix: <gray>[<gold>Skript<gray>] <reset> quotes error: Utilisation incorrecte des guillemets ("). Si vous voulez utiliser des guillemets dans du texte "entre guillemets", doublez-les : "". brackets error: Quantité ou emplacement des accolades incorrect. Assurez-vous que chaque accolade ouvrante '{' corresponde à une accolade fermante '}'. invalid reload: Skript ne peut être rechargé qu'avec la commande '/reload' de Bukkit ou la commande '/skript reload' de Skript. diff --git a/src/main/resources/lang/german.lang b/src/main/resources/lang/german.lang index 1594e3c0a88..cb9637e9dd7 100644 --- a/src/main/resources/lang/german.lang +++ b/src/main/resources/lang/german.lang @@ -9,7 +9,7 @@ none: <none> # -- Skript -- skript: copyright: ~ erstellt von & © Peter Güttinger alias Njol ~ - prefix: <gray>[<gold>Skript<gray>] + prefix: <gray>[<gold>Skript<gray>] <reset> quotes error: Unerlaubte Verwendung von Anführungszeichen ("). Wenn du solche in "Text mit Anführungszeichen" einfügen willst, verdopple sie: "". brackets error: Ungültige Anzahl/Anordnung von Klammern. Bitte sorge dafür, dass auf jede öffnende Klammer eine Schliessende folgt. invalid reload: Skript darf nur mittles Bukkits '/reload' oder Skripts '/skript reload' neu geladen werden. diff --git a/src/main/resources/lang/japanese.lang b/src/main/resources/lang/japanese.lang index 64fa4c0d000..708df5a717d 100644 --- a/src/main/resources/lang/japanese.lang +++ b/src/main/resources/lang/japanese.lang @@ -9,7 +9,7 @@ none: <none> # -- Skript -- skript: copyright: ~ created by & © Peter Güttinger aka Njol ~ - prefix: <gray>[<gold>Skript<gray>] # not used + prefix: <gray>[<gold>Skript<gray>] <reset> quotes error: クオーテーション(")の使い方が正しくありません。 クオーテーションをテキストに対して使用したい場合 "このように" 2つの "" で囲ってください。 brackets error: セクションが正しく閉じられていません。 カッコの配置か量を見直してみてください。 invalid reload: Skriptの再読み込みは Bukkit側の '/reload' もしくは Skript側の '/skript reload' でのみ再読み込みが可能です。 diff --git a/src/main/resources/lang/korean.lang b/src/main/resources/lang/korean.lang index 60bd1eb36d9..b2be122224b 100644 --- a/src/main/resources/lang/korean.lang +++ b/src/main/resources/lang/korean.lang @@ -9,7 +9,7 @@ none: <none> # -- Skript -- skript: copyright: ~ created by & © Peter Güttinger aka Njol ~ - prefix: <gray>[<gold>Skript<gray>] # not used + prefix: <gray>[<gold>Skript<gray>] <reset> quotes error: 따옴표(")가 잘못되었습니다. 인용문 텍스트에서 따옴표를 사용하려면 따옴표를 두 개 입력해야 합니다. brackets error: 대괄호의 양 또는 위치가 잘못되었습니다. 각 여는 대괄호에 닫는 대괄호가 입력되어있는지 확인하십시오. invalid reload: Skript는 Bukkit의 '/reload' 또는 스크립트의 '/skript reload' 로만 다시 로드할 수 있습니다. diff --git a/src/main/resources/lang/polish.lang b/src/main/resources/lang/polish.lang index acbefd751f9..633f7e555a9 100644 --- a/src/main/resources/lang/polish.lang +++ b/src/main/resources/lang/polish.lang @@ -9,7 +9,7 @@ none: <none> # -- Skript -- skript: copyright: ~ stworzony przez & © Peter "Njol" Güttinger ~ - prefix: <gray>[<gold>Skript<gray>] # not used + prefix: <gray>[<gold>Skript<gray>] <reset> quotes error: Nieprawidłowe użycie cudzysłowu ("). Jeśli chcesz użyć cudzysłowu w "cytowanym tekście", podwój go: "". brackets error: Nieprawidłowa liczba lub rozmieszczenie nawiasów. Upewnij się, że każdy nawias otwierający ma odpowiadający mu nawias zamykający. invalid reload: Skript może być przeładowany tylko przez polecenie '/reload' Bukkita lub '/skript reload'. diff --git a/src/main/resources/lang/simplifiedchinese.lang b/src/main/resources/lang/simplifiedchinese.lang index aa1900b22ae..e1229106eba 100644 --- a/src/main/resources/lang/simplifiedchinese.lang +++ b/src/main/resources/lang/simplifiedchinese.lang @@ -9,7 +9,7 @@ none: <none> # -- Skript -- skript: copyright: ~ created by & © Peter Güttinger aka Njol ~ - prefix: <gray>[<gold>Skript<gray>] # not used + prefix: <gray>[<gold>Skript<gray>] <reset> quotes error: 无效的引号使用(")。如果你想在"引号文本"中使用引号,双写它们("")。 brackets error: 括号的数量或位置无效。请确保每一个左括号都有一个相应的右括号。 invalid reload: Skript只能通过Bukkit的“/reload”或Skript的“/skript reload”命令来重新加载。 From dd8fe4894b4ceb1461d588a20931584249c47d92 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Mar 2023 14:19:32 -0700 Subject: [PATCH 261/619] Bump junit:junit from 4.12 to 4.13.2 (#5500) Bumps [junit:junit](https://github.com/junit-team/junit4) from 4.12 to 4.13.2. - [Release notes](https://github.com/junit-team/junit4/releases) - [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.12.md) - [Commits](https://github.com/junit-team/junit4/compare/r4.12...r4.13.2) --- updated-dependencies: - dependency-name: junit:junit dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index fff7d4c2e69..ca478f29c2a 100644 --- a/build.gradle +++ b/build.gradle @@ -38,7 +38,7 @@ dependencies { implementation fileTree(dir: 'lib', include: '*.jar') - testShadow group: 'junit', name: 'junit', version: '4.12' + testShadow group: 'junit', name: 'junit', version: '4.13.2' testShadow group: 'org.easymock', name: 'easymock', version: '4.2' } From e1ce30fb869b11d8a1aa26f9a8273e81a1f17247 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Mar 2023 14:37:38 -0700 Subject: [PATCH 262/619] Bump org.easymock:easymock from 4.2 to 5.1.0 (#5499) Bumps [org.easymock:easymock](https://github.com/easymock/easymock) from 4.2 to 5.1.0. - [Release notes](https://github.com/easymock/easymock/releases) - [Changelog](https://github.com/easymock/easymock/blob/master/ReleaseNotes.md) - [Commits](https://github.com/easymock/easymock/compare/easymock-4.2...easymock-5.1.0) --- updated-dependencies: - dependency-name: org.easymock:easymock dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ca478f29c2a..6b1b3472ca4 100644 --- a/build.gradle +++ b/build.gradle @@ -39,7 +39,7 @@ dependencies { implementation fileTree(dir: 'lib', include: '*.jar') testShadow group: 'junit', name: 'junit', version: '4.13.2' - testShadow group: 'org.easymock', name: 'easymock', version: '4.2' + testShadow group: 'org.easymock', name: 'easymock', version: '5.1.0' } compileJava.options.encoding = 'UTF-8' From 2fcb4d4644207185c204d60a3323f12192dc800c Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Fri, 10 Mar 2023 09:46:04 +0300 Subject: [PATCH 263/619] Add Translatable Tags (#5481) --- .../njol/skript/util/chat/BungeeConverter.java | 18 ++++++++++++++++-- .../skript/util/chat/MessageComponent.java | 3 +++ .../njol/skript/util/chat/SkriptChatCode.java | 9 ++++++++- src/main/resources/lang/default.lang | 1 + 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/njol/skript/util/chat/BungeeConverter.java b/src/main/java/ch/njol/skript/util/chat/BungeeConverter.java index 6eafc860c66..82522220d3a 100644 --- a/src/main/java/ch/njol/skript/util/chat/BungeeConverter.java +++ b/src/main/java/ch/njol/skript/util/chat/BungeeConverter.java @@ -18,6 +18,7 @@ */ package ch.njol.skript.util.chat; +import java.util.Arrays; import java.util.List; import ch.njol.skript.Skript; @@ -25,6 +26,7 @@ import net.md_5.bungee.api.chat.ClickEvent; import net.md_5.bungee.api.chat.HoverEvent; import net.md_5.bungee.api.chat.TextComponent; +import net.md_5.bungee.api.chat.TranslatableComponent; /** * Converts Skript's chat components into Bungee's BaseComponents which Spigot @@ -36,8 +38,20 @@ public class BungeeConverter { @SuppressWarnings("null") public static BaseComponent convert(MessageComponent origin) { - BaseComponent base = new TextComponent(origin.text); - + BaseComponent base; + if (origin.translation != null) { + String[] strings = origin.translation.split(":"); + String key = strings[0]; + if (strings.length > 1) { + base = new TranslatableComponent(key, Arrays.copyOfRange(strings, 1, strings.length, Object[].class)); + } else { + base = new TranslatableComponent(key); + } + base.addExtra(new TextComponent(origin.text)); + } else { + base = new TextComponent(origin.text); + } + base.setBold(origin.bold); base.setItalic(origin.italic); base.setUnderlined(origin.underlined); diff --git a/src/main/java/ch/njol/skript/util/chat/MessageComponent.java b/src/main/java/ch/njol/skript/util/chat/MessageComponent.java index 725114cca05..468e5fafdf2 100644 --- a/src/main/java/ch/njol/skript/util/chat/MessageComponent.java +++ b/src/main/java/ch/njol/skript/util/chat/MessageComponent.java @@ -89,6 +89,9 @@ public class MessageComponent { */ @Nullable public String font; + + @Nullable + public String translation; public static class ClickEvent { public ClickEvent(ClickEvent.Action action, String value) { diff --git a/src/main/java/ch/njol/skript/util/chat/SkriptChatCode.java b/src/main/java/ch/njol/skript/util/chat/SkriptChatCode.java index f8324e123ea..1caa202d424 100644 --- a/src/main/java/ch/njol/skript/util/chat/SkriptChatCode.java +++ b/src/main/java/ch/njol/skript/util/chat/SkriptChatCode.java @@ -156,8 +156,15 @@ public void updateComponent(MessageComponent component, String param) { insertion(true) { @Override public void updateComponent(MessageComponent component, String param) { component.insertion = param; } + }, + + translate(true) { + @Override + public void updateComponent(MessageComponent component, String param) { + component.translation = param; + } }; - + private boolean hasParam; @Nullable diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 662a7e7587e..0f167da60ff 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -409,6 +409,7 @@ chat styles: show_text: tooltip, show text, ttp font: font, f insertion: insertion, insert, ins + translate: translate, tr, lang # -- Directions -- directions: From 9291132ab9d1eb32fceb3862c8a2301e9d5eed45 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Fri, 10 Mar 2023 14:45:15 -0500 Subject: [PATCH 264/619] Converter and Comparator improvements (#5403) --- src/main/java/ch/njol/skript/Skript.java | 4 +- .../ch/njol/skript/registrations/Classes.java | 4 +- .../skript/registrations/Comparators.java | 2 +- .../njol/skript/registrations/Converters.java | 2 +- .../skript/lang/comparator/Comparators.java | 196 +++++++++---- .../lang/comparator/ConvertedComparator.java | 2 +- .../skript/lang/comparator/Relation.java | 6 +- .../lang/converter/ChainedConverter.java | 3 +- .../skript/lang/converter/Converters.java | 274 +++++++++++------- 9 files changed, 308 insertions(+), 185 deletions(-) diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 5ade760813d..e78798f91a1 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -1263,9 +1263,9 @@ public static void checkAcceptRegistrations() { } private static void stopAcceptingRegistrations() { - acceptRegistrations = false; - Converters.createChainedConverters(); + + acceptRegistrations = false; Classes.onRegistrationsStop(); } diff --git a/src/main/java/ch/njol/skript/registrations/Classes.java b/src/main/java/ch/njol/skript/registrations/Classes.java index 8f48370db56..126b454efa5 100644 --- a/src/main/java/ch/njol/skript/registrations/Classes.java +++ b/src/main/java/ch/njol/skript/registrations/Classes.java @@ -499,7 +499,7 @@ public static <T> T parse(final String s, final Class<T> c, final ParseContext c log.printLog(); return t; } - for (final ConverterInfo<?, ?> conv : org.skriptlang.skript.lang.converter.Converters.getConverterInfo()) { + for (final ConverterInfo<?, ?> conv : Converters.getConverterInfos()) { if (context == ParseContext.COMMAND && (conv.getFlags() & Commands.CONVERTER_NO_COMMAND_ARGUMENTS) != 0) continue; if (c.isAssignableFrom(conv.getTo())) { @@ -539,7 +539,7 @@ public static <T> Parser<? extends T> getParser(final Class<T> to) { if (to.isAssignableFrom(ci.getC()) && ci.getParser() != null) return (Parser<? extends T>) ci.getParser(); } - for (final ConverterInfo<?, ?> conv : org.skriptlang.skript.lang.converter.Converters.getConverterInfo()) { + for (final ConverterInfo<?, ?> conv : Converters.getConverterInfos()) { if (to.isAssignableFrom(conv.getTo())) { for (int i = classInfos.length - 1; i >= 0; i--) { final ClassInfo<?> ci = classInfos[i]; diff --git a/src/main/java/ch/njol/skript/registrations/Comparators.java b/src/main/java/ch/njol/skript/registrations/Comparators.java index 3e6a3a1f8b5..c027d7ad943 100644 --- a/src/main/java/ch/njol/skript/registrations/Comparators.java +++ b/src/main/java/ch/njol/skript/registrations/Comparators.java @@ -57,7 +57,7 @@ public static Relation compare(final @Nullable Object o1, final @Nullable Object } public static java.util.Comparator<Object> getJavaComparator() { - return org.skriptlang.skript.lang.comparator.Comparators.JAVA_COMPARATOR; + return (o1, o2) -> compare(o1, o2).getRelation(); } @Nullable diff --git a/src/main/java/ch/njol/skript/registrations/Converters.java b/src/main/java/ch/njol/skript/registrations/Converters.java index ae5282eeb5c..7a996faacab 100644 --- a/src/main/java/ch/njol/skript/registrations/Converters.java +++ b/src/main/java/ch/njol/skript/registrations/Converters.java @@ -36,7 +36,7 @@ private Converters() {} @SuppressWarnings("unchecked") public static <F, T> List<ConverterInfo<?, ?>> getConverters() { - return org.skriptlang.skript.lang.converter.Converters.getConverterInfo().stream() + return org.skriptlang.skript.lang.converter.Converters.getConverterInfos().stream() .map(unknownInfo -> { org.skriptlang.skript.lang.converter.ConverterInfo<F, T> info = (org.skriptlang.skript.lang.converter.ConverterInfo<F, T>) unknownInfo; return new ConverterInfo<>(info.getFrom(), info.getTo(), info.getConverter()::convert, info.getFlags()); diff --git a/src/main/java/org/skriptlang/skript/lang/comparator/Comparators.java b/src/main/java/org/skriptlang/skript/lang/comparator/Comparators.java index 5540cb613d4..8e3aa23de72 100644 --- a/src/main/java/org/skriptlang/skript/lang/comparator/Comparators.java +++ b/src/main/java/org/skriptlang/skript/lang/comparator/Comparators.java @@ -20,10 +20,11 @@ import ch.njol.skript.Skript; import ch.njol.skript.SkriptAPIException; -import ch.njol.skript.classes.Converter; -import ch.njol.skript.registrations.Converters; import ch.njol.util.Pair; import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Unmodifiable; +import org.skriptlang.skript.lang.converter.Converter; +import org.skriptlang.skript.lang.converter.Converters; import java.util.ArrayList; import java.util.Collections; @@ -44,35 +45,35 @@ private Comparators() {} /** * A default comparator to compare two objects using {@link Object#equals(Object)}. */ - public static final Comparator<Object, Object> EQUALS_COMPARATOR = (o1, o2) -> Relation.get(o1.equals(o2)); - - /** - * A Java {@link java.util.Comparator} for comparing two objects using {@link #compare(Object, Object)}. - */ - public static final java.util.Comparator<Object> JAVA_COMPARATOR = (o1, o2) -> compare(o1, o2).getRelation(); + private static final ComparatorInfo<Object, Object> EQUALS_COMPARATOR_INFO = new ComparatorInfo<>( + Object.class, + Object.class, + (o1, o2) -> Relation.get(o1.equals(o2)) + ); /** * A List containing information for all registered comparators. */ - private static final List<ComparatorInfo<?, ?>> COMPARATORS = Collections.synchronizedList(new ArrayList<>()); + private static final List<ComparatorInfo<?, ?>> COMPARATORS = new ArrayList<>(50); /** - * @return An unmodifiable, synchronized list containing all registered {@link ComparatorInfo}s. - * When traversing this list, please refer to {@link Collections#synchronizedList(List)} to ensure that - * the list is properly traversed due to its synchronized status. + * @return An unmodifiable list containing all registered {@link ComparatorInfo}s. * Please note that this does not include any special Comparators resolved by Skript during runtime. * This method ONLY returns Comparators explicitly registered during registration. * Thus, it is recommended to use {@link #getComparator(Class, Class)} if possible. */ + @Unmodifiable public static List<ComparatorInfo<?, ?>> getComparatorInfos() { + assertIsDoneLoading(); return Collections.unmodifiableList(COMPARATORS); } /** * A map for quickly accessing comparators that have already been resolved. + * Some pairs may point to a null value, indicating that no comparator exists between the two types. * This is useful for skipping complex lookups that may require conversion and inversion. */ - private static final Map<Pair<Class<?>, Class<?>>, Comparator<?, ?>> QUICK_ACCESS_COMPARATORS = Collections.synchronizedMap(new HashMap<>()); + private static final Map<Pair<Class<?>, Class<?>>, ComparatorInfo<?, ?>> QUICK_ACCESS_COMPARATORS = new HashMap<>(50); /** * Registers a new Comparator with Skript's collection of Comparators. @@ -87,18 +88,20 @@ public static <T1, T2> void registerComparator( ) { Skript.checkAcceptRegistrations(); - if (firstType == Object.class && secondType == Object.class) + if (firstType == Object.class && secondType == Object.class) { throw new IllegalArgumentException("It is not possible to add a comparator between objects"); + } - for (ComparatorInfo<?, ?> info : COMPARATORS) { - if (info.firstType == firstType && info.secondType == secondType) { - throw new SkriptAPIException( - "A Comparator comparing '" + firstType + "' and '" + secondType + " already exists!" - ); + synchronized (COMPARATORS) { + for (ComparatorInfo<?, ?> info : COMPARATORS) { + if (info.firstType == firstType && info.secondType == secondType) { + throw new SkriptAPIException( + "A Comparator comparing '" + firstType + "' and '" + secondType + "' already exists!" + ); + } } + COMPARATORS.add(new ComparatorInfo<>(firstType, secondType, comparator)); } - - COMPARATORS.add(new ComparatorInfo<>(firstType, secondType, comparator)); } /** @@ -106,41 +109,65 @@ public static <T1, T2> void registerComparator( * @param first The first object for comparison. * @param second The second object for comparison. * @return The Relation between the two provided objects. + * Guaranteed to be {@link Relation#NOT_EQUAL} if either parameter is null. */ @SuppressWarnings("unchecked") public static <T1, T2> Relation compare(@Nullable T1 first, @Nullable T2 second) { - if (first == null || second == null) + assertIsDoneLoading(); // this would be checked later on too, but we want this guaranteed to fail + + if (first == null || second == null) { return Relation.NOT_EQUAL; + } + + if (first == second) { // easiest check of them all! + return Relation.EQUAL; + } Comparator<T1, T2> comparator = getComparator((Class<T1>) first.getClass(), (Class<T2>) second.getClass()); - if (comparator == null) + if (comparator == null) { return Relation.NOT_EQUAL; + } return comparator.compare(first, second); } /** - * A method for obtaining a comparator that can compare two objects of 'firstType' and 'secondType'. + * A method for obtaining a Comparator that can compare two objects of 'firstType' and 'secondType'. * Please note that comparators may convert objects if necessary for comparisons. * @param firstType The first type for comparison. * @param secondType The second type for comparison. - * @return A comparator capable of determine the {@link Relation} between two objects of 'firstType' and 'secondType'. + * @return A Comparator capable of determine the {@link Relation} between two objects of 'firstType' and 'secondType'. * Will be null if no comparator capable of comparing two objects of 'firstType' and 'secondType' was found. */ @Nullable - @SuppressWarnings("unchecked") public static <T1, T2> Comparator<T1, T2> getComparator(Class<T1> firstType, Class<T2> secondType) { - if (Skript.isAcceptRegistrations()) - throw new SkriptAPIException("Comparators cannot be retrieved until Skript has finished registrations."); + ComparatorInfo<T1, T2> info = getComparatorInfo(firstType, secondType); + return info != null ? info.comparator : null; + } - Pair<Class<?>, Class<?>> pair = new Pair<>(firstType, secondType); - Comparator<T1, T2> comparator; + /** + * A method for obtaining the info of a Comparator that can compare two objects of 'firstType' and 'secondType'. + * Please note that comparators may convert objects if necessary for comparisons. + * @param firstType The first type for comparison. + * @param secondType The second type for comparison. + * @return The info of a Comparator capable of determine the {@link Relation} between two objects of 'firstType' and 'secondType'. + * Will be null if no info for comparing two objects of 'firstType' and 'secondType' was found. + */ + @Nullable + @SuppressWarnings("unchecked") + public static <T1, T2> ComparatorInfo<T1, T2> getComparatorInfo(Class<T1> firstType, Class<T2> secondType) { + assertIsDoneLoading(); - if (QUICK_ACCESS_COMPARATORS.containsKey(pair)) { - comparator = (Comparator<T1, T2>) QUICK_ACCESS_COMPARATORS.get(pair); - } else { // Compute QUICK_ACCESS for provided types - comparator = getComparator_i(firstType, secondType); - QUICK_ACCESS_COMPARATORS.put(pair, comparator); + Pair<Class<?>, Class<?>> pair = new Pair<>(firstType, secondType); + ComparatorInfo<T1, T2> comparator; + + synchronized (QUICK_ACCESS_COMPARATORS) { + if (QUICK_ACCESS_COMPARATORS.containsKey(pair)) { + comparator = (ComparatorInfo<T1, T2>) QUICK_ACCESS_COMPARATORS.get(pair); + } else { // Compute QUICK_ACCESS for provided types + comparator = getComparatorInfo_i(firstType, secondType); + QUICK_ACCESS_COMPARATORS.put(pair, comparator); + } } return comparator; @@ -151,7 +178,7 @@ public static <T1, T2> Comparator<T1, T2> getComparator(Class<T1> firstType, Cla * This method handles regular {@link Comparator}s, {@link ConvertedComparator}s, and {@link InverseComparator}s. * @param firstType The first type for comparison. * @param secondType The second type for comparison. - * @return A comparator capable of determine the {@link Relation} between two objects of 'firstType' and 'secondType'. + * @return The info of the comparator capable of determine the {@link Relation} between two objects of 'firstType' and 'secondType'. * Will be null if no comparator capable of comparing two objects of 'firstType' and 'secondType' was found. * @param <T1> The first type for comparison. * @param <T2> The second type for comparison. @@ -162,29 +189,32 @@ public static <T1, T2> Comparator<T1, T2> getComparator(Class<T1> firstType, Cla */ @Nullable @SuppressWarnings("unchecked") - private static <T1, T2, C1, C2> Comparator<T1, T2> getComparator_i( + private static <T1, T2, C1, C2> ComparatorInfo<T1, T2> getComparatorInfo_i( Class<T1> firstType, Class<T2> secondType ) { - // Look for an exact match for (ComparatorInfo<?, ?> info : COMPARATORS) { if (info.firstType == firstType && info.secondType == secondType) { - return (Comparator<T1, T2>) info.comparator; + return (ComparatorInfo<T1, T2>) info; } } // Look for a basically perfect match for (ComparatorInfo<?, ?> info : COMPARATORS) { if (info.firstType.isAssignableFrom(firstType) && info.secondType.isAssignableFrom(secondType)) { - return (Comparator<T1, T2>) info.comparator; + return (ComparatorInfo<T1, T2>) info; } } // Try to match and create an InverseComparator for (ComparatorInfo<?, ?> info : COMPARATORS) { if (info.comparator.supportsInversion() && info.firstType.isAssignableFrom(secondType) && info.secondType.isAssignableFrom(firstType)) { - return new InverseComparator<>((Comparator<T2, T1>) info.comparator); + return new ComparatorInfo<>( + firstType, + secondType, + new InverseComparator<>((Comparator<T2, T1>) info.comparator) + ); } } @@ -193,36 +223,57 @@ private static <T1, T2, C1, C2> Comparator<T1, T2> getComparator_i( ComparatorInfo<C1, C2> info = (ComparatorInfo<C1, C2>) unknownInfo; if (info.firstType.isAssignableFrom(firstType)) { // Attempt to convert the second argument to the second comparator type - Converter<T2, C2> sc2 = (Converter<T2, C2>) Converters.getConverter(secondType, info.secondType); - if (sc2 != null) - return new ConvertedComparator<>(null, info.comparator, sc2); + Converter<T2, C2> sc2 = Converters.getConverter(secondType, info.secondType); + if (sc2 != null) { + return new ComparatorInfo<>( + firstType, + secondType, + new ConvertedComparator<>(null, info.comparator, sc2) + ); + } } if (info.secondType.isAssignableFrom(secondType)) { // Attempt to convert the first argument to the first comparator type - Converter<T1, C1> fc1 = (Converter<T1, C1>) Converters.getConverter(firstType, info.firstType); - if (fc1 != null) - return new ConvertedComparator<>(fc1, info.comparator, null); + Converter<T1, C1> fc1 = Converters.getConverter(firstType, info.firstType); + if (fc1 != null) { + return new ComparatorInfo<>( + firstType, + secondType, + new ConvertedComparator<>(fc1, info.comparator, null) + ); + } } } // Attempt converting one parameter but with reversed types for (ComparatorInfo<?, ?> unknownInfo : COMPARATORS) { - if (!unknownInfo.comparator.supportsInversion()) // Unsupported for reversing types + if (!unknownInfo.comparator.supportsInversion()) { // Unsupported for reversing types continue; + } ComparatorInfo<C1, C2> info = (ComparatorInfo<C1, C2>) unknownInfo; if (info.secondType.isAssignableFrom(firstType)) { // Attempt to convert the second argument to the first comparator type - Converter<T2, C1> sc1 = (Converter<T2, C1>) Converters.getConverter(secondType, info.firstType); - if (sc1 != null) - return new InverseComparator<>(new ConvertedComparator<>(sc1, info.comparator, null)); + Converter<T2, C1> sc1 = Converters.getConverter(secondType, info.firstType); + if (sc1 != null) { + return new ComparatorInfo<>( + firstType, + secondType, + new InverseComparator<>(new ConvertedComparator<>(sc1, info.comparator, null)) + ); + } } if (info.firstType.isAssignableFrom(secondType)) { // Attempt to convert the first argument to the second comparator type - Converter<T1, C2> fc2 = (Converter<T1, C2>) Converters.getConverter(firstType, info.secondType); - if (fc2 != null) - new InverseComparator<>(new ConvertedComparator<>(null, info.comparator, fc2)); + Converter<T1, C2> fc2 = Converters.getConverter(firstType, info.secondType); + if (fc2 != null) { + return new ComparatorInfo<>( + firstType, + secondType, + new InverseComparator<>(new ConvertedComparator<>(null, info.comparator, fc2)) + ); + } } } @@ -231,34 +282,51 @@ private static <T1, T2, C1, C2> Comparator<T1, T2> getComparator_i( for (ComparatorInfo<?, ?> unknownInfo : COMPARATORS) { ComparatorInfo<C1, C2> info = (ComparatorInfo<C1, C2>) unknownInfo; - Converter<T1, C1> c1 = (Converter<T1, C1>) Converters.getConverter(firstType, info.firstType); - Converter<T2, C2> c2 = (Converter<T2, C2>) Converters.getConverter(secondType, info.secondType); - if (c1 != null && c2 != null) - return new ConvertedComparator<>(c1, info.comparator, c2); + Converter<T1, C1> c1 = Converters.getConverter(firstType, info.firstType); + Converter<T2, C2> c2 = Converters.getConverter(secondType, info.secondType); + if (c1 != null && c2 != null) { + return new ComparatorInfo<>( + firstType, + secondType, + new ConvertedComparator<>(c1, info.comparator, c2) + ); + } } // Attempt converting both parameters but with reversed types for (ComparatorInfo<?, ?> unknownInfo : COMPARATORS) { - if (!unknownInfo.comparator.supportsInversion()) // Unsupported for reversing types + if (!unknownInfo.comparator.supportsInversion()) { // Unsupported for reversing types continue; + } ComparatorInfo<C1, C2> info = (ComparatorInfo<C1, C2>) unknownInfo; - Converter<T1, C2> c1 = (Converter<T1, C2>) Converters.getConverter(firstType, info.secondType); - Converter<T2, C1> c2 = (Converter<T2, C1>) Converters.getConverter(secondType, info.firstType); - if (c1 != null && c2 != null) - return new InverseComparator<>(new ConvertedComparator<>(c2, info.comparator, c1)); + Converter<T1, C2> c1 = Converters.getConverter(firstType, info.secondType); + Converter<T2, C1> c2 = Converters.getConverter(secondType, info.firstType); + if (c1 != null && c2 != null) { + return new ComparatorInfo<>( + firstType, + secondType, + new InverseComparator<>(new ConvertedComparator<>(c2, info.comparator, c1)) + ); + } } // Same class but no comparator if (firstType != Object.class && secondType == firstType) { - return (Comparator<T1, T2>) EQUALS_COMPARATOR; + return (ComparatorInfo<T1, T2>) EQUALS_COMPARATOR_INFO; } // Well, we tried! return null; } + private static void assertIsDoneLoading() { + if (Skript.isAcceptRegistrations()) { + throw new SkriptAPIException("Comparators cannot be retrieved until Skript has finished registrations."); + } + } + } diff --git a/src/main/java/org/skriptlang/skript/lang/comparator/ConvertedComparator.java b/src/main/java/org/skriptlang/skript/lang/comparator/ConvertedComparator.java index d38a84521ba..eced387a77c 100644 --- a/src/main/java/org/skriptlang/skript/lang/comparator/ConvertedComparator.java +++ b/src/main/java/org/skriptlang/skript/lang/comparator/ConvertedComparator.java @@ -18,8 +18,8 @@ */ package org.skriptlang.skript.lang.comparator; -import ch.njol.skript.classes.Converter; import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.converter.Converter; /** * A ConvertedComparator is a comparator that converts its parameters so that they may be used diff --git a/src/main/java/org/skriptlang/skript/lang/comparator/Relation.java b/src/main/java/org/skriptlang/skript/lang/comparator/Relation.java index fcba1245c6c..1f52ed46106 100644 --- a/src/main/java/org/skriptlang/skript/lang/comparator/Relation.java +++ b/src/main/java/org/skriptlang/skript/lang/comparator/Relation.java @@ -72,8 +72,9 @@ public static Relation get(double d) { * @return Whether this Relation is part of the given Relation, e.g. <code>GREATER_OR_EQUAL.isImpliedBy(EQUAL)</code> returns true. */ public boolean isImpliedBy(Relation other) { - if (other == this) + if (other == this) { return true; + } switch (this) { case EQUAL: case GREATER: @@ -96,8 +97,9 @@ public boolean isImpliedBy(Relation other) { */ public boolean isImpliedBy(Relation... others) { for (Relation other : others) { - if (isImpliedBy(other)) + if (isImpliedBy(other)) { return true; + } } return false; } diff --git a/src/main/java/org/skriptlang/skript/lang/converter/ChainedConverter.java b/src/main/java/org/skriptlang/skript/lang/converter/ChainedConverter.java index ee6abc15c9e..6be4fb80ce0 100644 --- a/src/main/java/org/skriptlang/skript/lang/converter/ChainedConverter.java +++ b/src/main/java/org/skriptlang/skript/lang/converter/ChainedConverter.java @@ -52,8 +52,9 @@ final class ChainedConverter<F, M, T> implements Converter<F, T> { @Nullable public T convert(F from) { M middle = first.convert(from); - if (middle == null) + if (middle == null) { return null; + } return second.convert(middle); } diff --git a/src/main/java/org/skriptlang/skript/lang/converter/Converters.java b/src/main/java/org/skriptlang/skript/lang/converter/Converters.java index 23a4ddf5051..e8d6a98f8e9 100644 --- a/src/main/java/org/skriptlang/skript/lang/converter/Converters.java +++ b/src/main/java/org/skriptlang/skript/lang/converter/Converters.java @@ -20,7 +20,9 @@ import ch.njol.skript.Skript; import ch.njol.skript.SkriptAPIException; +import ch.njol.util.Pair; import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Unmodifiable; import java.lang.reflect.Array; import java.util.ArrayList; @@ -29,7 +31,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; /** * Converters are used to provide Skript with specific instructions for converting an object to a different type. @@ -42,25 +43,26 @@ private Converters() {} /** * A List containing information for all registered converters. */ - private static final List<ConverterInfo<?, ?>> CONVERTERS = Collections.synchronizedList(new ArrayList<>(50)); + private static final List<ConverterInfo<?, ?>> CONVERTERS = new ArrayList<>(50); /** - * @return An unmodifiable, synchronized list containing all registered {@link ConverterInfo}s. - * When traversing this list, please refer to {@link Collections#synchronizedList(List)} to ensure that - * the list is properly traversed due to its synchronized status. + * @return An unmodifiable list containing all registered {@link ConverterInfo}s. * Please note that this does not include any special Converters resolved by Skript during runtime. * This method ONLY returns converters explicitly registered during registration. * Thus, it is recommended to use {@link #getConverter(Class, Class)}. */ - public static List<ConverterInfo<?, ?>> getConverterInfo() { + @Unmodifiable + public static List<ConverterInfo<?, ?>> getConverterInfos() { + assertIsDoneLoading(); return Collections.unmodifiableList(CONVERTERS); } /** * A map for quickly access converters that have already been resolved. + * Some pairs may point to a null value, indicating that no converter exists between the two types. * This is useful for skipping complex lookups that may require chaining. */ - private static final Map<Integer, ConverterInfo<?, ?>> QUICK_ACCESS_CONVERTERS = Collections.synchronizedMap(new HashMap<>(50)); + private static final Map<Pair<Class<?>, Class<?>>, ConverterInfo<?, ?>> QUICK_ACCESS_CONVERTERS = new HashMap<>(50); /** * Registers a new Converter with Skript's collection of Converters. @@ -84,13 +86,14 @@ public static <F, T> void registerConverter(Class<F> from, Class<T> to, Converte ConverterInfo<F, T> info = new ConverterInfo<>(from, to, converter, flags); - if (exactConverterExists(from, to)) { - throw new SkriptAPIException( - "A Converter from '" + from + "' to '" + to + " already exists!" - ); + synchronized (CONVERTERS) { + if (exactConverterExists(from, to)) { + throw new SkriptAPIException( + "A Converter from '" + from + "' to '" + to + "' already exists!" + ); + } + CONVERTERS.add(info); } - - CONVERTERS.add(info); } /** @@ -102,64 +105,68 @@ public static <F, T> void registerConverter(Class<F> from, Class<T> to, Converte // REMIND how to manage overriding of converters? - shouldn't actually matter @SuppressWarnings("unchecked") public static <F, M, T> void createChainedConverters() { - for (int i = 0; i < CONVERTERS.size(); i++) { - - ConverterInfo<?, ?> unknownInfo1 = CONVERTERS.get(i); - for (int j = 0; j < CONVERTERS.size(); j++) { // Not from j = i+1 since new converters get added during the loops - - ConverterInfo<?, ?> unknownInfo2 = CONVERTERS.get(j); - - // chain info -> info2 - if ( - unknownInfo2.getFrom() != Object.class // Object can only exist at the beginning of a chain - && (unknownInfo1.getFlags() & Converter.NO_RIGHT_CHAINING) == 0 - && (unknownInfo2.getFlags() & Converter.NO_LEFT_CHAINING) == 0 - && unknownInfo2.getFrom().isAssignableFrom(unknownInfo1.getTo()) - && !exactConverterExists(unknownInfo1.getFrom(), unknownInfo2.getTo()) - ) { - ConverterInfo<F, M> info1 = (ConverterInfo<F, M>) unknownInfo1; - ConverterInfo<M, T> info2 = (ConverterInfo<M, T>) unknownInfo2; - - CONVERTERS.add(new ConverterInfo<>( - info1.getFrom(), - info2.getTo(), - new ChainedConverter<>(info1.getConverter(), info2.getConverter()), - info1.getFlags() | info2.getFlags() - )); - } + Skript.checkAcceptRegistrations(); + synchronized (CONVERTERS) { + for (int i = 0; i < CONVERTERS.size(); i++) { + + ConverterInfo<?, ?> unknownInfo1 = CONVERTERS.get(i); + for (int j = 0; j < CONVERTERS.size(); j++) { // Not from j = i+1 since new converters get added during the loops + + ConverterInfo<?, ?> unknownInfo2 = CONVERTERS.get(j); + + // chain info -> info2 + if ( + unknownInfo2.getFrom() != Object.class // Object can only exist at the beginning of a chain + && (unknownInfo1.getFlags() & Converter.NO_RIGHT_CHAINING) == 0 + && (unknownInfo2.getFlags() & Converter.NO_LEFT_CHAINING) == 0 + && unknownInfo2.getFrom().isAssignableFrom(unknownInfo1.getTo()) + && !exactConverterExists(unknownInfo1.getFrom(), unknownInfo2.getTo()) + ) { + ConverterInfo<F, M> info1 = (ConverterInfo<F, M>) unknownInfo1; + ConverterInfo<M, T> info2 = (ConverterInfo<M, T>) unknownInfo2; + + CONVERTERS.add(new ConverterInfo<>( + info1.getFrom(), + info2.getTo(), + new ChainedConverter<>(info1.getConverter(), info2.getConverter()), + info1.getFlags() | info2.getFlags() + )); + } + + // chain info2 -> info + else if ( + unknownInfo1.getFrom() != Object.class // Object can only exist at the beginning of a chain + && (unknownInfo1.getFlags() & Converter.NO_LEFT_CHAINING) == 0 + && (unknownInfo2.getFlags() & Converter.NO_RIGHT_CHAINING) == 0 + && unknownInfo1.getFrom().isAssignableFrom(unknownInfo2.getTo()) + && !exactConverterExists(unknownInfo2.getFrom(), unknownInfo1.getTo()) + ) { + ConverterInfo<M, T> info1 = (ConverterInfo<M, T>) unknownInfo1; + ConverterInfo<F, M> info2 = (ConverterInfo<F, M>) unknownInfo2; + + CONVERTERS.add(new ConverterInfo<>( + info2.getFrom(), + info1.getTo(), + new ChainedConverter<>(info2.getConverter(), info1.getConverter()), + info2.getFlags() | info1.getFlags() + )); + } - // chain info2 -> info - else if ( - unknownInfo1.getFrom() != Object.class // Object can only exist at the beginning of a chain - && (unknownInfo1.getFlags() & Converter.NO_LEFT_CHAINING) == 0 - && (unknownInfo2.getFlags() & Converter.NO_RIGHT_CHAINING) == 0 - && unknownInfo1.getFrom().isAssignableFrom(unknownInfo2.getTo()) - && !exactConverterExists(unknownInfo2.getFrom(), unknownInfo1.getTo()) - ) { - ConverterInfo<M, T> info1 = (ConverterInfo<M, T>) unknownInfo1; - ConverterInfo<F, M> info2 = (ConverterInfo<F, M>) unknownInfo2; - - CONVERTERS.add(new ConverterInfo<>( - info2.getFrom(), - info1.getTo(), - new ChainedConverter<>(info2.getConverter(), info1.getConverter()), - info2.getFlags() | info1.getFlags() - )); } } - } } /** - * Internal method. + * Internal method. All calling locations are expected to manually synchronize this method if necessary. * @return Whether a Converter exists that EXACTLY matches the provided types. */ private static boolean exactConverterExists(Class<?> from, Class<?> to) { for (ConverterInfo<?, ?> info : CONVERTERS) { - if (from == info.getFrom() && to == info.getTo()) + if (from == info.getFrom() && to == info.getTo()) { return true; + } } return false; } @@ -168,8 +175,10 @@ private static boolean exactConverterExists(Class<?> from, Class<?> to) { * @return Whether a Converter capable of converting 'fromType' to 'toType' exists. */ public static boolean converterExists(Class<?> fromType, Class<?> toType) { - if (toType.isAssignableFrom(fromType) || fromType.isAssignableFrom(toType)) + assertIsDoneLoading(); + if (toType.isAssignableFrom(fromType) || fromType.isAssignableFrom(toType)) { return true; + } return getConverter(fromType, toType) != null; } @@ -177,13 +186,28 @@ public static boolean converterExists(Class<?> fromType, Class<?> toType) { * @return Whether a Converter capable of converting 'fromType' to one of the provided 'toTypes' exists. */ public static boolean converterExists(Class<?> fromType, Class<?>... toTypes) { + assertIsDoneLoading(); for (Class<?> toType : toTypes) { - if (converterExists(fromType, toType)) + if (converterExists(fromType, toType)) { return true; + } } return false; } + /** + * A method for obtaining a Converter that can convert an object of 'fromType' into an object of 'toType'. + * @param fromType The type to convert from. + * @param toType The type to convert to. + * @return A Converter capable of converting an object of 'fromType' into an object of 'toType'. + * Will return null if no such Converter exists. + */ + @Nullable + public static <F, T> Converter<F, T> getConverter(Class<F> fromType, Class<T> toType) { + ConverterInfo<F, T> info = getConverterInfo(fromType, toType); + return info != null ? info.getConverter() : null; + } + /** * A method for obtaining the ConverterInfo of a Converter that can convert * an object of 'fromType' into an object of 'toType'. @@ -195,29 +219,21 @@ public static boolean converterExists(Class<?> fromType, Class<?>... toTypes) { @Nullable @SuppressWarnings("unchecked") public static <F, T> ConverterInfo<F, T> getConverterInfo(Class<F> fromType, Class<T> toType) { - int hash = Objects.hash(fromType, toType); + assertIsDoneLoading(); - ConverterInfo<F, T> info = (ConverterInfo<F, T>) QUICK_ACCESS_CONVERTERS.get(hash); + Pair<Class<?>, Class<?>> pair = new Pair<>(fromType, toType); + ConverterInfo<F, T> converter; - if (info == null) { // Manual lookup - info = getConverter_i(fromType, toType); - QUICK_ACCESS_CONVERTERS.put(hash, info); + synchronized (QUICK_ACCESS_CONVERTERS) { + if (QUICK_ACCESS_CONVERTERS.containsKey(pair)) { + converter = (ConverterInfo<F, T>) QUICK_ACCESS_CONVERTERS.get(pair); + } else { // Compute QUICK_ACCESS for provided types + converter = getConverterInfo_i(fromType, toType); + QUICK_ACCESS_CONVERTERS.put(pair, converter); + } } - return info; - } - - /** - * A method for obtaining a Converter that can convert an object of 'fromType' into an object of 'toType'. - * @param fromType The type to convert from. - * @param toType The type to convert to. - * @return A Converter capable of converting an object of 'fromType' into an object of 'toType'. - * Will return null if no such Converter exists. - */ - @Nullable - public static <F, T> Converter<F, T> getConverter(Class<F> fromType, Class<T> toType) { - ConverterInfo<F, T> info = getConverterInfo(fromType, toType); - return info != null ? info.getConverter() : null; + return converter; } /** @@ -237,26 +253,31 @@ public static <F, T> Converter<F, T> getConverter(Class<F> fromType, Class<T> to */ @Nullable @SuppressWarnings("unchecked") - private static <F, T extends ParentType, SubType extends F, ParentType> ConverterInfo<F, T> getConverter_i( + private static <F, T extends ParentType, SubType extends F, ParentType> ConverterInfo<F, T> getConverterInfo_i( Class<F> fromType, Class<T> toType ) { // Check for an exact match for (ConverterInfo<?, ?> info : CONVERTERS) { - if (fromType == info.getFrom() && toType == info.getTo()) + if (fromType == info.getFrom() && toType == info.getTo()) { return (ConverterInfo<F, T>) info; + } } // Check for an almost perfect match for (ConverterInfo<?, ?> info : CONVERTERS) { - if (info.getFrom().isAssignableFrom(fromType) && toType.isAssignableFrom(info.getTo())) + if (info.getFrom().isAssignableFrom(fromType) && toType.isAssignableFrom(info.getTo())) { return (ConverterInfo<F, T>) info; + } } // We don't want to create "maybe" converters for 'Object -> X' conversions // Instead, we should just try and convert during runtime when we have a better idea of the fromType - if (fromType == Object.class) - return new ConverterInfo<>(fromType, toType, fromObject -> Converters.convert(fromObject, toType), 0); + if (fromType == Object.class) { + return new ConverterInfo<>( + fromType, toType, fromObject -> Converters.convert(fromObject, toType), Converter.NO_LEFT_CHAINING + ); + } // Attempt to find converters that have either 'from' OR 'to' not exactly matching for (ConverterInfo<?, ?> unknownInfo : CONVERTERS) { @@ -267,10 +288,11 @@ private static <F, T extends ParentType, SubType extends F, ParentType> Converte // Basically, this converter might convert 'F' into something that's shares a parent with 'T' return new ConverterInfo<>(fromType, toType, fromObject -> { Object converted = info.getConverter().convert(fromObject); - if (toType.isInstance(converted)) + if (toType.isInstance(converted)) { return (T) converted; + } return null; - }, 0); + }, Converter.ALL_CHAINING); } else if (fromType.isAssignableFrom(unknownInfo.getFrom()) && toType.isAssignableFrom(unknownInfo.getTo())) { ConverterInfo<SubType, T> info = (ConverterInfo<SubType, T>) unknownInfo; @@ -278,10 +300,11 @@ private static <F, T extends ParentType, SubType extends F, ParentType> Converte // 'from' doesn't exactly match and needs to be filtered // Basically, this converter will only convert certain 'F' objects return new ConverterInfo<>(fromType, toType, fromObject -> { - if (!info.getFrom().isInstance(fromType)) + if (!info.getFrom().isInstance(fromType)) { return null; + } return info.getConverter().convert((SubType) fromObject); - }, 0); + }, Converter.ALL_CHAINING); } } @@ -295,17 +318,20 @@ private static <F, T extends ParentType, SubType extends F, ParentType> Converte // Basically, this converter will only convert certain 'F' objects // and some conversion results will only share a parent with 'T' return new ConverterInfo<>(fromType, toType, fromObject -> { - if (!info.getFrom().isInstance(fromObject)) + if (!info.getFrom().isInstance(fromObject)) { return null; + } Object converted = info.getConverter().convert((SubType) fromObject); - if (toType.isInstance(converted)) + if (toType.isInstance(converted)) { return (T) converted; + } return null; - }, 0); + }, Converter.ALL_CHAINING); } } + // No converter available return null; } @@ -319,15 +345,19 @@ private static <F, T extends ParentType, SubType extends F, ParentType> Converte @Nullable @SuppressWarnings("unchecked") public static <From, To> To convert(@Nullable From from, Class<To> toType) { - if (from == null) + assertIsDoneLoading(); + if (from == null) { return null; + } - if (toType.isInstance(from)) + if (toType.isInstance(from)) { return (To) from; + } Converter<From, To> converter = getConverter((Class<From>) from.getClass(), toType); - if (converter == null) + if (converter == null) { return null; + } return converter.convert(from); } @@ -341,19 +371,23 @@ public static <From, To> To convert(@Nullable From from, Class<To> toType) { @Nullable @SuppressWarnings("unchecked") public static <From, To> To convert(@Nullable From from, Class<? extends To>[] toTypes) { - if (from == null) + assertIsDoneLoading(); + if (from == null) { return null; + } for (Class<? extends To> toType : toTypes) { - if (toType.isInstance(from)) + if (toType.isInstance(from)) { return (To) from; + } } Class<From> fromType = (Class<From>) from.getClass(); for (Class<? extends To> toType : toTypes) { Converter<From, ? extends To> converter = getConverter(fromType, toType); - if (converter != null) + if (converter != null) { return converter.convert(from); + } } return null; @@ -368,19 +402,22 @@ public static <From, To> To convert(@Nullable From from, Class<? extends To>[] t * This can happen if an object contained within 'from' is not successfully converted. */ @SuppressWarnings("unchecked") - public static <To> To[] convert(@Nullable Object[] from, Class<To> toType) { - //noinspection ConstantConditions - if (from == null) + public static <To> To[] convert(Object @Nullable [] from, Class<To> toType) { + assertIsDoneLoading(); + if (from == null) { return (To[]) Array.newInstance(toType, 0); + } - if (toType.isAssignableFrom(from.getClass().getComponentType())) + if (toType.isAssignableFrom(from.getClass().getComponentType())) { return (To[]) from; + } List<To> converted = new ArrayList<>(from.length); for (Object fromSingle : from) { To convertedSingle = convert(fromSingle, toType); - if (convertedSingle != null) + if (convertedSingle != null) { converted.add(convertedSingle); + } } return converted.toArray((To[]) Array.newInstance(toType, converted.size())); @@ -397,23 +434,26 @@ public static <To> To[] convert(@Nullable Object[] from, Class<To> toType) { * And, of course, the returned array may contain objects of a different type. */ @SuppressWarnings("unchecked") - public static <To> To[] convert(@Nullable Object[] from, Class<? extends To>[] toTypes, Class<To> superType) { - //noinspection ConstantConditions - if (from == null) + public static <To> To[] convert(Object @Nullable [] from, Class<? extends To>[] toTypes, Class<To> superType) { + assertIsDoneLoading(); + if (from == null) { return (To[]) Array.newInstance(superType, 0); + } Class<?> fromType = from.getClass().getComponentType(); for (Class<? extends To> toType : toTypes) { - if (toType.isAssignableFrom(fromType)) + if (toType.isAssignableFrom(fromType)) { return (To[]) from; + } } List<To> converted = new ArrayList<>(from.length); for (Object fromSingle : from) { To convertedSingle = convert(fromSingle, toTypes); - if (convertedSingle != null) + if (convertedSingle != null) { converted.add(convertedSingle); + } } return converted.toArray((To[]) Array.newInstance(superType, converted.size())); @@ -430,17 +470,20 @@ public static <To> To[] convert(@Nullable Object[] from, Class<? extends To>[] t */ @SuppressWarnings("unchecked") public static <From, To> To[] convert(From[] from, Class<To> toType, Converter<? super From, ? extends To> converter) { + assertIsDoneLoading(); To[] converted = (To[]) Array.newInstance(toType, from.length); int j = 0; for (From fromSingle : from) { To convertedSingle = fromSingle == null ? null : converter.convert(fromSingle); - if (convertedSingle != null) + if (convertedSingle != null) { converted[j++] = convertedSingle; + } } - if (j != converted.length) + if (j != converted.length) { converted = Arrays.copyOf(converted, j); + } return converted; } @@ -454,8 +497,9 @@ public static <From, To> To[] convert(From[] from, Class<To> toType, Converter<? */ public static <To> To convertStrictly(Object from, Class<To> toType) { To converted = convert(from, toType); - if (converted == null) + if (converted == null) { throw new ClassCastException("Cannot convert '" + from + "' to an object of type '" + toType + "'"); + } return converted; } @@ -468,12 +512,14 @@ public static <To> To convertStrictly(Object from, Class<To> toType) { */ @SuppressWarnings("unchecked") public static <To> To[] convertStrictly(Object[] from, Class<To> toType) { + assertIsDoneLoading(); To[] converted = (To[]) Array.newInstance(toType, from.length); for (int i = 0; i < from.length; i++) { To convertedSingle = convert(from[i], toType); - if (convertedSingle == null) + if (convertedSingle == null) { throw new ClassCastException("Cannot convert '" + from[i] + "' to an object of type '" + toType + "'"); + } converted[i] = convertedSingle; } @@ -496,4 +542,10 @@ public static <From, To> To[] convertUnsafe(From[] from, Class<?> toType, Conver return convert(from, (Class<To>) toType, converter); } + private static void assertIsDoneLoading() { + if (Skript.isAcceptRegistrations()) { + throw new SkriptAPIException("Converters cannot be retrieved until Skript has finished registrations."); + } + } + } From f4ad063eff42696592b34d14b5e328b8709de52e Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Fri, 10 Mar 2023 12:58:17 -0700 Subject: [PATCH 265/619] Bump version tag to 2.7.0-beta1 (#5390) --- gradle.properties | 2 +- .../njol/skript/classes/EnumSerializer.java | 2 +- .../skript/classes/data/BukkitClasses.java | 4 +- .../skript/classes/data/DefaultFunctions.java | 2 +- .../skript/conditions/CondAnchorWorks.java | 2 +- .../njol/skript/conditions/CondIsFrozen.java | 2 +- .../njol/skript/conditions/CondIsGliding.java | 2 +- .../skript/conditions/CondIsInvisible.java | 2 +- .../ch/njol/skript/conditions/CondIsOp.java | 2 +- .../conditions/CondIsPreferredTool.java | 2 +- .../skript/conditions/CondIsStackable.java | 2 +- .../njol/skript/conditions/CondIsValid.java | 2 +- .../njol/skript/conditions/CondIsWithin.java | 2 +- .../conditions/CondIsWithinLocation.java | 79 +++++++++++++++++++ .../skript/conditions/CondPlayedBefore.java | 2 +- .../conditions/CondRespawnLocation.java | 2 +- .../njol/skript/conditions/CondWillHatch.java | 2 +- .../skript/conditions/CondWithinRadius.java | 2 +- .../ch/njol/skript/effects/EffContinue.java | 2 +- .../java/ch/njol/skript/effects/EffEquip.java | 5 +- .../ch/njol/skript/effects/EffInvisible.java | 2 +- .../ch/njol/skript/effects/EffKnockback.java | 2 +- .../java/ch/njol/skript/effects/EffLook.java | 2 +- .../njol/skript/effects/EffMakeEggHatch.java | 2 +- .../ch/njol/skript/effects/EffPathfind.java | 2 +- .../ch/njol/skript/effects/EffStopSound.java | 2 +- .../java/ch/njol/skript/events/EvtDamage.java | 2 +- .../skript/events/EvtExperienceChange.java | 2 +- .../java/ch/njol/skript/events/EvtItem.java | 2 +- .../skript/events/EvtPlayerChunkEnter.java | 2 +- .../ch/njol/skript/events/EvtSpectate.java | 2 +- .../ch/njol/skript/events/SimpleEvents.java | 14 ++-- .../ch/njol/skript/expressions/ExprAge.java | 2 +- .../expressions/ExprAllBannedEntries.java | 2 +- .../skript/expressions/ExprAnvilText.java | 2 +- .../njol/skript/expressions/ExprArgument.java | 2 +- .../ch/njol/skript/expressions/ExprBed.java | 2 +- .../skript/expressions/ExprBookPages.java | 2 +- .../skript/expressions/ExprBreakSpeed.java | 2 +- .../njol/skript/expressions/ExprCharges.java | 2 +- .../njol/skript/expressions/ExprCommand.java | 2 +- .../ch/njol/skript/expressions/ExprEgg.java | 2 +- .../njol/skript/expressions/ExprElement.java | 2 +- .../skript/expressions/ExprExperience.java | 2 +- .../skript/expressions/ExprFireTicks.java | 2 +- .../skript/expressions/ExprFormatDate.java | 2 +- .../skript/expressions/ExprFreezeTicks.java | 2 +- .../expressions/ExprHatchingNumber.java | 2 +- .../skript/expressions/ExprHatchingType.java | 2 +- .../skript/expressions/ExprJoinSplit.java | 2 +- .../expressions/ExprLastSpawnedEntity.java | 2 +- .../ch/njol/skript/expressions/ExprLoot.java | 2 +- .../expressions/ExprMaxFreezeTicks.java | 2 +- .../skript/expressions/ExprMaxPlayers.java | 2 +- .../skript/expressions/ExprMoonPhase.java | 2 +- .../ch/njol/skript/expressions/ExprName.java | 2 +- .../skript/expressions/ExprNearestEntity.java | 2 +- .../ch/njol/skript/expressions/ExprOps.java | 2 +- .../skript/expressions/ExprPickupDelay.java | 2 +- .../njol/skript/expressions/ExprPlugins.java | 2 +- .../expressions/ExprPotionEffectTier.java | 2 +- .../skript/expressions/ExprRawString.java | 2 +- .../skript/expressions/ExprSeaPickles.java | 2 +- .../ch/njol/skript/expressions/ExprSets.java | 2 +- .../skript/expressions/ExprSourceBlock.java | 2 +- .../expressions/ExprSpectatorTarget.java | 2 +- .../skript/expressions/ExprTimePlayed.java | 2 +- .../njol/skript/expressions/ExprTypeOf.java | 2 +- .../skript/expressions/ExprValueWithin.java | 2 +- .../skript/expressions/ExprVectorRandom.java | 2 +- .../expressions/ExprWorldEnvironment.java | 2 +- .../ch/njol/skript/expressions/LitPi.java | 2 +- .../skript/structures/StructFunction.java | 2 +- .../skript/test/runner/CondMethodExists.java | 2 +- 74 files changed, 159 insertions(+), 83 deletions(-) create mode 100644 src/main/java/ch/njol/skript/conditions/CondIsWithinLocation.java diff --git a/gradle.properties b/gradle.properties index bfe06bfd9d6..54899b7a0b6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ groupid=ch.njol name=skript -version=2.7.0-dev +version=2.7.0-beta1 jarName=Skript.jar testEnv=java17/paper-1.19.3 testEnvJavaVersion=17 diff --git a/src/main/java/ch/njol/skript/classes/EnumSerializer.java b/src/main/java/ch/njol/skript/classes/EnumSerializer.java index d0c32ab039c..eb76d781ca7 100644 --- a/src/main/java/ch/njol/skript/classes/EnumSerializer.java +++ b/src/main/java/ch/njol/skript/classes/EnumSerializer.java @@ -37,7 +37,7 @@ public EnumSerializer(Class<T> c) { } /** - * Enum serialization has been using String serialization since Skript (INSERT VERSION) + * Enum serialization has been using String serialization since Skript (2.7) */ @Override @Deprecated 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 513c97b63e5..99958b74a10 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -1485,14 +1485,14 @@ public String toVariableNameString(EnchantmentOffer eo) { .user("(world ?)?environments?") .name("World Environment") .description("Represents the environment of a world.") - .since("INSERT VERSION")); + .since("2.7")); if (Skript.classExists("io.papermc.paper.world.MoonPhase")) { Classes.registerClass(new EnumClassInfo<>(MoonPhase.class, "moonphase", "moon phases") .user("(lunar|moon) ?phases?") .name("Moon Phase") .description("Represents the phase of a moon.") - .since("INSERT VERSION") + .since("2.7") .requiredPlugins("Paper 1.16+")); } } diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java b/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java index adc71b82041..d9c16f703af 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java @@ -85,7 +85,7 @@ public Number[] executeSimple(Object[][] params) { } }.description("Rounds a number, i.e. returns the closest integer to the argument. Place a second argument to define the decimal placement.") .examples("round(2.34) = 2", "round(2) = 2", "round(2.99) = 3", "round(2.5) = 3") - .since("2.2, INSERT VERSION (decimal placement)")); + .since("2.2, 2.7 (decimal placement)")); Functions.registerFunction(new SimpleJavaFunction<Long>("ceil", numberParam, DefaultClasses.LONG, true) { @Override diff --git a/src/main/java/ch/njol/skript/conditions/CondAnchorWorks.java b/src/main/java/ch/njol/skript/conditions/CondAnchorWorks.java index 48da63a0fc8..8efc6ba2373 100644 --- a/src/main/java/ch/njol/skript/conditions/CondAnchorWorks.java +++ b/src/main/java/ch/njol/skript/conditions/CondAnchorWorks.java @@ -36,7 +36,7 @@ @Description("Checks whether or not respawn anchors work in a world.") @Examples("respawn anchors work in world \"world_nether\"") @RequiredPlugins("Minecraft 1.16+") -@Since("INSERT VERSION") +@Since("2.7") public class CondAnchorWorks extends Condition { static { diff --git a/src/main/java/ch/njol/skript/conditions/CondIsFrozen.java b/src/main/java/ch/njol/skript/conditions/CondIsFrozen.java index f5e7b69e439..d32cef4ec4e 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsFrozen.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsFrozen.java @@ -32,7 +32,7 @@ "if player is frozen:", "\tkill player" }) -@Since("INSERT VERSION") +@Since("2.7") public class CondIsFrozen extends PropertyCondition<Entity> { static { diff --git a/src/main/java/ch/njol/skript/conditions/CondIsGliding.java b/src/main/java/ch/njol/skript/conditions/CondIsGliding.java index 352e6986375..36dc35f6dae 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsGliding.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsGliding.java @@ -29,7 +29,7 @@ @Name("Is Gliding") @Description("Checks whether a living entity is gliding.") @Examples("if player is gliding") -@Since("INSERT VERSION") +@Since("2.7") public class CondIsGliding extends PropertyCondition<LivingEntity> { static { diff --git a/src/main/java/ch/njol/skript/conditions/CondIsInvisible.java b/src/main/java/ch/njol/skript/conditions/CondIsInvisible.java index f5de7b55442..baa1fec0492 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsInvisible.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsInvisible.java @@ -32,7 +32,7 @@ @Name("Is Invisible") @Description("Checks whether a living entity is invisible.") @Examples("target entity is invisible") -@Since("INSERT VERSION") +@Since("2.7") public class CondIsInvisible extends PropertyCondition<LivingEntity> { static { diff --git a/src/main/java/ch/njol/skript/conditions/CondIsOp.java b/src/main/java/ch/njol/skript/conditions/CondIsOp.java index fd90a64942d..22304c3e589 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsOp.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsOp.java @@ -28,7 +28,7 @@ @Name("Is Operator") @Description("Checks whether a player is a server operator.") @Examples("player is an operator") -@Since("INSERT VERSION") +@Since("2.7") public class CondIsOp extends PropertyCondition<OfflinePlayer> { static { diff --git a/src/main/java/ch/njol/skript/conditions/CondIsPreferredTool.java b/src/main/java/ch/njol/skript/conditions/CondIsPreferredTool.java index c03fcc798cd..301e340e14a 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsPreferredTool.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsPreferredTool.java @@ -48,7 +48,7 @@ "\telse:", "\t\tcancel event" }) -@Since("INSERT VERSION") +@Since("2.7") @RequiredPlugins("1.16.5+, Paper 1.19.2+ (blockdata)") public class CondIsPreferredTool extends Condition { diff --git a/src/main/java/ch/njol/skript/conditions/CondIsStackable.java b/src/main/java/ch/njol/skript/conditions/CondIsStackable.java index f419e0ba2dd..1db6e77a4f1 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsStackable.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsStackable.java @@ -32,7 +32,7 @@ "birch wood is stackable", "torch is stackable" }) -@Since("INSERT VERSION") +@Since("2.7") public class CondIsStackable extends PropertyCondition<ItemStack> { static { diff --git a/src/main/java/ch/njol/skript/conditions/CondIsValid.java b/src/main/java/ch/njol/skript/conditions/CondIsValid.java index b2735c42724..a22e7f05b0e 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsValid.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsValid.java @@ -29,7 +29,7 @@ @Name("Is Valid") @Description("Checks whether an entity has died or been despawned for some other reason.") @Examples("if event-entity is valid") -@Since("INSERT VERSION") +@Since("2.7") public class CondIsValid extends PropertyCondition<Entity> { static { diff --git a/src/main/java/ch/njol/skript/conditions/CondIsWithin.java b/src/main/java/ch/njol/skript/conditions/CondIsWithin.java index fee755ec03b..ea466e3ff1b 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsWithin.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsWithin.java @@ -57,7 +57,7 @@ "\tcancel event", "\tsend \"Back up!\" to attacker and victim", }) -@Since("INSERT VERSION") +@Since("2.7") @RequiredPlugins("MC 1.17+ (within block)") public class CondIsWithin extends Condition { diff --git a/src/main/java/ch/njol/skript/conditions/CondIsWithinLocation.java b/src/main/java/ch/njol/skript/conditions/CondIsWithinLocation.java new file mode 100644 index 00000000000..bb3083ffd8b --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondIsWithinLocation.java @@ -0,0 +1,79 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.conditions; + +import org.bukkit.Location; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import ch.njol.skript.conditions.base.PropertyCondition; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Condition; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.util.AABB; +import ch.njol.util.Kleenean; + +@Name("Is Within Location") +@Description({ + "Whether a location is within two other locations forming a cuboid.", + "Using the <a href='conditions.html#CondCompare'>is between</a> condition will refer to a straight line between locations." +}) +@Examples({ + "if player's location is within {_loc1} and {_loc2}:", + "\tsend \"You are in a PvP zone!\" to player" +}) +@Since("2.7") +public class CondIsWithinLocation extends Condition { + + static { + PropertyCondition.register(CondIsWithinLocation.class, "within %location% and %location%", "locations"); + } + + private Expression<Location> locsToCheck, loc1, loc2; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + setNegated(matchedPattern == 1); + locsToCheck = (Expression<Location>) exprs[0]; + loc1 = (Expression<Location>) exprs[1]; + loc2 = (Expression<Location>) exprs[2]; + return true; + } + + @Override + public boolean check(Event event) { + Location one = loc1.getSingle(event); + Location two = loc2.getSingle(event); + if (one == null || two == null || one.getWorld() != two.getWorld()) + return false; + AABB box = new AABB(one, two); + return locsToCheck.check(event, box::contains, isNegated()); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return locsToCheck.toString(event, debug) + " is within " + loc1.toString(event, debug) + " and " + loc2.toString(event, debug); + } + +} diff --git a/src/main/java/ch/njol/skript/conditions/CondPlayedBefore.java b/src/main/java/ch/njol/skript/conditions/CondPlayedBefore.java index e35d748b7f2..06e75a63181 100644 --- a/src/main/java/ch/njol/skript/conditions/CondPlayedBefore.java +++ b/src/main/java/ch/njol/skript/conditions/CondPlayedBefore.java @@ -38,7 +38,7 @@ "player has played on this server before", "player hasn't played before" }) -@Since("1.4, INSERT VERSION (multiple players)") +@Since("1.4, 2.7 (multiple players)") public class CondPlayedBefore extends Condition { static { diff --git a/src/main/java/ch/njol/skript/conditions/CondRespawnLocation.java b/src/main/java/ch/njol/skript/conditions/CondRespawnLocation.java index 73965a6df21..91ec5386f60 100644 --- a/src/main/java/ch/njol/skript/conditions/CondRespawnLocation.java +++ b/src/main/java/ch/njol/skript/conditions/CondRespawnLocation.java @@ -41,7 +41,7 @@ "\tbroadcast \"%player% is respawning in their bed! So cozy!\"" }) @RequiredPlugins("Minecraft 1.16+") -@Since("INSERT VERSION") +@Since("2.7") @Events("respawn") public class CondRespawnLocation extends Condition { diff --git a/src/main/java/ch/njol/skript/conditions/CondWillHatch.java b/src/main/java/ch/njol/skript/conditions/CondWillHatch.java index 5379ed22b81..e344e90529f 100644 --- a/src/main/java/ch/njol/skript/conditions/CondWillHatch.java +++ b/src/main/java/ch/njol/skript/conditions/CondWillHatch.java @@ -40,7 +40,7 @@ "\t\tsend \"Better luck next time!\" to the player" }) @Events("Egg Throw") -@Since("INSERT VERSION") +@Since("2.7") public class CondWillHatch extends Condition { static { diff --git a/src/main/java/ch/njol/skript/conditions/CondWithinRadius.java b/src/main/java/ch/njol/skript/conditions/CondWithinRadius.java index 3d802665d65..3bd301f442b 100644 --- a/src/main/java/ch/njol/skript/conditions/CondWithinRadius.java +++ b/src/main/java/ch/njol/skript/conditions/CondWithinRadius.java @@ -40,7 +40,7 @@ "\t\tcancel event", "\t\tsend \"You can't PVP in spawn.\"" }) -@Since("INSERT VERSION") +@Since("2.7") public class CondWithinRadius extends Condition { static { diff --git a/src/main/java/ch/njol/skript/effects/EffContinue.java b/src/main/java/ch/njol/skript/effects/EffContinue.java index 74a36d2c72d..263ffb39059 100644 --- a/src/main/java/ch/njol/skript/effects/EffContinue.java +++ b/src/main/java/ch/njol/skript/effects/EffContinue.java @@ -44,7 +44,7 @@ "\tif loop-value does not have permission \"moderator\":", "\t\tcontinue # filter out non moderators", "\tbroadcast \"%loop-player% is a moderator!\" # Only moderators get broadcast"}) -@Since("2.2-dev37, INSERT VERSION (while loops)") +@Since("2.2-dev37, 2.7 (while loops)") public class EffContinue extends Effect { static { diff --git a/src/main/java/ch/njol/skript/effects/EffEquip.java b/src/main/java/ch/njol/skript/effects/EffEquip.java index 5f3e4ca907a..c66a5a1d2fd 100644 --- a/src/main/java/ch/njol/skript/effects/EffEquip.java +++ b/src/main/java/ch/njol/skript/effects/EffEquip.java @@ -46,9 +46,6 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; -/** - * @author Peter Güttinger - */ @Name("Equip") @Description("Equips or unequips an entity with some given armor. This will replace any armor that the entity is wearing.") @Examples({ @@ -58,7 +55,7 @@ "unequip all armor from player", "unequip player's armor" }) -@Since("1.0, INSERT VERSION (multiple entities, unequip)") +@Since("1.0, 2.7 (multiple entities, unequip)") public class EffEquip extends Effect { static { diff --git a/src/main/java/ch/njol/skript/effects/EffInvisible.java b/src/main/java/ch/njol/skript/effects/EffInvisible.java index 60e0873b715..b6e2cd3a5a5 100644 --- a/src/main/java/ch/njol/skript/effects/EffInvisible.java +++ b/src/main/java/ch/njol/skript/effects/EffInvisible.java @@ -37,7 +37,7 @@ "When setting an entity to invisible while using an invisibility potion on it, the potion will be overridden and when it runs out the entity keeps its invisibility." }) @Examples("make target entity invisible") -@Since("INSERT VERSION") +@Since("2.7") public class EffInvisible extends Effect { static { diff --git a/src/main/java/ch/njol/skript/effects/EffKnockback.java b/src/main/java/ch/njol/skript/effects/EffKnockback.java index 8cd464c2a1c..2902208b23f 100644 --- a/src/main/java/ch/njol/skript/effects/EffKnockback.java +++ b/src/main/java/ch/njol/skript/effects/EffKnockback.java @@ -40,7 +40,7 @@ "knockback player north", "knock victim (vector from attacker to victim) with strength 10" }) -@Since("INSERT VERSION") +@Since("2.7") @RequiredPlugins("Paper 1.19.2+") public class EffKnockback extends Effect { diff --git a/src/main/java/ch/njol/skript/effects/EffLook.java b/src/main/java/ch/njol/skript/effects/EffLook.java index 6adb1ee96f3..1c1abfaa9f0 100644 --- a/src/main/java/ch/njol/skript/effects/EffLook.java +++ b/src/main/java/ch/njol/skript/effects/EffLook.java @@ -50,7 +50,7 @@ "", "force {_enderman} to face the block 3 meters above {_location} at head rotation speed 100.5 and max head pitch -40" }) -@Since("INSERT VERSION") +@Since("2.7") @RequiredPlugins("Paper 1.17+, Paper 1.19.1+ (Players & Look Anchors)") public class EffLook extends Effect { diff --git a/src/main/java/ch/njol/skript/effects/EffMakeEggHatch.java b/src/main/java/ch/njol/skript/effects/EffMakeEggHatch.java index 1f46d878ed6..a8460592acd 100644 --- a/src/main/java/ch/njol/skript/effects/EffMakeEggHatch.java +++ b/src/main/java/ch/njol/skript/effects/EffMakeEggHatch.java @@ -40,7 +40,7 @@ "\tmake the egg hatch" }) @Events("Egg Throw") -@Since("INSERT VERSION") +@Since("2.7") public class EffMakeEggHatch extends Effect { static { diff --git a/src/main/java/ch/njol/skript/effects/EffPathfind.java b/src/main/java/ch/njol/skript/effects/EffPathfind.java index eb53966d0f6..8de036a6133 100644 --- a/src/main/java/ch/njol/skript/effects/EffPathfind.java +++ b/src/main/java/ch/njol/skript/effects/EffPathfind.java @@ -43,7 +43,7 @@ "make all cows stop pathfinding", "make event-entity pathfind towards player at speed 1" }) -@Since("INSERT VERSION") +@Since("2.7") @RequiredPlugins("Paper") public class EffPathfind extends Effect { diff --git a/src/main/java/ch/njol/skript/effects/EffStopSound.java b/src/main/java/ch/njol/skript/effects/EffStopSound.java index 2f4da1c7861..46a22c22795 100644 --- a/src/main/java/ch/njol/skript/effects/EffStopSound.java +++ b/src/main/java/ch/njol/skript/effects/EffStopSound.java @@ -52,7 +52,7 @@ "stop all sound for all players", "stop sound in record category" }) -@Since("2.4, INSERT VERSION (stop all sounds)") +@Since("2.4, 2.7 (stop all sounds)") @RequiredPlugins("MC 1.17.1 (stop all sounds)") public class EffStopSound extends Effect { diff --git a/src/main/java/ch/njol/skript/events/EvtDamage.java b/src/main/java/ch/njol/skript/events/EvtDamage.java index 0a79634999d..4480b505faf 100644 --- a/src/main/java/ch/njol/skript/events/EvtDamage.java +++ b/src/main/java/ch/njol/skript/events/EvtDamage.java @@ -43,7 +43,7 @@ public class EvtDamage extends SkriptEvent { Skript.registerEvent("Damage", EvtDamage.class, EntityDamageEvent.class, "damag(e|ing) [of %-entitydata%] [by %-entitydata%]") .description("Called when an entity receives damage, e.g. by an attack from another entity, lava, fire, drowning, fall, suffocation, etc.") .examples("on damage:", "on damage of a player:", "on damage of player by zombie:") - .since("1.0, INSERT VERSION (by entity)"); + .since("1.0, 2.7 (by entity)"); } @Nullable diff --git a/src/main/java/ch/njol/skript/events/EvtExperienceChange.java b/src/main/java/ch/njol/skript/events/EvtExperienceChange.java index 9f184b4606a..5fb17ee5c21 100644 --- a/src/main/java/ch/njol/skript/events/EvtExperienceChange.java +++ b/src/main/java/ch/njol/skript/events/EvtExperienceChange.java @@ -39,7 +39,7 @@ public class EvtExperienceChange extends SkriptEvent { "\tset {_xp} to event-experience", "\tbroadcast \"%{_xp}%\"" ) - .since("INSERT VERSION"); + .since("2.7"); EventValues.registerEventValue(PlayerExpChangeEvent.class, Experience.class, new Getter<Experience, PlayerExpChangeEvent>() { @Override @Nullable diff --git a/src/main/java/ch/njol/skript/events/EvtItem.java b/src/main/java/ch/njol/skript/events/EvtItem.java index cc9e7336c0a..30ae63703cc 100644 --- a/src/main/java/ch/njol/skript/events/EvtItem.java +++ b/src/main/java/ch/njol/skript/events/EvtItem.java @@ -72,7 +72,7 @@ public class EvtItem extends SkriptEvent { "on entity drop of an egg:", "\tif event-entity is a chicken:", "\t\tset item of event-dropped item to a diamond") - .since("<i>unknown</i> (before 2.1), INSERT VERSION (entity)"); + .since("<i>unknown</i> (before 2.1), 2.7 (entity)"); if (hasPrepareCraftEvent) { // Must be loaded before CraftItemEvent Skript.registerEvent("Prepare Craft", EvtItem.class, PrepareItemCraftEvent.class, "[player] (preparing|beginning) craft[ing] [[of] %-itemtypes%]") .description("Called just before displaying crafting result to player. Note that setting the result item might or might not work due to Bukkit bugs.") diff --git a/src/main/java/ch/njol/skript/events/EvtPlayerChunkEnter.java b/src/main/java/ch/njol/skript/events/EvtPlayerChunkEnter.java index ada1484deae..8e7cdf9b866 100644 --- a/src/main/java/ch/njol/skript/events/EvtPlayerChunkEnter.java +++ b/src/main/java/ch/njol/skript/events/EvtPlayerChunkEnter.java @@ -36,7 +36,7 @@ public class EvtPlayerChunkEnter extends SkriptEvent { .examples( "on player enters a chunk:", "\tsend \"You entered a chunk: %past event-chunk% -> %event-chunk%!\" to player" - ).since("INSERT VERSION"); + ).since("2.7"); } @Override diff --git a/src/main/java/ch/njol/skript/events/EvtSpectate.java b/src/main/java/ch/njol/skript/events/EvtSpectate.java index 33e5cf4a733..8438abb3644 100644 --- a/src/main/java/ch/njol/skript/events/EvtSpectate.java +++ b/src/main/java/ch/njol/skript/events/EvtSpectate.java @@ -43,7 +43,7 @@ public class EvtSpectate extends SkriptEvent { .description("Called with a player starts, stops or swaps spectating an entity.") .examples("on player start spectating of a zombie:") .requiredPlugins("Paper") - .since("INSERT VERSION"); + .since("2.7"); } private Literal<EntityData<?>> datas; diff --git a/src/main/java/ch/njol/skript/events/SimpleEvents.java b/src/main/java/ch/njol/skript/events/SimpleEvents.java index 1873933b05c..6e1aa2b7004 100644 --- a/src/main/java/ch/njol/skript/events/SimpleEvents.java +++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java @@ -623,7 +623,7 @@ public class SimpleEvents { "\tchance of 5%:", "\t\tset repair cost to repair cost * 50%", "\t\tsend \"You're LUCKY! You got 50% discount.\" to player") - .since("INSERT VERSION"); + .since("2.7"); if (Skript.classExists("io.papermc.paper.event.player.PlayerTradeEvent")) { Skript.registerEvent("Player Trade", SimpleEvent.class, PlayerTradeEvent.class, "player trad(e|ing)") .description("Called when a player has traded with a villager.") @@ -632,7 +632,7 @@ public class SimpleEvents { "\tchance of 50%:", "\t\tcancel event", "\t\tsend \"The trade was somehow denied!\" to player") - .since("INSERT VERSION"); + .since("2.7"); } if (Skript.classExists("com.destroystokyo.paper.event.entity.EntityJumpEvent")) { Skript.registerEvent("Entity Jump", SimpleEvent.class, EntityJumpEvent.class, "entity jump[ing]") @@ -641,7 +641,7 @@ public class SimpleEvents { .examples("on entity jump:", "\tif entity is a wither skeleton:", "\t\tcancel event") - .since("INSERT VERSION"); + .since("2.7"); } if (Skript.classExists("com.destroystokyo.paper.event.block.AnvilDamagedEvent")) { Skript.registerEvent("Anvil Damage", SimpleEvent.class, AnvilDamagedEvent.class, "anvil damag(e|ing)") @@ -650,7 +650,7 @@ public class SimpleEvents { .requiredPlugins("Paper") .examples("on anvil damage:", "\tcancel the event") - .since("INSERT VERSION"); + .since("2.7"); } if (Skript.classExists("io.papermc.paper.event.player.PlayerInventorySlotChangeEvent")) { Skript.registerEvent("Inventory Slot Change", SimpleEvent.class, PlayerInventorySlotChangeEvent.class, "[player] inventory slot chang(e|ing)") @@ -661,7 +661,7 @@ public class SimpleEvents { "\tif event-item is a diamond:", "\t\tsend \"You obtained a diamond!\" to player" ) - .since("INSERT VERSION"); + .since("2.7"); } //noinspection deprecation Skript.registerEvent("Chat", SimpleEvent.class, AsyncPlayerChatEvent.class, "chat") @@ -692,7 +692,7 @@ public class SimpleEvents { "\tadd 64 diamonds", "\tsend \"You hit the jackpot!!\"" ) - .since("INSERT VERSION") + .since("2.7") .requiredPlugins("MC 1.16+"); } if (Skript.classExists("io.papermc.paper.event.player.PlayerDeepSleepEvent")) { @@ -705,7 +705,7 @@ public class SimpleEvents { "on player deep sleeping:", "\tsend \"Zzzz..\" to player" ) - .since("INSERT VERSION") + .since("2.7") .requiredPlugins("Paper 1.16+"); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprAge.java b/src/main/java/ch/njol/skript/expressions/ExprAge.java index 50b77a6c8fb..2a29300beb0 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprAge.java +++ b/src/main/java/ch/njol/skript/expressions/ExprAge.java @@ -52,7 +52,7 @@ "spawn a baby cow at player", "set age of last spawned entity to -1200 # in ticks = 60 seconds" }) -@Since("INSERT VERSION") +@Since("2.7") public class ExprAge extends SimplePropertyExpression<Object, Integer> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprAllBannedEntries.java b/src/main/java/ch/njol/skript/expressions/ExprAllBannedEntries.java index 8e2a4abe84a..48e7e7eced6 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprAllBannedEntries.java +++ b/src/main/java/ch/njol/skript/expressions/ExprAllBannedEntries.java @@ -40,7 +40,7 @@ "\ttrigger:", "\t\tsend all the banned players" }) -@Since("INSERT VERSION") +@Since("2.7") public class ExprAllBannedEntries extends SimpleExpression<Object> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprAnvilText.java b/src/main/java/ch/njol/skript/expressions/ExprAnvilText.java index d2f9f36df45..ed37573183e 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprAnvilText.java +++ b/src/main/java/ch/njol/skript/expressions/ExprAnvilText.java @@ -38,7 +38,7 @@ "\tif the anvil input text of the event-inventory is \"FREE OP\":", "\t\tban player" }) -@Since("INSERT VERSION") +@Since("2.7") public class ExprAnvilText extends SimplePropertyExpression<Inventory, String> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprArgument.java b/src/main/java/ch/njol/skript/expressions/ExprArgument.java index 60d545650f1..622aa229a4f 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprArgument.java +++ b/src/main/java/ch/njol/skript/expressions/ExprArgument.java @@ -60,7 +60,7 @@ "add argument 1 to argument 2", "heal the last argument" }) -@Since("1.0, INSERT VERSION (support for command events)") +@Since("1.0, 2.7 (support for command events)") public class ExprArgument extends SimpleExpression<Object> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprBed.java b/src/main/java/ch/njol/skript/expressions/ExprBed.java index 1f9684514a8..d33ba566854 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprBed.java +++ b/src/main/java/ch/njol/skript/expressions/ExprBed.java @@ -52,7 +52,7 @@ "set the bed location of player to spawn location of world(\"world\") # unsafe/invalid bed location", "set the safe bed location of player to spawn location of world(\"world\") # safe/valid bed location" }) -@Since("2.0, INSERT VERSION (offlineplayers, safe bed)") +@Since("2.0, 2.7 (offlineplayers, safe bed)") public class ExprBed extends SimplePropertyExpression<OfflinePlayer, Location> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprBookPages.java b/src/main/java/ch/njol/skript/expressions/ExprBookPages.java index ec5df08f3f6..917e3909de9 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprBookPages.java +++ b/src/main/java/ch/njol/skript/expressions/ExprBookPages.java @@ -48,7 +48,7 @@ "\tmessage \"Book Page 1: %page 1 of event-item%\"", "set page 1 of player's held item to \"Book writing\"" }) -@Since("2.2-dev31, INSERT VERSION (changers)") +@Since("2.2-dev31, 2.7 (changers)") public class ExprBookPages extends SimpleExpression<String> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprBreakSpeed.java b/src/main/java/ch/njol/skript/expressions/ExprBreakSpeed.java index 2c59e593fef..30ed01d4f2b 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprBreakSpeed.java +++ b/src/main/java/ch/njol/skript/expressions/ExprBreakSpeed.java @@ -48,7 +48,7 @@ "\tevent-block is set", "\tsend \"Break Speed: %break speed for player%\" to player" }) -@Since("INSERT VERSION") +@Since("2.7") @RequiredPlugins("1.17+") public class ExprBreakSpeed extends SimpleExpression<Float> { diff --git a/src/main/java/ch/njol/skript/expressions/ExprCharges.java b/src/main/java/ch/njol/skript/expressions/ExprCharges.java index 09796d77378..25c0ae9a9cc 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprCharges.java +++ b/src/main/java/ch/njol/skript/expressions/ExprCharges.java @@ -43,7 +43,7 @@ @Description("The charges of a respawn anchor.") @Examples({"set the charges of event-block to 3"}) @RequiredPlugins("Minecraft 1.16+") -@Since("INSERT VERSION") +@Since("2.7") public class ExprCharges extends SimplePropertyExpression<Block, Integer> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprCommand.java b/src/main/java/ch/njol/skript/expressions/ExprCommand.java index f7cfd7b3a78..4809e9eaaa7 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprCommand.java +++ b/src/main/java/ch/njol/skript/expressions/ExprCommand.java @@ -47,7 +47,7 @@ "\t\tif the command is not \"exit\":", "\t\t\tmessage \"You're not allowed to use commands during the game\"", "\t\t\tcancel the event"}) -@Since("2.0, INSERT VERSION (support for script commands)") +@Since("2.0, 2.7 (support for script commands)") @Events("command") public class ExprCommand extends SimpleExpression<String> { diff --git a/src/main/java/ch/njol/skript/expressions/ExprEgg.java b/src/main/java/ch/njol/skript/expressions/ExprEgg.java index 20e6ba0907f..180fb1cf5a9 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprEgg.java +++ b/src/main/java/ch/njol/skript/expressions/ExprEgg.java @@ -34,7 +34,7 @@ @Description("The egg thrown in a Player Egg Throw event.") @Examples("spawn an egg at the egg") @Events("Egg Throw") -@Since("INSERT VERSION") +@Since("2.7") public class ExprEgg extends EventValueExpression<Egg> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprElement.java b/src/main/java/ch/njol/skript/expressions/ExprElement.java index 9ba78ee8f16..52323f8e5e3 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprElement.java +++ b/src/main/java/ch/njol/skript/expressions/ExprElement.java @@ -43,7 +43,7 @@ @Description({"The first, last or a random element of a set, e.g. a list variable.", "See also: <a href='#ExprRandom'>random</a>"}) @Examples("give a random element out of {free items::*} to the player") -@Since("2.0, INSERT VERSION (relative to last element)") +@Since("2.0, 2.7 (relative to last element)") public class ExprElement extends SimpleExpression<Object> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprExperience.java b/src/main/java/ch/njol/skript/expressions/ExprExperience.java index febf5526d2e..b654f870068 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprExperience.java +++ b/src/main/java/ch/njol/skript/expressions/ExprExperience.java @@ -50,7 +50,7 @@ "on break of diamond ore:", "\tif tool of player = diamond pickaxe:", "\t\tadd 100 to dropped experience"}) -@Since("2.1, 2.5.3 (block break event), INSERT VERSION (experience change event)") +@Since("2.1, 2.5.3 (block break event), 2.7 (experience change event)") @Events({"experience spawn", "break / mine", "experience change"}) public class ExprExperience extends SimpleExpression<Experience> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprFireTicks.java b/src/main/java/ch/njol/skript/expressions/ExprFireTicks.java index d801320ec36..a6c3d8ea57c 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprFireTicks.java +++ b/src/main/java/ch/njol/skript/expressions/ExprFireTicks.java @@ -33,7 +33,7 @@ @Name("Entity Fire Burn Duration") @Description("How much time an entity will be burning for.") @Examples({"send \"You will stop burning in %fire time of player%\""}) -@Since("INSERT VERSION") +@Since("2.7") public class ExprFireTicks extends SimplePropertyExpression<Entity, Timespan> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprFormatDate.java b/src/main/java/ch/njol/skript/expressions/ExprFormatDate.java index df7f32cf925..5f6e153a488 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprFormatDate.java +++ b/src/main/java/ch/njol/skript/expressions/ExprFormatDate.java @@ -49,7 +49,7 @@ "\t\tsend \"Full date: %now formatted human-readable%\" to sender", "\t\tsend \"Short date: %now formatted as \"yyyy-MM-dd\"%\" to sender" }) -@Since("2.2-dev31, INSERT VERSION (support variables in format)") +@Since("2.2-dev31, 2.7 (support variables in format)") public class ExprFormatDate extends PropertyExpression<Date, String> { private static final SimpleDateFormat DEFAULT_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); diff --git a/src/main/java/ch/njol/skript/expressions/ExprFreezeTicks.java b/src/main/java/ch/njol/skript/expressions/ExprFreezeTicks.java index ec7b8b79126..4ae246eb7c7 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprFreezeTicks.java +++ b/src/main/java/ch/njol/skript/expressions/ExprFreezeTicks.java @@ -37,7 +37,7 @@ "player's freeze time is less than 3 seconds:", "\tsend \"you're about to freeze!\" to the player" }) -@Since("INSERT VERSION") +@Since("2.7") public class ExprFreezeTicks extends SimplePropertyExpression<Entity, Timespan> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprHatchingNumber.java b/src/main/java/ch/njol/skript/expressions/ExprHatchingNumber.java index 709a3377ce8..fa708a3116a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprHatchingNumber.java +++ b/src/main/java/ch/njol/skript/expressions/ExprHatchingNumber.java @@ -45,7 +45,7 @@ "\tset the hatching number to 10" }) @Events("Egg Throw") -@Since("INSERT VERSION") +@Since("2.7") public class ExprHatchingNumber extends SimpleExpression<Byte> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprHatchingType.java b/src/main/java/ch/njol/skript/expressions/ExprHatchingType.java index b6e6edba2b8..7e1f0dd73b6 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprHatchingType.java +++ b/src/main/java/ch/njol/skript/expressions/ExprHatchingType.java @@ -45,7 +45,7 @@ "\tset the hatching entity type to a primed tnt" }) @Events("Egg Throw") -@Since("INSERT VERSION") +@Since("2.7") public class ExprHatchingType extends SimpleExpression<EntityData<?>> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprJoinSplit.java b/src/main/java/ch/njol/skript/expressions/ExprJoinSplit.java index 207a3ab70de..0365ebf177e 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprJoinSplit.java +++ b/src/main/java/ch/njol/skript/expressions/ExprJoinSplit.java @@ -45,7 +45,7 @@ "message \"Online players: %join all players with \"\" | \"\"%\" # %all players% would use the default \"x, y, and z\"", "set {_s::*} to the string argument split at \",\"" }) -@Since("2.1, 2.5.2 (regex support), INSERT VERSION (case sensitivity)") +@Since("2.1, 2.5.2 (regex support), 2.7 (case sensitivity)") public class ExprJoinSplit extends SimpleExpression<String> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprLastSpawnedEntity.java b/src/main/java/ch/njol/skript/expressions/ExprLastSpawnedEntity.java index 1dc3ef9df7e..92bd4ea8e35 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprLastSpawnedEntity.java +++ b/src/main/java/ch/njol/skript/expressions/ExprLastSpawnedEntity.java @@ -60,7 +60,7 @@ "teleport player to last struck lightning", "delete last launched firework" }) -@Since("1.3 (spawned entity), 2.0 (shot entity), 2.2-dev26 (dropped item), INSERT VERSION (struck lightning, firework)") +@Since("1.3 (spawned entity), 2.0 (shot entity), 2.2-dev26 (dropped item), 2.7 (struck lightning, firework)") public class ExprLastSpawnedEntity extends SimpleExpression<Entity> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprLoot.java b/src/main/java/ch/njol/skript/expressions/ExprLoot.java index 9a91c3a85e5..e58c027514b 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprLoot.java +++ b/src/main/java/ch/njol/skript/expressions/ExprLoot.java @@ -47,7 +47,7 @@ "\tadd 64 diamonds", "\tsend \"You hit the jackpot!!\"" }) -@Since("INSERT VERSION") +@Since("2.7") @RequiredPlugins("MC 1.16+") public class ExprLoot extends SimpleExpression<ItemStack> { diff --git a/src/main/java/ch/njol/skript/expressions/ExprMaxFreezeTicks.java b/src/main/java/ch/njol/skript/expressions/ExprMaxFreezeTicks.java index b477fe4df4a..1e69a508e4a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprMaxFreezeTicks.java +++ b/src/main/java/ch/njol/skript/expressions/ExprMaxFreezeTicks.java @@ -37,7 +37,7 @@ "difference between player's freeze time and player's max freeze time is less than 1 second:", "\tsend \"you're about to freeze!\" to the player" }) -@Since("INSERT VERSION") +@Since("2.7") public class ExprMaxFreezeTicks extends SimplePropertyExpression<Entity, Timespan> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprMaxPlayers.java b/src/main/java/ch/njol/skript/expressions/ExprMaxPlayers.java index 36f5cf417fe..d3a63c6e16f 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprMaxPlayers.java +++ b/src/main/java/ch/njol/skript/expressions/ExprMaxPlayers.java @@ -44,7 +44,7 @@ @Examples({"on server list ping:", " set the max players count to (online players count + 1)"}) @RequiredPlugins("Paper 1.16+ (modify max real players)") -@Since("2.3, INSERT VERSION (modify max real players)") +@Since("2.3, 2.7 (modify max real players)") public class ExprMaxPlayers extends SimpleExpression<Integer> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprMoonPhase.java b/src/main/java/ch/njol/skript/expressions/ExprMoonPhase.java index 0dab23d3b1f..3cc9dcbbcc3 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprMoonPhase.java +++ b/src/main/java/ch/njol/skript/expressions/ExprMoonPhase.java @@ -35,7 +35,7 @@ "if moon phase of player's world is full moon:", "\tsend \"Watch for the wolves!\"" }) -@Since("INSERT VERSION") +@Since("2.7") @RequiredPlugins("Paper 1.16+") public class ExprMoonPhase extends SimplePropertyExpression<World, MoonPhase> { diff --git a/src/main/java/ch/njol/skript/expressions/ExprName.java b/src/main/java/ch/njol/skript/expressions/ExprName.java index 056691dc6ff..d4b219b3fda 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprName.java +++ b/src/main/java/ch/njol/skript/expressions/ExprName.java @@ -108,7 +108,7 @@ "\tset the player's tab list name to \"<green>%player's name%\"", "set the name of the player's tool to \"Legendary Sword of Awesomeness\"" }) -@Since("before 2.1, 2.2-dev20 (inventory name), 2.4 (non-living entity support, changeable inventory name), INSERT VERSION (worlds)") +@Since("before 2.1, 2.2-dev20 (inventory name), 2.4 (non-living entity support, changeable inventory name), 2.7 (worlds)") public class ExprName extends SimplePropertyExpression<Object, String> { @Nullable diff --git a/src/main/java/ch/njol/skript/expressions/ExprNearestEntity.java b/src/main/java/ch/njol/skript/expressions/ExprNearestEntity.java index be92bf52536..a2c37a1ab6b 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprNearestEntity.java +++ b/src/main/java/ch/njol/skript/expressions/ExprNearestEntity.java @@ -49,7 +49,7 @@ "on click:", "\tkill nearest pig" }) -@Since("INSERT VERSION") +@Since("2.7") public class ExprNearestEntity extends SimpleExpression<Entity> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprOps.java b/src/main/java/ch/njol/skript/expressions/ExprOps.java index 8fa9ae65eb7..8b281cfbeff 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprOps.java +++ b/src/main/java/ch/njol/skript/expressions/ExprOps.java @@ -42,7 +42,7 @@ @Name("All Operators") @Description("The list of operators on the server.") @Examples("set {_ops::*} to all operators") -@Since("INSERT VERSION") +@Since("2.7") public class ExprOps extends SimpleExpression<OfflinePlayer> { private boolean nonOps; diff --git a/src/main/java/ch/njol/skript/expressions/ExprPickupDelay.java b/src/main/java/ch/njol/skript/expressions/ExprPickupDelay.java index 7fffb26312b..674406006be 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprPickupDelay.java +++ b/src/main/java/ch/njol/skript/expressions/ExprPickupDelay.java @@ -37,7 +37,7 @@ "drop diamond sword at {_location} without velocity", "set pickup delay of last dropped item to 5 seconds" }) -@Since("INSERT VERSION") +@Since("2.7") public class ExprPickupDelay extends SimplePropertyExpression<Entity, Timespan> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprPlugins.java b/src/main/java/ch/njol/skript/expressions/ExprPlugins.java index e2d80244332..01a1e730f0b 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprPlugins.java +++ b/src/main/java/ch/njol/skript/expressions/ExprPlugins.java @@ -43,7 +43,7 @@ "", "send \"Plugins (%size of loaded plugins%): %plugins%\" to player" }) -@Since("INSERT VERSION") +@Since("2.7") public class ExprPlugins extends SimpleExpression<String> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprPotionEffectTier.java b/src/main/java/ch/njol/skript/expressions/ExprPotionEffectTier.java index 2fdee7bd42b..d47061d4756 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprPotionEffectTier.java +++ b/src/main/java/ch/njol/skript/expressions/ExprPotionEffectTier.java @@ -40,7 +40,7 @@ @Name("Potion Effect Tier") @Description("An expression to obtain the amplifier of a potion effect applied to an entity.") @Examples("if the amplifier of haste of player >= 3:") -@Since("INSERT VERSION") +@Since("2.7") public class ExprPotionEffectTier extends SimpleExpression<Integer> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprRawString.java b/src/main/java/ch/njol/skript/expressions/ExprRawString.java index 87eca7adf88..d3ea952210f 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprRawString.java +++ b/src/main/java/ch/njol/skript/expressions/ExprRawString.java @@ -43,7 +43,7 @@ @Description("Returns the string without formatting (colors etc.) and without stripping them from it, " + "e.g. <code>raw \"&aHello There!\"</code> would output <code>&aHello There!</code>") @Examples("send raw \"&aThis text is unformatted!\" to all players") -@Since("INSERT VERSION") +@Since("2.7") public class ExprRawString extends SimpleExpression<String> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprSeaPickles.java b/src/main/java/ch/njol/skript/expressions/ExprSeaPickles.java index df5b3f1ff1a..0b0691b8881 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprSeaPickles.java +++ b/src/main/java/ch/njol/skript/expressions/ExprSeaPickles.java @@ -47,7 +47,7 @@ "\tset event-block's sea pickle count to event-block's maximum sea pickle count", "\tsend \"This bad boy is going to hold so many pickles now!!\"" }) -@Since("INSERT VERSION") +@Since("2.7") public class ExprSeaPickles extends SimplePropertyExpression<Block, Integer> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprSets.java b/src/main/java/ch/njol/skript/expressions/ExprSets.java index 1652354d1e4..f5e3a48c538 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprSets.java +++ b/src/main/java/ch/njol/skript/expressions/ExprSets.java @@ -46,7 +46,7 @@ "\tset loop-value attribute of player to 10", "\tmessage \"Set attribute %loop-value% to 10!\"" }) -@Since("INSERT VERSION") +@Since("<i>unknown</i> (before 1.4.2), 2.7 (colors)") public class ExprSets extends SimpleExpression<Object> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprSourceBlock.java b/src/main/java/ch/njol/skript/expressions/ExprSourceBlock.java index e120943c538..d74b26efb76 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprSourceBlock.java +++ b/src/main/java/ch/njol/skript/expressions/ExprSourceBlock.java @@ -43,7 +43,7 @@ "\tif the source block is a grass block:", "\t\tset the source block to a dirt block" }) -@Since("INSERT VERSION") +@Since("2.7") public class ExprSourceBlock extends SimpleExpression<Block> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprSpectatorTarget.java b/src/main/java/ch/njol/skript/expressions/ExprSpectatorTarget.java index 5dd6ca5f71d..f2ab04468cf 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprSpectatorTarget.java +++ b/src/main/java/ch/njol/skript/expressions/ExprSpectatorTarget.java @@ -62,7 +62,7 @@ "\tset spectator target to the nearest skeleton" }) @RequiredPlugins("Paper") -@Since("2.4-alpha4, INSERT VERSION (Paper Spectator Event)") +@Since("2.4-alpha4, 2.7 (Paper Spectator Event)") public class ExprSpectatorTarget extends SimpleExpression<Entity> { private static final boolean EVENT_SUPPORT = Skript.classExists("com.destroystokyo.paper.event.player.PlayerStartSpectatingEntityEvent"); diff --git a/src/main/java/ch/njol/skript/expressions/ExprTimePlayed.java b/src/main/java/ch/njol/skript/expressions/ExprTimePlayed.java index 7e6d2cf1831..708b24ede15 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTimePlayed.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTimePlayed.java @@ -47,7 +47,7 @@ "set player's time played to 0 seconds" }) @RequiredPlugins("MC 1.15+ (offline players)") -@Since("2.5, INSERT VERSION (offline players)") +@Since("2.5, 2.7 (offline players)") public class ExprTimePlayed extends SimplePropertyExpression<OfflinePlayer, Timespan> { private static final boolean IS_OFFLINE_SUPPORTED = Skript.methodExists(OfflinePlayer.class, "getStatistic", Statistic.class); diff --git a/src/main/java/ch/njol/skript/expressions/ExprTypeOf.java b/src/main/java/ch/njol/skript/expressions/ExprTypeOf.java index ed62d174d63..8552af36cb4 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTypeOf.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTypeOf.java @@ -43,7 +43,7 @@ }) @Examples({"on rightclick on an entity:", "\tmessage \"This is a %type of clicked entity%!\""}) -@Since("1.4, 2.5.2 (potion effect), INSERT VERSION (block datas)") +@Since("1.4, 2.5.2 (potion effect), 2.7 (block datas)") public class ExprTypeOf extends SimplePropertyExpression<Object, Object> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprValueWithin.java b/src/main/java/ch/njol/skript/expressions/ExprValueWithin.java index df5d88d6571..d6c1e73b05a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprValueWithin.java +++ b/src/main/java/ch/njol/skript/expressions/ExprValueWithin.java @@ -50,7 +50,7 @@ "set {_list::*} to \"something\", 10, \"test\" and a zombie", "broadcast the strings within {_list::*} # \"something\", \"test\"" }) -@Since("INSERT VERSION") +@Since("2.7") public class ExprValueWithin extends WrapperExpression<Object> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorRandom.java b/src/main/java/ch/njol/skript/expressions/ExprVectorRandom.java index 6834b1d4992..d5315ca05e2 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorRandom.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorRandom.java @@ -42,7 +42,7 @@ @Name("Vectors - Random Vector") @Description("Creates a random vector.") @Examples({"set {_v} to a random vector"}) -@Since("2.2-dev28, INSERT VERSION (signed components)") +@Since("2.2-dev28, 2.7 (signed components)") public class ExprVectorRandom extends SimpleExpression<Vector> { private static final Random random = new Random(); diff --git a/src/main/java/ch/njol/skript/expressions/ExprWorldEnvironment.java b/src/main/java/ch/njol/skript/expressions/ExprWorldEnvironment.java index 4f57005de57..dd7505c8556 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprWorldEnvironment.java +++ b/src/main/java/ch/njol/skript/expressions/ExprWorldEnvironment.java @@ -33,7 +33,7 @@ "if environment of player's world is nether:", "\tapply fire resistance to player for 10 minutes" }) -@Since("INSERT VERSION") +@Since("2.7") public class ExprWorldEnvironment extends SimplePropertyExpression<World, Environment> { static { diff --git a/src/main/java/ch/njol/skript/expressions/LitPi.java b/src/main/java/ch/njol/skript/expressions/LitPi.java index 2fe393c3fb4..b1c6fe4c976 100644 --- a/src/main/java/ch/njol/skript/expressions/LitPi.java +++ b/src/main/java/ch/njol/skript/expressions/LitPi.java @@ -35,7 +35,7 @@ @Name("Pi") @Description("Returns the mathematical constant pi. (approx. 3.1415926535)") @Examples("set {_tau} to pi * 2") -@Since("INSERT VERSION") +@Since("2.7") public class LitPi extends SimpleLiteral<Double> { static { diff --git a/src/main/java/ch/njol/skript/structures/StructFunction.java b/src/main/java/ch/njol/skript/structures/StructFunction.java index eab8583aed8..608e0dcd5f3 100644 --- a/src/main/java/ch/njol/skript/structures/StructFunction.java +++ b/src/main/java/ch/njol/skript/structures/StructFunction.java @@ -49,7 +49,7 @@ "local function giveApple(amount: number) :: item:", "\treturn {_amount} of apple" }) -@Since("2.2, INSERT VERSION (local functions)") +@Since("2.2, 2.7 (local functions)") public class StructFunction extends Structure { public static final Priority PRIORITY = new Priority(400); diff --git a/src/main/java/ch/njol/skript/test/runner/CondMethodExists.java b/src/main/java/ch/njol/skript/test/runner/CondMethodExists.java index 17d821ad6aa..0735ea997ab 100644 --- a/src/main/java/ch/njol/skript/test/runner/CondMethodExists.java +++ b/src/main/java/ch/njol/skript/test/runner/CondMethodExists.java @@ -41,7 +41,7 @@ @Name("Method Exists") @Description("Checks if a method exists") @Examples("if method \"org.bukkit.Bukkit#getPluginCommand(java.lang.String)") -@Since("INSERT VERSION") +@Since("2.7") public class CondMethodExists extends PropertyCondition<String> { private final static Pattern SIGNATURE_PATTERN = Pattern.compile("(?<class>.+)#(?<name>.+)\\((?<params>.*)\\)"); From cc8197cffe58c876bb8f23c5568971f34118ae5e Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Fri, 10 Mar 2023 20:16:46 -0700 Subject: [PATCH 266/619] Update aliases (#5505) --- skript-aliases | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skript-aliases b/skript-aliases index e99be898254..fb9c3044e55 160000 --- a/skript-aliases +++ b/skript-aliases @@ -1 +1 @@ -Subproject commit e99be898254965b0207992f66a3289ab6744106e +Subproject commit fb9c3044e555667b4dc5558467608bd55fa32df0 From 159bf1b822da5e42b13d49bc2dc15fa026069cab Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Sat, 11 Mar 2023 08:50:06 -0500 Subject: [PATCH 267/619] Fix ExprAttacker not working in damage events (#5508) --- src/main/java/ch/njol/skript/expressions/ExprAttacker.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprAttacker.java b/src/main/java/ch/njol/skript/expressions/ExprAttacker.java index ac06cdb16ec..31cd3a765a9 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprAttacker.java +++ b/src/main/java/ch/njol/skript/expressions/ExprAttacker.java @@ -22,6 +22,7 @@ import org.bukkit.entity.Projectile; import org.bukkit.event.Event; import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.entity.EntityDeathEvent; import org.bukkit.event.vehicle.VehicleDamageEvent; import org.bukkit.event.vehicle.VehicleDestroyEvent; @@ -61,7 +62,7 @@ public class ExprAttacker extends SimpleExpression<Entity> { @Override public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) { - if (!getParser().isCurrentEvent(EntityDamageByEntityEvent.class, EntityDeathEvent.class, VehicleDamageEvent.class, VehicleDestroyEvent.class)) { + if (!getParser().isCurrentEvent(EntityDamageEvent.class, EntityDeathEvent.class, VehicleDamageEvent.class, VehicleDestroyEvent.class)) { Skript.error("Cannot use 'attacker' outside of a damage/death/destroy event", ErrorQuality.SEMANTIC_ERROR); return false; } From 8b8551cd2b06a8369f28b02b8ddb830eb8970d2d Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Sun, 12 Mar 2023 15:38:47 -0400 Subject: [PATCH 268/619] Fix Converter compatibility for Property Expressions (#5507) --- .../ch/njol/skript/expressions/ExprChunk.java | 17 +++------ .../njol/skript/expressions/ExprColoured.java | 13 ++----- .../njol/skript/expressions/ExprGameMode.java | 27 +++++-------- .../skript/expressions/ExprLightLevel.java | 20 ++++------ .../njol/skript/expressions/ExprShooter.java | 26 +++++-------- .../njol/skript/expressions/ExprTarget.java | 38 ++++++++----------- .../njol/skript/expressions/ExprVehicle.java | 30 +++++++-------- .../ch/njol/skript/expressions/ExprWorld.java | 28 ++++++-------- .../expressions/base/PropertyExpression.java | 5 ++- .../base/SimplePropertyExpression.java | 3 +- src/main/java/ch/njol/skript/util/Getter.java | 4 +- 11 files changed, 84 insertions(+), 127 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprChunk.java b/src/main/java/ch/njol/skript/expressions/ExprChunk.java index 666d5d5f9c7..a1d384edf10 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprChunk.java +++ b/src/main/java/ch/njol/skript/expressions/ExprChunk.java @@ -18,14 +18,8 @@ */ package ch.njol.skript.expressions; -import org.bukkit.Chunk; -import org.bukkit.Location; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer.ChangeMode; -import org.skriptlang.skript.lang.converter.Converter; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; @@ -36,6 +30,10 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.util.Direction; import ch.njol.util.Kleenean; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; /** * @author Peter Güttinger @@ -69,12 +67,7 @@ public boolean init(final Expression<?>[] exprs, final int matchedPattern, final @Override protected Chunk[] get(final Event e, final Location[] source) { - return get(source, new Converter<Location, Chunk>() { - @Override - public Chunk convert(final Location l) { - return l.getChunk(); - } - }); + return get(source, Location::getChunk); } @Override diff --git a/src/main/java/ch/njol/skript/expressions/ExprColoured.java b/src/main/java/ch/njol/skript/expressions/ExprColoured.java index 5736ec1c06d..efff23ec78a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprColoured.java +++ b/src/main/java/ch/njol/skript/expressions/ExprColoured.java @@ -18,11 +18,7 @@ */ package ch.njol.skript.expressions; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; -import org.skriptlang.skript.lang.converter.Converter; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; @@ -34,6 +30,8 @@ import ch.njol.skript.util.Utils; import ch.njol.skript.util.chat.ChatMessages; import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; @Name("Colored / Uncolored") @Description({"Parses <color>s and, optionally, chat styles in a message or removes", @@ -78,12 +76,7 @@ public boolean init(final Expression<?>[] exprs, final int matchedPattern, final @Override protected String[] get(final Event e, final String[] source) { - return get(source, new Converter<String, String>() { - @Override - public String convert(final String s) { - return color ? Utils.replaceChatStyles(s) : "" + ChatMessages.stripStyles(s); - } - }); + return get(source, s -> color ? Utils.replaceChatStyles(s) : "" + ChatMessages.stripStyles(s)); } @Override diff --git a/src/main/java/ch/njol/skript/expressions/ExprGameMode.java b/src/main/java/ch/njol/skript/expressions/ExprGameMode.java index 8e9dc4e9b17..9ca91460f71 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprGameMode.java +++ b/src/main/java/ch/njol/skript/expressions/ExprGameMode.java @@ -18,17 +18,8 @@ */ package ch.njol.skript.expressions; -import org.bukkit.Bukkit; -import org.bukkit.GameMode; -import org.bukkit.entity.Player; -import org.bukkit.event.Event; -import org.bukkit.event.player.PlayerGameModeChangeEvent; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.ScriptLoader; import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer.ChangeMode; -import org.skriptlang.skript.lang.converter.Converter; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; @@ -40,6 +31,12 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.player.PlayerGameModeChangeEvent; +import org.eclipse.jdt.annotation.Nullable; /** * @author Peter Güttinger @@ -64,14 +61,10 @@ public boolean init(final Expression<?>[] vars, final int matchedPattern, final @Override protected GameMode[] get(final Event e, final Player[] source) { - return get(source, new Converter<Player, GameMode>() { - @Override - @Nullable - public GameMode convert(final Player p) { - if (getTime() >= 0 && e instanceof PlayerGameModeChangeEvent && ((PlayerGameModeChangeEvent) e).getPlayer() == p && !Delay.isDelayed(e)) - return ((PlayerGameModeChangeEvent) e).getNewGameMode(); - return p.getGameMode(); - } + return get(source, player -> { + if (getTime() >= 0 && e instanceof PlayerGameModeChangeEvent && ((PlayerGameModeChangeEvent) e).getPlayer() == player && !Delay.isDelayed(e)) + return ((PlayerGameModeChangeEvent) e).getNewGameMode(); + return player.getGameMode(); }); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprLightLevel.java b/src/main/java/ch/njol/skript/expressions/ExprLightLevel.java index d8580bdb130..0e744752fcc 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprLightLevel.java +++ b/src/main/java/ch/njol/skript/expressions/ExprLightLevel.java @@ -18,13 +18,7 @@ */ package ch.njol.skript.expressions; -import org.bukkit.Location; -import org.bukkit.block.Block; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; -import org.skriptlang.skript.lang.converter.Converter; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; @@ -35,6 +29,10 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.util.Direction; import ch.njol.util.Kleenean; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; /** * @author Peter Güttinger @@ -72,12 +70,10 @@ public Class<Byte> getReturnType() { @Override protected Byte[] get(final Event e, final Location[] source) { - return get(source, new Converter<Location, Byte>() { - @Override - public Byte convert(final Location l) { - final Block b = l.getBlock(); - return whatLight == ANY ? b.getLightLevel() : whatLight == BLOCK ? b.getLightFromBlocks() : b.getLightFromSky(); - } + return get(source, location -> { + Block block = location.getBlock(); + return whatLight == ANY ? block.getLightLevel() + : whatLight == BLOCK ? block.getLightFromBlocks() : block.getLightFromSky(); }); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprShooter.java b/src/main/java/ch/njol/skript/expressions/ExprShooter.java index b7589c9be2a..f38127beb66 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprShooter.java +++ b/src/main/java/ch/njol/skript/expressions/ExprShooter.java @@ -18,15 +18,8 @@ */ package ch.njol.skript.expressions; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Projectile; -import org.bukkit.projectiles.ProjectileSource; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer.ChangeMode; -import org.skriptlang.skript.lang.converter.Converter; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; @@ -36,6 +29,11 @@ import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Projectile; +import org.bukkit.event.Event; +import org.bukkit.projectiles.ProjectileSource; +import org.eclipse.jdt.annotation.Nullable; /** * @author Peter Güttinger @@ -58,15 +56,11 @@ public boolean init(final Expression<?>[] exprs, final int matchedPattern, final @Override protected LivingEntity[] get(final Event e, final Projectile[] source) { - return get(source, new Converter<Projectile, LivingEntity>() { - @Override - @Nullable - public LivingEntity convert(final Projectile p) { - final Object o = p != null ? p.getShooter() : null; - if (o instanceof LivingEntity) - return (LivingEntity) o; - return null; - } + return get(source, projectile -> { + Object shooter = projectile != null ? projectile.getShooter() : null; + if (shooter instanceof LivingEntity) + return (LivingEntity) shooter; + return null; }); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprTarget.java b/src/main/java/ch/njol/skript/expressions/ExprTarget.java index 0bdef493a37..c72c9510597 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTarget.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTarget.java @@ -18,19 +18,8 @@ */ package ch.njol.skript.expressions; -import java.util.List; - -import org.bukkit.entity.Entity; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Mob; -import org.bukkit.event.Event; -import org.bukkit.event.entity.EntityTargetEvent; -import org.bukkit.util.Vector; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer.ChangeMode; -import org.skriptlang.skript.lang.converter.Converter; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; @@ -44,6 +33,15 @@ import ch.njol.skript.registrations.Classes; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Mob; +import org.bukkit.event.Event; +import org.bukkit.event.entity.EntityTargetEvent; +import org.bukkit.util.Vector; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.List; /** * @author Peter Güttinger @@ -75,18 +73,14 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override protected Entity[] get(Event e, LivingEntity[] source) { - return get(source, new Converter<LivingEntity, Entity>() { - @Override - @Nullable - public Entity convert(LivingEntity en) { - if (getTime() >= 0 && e instanceof EntityTargetEvent && en.equals(((EntityTargetEvent) e).getEntity()) && !Delay.isDelayed(e)) { - Entity target = ((EntityTargetEvent) e).getTarget(); - if (target == null || type != null && !type.isInstance(target)) - return null; - return target; - } - return getTarget(en, type); + return get(source, en -> { + if (getTime() >= 0 && e instanceof EntityTargetEvent && en.equals(((EntityTargetEvent) e).getEntity()) && !Delay.isDelayed(e)) { + Entity target = ((EntityTargetEvent) e).getTarget(); + if (target == null || type != null && !type.isInstance(target)) + return null; + return target; } + return getTarget(en, type); }); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVehicle.java b/src/main/java/ch/njol/skript/expressions/ExprVehicle.java index d13e4a428d3..2a6147f3751 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVehicle.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVehicle.java @@ -56,26 +56,22 @@ public class ExprVehicle extends SimplePropertyExpression<Entity, Entity> { @Override protected Entity[] get(final Event e, final Entity[] source) { - return get(source, new Converter<Entity, Entity>() { - @Override - @Nullable - public Entity convert(final Entity p) { - if (getTime() >= 0 && e instanceof VehicleEnterEvent && p.equals(((VehicleEnterEvent) e).getEntered()) && !Delay.isDelayed(e)) { - return ((VehicleEnterEvent) e).getVehicle(); - } - if (getTime() >= 0 && e instanceof VehicleExitEvent && p.equals(((VehicleExitEvent) e).getExited()) && !Delay.isDelayed(e)) { - return ((VehicleExitEvent) e).getVehicle(); + return get(source, entity -> { + if (getTime() >= 0 && e instanceof VehicleEnterEvent && entity.equals(((VehicleEnterEvent) e).getEntered()) && !Delay.isDelayed(e)) { + return ((VehicleEnterEvent) e).getVehicle(); + } + if (getTime() >= 0 && e instanceof VehicleExitEvent && entity.equals(((VehicleExitEvent) e).getExited()) && !Delay.isDelayed(e)) { + return ((VehicleExitEvent) e).getVehicle(); + } + if (hasMountEvents) { + if (getTime() >= 0 && e instanceof EntityMountEvent && entity.equals(((EntityMountEvent) e).getEntity()) && !Delay.isDelayed(e)) { + return ((EntityMountEvent) e).getMount(); } - if (hasMountEvents) { - if (getTime() >= 0 && e instanceof EntityMountEvent && p.equals(((EntityMountEvent) e).getEntity()) && !Delay.isDelayed(e)) { - return ((EntityMountEvent) e).getMount(); - } - if (getTime() >= 0 && e instanceof EntityDismountEvent && p.equals(((EntityDismountEvent) e).getEntity()) && !Delay.isDelayed(e)) { - return ((EntityDismountEvent) e).getDismounted(); - } + if (getTime() >= 0 && e instanceof EntityDismountEvent && entity.equals(((EntityDismountEvent) e).getEntity()) && !Delay.isDelayed(e)) { + return ((EntityDismountEvent) e).getDismounted(); } - return p.getVehicle(); } + return entity.getVehicle(); }); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprWorld.java b/src/main/java/ch/njol/skript/expressions/ExprWorld.java index 127109e0b4a..a27a021f683 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprWorld.java +++ b/src/main/java/ch/njol/skript/expressions/ExprWorld.java @@ -74,23 +74,19 @@ public boolean init(final Expression<?>[] exprs, final int matchedPattern, final protected World[] get(final Event e, final Object[] source) { if (source instanceof World[]) // event value (see init) return (World[]) source; - return get(source, new Converter<Object, World>() { - @Override - @Nullable - public World convert(final Object o) { - if (o instanceof Entity) { - if (getTime() > 0 && e instanceof PlayerTeleportEvent && o.equals(((PlayerTeleportEvent) e).getPlayer()) && !Delay.isDelayed(e)) - return ((PlayerTeleportEvent) e).getTo().getWorld(); - else - return ((Entity) o).getWorld(); - } else if (o instanceof Location) { - return ((Location) o).getWorld(); - } else if (o instanceof Chunk) { - return ((Chunk) o).getWorld(); - } - assert false : o; - return null; + return get(source, obj -> { + if (obj instanceof Entity) { + if (getTime() > 0 && e instanceof PlayerTeleportEvent && obj.equals(((PlayerTeleportEvent) e).getPlayer()) && !Delay.isDelayed(e)) + return ((PlayerTeleportEvent) e).getTo().getWorld(); + else + return ((Entity) obj).getWorld(); + } else if (obj instanceof Location) { + return ((Location) obj).getWorld(); + } else if (obj instanceof Chunk) { + return ((Chunk) obj).getWorld(); } + assert false : obj; + return null; }); } diff --git a/src/main/java/ch/njol/skript/expressions/base/PropertyExpression.java b/src/main/java/ch/njol/skript/expressions/base/PropertyExpression.java index d6c40b28e38..1a41665c3d7 100644 --- a/src/main/java/ch/njol/skript/expressions/base/PropertyExpression.java +++ b/src/main/java/ch/njol/skript/expressions/base/PropertyExpression.java @@ -112,10 +112,11 @@ public final T[] getAll(Event event) { * @return An array containing the converted values * @throws ArrayStoreException if the converter returned invalid values */ - protected T[] get(final F[] source, final Converter<? super F, ? extends T> converter) { + @SuppressWarnings("deprecation") // for backwards compatibility + protected T[] get(final F[] source, final ch.njol.skript.classes.Converter<? super F, ? extends T> converter) { assert source != null; assert converter != null; - return Converters.convertUnsafe(source, getReturnType(), converter); + return ch.njol.skript.registrations.Converters.convertUnsafe(source, getReturnType(), converter); } @Override diff --git a/src/main/java/ch/njol/skript/expressions/base/SimplePropertyExpression.java b/src/main/java/ch/njol/skript/expressions/base/SimplePropertyExpression.java index 7b23e10d8d5..2e7b581b5d2 100644 --- a/src/main/java/ch/njol/skript/expressions/base/SimplePropertyExpression.java +++ b/src/main/java/ch/njol/skript/expressions/base/SimplePropertyExpression.java @@ -18,10 +18,10 @@ */ package ch.njol.skript.expressions.base; +import ch.njol.skript.classes.Converter; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; -import org.skriptlang.skript.lang.converter.Converter; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; @@ -33,6 +33,7 @@ * @see PropertyExpression * @see PropertyExpression#register(Class, Class, String, String) */ +@SuppressWarnings("deprecation") // for backwards compatibility public abstract class SimplePropertyExpression<F, T> extends PropertyExpression<F, T> implements Converter<F, T> { @SuppressWarnings({"unchecked", "null"}) diff --git a/src/main/java/ch/njol/skript/util/Getter.java b/src/main/java/ch/njol/skript/util/Getter.java index 44de2746e70..d2fa64e21a7 100644 --- a/src/main/java/ch/njol/skript/util/Getter.java +++ b/src/main/java/ch/njol/skript/util/Getter.java @@ -18,10 +18,9 @@ */ package ch.njol.skript.util; +import ch.njol.skript.classes.Converter; import org.eclipse.jdt.annotation.Nullable; -import org.skriptlang.skript.lang.converter.Converter; - /** * Used to get a specific value from instances of some type. * @@ -29,6 +28,7 @@ * @param <A> the type which holds the value * @author Peter Güttinger */ +@SuppressWarnings("deprecation") // for backwards compatibility public abstract class Getter<R, A> implements Converter<A, R> { /** From 88bf614d89ececf8abfc5e797498b8d3441fd25d Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Sun, 12 Mar 2023 13:43:16 -0600 Subject: [PATCH 269/619] Disable JUnit in build (#5501) --- build.gradle | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/build.gradle b/build.gradle index 6b1b3472ca4..dc18aaee871 100644 --- a/build.gradle +++ b/build.gradle @@ -72,6 +72,12 @@ task build(overwrite: true, type: ShadowJar) { from sourceSets.main.output } +// Excludes the tests for the build task. Should be using junit, junitJava17, junitJava8, skriptTest, quickTest. +// We do not want tests to run for building. That's time consuming and annoying. Especially in development. +test { + exclude '**/*' +} + task relocateShadowJar(type: ConfigureShadowRelocation) { target = tasks.shadowJar } From b93cdd3cb1732ffd629b80e1c9649e81b9001af5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Mar 2023 23:05:53 -0600 Subject: [PATCH 270/619] Update ShadowJar to 8.1.0 and Gradle to 8.0.1 (#5477) --- build.gradle | 20 +++++++------------- gradle/wrapper/gradle-wrapper.jar | Bin 59821 -> 61608 bytes gradle/wrapper/gradle-wrapper.properties | 3 ++- gradlew | 18 ++++++++++++++---- gradlew.bat | 15 +++++++++------ 5 files changed, 32 insertions(+), 24 deletions(-) diff --git a/build.gradle b/build.gradle index dc18aaee871..6a0543906ce 100644 --- a/build.gradle +++ b/build.gradle @@ -1,11 +1,10 @@ -import com.github.jengelman.gradle.plugins.shadow.tasks.ConfigureShadowRelocation import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import org.apache.tools.ant.filters.ReplaceTokens import java.time.LocalTime plugins { - id 'com.github.johnrengelman.shadow' version '7.1.2' + id 'com.github.johnrengelman.shadow' version '8.1.0' id 'com.github.hierynomus.license' version '0.16.1' id 'maven-publish' id 'java' @@ -57,18 +56,18 @@ task checkAliases { task testJar(type: ShadowJar) { dependsOn(compileTestJava, licenseTest) - archiveName 'Skript-JUnit.jar' + archiveFileName = 'Skript-JUnit.jar' from sourceSets.test.output, sourceSets.main.output, project.configurations.testShadow } task jar(overwrite: true, type: ShadowJar) { dependsOn checkAliases - archiveName jarName ? 'Skript.jar' : jarName + archiveFileName = jarName ? 'Skript.jar' : jarName from sourceSets.main.output } task build(overwrite: true, type: ShadowJar) { - archiveName jarName ? 'Skript.jar' : jarName + archiveFileName = jarName ? 'Skript.jar' : jarName from sourceSets.main.output } @@ -78,17 +77,12 @@ test { exclude '**/*' } -task relocateShadowJar(type: ConfigureShadowRelocation) { - target = tasks.shadowJar -} - task sourceJar(type: Jar) { from sourceSets.main.allJava archiveClassifier = 'sources' } tasks.withType(ShadowJar) { - dependsOn relocateShadowJar configurations = [ project.configurations.shadow ] @@ -278,7 +272,7 @@ task githubResources(type: ProcessResources) { task githubRelease(type: ShadowJar) { from sourceSets.main.output dependsOn githubResources - archiveName = 'Skript-github.jar' + archiveFileName = 'Skript-github.jar' manifest { attributes( 'Name': 'ch/njol/skript', @@ -313,7 +307,7 @@ task spigotResources(type: ProcessResources) { task spigotRelease(type: ShadowJar) { from sourceSets.main.output dependsOn spigotResources - archiveName = 'Skript-spigot.jar' + archiveFileName = 'Skript-spigot.jar' manifest { attributes( 'Name': 'ch/njol/skript', @@ -344,7 +338,7 @@ task nightlyResources(type: ProcessResources) { task nightlyRelease(type: ShadowJar) { from sourceSets.main.output dependsOn nightlyResources, licenseMain - archiveName = 'Skript-nightly.jar' + archiveFileName = 'Skript-nightly.jar' manifest { attributes( 'Name': 'ch/njol/skript', diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 41d9927a4d4fb3f96a785543079b8df6723c946b..ccebba7710deaf9f98673a68957ea02138b60d0a 100644 GIT binary patch delta 39692 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 GdG|kGW5mAz delta 37934 zcmY(KV{m3c+pRNkGO=yjwx8IxZEMGwcw*bOGchK1GQq^QCYs<(eeYY}Ip<$@?XKz{ zy{qr*UTby5E=27j1eCHI1SEX&7X-{iJoGQ{iA*kE{%3{^2?hq{<Z8tX0rsC2q+7U& zzfu45F&Xj;Au>D|7%c3+MWSG-l#6AZ$@oq(z=Nbuiii=&#%_S?8<a+(2or;<NANHC z3)0L9T<LwdHw=6(lExi~XPV(gbU0W8cUCuIx0&V6r<qx!Z2-(W`Ulv3NAtJtFIT(0 z2@1r>{v1$Pc{atr-GX(i1DJLQk1NqSaP5k>Vj~4DX3d$+%21D^re;c)Ed&&N9Sgn& z12?Ix6GT=`G#Eo;E{=aywkJ5$=BeqW$t{YpeXiigcGPVbJr?*Z+VkCMM5v<hc!O&- z{Yix<WRB<Maw}&jCQ44F4Xj#Ez1FX3*q1epPmO3&ad>OgKcvxMyIy`#RY}0x>r)+6 zm^YrIC!70-u2i~)3jDcc878+6ziFgt0EBE#S(43?5mE#N)&ZrmKaGT@Fx}vn^xRC! zpT!Rj*J6}8{p;HbsfhDvj168RUG}@7Ts8uj&$?{QyzDEF7bj=U<mfk%3yol2o$Fop zh}MJqz~0?+=aDxLvDE5`WJ`!=;3xiY`Wf%e4`oVZ=UgwIFLO-us_bWKcX0EqQcatp zW>@sK`Bo<?l1_>wYQCqx#fp8O8e>zdhHXdmhxlqEH-7aa`hHO{92s!>pM(A1Q?&WQ zP)!U421Ws&ELx76EaE}{Y^b4Y;d~Ohtu-)wW2K7<X%qv2p)<E?HfYAt%t0}JacEg* z8YiuB^gOjJp0mBK{UPua-2MUgNcoGcburzu3`pL8;O_>3OndsL)|$8fJQ;9*3cbIx z1{FVpq`=VG42PiLu*EY{SSZc#t|x9pQ(4GOG^G&I&Gkq9*p9vfm}5P$C|KtD0~#(p z#A1rdb>YqigD_~SY-AmzW(3@n{=cFO{V7DhnyCDmh$$vPVln}Z27V!lC<?QHwwU1P z%w=tH%}mD)DR)=%Qe=Nd8>dJ3h}GCZ*wFZW(44wJ)_&Y78A@ZaJYhGAIlVIC7kA>7 z=Fya>&e)y7w5~e<?3{MDwKWZ1KJZSuP9Xi5vhK?C;MmxG<Pk4ms6&8=IzMB)j8Eg@ zXwI17YjeaJ6O48&Wj=u>_PVw9Occ1VC!ZqmW1_A_GdXqCv^=@#Sj<v5xlcR2(HKM2 zYi$9iHeFg|=}b1vP{Y?2>y)T4KpTpO-ev-IV7=j9G3Y@75!#_a)9DC#UJFDC6kgoa zjt-@HJu=m6YD49q-Wb=^KEdMJ4pTIDSFXmaWBPc<ObfX73O>0m9p%>OOrxVVd?TQU z(xY}<IhN4{-sbNgE2xK&`!0n!M!z_H%i8GN;}B4kt=41;OMx+1D7@l6;l!XOj~nLV zyxbe`3jrO#tv1EjD?1!UoQNFSpJ7Wda;L|rH;Ylq&y6{^%+;S8FYnbx#o7}OJuJi! zpG`}ai)n|g4cmZ6NP$GVYd+?sECs{og=eUddDad_oTw}=2;qmniZ%nQMq9z~4^hJ0 z9156VYW12pFH<M9n}DE2E81cASFEMe4N=`t)qnzwcI2RpcJ0ned>7awG>(dT;d=*+ z3H597IAYWhz8vU5ZSMw$T;B$jU0_Dq?|cg(z9Ao)qo-qe5UK}k?%K2w4=-$TrF1Fe z(hL@?o0O>Xa+gPM|D0($%^yW=ElX+D1P7ThPAMdAOEkGymyc$j^Jyfj=O?O%-=<A0 z@sk6p2a2Ax7dNW+zA_Yg)b4hI-zO}lkElknYn+<8%8#w1c<}DH`t04}&^A-+e+??X z3oab26vN+GT$!&kG-~fbUGX7wt+xF#?p|tJvnOluy`AkTNy$^ZlRRgx$o8t)=qY8i z6}(Amt4OS<@L^>}fYDpg|7!_%r?F08@stdB#uchDb2GFdg?fwW__t?GXX=6&1a5O> z+b*OD3Xr|Q_GV$I(ui{F7xl98V)KG`5gUw7n-Im_p$$wi;P^0=z(5F#$tIA+6p|Ac zkQA>lR|!IcZEND61zzaA`oFF}A%b(9IR3&8Aj$xaWGUpT(lcHa@SV|$rZgJv4h4YU z)F<pl@dur6(Z@|Z-Jn^pHAzo$2|2Hkw^m#9Z?ggG?><=4Cjqm|6`#xxb>i`_fOv{% z&|`Oj+DVBUv*_<gq=&-%K%Q9gcm&CM)i-Vz6w2ryV)NVNh^U(-*dmNPF8mUn8Gds6 zEKqp;bn$c3(rq*I$VFxc3X~K0Jc>Y1_QAeFPC?iY4Y|}Yeozl<kwn8K+$-g9-d;r5 z_pomgo{F6rxLalU5)YS^%X?NuFDE|!=#87s-q+BNmE;-^wPry?@8>7@Yr`L#(0dvL z8MyEG2E_^QeIj^&2m^i+b(Lb;d_%=i=E+sBq*=E8E>aHn$O=v;7N5LH(&fScZAB~K zdJGMpL|g`jjE?g~0P+!X43=lye6yEF=@i0z@7hZ({PVvBvhEBw+1!gDIoGrH|EY~W zEqeVN$vR$I65$r;sxmYfx*{8R;FKEl@HTdOBzSnTlo%4#GnAc(8%*6DYa*W!LeER- zy(h_w*hYNhVjg7yi<uwfAHMUrx$`bkg2}gD7yvZ@yu<d>KmU{fcj73*99oE#N#-2S zap>noKfbj@PwF7iV#?|Wo2QC3*%X=S*P|-(=65wAIx{0nfU{x3&){$#>BkTszNN$5 zWp~FC=u4`6?z#9jRW+NQ9-dE2W0h7|$Uva78*I$ex;o10wCo{Z*n8%OX{25a)r|+M z1%wkBK8{3RE>~^%g&Cna_V?d~0V|%DI|4(8OJ5-KzBW0fkqVrpM%lRyc#fp5FNEA! zJ%@<XGeXr5=jvQ-+yMcs1PtpM5m&4(p{qH@eNzaUTcs{byUSGtL2fX&d>_PXC)P=a zUG+FO1)78g2ab{{xVmcL+5X{H`l#l0fWmPzI=X7|z@$ZH5b0w}HmUBNzAPR_m>wL~ zsW~;yRP6zqSqh45JJpM;fs3lEN4x9*ebLDw58RSkwzflQ)^+mnTGsfEg7?PcLHVdt z`mn39A5)`sc{1ZN!C8{kpt>QQm)Z{0=2{W%c40@WIo1!nkpug2wysnwWpLdSU{K?$ z1CM<nAt6?L!Qvn-0$Q~w#I<&Y)chb^PRmKB&vJikxLpP(3$@_&LgSTa_U<=bkCPMj zMNaO9^x-x&vKf5u*}=aZj=K>0+&84+kAQlv#PyB><0b|rU+%}TwG%WWCH+$4^`aQ^ zvZV*E;wnC<P{r>>^43M6gEH|F*f{K{@SGg|DI{5s^W3IaII6d+s38BlV50o48-kl# zS=qGSe3+H9i&^onqb++(Z+jJis;>PQ>$ruQwcK0VMWMgp<M%dmwKYCu9`=VhhYYaW z@Cd9mR`>VTpJj!t6n`T|SzfxGQ!ZFv+eOat;-Vh-w7USr!iRpmmX@{%;06M7S)mP) zZ|$1)5r$(=diVyN^j+S&AU|4~IllUwtI#<!3v_@oUCDj!olh=iWL#2ydsWVp@`1MQ z#j+^qu<LPRe!V50f+fzFSZ_Q9dTReRS(u-LJdYA=UP`MuX8ip<oawI-+&Q=E6F=#D z@RzqJlef}erBUpK4$b%jfSe4iC;17vlg#w%a_^U{^EtFzP2IBNYQ+~&t}BycS-0Qn zJPvp62&rJ@_7KPvOYtm!x8fjYiQ5?M@;NHf8s3B{cj*-4y4XkJ6MFsyDS)tX3>o7Y z1-0dj%}@kl4L~86#x6dxAVp||jnKi-DKpE#m7TyP!zRDOMXMqQ<gQy6y>&3D+8LS4 zC0IF=i#RB&zO2ttLTI9&7U37w*=4DZpGfr7{MkC~-KU)MKTk#^SQnYZ-Y8A@X+O1D zzGQV~4cAjP_pLM9pOCqVo7q5!=DXOv(f^NQc_FnMg%Dt1fiPfT9RFo;D_0Y9`{WNl zM&P%bnk2^OrNW7VBR9veY^8Q38Y^gnLh`LZvY@2E_|>GZpapy*JNIGl&z85F*{SdW zJ6~4O6pzp;0`e435TDSq*7FYf(Aq{za!D=*|LGNPcc0s1&NgrN)BJsYAha_cC4@99 zUg>U2uuxC+61z%I<WuTHa4@pVN<Yr12av;#q7g(R6ur}v#q7RMM&+&_U4SxB5<dy; z>pUU?PE7BVf6`I4k0+kq=ctkF6A@f=p6X_ylwzN`aU)Sm_G7c8d-qozO&t17&Y|{) zPsD(_w7G8-slKyZQen9@uWQ%iVb%lIaFzmnRFNILAILjx0YAnB6Z~@8_v*)V6p+=& zM7Yfse(j6!R<$ovZyaN*fkw4`dU9Y0gW(0}yf&Nn?)sLN2~K+8D3&dZbnn?Ex|QT_ z5K9o5Vjg-~=~1Ho`ZY*<d=xP2IsqAt^3zLU)$$wk^GH?6v)O$wOJ4?BDv1_hKAZl@ zl+hT1G=y)fpATm&TMiG{wDD9oCor3$#W3P-Z$)3tw&RUB#v5N~1d@!WojS=ld`561 zxYwR*{Mi_G&gbaNuumm!<ad;An`V$iGzPDE+Hm1`MvzZGq({*15=pw{_|utZW0amI zAEIi2w4tnslUI>!{QEob-tX-G6xJ3O952rnrW*aq<FT0YRz+29-qN6>0+h6!Rvwkv z@=~mYl<ZL$L613GdpaJCvJF+H|JlHxRQbW9RZ@CXVI8jAqhocViEYsy<>z%%>k!xj zA*T+QWHsq3#2;0PHpBFwZ(P~+S{$DQR$xn!61cJ_t@}M}S>x4~U*%n2acI<x&}zgb zk?my>&eZ%)E!*B}Xp|s$1{_TcywUThxlT=FOSRya9{z?ktI9B&eBas?@~zepsB=UG zky@Xrs!;bSuH!CnWW`O*A<wbt!3M}>MSQ{QhJ_Fq#pk#a;c^CdnkUfA{e5%8Y-5sT zm&rG{y*tO<AahnF&TQEm`3RO6`HjhG0H>;$f54`K{E-~V?ior@4H)V(wI|l@!Q>LW z!|y_qE+Zyu6cA7yqt`q6p)}CzD=Z@e<rm#c(|?VzhyEHQQ0g?to-;zMJ(AxM+Go7y zq<*9HT|NTbP?C9zsv4!xX_qhwXDyaoGKs8YMTS%1Q@TJfva;Cf?-Q{%!SJqfXfTKk zmbRBoT+jILJ$FZ(wJ49q0*gi{)B<diD5h($1(B1O53<VP=ugFB=Tl~)!kbr?fX2`N z^w$5|U?n&!P0^x(fmKj}fsrPgco6|iX7(m-ZX-Hy-g?WaLSwpH%?|_{vU?IBSnwtu zLl7+7FXL~7F!Z-c+MXfkV&08;4wOw4CFKk$lqqF?+VCw5rS64crE_y=yaOTyjBtLZ z_U@0TExEMpQ@x(n=O0hK)H+S!pyTZ8Z2#MUr#!!_&f~oMe7CDsqt9tKW;9@)?n8xk zc^>dS5kBn`IC*ck4y>H}^!_PH?0w?$nwlP{(099F#t|F=GI>l&mlH5=5P!%q<eOkZ zIEDCW+HQ?8Ujd))YmMYL-2Zi;rc$4(!>Urh&N)01%e6EzKwv|>{+3{V3e#SrDD$`w zDX+f4Xcvk|ckrvA`xoZBIX%EXKEse9%S{8x`vg%)Y4?8Q34ERbby{_v;eIZ=XLD$| zM~s6Lezi*fxNuqjn(VedqyT{6_C4|FRX$85G91B<lB|ZZXdT3+?->=>T7aReA4z9M zoi3&0j;a@zvDJKLD?B|cvO%53Eb3I{IG0$Y%i;=OrQfW(gGP4PwFC<PYS4Lk(+rBH za#!_$4i!Z&(JU_?=R7ux?<|quQ`##wtDR-Ozp7tp`BaNhV{RSoR{Mw?UW$z(h1aWi zmrtx#^~fEbtFLHmF}f3AbVq7%sS~OCXdj}clG9zQ_|$9TXn3pd;v{3mj>MYlsVkMn zVk~x^$B_yWa;}a&jR8e2f<3&r#EN*8>Q)pMc*f>;!R|XdT`jz(!&5&C97jbNuCKS2 zemB?pt*$(7DL0hk+voZ{1o)b%Cm?c1Oqo+~ZBD7LU?r5w3tBoZ7u#54S>3fgI{lj4 z>ZZro+?p8{g;C*TonGJEaMN(|qiLzGg=iuiG@GldE${CBQ41jRUz~mQY<+rox;%sT z^n@0_%+Mho+&jdS|JZu;IFDgt=`6n8HF1d#&%Hfx-gj0-mW~^J2MH#!B*o>rqGi$H zc%2tRC&T=xm14t;8q&p#vU#jvraB&)f*HrG3_VFOg^sn9!1=M+q_G|=f#3ZAnIS8l zxSQcvmaM$9Hwdgvmf?Hyl|r#?rc0WlHiuWX`bHGz#kFASW#%~CHjt{v4aZ(RoH&xN zm0!o>4bI<+3U+BINe%y^;+sx{Me>}HZwrpThu&`tXb`i`0;Opk=lZ%9`HXO=b%G5Y zi285TUT=!UQzjt@DUm7pYq*@U;PpT~e~k|@Ba}B!SON6q1AEncJ+E1zr;koCc2k<Z zS0Z1H@X;#lh7GRLBj9-C{tlj9|I0ml@~{onWYxTnQ4iZMaQ9i!ucHg9=!8Wfh(;h^ zC_CcBy@|ISD2Z_R{XN89cm$!jv5(t&GHw{3TcRi;rQ&3_VU8G7^Br@X$-ZPtW-3jv z+L@7LJrFRSllcZ>sQY<^8=EP`Rwms@lFOEK9)ZXYfY){}ii?xMrCwN%j5a=4*OR6^ zWVxs%c;YphxKSu2q0S_ElZ)|YPp<FTG(r_&wFMd8-!ETDOvHcrp$w15#wF<F%7;O` zX_pa@s6w8{<u(yI8|=a~;oP6sk|~qT(o>jV+67RcO+wLGhBm%|C`*xX-^Wid{(2Pc zlwojTD$@5T?Y_ek|74ZUz*2IVimn_LE)PQC!(sme0mWAyo<Zkbuc0IfT~1~qsWPU- zofH&g#lj8k9kv?MNQ%Ml(4NP(QsbC_IAPvZ(<M{KsL4$qfw-ufa=|v0$fMswlE7?( zAPF?;6?4J_PJf@$F&x38`dZzTn`&$++TB6DR!3coI>5=DK))@P<l;b7tXBk|>*GUz zU{Iu{*l+RkXR1s|=xK!`-aD^cj?t!G_*hz6%s5>Q6^T0_5ViOWKRoVioTX-<9wI&0 zhS%88QQ}=2Y%oDjUGB=^C18R1zTi!l-vKc3@{l=#hlawYQ_?f{e6K9D_MKdAJN2kK z0MqmlyLES+L%EID{eI|((u4O~(n$FkI!<_$+xEf2SEktS6r`sjry7}u7tFzW+cL_1 zI}N{GbW7T+v7d17fCq#ivrjXr=5u^d5U)I|J>-q%WBr)DvEqCs2rw)rcb6&NI0aDX zHV|NR?@*t_Du(kHPmsFQipx)Ij4g&a&=KJh|Ft|E9|FXpGpI{T6u6!_HjAfP3XQXu z@Y{vAx&G;+Mmyb{y)e?{2!x9#kq_~sPj}dfE6tsy8kRETIr+ig_wLx8K2pAaQqFId z-@d;EN%biK1@p`&g3pRH`pmaww?OEdg+8g(oWXwNGd-R#LstFWD#r&OAigjCoDDBr zb*mC6zd!cUR?{wlb}=4~=h&db8vO1{hk|;_o2i%~)ggNF7&fS6`^Fe3yI=mYA@7|< zxcH14D7JqMyOIAF6%Ox~k5f~3F|i`{VB%ti1Ue$=c0Ooy`_M|#jUbAK1t=LZ-7O2U z8OJy?|L%Mjs<Y1;+>BntORM;va}pPG<inwT3vSOX&L-uVy;P4q0_S(AFtbJC0h@6v zBBoRp8}utVP)c}=Qh85qANvJkao6G8w>$K=7SyZS9t&YWKsE$hD-67wA4xpFwMfFi z46PXocW}S?D^|Aoq!m~UIG_dcf*I@&U~A%M^u`0vE`~<y)=rD=j}CTs98!kZ)3?V~ zl5%XIkJ?ZbEA~1a!|NMnwtEx&5Q?Qo`b`4fUZ}AG7YFp$gLx;f5u<WY&fEHD&Hf6S zG3ushH%&`@|GSq~ei?9Q7c^LJ`-azt^!Lw`Lq(s&KKJu9c<;r+8XyS|p(dh?%t3$? z$8`}uJcxMip~mfE=Gg4`gr*4x%fh&<>jHts{U`TDncj3&-53}q$Q*D=R42jWhNyHW zLmlJV1+#`pG={)$_Z+wqqUXTBm}|LH&>(ebHqeCZ-ClThKs{+v<hW?Aj<8I)uvK<M zl%}SY6<9XLq(%0H0?Z9o;=<+B;^fqV&q^yTUjRu%t$pG<ei<!EHjs0`)q>BAJjj)@ z&Vq)~0?LNCJf8q&7yLX|sQec`IexDv-;}FDXWM}Lh?BqzFGJc`UOqbC*Mddvq}c0r zLp8UUEqwS!YT`&9l0g!q)0gEJ$+jKL@(FH}pZy~RO+jBvfFp1<5D{k*yRya?zZK^_ zO}?V?t~iJ?e>D3s170ea@Td6A)CoibZruJ3+oM56sOjEzezZMbf6$+Y(nVUyL#J0^ zKmDllDl=_u6CG5KXve^jDXsiWs`Q!^d_^*wiR(lG+MyOFyl>e({H9;>Q%H9Us6|^u z^b-FJANWlO0$tn$AaFV+b|%)0{c#~$NTIU6owwwckjR*fY^aQpyMaFA=&6vWB;?~$ z&N`+2H7kgRor7yGq1SY#RBgw}W;>oBEy5IEnQ>np>3k?ZjN$Dbfl!RzhG92w(=TA` ze@P%KLciyx%t~whiPvPUp@G%m)rfedvCkvK-rFvU24G`KLXy+5!h9OjkrY+_@{3@q zHl^}}y(THP#))Ko7xJ4hGu@XW-Dnk#>X16v;}ErB|BM_(KtPEjmof;Q_m&l?88JsF zI-fm@vfR$gD!;*oE2@0sWH%#Cr!8%j-EE)S`9^9R7ZNBfV4?eCx&rW2!oI@jw3J&% z(O8MO0wPL;1n9OwQO$!}?y@&PyS()!^v54-TX@)}wlzmu`sBVF^WH{^#<dVnQI8uX zt7hUHmL#xmjE<+$-pt*>EcTC${uYUx{AIP{>1>xTf}cZ2#<4Q?M*hriUN)#^7a6$x zosbL&E536neLopI#cn4HB$pl7cNGgv?>MiBfM{xI54UJdNu<xhZ;oa674ZnBUBLne z<$5P5ukC0mZ~-}3a}Ezto6jtx`BV4+{Jzq0oN(x^)z?v>TXo58YQyR~zwY;uP_@8Y zPw3CzNuNXubA9Q(-yY=(E=ag}ZyD<=54&E`)m=jf%ntme*tR2ALYe0!Ibt*y-%pmc zfr|$)L7Gopb*6T!HFWrtpLxmy!L8nvp&@a<f*Z9veRA|6%nq(<#=pBJsh8DtC96-M z^g<INFUlQU*3}Aw@b-6gBW4)N*u6&yDW~y*e3K>!TP{zAGuO?&pQdDKCO-dN)>S=4 z)0ItMpt3D;v`y26Y51$ED`iwzUj)N=0@yux_-mXMILX`#kWr@m(%*(m=k!88A{x=h z=LHmm!sF>4enHlfl#3<kk%#xFlXWM;G0^2WYokAZ%1!AAd5ih(fF@aZ;=T%zR8`{^ zM`x|?tLvx%IsTl1$1Yh0`(A|9b-3lc5Un0&98WwN!47%O3F1Vr{xX!d!Ekj2fNB?- zqpq}D1tyJ6@nK7zZKVNT!N@xKaa&i;#u<g8toijf_v{lm7(NT7f-%bJ;<Y!g0UD;i z4BLaVY7Z@OR1kKMv(Z^rQ33Z#NqvxY)hWeLuI^bAV>Y`Yh<O7sigUclFqxFxR|S$S ziB*+qbPf(3gPN}@r>9AhhRS^kz?l90pJeemhWg35J%rruS}7bl7C2)$XXJ|(+;>J^ z9*mG@+}C)-z3YW$Z6AHd(H59@c~AB+B5jqoMh8qUx8&+a$lUb8`|Qnq_kn&MM%?~U zxiM><_U+ulf0ZIjx6duMYPSJXA$l7woemeg*40Nc;_T8XbUU+fluFA2;1sOqsx)=q z#Zx-fbvj*V>zk~%v_9<5T1bktIfm0sEsVJ;Z*tKU-0Aj?UCF7sn_(2JssE#yI_{*S zu|}ZFuj@N2UScxg_M8V}9bAHIH@Op5r8`&ELw%~s{isFgCda<y?!G4~uko72?w+~r zk)_^8FP^@dUzYcZ&`MAW#8XMy^C)VO*{{CnvM*81v~R7(8%A(Fr?PgPsjK<8kVd2I zF$@1eU=WJgDtii6^Y27^49K^dtcMDMLGd2UrEEbw>B7_BuwlKF5V-<Bsw>tgA<yuF zSPg}}WcdT`0KpNPiK<M-0cn)klI~bU(_~3dq1bN)8rFdq;ym>OT8RH{%Up--&z@Mm z!l8V)na@9+lDw1u22*f6kaS1Sm*&1_;~VC5r{|t?=s{c4G(HkCHp4_)$62BM#j8LK z`|`D0Uv4(I93yMknm`cwVNGs!@$0uwQtKx&UkjcwT)yywqhUwvxyd<cRGA4=6hZvZ z6CE6e-LI#AA0S*s0GPJbu8urIQH;*-u<0Uu)gmRZv0gv<gQq7js?;jP3s*6G4GOt2 zeANou&B5W(`BCT5!9L~n89JFNvV!hpRfJMf;Bp#`lP+jAPof*doRmo5G%(B>K7*mA zA(388{0<}w=mNcLtTM8VtrDxx#wyLgC@FqgbM0B72TCZM0)t9VIDZJv3CbBEdbieD z;d7G;2}N+X^uIee!W<%%%<-3oN<In`mj`ABQoNj>KJB2;Uw1@#Kvl~PUkhAL-DBjC zsG7?mKAxP{35PbFp4W#f;;z4f*|3qck&>MGYgrXrfw8Ko2}^C%{u1TOm<o!4B2mWK zq?sYQ*a_FX2fz}*M7(&<PR#!}j8oR7>F?PV3PXy>HoT&}l4p*FZ?UY@|NJ^TGrs1= znXy$$toL|8!-bi;3p>?DF23r%{NibOqPxvB_SM}W;^B&K+mV&X-Be)bCS06^i}xJ& zUMeCYOFyuDI~-)zuX2t*GY5+kVOaE&wPurEQ|hbME0C)n+dWSEo0E=>>P>@e@edYH zA9kVd*WR=^TVq@?nd*iQ|CC&4^$@wly!L*iAF$b|kTZ-tXSFixgNCd9;P(T!V-M8d z=YsnGsy*ZsOi80XyLmf9a<y5v7j3;ky;J)Bkl_Hr^ZI`#=F}$0bnit($*n7$)E5>v zI5Vco8v}lkVXWXM<z�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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index aa991fceae6..fc10b601f7c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-bin.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 1b6c787337f..79a61d421cc 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,10 +80,10 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' @@ -143,12 +143,16 @@ fi if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -205,6 +209,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index 107acd32c4e..93e3f59f135 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal From aba2f87adde20d2ca2fbafc2d0eefc90faafd12a Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Tue, 14 Mar 2023 01:15:15 -0400 Subject: [PATCH 271/619] Fix false negatives with some item comparisons (#5511) --- .../java/ch/njol/skript/aliases/ItemData.java | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/main/java/ch/njol/skript/aliases/ItemData.java b/src/main/java/ch/njol/skript/aliases/ItemData.java index faaa8c76169..89c71cac05a 100644 --- a/src/main/java/ch/njol/skript/aliases/ItemData.java +++ b/src/main/java/ch/njol/skript/aliases/ItemData.java @@ -46,7 +46,6 @@ import java.nio.file.Paths; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Objects; import java.util.Set; @@ -415,18 +414,25 @@ private static MatchQuality compareItemMetas(ItemMeta first, ItemMeta second) { } // awful but we have to make these values the same so that they don't matter for comparison - second = second.clone(); // don't actually change it + // clone to avoid affecting user + first = first.clone(); + second = second.clone(); - second.setDisplayName(ourName); // set our name - second.setLore(ourLore); // set our lore - for (Enchantment theirEnchant : theirEnchants.keySet()) // remove their enchants + first.setDisplayName(null); + second.setDisplayName(null); + + first.setLore(null); + second.setLore(null); + + for (Enchantment ourEnchant : ourEnchants.keySet()) + first.removeEnchant(ourEnchant); + for (Enchantment theirEnchant : theirEnchants.keySet()) second.removeEnchant(theirEnchant); - for (Entry<Enchantment, Integer> ourEnchant : ourEnchants.entrySet()) // add our enchants - second.addEnchant(ourEnchant.getKey(), ourEnchant.getValue(), true); - for (ItemFlag theirFlag : theirFlags) // remove their flags + + for (ItemFlag ourFlag : ourFlags) + first.removeItemFlags(ourFlag); + for (ItemFlag theirFlag : theirFlags) second.removeItemFlags(theirFlag); - for (ItemFlag ourFlag : ourFlags) // add our flags - second.addItemFlags(ourFlag); return first.equals(second) ? quality : MatchQuality.SAME_MATERIAL; } From 1f1fff6e0d8937852513fbd8d03aedfd12284a35 Mon Sep 17 00:00:00 2001 From: Kiip <25848425+kiip1@users.noreply.github.com> Date: Wed, 15 Mar 2023 07:31:31 +0100 Subject: [PATCH 272/619] Adds support for debugging and profiling (#5299) --- build.gradle | 61 +++++++++------ src/main/java/ch/njol/skript/Skript.java | 6 +- .../skript/test/platform/Environment.java | 77 ++++--------------- .../skript/test/platform/PlatformMain.java | 45 +++++------ .../ch/njol/skript/test/runner/TestMode.java | 6 ++ 5 files changed, 87 insertions(+), 108 deletions(-) diff --git a/build.gradle b/build.gradle index 6a0543906ce..eb5f8c75f3f 100644 --- a/build.gradle +++ b/build.gradle @@ -180,8 +180,13 @@ tasks.register('testNaming') { } } +enum Modifiers { + DEV_MODE, GEN_DOCS, DEBUG, PROFILE, JUNIT +} + // Create a test task with given name, environments dir/file, dev mode and java version. -void createTestTask(String name, String desc, String environment, boolean devMode, int javaVersion, boolean genDocs, boolean junit) { +void createTestTask(String name, String desc, String environments, int javaVersion, Modifiers... modifiers) { + boolean junit = modifiers.contains(Modifiers.JUNIT) tasks.register(name, JavaExec) { description = desc; dependsOn licenseTest @@ -193,7 +198,7 @@ void createTestTask(String name, String desc, String environment, boolean devMod javaLauncher = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(javaVersion) } - if (devMode) { + if (modifiers.contains(Modifiers.DEV_MODE)) { standardInput = System.in } group = 'execution' @@ -207,11 +212,20 @@ void createTestTask(String name, String desc, String environment, boolean devMod 'build/test_runners', junit ? 'src/test/skript/tests/junit' : 'src/test/skript/tests', 'src/test/resources/runner_data', - environment, - devMode, - genDocs, - junit + environments, + modifiers.contains(Modifiers.DEV_MODE), + modifiers.contains(Modifiers.GEN_DOCS), + junit, + modifiers.contains(Modifiers.DEBUG), + project.findProperty('verbosity') ?: "null" ] + + if (modifiers.contains(Modifiers.PROFILE)) { + if (!project.hasProperty('profiler')) + throw new MissingPropertyException('Add parameter -Pprofiler=<path to profiler>', 'profiler', String.class) + + args += '-agentpath:' + project.property('profiler') + '=port=8849,nowait' + } } } @@ -222,29 +236,28 @@ def oldestJava = 8 tasks.withType(JavaCompile).configureEach { options.compilerArgs += ['-source', '' + oldestJava, '-target', '' + oldestJava] } - -createTestTask('JUnitQuick', 'Runs JUnit tests on one environment being the latest supported Java and Minecraft.', 'src/test/skript/environments/' + latestEnv, false, latestJava, false, true) -createTestTask('JUnitJava17', 'Runs JUnit tests on all Java 17 environments.', 'src/test/skript/environments/java17', false, latestJava, false, true) -createTestTask('JUnitJava8', 'Runs JUnit tests on all Java 8 environments.', 'src/test/skript/environments/java8', false, oldestJava, false, true) -tasks.register('JUnit') { - description = 'Runs JUnit tests on all environments.' - dependsOn JUnitJava8, JUnitJava17 -} - // Register different Skript testing tasks -createTestTask('quickTest', 'Runs tests on one environment being the latest supported Java and Minecraft.', 'src/test/skript/environments/' + latestEnv, false, latestJava, false, false) -createTestTask('skriptTestJava17', 'Runs tests on all Java 17 environments.', 'src/test/skript/environments/java17', false, latestJava, false, false) -createTestTask('skriptTestJava8', 'Runs tests on all Java 8 environments.', 'src/test/skript/environments/java8', false, oldestJava, false, false) -createTestTask('skriptTestDev', 'Runs testing server and uses \'system.in\' for command input, stop server to finish.', 'src/test/skript/environments/' + (project.property('testEnv') == null - ? latestEnv : project.property('testEnv') + '.json'), true, Integer.parseInt(project.property('testEnvJavaVersion') == null - ? latestJava : project.property('testEnvJavaVersion')), false, false) +String environments = 'src/test/skript/environments/'; +String env = project.property('testEnv') == null ? latestEnv : project.property('testEnv') + '.json' +int envJava = project.property('testEnvJavaVersion') == null ? latestJava : Integer.parseInt(project.property('testEnvJavaVersion') as String) +createTestTask('quickTest', 'Runs tests on one environment being the latest supported Java and Minecraft.', environments + latestEnv, latestJava) +createTestTask('skriptTestJava17', 'Runs tests on all Java 17 environments.', environments + 'java17', latestJava) +createTestTask('skriptTestJava8', 'Runs tests on all Java 8 environments.', environments + 'java8', oldestJava) +createTestTask('skriptTestDev', 'Runs testing server and uses \'system.in\' for command input, stop server to finish.', environments + env, envJava, Modifiers.DEV_MODE, Modifiers.DEBUG) +createTestTask('skriptProfile', 'Starts the testing server with JProfiler support.', environments + latestEnv, latestJava, Modifiers.PROFILE) +createTestTask('genDocs', 'Generates the Skript documentation website html files.', environments + env, envJava, Modifiers.GEN_DOCS) tasks.register('skriptTest') { description = 'Runs tests on all environments.' dependsOn skriptTestJava8, skriptTestJava17 } -createTestTask('genDocs', 'Generates the Skript documentation website html files.', 'src/test/skript/environments/' + (project.property('testEnv') == null - ? latestEnv : project.property('testEnv') + '.json'), false, Integer.parseInt(project.property('testEnvJavaVersion') == null - ? latestJava : project.property('testEnvJavaVersion')), true, false) + +createTestTask('JUnitQuick', 'Runs JUnit tests on one environment being the latest supported Java and Minecraft.', environments + latestEnv, latestJava, Modifiers.JUNIT) +createTestTask('JUnitJava17', 'Runs JUnit tests on all Java 17 environments.', environments + 'java17', latestJava, Modifiers.JUNIT) +createTestTask('JUnitJava8', 'Runs JUnit tests on all Java 8 environments.', environments + 'java8', oldestJava, Modifiers.JUNIT) +tasks.register('JUnit') { + description = 'Runs JUnit tests on all environments.' + dependsOn JUnitJava8, JUnitJava17 +} // Build flavor configurations task githubResources(type: ProcessResources) { diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index e78798f91a1..29bef4366c2 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -483,7 +483,11 @@ public void onEnable() { // Config must be loaded after Java and Skript classes are parseable // ... but also before platform check, because there is a config option to ignore some errors SkriptConfig.load(); - + + // Now override the verbosity if test mode is enabled + if (TestMode.VERBOSITY != null) + SkriptLogger.setVerbosity(Verbosity.valueOf(TestMode.VERBOSITY)); + // Check server software, Minecraft version, etc. if (!checkServerPlatform()) { disabled = true; // Nothing was loaded, nothing needs to be unloaded diff --git a/src/main/java/ch/njol/skript/test/platform/Environment.java b/src/main/java/ch/njol/skript/test/platform/Environment.java index cd7fb24d3f3..f2e9cf38cd2 100644 --- a/src/main/java/ch/njol/skript/test/platform/Environment.java +++ b/src/main/java/ch/njol/skript/test/platform/Environment.java @@ -38,7 +38,12 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; /** * Test environment information. @@ -227,7 +232,9 @@ public void initialize(Path dataRoot, Path runnerRoot, boolean remake) throws IO } @Nullable - public TestResults runTests(Path runnerRoot, Path testsRoot, boolean devMode, boolean genDocs, String... jvmArgs) throws IOException, InterruptedException { + public TestResults runTests(Path runnerRoot, Path testsRoot, boolean devMode, boolean genDocs, boolean jUnit, boolean debug, + String verbosity, Set<String> jvmArgs) throws IOException, InterruptedException { + Path env = runnerRoot.resolve(name); Path resultsPath = env.resolve("test_results.json"); Files.deleteIfExists(resultsPath); @@ -238,11 +245,16 @@ public TestResults runTests(Path runnerRoot, Path testsRoot, boolean devMode, bo args.add("-Dskript.testing.dir=" + testsRoot); args.add("-Dskript.testing.devMode=" + devMode); args.add("-Dskript.testing.genDocs=" + genDocs); + args.add("-Dskript.testing.junit=" + jUnit); + if (!verbosity.equalsIgnoreCase("null")) + args.add("-Dskript.testing.verbosity=" + verbosity); if (genDocs) args.add("-Dskript.forceregisterhooks"); args.add("-Dskript.testing.results=test_results.json"); args.add("-Ddisable.watchdog=true"); - args.addAll(Arrays.asList(jvmArgs)); + if (debug) + args.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000"); + args.addAll(jvmArgs); args.addAll(Arrays.asList(commandLine)); Process process = new ProcessBuilder(args) @@ -253,16 +265,11 @@ public TestResults runTests(Path runnerRoot, Path testsRoot, boolean devMode, bo .start(); // When we exit, try to make them exit too - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - if (process.isAlive()) { - process.destroy(); - } - })); + Runtime.getRuntime().addShutdownHook(new Thread(process::destroy)); // Catch tests running for abnormally long time if (!devMode) { new Timer("runner watchdog", true).schedule(new TimerTask() { - @Override public void run() { if (process.isAlive()) { @@ -285,56 +292,4 @@ public void run() { return results; } - @Nullable - public TestResults runJUnit(Path runnerRoot, Path testsRoot, String... jvmArgs) throws IOException, InterruptedException { - Path env = runnerRoot.resolve(name); - Path resultsPath = env.resolve("test_results.json"); - Files.deleteIfExists(resultsPath); - List<String> args = new ArrayList<>(); - args.add(System.getProperty("java.home") + File.separator + "bin" + File.separator + "java"); - args.add("-ea"); - args.add("-Dskript.testing.enabled=true"); - args.add("-Dskript.testing.dir=" + testsRoot); - args.add("-Dskript.testing.junit=true"); - args.add("-Dskript.testing.results=test_results.json"); - args.add("-Ddisable.watchdog=true"); - args.addAll(Arrays.asList(jvmArgs)); - args.addAll(Arrays.asList(commandLine)); - - Process process = new ProcessBuilder(args) - .directory(env.toFile()) - .redirectOutput(Redirect.INHERIT) - .redirectError(Redirect.INHERIT) - .redirectInput(Redirect.INHERIT) - .start(); - - // When we exit, try to make them exit too - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - if (process.isAlive()) { - process.destroy(); - } - })); - - new Timer("runner watchdog", true).schedule(new TimerTask() { - - @Override - public void run() { - if (process.isAlive()) { - System.err.println("Test environment is taking too long, failing..."); - System.exit(1); - } - } - }, TIMEOUT); - int code = process.waitFor(); - if (code != 0) - throw new IOException("environment returned with code " + code); - - // Read test results - if (!Files.exists(resultsPath)) - return null; - TestResults results = new Gson().fromJson(new String(Files.readAllBytes(resultsPath)), TestResults.class); - assert results != null; - return results; - } - } diff --git a/src/main/java/ch/njol/skript/test/platform/PlatformMain.java b/src/main/java/ch/njol/skript/test/platform/PlatformMain.java index 29f09c30a15..f5d95935844 100644 --- a/src/main/java/ch/njol/skript/test/platform/PlatformMain.java +++ b/src/main/java/ch/njol/skript/test/platform/PlatformMain.java @@ -18,30 +18,31 @@ */ package ch.njol.skript.test.platform; +import ch.njol.skript.test.utils.TestResults; +import ch.njol.util.NonNullPair; +import com.google.common.collect.Sets; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonSyntaxException; +import org.apache.commons.lang.StringUtils; + import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; -import org.apache.commons.lang.StringUtils; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonSyntaxException; - -import ch.njol.skript.test.utils.TestResults; -import ch.njol.util.NonNullPair; - /** * Main entry point of test platform. It allows running this Skript on * multiple testing environments. @@ -51,7 +52,7 @@ public class PlatformMain { public static void main(String... args) throws IOException, InterruptedException { System.out.println("Initializing Skript test platform..."); Gson gson = new GsonBuilder().setPrettyPrinting().create(); - + Path runnerRoot = Paths.get(args[0]); assert runnerRoot != null; Path testsRoot = Paths.get(args[1]).toAbsolutePath(); @@ -62,8 +63,13 @@ public static void main(String... args) throws IOException, InterruptedException assert envsRoot != null; boolean devMode = "true".equals(args[4]); boolean genDocs = "true".equals(args[5]); - boolean junit = "true".equals(args[6]); - + boolean jUnit = "true".equals(args[6]); + boolean debug = "true".equals(args[7]); + String verbosity = args[8].toUpperCase(Locale.ENGLISH); + Set<String> jvmArgs = Sets.newHashSet(Arrays.copyOfRange(args, 9, args.length)); + if (jvmArgs.stream().noneMatch(arg -> arg.contains("-Xmx"))) + jvmArgs.add("-Xmx5G"); + // Load environments List<Environment> envs; if (Files.isDirectory(envsRoot)) { @@ -81,22 +87,17 @@ public static void main(String... args) throws IOException, InterruptedException } System.out.println("Test environments: " + String.join(", ", envs.stream().map(Environment::getName).collect(Collectors.toList()))); - - Map<String, List<NonNullPair<Environment, String>>> failures = new HashMap<>(); + Set<String> allTests = new HashSet<>(); - + Map<String, List<NonNullPair<Environment, String>>> failures = new HashMap<>(); + boolean docsFailed = false; // Run tests and collect the results envs.sort(Comparator.comparing(Environment::getName)); for (Environment env : envs) { System.out.println("Starting testing on " + env.getName()); env.initialize(dataRoot, runnerRoot, false); - TestResults results = null; - if (junit) { - results = env.runJUnit(runnerRoot, testsRoot, "-Xmx5G"); - } else { - results = env.runTests(runnerRoot, testsRoot, devMode, genDocs, "-Xmx5G"); - } + TestResults results = env.runTests(runnerRoot, testsRoot, devMode, genDocs, jUnit, debug, verbosity, jvmArgs); if (results == null) { if (devMode) { // Nothing to report, it's the dev mode environment. @@ -142,7 +143,7 @@ public static void main(String... args) throws IOException, InterruptedException StringBuilder output = new StringBuilder(String.format("%s Results %s%n", StringUtils.repeat("-", 25), StringUtils.repeat("-", 25))); output.append("\nTested environments: " + String.join(", ", envs.stream().map(Environment::getName).collect(Collectors.toList()))); - output.append("\nSucceeded:\n " + String.join((junit ? "\n " : ", "), succeeded)); + output.append("\nSucceeded:\n " + String.join((jUnit ? "\n " : ", "), succeeded)); if (!failNames.isEmpty()) { // More space for failed tests, they're important output.append("\nFailed:"); diff --git a/src/main/java/ch/njol/skript/test/runner/TestMode.java b/src/main/java/ch/njol/skript/test/runner/TestMode.java index 9374269f710..e0927c6dc61 100644 --- a/src/main/java/ch/njol/skript/test/runner/TestMode.java +++ b/src/main/java/ch/njol/skript/test/runner/TestMode.java @@ -57,6 +57,12 @@ public class TestMode { * If Skript should run the gen-docs command. */ public static final boolean GEN_DOCS = "true".equals(System.getProperty(ROOT + "genDocs")); + + /** + * Overrides the logging verbosity in the config with the property. + */ + @Nullable + public static final String VERBOSITY = ENABLED ? System.getProperty(ROOT + "verbosity") : null; /** * Path to file where to save results in JSON format. From 662185196ca6241408c76cad544f8f1a514bd5b0 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Thu, 16 Mar 2023 03:04:20 -0600 Subject: [PATCH 273/619] Add default file junit tests (#5496) Add file tests --- .../test/tests/files/FilesGenerate.java | 55 +++++++++++++++++++ .../skript/test/tests/files/package-info.java | 24 ++++++++ 2 files changed, 79 insertions(+) create mode 100644 src/test/java/org/skriptlang/skript/test/tests/files/FilesGenerate.java create mode 100644 src/test/java/org/skriptlang/skript/test/tests/files/package-info.java diff --git a/src/test/java/org/skriptlang/skript/test/tests/files/FilesGenerate.java b/src/test/java/org/skriptlang/skript/test/tests/files/FilesGenerate.java new file mode 100644 index 00000000000..a6bcabcea71 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/files/FilesGenerate.java @@ -0,0 +1,55 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.test.tests.files; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.File; + +import org.junit.Test; + +import ch.njol.skript.Skript; +import ch.njol.skript.SkriptConfig; + +/** + * Ensures that the default files from Skript are generated. + */ +public class FilesGenerate { + + @Test + public void checkFiles() { + Skript skript = Skript.getInstance(); + File dataFolder = skript.getDataFolder(); + assertTrue(skript.getScriptsFolder().exists()); + assertTrue(skript.getScriptsFolder().isDirectory()); + assertTrue(new File(dataFolder, "config.sk").exists()); + assertTrue(new File(dataFolder, "features.sk").exists()); + File lang = new File(dataFolder, "lang"); + assertTrue(lang.exists()); + assertTrue(lang.isDirectory()); + } + + @Test + @SuppressWarnings("deprecation") + public void checkConfigurationVersion() { + assertEquals(SkriptConfig.getConfig().get("version"), Skript.getInstance().getDescription().getVersion()); + } + +} diff --git a/src/test/java/org/skriptlang/skript/test/tests/files/package-info.java b/src/test/java/org/skriptlang/skript/test/tests/files/package-info.java new file mode 100644 index 00000000000..f7a382d74b5 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/files/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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +@NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) +package org.skriptlang.skript.test.tests.files; + +import org.eclipse.jdt.annotation.DefaultLocation; +import org.eclipse.jdt.annotation.NonNullByDefault; + From 157b896bdf0ad55da41deaf9815d102763acec9a Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Fri, 17 Mar 2023 03:04:49 -0600 Subject: [PATCH 274/619] Adapt consistancy in dependabot.yml (#5442) --- .github/dependabot.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 7df8e33c9b1..191b0e340d9 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,13 +3,13 @@ updates: - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "daily" + interval: "weekly" labels: - "dependencies" - - package-ecosystem: gradle + - package-ecosystem: "gradle" directory: "/" schedule: - interval: daily + interval: "weekly" labels: - "dependencies" open-pull-requests-limit: 10 From f858c4a4bf1955b4afdc108ffb5c1f7d5fdf2b07 Mon Sep 17 00:00:00 2001 From: sovdee <ethan.sovde@mail.utoronto.ca> Date: Fri, 17 Mar 2023 06:53:37 -0400 Subject: [PATCH 275/619] Adds Explicit ItemStack Support to ExprItemAmount, fixing issue with item amount of variables. (#5513) --- .../skript/expressions/ExprItemAmount.java | 91 ++++++++++--------- .../551-item amount on itemstacks.sk | 27 ++++++ 2 files changed, 76 insertions(+), 42 deletions(-) create mode 100644 src/test/skript/tests/regressions/551-item amount on itemstacks.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprItemAmount.java b/src/main/java/ch/njol/skript/expressions/ExprItemAmount.java index 824f4448c0c..8c146d0b448 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprItemAmount.java +++ b/src/main/java/ch/njol/skript/expressions/ExprItemAmount.java @@ -19,7 +19,6 @@ package ch.njol.skript.expressions; import ch.njol.skript.aliases.ItemType; -import ch.njol.skript.classes.Changer; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; @@ -29,6 +28,7 @@ import ch.njol.skript.util.slot.Slot; import ch.njol.util.coll.CollectionUtils; import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; import org.eclipse.jdt.annotation.Nullable; @Name("Item Amount") @@ -36,64 +36,72 @@ @Examples("send \"You have got %item amount of player's tool% %player's tool% in your hand!\" to player") @Since("2.2-dev24") public class ExprItemAmount extends SimplePropertyExpression<Object, Long> { - - static { - register(ExprItemAmount.class, Long.class, "item[[ ]stack] (amount|size|number)", "slots/itemtypes"); - } - + static { + register(ExprItemAmount.class, Long.class, "item[[ ]stack] (amount|size|number)", "slots/itemtypes/itemstacks"); + } + @Override public Long convert(final Object item) { - return (long) (item instanceof ItemType ? ((ItemType) item).getAmount() : ((Slot) item).getAmount()); + if (item instanceof ItemType) { + return (long) ((ItemType) item).getAmount(); + } else if (item instanceof Slot) { + return (long) ((Slot) item).getAmount(); + } else { + return (long) ((ItemStack) item).getAmount(); + } } - + @Override - public @Nullable Class<?>[] acceptChange(Changer.ChangeMode mode) { - return (mode != ChangeMode.REMOVE_ALL) ? CollectionUtils.array(Number.class) : null; - } + @Nullable + public Class<?>[] acceptChange(ChangeMode mode) { + switch (mode) { + case SET: + case ADD: + case RESET: + case DELETE: + case REMOVE: + return CollectionUtils.array(Long.class); + } + return null; + } - @Override - public void change(Event event, @Nullable Object[] delta, Changer.ChangeMode mode) { - int amount = delta != null ? ((Number) delta[0]).intValue() : 0; - switch (mode) { - case ADD: - for (Object obj : getExpr().getArray(event)) + @Override + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + int amount = delta != null ? ((Number) delta[0]).intValue() : 0; + switch (mode) { + case REMOVE: + amount = -amount; + // fall through + case ADD: + for (Object obj : getExpr().getArray(event)) if (obj instanceof ItemType) { ItemType item = ((ItemType) obj); item.setAmount(item.getAmount() + amount); - } else { + } else if (obj instanceof Slot) { Slot slot = ((Slot) obj); slot.setAmount(slot.getAmount() + amount); + } else { + ItemStack item = ((ItemStack) obj); + item.setAmount(item.getAmount() + amount); } - break; - case SET: + break; + case RESET: + case DELETE: + amount = 1; + // fall through + case SET: for (Object obj : getExpr().getArray(event)) - if (obj instanceof ItemType) + if (obj instanceof ItemType) { ((ItemType) obj).setAmount(amount); - else + } else if (obj instanceof Slot) { ((Slot) obj).setAmount(amount); - break; - case REMOVE: - for (Object obj : getExpr().getArray(event)) - if (obj instanceof ItemType) { - ItemType item = ((ItemType) obj); - item.setAmount(item.getAmount() - amount); } else { - Slot slot = ((Slot) obj); - slot.setAmount(slot.getAmount() - amount); + ((ItemStack) obj).setAmount(amount); } - break; - case REMOVE_ALL: - case RESET: - case DELETE: - for (Object obj : getExpr().getArray(event)) - if (obj instanceof ItemType) - ((ItemType) obj).setAmount(1); - else - ((Slot) obj).setAmount(1); break; - } - } + } + } @Override public Class<? extends Long> getReturnType() { @@ -104,5 +112,4 @@ public Class<? extends Long> getReturnType() { protected String getPropertyName() { return "item[[ ]stack] (amount|size|number)"; } - } diff --git a/src/test/skript/tests/regressions/551-item amount on itemstacks.sk b/src/test/skript/tests/regressions/551-item amount on itemstacks.sk new file mode 100644 index 00000000000..809f5497f94 --- /dev/null +++ b/src/test/skript/tests/regressions/551-item amount on itemstacks.sk @@ -0,0 +1,27 @@ +test "item amount of itemstack": + set {_loc} to spawn of world "world" + set {_old-block} to type of block at {_loc} + set block at {_loc} to chest + add 10 stone to inventory of block at {_loc} + set {_item} to slot 0 of inventory of block at {_loc} + assert item amount of {_item} is 10 with "Amount of itemstack is not 10: %{_amount}%" + remove 1 from item amount of {_item} + assert item amount of {_item} is 9 with "Amount of itemstack is not 9: %{_amount}%" + set block at {_loc} to {_old-block} + +test "item amount of itemtype": + set {_item} to 10 stone + set {_amount} to item amount of {_item} + assert item amount of {_item} is 10 with "Amount of itemtype is not 10: %{_amount}%" + remove 1 from item amount of {_item} + assert item amount of {_item} is 9 with "Amount of itemtype is not 9: %{_amount}%" + +test "item amount of slot": + set {_loc} to spawn of world "world" + set {_old-block} to type of block at {_loc} + set block at {_loc} to chest + add 10 stone to inventory of block at {_loc} + assert item amount of (slot 0 of inventory of block at {_loc}) is 10 with "Amount of slot is not 10: %{_amount}%" + remove 1 from item amount of (slot 0 of inventory of block at {_loc}) + assert item amount of (slot 0 of inventory of block at {_loc}) is 9 with "Amount of slot is not 9: %{_amount}%" + set block at {_loc} to {_old-block} From f8f0437f9a222c1094784b465c0961c36ad16db2 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Sat, 18 Mar 2023 06:04:23 +0300 Subject: [PATCH 276/619] =?UTF-8?q?=F0=9F=9A=80=20Add=20Structure=20Suppor?= =?UTF-8?q?t=20to=20Docs=20(#5523)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/ch/njol/skript/Skript.java | 4 +- .../ch/njol/skript/doc/HTMLGenerator.java | 49 ++++++++++++++++++- .../ch/njol/skript/lang/SkriptEventInfo.java | 2 + .../skript/structures/StructFunction.java | 1 + .../njol/skript/structures/StructOptions.java | 2 + .../skript/structures/StructVariables.java | 1 + .../skript/lang/structure/Structure.java | 1 - 7 files changed, 56 insertions(+), 4 deletions(-) diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 29bef4366c2..4338a557a5d 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -1438,7 +1438,7 @@ public boolean check(final @Nullable ExpressionInfo<?, ?> i) { // ================ EVENTS ================ private static final List<StructureInfo<? extends Structure>> structures = new ArrayList<>(10); - + /** * Registers an event. * @@ -1469,7 +1469,7 @@ public static <E extends SkriptEvent> SkriptEventInfo<E> registerEvent(String na String[] transformedPatterns = new String[patterns.length]; for (int i = 0; i < patterns.length; i++) - transformedPatterns[i] = "[on] " + SkriptEvent.fixPattern(patterns[i]) + " [with priority (lowest|low|normal|high|highest|monitor)]"; + transformedPatterns[i] = "[on] " + SkriptEvent.fixPattern(patterns[i]) + SkriptEventInfo.EVENT_PRIORITY_SYNTAX; SkriptEventInfo<E> r = new SkriptEventInfo<>(name, transformedPatterns, c, originClassPath, events); structures.add(r); diff --git a/src/main/java/ch/njol/skript/doc/HTMLGenerator.java b/src/main/java/ch/njol/skript/doc/HTMLGenerator.java index 39ead176641..0da3679904c 100644 --- a/src/main/java/ch/njol/skript/doc/HTMLGenerator.java +++ b/src/main/java/ch/njol/skript/doc/HTMLGenerator.java @@ -36,8 +36,10 @@ import com.google.common.collect.Lists; import com.google.common.io.Files; import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang.WordUtils; import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.entry.EntryData; +import org.skriptlang.skript.lang.entry.EntryValidator; +import org.skriptlang.skript.lang.structure.StructureInfo; import java.io.File; import java.io.IOException; @@ -50,6 +52,7 @@ import java.util.List; import java.util.Locale; import java.util.regex.Pattern; +import java.util.stream.Collectors; /** * Template engine, primarily used for generating Skript documentation @@ -274,6 +277,21 @@ else if (!filesInside.getName().matches("(?i)(.*)\\.(html?|js|css|json)")) { String genType = genParams[0]; boolean isDocsPage = genType.equals("docs"); + if (genType.equals("structures") || isDocsPage) { + + for (Iterator<StructureInfo<?>> it = sortedAnnotatedIterator( + (Iterator) Skript.getStructures().stream().filter(structure -> structure.getClass() == StructureInfo.class).iterator()); + it.hasNext(); ) { + + StructureInfo<?> info = it.next(); + assert info != null; + if (info.c.getAnnotation(NoDoc.class) != null) + continue; + String desc = generateAnnotated(descTemp, info, generated.toString(), "Structure"); + generated.append(desc); + } + } + if (genType.equals("expressions") || isDocsPage) { for (Iterator<ExpressionInfo<?,?>> it = sortedAnnotatedIterator((Iterator) Skript.getExpressions()); it.hasNext(); ) { ExpressionInfo<?,?> info = it.next(); @@ -496,6 +514,25 @@ private String generateAnnotated(String descTemp, SyntaxElementInfo<?> info, @Nu // New Elements desc = handleIf(desc, "${if new-element}", NEW_TAG_PATTERN.matcher((since != null ? since.value() : "")).find()); + // Structure - EntryData + if (info instanceof StructureInfo) { + EntryValidator entryValidator = ((StructureInfo<?>) info).entryValidator; + List<EntryData<?>> entryDataList = new ArrayList<>(); + if (entryValidator != null) + entryDataList.addAll(entryValidator.getEntryData()); + + // TODO add type of entrydata like boolean/string/section etc. + desc = handleIf(desc, "${if structure-optional-entrydata}", entryValidator != null); + desc = desc.replace("${element.structure-optional-entrydata}", entryValidator == null ? "" : Joiner.on(", ").join(entryDataList.stream().filter(EntryData::isOptional).map(EntryData::getKey).collect(Collectors.toList()))); + + desc = handleIf(desc, "${if structure-required-entrydata}", entryValidator != null); + desc = desc.replace("${element.structure-required-entrydata}", entryValidator == null ? "" : Joiner.on(", ").join(entryDataList.stream().filter(entryData -> !entryData.isOptional()).map(EntryData::getKey).collect(Collectors.toList()))); + } else { + desc = handleIf(desc, "${if structure-optional-entrydata}", false); + desc = handleIf(desc, "${if structure-required-entrydata}", false); + + } + // Type desc = desc.replace("${element.type}", type); @@ -604,6 +641,9 @@ private String generateEvent(String descTemp, SkriptEventInfo<?> info, @Nullable // Return Type desc = handleIf(desc, "${if return-type}", false); + desc = handleIf(desc, "${if structure-optional-entrydata}", false); + desc = handleIf(desc, "${if structure-required-entrydata}", false); + // Generate Templates List<String> toGen = Lists.newArrayList(); int generate = desc.indexOf("${generate"); @@ -623,6 +663,7 @@ private String generateEvent(String descTemp, SkriptEventInfo<?> info, @Nullable for (String line : getDefaultIfNullOrEmpty(info.patterns, "Missing patterns.")) { assert line != null; line = cleanPatterns(line); + line = line.replace(SkriptEventInfo.EVENT_PRIORITY_SYNTAX, ""); // replace priority syntax in event syntaxes String parsed = pattern.replace("${element.pattern}", line); patterns.append(parsed); } @@ -704,6 +745,9 @@ private String generateClass(String descTemp, ClassInfo<?> info, @Nullable Strin // Return Type desc = handleIf(desc, "${if return-type}", false); + desc = handleIf(desc, "${if structure-optional-entrydata}", false); + desc = handleIf(desc, "${if structure-required-entrydata}", false); + // Generate Templates List<String> toGen = Lists.newArrayList(); int generate = desc.indexOf("${generate"); @@ -788,6 +832,9 @@ private String generateFunction(String descTemp, JavaFunction<?> info) { // Type desc = desc.replace("${element.type}", "Function"); + desc = handleIf(desc, "${if structure-optional-entrydata}", false); + desc = handleIf(desc, "${if structure-required-entrydata}", false); + // Generate Templates List<String> toGen = Lists.newArrayList(); int generate = desc.indexOf("${generate"); diff --git a/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java b/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java index 1151a073378..3581e655d70 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java +++ b/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java @@ -28,6 +28,8 @@ import ch.njol.skript.SkriptAPIException; public final class SkriptEventInfo<E extends SkriptEvent> extends StructureInfo<E> { + + public static final String EVENT_PRIORITY_SYNTAX = " [with priority (lowest|low|normal|high|highest|monitor)]"; public Class<? extends Event>[] events; public final String name; diff --git a/src/main/java/ch/njol/skript/structures/StructFunction.java b/src/main/java/ch/njol/skript/structures/StructFunction.java index 608e0dcd5f3..9b52d4fd94e 100644 --- a/src/main/java/ch/njol/skript/structures/StructFunction.java +++ b/src/main/java/ch/njol/skript/structures/StructFunction.java @@ -46,6 +46,7 @@ @Examples({ "function sayMessage(message: text):", "\tbroadcast {_message} # our message argument is available in '{_message}'", + "", "local function giveApple(amount: number) :: item:", "\treturn {_amount} of apple" }) diff --git a/src/main/java/ch/njol/skript/structures/StructOptions.java b/src/main/java/ch/njol/skript/structures/StructOptions.java index 070422979ed..574592e5c96 100644 --- a/src/main/java/ch/njol/skript/structures/StructOptions.java +++ b/src/main/java/ch/njol/skript/structures/StructOptions.java @@ -49,11 +49,13 @@ @Examples({ "options:", "\tno_permission: You're missing the required permission to execute this command!", + "", "command /ping:", "\tpermission: command.ping", "\tpermission message: {@no_permission}", "\ttrigger:", "\t\tmessage \"Pong!\"", + "", "command /pong:", "\tpermission: command.pong", "\tpermission message: {@no_permission}", diff --git a/src/main/java/ch/njol/skript/structures/StructVariables.java b/src/main/java/ch/njol/skript/structures/StructVariables.java index d0d49c5a8c7..06326b2eaff 100644 --- a/src/main/java/ch/njol/skript/structures/StructVariables.java +++ b/src/main/java/ch/njol/skript/structures/StructVariables.java @@ -68,6 +68,7 @@ "variables:", "\t{joins} = 0", "\t{balance::%player%} = 0", + "", "on join:", "\tadd 1 to {joins}", "\tmessage \"Your balance is %{balance::%player%}%\"", diff --git a/src/main/java/org/skriptlang/skript/lang/structure/Structure.java b/src/main/java/org/skriptlang/skript/lang/structure/Structure.java index 8e0a0d9cddb..1a787c366a8 100644 --- a/src/main/java/org/skriptlang/skript/lang/structure/Structure.java +++ b/src/main/java/org/skriptlang/skript/lang/structure/Structure.java @@ -51,7 +51,6 @@ * The values of these entries can be obtained by parsing the Structure's sub{@link Node}s * through registered {@link EntryData}. */ -// TODO STRUCTURE add Structures to docs public abstract class Structure implements SyntaxElement, Debuggable { /** From 7c0adb096963ec959c3119893cd8d730f13b5699 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Fri, 17 Mar 2023 23:49:19 -0400 Subject: [PATCH 277/619] Revert currentEvents nullability changes (#5534) --- src/main/java/ch/njol/skript/ScriptLoader.java | 8 +------- .../skript/lang/parser/ParserInstance.java | 18 +++++++----------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/src/main/java/ch/njol/skript/ScriptLoader.java b/src/main/java/ch/njol/skript/ScriptLoader.java index f32be7414e3..14cd0b3e628 100644 --- a/src/main/java/ch/njol/skript/ScriptLoader.java +++ b/src/main/java/ch/njol/skript/ScriptLoader.java @@ -29,7 +29,6 @@ import ch.njol.skript.lang.TriggerItem; import ch.njol.skript.lang.TriggerSection; import ch.njol.skript.lang.parser.ParserInstance; -import ch.njol.skript.lang.util.ContextlessEvent; import ch.njol.skript.log.CountingLogHandler; import ch.njol.skript.log.LogEntry; import ch.njol.skript.log.RetainingLogHandler; @@ -45,7 +44,6 @@ import ch.njol.util.NonNullPair; import ch.njol.util.OpenCloseable; import ch.njol.util.StringUtils; -import ch.njol.util.coll.CollectionUtils; import org.bukkit.Bukkit; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @@ -1217,11 +1215,7 @@ public static String getCurrentEventName() { @SafeVarargs @Deprecated public static void setCurrentEvent(String name, @Nullable Class<? extends Event>... events) { - if (events.length == 0) { - getParser().setCurrentEvent(name, CollectionUtils.array(ContextlessEvent.class)); - } else { - getParser().setCurrentEvent(name, events); - } + getParser().setCurrentEvent(name, events); } /** diff --git a/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java b/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java index 9824e069acc..86d6a209d08 100644 --- a/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java +++ b/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java @@ -26,11 +26,9 @@ import ch.njol.skript.lang.SkriptEvent; import ch.njol.skript.lang.SkriptParser; import ch.njol.skript.lang.TriggerSection; -import ch.njol.skript.lang.util.ContextlessEvent; import ch.njol.skript.log.HandlerList; import ch.njol.skript.structures.StructOptions.OptionsData; import ch.njol.util.Kleenean; -import ch.njol.util.Validate; import ch.njol.util.coll.CollectionUtils; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @@ -176,7 +174,8 @@ public final boolean isCurrentStructure(Class<? extends Structure>... structureC @Nullable private String currentEventName; - private Class<? extends Event>[] currentEvents = CollectionUtils.array(ContextlessEvent.class); + + private Class<? extends Event> @Nullable [] currentEvents = null; public void setCurrentEventName(@Nullable String currentEventName) { this.currentEventName = currentEventName; @@ -190,17 +189,14 @@ public String getCurrentEventName() { /** * @param currentEvents The events that may be present during execution. * An instance of the events present in the provided array MUST be used to execute any loaded items. - * If items are to be loaded without context, use {@link ContextlessEvent}. */ - public void setCurrentEvents(Class<? extends Event>[] currentEvents) { - Validate.isTrue(currentEvents.length != 0, "'currentEvents' may not be empty!"); + public void setCurrentEvents(Class<? extends Event> @Nullable [] currentEvents) { this.currentEvents = currentEvents; getDataInstances().forEach(data -> data.onCurrentEventsChange(currentEvents)); } @SafeVarargs - public final void setCurrentEvent(String name, Class<? extends Event>... events) { - Validate.isTrue(events.length != 0, "'events' may not be empty!"); + public final void setCurrentEvent(String name, @Nullable Class<? extends Event>... events) { currentEventName = name; setCurrentEvents(events); setHasDelayBefore(Kleenean.FALSE); @@ -208,11 +204,11 @@ public final void setCurrentEvent(String name, Class<? extends Event>... events) public void deleteCurrentEvent() { currentEventName = null; - setCurrentEvents(CollectionUtils.array(ContextlessEvent.class)); + setCurrentEvents(null); setHasDelayBefore(Kleenean.FALSE); } - public Class<? extends Event>[] getCurrentEvents() { + public Class<? extends Event> @Nullable [] getCurrentEvents() { return currentEvents; } @@ -430,7 +426,7 @@ protected final ParserInstance getParser() { @Deprecated public void onCurrentScriptChange(@Nullable Config currentScript) { } - public void onCurrentEventsChange(Class<? extends Event>[] currentEvents) { } + public void onCurrentEventsChange(Class<? extends Event> @Nullable [] currentEvents) { } } From d2e7ce9084fbc7e1354da424dc66baf0d097121d Mon Sep 17 00:00:00 2001 From: Kiip <25848425+kiip1@users.noreply.github.com> Date: Sat, 18 Mar 2023 19:32:52 +0100 Subject: [PATCH 278/619] Fix #5532 (#5535) Fix the issue --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index eb5f8c75f3f..c5b5b0ac580 100644 --- a/build.gradle +++ b/build.gradle @@ -220,7 +220,7 @@ void createTestTask(String name, String desc, String environments, int javaVersi project.findProperty('verbosity') ?: "null" ] - if (modifiers.contains(Modifiers.PROFILE)) { + if (!gradle.taskGraph.hasTask(":tasks") && !gradle.startParameter.dryRun && modifiers.contains(Modifiers.PROFILE)) { if (!project.hasProperty('profiler')) throw new MissingPropertyException('Add parameter -Pprofiler=<path to profiler>', 'profiler', String.class) From 0bd03f9a6bda7d0a226d5dd9f18118fa76ada087 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Mon, 20 Mar 2023 19:12:19 -0600 Subject: [PATCH 279/619] Fix unrelated default variables (#5529) --- .../skript/structures/StructVariables.java | 14 ++++++- .../skript/tests/misc/default variables.sk | 38 ++++++++++++------- 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/src/main/java/ch/njol/skript/structures/StructVariables.java b/src/main/java/ch/njol/skript/structures/StructVariables.java index 06326b2eaff..b6810c4812e 100644 --- a/src/main/java/ch/njol/skript/structures/StructVariables.java +++ b/src/main/java/ch/njol/skript/structures/StructVariables.java @@ -96,7 +96,10 @@ public DefaultVariables(Collection<NonNullPair<String, Object>> variables) { public void add(String variable, Class<?>... hints) { if (hints == null || hints.length <= 0) return; - if (CollectionUtils.containsAll(hints, Object.class)) // Ignore useless type hint + 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()) return; this.hints.getFirst().put(variable, hints); } @@ -152,8 +155,15 @@ public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResu } String name = n.getKey().toLowerCase(Locale.ENGLISH); - if (name.startsWith("{") && name.endsWith("}")) + if (name.startsWith("{") && name.endsWith("}")) { name = name.substring(1, name.length() - 1); + } else { + // TODO deprecated, remove this ability soon. + Skript.warning( + "It is suggested to use brackets around the name of a variable. Example: {example::%player%} = 5\n" + + "Excluding brackets is deprecated, meaning this warning will become an error in the future." + ); + } if (name.startsWith(Variable.LOCAL_VARIABLE_TOKEN)) { Skript.error("'" + name + "' cannot be a local variable in default variables structure"); diff --git a/src/test/skript/tests/misc/default variables.sk b/src/test/skript/tests/misc/default variables.sk index 75006862736..dd420c74240 100644 --- a/src/test/skript/tests/misc/default variables.sk +++ b/src/test/skript/tests/misc/default variables.sk @@ -1,28 +1,38 @@ variables: - {testing variables1::%entity%} = 1337 - {testing variables2::%object%} = 1337 - {testing variables3::%entity%::%object%} = 1337 + {testing::variables1::%entity%} = 1337 + {testing::variables2::%object%} = 1337 + {testing::variables3::%entity%::%object%} = 1337 + testing::no brackets::%living entity% = "String" + testing::variables4 = 1.6900000000001 + {testing::regression::%offlineplayer%} = 1337 on test "default variables": spawn a pig at spawn of world "world" - assert {testing variables1::%last spawned pig%} is set with "default variable 1 was not set" - assert {testing variables1::%last spawned pig%} is 1337 with "default variable 1 failed: Value with pig = %{testing variables1::%last spawned pig%}%" + assert {testing::variables1::%last spawned pig%} is 1337 with "default variable 1 failed" set {_string} to "empty string" set {_local} to {_string} - assert {_local} is {_string} with "Local variable failed to copy another local variable." + assert {_local} is {_string} with "Local variable failed to copy another local variable" - assert {testing variables2::%{_string}%} is set with "default variable 2 was not set" - assert {testing variables2::%{_string}%} is 1337 with "default variable 2 failed: Value with string = %{testing variables2::%{_string}%}%" + assert {testing::variables2::%{_string}%} is 1337 with "default variable 2 failed: Value with string" + assert {testing::variables3::%last spawned pig%::%{_string}%} is 1337 with "default variable 3 failed: Value with entity and string" + assert {testing::no brackets::%last spawned pig%} is "String" with "default variable 4 failed: Value with entity and string" + assert {testing::variables4} is 1.6900000000001 with "default variable 5 failed: Value with no entity and double" - assert {testing variables3::%last spawned pig%::%{_string}%} is set with "default variable 3 was not set" - assert {testing variables3::%last spawned pig%::%{_string}%} is 1337 with "default variable 3 failed: Value with entity and string = %{testing variables3::%last spawned pig%::%{_string}%}%" + set {_player} to "Njol" parsed as offlineplayer + assert {testing::regression::%{_player}%} is 1337 with "default variable 6 failed: Value with player and double" - delete last spawned pig - delete {testing variables1::*}, {testing variables2::*} and {testing variables3::*} + # If this fails, the scope of the default variables was not set, and it shouldn't be set for undefined, unrelated default variables. + set {testing::unrelated::%{_player}%} to true + assert {testing::unrelated::%{_player}%} is true with "unrelated variable to default variables conflict" + + delete {testing::*} + assert {testing::*} is not set with "Failed to delete default variables" - assert {testing variables1::*}, {testing variables2::*} and {testing variables3::*} are not set with "Failed to delete default variables." + delete last spawned pig + clear all entities +# Fail over if something happens. on script unload: - delete {testing variables1::*}, {testing variables2::*} and {testing variables3::*} + delete {testing::*} From 97a7c71c0822e0af67906bb4b8dd16694ec3d2fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Mar 2023 02:35:01 -0600 Subject: [PATCH 280/619] Bump com.github.johnrengelman.shadow from 8.1.0 to 8.1.1 (#5540) Bumps com.github.johnrengelman.shadow from 8.1.0 to 8.1.1. --- updated-dependencies: - dependency-name: com.github.johnrengelman.shadow dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index c5b5b0ac580..5782a98ceee 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ import org.apache.tools.ant.filters.ReplaceTokens import java.time.LocalTime plugins { - id 'com.github.johnrengelman.shadow' version '8.1.0' + id 'com.github.johnrengelman.shadow' version '8.1.1' id 'com.github.hierynomus.license' version '0.16.1' id 'maven-publish' id 'java' From 671d533d20905588f546c44f3b406b3c1258d210 Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Tue, 21 Mar 2023 12:00:58 +0300 Subject: [PATCH 281/619] Fix Raw String of non-literal (#5516) --- .../skript/expressions/ExprRawString.java | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprRawString.java b/src/main/java/ch/njol/skript/expressions/ExprRawString.java index d3ea952210f..6b9d907da1a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprRawString.java +++ b/src/main/java/ch/njol/skript/expressions/ExprRawString.java @@ -23,21 +23,20 @@ import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; -import ch.njol.skript.expressions.base.PropertyExpression; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionList; import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.VariableString; import ch.njol.skript.lang.util.SimpleExpression; -import ch.njol.skript.util.LiteralUtils; +import ch.njol.skript.util.SkriptColor; import ch.njol.util.Kleenean; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; +import java.util.regex.Pattern; @Name("Raw String") @Description("Returns the string without formatting (colors etc.) and without stripping them from it, " + @@ -46,6 +45,8 @@ @Since("2.7") public class ExprRawString extends SimpleExpression<String> { + private static final Pattern HEX_PATTERN = Pattern.compile("(?i)&x((?:&\\p{XDigit}){6})"); + static { Skript.registerExpression(ExprRawString.class, String.class, ExpressionType.COMBINED, "raw %strings%"); } @@ -71,14 +72,21 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye } @Override - protected String[] get(Event e) { + protected String[] get(Event event) { List<String> strings = new ArrayList<>(); for (Expression<? extends String> message : messages) { if (message instanceof VariableString) { - strings.add(((VariableString) message).toUnformattedString(e)); + strings.add(((VariableString) message).toUnformattedString(event)); continue; } - strings.addAll(Arrays.asList(message.getArray(e))); + for (String string : message.getArray(event)) { + String raw = SkriptColor.replaceColorChar(string); + if (raw.toLowerCase().contains("&x")) { + raw = HEX_PATTERN.matcher(raw).replaceAll(matchResult -> + "<#" + matchResult.group(1).replace("&", "") + '>'); + } + strings.add(raw); + } } return strings.toArray(new String[0]); } @@ -97,4 +105,5 @@ public Class<? extends String> getReturnType() { public String toString(@Nullable Event e, boolean debug) { return "raw " + expr.toString(e, debug); } + } From a1469bea672a81c0e75ff55a4625ba186b51d9e3 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Wed, 22 Mar 2023 05:16:17 -0600 Subject: [PATCH 282/619] Update to 1.19.4 (#5527) --- build.gradle | 4 +-- gradle.properties | 2 +- .../java/ch/njol/skript/entity/BoatData.java | 1 - .../njol/skript/entity/SimpleEntityData.java | 21 ++++++++++++--- .../skript/util/visual/VisualEffects.java | 17 +++++++----- src/main/resources/lang/default.lang | 27 ++++++++++++++++++- .../environments/java17/paper-1.19.4.json | 17 ++++++++++++ 7 files changed, 74 insertions(+), 15 deletions(-) create mode 100644 src/test/skript/environments/java17/paper-1.19.4.json diff --git a/build.gradle b/build.gradle index 5782a98ceee..b85cca78b57 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ allprojects { dependencies { shadow group: 'io.papermc', name: 'paperlib', version: '1.0.8' shadow group: 'org.bstats', name: 'bstats-bukkit', version: '3.0.1' - implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.19.3-R0.1-SNAPSHOT' + implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.19.4-R0.1-SNAPSHOT' implementation group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.700' implementation group: 'com.google.code.findbugs', name: 'findbugs', version: '3.0.1' implementation group: 'com.sk89q.worldguard', name: 'worldguard-legacy', version: '7.0.0-SNAPSHOT' @@ -229,7 +229,7 @@ void createTestTask(String name, String desc, String environments, int javaVersi } } -def latestEnv = 'java17/paper-1.19.3.json' +def latestEnv = 'java17/paper-1.19.4.json' def latestJava = 17 def oldestJava = 8 diff --git a/gradle.properties b/gradle.properties index 54899b7a0b6..f200caba110 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,5 +2,5 @@ groupid=ch.njol name=skript version=2.7.0-beta1 jarName=Skript.jar -testEnv=java17/paper-1.19.3 +testEnv=java17/paper-1.19.4 testEnvJavaVersion=17 diff --git a/src/main/java/ch/njol/skript/entity/BoatData.java b/src/main/java/ch/njol/skript/entity/BoatData.java index 277b9a6cc4b..dfeb1f22890 100644 --- a/src/main/java/ch/njol/skript/entity/BoatData.java +++ b/src/main/java/ch/njol/skript/entity/BoatData.java @@ -114,7 +114,6 @@ public boolean isSupertypeOf(EntityData<?> e) { private static final ItemType jungleBoat = Aliases.javaItemType("jungle boat"); private static final ItemType acaciaBoat = Aliases.javaItemType("acacia boat"); private static final ItemType darkOakBoat = Aliases.javaItemType("dark oak boat"); - public boolean isOfItemType(ItemType i){ if (i.getRandom() == null) diff --git a/src/main/java/ch/njol/skript/entity/SimpleEntityData.java b/src/main/java/ch/njol/skript/entity/SimpleEntityData.java index eef1aa16029..963244c9844 100644 --- a/src/main/java/ch/njol/skript/entity/SimpleEntityData.java +++ b/src/main/java/ch/njol/skript/entity/SimpleEntityData.java @@ -31,6 +31,7 @@ import org.bukkit.entity.Arrow; import org.bukkit.entity.Bat; import org.bukkit.entity.Blaze; +import org.bukkit.entity.BlockDisplay; import org.bukkit.entity.Camel; import org.bukkit.entity.CaveSpider; import org.bukkit.entity.ChestedHorse; @@ -39,6 +40,7 @@ import org.bukkit.entity.Cow; import org.bukkit.entity.Creature; import org.bukkit.entity.Damageable; +import org.bukkit.entity.Display; import org.bukkit.entity.Dolphin; import org.bukkit.entity.Donkey; import org.bukkit.entity.DragonFireball; @@ -70,7 +72,9 @@ import org.bukkit.entity.Husk; import org.bukkit.entity.Illager; import org.bukkit.entity.Illusioner; +import org.bukkit.entity.Interaction; import org.bukkit.entity.IronGolem; +import org.bukkit.entity.ItemDisplay; import org.bukkit.entity.ItemFrame; import org.bukkit.entity.LargeFireball; import org.bukkit.entity.LeashHitch; @@ -104,6 +108,7 @@ import org.bukkit.entity.SkeletonHorse; import org.bukkit.entity.Slime; import org.bukkit.entity.SmallFireball; +import org.bukkit.entity.Sniffer; import org.bukkit.entity.Snowball; import org.bukkit.entity.Snowman; import org.bukkit.entity.SpectralArrow; @@ -114,6 +119,7 @@ import org.bukkit.entity.Strider; import org.bukkit.entity.TNTPrimed; import org.bukkit.entity.Tadpole; +import org.bukkit.entity.TextDisplay; import org.bukkit.entity.ThrownExpBottle; import org.bukkit.entity.TippedArrow; import org.bukkit.entity.Trident; @@ -131,6 +137,7 @@ import org.bukkit.entity.Zoglin; import org.bukkit.entity.Zombie; import org.bukkit.entity.ZombieHorse; + import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; @@ -138,9 +145,6 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.yggdrasil.Fields; -/** - * @author Peter Güttinger - */ public class SimpleEntityData extends EntityData<Entity> { public final static class SimpleEntityDataInfo { @@ -296,7 +300,16 @@ private static void addSuperEntity(String codeName, Class<? extends Entity> enti if (Skript.isRunningMinecraft(1,19,3)) addSimpleEntity("camel", Camel.class); - + + if (Skript.isRunningMinecraft(1,19,4)) { + addSimpleEntity("sniffer", Sniffer.class); + addSimpleEntity("text display", TextDisplay.class); + addSimpleEntity("item display", ItemDisplay.class); + addSimpleEntity("block display", BlockDisplay.class); + addSimpleEntity("interaction", Interaction.class); + addSimpleEntity("display", Display.class); + } + // Register zombie after Husk and Drowned to make sure both work addSimpleEntity("zombie", Zombie.class); // Register squid after glow squid to make sure both work diff --git a/src/main/java/ch/njol/skript/util/visual/VisualEffects.java b/src/main/java/ch/njol/skript/util/visual/VisualEffects.java index ef2bfc57b90..529887c5660 100644 --- a/src/main/java/ch/njol/skript/util/visual/VisualEffects.java +++ b/src/main/java/ch/njol/skript/util/visual/VisualEffects.java @@ -40,7 +40,12 @@ import org.bukkit.potion.PotionEffectType; import org.eclipse.jdt.annotation.Nullable; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.stream.Stream; @@ -83,11 +88,11 @@ public static String getAllNames() { private static void generateTypes() { List<VisualEffectType> types = new ArrayList<>(); Stream.of(Effect.class, EntityEffect.class, Particle.class) - .map(Class::getEnumConstants) - .flatMap(Arrays::stream) - .map(VisualEffectType::of) - .filter(Objects::nonNull) - .forEach(types::add); + .map(Class::getEnumConstants) + .flatMap(Arrays::stream) + .map(VisualEffectType::of) + .filter(Objects::nonNull) + .forEach(types::add); for (VisualEffectType type : types) { String id = type.getId(); diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 0f167da60ff..5e660d62c89 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -261,6 +261,8 @@ biomes: # 1.19 biomes mangrove_swamp: mangrove swamp deep_dark: deep dark + # 1.19.4 + cherry_grove: cherry grove # -- Tree Types -- tree types: @@ -491,6 +493,15 @@ entities: dark oak boat: name: dark oak boat¦s pattern: dark oak boat(|1¦s) + mangrove boat: + name: mangrove boat¦s + pattern: mangrove boat(|1¦s) + bamboo raft: + name: bamboo raft¦s + pattern: bamboo (boat|raft)(|1¦s) + cherry boat: + name: cherry boat¦s + pattern: cherry [blossom] boat(|1¦s) blaze: name: blaze¦s pattern: blaze(|1¦s) @@ -1693,7 +1704,17 @@ visual effects: shriek: name: shriek @- pattern: shriek - + + # 1.19.4 particles + dripping_cherry_leaves: + name: dripping cherry leaves @- + pattern: dripping cherry leaves + falling_cherry_leaves: + name: falling cherry leaves @- + pattern: falling cherry leaves + landing_cherry_leaves: + name: landing cherry leaves @- + pattern: landing cherry leaves # -- Inventory Actions -- inventory actions: @@ -1764,6 +1785,10 @@ inventory types: composter: composter inventory @a chiseled_bookshelf: chiseled bookshelf @a, bookshelf @a + jukebox: jukebox @a + # The smithing new table is required for Skript to work, but this is a draft API for 1.20 + smithing_new: new smithing table @a, upgrade gear @a, upgrade gear table @a + # -- Spawn Reasons -- spawn reasons: bed: bed diff --git a/src/test/skript/environments/java17/paper-1.19.4.json b/src/test/skript/environments/java17/paper-1.19.4.json new file mode 100644 index 00000000000..0f6eb151404 --- /dev/null +++ b/src/test/skript/environments/java17/paper-1.19.4.json @@ -0,0 +1,17 @@ +{ + "name": "paper-1.19.4", + "resources": [ + {"source": "server.properties.generic", "target": "server.properties"} + ], + "paperDownloads": [ + { + "version": "1.19.4", + "target": "paperclip.jar" + } + ], + "skriptTarget": "plugins/Skript.jar", + "commandLine": [ + "-Dcom.mojang.eula.agree=true", + "-jar", "paperclip.jar", "--nogui" + ] +} From 30688c4bc147cdb160af32544acc57bcbf47e260 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Wed, 22 Mar 2023 05:23:16 -0600 Subject: [PATCH 283/619] Add debug effect (#5537) --- .../ch/njol/skript/test/runner/EffDebug.java | 155 ++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 src/main/java/ch/njol/skript/test/runner/EffDebug.java diff --git a/src/main/java/ch/njol/skript/test/runner/EffDebug.java b/src/main/java/ch/njol/skript/test/runner/EffDebug.java new file mode 100644 index 00000000000..0a624eb03f5 --- /dev/null +++ b/src/main/java/ch/njol/skript/test/runner/EffDebug.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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.test.runner; + +import java.util.Arrays; +import java.util.logging.Logger; + +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.NoDoc; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Condition; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.log.ParseLogHandler; +import ch.njol.skript.log.SkriptLogger; +import ch.njol.skript.util.LiteralUtils; +import ch.njol.util.Kleenean; + +@Name("Debug") +@Description({ + "Debug an expression by printing data of it to console.", + "Can also debug what class an input is parsed as for Conditions and Effects.", + "Useful for dealing with conflict debugging." +}) +@Since("INSERT VERSION") +@NoDoc +public class EffDebug extends Effect { + + static { + if (TestMode.ENABLED) + Skript.registerEffect(EffDebug.class, + "debug [:verbose] %objects%", + "debug-effect <.+>", + "debug-condition <.+>" + ); + } + + private Expression<?> expressions; + private boolean debug = Skript.debug(); + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (matchedPattern == 1) { + String string = parseResult.regexes.get(0).group(); + ParseLogHandler logHandler = SkriptLogger.startParseLogHandler(); + try { + Effect effect = Effect.parse(string, "Can't understand this effect: " + string); + if (effect == null) { + logHandler.printError(); + } else { + Logger logger = Skript.getInstance().getLogger(); + logger.info("--------------------"); + logger.info("Parsed '" + string + "' to be Effect " + effect.getClass().getName()); + logger.info("--------------------"); + logHandler.printLog(); + } + } finally { + logHandler.stop(); + } + return true; + } else if (matchedPattern == 2) { + String string = parseResult.regexes.get(0).group(); + ParseLogHandler logHandler = SkriptLogger.startParseLogHandler(); + try { + Condition conditon = Condition.parse(string, "Can't understand this conditon: " + string); + if (conditon == null) { + logHandler.printError(); + } else { + Logger logger = Skript.getInstance().getLogger(); + logger.info("--------------------"); + logger.info("Parsed '" + string + "' to be Condition " + conditon.getClass().getName()); + logger.info("--------------------"); + logHandler.printLog(); + } + } finally { + logHandler.stop(); + } + return true; + } + expressions = exprs[0]; + if (LiteralUtils.canInitSafely(expressions)) + expressions = LiteralUtils.defendExpression(expressions); + if (parseResult.hasTag("verbose")) + debug = true; + print(); + return true; + } + + @Override + protected void execute(Event event) { + if (expressions == null) + return; + print(event, debug); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "debug " + expressions.toString(event, debug); + } + + private void print() { + print(null, debug); + } + + private void print(@Nullable Event event, boolean debug) { + Skript.info("--------------------"); + Skript.info(event == null ? "PARSE TIME" : "RUNTIME"); + Skript.info("\tExpression " + expressions.getClass().getName()); + Skript.info("\ttoString: " + expressions.toString(event, debug)); + if (LiteralUtils.hasUnparsedLiteral(expressions)) { + Skript.info("EXPRESSION WAS UNPARSED LITERAL"); + Skript.info("--------------------"); + return; + } + Skript.info("\tChangers: " + Arrays.toString(expressions.getAcceptedChangeModes().entrySet().stream() + .map(entry -> entry.getValue().getClass().getSimpleName() + ":" + entry.getKey().name()) + .toArray(String[]::new))); + Skript.info("\tAnd: " + expressions.getAnd()); + Skript.info("\tReturn type: " + expressions.getReturnType()); + Skript.info("\tisDefault: " + expressions.isDefault()); + Skript.info("\tisSingle: " + expressions.isSingle()); + Skript.info("--------------------"); + if (expressions instanceof Literal) { + Skript.info("Literal Values: " + Arrays.toString(((Literal<?>) expressions).getArray())); + Skript.info("--------------------"); + } else if (event != null) { + Skript.info("Values: " + Arrays.toString(expressions.getArray(event))); + Skript.info("--------------------"); + } + } + +} From b413b6fe80041fde6954bbb6e8b151436bdb8e45 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Wed, 22 Mar 2023 16:07:25 -0600 Subject: [PATCH 284/619] Fix a few script tests (#5350) --- ...required in some places it shouldn't be.sk | 1 + .../tests/syntaxes/effects/EffReplace.sk | 2 +- .../syntaxes/expressions/ExprArrowsStuck.sk | 34 +++++++++---------- .../expressions/ExprEntityAttribute.sk | 34 +++++++++---------- .../syntaxes/expressions/ExprEntityTamer.sk | 10 +++--- .../syntaxes/structures/StructCommand.sk | 2 +- 6 files changed, 42 insertions(+), 41 deletions(-) diff --git a/src/test/skript/tests/regressions/590-escaping quotes is required in some places it shouldn't be.sk b/src/test/skript/tests/regressions/590-escaping quotes is required in some places it shouldn't be.sk index d8552d32207..cfa2aaa63e9 100644 --- a/src/test/skript/tests/regressions/590-escaping quotes is required in some places it shouldn't be.sk +++ b/src/test/skript/tests/regressions/590-escaping quotes is required in some places it shouldn't be.sk @@ -16,4 +16,5 @@ test "double quote parsing": assert {_abc%%} is not set with "simple variable with escaped percentage sign failed" assert {_abc%1 + 1%} is not set with "simple variable with expression failed" + suppress starting with expression warnings assert {_%subtext of "test" from characters 1 to 1%} is not set with "variable with expression with string failed" diff --git a/src/test/skript/tests/syntaxes/effects/EffReplace.sk b/src/test/skript/tests/syntaxes/effects/EffReplace.sk index bb40dc3c6d3..26c38b89690 100644 --- a/src/test/skript/tests/syntaxes/effects/EffReplace.sk +++ b/src/test/skript/tests/syntaxes/effects/EffReplace.sk @@ -43,7 +43,7 @@ test "replace strings": assert {_text::8} is "My name is bob and this is my friend bob!" with "replace first pattern 1 (case sensitivity enabled) (%{_text::7}%) failed" # Replacing in an ExpressionList - set {_list::*} to "1", "2", "12" + set {_list::*} to "1", "2", and "12" replace all "1" in {_list::1}, {_list::2} and {_list::3} with "(replaced)" assert {_list::1} is "(replaced)" with "Incorrect ExpressionList replacement value (1)" assert {_list::2} is "2" with "Incorrect ExpressionList replacement value (2)" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprArrowsStuck.sk b/src/test/skript/tests/syntaxes/expressions/ExprArrowsStuck.sk index ee22cfa5aae..9b41359b7af 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprArrowsStuck.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprArrowsStuck.sk @@ -1,18 +1,18 @@ test "arrows stuck": - spawn zombie at location(0, 64, 0, world "world") - set {_m} to last spawned zombie - assert arrows stuck in {_m} is 0 with "entity spawned with arrows stuck" - set arrows stuck in {_m} to 31 - assert arrows stuck in {_m} is 31 with "arrows stuck set failed" - add 5 to arrows stuck in {_m} - assert arrows stuck in {_m} is 36 with "arrows stuck add ##1 failed" - remove 10 from arrows stuck in {_m} - assert arrows stuck in {_m} is 26 with "arrows stuck remove ##1 failed" - remove 999 from arrows stuck in {_m} - assert arrows stuck in {_m} is 0 with "arrows stuck remove ##2 failed" - remove -2 from arrows stuck in {_m} - assert arrows stuck in {_m} is 2 with "arrows stuck remove ##3 failed" - add -1 to arrows stuck in {_m} - assert arrows stuck in {_m} is 1 with "arrows stuck add ##2 failed" - delete arrows stuck in {_m} - assert arrows stuck in {_m} is 0 with "arrows stuck delete failed" + spawn zombie at spawn of world "world": + assert arrows stuck in event-entity is 0 with "entity spawned with arrows stuck" + set arrows stuck in event-entity to 31 + assert arrows stuck in event-entity is 31 with "arrows stuck set failed" + add 5 to arrows stuck in event-entity + assert arrows stuck in event-entity is 36 with "arrows stuck add ##1 failed" + remove 10 from arrows stuck in event-entity + assert arrows stuck in event-entity is 26 with "arrows stuck remove ##1 failed" + remove 999 from arrows stuck in event-entity + assert arrows stuck in event-entity is 0 with "arrows stuck remove ##2 failed" + remove -2 from arrows stuck in event-entity + assert arrows stuck in event-entity is 2 with "arrows stuck remove ##3 failed" + add -1 to arrows stuck in event-entity + assert arrows stuck in event-entity is 1 with "arrows stuck add ##2 failed" + delete arrows stuck in event-entity + assert arrows stuck in event-entity is 0 with "arrows stuck delete failed" + delete event-entity diff --git a/src/test/skript/tests/syntaxes/expressions/ExprEntityAttribute.sk b/src/test/skript/tests/syntaxes/expressions/ExprEntityAttribute.sk index 6178b6d973d..730afc9d164 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprEntityAttribute.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprEntityAttribute.sk @@ -1,21 +1,21 @@ test "attributes 1": - spawn cow at location(0, 64, 0, world "world") - set {_m} to last spawned cow - assert movement speed attribute of {_m} is set with "attribute get failed" - set movement speed attribute of {_m} to 3.14 - assert movement speed attribute of {_m} is 3.14 with "attribute set failed" - add 5 to movement speed attribute of {_m} - assert movement speed attribute of {_m} is 8.14 with "attribute add failed" - remove 4 from movement speed attribute of {_m} - assert movement speed attribute of {_m} is 4.14 with "attribute remove ##1 failed" - remove 10 from movement speed attribute of {_m} - assert movement speed attribute of {_m} is -5.86 with "attribute remove ##2 failed" # Negative attribute values should be safe - delete movement speed attribute of {_m} - assert movement speed attribute of {_m} is 0 with "attribute delete failed" + spawn cow at spawn of world "world": + assert movement speed attribute of event-entity is set with "attribute get failed" + set movement speed attribute of event-entity to 3.14 + assert movement speed attribute of event-entity is 3.14 with "attribute set failed" + add 5 to movement speed attribute of event-entity + assert movement speed attribute of event-entity is 8.14 with "attribute add failed" + remove 4 from movement speed attribute of event-entity + assert movement speed attribute of event-entity is 4.14 with "attribute remove ##1 failed" + remove 10 from movement speed attribute of event-entity + assert movement speed attribute of event-entity is -5.86 with "attribute remove ##2 failed" # Negative attribute values should be safe + delete movement speed attribute of event-entity + assert movement speed attribute of event-entity is 0 with "attribute delete failed" + delete event-entity test "attributes 2" when minecraft version is not "1.9.4": # Test the Reset ChangeMode only on versions that have it - spawn cow at location(0, 65, 0, world "world") - set {_m} to last spawned cow - reset movement speed attribute of {_m} - assert movement speed attribute of {_m} is set with "attribute reset failed" # No need to compare with a fixed constant + spawn cow at spawn of world "world": + reset movement speed attribute of event-entity + assert movement speed attribute of event-entity is set with "attribute reset failed" # No need to compare with a fixed constant + delete event-entity diff --git a/src/test/skript/tests/syntaxes/expressions/ExprEntityTamer.sk b/src/test/skript/tests/syntaxes/expressions/ExprEntityTamer.sk index 93efd65f402..5c84c516ae7 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprEntityTamer.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprEntityTamer.sk @@ -1,6 +1,6 @@ test "entity tamer expression/condition": - spawn a wolf at spawn of world "world" - assert last spawned entity is tameable with "entity is tameable condition not passing, last spawned wolf is not tameable" - set owner of last spawned wolf to "Notch" parsed as offline player - assert "%owner of last spawned wolf%" = "Notch" with "Owner of last spawned wolf was not set" - delete last spawned wolf \ No newline at end of file + spawn a wolf at spawn of world "world": + assert event-entity is tameable with "entity is tameable condition not passing, last spawned wolf is not tameable" + set owner of event-entity to "Notch" parsed as offline player + assert "%owner of event-entity%" = "Notch" with "Owner of last spawned wolf was not set" + delete event-entity diff --git a/src/test/skript/tests/syntaxes/structures/StructCommand.sk b/src/test/skript/tests/syntaxes/structures/StructCommand.sk index b15168b0996..dd20198002a 100644 --- a/src/test/skript/tests/syntaxes/structures/StructCommand.sk +++ b/src/test/skript/tests/syntaxes/structures/StructCommand.sk @@ -1,6 +1,6 @@ test "commands": execute command "skriptcommand taco" - execute command "/somecommand burrito is tasty" + execute command "//somecommand burrito is tasty" command skriptcommand <text> [<text>] [<itemtype = %dirt block named "steve"%>]: trigger: From 6374841f8caa3bb398034b8ea8f5bd35e792b7cc Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Wed, 22 Mar 2023 16:51:29 -0600 Subject: [PATCH 285/619] Gradle auto apply correct JDK version (#5541) --- build.gradle | 11 ++++++++--- settings.gradle | 5 +++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index b85cca78b57..14fa0d5a392 100644 --- a/build.gradle +++ b/build.gradle @@ -41,9 +41,6 @@ dependencies { testShadow group: 'org.easymock', name: 'easymock', version: '5.1.0' } -compileJava.options.encoding = 'UTF-8' -compileTestJava.options.encoding = 'UTF-8' - task checkAliases { description 'Checks for the existence of the aliases.' doLast { @@ -233,9 +230,17 @@ def latestEnv = 'java17/paper-1.19.4.json' def latestJava = 17 def oldestJava = 8 +java { + toolchain.languageVersion.set(JavaLanguageVersion.of(latestJava)) +} + tasks.withType(JavaCompile).configureEach { options.compilerArgs += ['-source', '' + oldestJava, '-target', '' + oldestJava] } + +compileJava.options.encoding = 'UTF-8' +compileTestJava.options.encoding = 'UTF-8' + // Register different Skript testing tasks String environments = 'src/test/skript/environments/'; String env = project.property('testEnv') == null ? latestEnv : project.property('testEnv') + '.json' diff --git a/settings.gradle b/settings.gradle index 051b410bf2e..f48b9e264c6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,6 @@ + +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.4.0' +} + rootProject.name = 'Skript' From 4cd9133b35d1e05a7ad10d5cb79e710a23d81f60 Mon Sep 17 00:00:00 2001 From: sovdee <ethansovde@gmail.com> Date: Thu, 23 Mar 2023 01:25:29 -0400 Subject: [PATCH 286/619] Fix EffDrop overwriting values of merged xp orbs. (#5491) --- .../njol/skript/bukkitutil/PlayerUtils.java | 86 +++++++++-- .../java/ch/njol/skript/effects/EffDrop.java | 3 +- .../java/ch/njol/skript/entity/XpOrbData.java | 4 +- .../expressions/ExprTotalExperience.java | 138 ++++++++++++++++++ .../5491-xp orb merge overwrite.sk | 17 +++ .../expressions/ExprTotalExperience.sk | 15 ++ 6 files changed, 250 insertions(+), 13 deletions(-) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprTotalExperience.java create mode 100644 src/test/skript/tests/regressions/5491-xp orb merge overwrite.sk create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprTotalExperience.sk diff --git a/src/main/java/ch/njol/skript/bukkitutil/PlayerUtils.java b/src/main/java/ch/njol/skript/bukkitutil/PlayerUtils.java index 20c945ee4bb..2d020a08ec8 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/PlayerUtils.java +++ b/src/main/java/ch/njol/skript/bukkitutil/PlayerUtils.java @@ -18,21 +18,19 @@ */ package ch.njol.skript.bukkitutil; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - +import ch.njol.skript.Skript; +import ch.njol.skript.util.Task; +import com.google.common.collect.ImmutableList; import org.bukkit.Bukkit; import org.bukkit.GameMode; import org.bukkit.Material; import org.bukkit.entity.Player; import org.eclipse.jdt.annotation.Nullable; -import com.google.common.collect.ImmutableList; - -import ch.njol.skript.Skript; -import ch.njol.skript.util.Task; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; /** * Contains utility methods related to players @@ -94,4 +92,74 @@ public static boolean canEat(Player p, Material food) { return false; } + + /** + * Gets the experience points needed to reach the next level, starting at the given level. + * E.g. getLevelXP(30) returns the experience points needed to reach level 31 from level 30. + * + * @param level The starting level + * @return The experience points needed to reach the next level + */ + public static int getLevelXP(int level) { + if (level <= 15) + return (2 * level) + 7; + if (level <= 30) + return (5 * level) - 38; + return (9 * level) - 158; + } + + /** + * Gets the cumulative experience needed to reach the given level, but no further. + * E.g. getCumulativeXP(30) returns the experience points needed to reach level 30 from level 0. + * + * @param level The level to get the cumulative XP for + * @return The experience points needed to reach the given level + */ + public static int getCumulativeXP(int level) { + if (level <= 15) + return (level * level) + (6 * level); + if (level <= 30) + return (int) (2.5 * (level * level) - (40.5 * level) + 360); + return (int) (4.5 * (level * level) - (162.5 * level) + 2220); + } + + /** + * Gets the total experience points needed to reach the given level, including the given progress. + * E.g. getTotalXP(30, 0.5) returns the experience points needed to reach level 30 from level 0, and have a half-full xp bar. + * + * @param level The level to get the total XP of + * @param progress The progress towards the next level, between 0 and 1 + * @return The total experience points needed to reach the given level and progress + */ + public static int getTotalXP(int level, double progress) { + return (int) (getCumulativeXP(level) + getLevelXP(level) * progress); + } + + /** + * Gets the total experience points of the given player. + * + * @param player The player to get the total XP of + * @return The total experience points of the given player + */ + public static int getTotalXP(Player player) { + return getTotalXP(player.getLevel(), player.getExp()); + } + + /** + * Sets the total experience points of the given player. + * + * @param player The player to set the total XP of + * @param experience The total experience points to set + */ + public static void setTotalXP(Player player, int experience) { + int level = 0; + if (experience < 0) + experience = 0; + while (experience >= getLevelXP(level)) { + experience -= getLevelXP(level); + level++; + } + player.setLevel(level); + player.setExp((float) experience / getLevelXP(level)); + } } diff --git a/src/main/java/ch/njol/skript/effects/EffDrop.java b/src/main/java/ch/njol/skript/effects/EffDrop.java index 7efdd225162..72b7b7b0ca9 100644 --- a/src/main/java/ch/njol/skript/effects/EffDrop.java +++ b/src/main/java/ch/njol/skript/effects/EffDrop.java @@ -33,7 +33,6 @@ import ch.njol.skript.util.Experience; import ch.njol.util.Kleenean; import org.bukkit.Location; -import org.bukkit.Material; import org.bukkit.entity.Entity; import org.bukkit.entity.ExperienceOrb; import org.bukkit.entity.Item; @@ -80,7 +79,7 @@ public void execute(Event e) { for (Object o : os) { if (o instanceof Experience) { ExperienceOrb orb = l.getWorld().spawn(l, ExperienceOrb.class); - orb.setExperience(((Experience) o).getXP()); + orb.setExperience(((Experience) o).getXP() + orb.getExperience()); // ensure we maintain previous experience, due to spigot xp merging behavior EffSecSpawn.lastSpawned = orb; } else { if (o instanceof ItemStack) diff --git a/src/main/java/ch/njol/skript/entity/XpOrbData.java b/src/main/java/ch/njol/skript/entity/XpOrbData.java index 1b45fa9955b..8582d513c33 100644 --- a/src/main/java/ch/njol/skript/entity/XpOrbData.java +++ b/src/main/java/ch/njol/skript/entity/XpOrbData.java @@ -67,7 +67,7 @@ protected boolean match(final ExperienceOrb entity) { @Override public void set(final ExperienceOrb entity) { if (xp != -1) - entity.setExperience(xp); + entity.setExperience(xp + entity.getExperience()); } @Override @@ -77,7 +77,7 @@ public ExperienceOrb spawn(Location loc, @Nullable Consumer<ExperienceOrb> consu if (orb == null) return null; if (xp == -1) - orb.setExperience(1); + orb.setExperience(1 + orb.getExperience()); return orb; } diff --git a/src/main/java/ch/njol/skript/expressions/ExprTotalExperience.java b/src/main/java/ch/njol/skript/expressions/ExprTotalExperience.java new file mode 100644 index 00000000000..38138933994 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprTotalExperience.java @@ -0,0 +1,138 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.bukkitutil.PlayerUtils; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import org.bukkit.entity.Entity; +import org.bukkit.entity.ExperienceOrb; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Total Experience") +@Description({ + "The total experience, in points, of players or experience orbs.", + "Adding to a player's experience will trigger Mending, but setting their experience will not." +}) +@Examples({ + "set total experience of player to 100", + "", + "add 100 to player's experience", + "", + "if player's total experience is greater than 100:", + "\tset player's total experience to 0", + "\tgive player 1 diamond" +}) +@Since("INSERT VERSION") +public class ExprTotalExperience extends SimplePropertyExpression<Entity, Integer> { + + static { + register(ExprTotalExperience.class, Integer.class, "[total] experience", "entities"); + } + + @Override + @Nullable + public Integer convert(Entity entity) { + // experience orbs + if (entity instanceof ExperienceOrb) + return ((ExperienceOrb) entity).getExperience(); + + // players need special treatment + if (entity instanceof Player) + return PlayerUtils.getTotalXP(((Player) entity).getLevel(), ((Player) entity).getExp()); + + // invalid entity type + return null; + } + + @Override + @Nullable + public Class<?>[] acceptChange(ChangeMode mode) { + switch (mode) { + case ADD: + case REMOVE: + case SET: + case DELETE: + case RESET: + return new Class[]{Number.class}; + case REMOVE_ALL: + default: + return null; + } + } + + @Override + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + int change = delta == null ? 0 : ((Number) delta[0]).intValue(); + switch (mode) { + case RESET: + case DELETE: + // RESET and DELETE will have change = 0, so just fall through to SET + case SET: + if (change < 0) + change = 0; + for (Entity entity : getExpr().getArray(event)) { + if (entity instanceof ExperienceOrb) { + ((ExperienceOrb) entity).setExperience(change); + } else if (entity instanceof Player) { + PlayerUtils.setTotalXP((Player) entity, change); + } + } + break; + case REMOVE: + change = -change; + // fall through to ADD + case ADD: + int xp; + for (Entity entity : getExpr().getArray(event)) { + if (entity instanceof ExperienceOrb) { + //ensure we don't go below 0 + xp = ((ExperienceOrb) entity).getExperience() + change; + ((ExperienceOrb) entity).setExperience(Math.max(xp, 0)); + } else if (entity instanceof Player) { + // can only giveExp() positive experience + if (change < 0) { + // ensure we don't go below 0 + xp = PlayerUtils.getTotalXP((Player) entity) + change; + PlayerUtils.setTotalXP((Player) entity, (Math.max(xp, 0))); + } else { + ((Player) entity).giveExp(change); + } + } + } + break; + } + } + + @Override + public Class<? extends Integer> getReturnType() { + return Integer.class; + } + + @Override + protected String getPropertyName() { + return "total experience"; + } +} diff --git a/src/test/skript/tests/regressions/5491-xp orb merge overwrite.sk b/src/test/skript/tests/regressions/5491-xp orb merge overwrite.sk new file mode 100644 index 00000000000..ca5b9b46d6e --- /dev/null +++ b/src/test/skript/tests/regressions/5491-xp orb merge overwrite.sk @@ -0,0 +1,17 @@ +test "spawn xp orb overwriting merged value" when running minecraft "1.14.4": + # 1.13.2 seems not to merge xp orbs in the same way, so this test is skipped + + # sanitize + kill all xp orbs + set {_spawn} to spawn of world "world" + + # test drop effect + loop 10 times: + drop 10 xp at {_spawn} + assert experience of (all xp orbs) is 100 with "merged xp orb from drop should have 100 xp, but has %experience of (all xp orbs)%" + + # test spawn effect + kill all xp orbs + loop 10 times: + spawn 10 xp at {_spawn} + assert experience of (all xp orbs) is 100 with "merged xp orb from spawn should have 100 xp, but has %experience of (all xp orbs)%" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprTotalExperience.sk b/src/test/skript/tests/syntaxes/expressions/ExprTotalExperience.sk new file mode 100644 index 00000000000..9291fb18eb9 --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprTotalExperience.sk @@ -0,0 +1,15 @@ +test "total experience": + spawn 10 xp at spawn of world "world": + assert experience of entity is 0 with "spawned orb should have 0 experience in section" + set experience of entity to 100 + assert experience of entity is 100 with "set experience of entity did not work" + reset experience of entity + assert experience of entity is 0 with "reset experience of entity did not work" + add 50 to experience of entity + assert experience of entity is 50 with "add experience to entity did not work" + remove 25 from experience of entity + assert experience of entity is 25 with "remove experience from entity did not work" + remove 50 from experience of entity + assert experience of entity is 0 with "remove too much experience from entity did not work" + assert experience of last spawned entity is 10 with "entity should have 10 experience after section" + delete last spawned entity From d1839ac40bd3db768988e3d0944ae4c2d80f25e5 Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Fri, 24 Mar 2023 23:46:39 +0100 Subject: [PATCH 287/619] Clean up variables package (#5411) * Reformat variables, add comments and Javadocs Fix VariablesMap copying not making a deep copy Fix wrong variable being returned during peak variable changes Fix variable name comparator returning wrong value in some cases * Fix tests, my bad * Some reformatting, added comment on saveTask changes concurrency * Make SerializedVariable (and inner class) fields final --------- Co-authored-by: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> --- .../skript/variables/FlatFileStorage.java | 651 +++++++++------ .../skript/variables/SerializedVariable.java | 58 +- .../ch/njol/skript/variables/Variables.java | 774 ++++++++++++------ .../njol/skript/variables/VariablesMap.java | 376 ++++++--- .../skript/variables/VariablesStorage.java | 420 +++++++--- 5 files changed, 1497 insertions(+), 782 deletions(-) diff --git a/src/main/java/ch/njol/skript/variables/FlatFileStorage.java b/src/main/java/ch/njol/skript/variables/FlatFileStorage.java index 02de372d561..04bb514d399 100644 --- a/src/main/java/ch/njol/skript/variables/FlatFileStorage.java +++ b/src/main/java/ch/njol/skript/variables/FlatFileStorage.java @@ -18,15 +18,29 @@ */ package ch.njol.skript.variables; +import ch.njol.skript.Skript; +import ch.njol.skript.config.SectionNode; +import ch.njol.skript.lang.Variable; +import ch.njol.skript.log.SkriptLogger; +import ch.njol.skript.registrations.Classes; +import ch.njol.skript.util.ExceptionUtils; +import ch.njol.skript.util.FileUtils; +import ch.njol.skript.util.Task; +import ch.njol.skript.util.Utils; +import ch.njol.skript.util.Version; +import ch.njol.util.NotifyingReference; +import org.eclipse.jdt.annotation.Nullable; + import java.io.BufferedReader; import java.io.File; -import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.util.ArrayList; import java.util.Map.Entry; import java.util.TreeMap; @@ -34,350 +48,386 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.Skript; -import ch.njol.skript.config.SectionNode; -import ch.njol.skript.lang.Variable; -import ch.njol.skript.log.SkriptLogger; -import ch.njol.skript.registrations.Classes; -import ch.njol.skript.util.ExceptionUtils; -import ch.njol.skript.util.FileUtils; -import ch.njol.skript.util.Task; -import ch.njol.skript.util.Utils; -import ch.njol.skript.util.Version; -import ch.njol.util.NotifyingReference; - /** - * TODO use a database (SQLite) instead and only load a limited amount of variables into RAM - e.g. 2 GB (configurable). If more variables are available they will be loaded when - * accessed. (rem: print a warning when Skript starts) - * rem: store null variables (in memory) to prevent looking up the same variables over and over again - * - * @author Peter Güttinger + * A variable storage that stores its content in a + * comma-separated value file (CSV file). + */ +/* + * TODO use a database (SQLite) instead and only load a limited amount of variables into RAM - e.g. 2 GB (configurable). + * If more variables are available they will be loaded when + * accessed. (rem: print a warning when Skript starts) + * rem: store null variables (in memory) to prevent looking up the same variables over and over again */ public class FlatFileStorage extends VariablesStorage { - - @SuppressWarnings("null") - public final static Charset UTF_8 = Charset.forName("UTF-8"); - + /** - * A Lock on this object must be acquired after connectionLock (if that lock is used) (and thus also after {@link Variables#getReadLock()}). + * The {@link Charset} used in the CSV storage file. + */ + public static final Charset FILE_CHARSET = StandardCharsets.UTF_8; + + /** + * The delay for the save task. + */ + private static final long SAVE_TASK_DELAY = 5 * 60 * 20; + + /** + * The period for the save task, how long (in ticks) between each save. + */ + private static final long SAVE_TASK_PERIOD = 5 * 60 * 20; + + /** + * A reference to the {@link PrintWriter} that is used to write + * to the {@link #file}. + * <p> + * A Lock on this object must be acquired after connectionLock + * if that lock is used + * (and thus also after {@link Variables#getReadLock()}). */ private final NotifyingReference<PrintWriter> changesWriter = new NotifyingReference<>(); - + + /** + * Whether the storage has been loaded. + */ private volatile boolean loaded = false; - - final AtomicInteger changes = new AtomicInteger(0); - private final int REQUIRED_CHANGES_FOR_RESAVE = 1000; - + + /** + * The amount of {@link #changes} needed + * for a new {@link #saveVariables(boolean) save}. + */ + private static final int REQUIRED_CHANGES_FOR_RESAVE = 1000; + + /** + * The amount of variable changes written since the last full save. + * + * @see #REQUIRED_CHANGES_FOR_RESAVE + */ + private final AtomicInteger changes = new AtomicInteger(0); + + /** + * The save task. + * + * @see #changes + * @see #saveVariables(boolean) + * @see #REQUIRED_CHANGES_FOR_RESAVE + * @see #SAVE_TASK_DELAY + * @see #SAVE_TASK_PERIOD + */ @Nullable private Task saveTask; - + + /** + * Whether there was an error while loading variables. + * <p> + * Set back to {@code false} when a backup has been made + * of the variable file that caused the error. + */ private boolean loadError = false; - - protected FlatFileStorage(final String name) { + + /** + * Create a new CSV storage of the given name. + * + * @param name the name. + */ + protected FlatFileStorage(String name) { super(name); } - + /** - * Doesn'ts lock the connection as required by {@link Variables#variableLoaded(String, Object, VariablesStorage)}. + * Loads the variables in the CSV file. + * <p> + * Doesn't lock the connection, as required by + * {@link Variables#variableLoaded(String, Object, VariablesStorage)}. */ - @SuppressWarnings({"deprecation"}) + @SuppressWarnings("deprecation") @Override - protected boolean load_i(final SectionNode n) { + protected boolean load_i(SectionNode sectionNode) { SkriptLogger.setNode(null); - - IOException ioEx = null; - int unsuccessful = 0; - final StringBuilder invalid = new StringBuilder(); - - Version varVersion = Skript.getVersion(); // will be set later - - final Version v2_0_beta3 = new Version(2, 0, "beta 3"); + + if (file == null) { + assert false : this; + return false; + } + + // Keep track of loading errors + IOException ioException = null; + int unsuccessfulVariableCount = 0; + StringBuilder invalid = new StringBuilder(); + + // The Skript version this CSV was created with + Version csvSkriptVersion; + + // Some variables used to allow legacy CSV files to be loaded + Version v2_0_beta3 = new Version(2, 0, "beta 3"); boolean update2_0_beta3 = false; - final Version v2_1 = new Version(2, 1); + Version v2_1 = new Version(2, 1); boolean update2_1 = false; - - BufferedReader r = null; - try { - r = new BufferedReader(new InputStreamReader(new FileInputStream(file), UTF_8)); - String line = null; + + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(Files.newInputStream(file.toPath()), FILE_CHARSET))) { + String line; int lineNum = 0; - while ((line = r.readLine()) != null) { + while ((line = reader.readLine()) != null) { lineNum++; + line = line.trim(); + if (line.isEmpty() || line.startsWith("#")) { + // Line doesn't contain variable if (line.startsWith("# version:")) { + // Update the version accordingly + try { - varVersion = new Version("" + line.substring("# version:".length()).trim()); - update2_0_beta3 = varVersion.isSmallerThan(v2_0_beta3); - update2_1 = varVersion.isSmallerThan(v2_1); - } catch (final IllegalArgumentException e) {} + csvSkriptVersion = new Version(line.substring("# version:".length()).trim()); + update2_0_beta3 = csvSkriptVersion.isSmallerThan(v2_0_beta3); + update2_1 = csvSkriptVersion.isSmallerThan(v2_1); + } catch (IllegalArgumentException ignored) { + } } + continue; } - final String[] split = splitCSV(line); + + String[] split = splitCSV(line); if (split == null || split.length != 3) { + // Invalid CSV line + Skript.error("invalid amount of commas in line " + lineNum + " ('" + line + "')"); if (invalid.length() != 0) invalid.append(", "); + invalid.append(split == null ? "<unknown>" : split[0]); - unsuccessful++; + unsuccessfulVariableCount++; continue; } + if (split[1].equals("null")) { - Variables.variableLoaded("" + split[0], null, this); + Variables.variableLoaded(split[0], null, this); } else { - Object d; - if (update2_1) - d = Classes.deserialize("" + split[1], "" + split[2]); - else - d = Classes.deserialize("" + split[1], decode("" + split[2])); - if (d == null) { + Object deserializedValue; + if (update2_1) { + // Use old deserialization if variables come from old Skript version + deserializedValue = Classes.deserialize(split[1], split[2]); + } else { + deserializedValue = Classes.deserialize(split[1], decode(split[2])); + } + + if (deserializedValue == null) { + // Couldn't deserialize variable if (invalid.length() != 0) invalid.append(", "); + invalid.append(split[0]); - unsuccessful++; + unsuccessfulVariableCount++; continue; } - if (d instanceof String && update2_0_beta3) { - d = Utils.replaceChatStyles((String) d); + + // Legacy + if (deserializedValue instanceof String && update2_0_beta3) { + deserializedValue = Utils.replaceChatStyles((String) deserializedValue); } - Variables.variableLoaded("" + split[0], d, this); + + Variables.variableLoaded(split[0], deserializedValue, this); } } - } catch (final IOException e) { + } catch (IOException e) { loadError = true; - ioEx = e; - } finally { - if (r != null) { - try { - r.close(); - } catch (final IOException e) {} - } + ioException = e; } - - final File file = this.file; - if (file == null) { - assert false : this; - return false; - } - - if (ioEx != null || unsuccessful > 0 || update2_1) { - if (unsuccessful > 0) { - Skript.error(unsuccessful + " variable" + (unsuccessful == 1 ? "" : "s") + " could not be loaded!"); + + if (ioException != null || unsuccessfulVariableCount > 0 || update2_1) { + // Something's wrong (or just an old version) + if (unsuccessfulVariableCount > 0) { + Skript.error(unsuccessfulVariableCount + " variable" + (unsuccessfulVariableCount == 1 ? "" : "s") + + " could not be loaded!"); Skript.error("Affected variables: " + invalid.toString()); } - if (ioEx != null) { - Skript.error("An I/O error occurred while loading the variables: " + ExceptionUtils.toString(ioEx)); + + if (ioException != null) { + Skript.error("An I/O error occurred while loading the variables: " + ExceptionUtils.toString(ioException)); Skript.error("This means that some to all variables could not be loaded!"); } + try { if (update2_1) { Skript.info("[2.1] updating " + file.getName() + " to the new format..."); } - final File bu = FileUtils.backup(file); - Skript.info("Created a backup of " + file.getName() + " as " + bu.getName()); + + // Back up CSV file + File backupFile = FileUtils.backup(file); + Skript.info("Created a backup of " + file.getName() + " as " + backupFile.getName()); + loadError = false; - } catch (final IOException ex) { + } catch (IOException ex) { Skript.error("Could not backup " + file.getName() + ": " + ex.getMessage()); } } - + if (update2_1) { + // Save variables in new format saveVariables(false); Skript.info(file.getName() + " successfully updated."); } - + connect(); - - saveTask = new Task(Skript.getInstance(), 5 * 60 * 20, 5 * 60 * 20, true) { + + // Start the save task + saveTask = new Task(Skript.getInstance(), SAVE_TASK_DELAY, SAVE_TASK_PERIOD, true) { @Override public void run() { + // Due to concurrency, the amount of changes may change between the get and set call + // but that's not a big issue if (changes.get() >= REQUIRED_CHANGES_FOR_RESAVE) { saveVariables(false); changes.set(0); } } }; - - return ioEx == null; + + return ioException == null; } - + @Override protected void allLoaded() { // no transaction support } - + @Override protected boolean requiresFile() { return true; } - - @Override - protected File getFile(final String file) { - return new File(file); - } - - static String encode(final byte[] data) { - final char[] r = new char[data.length * 2]; - for (int i = 0; i < data.length; i++) { - r[2 * i] = Character.toUpperCase(Character.forDigit((data[i] & 0xF0) >>> 4, 16)); - r[2 * i + 1] = Character.toUpperCase(Character.forDigit(data[i] & 0xF, 16)); - } - return new String(r); - } - - static byte[] decode(final String hex) { - final byte[] r = new byte[hex.length() / 2]; - for (int i = 0; i < r.length; i++) { - r[i] = (byte) ((Character.digit(hex.charAt(2 * i), 16) << 4) + Character.digit(hex.charAt(2 * i + 1), 16)); - } - return r; - } - - @SuppressWarnings("null") - private final static Pattern csv = Pattern.compile("(?<=^|,)\\s*([^\",]*|\"([^\"]|\"\")*\")\\s*(,|$)"); - - @Nullable - static String[] splitCSV(final String line) { - final Matcher m = csv.matcher(line); - int lastEnd = 0; - final ArrayList<String> r = new ArrayList<>(); - while (m.find()) { - if (lastEnd != m.start()) - return null; - final String v = m.group(1); - if (v.startsWith("\"")) - r.add(v.substring(1, v.length() - 1).replace("\"\"", "\"")); - else - r.add(v.trim()); - lastEnd = m.end(); - } - if (lastEnd != line.length()) - return null; - return r.toArray(new String[r.size()]); - } - + @Override - protected boolean save(final String name, final @Nullable String type, final @Nullable byte[] value) { - synchronized (connectionLock) { - synchronized (changesWriter) { - if (!loaded && type == null) - return true; // deleting variables is not really required for this kind of storage, as it will be completely rewritten every once in a while, and at least once when the server stops. - PrintWriter cw; - while ((cw = changesWriter.get()) == null) { - try { - changesWriter.wait(); - } catch (final InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - writeCSV(cw, name, type, value == null ? "" : encode(value)); - cw.flush(); - changes.incrementAndGet(); - } - } - return true; + protected File getFile(String fileName) { + return new File(fileName); } - - /** - * Use with find() - */ - @SuppressWarnings("null") - private final static Pattern containsWhitespace = Pattern.compile("\\s"); - - private static void writeCSV(final PrintWriter pw, final String... values) { - assert values.length == 3; // name, type, value - for (int i = 0; i < values.length; i++) { - if (i != 0) - pw.print(", "); - String v = values[i]; - if (v != null && (v.contains(",") || v.contains("\"") || v.contains("#") || containsWhitespace.matcher(v).find())) - v = '"' + v.replace("\"", "\"\"") + '"'; - pw.print(v); - } - pw.println(); - } - + @Override protected final void disconnect() { synchronized (connectionLock) { clearChangesQueue(); synchronized (changesWriter) { - final PrintWriter cw = changesWriter.get(); - if (cw != null) { - cw.close(); + PrintWriter printWriter = changesWriter.get(); + + if (printWriter != null) { + printWriter.close(); changesWriter.set(null); } } } } - + @Override protected final boolean connect() { synchronized (connectionLock) { synchronized (changesWriter) { + assert file != null; // file should be non-null after load + if (changesWriter.get() != null) return true; - try (FileOutputStream fos = new FileOutputStream(file, true)){ - changesWriter.set(new PrintWriter(new OutputStreamWriter(fos, UTF_8))); + + // Open the file stream, and create the PrintWriter with it + try (FileOutputStream fos = new FileOutputStream(file, true)) { + changesWriter.set(new PrintWriter(new OutputStreamWriter(fos, FILE_CHARSET))); loaded = true; return true; } catch (IOException e) { // close() might throw ANY IOException + //noinspection ThrowableNotThrown Skript.exception(e); return false; } } } } - + @Override public void close() { clearChangesQueue(); super.close(); saveVariables(true); // also closes the writer } - + + @Override + protected boolean save(String name, @Nullable String type, @Nullable byte[] value) { + synchronized (connectionLock) { + synchronized (changesWriter) { + if (!loaded && type == null) { + // deleting variables is not really required for this kind of storage, + // as it will be completely rewritten every once in a while, + // and at least once when the server stops. + return true; + } + + // Get the PrintWriter, waiting for it to be available if needed + PrintWriter printWriter; + while ((printWriter = changesWriter.get()) == null) { + try { + changesWriter.wait(); + } catch (InterruptedException e) { + // Re-interrupt thread + Thread.currentThread().interrupt(); + } + } + + writeCSV(printWriter, name, type, value == null ? "" : encode(value)); + printWriter.flush(); + + changes.incrementAndGet(); + } + } + return true; + } + /** - * Completely rewrites the while file - * + * Completely rewrites the CSV file. + * <p> + * The {@code finalSave} argument is used to determine if + * the {@link #saveTask save} and {@link #backupTask backup} tasks + * should be cancelled, and if the storage should reconnect after saving. + * * @param finalSave whether this is the last save in this session or not. */ - public final void saveVariables(final boolean finalSave) { + public final void saveVariables(boolean finalSave) { if (finalSave) { - final Task st = saveTask; - if (st != null) - st.cancel(); - final Task bt = backupTask; - if (bt != null) - bt.cancel(); + // Cancel save and backup tasks, not needed with final save anyway + if (saveTask != null) + saveTask.cancel(); + if (backupTask != null) + backupTask.cancel(); } + try { + // Acquire read lock Variables.getReadLock().lock(); + synchronized (connectionLock) { try { - final File f = file; - if (f == null) { + if (file == null) { + // This storage requires a file, so file should be nonnull assert false : this; return; } + disconnect(); + if (loadError) { + // There was an error while loading the CSV file, create a backup of it try { - final File backup = FileUtils.backup(f); - Skript.info("Created a backup of the old " + f.getName() + " as " + backup.getName()); + File backup = FileUtils.backup(file); + Skript.info("Created a backup of the old " + file.getName() + " as " + backup.getName()); loadError = false; - } catch (final IOException e) { - Skript.error("Could not backup the old " + f.getName() + ": " + ExceptionUtils.toString(e)); + } catch (IOException e) { + Skript.error("Could not backup the old " + file.getName() + ": " + ExceptionUtils.toString(e)); Skript.error("No variables are saved!"); return; } } + + // Write the variables to a temporary file, giving less problems if saving fails + // (if saving fails during writing to the actual file, + // the data in the actual file may be partially lost) File tempFile = new File(file.getParentFile(), file.getName() + ".temp"); - PrintWriter pw = null; - try { - pw = new PrintWriter(tempFile, "UTF-8"); + + try (PrintWriter pw = new PrintWriter(tempFile, "UTF-8")) { pw.println("# === Skript's variable storage ==="); pw.println("# Please do not modify this file manually!"); pw.println("#"); @@ -387,14 +437,14 @@ public final void saveVariables(final boolean finalSave) { pw.println(); pw.flush(); pw.close(); - FileUtils.move(tempFile, f, true); - } catch (final IOException e) { - Skript.error("Unable to make a final save of the database '" + databaseName + "' (no variables are lost): " + ExceptionUtils.toString(e)); // FIXME happens at random - check locks/threads - } finally { - if (pw != null) - pw.close(); + FileUtils.move(tempFile, file, true); + } catch (IOException e) { + Skript.error("Unable to make a final save of the database '" + databaseName + + "' (no variables are lost): " + ExceptionUtils.toString(e)); + // FIXME happens at random - check locks/threads } } finally { + // Reconnect if needed if (!finalSave) { connect(); } @@ -402,8 +452,8 @@ public final void saveVariables(final boolean finalSave) { } } finally { Variables.getReadLock().unlock(); - boolean gotLock = Variables.variablesLock.writeLock().tryLock(); - if (gotLock) { // Only process queue now if it doesn't require us to wait + boolean gotWriteLock = Variables.variablesLock.writeLock().tryLock(); + if (gotWriteLock) { // Only process queue now if it doesn't require us to wait try { Variables.processChangeQueue(); } finally { @@ -412,43 +462,166 @@ public final void saveVariables(final boolean finalSave) { } } } - + /** * Saves the variables. * <p> * This method uses the sorted variables map to save the variables in order. - * - * @param pw - * @param parent The parent's name with {@link Variable#SEPARATOR} at the end - * @param map + * + * @param pw the print writer to write the CSV lines too. + * @param parent The parent's name with {@link Variable#SEPARATOR} at the end. + * @param map the variables map. */ @SuppressWarnings("unchecked") private void save(PrintWriter pw, String parent, TreeMap<String, Object> map) { - outer: for (Entry<String, Object> e : map.entrySet()) { - Object val = e.getValue(); - if (val == null) - continue; - if (val instanceof TreeMap) { - save(pw, parent + e.getKey() + Variable.SEPARATOR, (TreeMap<String, Object>) val); + // Iterate over all children + for (Entry<String, Object> childEntry : map.entrySet()) { + Object childNode = childEntry.getValue(); + String childKey = childEntry.getKey(); + + if (childNode == null) + continue; // Leaf node + + if (childNode instanceof TreeMap) { + // TreeMap found, recurse + save(pw, parent + childKey + Variable.SEPARATOR, (TreeMap<String, Object>) childNode); } else { - String name = e.getKey() == null ? parent.substring(0, parent.length() - Variable.SEPARATOR.length()) : parent + e.getKey(); + // Remove variable separator if needed + String name = childKey == null ? parent.substring(0, parent.length() - Variable.SEPARATOR.length()) : parent + childKey; try { - for (VariablesStorage s : Variables.storages) { - if (s.accept(name)) { - if (s == this) { - SerializedVariable.Value value = Classes.serialize(val); - if (value != null) - writeCSV(pw, name, value.type, encode(value.data)); + // Loop over storages to make sure this variable is ours to store + for (VariablesStorage storage : Variables.STORAGES) { + if (storage.accept(name)) { + if (storage == this) { + // Serialize the value + SerializedVariable.Value serializedValue = Classes.serialize(childNode); + + // Write the CSV line + if (serializedValue != null) + writeCSV(pw, name, serializedValue.type, encode(serializedValue.data)); } - continue outer; + + break; } } } catch (Exception ex) { + //noinspection ThrowableNotThrown Skript.exception(ex, "Error saving variable named " + name); } } } } - + + /** + * Encode the given byte array to a hexadecimal string. + * + * @param data the byte array to encode. + * @return the hex string. + */ + static String encode(byte[] data) { + char[] encoded = new char[data.length * 2]; + + for (int i = 0; i < data.length; i++) { + encoded[2 * i] = Character.toUpperCase(Character.forDigit((data[i] & 0xF0) >>> 4, 16)); + encoded[2 * i + 1] = Character.toUpperCase(Character.forDigit(data[i] & 0xF, 16)); + } + + return new String(encoded); + } + + /** + * Decodes the given hexadecimal string to a byte array. + * + * @param hex the hex string to encode. + * @return the byte array. + */ + static byte[] decode(String hex) { + byte[] decoded = new byte[hex.length() / 2]; + + for (int i = 0; i < decoded.length; i++) { + decoded[i] = (byte) ((Character.digit(hex.charAt(2 * i), 16) << 4) + Character.digit(hex.charAt(2 * i + 1), 16)); + } + + return decoded; + } + + /** + * A regex pattern of a line in a CSV file. + */ + private static final Pattern CSV_LINE_PATTERN = Pattern.compile("(?<=^|,)\\s*([^\",]*|\"([^\"]|\"\")*\")\\s*(,|$)"); + + /** + * Splits the given CSV line into its values. + * + * @param line the CSV line. + * @return the array of values. + * + * @see #CSV_LINE_PATTERN + */ + @Nullable + static String[] splitCSV(String line) { + Matcher matcher = CSV_LINE_PATTERN.matcher(line); + + int lastEnd = 0; + ArrayList<String> result = new ArrayList<>(); + + while (matcher.find()) { + if (lastEnd != matcher.start()) + return null; // other stuff inbetween finds + + String value = matcher.group(1); + if (value.startsWith("\"")) + // Unescape value + result.add(value.substring(1, value.length() - 1).replace("\"\"", "\"")); + else + result.add(value.trim()); + + lastEnd = matcher.end(); + } + + if (lastEnd != line.length()) + return null; // other stuff after last find + + return result.toArray(new String[0]); + } + + /** + * A regex pattern to check if a string contains whitespace. + * <p> + * Use with {@link Matcher#find()} to search the whole string for whitespace. + */ + private static final Pattern CONTAINS_WHITESPACE = Pattern.compile("\\s"); + + /** + * Writes the given 3 values as a CSV value to the given {@link PrintWriter}. + * + * @param printWriter the print writer. + * @param values the values, must have a length of {@code 3}. + */ + private static void writeCSV(PrintWriter printWriter, String... values) { + assert values.length == 3; // name, type, value + + for (int i = 0; i < values.length; i++) { + if (i != 0) + printWriter.print(", "); + + String value = values[i]; + + // Check if the value should be escaped + boolean escapingNeeded = value != null + && (value.contains(",") + || value.contains("\"") + || value.contains("#") + || CONTAINS_WHITESPACE.matcher(value).find()); + if (escapingNeeded) { + value = '"' + value.replace("\"", "\"\"") + '"'; + } + + printWriter.print(value); + } + + printWriter.println(); + } + } diff --git a/src/main/java/ch/njol/skript/variables/SerializedVariable.java b/src/main/java/ch/njol/skript/variables/SerializedVariable.java index f39fdff81d6..27bd6622e4d 100644 --- a/src/main/java/ch/njol/skript/variables/SerializedVariable.java +++ b/src/main/java/ch/njol/skript/variables/SerializedVariable.java @@ -21,26 +21,60 @@ import org.eclipse.jdt.annotation.Nullable; /** - * @author Peter Güttinger + * An instance of a serialized variable, contains the variable name + * and the serialized value. */ public class SerializedVariable { - public String name; + + /** + * The name of the variable. + */ + public final String name; + + /** + * The serialized value of the variable. + * <p> + * A value of {@code null} indicates the variable will be deleted. + */ @Nullable - public Value value; - - public SerializedVariable(final String name, final @Nullable Value value) { + public final Value value; + + /** + * Creates a new serialized variable with the given name and value. + * + * @param name the given name. + * @param value the given value, or {@code null} to indicate a deletion. + */ + public SerializedVariable(String name, @Nullable Value value) { this.name = name; this.value = value; } - - public final static class Value { - public String type; - public byte[] data; - - public Value(final String type, final byte[] data) { + + /** + * A serialized value of a variable. + */ + public static final class Value { + + /** + * The type of this value. + */ + public final String type; + + /** + * The serialized value data. + */ + public final byte[] data; + + /** + * Creates a new serialized value. + * @param type the value type. + * @param data the serialized value data. + */ + public Value(String type, byte[] data) { this.type = type; this.data = data; } + } - + } diff --git a/src/main/java/ch/njol/skript/variables/Variables.java b/src/main/java/ch/njol/skript/variables/Variables.java index 6d9706d93f4..7ce886d016d 100644 --- a/src/main/java/ch/njol/skript/variables/Variables.java +++ b/src/main/java/ch/njol/skript/variables/Variables.java @@ -18,32 +18,6 @@ */ package ch.njol.skript.variables; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Queue; -import java.util.TreeMap; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.regex.Pattern; - -import org.bukkit.Bukkit; -import org.bukkit.configuration.serialization.ConfigurationSerializable; -import org.bukkit.configuration.serialization.ConfigurationSerialization; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; -import org.skriptlang.skript.lang.converter.Converters; - import ch.njol.skript.Skript; import ch.njol.skript.SkriptAPIException; import ch.njol.skript.SkriptConfig; @@ -57,152 +31,214 @@ import ch.njol.skript.registrations.Classes; import ch.njol.skript.variables.DatabaseStorage.Type; import ch.njol.skript.variables.SerializedVariable.Value; -import ch.njol.util.Closeable; import ch.njol.util.Kleenean; import ch.njol.util.NonNullPair; import ch.njol.util.SynchronizedReference; import ch.njol.yggdrasil.Yggdrasil; +import org.bukkit.Bukkit; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.ConfigurationSerialization; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.converter.Converters; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Queue; +import java.util.TreeMap; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.regex.Pattern; /** - * @author Peter Güttinger + * Handles all things related to variables. + * + * @see #setVariable(String, Object, Event, boolean) + * @see #getVariable(String, Event, boolean) */ -public abstract class Variables { - private Variables() {} - - public final static short YGGDRASIL_VERSION = 1; - - public final static Yggdrasil yggdrasil = new Yggdrasil(YGGDRASIL_VERSION); +public class Variables { + + /** + * The version of {@link Yggdrasil} this class is using. + */ + public static final short YGGDRASIL_VERSION = 1; + + /** + * The {@link Yggdrasil} instance used for (de)serialization. + */ + public static final Yggdrasil yggdrasil = new Yggdrasil(YGGDRASIL_VERSION); + /** + * Whether variable names are case-sensitive. + */ public static boolean caseInsensitiveVariables = true; - - private final static String configurationSerializablePrefix = "ConfigurationSerializable_"; + + /** + * The {@link ch.njol.yggdrasil.ClassResolver#getID(Class) ID} prefix + * for {@link ConfigurationSerializable} classes. + */ + private static final String CONFIGURATION_SERIALIZABLE_PREFIX = "ConfigurationSerializable_"; + + // Register some things with Yggdrasil static { yggdrasil.registerSingleClass(Kleenean.class, "Kleenean"); + // Register ConfigurationSerializable, Bukkit's serialization system yggdrasil.registerClassResolver(new ConfigurationSerializer<ConfigurationSerializable>() { { - init(); // separate method for the annotation - } - - @SuppressWarnings("unchecked") - private void init() { - // used by asserts + //noinspection unchecked info = (ClassInfo<? extends ConfigurationSerializable>) (ClassInfo<?>) Classes.getExactClassInfo(Object.class); + // Info field is mostly unused in superclass, due to methods overridden below, + // so this illegal cast is fine } - - @SuppressWarnings({"unchecked"}) + @Override @Nullable - public String getID(final @NonNull Class<?> c) { - if (ConfigurationSerializable.class.isAssignableFrom(c) && Classes.getSuperClassInfo(c) == Classes.getExactClassInfo(Object.class)) - return configurationSerializablePrefix + ConfigurationSerialization.getAlias((Class<? extends ConfigurationSerializable>) c); + public String getID(@NonNull Class<?> c) { + if (ConfigurationSerializable.class.isAssignableFrom(c) + && Classes.getSuperClassInfo(c) == Classes.getExactClassInfo(Object.class)) + return CONFIGURATION_SERIALIZABLE_PREFIX + + ConfigurationSerialization.getAlias(c.asSubclass(ConfigurationSerializable.class)); + return null; } - + @Override @Nullable - public Class<? extends ConfigurationSerializable> getClass(final @NonNull String id) { - if (id.startsWith(configurationSerializablePrefix)) - return ConfigurationSerialization.getClassByAlias(id.substring(configurationSerializablePrefix.length())); + public Class<? extends ConfigurationSerializable> getClass(@NonNull String id) { + if (id.startsWith(CONFIGURATION_SERIALIZABLE_PREFIX)) + return ConfigurationSerialization.getClassByAlias( + id.substring(CONFIGURATION_SERIALIZABLE_PREFIX.length())); + return null; } }); } - - static List<VariablesStorage> storages = new ArrayList<>(); - + + /** + * The variable storages configured. + */ + static final List<VariablesStorage> STORAGES = new ArrayList<>(); + + /** + * Load the variables configuration and all variables. + * <p> + * May only be called once, when Skript is loading. + * + * @return whether the loading was successful. + */ public static boolean load() { assert variables.treeMap.isEmpty(); assert variables.hashMap.isEmpty(); - assert storages.isEmpty(); - - final Config c = SkriptConfig.getConfig(); - if (c == null) + assert STORAGES.isEmpty(); + + Config config = SkriptConfig.getConfig(); + if (config == null) throw new SkriptAPIException("Cannot load variables before the config"); - final Node databases = c.getMainNode().get("databases"); - if (databases == null || !(databases instanceof SectionNode)) { + + Node databases = config.getMainNode().get("databases"); + if (!(databases instanceof SectionNode)) { Skript.error("The config is missing the required 'databases' section that defines where the variables are saved"); return false; } - - Skript.closeOnDisable(new Closeable() { - @Override - public void close() { - Variables.close(); - } - }); - + + Skript.closeOnDisable(Variables::close); + // reports once per second how many variables were loaded. Useful to make clear that Skript is still doing something if it's loading many variables - final Thread loadingLoggerThread = new Thread() { - @Override - public void run() { - while (true) { - try { - Thread.sleep(Skript.logNormal() ? 1000 : 5000); // low verbosity won't disable these messages, but makes them more rare - } catch (final InterruptedException e) {} - synchronized (tempVars) { - final Map<String, NonNullPair<Object, VariablesStorage>> tvs = tempVars.get(); - if (tvs != null) - Skript.info("Loaded " + tvs.size() + " variables so far..."); - else - break; - } + Thread loadingLoggerThread = new Thread(() -> { + while (true) { + try { + Thread.sleep(Skript.logNormal() ? 1000 : 5000); // low verbosity won't disable these messages, but makes them more rare + } catch (InterruptedException ignored) {} + + synchronized (TEMP_VARIABLES) { + Map<String, NonNullPair<Object, VariablesStorage>> tvs = TEMP_VARIABLES.get(); + if (tvs != null) + Skript.info("Loaded " + tvs.size() + " variables so far..."); + else + break; // variables loaded, exit thread } } - }; + }); loadingLoggerThread.start(); - + try { boolean successful = true; - for (final Node node : (SectionNode) databases) { + + for (Node node : (SectionNode) databases) { if (node instanceof SectionNode) { - final SectionNode n = (SectionNode) node; - final String type = n.getValue("type"); + SectionNode sectionNode = (SectionNode) node; + + String type = sectionNode.getValue("type"); if (type == null) { Skript.error("Missing entry 'type' in database definition"); successful = false; continue; } - - final String name = n.getKey(); + + String name = sectionNode.getKey(); assert name != null; - final VariablesStorage s; - if (type.equalsIgnoreCase("csv") || type.equalsIgnoreCase("file") || type.equalsIgnoreCase("flatfile")) { - s = new FlatFileStorage(name); + + // Initiate the right VariablesStorage class + VariablesStorage variablesStorage; + if (type.equalsIgnoreCase("csv") + || type.equalsIgnoreCase("file") + || type.equalsIgnoreCase("flatfile")) { + variablesStorage = new FlatFileStorage(name); } else if (type.equalsIgnoreCase("mysql")) { - s = new DatabaseStorage(name, Type.MYSQL); + variablesStorage = new DatabaseStorage(name, Type.MYSQL); } else if (type.equalsIgnoreCase("sqlite")) { - s = new DatabaseStorage(name, Type.SQLITE); + variablesStorage = new DatabaseStorage(name, Type.SQLITE); } else { if (!type.equalsIgnoreCase("disabled") && !type.equalsIgnoreCase("none")) { Skript.error("Invalid database type '" + type + "'"); successful = false; } + continue; } - - final int x; - synchronized (tempVars) { - final Map<String, NonNullPair<Object, VariablesStorage>> tvs = tempVars.get(); + + // Get the amount of variables currently loaded + int totalVariablesLoaded; + synchronized (TEMP_VARIABLES) { + Map<String, NonNullPair<Object, VariablesStorage>> tvs = TEMP_VARIABLES.get(); assert tvs != null; - x = tvs.size(); + totalVariablesLoaded = tvs.size(); } - final long start = System.currentTimeMillis(); + + long start = System.currentTimeMillis(); if (Skript.logVeryHigh()) Skript.info("Loading database '" + node.getKey() + "'..."); - - if (s.load(n)) - storages.add(s); + + // Load the variables + if (variablesStorage.load(sectionNode)) + STORAGES.add(variablesStorage); else successful = false; - - final int d; - synchronized (tempVars) { - final Map<String, NonNullPair<Object, VariablesStorage>> tvs = tempVars.get(); + + // Get the amount of variables loaded by this variables storage object + int newVariablesLoaded; + synchronized (TEMP_VARIABLES) { + Map<String, NonNullPair<Object, VariablesStorage>> tvs = TEMP_VARIABLES.get(); assert tvs != null; - d = tvs.size() - x; + newVariablesLoaded = tvs.size() - totalVariablesLoaded; + } + + if (Skript.logVeryHigh()) { + Skript.info("Loaded " + newVariablesLoaded + " variables from the database " + + "'" + sectionNode.getKey() + "' in " + + ((System.currentTimeMillis() - start) / 100) / 10.0 + " seconds"); } - if (Skript.logVeryHigh()) - Skript.info("Loaded " + d + " variables from the database '" + n.getKey() + "' in " + ((System.currentTimeMillis() - start) / 100) / 10.0 + " seconds"); } else { Skript.error("Invalid line in databases: databases must be defined as sections"); successful = false; @@ -210,8 +246,8 @@ public void run() { } if (!successful) return false; - - if (storages.isEmpty()) { + + if (STORAGES.isEmpty()) { Skript.error("No databases to store variables are defined. Please enable at least the default database, even if you don't use variables at all."); return false; } @@ -219,58 +255,107 @@ public void run() { SkriptLogger.setNode(null); // make sure to put the loaded variables into the variables map - final int n = onStoragesLoaded(); - if (n != 0) { - Skript.warning(n + " variables were possibly discarded due to not belonging to any database (SQL databases keep such variables and will continue to generate this warning, while CSV discards them)."); + int notStoredVariablesCount = onStoragesLoaded(); + if (notStoredVariablesCount != 0) { + Skript.warning(notStoredVariablesCount + " variables were possibly discarded due to not belonging to any database " + + "(SQL databases keep such variables and will continue to generate this warning, " + + "while CSV discards them)."); } - + + // Interrupt the loading logger thread to make it exit earlier loadingLoggerThread.interrupt(); - + saveThread.start(); } return true; } - - @SuppressWarnings("null") - private final static Pattern variableNameSplitPattern = Pattern.compile(Pattern.quote(Variable.SEPARATOR)); - - @SuppressWarnings("null") - public static String[] splitVariableName(final String name) { - return variableNameSplitPattern.split(name); + + /** + * A pattern to split variable names using {@link Variable#SEPARATOR}. + */ + private static final Pattern VARIABLE_NAME_SPLIT_PATTERN = Pattern.compile(Pattern.quote(Variable.SEPARATOR)); + + /** + * Splits the given variable name into its parts, + * separated by {@link Variable#SEPARATOR}. + * + * @param name the variable name. + * @return the parts. + */ + public static String[] splitVariableName(String name) { + return VARIABLE_NAME_SPLIT_PATTERN.split(name); } - - final static ReadWriteLock variablesLock = new ReentrantReadWriteLock(true); + + /** + * A lock for reading and writing variables. + */ + static final ReadWriteLock variablesLock = new ReentrantReadWriteLock(true); + /** + * The {@link VariablesMap} storing global variables, * must be locked with {@link #variablesLock}. */ - final static VariablesMap variables = new VariablesMap(); + static final VariablesMap variables = new VariablesMap(); - private final static Map<Event, VariablesMap> localVariables = new ConcurrentHashMap<>(); - /** + * A map storing all local variables, + * indexed by their {@link Event}. + */ + private static final Map<Event, VariablesMap> localVariables = new ConcurrentHashMap<>(); + + /** + * Gets the {@link TreeMap} of all global variables. + * <p> * Remember to lock with {@link #getReadLock()} and to not make any changes! */ static TreeMap<String, Object> getVariables() { return variables.treeMap; } - + + /** + * Gets the {@link Map} of all global variables. + * <p> + * This map cannot be modified. + * Remember to lock with {@link #getReadLock()}! + */ + static Map<String, Object> getVariablesHashMap() { + return Collections.unmodifiableMap(variables.hashMap); + } + + /** + * Gets the lock for reading variables. + * + * @return the lock. + * + * @see #variablesLock + */ + static Lock getReadLock() { + return variablesLock.readLock(); + } + /** * Removes local variables associated with given event and returns them, * if they exist. - * @param event Event. - * @return Local variables or null. + * + * @param event the event. + * @return the local variables from the event, + * or {@code null} if the event had no local variables. */ @Nullable public static VariablesMap removeLocals(Event event) { return localVariables.remove(event); } - + /** * Sets local variables associated with given event. - * If the given map is null, local variables for this event will be <b>removed</b> if they are present! + * <p> + * If the given map is {@code null}, local variables for this event + * will be <b>removed</b>. + * <p> * Warning: this can overwrite local variables! - * @param event Event. - * @param map New local variables. + * + * @param event the event. + * @param map the new local variables. */ public static void setLocalVariables(Event event, @Nullable Object map) { if (map != null) { @@ -281,34 +366,21 @@ public static void setLocalVariables(Event event, @Nullable Object map) { } /** - * Creates a copy of the VariablesMap for local variables in an event. - * @param event The event to copy local variables from. - * @return A VariablesMap copy for the local variables in an event. + * Creates a copy of the {@link VariablesMap} for local variables + * in an event. + * + * @param event the event to copy local variables from. + * @return the copy. */ @Nullable public static Object copyLocalVariables(Event event) { VariablesMap from = localVariables.get(event); if (from == null) return null; - VariablesMap copy = new VariablesMap(); - copy.hashMap.putAll(from.hashMap); - copy.treeMap.putAll(from.treeMap); - return copy; - } - - /** - * Remember to lock with {@link #getReadLock()}! - */ - @SuppressWarnings("null") - static Map<String, Object> getVariablesHashMap() { - return Collections.unmodifiableMap(variables.hashMap); - } - - @SuppressWarnings("null") - static Lock getReadLock() { - return variablesLock.readLock(); + + return from.copy(); } - + /** * Returns the internal value of the requested variable. * <p> @@ -316,30 +388,46 @@ static Lock getReadLock() { * <p> * This does not take into consideration default variables. You must use get methods from {@link ch.njol.skript.lang.Variable} * - * @param name - * @return an Object for a normal Variable or a Map<String, Object> for a list variable, or null if the variable is not set. + * @param name the variable's name. + * @param event if {@code local} is {@code true}, this is the event + * the local variable resides in. + * @param local if this variable is a local or global variable. + * @return an {@link Object} for a normal variable + * or a {@code Map<String, Object>} for a list variable, + * or {@code null} if the variable is not set. */ + // TODO don't expose the internal value, bad API @Nullable - public static Object getVariable(final String name, final @Nullable Event e, final boolean local) { - String n = name; - if (caseInsensitiveVariables) { - n = name.toLowerCase(Locale.ENGLISH); - } - assert n != null; - if (local) { - final VariablesMap map = localVariables.get(e); + public static Object getVariable(String name, @Nullable Event event, boolean local) { + String n; + if (caseInsensitiveVariables) { + n = name.toLowerCase(Locale.ENGLISH); + } else { + n = name; + } + + if (local) { + VariablesMap map = localVariables.get(event); if (map == null) return null; + return map.getVariable(n); } else { // Prevent race conditions from returning variables with incorrect values if (!changeQueue.isEmpty()) { - for (VariableChange change : changeQueue) { - if (change.name.equals(n)) - return change.value; + // Gets the last VariableChange made + VariableChange variableChange = changeQueue.stream() + .filter(change -> change.name.equals(n)) + .reduce((first, second) -> second) + // Gets last value, as iteration is from head to tail, + // and adding occurs at the tail (and we want the most recently added) + .orElse(null); + + if (variableChange != null) { + return variableChange.value; } } - + try { variablesLock.readLock().lock(); return variables.getVariable(n); @@ -351,9 +439,10 @@ public static Object getVariable(final String name, final @Nullable Event e, fin /** * Deletes a variable. - * - * @param name The variable's name. - * @param event if <tt>local</tt> is true, this is the event the local variable resides. + * + * @param name the variable's name. + * @param event if {@code local} is {@code true}, this is the event + * the local variable resides in. * @param local if this variable is a local or global variable. */ public static void deleteVariable(String name, @Nullable Event event, boolean local) { @@ -363,139 +452,216 @@ public static void deleteVariable(String name, @Nullable Event event, boolean lo /** * Sets a variable. * - * @param name The variable's name. Can be a "list variable::*" (<tt>value</tt> must be <tt>null</tt> in this case) - * @param value The variable's value. Use <tt>null</tt> to delete the variable. - * @param event if <tt>local</tt> is true, this is the event the local variable resides. + * @param name the variable's name. + * Can be a "list variable::*", but {@code value} + * must be {@code null} in this case. + * @param value The variable's value. Use {@code null} + * to delete the variable. + * @param event if {@code local} is {@code true}, this is the event + * the local variable resides in. * @param local if this variable is a local or global variable. */ public static void setVariable(String name, @Nullable Object value, @Nullable Event event, boolean local) { - String n = name; - if (caseInsensitiveVariables) { - n = name.toLowerCase(Locale.ENGLISH); - } - assert n != null; - if (value != null) { - assert !n.endsWith("::*"); - final ClassInfo<?> ci = Classes.getSuperClassInfo(value.getClass()); - final Class<?> sas = ci.getSerializeAs(); + if (caseInsensitiveVariables) { + name = name.toLowerCase(Locale.ENGLISH); + } + + // Check if conversion is needed due to ClassInfo#getSerializeAs + if (value != null) { + assert !name.endsWith("::*"); + + ClassInfo<?> ci = Classes.getSuperClassInfo(value.getClass()); + Class<?> sas = ci.getSerializeAs(); + if (sas != null) { value = Converters.convert(value, sas); assert value != null : ci + ", " + sas; } } + if (local) { - assert event != null : n; + assert event != null : name; + + // Get the variables map and set the variable in it VariablesMap map = localVariables.computeIfAbsent(event, e -> new VariablesMap()); - map.setVariable(n, value); + map.setVariable(name, value); } else { - setVariable(n, value); + setVariable(name, value); } } - - static void setVariable(final String name, @Nullable final Object value) { + + /** + * Sets the given global variable name to the given value. + * + * @param name the variable name. + * @param value the value, or {@code null} to delete the variable. + */ + static void setVariable(String name, @Nullable Object value) { boolean gotLock = variablesLock.writeLock().tryLock(); if (gotLock) { try { + // Set the variable variables.setVariable(name, value); + // ..., save the variable change saveVariableChange(name, value); - processChangeQueue(); // Process all previously queued writes + // ..., and process all previously queued changes + processChangeQueue(); } finally { variablesLock.writeLock().unlock(); } - } else { // Can't block here, queue the change + } else { + // Couldn't acquire variable write lock, queue the change (blocking here is a bad idea) queueVariableChange(name, value); } } - + /** - * Changes to variables that have not yet been written. + * Changes to variables that have not yet been performed. */ - final static Queue<VariableChange> changeQueue = new ConcurrentLinkedQueue<>(); - + static final Queue<VariableChange> changeQueue = new ConcurrentLinkedQueue<>(); + /** * A variable change name-value pair. */ private static class VariableChange { - + + /** + * The name of the changed variable. + */ public final String name; + + /** + * The (possibly {@code null}) value of the variable change. + */ @Nullable public final Object value; - + + /** + * Creates a new {@link VariableChange} with the given name and value. + * + * @param name the variable name. + * @param value the new variable value. + */ public VariableChange(String name, @Nullable Object value) { this.name = name; this.value = value; } + } - + /** * Queues a variable change. Only to be called when direct write is not * possible, but thread cannot be allowed to block. - * @param name Variable name. - * @param value New value. + * + * @param name the variable name. + * @param value the new value. */ private static void queueVariableChange(String name, @Nullable Object value) { changeQueue.add(new VariableChange(name, value)); } - + /** - * Processes all entries in variable change queue. Note that caller MUST - * acquire write lock before calling this, then release it. + * Processes all entries in variable change queue. + * <p> + * Note that caller must acquire write lock before calling this, + * then release it. */ static void processChangeQueue() { while (true) { // Run as long as we still have changes VariableChange change = changeQueue.poll(); if (change == null) break; - + + // Set and save variable variables.setVariable(change.name, change.value); saveVariableChange(change.name, change.value); } } - + /** - * Stores loaded variables while variable storages are loaded. + * Stores loaded variables while variable storages are being loaded. * <p> * Access must be synchronised. */ - final static SynchronizedReference<Map<String, NonNullPair<Object, VariablesStorage>>> tempVars = new SynchronizedReference<>(new HashMap<String, NonNullPair<Object, VariablesStorage>>()); - + private static final SynchronizedReference<Map<String, NonNullPair<Object, VariablesStorage>>> TEMP_VARIABLES = + new SynchronizedReference<>(new HashMap<>()); + + /** + * The amount of variable conflicts between variable storages where + * a warning will be given, with any conflicts than this value, no more + * warnings will be given. + * + * @see #loadConflicts + */ private static final int MAX_CONFLICT_WARNINGS = 50; + + /** + * Keeps track of the amount of variable conflicts between variable storages + * while loading. + */ private static int loadConflicts = 0; - + /** - * Sets a variable and moves it to the appropriate database if the config was changed. Must only be used while variables are loaded when Skript is starting. - * <p> - * Must be called on Bukkit's main thread. + * Sets a variable and moves it to the appropriate database + * if the config was changed. * <p> - * This method directly invokes {@link VariablesStorage#save(String, String, byte[])}, i.e. you should not be holding any database locks or such when calling this! + * Must only be used while variables are loaded + * when Skript is starting. Must be called on Bukkit's main thread. + * This method directly invokes + * {@link VariablesStorage#save(String, String, byte[])}, + * i.e. you should not be holding any database locks or such + * when calling this! * - * @param name - * @param value - * @param source + * @param name the variable name. + * @param value the variable value. + * @param source the storage the variable came from. * @return Whether the variable was stored somewhere. Not valid while storages are loading. */ - static boolean variableLoaded(final String name, final @Nullable Object value, final VariablesStorage source) { + static boolean variableLoaded(String name, @Nullable Object value, VariablesStorage source) { assert Bukkit.isPrimaryThread(); // required by serialisation - - synchronized (tempVars) { - final Map<String, NonNullPair<Object, VariablesStorage>> tvs = tempVars.get(); + + if (value == null) + return false; + + synchronized (TEMP_VARIABLES) { + Map<String, NonNullPair<Object, VariablesStorage>> tvs = TEMP_VARIABLES.get(); if (tvs != null) { - if (value == null) - return false; - final NonNullPair<Object, VariablesStorage> v = tvs.get(name); - if (v != null && v.getSecond() != source) {// variable already loaded from another database + NonNullPair<Object, VariablesStorage> existingVariable = tvs.get(name); + + // Check for conflicts with other storages + conflict: if (existingVariable != null) { + VariablesStorage existingVariableStorage = existingVariable.getSecond(); + + if (existingVariableStorage == source) { + // No conflict if from the same storage + break conflict; + } + + // Variable already loaded from another database, conflict loadConflicts++; - if (loadConflicts <= MAX_CONFLICT_WARNINGS) - Skript.warning("The variable {" + name + "} was loaded twice from different databases (" + v.getSecond().databaseName + " and " + source.databaseName + "), only the one from " + source.databaseName + " will be kept."); - else if (loadConflicts == MAX_CONFLICT_WARNINGS + 1) - Skript.warning("[!] More than " + MAX_CONFLICT_WARNINGS + " variables were loaded more than once from different databases, no more warnings will be printed."); - v.getSecond().save(name, null, null); + + // Warn if needed + if (loadConflicts <= MAX_CONFLICT_WARNINGS) { + Skript.warning("The variable {" + name + "} was loaded twice from different databases (" + + existingVariableStorage.databaseName + " and " + source.databaseName + + "), only the one from " + source.databaseName + " will be kept."); + } else if (loadConflicts == MAX_CONFLICT_WARNINGS + 1) { + Skript.warning("[!] More than " + MAX_CONFLICT_WARNINGS + + " variables were loaded more than once from different databases, " + + "no more warnings will be printed."); + } + + // Remove the value from the existing variable's storage + existingVariableStorage.save(name, null, null); } + + // Add to the loaded variables tvs.put(name, new NonNullPair<>(value, source)); + return false; } } - + variablesLock.writeLock().lock(); try { variables.setVariable(name, value); @@ -503,12 +669,20 @@ else if (loadConflicts == MAX_CONFLICT_WARNINGS + 1) variablesLock.writeLock().unlock(); } + // Move the variable to the right storage try { - for (final VariablesStorage s : storages) { - if (s.accept(name)) { - if (s != source) { - final Value v = serialize(value); - s.save(name, v != null ? v.type : null, v != null ? v.data : null); + for (VariablesStorage variablesStorage : STORAGES) { + if (variablesStorage.accept(name)) { + if (variablesStorage != source) { + // Serialize and set value in new storage + Value serializedValue = serialize(value); + if (serializedValue == null) { + variablesStorage.save(name, null, null); + } else { + variablesStorage.save(name, serializedValue.type, serializedValue.data); + } + + // Remove from old storage if (value != null) source.save(name, null, null); } @@ -516,85 +690,139 @@ else if (loadConflicts == MAX_CONFLICT_WARNINGS + 1) } } } catch (Exception e) { + //noinspection ThrowableNotThrown Skript.exception(e, "Error saving variable named " + name); } + return false; } - + /** - * Stores loaded variables into the variables map and the appropriate databases. + * Stores loaded variables into the variables map + * and the appropriate databases. * - * @return How many variables were not stored anywhere + * @return the amount of variables + * that don't have a storage that accepts them. */ @SuppressWarnings("null") private static int onStoragesLoaded() { if (loadConflicts > MAX_CONFLICT_WARNINGS) Skript.warning("A total of " + loadConflicts + " variables were loaded more than once from different databases"); + Skript.debug("Databases loaded, setting variables..."); - - synchronized (tempVars) { - final Map<String, NonNullPair<Object, VariablesStorage>> tvs = tempVars.get(); - tempVars.set(null); + + synchronized (TEMP_VARIABLES) { + Map<String, NonNullPair<Object, VariablesStorage>> tvs = TEMP_VARIABLES.get(); + TEMP_VARIABLES.set(null); assert tvs != null; + variablesLock.writeLock().lock(); try { - int n = 0; - for (final Entry<String, NonNullPair<Object, VariablesStorage>> tv : tvs.entrySet()) { + // Calculate the amount of variables that don't have a storage + int unstoredVariables = 0; + for (Entry<String, NonNullPair<Object, VariablesStorage>> tv : tvs.entrySet()) { if (!variableLoaded(tv.getKey(), tv.getValue().getFirst(), tv.getValue().getSecond())) - n++; + unstoredVariables++; } - - for (final VariablesStorage s : storages) - s.allLoaded(); - + + for (VariablesStorage variablesStorage : STORAGES) + variablesStorage.allLoaded(); + Skript.debug("Variables set. Queue size = " + saveQueue.size()); - - return n; + + return unstoredVariables; } finally { variablesLock.writeLock().unlock(); } } } - + + /** + * Creates a {@link SerializedVariable} from the given variable name + * and value. + * <p> + * Must be called from Bukkit's main thread. + * + * @param name the variable name. + * @param value the value. + * @return the serialized variable. + */ public static SerializedVariable serialize(String name, @Nullable Object value) { assert Bukkit.isPrimaryThread(); + + // First, serialize the variable. SerializedVariable.Value var; try { var = serialize(value); } catch (Exception e) { throw Skript.exception(e, "Error saving variable named " + name); } + return new SerializedVariable(name, var); } - + + /** + * Serializes the given value. + * <p> + * Must be called from Bukkit's main thread. + * + * @param value the value to serialize. + * @return the serialized value. + */ public static SerializedVariable.@Nullable Value serialize(@Nullable Object value) { assert Bukkit.isPrimaryThread(); + return Classes.serialize(value); } + /** + * Serializes and adds the variable change to the {@link #saveQueue}. + * + * @param name the variable name. + * @param value the value of the variable. + */ private static void saveVariableChange(String name, @Nullable Object value) { saveQueue.add(serialize(name, value)); } - + + /** + * The queue of serialized variables that have not yet been written + * to the storage. + */ static final BlockingQueue<SerializedVariable> saveQueue = new LinkedBlockingQueue<>(); - - static volatile boolean closed = false; - + + /** + * Whether the {@link #saveThread} should be stopped. + */ + private static volatile boolean closed = false; + + /** + * The thread that saves variables, i.e. stores in the appropriate storage. + */ private static final Thread saveThread = Skript.newThread(() -> { while (!closed) { try { // Save one variable change - SerializedVariable v = saveQueue.take(); - for (VariablesStorage s : storages) { - if (s.accept(v.name)) { - s.save(v); + SerializedVariable variable = saveQueue.take(); + + for (VariablesStorage variablesStorage : STORAGES) { + if (variablesStorage.accept(variable.name)) { + variablesStorage.save(variable); + break; } } - } catch (final InterruptedException ignored) {} + } catch (InterruptedException ignored) {} } }, "Skript variable save thread"); - + + /** + * Closes the variable systems: + * <ul> + * <li>Process all changes left in the {@link #changeQueue}.</li> + * <li>Stops the {@link #saveThread}.</li> + * </ul> + */ public static void close() { try { // Ensure that all changes are to save soon variablesLock.writeLock().lock(); @@ -602,16 +830,24 @@ public static void close() { } finally { variablesLock.writeLock().unlock(); } - + + // First, make sure all variables are saved while (saveQueue.size() > 0) { try { Thread.sleep(10); - } catch (final InterruptedException e) {} + } catch (InterruptedException ignored) {} } + + // Then we can safely interrupt and stop the thread closed = true; saveThread.interrupt(); } - + + /** + * Gets the amount of variables currently on the server. + * + * @return the amount of variables. + */ public static int numVariables() { try { variablesLock.readLock().lock(); @@ -620,5 +856,5 @@ public static int numVariables() { variablesLock.readLock().unlock(); } } - + } diff --git a/src/main/java/ch/njol/skript/variables/VariablesMap.java b/src/main/java/ch/njol/skript/variables/VariablesMap.java index 1b0ca621358..ef51ae3bec3 100644 --- a/src/main/java/ch/njol/skript/variables/VariablesMap.java +++ b/src/main/java/ch/njol/skript/variables/VariablesMap.java @@ -28,235 +28,345 @@ import java.util.Map.Entry; import java.util.TreeMap; +/** + * A map for storing variables in a sorted and efficient manner. + */ final class VariablesMap { - final static Comparator<String> variableNameComparator = new Comparator<String>() { - @Override - public int compare(@Nullable String s1, @Nullable String s2) { - if (s1 == null) - return s2 == null ? 0 : -1; - - if (s2 == null) - return 1; - - int i = 0; - int j = 0; - - boolean lastNumberNegative = false; - boolean afterDecimalPoint = false; - while (i < s1.length() && j < s2.length()) { - char c1 = s1.charAt(i); - char c2 = s2.charAt(j); - - if ('0' <= c1 && c1 <= '9' && '0' <= c2 && c2 <= '9') { - // Numbers/digits are treated differently from other characters. - - // The index after the last digit - int i2 = StringUtils.findLastDigit(s1, i); - int j2 = StringUtils.findLastDigit(s2, j); - - // Amount of leading zeroes - int z1 = 0; - int z2 = 0; - - // Skip leading zeroes (except for the last if all 0's) - if (!afterDecimalPoint) { - if (c1 == '0') { - while (i < i2 - 1 && s1.charAt(i) == '0') { - i++; - z1++; - } - } - if (c2 == '0') { - while (j < j2 - 1 && s2.charAt(j) == '0') { - j++; - z2++; - } - } - } - // Keep in mind that c1 and c2 may not have the right value (e.g. s1.charAt(i)) for the rest of this block + /** + * The comparator for comparing variable names. + */ + static final Comparator<String> VARIABLE_NAME_COMPARATOR = (s1, s2) -> { + if (s1 == null) + return s2 == null ? 0 : -1; - // If the number is prefixed by a '-', it should be treated as negative, thus inverting the order. - // If the previous number was negative, and the only thing separating them was a '.', - // then this number should also be in inverted order. - boolean previousNegative = lastNumberNegative; + if (s2 == null) + return 1; - // i - z1 contains the first digit, so i - z1 - 1 may contain a `-` indicating this number is negative - lastNumberNegative = i - z1 > 0 && s1.charAt(i - z1 - 1) == '-'; - int isPositive = (lastNumberNegative | previousNegative) ? -1 : 1; + int i = 0; + int j = 0; - // Different length numbers (99 > 9) - if (!afterDecimalPoint && i2 - i != j2 - j) - return ((i2 - i) - (j2 - j)) * isPositive; + boolean lastNumberNegative = false; + boolean afterDecimalPoint = false; + while (i < s1.length() && j < s2.length()) { + char c1 = s1.charAt(i); + char c2 = s2.charAt(j); - // Iterate over the digits - while (i < i2 && j < j2) { - char d1 = s1.charAt(i); - char d2 = s2.charAt(j); + if ('0' <= c1 && c1 <= '9' && '0' <= c2 && c2 <= '9') { + // Numbers/digits are treated differently from other characters. - // If the digits differ, return a value dependent on the sign - if (d1 != d2) - return (d1 - d2) * isPositive; + // The index after the last digit + int i2 = StringUtils.findLastDigit(s1, i); + int j2 = StringUtils.findLastDigit(s2, j); - i++; - j++; + // Amount of leading zeroes + int z1 = 0; + int z2 = 0; + + // Skip leading zeroes (except for the last if all 0's) + if (!afterDecimalPoint) { + if (c1 == '0') { + while (i < i2 - 1 && s1.charAt(i) == '0') { + i++; + z1++; + } } + if (c2 == '0') { + while (j < j2 - 1 && s2.charAt(j) == '0') { + j++; + z2++; + } + } + } + // Keep in mind that c1 and c2 may not have the right value (e.g. s1.charAt(i)) for the rest of this block - // Different length numbers (1.99 > 1.9) - if (afterDecimalPoint && i2 - i != j2 - j) - return ((i2 - i) - (j2 - j)) * isPositive; + // If the number is prefixed by a '-', it should be treated as negative, thus inverting the order. + // If the previous number was negative, and the only thing separating them was a '.', + // then this number should also be in inverted order. + boolean previousNegative = lastNumberNegative; - // If the numbers are equal, but either has leading zeroes, - // more leading zeroes is a lesser number (01 < 1) - if (z1 != 0 || z2 != 0) - return (z1 - z2) * isPositive; + // i - z1 contains the first digit, so i - z1 - 1 may contain a `-` indicating this number is negative + lastNumberNegative = i - z1 > 0 && s1.charAt(i - z1 - 1) == '-'; + int isPositive = (lastNumberNegative | previousNegative) ? -1 : 1; - afterDecimalPoint = true; - } else { - // Normal characters - if (c1 != c2) - return c1 - c2; - - // Reset the last number flags if we're exiting a number. - if (c1 != '.') { - lastNumberNegative = false; - afterDecimalPoint = false; - } + // Different length numbers (99 > 9) + if (!afterDecimalPoint && i2 - i != j2 - j) + return ((i2 - i) - (j2 - j)) * isPositive; + + // Iterate over the digits + while (i < i2 && j < j2) { + char d1 = s1.charAt(i); + char d2 = s2.charAt(j); + + // If the digits differ, return a value dependent on the sign + if (d1 != d2) + return (d1 - d2) * isPositive; i++; j++; } + + // Different length numbers (1.99 > 1.9) + if (afterDecimalPoint && i2 - i != j2 - j) + return ((i2 - i) - (j2 - j)) * isPositive; + + // If the numbers are equal, but either has leading zeroes, + // more leading zeroes is a lesser number (01 < 1) + if (z1 != z2) + return (z1 - z2) * isPositive; + + afterDecimalPoint = true; + } else { + // Normal characters + if (c1 != c2) + return c1 - c2; + + // Reset the last number flags if we're exiting a number. + if (c1 != '.') { + lastNumberNegative = false; + afterDecimalPoint = false; + } + + i++; + j++; } - if (i < s1.length()) - return lastNumberNegative ? -1 : 1; - if (j < s2.length()) - return lastNumberNegative ? 1 : -1; - return 0; } + if (i < s1.length()) + return lastNumberNegative ? -1 : 1; + if (j < s2.length()) + return lastNumberNegative ? 1 : -1; + return 0; }; + /** + * The map that stores all non-list variables. + */ final HashMap<String, Object> hashMap = new HashMap<>(); + /** + * The tree of variables, branched by the list structure of the variables. + */ final TreeMap<String, Object> treeMap = new TreeMap<>(); - + /** * Returns the internal value of the requested variable. * <p> * <b>Do not modify the returned value!</b> - * - * @param name - * @return an Object for a normal Variable or a Map<String, Object> for a list variable, or null if the variable is not set. + * + * @param name the name of the variable, possibly a list variable. + * @return an {@link Object} for a normal variable or a + * {@code Map<String, Object>} for a list variable, + * or {@code null} if the variable is not set. */ @SuppressWarnings("unchecked") @Nullable - final Object getVariable(String name) { + Object getVariable(String name) { if (!name.endsWith("*")) { + // Not a list variable, quick access from the hash map return hashMap.get(name); } else { + // List variable, search the tree branches String[] split = Variables.splitVariableName(name); - Map<String, Object> current = treeMap; + Map<String, Object> parent = treeMap; + + // Iterate over the parts of the variable name for (int i = 0; i < split.length; i++) { String n = split[i]; if (n.equals("*")) { + // End of variable name, return map assert i == split.length - 1; - return current; + return parent; } - Object o = current.get(n); - if (o == null) + + // Check if the current (sub-)tree has the expected child node + Object childNode = parent.get(n); + if (childNode == null) return null; - if (o instanceof Map) { - current = (Map<String, Object>) o; + + // Continue the iteration if the child node is a tree itself + if (childNode instanceof Map) { + // Continue iterating with the subtree + parent = (Map<String, Object>) childNode; assert i != split.length - 1; - continue; } else { + // ..., otherwise the list variable doesn't exist here return null; } } return null; } } - + /** - * Sets a variable. - * - * @param name The variable's name. Can be a "list variable::*" (<tt>value</tt> must be <tt>null</tt> in this case) - * @param value The variable's value. Use <tt>null</tt> to delete the variable. + * Sets the given variable to the given value. + * <p> + * This method accepts list variables, + * but these may only be set to {@code null}. + * + * @param name the variable name. + * @param value the variable value, {@code null} to delete the variable. */ @SuppressWarnings("unchecked") - final void setVariable(String name, @Nullable Object value) { + void setVariable(String name, @Nullable Object value) { + // First update the hash map easily if (!name.endsWith("*")) { if (value == null) hashMap.remove(name); else hashMap.put(name, value); } + + // Then update the tree map by going down the branches String[] split = Variables.splitVariableName(name); TreeMap<String, Object> parent = treeMap; + + // Iterate over the parts of the variable name for (int i = 0; i < split.length; i++) { - String n = split[i]; - Object current = parent.get(n); - if (current == null) { + String childNodeName = split[i]; + Object childNode = parent.get(childNodeName); + + if (childNode == null) { + // Expected child node not found if (i == split.length - 1) { + // End of the variable name reached, set variable if needed if (value != null) - parent.put(n, value); + parent.put(childNodeName, value); + break; } else if (value != null) { - parent.put(n, current = new TreeMap<>(variableNameComparator)); - parent = (TreeMap<String, Object>) current; - continue; + // Create child node, add it to parent and continue iteration + childNode = new TreeMap<>(VARIABLE_NAME_COMPARATOR); + + parent.put(childNodeName, childNode); + parent = (TreeMap<String, Object>) childNode; } else { + // Want to set variable to null, bu variable is already null break; } - } else if (current instanceof TreeMap) { + } else if (childNode instanceof TreeMap) { + // Child node found + TreeMap<String, Object> childNodeMap = ((TreeMap<String, Object>) childNode); + if (i == split.length - 1) { + // End of variable name reached, adjust child node accordingly if (value == null) - ((TreeMap<String, Object>) current).remove(null); + childNodeMap.remove(null); else - ((TreeMap<String, Object>) current).put(null, value); + childNodeMap.put(null, value); + break; } else if (i == split.length - 2 && split[i + 1].equals("*")) { + // Second to last part of variable name assert value == null; - deleteFromHashMap(StringUtils.join(split, Variable.SEPARATOR, 0, i + 1), (TreeMap<String, Object>) current); - Object v = ((TreeMap<String, Object>) current).get(null); - if (v == null) - parent.remove(n); + + // Delete all indices of the list variable from hashMap + deleteFromHashMap(StringUtils.join(split, Variable.SEPARATOR, 0, i + 1), childNodeMap); + + // If the list variable itself has a value , + // e.g. list `{mylist::3}` while variable `{mylist}` also has a value, + // then adjust the parent for that + Object currentChildValue = childNodeMap.get(null); + if (currentChildValue == null) + parent.remove(childNodeName); else - parent.put(n, v); + parent.put(childNodeName, currentChildValue); + break; } else { - parent = (TreeMap<String, Object>) current; - continue; + // Continue iteration + parent = childNodeMap; } } else { + // Ran into leaf node if (i == split.length - 1) { + // If we arrived at the end of the variable name, update parent if (value == null) - parent.remove(n); + parent.remove(childNodeName); else - parent.put(n, value); + parent.put(childNodeName, value); + break; } else if (value != null) { - TreeMap<String, Object> c = new TreeMap<>(variableNameComparator); - c.put(null, current); - parent.put(n, c); - parent = c; - continue; + // Need to continue iteration, create new child node and put old value in it + TreeMap<String, Object> newChildNodeMap = new TreeMap<>(VARIABLE_NAME_COMPARATOR); + newChildNodeMap.put(null, childNode); + + // Add new child node to parent + parent.put(childNodeName, newChildNodeMap); + parent = newChildNodeMap; } else { break; } } } } - + + /** + * Deletes all indices of a list variable from the {@link #hashMap}. + * + * @param parent the list variable prefix, + * e.g. {@code list} for {@code list::*}. + * @param current the map of the list variable. + */ @SuppressWarnings("unchecked") void deleteFromHashMap(String parent, TreeMap<String, Object> current) { for (Entry<String, Object> e : current.entrySet()) { if (e.getKey() == null) continue; - hashMap.remove(parent + Variable.SEPARATOR + e.getKey()); + String childName = parent + Variable.SEPARATOR + e.getKey(); + + // Remove from hashMap + hashMap.remove(childName); + + // Recurse if needed Object val = e.getValue(); if (val instanceof TreeMap) { - deleteFromHashMap(parent + Variable.SEPARATOR + e.getKey(), (TreeMap<String, Object>) val); + deleteFromHashMap(childName, (TreeMap<String, Object>) val); } } } - + + /** + * Creates a copy of this map. + * + * @return the copy. + */ + public VariablesMap copy() { + VariablesMap copy = new VariablesMap(); + + copy.hashMap.putAll(hashMap); + + TreeMap<String, Object> treeMapCopy = copyTreeMap(treeMap); + copy.treeMap.putAll(treeMapCopy); + + return copy; + } + + /** + * Makes a deep copy of the given {@link TreeMap}. + * <p> + * The 'deep copy' means that each subtree of the given tree is copied + * as well. + * + * @param original the original tree map. + * @return the copy. + */ + @SuppressWarnings("unchecked") + private static TreeMap<String, Object> copyTreeMap(TreeMap<String, Object> original) { + TreeMap<String, Object> copy = new TreeMap<>(VARIABLE_NAME_COMPARATOR); + + for (Entry<String, Object> child : original.entrySet()) { + String key = child.getKey(); + Object value = child.getValue(); + + // Copy by recursion if the child is a TreeMap + if (value instanceof TreeMap) { + value = copyTreeMap((TreeMap<String, Object>) value); + } + + copy.put(key, value); + } + + return copy; + } + } diff --git a/src/main/java/ch/njol/skript/variables/VariablesStorage.java b/src/main/java/ch/njol/skript/variables/VariablesStorage.java index b706e512a10..f7403e3985e 100644 --- a/src/main/java/ch/njol/skript/variables/VariablesStorage.java +++ b/src/main/java/ch/njol/skript/variables/VariablesStorage.java @@ -18,14 +18,6 @@ */ package ch.njol.skript.variables; -import java.io.File; -import java.io.IOException; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; - -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.config.SectionNode; import ch.njol.skript.lang.ParseContext; @@ -37,261 +29,431 @@ import ch.njol.skript.util.Timespan; import ch.njol.skript.variables.SerializedVariable.Value; import ch.njol.util.Closeable; +import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Contract; -// FIXME ! large databases (>25 MB) cause the server to be unresponsive instead of loading slowly +import java.io.File; +import java.io.IOException; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; /** - * @author Peter Güttinger + * A variable storage is holds the means and methods of storing variables. + * <p> + * This is usually some sort of database, and could be as simply as a text file. + * + * @see FlatFileStorage + * @see DatabaseStorage */ +// FIXME ! large databases (>25 MB) cause the server to be unresponsive instead of loading slowly public abstract class VariablesStorage implements Closeable { - - private final static int QUEUE_SIZE = 1000, FIRST_WARNING = 300; - + + /** + * The size of the variable changes queue. + */ + private static final int QUEUE_SIZE = 1000; + /** + * The threshold of the size of the variable change + * after which a warning will be sent. + */ + private static final int FIRST_WARNING = 300; + final LinkedBlockingQueue<SerializedVariable> changesQueue = new LinkedBlockingQueue<>(QUEUE_SIZE); - + + /** + * Whether this variable storage has been {@link #close() closed}. + */ protected volatile boolean closed = false; - + + /** + * The name of the database, i.e. this storage. + */ protected final String databaseName; - + + /** + * The file associated with this variable storage. + * Can be {@code null} if no file is required. + */ @Nullable protected File file; - + /** - * null for '.*' or '.+' + * The pattern of the variable name this storage accepts. + * {@code null} for '{@code .*}' or '{@code .*}'. */ @Nullable - private Pattern variablePattern; - + private Pattern variableNamePattern; + + /** + * The thread used for writing variables to the storage. + */ // created in the constructor, started in load() private final Thread writeThread; - - protected VariablesStorage(final String name) { + + /** + * Creates a new variable storage with the given name. + * <p> + * This will also create the {@link #writeThread}, but it must be started + * with {@link #load(SectionNode)}. + * + * @param name the name. + */ + protected VariablesStorage(String name) { databaseName = name; - writeThread = Skript.newThread(new Runnable() { - @Override - public void run() { - while (!closed) { - try { - final SerializedVariable var = changesQueue.take(); - final Value d = var.value; - if (d != null) - save(var.name, d.type, d.data); - else - save(var.name, null, null); - } catch (final InterruptedException e) {} + + writeThread = Skript.newThread(() -> { + while (!closed) { + try { + // Take a variable from the queue and process it + SerializedVariable variable = changesQueue.take(); + Value value = variable.value; + + // Actually save the variable + if (value != null) + save(variable.name, value.type, value.data); + else + save(variable.name, null, null); + } catch (InterruptedException ignored) { + // Ignored as the `closed` field will indicate whether the thread actually needs to stop } } }, "Skript variable save thread for database '" + name + "'"); } - + + /** + * Gets the string value at the given key of the given section node. + * + * @param sectionNode the section node. + * @param key the key. + * @return the value, or {@code null} if the value was invalid, + * or not found. + */ @Nullable - protected String getValue(final SectionNode n, final String key) { - return getValue(n, key, String.class); + protected String getValue(SectionNode sectionNode, String key) { + return getValue(sectionNode, key, String.class); } - + + /** + * Gets the value at the given key of the given section node, + * parsed with the given type. + * + * @param sectionNode the section node. + * @param key the key. + * @param type the type. + * @return the parsed value, or {@code null} if the value was invalid, + * or not found. + * @param <T> the type. + */ @Nullable - protected <T> T getValue(final SectionNode n, final String key, final Class<T> type) { - final String v = n.getValue(key); - if (v == null) { + protected <T> T getValue(SectionNode sectionNode, String key, Class<T> type) { + String rawValue = sectionNode.getValue(key); + // Section node doesn't have this key + if (rawValue == null) { Skript.error("The config is missing the entry for '" + key + "' in the database '" + databaseName + "'"); return null; } - final ParseLogHandler log = SkriptLogger.startParseLogHandler(); - try { - final T r = Classes.parse(v, type, ParseContext.CONFIG); - if (r == null) - log.printError("The entry for '" + key + "' in the database '" + databaseName + "' must be " + Classes.getSuperClassInfo(type).getName().withIndefiniteArticle()); + + try (ParseLogHandler log = SkriptLogger.startParseLogHandler()) { + T parsedValue = Classes.parse(rawValue, type, ParseContext.CONFIG); + + if (parsedValue == null) + // Parsing failed + log.printError("The entry for '" + key + "' in the database '" + databaseName + "' must be " + + Classes.getSuperClassInfo(type).getName().withIndefiniteArticle()); else log.printLog(); - return r; - } finally { - log.stop(); + + return parsedValue; } } - - public final boolean load(final SectionNode n) { - final String pattern = getValue(n, "pattern"); + + /** + * Loads the configuration for this variable storage + * from the given section node. + * + * @param sectionNode the section node. + * @return whether the loading succeeded. + */ + public final boolean load(SectionNode sectionNode) { + String pattern = getValue(sectionNode, "pattern"); if (pattern == null) return false; + try { - variablePattern = pattern.equals(".*") || pattern.equals(".+") ? null : Pattern.compile(pattern); - } catch (final PatternSyntaxException e) { + // Set variable name pattern, see field javadoc for explanation of null value + variableNamePattern = pattern.equals(".*") || pattern.equals(".+") ? null : Pattern.compile(pattern); + } catch (PatternSyntaxException e) { Skript.error("Invalid pattern '" + pattern + "': " + e.getLocalizedMessage()); return false; } - + if (requiresFile()) { - final String f = getValue(n, "file"); - if (f == null) + // Initialize file + String fileName = getValue(sectionNode, "file"); + if (fileName == null) return false; - final File file = getFile(f).getAbsoluteFile(); - this.file = file; + + this.file = getFile(fileName).getAbsoluteFile(); + if (file.exists() && !file.isFile()) { Skript.error("The database file '" + file.getName() + "' must be an actual file, not a directory."); return false; - } else { - try { - file.createNewFile(); - } catch (final IOException e) { - Skript.error("Cannot create the database file '" + file.getName() + "': " + e.getLocalizedMessage()); - return false; - } } + + // Create the file if it does not exist yet + try { + //noinspection ResultOfMethodCallIgnored + file.createNewFile(); + } catch (IOException e) { + Skript.error("Cannot create the database file '" + file.getName() + "': " + e.getLocalizedMessage()); + return false; + } + + // Check for read & write permissions to the file if (!file.canWrite()) { Skript.error("Cannot write to the database file '" + file.getName() + "'!"); return false; } if (!file.canRead()) { Skript.error("Cannot read from the database file '" + file.getName() + "'!"); -// Skript.error("This means that no variables will be available and can also prevent new variables from being saved!"); -// try { -// final File backup = FileUtils.backup(file); -// Skript.error("A backup of your variables.csv was created as " + backup.getName()); -// } catch (final IOException e) { -// Skript.error("Failed to create a backup of your variables.csv: " + e.getLocalizedMessage()); -// loadError = true; -// } return false; } - - if (!"0".equals(getValue(n, "backup interval"))) { - final Timespan backupInterval = getValue(n, "backup interval", Timespan.class); + + // Set the backup interval, if present & enabled + if (!"0".equals(getValue(sectionNode, "backup interval"))) { + Timespan backupInterval = getValue(sectionNode, "backup interval", Timespan.class); + if (backupInterval != null) startBackupTask(backupInterval); } } - - if (!load_i(n)) + + // Load the entries custom to the variable storage + if (!load_i(sectionNode)) return false; - + writeThread.start(); Skript.closeOnDisable(this); - + return true; } - + /** * Loads variables stored here. - * - * @return Whether the database could be loaded successfully, i.e. whether the config is correct and all variables could be loaded + * + * @return Whether the database could be loaded successfully, + * i.e. whether the config is correct and all variables could be loaded. */ protected abstract boolean load_i(SectionNode n); - + /** - * Called after all storages have been loaded, and variables have been redistributed if settings have changed. This should commit the first transaction (which is not empty if - * variables have been moved from another database to this one or vice versa), and start repeating transactions if applicable. + * Called after all storages have been loaded, and variables + * have been redistributed if settings have changed. + * This should commit the first transaction (which is not empty if + * variables have been moved from another database to this one or vice versa), + * and start repeating transactions if applicable. */ protected abstract void allLoaded(); - + + /** + * Checks if this storage requires a file for storing its data. + * + * @return if this storage needs a file. + */ protected abstract boolean requiresFile(); - - protected abstract File getFile(String file); - + + /** + * Gets the file needed for this variable storage from the given file name. + * <p> + * Will only be called if {@link #requiresFile()} is {@code true}. + * + * @param fileName the given file name. + * @return the {@link File} object. + */ + protected abstract File getFile(String fileName); + /** - * Must be locked after {@link Variables#getReadLock()} (if that lock is used at all) + * Must be locked after {@link Variables#getReadLock()} + * (if that lock is used at all). */ protected final Object connectionLock = new Object(); - + /** - * (Re)connects to the database (not called on the first connect - do this in {@link #load_i(SectionNode)}). - * - * @return Whether the connection could be re-established. An error should be printed by this method prior to returning false. + * (Re)connects to the database. + * <p> + * Not called on the first connect: do this in {@link #load_i(SectionNode)}. + * An error should be printed by this method + * prior to returning {@code false}. + * + * @return whether the connection could be re-established. */ protected abstract boolean connect(); - + /** * Disconnects from the database. */ protected abstract void disconnect(); - + + /** + * The backup task, or {@code null} if automatic backups are disabled. + */ @Nullable protected Task backupTask = null; - - public void startBackupTask(final Timespan t) { - final File file = this.file; - if (file == null || t.getTicks_i() == 0) + + /** + * Starts the backup task, with the given backup interval. + * + * @param backupInterval the backup interval. + */ + public void startBackupTask(Timespan backupInterval) { + // File is null or backup interval is invalid + if (file == null || backupInterval.getTicks_i() == 0) return; - backupTask = new Task(Skript.getInstance(), t.getTicks_i(), t.getTicks_i(), true) { + + backupTask = new Task(Skript.getInstance(), backupInterval.getTicks_i(), backupInterval.getTicks_i(), true) { @Override public void run() { synchronized (connectionLock) { + // Disconnect, disconnect(); try { + // ..., then backup FileUtils.backup(file); - } catch (final IOException e) { + } catch (IOException e) { Skript.error("Automatic variables backup failed: " + e.getLocalizedMessage()); } finally { + // ... and reconnect connect(); } } } }; } - - boolean accept(final @Nullable String var) { + + /** + * Checks if this variable storage accepts the given variable name. + * + * @param var the variable name. + * @return if this storage accepts the variable name. + * + * @see #variableNamePattern + */ + boolean accept(@Nullable String var) { if (var == null) return false; - return variablePattern != null ? variablePattern.matcher(var).matches() : true; + + return variableNamePattern == null || variableNamePattern.matcher(var).matches(); } - + + /** + * The interval between warnings that many variables are being written + * at once, in seconds. + */ + private static final int WARNING_INTERVAL = 10; + /** + * The interval between errors that too many variables are being written + * at once, in seconds. + */ + private static final int ERROR_INTERVAL = 10; + + /** + * The last time a warning was printed for many variables in the queue. + */ private long lastWarning = Long.MIN_VALUE; - private final static int WARNING_INTERVAL = 10; + /** + * The last time an error was printed for too many variables in the queue. + */ private long lastError = Long.MIN_VALUE; - private final static int ERROR_INTERVAL = 10; - + /** + * Saves the given serialized variable. + * <p> * May be called from a different thread than Bukkit's main thread. + * + * @param var the serialized variable. */ - final void save(final SerializedVariable var) { + final void save(SerializedVariable var) { if (changesQueue.size() > FIRST_WARNING && lastWarning < System.currentTimeMillis() - WARNING_INTERVAL * 1000) { - Skript.warning("Cannot write variables to the database '" + databaseName + "' at sufficient speed; server performance may suffer and many variables will be lost if the server crashes. (this warning will be repeated at most once every " + WARNING_INTERVAL + " seconds)"); + // Too many variables queued up to save, warn the server + Skript.warning("Cannot write variables to the database '" + databaseName + "' at sufficient speed; " + + "server performance may suffer and many variables will be lost if the server crashes. " + + "(this warning will be repeated at most once every " + WARNING_INTERVAL + " seconds)"); + lastWarning = System.currentTimeMillis(); } + if (!changesQueue.offer(var)) { + // Variable changes queue filled up + if (lastError < System.currentTimeMillis() - ERROR_INTERVAL * 1000) { - Skript.error("Skript cannot save any variables to the database '" + databaseName + "'. The server will hang and may crash if no more variables can be saved."); + // Inform console about overload of variable changes + Skript.error("Skript cannot save any variables to the database '" + databaseName + "'. " + + "The server will hang and may crash if no more variables can be saved."); + lastError = System.currentTimeMillis(); } + + // Halt thread until variables queue starts clearing up while (true) { try { // REMIND add repetitive error and/or stop saving variables altogether? changesQueue.put(var); break; - } catch (final InterruptedException e) {} + } catch (InterruptedException ignored) {} } } } - + /** - * Called when Skript gets disabled. The default implementation will wait for all variables to be saved before setting {@link #closed} to true and stopping the write thread, - * thus <tt>super.close()</tt> must be called if this method is overridden! + * Called when Skript gets disabled. + * <p> + * The default implementation will wait for all variables to be saved + * before setting {@link #closed} to {@code true} and stopping + * the {@link #writeThread write thread}. + * <p> + * Therefore, make sure to call {@code super.close()} + * if this method is overridden. */ @Override public void close() { + // Wait for all variable changes to be processed while (changesQueue.size() > 0) { try { Thread.sleep(10); - } catch (final InterruptedException e) {} + } catch (InterruptedException ignored) {} } + + // Now safely close storage and interrupt thread closed = true; writeThread.interrupt(); } - + /** - * Clears the saveQueue of unsaved variables. Only used if all variables are saved immediately after calling this method. + * Clears the {@link #changesQueue queue} of unsaved variables. + * <p> + * Only used if all variables are saved immediately + * after calling this method. */ protected void clearChangesQueue() { changesQueue.clear(); } - + /** - * Saves a variable. This is called from the main thread while variables are transferred between databases, and from the {@link #writeThread} afterwards. - * - * @param name - * @param type - * @param value - * @return Whether the variable was saved + * Saves a variable. + * <p> + * This is called from the main thread + * while variables are transferred between databases, + * and from the {@link #writeThread} afterwards. + * <p> + * {@code type} and {@code value} are <i>both</i> {@code null} + * iff this call is to delete the variable. + * + * @param name the name of the variable. + * @param type the type of the variable. + * @param value the serialized value of the variable. + * @return Whether the variable was saved. */ protected abstract boolean save(String name, @Nullable String type, @Nullable byte[] value); - + } From 4e3e4eb26a22b60db1070623a2a3042672d86533 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Fri, 24 Mar 2023 17:25:14 -0600 Subject: [PATCH 288/619] Fix profiler causing exception when building (#5543) --- build.gradle | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index 14fa0d5a392..979ec25da8d 100644 --- a/build.gradle +++ b/build.gradle @@ -217,11 +217,15 @@ void createTestTask(String name, String desc, String environments, int javaVersi project.findProperty('verbosity') ?: "null" ] - if (!gradle.taskGraph.hasTask(":tasks") && !gradle.startParameter.dryRun && modifiers.contains(Modifiers.PROFILE)) { - if (!project.hasProperty('profiler')) - throw new MissingPropertyException('Add parameter -Pprofiler=<path to profiler>', 'profiler', String.class) - - args += '-agentpath:' + project.property('profiler') + '=port=8849,nowait' + // Do first is used when throwing exceptions. + // This way it's not called when defining the task. + doFirst { + if (!gradle.taskGraph.hasTask(":tasks") && !gradle.startParameter.dryRun && modifiers.contains(Modifiers.PROFILE)) { + if (!project.hasProperty('profiler')) + throw new MissingPropertyException('Add parameter -Pprofiler=<path to profiler>', 'profiler', String.class) + + args += '-agentpath:' + project.property('profiler') + '=port=8849,nowait' + } } } } From 223f3006f1eb841e575977d0d2b6805c43531056 Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Sat, 25 Mar 2023 07:47:26 +0300 Subject: [PATCH 289/619] Fix Entity Changer (#5476) --- .../skript/classes/data/DefaultChangers.java | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultChangers.java b/src/main/java/ch/njol/skript/classes/data/DefaultChangers.java index de48c85685a..dafcf8c4c78 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultChangers.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultChangers.java @@ -77,6 +77,7 @@ public void change(final Entity[] entities, final @Nullable Object[] delta, fina } return; } + boolean hasItem = false; for (final Entity e : entities) { for (final Object d : delta) { if (d instanceof PotionEffectType) { @@ -90,16 +91,18 @@ public void change(final Entity[] entities, final @Nullable Object[] delta, fina if (d instanceof Experience) { p.giveExp(((Experience) d).getXP()); } else if (d instanceof Inventory) { - final PlayerInventory invi = p.getInventory(); - if (mode == ChangeMode.ADD) { - for (final ItemStack i : (Inventory) d) { - if (i != null) - invi.addItem(i); + PlayerInventory inventory = p.getInventory(); + for (ItemStack itemStack : (Inventory) d) { + if (itemStack == null) + continue; + if (mode == ChangeMode.ADD) { + inventory.addItem(itemStack); + } else { + inventory.remove(itemStack); } - } else { - invi.removeItem(((Inventory) d).getContents()); } } else if (d instanceof ItemType) { + hasItem = true; final PlayerInventory invi = p.getInventory(); if (mode == ChangeMode.ADD) ((ItemType) d).addTo(invi); @@ -111,7 +114,7 @@ else if (mode == ChangeMode.REMOVE) } } } - if (e instanceof Player) + if (e instanceof Player && hasItem) PlayerUtils.updateInventory((Player) e); } } @@ -259,7 +262,10 @@ public void change(final Inventory[] invis, final @Nullable Object[] delta, fina for (final Object d : delta) { if (d instanceof Inventory) { assert mode == ChangeMode.REMOVE; - invi.removeItem(((Inventory) d).getContents()); + for (ItemStack itemStack : (Inventory) d) { + if (itemStack != null) + invi.removeItem(itemStack); + } } else { if (mode == ChangeMode.REMOVE) ((ItemType) d).removeFrom(invi); From 72759882b52e14c81277a133cd8601e7f2046bf0 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Fri, 24 Mar 2023 23:23:17 -0600 Subject: [PATCH 290/619] Add missing .lang values for 1.19.4 (#5548) --- src/main/resources/lang/default.lang | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 5e660d62c89..03307a009d0 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -1221,6 +1221,25 @@ entities: camel: name: camel¦s pattern: camel(|1¦s) + # 1.19.4 Entities + sniffer: + name: sniffer¦s + pattern: sniffer(|1¦s) + interaction: + name: interaction¦s + pattern: interaction(|1¦s) + display: + name: display¦s + pattern: display(|1¦s) + block display: + name: block display¦s + pattern: block display(|1¦s) + item display: + name: item display¦s + pattern: item display(|1¦s) + text display: + name: text display¦s + pattern: text display(|1¦s) # -- Heal Reasons -- heal reasons: From 1fd5265d9d5c485fb185b4a1f9061929e3689a33 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Fri, 24 Mar 2023 23:33:57 -0600 Subject: [PATCH 291/619] Bump version to 2.7.0-beta2 (#5509) --- gradle.properties | 2 +- .../java/ch/njol/skript/expressions/ExprTotalExperience.java | 2 +- src/main/java/ch/njol/skript/test/runner/EffDebug.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index f200caba110..220737a816b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ groupid=ch.njol name=skript -version=2.7.0-beta1 +version=2.7.0-beta2 jarName=Skript.jar testEnv=java17/paper-1.19.4 testEnvJavaVersion=17 diff --git a/src/main/java/ch/njol/skript/expressions/ExprTotalExperience.java b/src/main/java/ch/njol/skript/expressions/ExprTotalExperience.java index 38138933994..34d8141687a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTotalExperience.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTotalExperience.java @@ -45,7 +45,7 @@ "\tset player's total experience to 0", "\tgive player 1 diamond" }) -@Since("INSERT VERSION") +@Since("2.7") public class ExprTotalExperience extends SimplePropertyExpression<Entity, Integer> { static { diff --git a/src/main/java/ch/njol/skript/test/runner/EffDebug.java b/src/main/java/ch/njol/skript/test/runner/EffDebug.java index 0a624eb03f5..7b0ca6ef9a3 100644 --- a/src/main/java/ch/njol/skript/test/runner/EffDebug.java +++ b/src/main/java/ch/njol/skript/test/runner/EffDebug.java @@ -45,7 +45,7 @@ "Can also debug what class an input is parsed as for Conditions and Effects.", "Useful for dealing with conflict debugging." }) -@Since("INSERT VERSION") +@Since("2.7") @NoDoc public class EffDebug extends Effect { From 80dc56a8f09e8762480ae4913bd52377d27e40ec Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Thu, 30 Mar 2023 03:23:41 -0600 Subject: [PATCH 292/619] Remove final on isSingle PropertyExpression (#5455) Remove final on isSingle --- .../ch/njol/skript/expressions/base/PropertyExpression.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/expressions/base/PropertyExpression.java b/src/main/java/ch/njol/skript/expressions/base/PropertyExpression.java index 1a41665c3d7..1bc06c9e7ce 100644 --- a/src/main/java/ch/njol/skript/expressions/base/PropertyExpression.java +++ b/src/main/java/ch/njol/skript/expressions/base/PropertyExpression.java @@ -120,7 +120,7 @@ protected T[] get(final F[] source, final ch.njol.skript.classes.Converter<? sup } @Override - public final boolean isSingle() { + public boolean isSingle() { return expr.isSingle(); } From 238c4df42f1f55c7b4f08ef76c438cdf66d2ab4c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Apr 2023 23:55:34 -0600 Subject: [PATCH 293/619] Bump org.bstats:bstats-bukkit from 3.0.1 to 3.0.2 (#5578) Bumps [org.bstats:bstats-bukkit](https://github.com/Bastian/bStats-Metrics) from 3.0.1 to 3.0.2. - [Release notes](https://github.com/Bastian/bStats-Metrics/releases) - [Commits](https://github.com/Bastian/bStats-Metrics/compare/v3.0.1...v3.0.2) --- updated-dependencies: - dependency-name: org.bstats:bstats-bukkit dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 979ec25da8d..081f1c9f84a 100644 --- a/build.gradle +++ b/build.gradle @@ -26,7 +26,7 @@ allprojects { dependencies { shadow group: 'io.papermc', name: 'paperlib', version: '1.0.8' - shadow group: 'org.bstats', name: 'bstats-bukkit', version: '3.0.1' + shadow group: 'org.bstats', name: 'bstats-bukkit', version: '3.0.2' implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.19.4-R0.1-SNAPSHOT' implementation group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.700' implementation group: 'com.google.code.findbugs', name: 'findbugs', version: '3.0.1' From 0d44f0005db22f54252d78920a030be51cf9f340 Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Thu, 6 Apr 2023 01:40:56 +0300 Subject: [PATCH 294/619] ExprRandomNumber ArithmeticException fix (#5443) --- .../skript/expressions/ExprRandomNumber.java | 85 ++++++++++--------- 1 file changed, 45 insertions(+), 40 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprRandomNumber.java b/src/main/java/ch/njol/skript/expressions/ExprRandomNumber.java index d5a15a5b464..79ed6b650b8 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprRandomNumber.java +++ b/src/main/java/ch/njol/skript/expressions/ExprRandomNumber.java @@ -19,7 +19,9 @@ package ch.njol.skript.expressions; import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; +import ch.njol.util.Math2; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @@ -33,68 +35,71 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; -import ch.njol.util.Math2; -/** - * @author Peter Güttinger - */ @Name("Random Number") -@Description({"A random number or integer between two given numbers. Use 'number' if you want any number with decimal parts, or use use 'integer' if you only want whole numbers.", - "Please note that the order of the numbers doesn't matter, i.e. <code>random number between 2 and 1</code> will work as well as <code>random number between 1 and 2</code>."}) -@Examples({"set the player's health to a random number between 5 and 10", - "send \"You rolled a %random integer from 1 to 6%!\" to the player"}) +@Description({ + "A random number or integer between two given numbers. Use 'number' if you want any number with decimal parts, or use use 'integer' if you only want whole numbers.", + "Please note that the order of the numbers doesn't matter, i.e. <code>random number between 2 and 1</code> will work as well as <code>random number between 1 and 2</code>." +}) +@Examples({ + "set the player's health to a random number between 5 and 10", + "send \"You rolled a %random integer from 1 to 6%!\" to the player" +}) @Since("1.4") public class ExprRandomNumber extends SimpleExpression<Number> { + static { Skript.registerExpression(ExprRandomNumber.class, Number.class, ExpressionType.COMBINED, - "[a] random (1¦integer|2¦number) (from|between) %number% (to|and) %number%"); + "[a] random (:integer|number) (from|between) %number% (to|and) %number%"); } - - @SuppressWarnings("null") - private Expression<? extends Number> lower, upper; - - private final Random rand = new Random(); - - private boolean integer; - - @SuppressWarnings({"unchecked", "null"}) + + private final Random random = ThreadLocalRandom.current(); + private Expression<Number> from, to; + private boolean isInteger; + @Override - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parser) { - lower = (Expression<Number>) exprs[0]; - upper = (Expression<Number>) exprs[1]; - integer = parser.mark == 1; + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) { + from = (Expression<Number>) exprs[0]; + to = (Expression<Number>) exprs[1]; + isInteger = parser.hasTag("integer"); return true; } - + @Override @Nullable - protected Number[] get(final Event e) { - final Number l = lower.getSingle(e); - final Number u = upper.getSingle(e); - if (u == null || l == null) - return null; - final double ll = Math.min(l.doubleValue(), u.doubleValue()); - final double uu = Math.max(l.doubleValue(), u.doubleValue()); - if (integer) { - return new Long[] {Math2.ceil(ll) + Math2.mod(rand.nextLong(), Math2.floor(uu) - Math2.ceil(ll) + 1)}; + protected Number[] get(Event event) { + Number from = this.from.getSingle(event); + Number to = this.to.getSingle(event); + + if (to == null || from == null) + return new Number[0]; + + double min = Math.min(from.doubleValue(), to.doubleValue()); + double max = Math.max(from.doubleValue(), to.doubleValue()); + + if (isInteger) { + if (max - min < 1) + return new Long[0]; + return new Long[] {random.nextLong(Math2.ceil(min), Math2.floor(max) + 1)}; } else { - return new Double[] {ll + rand.nextDouble() * (uu - ll)}; + return new Double[] {min + random.nextDouble() * (max - min)}; } } - + @Override public Class<? extends Number> getReturnType() { - return integer ? Long.class : Double.class; + return isInteger ? Long.class : Double.class; } - + @Override - public String toString(final @Nullable Event e, final boolean debug) { - return "a random " + (integer ? "integer" : "number") + " between " + lower.toString(e, debug) + " and " + upper.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "a random " + (isInteger ? "integer" : "number") + " between " + from.toString(event, debug) + " and " + to.toString(event, debug); } - + @Override public boolean isSingle() { return true; } - + } From c4e272043f9f1c9e90f5bea506d911578b6ea354 Mon Sep 17 00:00:00 2001 From: Fusezion <fusezionstream@gmail.com> Date: Wed, 5 Apr 2023 20:55:14 -0400 Subject: [PATCH 295/619] Removal of ExprIdOf (#5564) --- .../njol/skript/aliases/MaterialRegistry.java | 126 --------- .../ch/njol/skript/expressions/ExprIdOf.java | 239 ------------------ 2 files changed, 365 deletions(-) delete mode 100644 src/main/java/ch/njol/skript/aliases/MaterialRegistry.java delete mode 100644 src/main/java/ch/njol/skript/expressions/ExprIdOf.java diff --git a/src/main/java/ch/njol/skript/aliases/MaterialRegistry.java b/src/main/java/ch/njol/skript/aliases/MaterialRegistry.java deleted file mode 100644 index d677715d4ce..00000000000 --- a/src/main/java/ch/njol/skript/aliases/MaterialRegistry.java +++ /dev/null @@ -1,126 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.aliases; - -import java.util.ArrayList; -import java.util.List; - -import org.bukkit.Material; - -import ch.njol.skript.Skript; - -/** - * Manages Skript's own number -> Material mappings. They are used to save - * items as variables. - */ -@Deprecated -public class MaterialRegistry { - - /** - * Loads a material registry from an array of strings. New materials will - * be added and legacy names updated when possible. - * @param names Material names. - * @return Material registry. - */ - public static MaterialRegistry load(String[] names) { - Material[] materials = Material.values(); // All materials the server knows - List<Material> mappings = new ArrayList<>(materials.length); // Our mappings - - // Use names we have to fill at least some mappings - boolean[] processed = new boolean[materials.length]; - for (String name : names) { - if (name == null) - continue; // This slot is intentionally empty - - Material mat = Material.getMaterial(name); - if (mat == null) { // Try getting legacy material instead - mat = Material.getMaterial(name, true); - } - - mappings.add(mat); - if (mat != null) { - processed[mat.ordinal()] = true; // This material exists - } - } - - // Add the missing materials, if any - if (names.length < materials.length) { - for (Material mat : materials) { - if (!processed[mat.ordinal()]) { // Not in registry yet - mappings.add(mat); - } - } - } - - return new MaterialRegistry(mappings.toArray(new Material[mappings.size()])); - } - - /** - * Materials by their number ids. - */ - private Material[] materials; - - /** - * Ids by ordinals of materials they represent. - */ - private int[] ids; - - /** - * Creates a material registry from existing data. - * @param materials Materials by their number ids. - */ - public MaterialRegistry(Material[] materials) { - this.materials = materials; - this.ids = new int[materials.length]; - for (int i = 0; i < materials.length; i++) { - Material m = materials[i]; - if (m != null) - ids[m.ordinal()] = i; - } - } - - /** - * Creates a new material registry. - */ - public MaterialRegistry() { - this(Material.values()); - } - - public Material getMaterial(int id) { - try { - Material m = materials[id]; - assert m != null; - return m; - } catch (ArrayIndexOutOfBoundsException e) { - throw new IllegalArgumentException("invalid material id"); - } - } - - public int getId(Material material) { - try { - return ids[material.ordinal()]; - } catch (ArrayIndexOutOfBoundsException e) { - throw new AssertionError("material registry out-of-date"); - } - } - - public Material[] getMaterials() { - return materials; - } -} diff --git a/src/main/java/ch/njol/skript/expressions/ExprIdOf.java b/src/main/java/ch/njol/skript/expressions/ExprIdOf.java deleted file mode 100644 index fac577dd70c..00000000000 --- a/src/main/java/ch/njol/skript/expressions/ExprIdOf.java +++ /dev/null @@ -1,239 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.expressions; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.NoSuchElementException; - -import org.bukkit.Material; -import org.bukkit.event.Event; -import org.bukkit.inventory.ItemStack; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.Skript; -import ch.njol.skript.aliases.ItemData; -import ch.njol.skript.aliases.ItemType; -import ch.njol.skript.classes.Changer.ChangeMode; -import ch.njol.skript.classes.Changer.ChangerUtils; -import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Examples; -import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.Since; -import ch.njol.skript.expressions.base.PropertyExpression; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.ExpressionType; -import ch.njol.skript.lang.Literal; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.util.Kleenean; -import ch.njol.util.coll.iterator.SingleItemIterator; - -/** - * @author Peter Güttinger - */ -@Name("Id") -@Description("The id of a specific item. You usually don't need this expression as you can likely do everything with aliases.") -@Examples({"message \"the ID of %type of the clicked block% is %id of the clicked block%.\""}) -@Since("1.0") -public class ExprIdOf extends PropertyExpression<ItemType, Long> { - - static { - Skript.registerExpression(ExprIdOf.class, Long.class, ExpressionType.PROPERTY, "[the] id(1¦s|) of %itemtype%", "%itemtype%'[s] id(1¦s|)"); - } - - @Nullable - private static final MethodHandle getMaterialMethod; - - static { - MethodHandle mh; - try { - mh = MethodHandles.lookup().findStatic(Material.class, "getMaterial", MethodType.methodType(Material.class, int.class)); - } catch (NoSuchMethodException | IllegalAccessException e) { - mh = null; - } - getMaterialMethod = mh; - } - - private boolean single = false; - - @SuppressWarnings({"unchecked", "null"}) - @Override - public boolean init(final Expression<?>[] vars, final int matchedPattern, final Kleenean isDelayed, final ParseResult parser) { - if (getMaterialMethod == null) { - Skript.error("Items do not have numeric ids on Minecraft 1.13 or newer."); - return false; - } else { - Skript.warning("Items do not have numeric ids on Minecraft 1.13 or newer. This script will not work on those versions!"); - } - - setExpr((Expression<ItemType>) vars[0]); - if (parser.mark != 1) { - single = true; - if (!getExpr().isSingle() || (getExpr() instanceof Literal && ((Literal<ItemType>) getExpr()).getSingle().getTypes().size() != 1)) { - Skript.warning("'" + getExpr() + "' has multiple ids"); - single = false; - } - } - return true; - } - - @SuppressWarnings("null") - @Override - protected Long[] get(final Event e, final ItemType[] source) { - if (single) { - final ItemType t = getExpr().getSingle(e); - if (t == null) - return new Long[0]; - return new Long[] {(long) t.getTypes().get(0).getType().getId()}; - } - final ArrayList<Long> r = new ArrayList<>(); - for (final ItemType t : source) { - for (final ItemData d : t) { - r.add(Long.valueOf(d.getType().getId())); - } - } - return r.toArray(new Long[r.size()]); - } - - @Override - public String toString(final @Nullable Event e, final boolean debug) { - return "the id" + (single ? "" : "s") + " of " + getExpr().toString(e, debug); - } - - boolean changeItemStack; - - @Override - @Nullable - public Class<?>[] acceptChange(final ChangeMode mode) { - if (!getExpr().isSingle()) - return null; - if (!ChangerUtils.acceptsChange(getExpr(), ChangeMode.SET, ItemStack.class, ItemType.class)) - return null; - changeItemStack = ChangerUtils.acceptsChange(getExpr(), ChangeMode.SET, ItemStack.class); - switch (mode) { - case ADD: - case REMOVE: - case SET: - return new Class[] {Number.class}; - case RESET: - case DELETE: - case REMOVE_ALL: - default: - return null; - } - } - - @SuppressWarnings("deprecation") - @Override - public void change(final Event e, final @Nullable Object[] delta, final ChangeMode mode) { - assert delta != null; - final int i = ((Number) delta[0]).intValue(); - final ItemType it = getExpr().getSingle(e); - if (it == null) - return; - final ItemStack is = it.getRandom(); - if (is == null) - return; - int type = is.getType().getId(); - switch (mode) { - case ADD: - type += i; - break; - case REMOVE: - type -= i; - break; - case SET: - type = i; - break; - case RESET: - case DELETE: - case REMOVE_ALL: - default: - assert false; - return; - } - Material m = null; - try { - assert getMaterialMethod != null; // Got past init - m = (Material) getMaterialMethod.invoke(type); - } catch (Throwable ex) { - Skript.exception(ex); - } - if (m != null) { - is.setType(m); - if (changeItemStack) - getExpr().change(e, new ItemStack[] {is}, ChangeMode.SET); - else - getExpr().change(e, new ItemType[] {new ItemType(is)}, ChangeMode.SET); - } - } - - @Override - @Nullable - public Iterator<Long> iterator(final Event e) { - if (single) { - final ItemType t = getExpr().getSingle(e); - if (t == null) - return null; - if (t.numTypes() == 0) - return null; - return new SingleItemIterator<>((long) t.getTypes().get(0).getType().getId()); - } - final Iterator<? extends ItemType> iter = getExpr().iterator(e); - if (iter == null || !iter.hasNext()) - return null; - return new Iterator<Long>() { - private Iterator<ItemData> current = iter.next().iterator(); - - @Override - public boolean hasNext() { - while (iter.hasNext() && !current.hasNext()) { - current = iter.next().iterator(); - } - return current.hasNext(); - } - - @Override - public Long next() { - if (!hasNext()) - throw new NoSuchElementException(); - return (long) current.next().getType().getId(); - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - }; - } - - @Override - public Class<Long> getReturnType() { - return Long.class; - } - - @Override - public boolean isLoopOf(final String s) { - return s.equalsIgnoreCase("id"); - } - -} From ca1b51cf337e7ab012b8535fb68ff0bb1cd72a03 Mon Sep 17 00:00:00 2001 From: DelayedGaming <72163224+DelayedGaming@users.noreply.github.com> Date: Sat, 8 Apr 2023 05:17:58 +0800 Subject: [PATCH 296/619] EvtPlayerChunkEnter - Make 'player' Keyword Optional (#5585) --- src/main/java/ch/njol/skript/events/EvtPlayerChunkEnter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/events/EvtPlayerChunkEnter.java b/src/main/java/ch/njol/skript/events/EvtPlayerChunkEnter.java index 8e7cdf9b866..e79030c1553 100644 --- a/src/main/java/ch/njol/skript/events/EvtPlayerChunkEnter.java +++ b/src/main/java/ch/njol/skript/events/EvtPlayerChunkEnter.java @@ -31,7 +31,7 @@ public class EvtPlayerChunkEnter extends SkriptEvent { static { - Skript.registerEvent("Player Chunk Enters", EvtPlayerChunkEnter.class, PlayerMoveEvent.class, "player (enter[s] [a] chunk|chunk enter[ing])") + Skript.registerEvent("Player Chunk Enters", EvtPlayerChunkEnter.class, PlayerMoveEvent.class, "[player] (enter[s] [a] chunk|chunk enter[ing])") .description("Called when a player enters a chunk. Note that this event is based on 'player move' event, and may be called frequent internally.") .examples( "on player enters a chunk:", From 7b2949edbcafa53eb1ea95057161f889564cf033 Mon Sep 17 00:00:00 2001 From: sovdee <ethansovde@gmail.com> Date: Sat, 8 Apr 2023 01:07:13 -0400 Subject: [PATCH 297/619] Fix Comparison Mistake in CondIsWithin (#5587) --- src/main/java/ch/njol/skript/conditions/CondIsWithin.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/njol/skript/conditions/CondIsWithin.java b/src/main/java/ch/njol/skript/conditions/CondIsWithin.java index ea466e3ff1b..2f904e99a2a 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsWithin.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsWithin.java @@ -135,12 +135,12 @@ public boolean check(Event event) { // Chunks if (area instanceof Chunk) { - return locsToCheck.check(event, (loc) -> loc.getChunk() == area, isNegated()); + return locsToCheck.check(event, (loc) -> loc.getChunk().equals(area), isNegated()); } // Worlds if (area instanceof World) { - return locsToCheck.check(event, (loc) -> loc.getWorld() == area, isNegated()); + return locsToCheck.check(event, (loc) -> loc.getWorld().equals(area), isNegated()); } // fall-back From c6a1e6efdf99eb963720b111a89be0d6416a8c87 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Sat, 8 Apr 2023 12:42:45 -0600 Subject: [PATCH 298/619] Remove travis CI tag (#5522) Co-authored-by: Kenzie <admin@moderocky.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index edc1bdb9596..e23e1a288d2 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ --- -# Skript [](https://travis-ci.org/SkriptLang/Skript) +# Skript **Skript** is a Minecraft plugin for Paper/Spigot, which allows server owners and other people to modify their servers without learning Java. It can also be useful if you *do* know Java; some tasks are quicker to do with Skript, and so it can be used From 8dfa83d73c8c1b89cbba6558163f650a03375356 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Thu, 13 Apr 2023 21:13:21 -0600 Subject: [PATCH 299/619] Fix two bugs with block iterator (#5566) --- .../njol/skript/expressions/ExprBlocks.java | 125 +++++++++--------- .../skript/expressions/ExprDirection.java | 2 +- .../njol/skript/util/BlockLineIterator.java | 68 +++++----- ...ll-5566-block iterator being 100 always.sk | 5 + 4 files changed, 105 insertions(+), 95 deletions(-) create mode 100644 src/test/skript/tests/regressions/pull-5566-block iterator being 100 always.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprBlocks.java b/src/main/java/ch/njol/skript/expressions/ExprBlocks.java index 5b3915a55ab..a8d245fe28b 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprBlocks.java +++ b/src/main/java/ch/njol/skript/expressions/ExprBlocks.java @@ -18,15 +18,17 @@ */ package ch.njol.skript.expressions; -import java.util.ArrayList; import java.util.Iterator; import org.bukkit.Chunk; import org.bukkit.Location; import org.bukkit.block.Block; import org.bukkit.event.Event; +import org.bukkit.util.Vector; import org.eclipse.jdt.annotation.Nullable; +import com.google.common.collect.Lists; + import ch.njol.skript.Skript; import ch.njol.skript.SkriptConfig; import ch.njol.skript.doc.Description; @@ -42,11 +44,7 @@ import ch.njol.skript.util.Direction; import ch.njol.util.Kleenean; import ch.njol.util.coll.iterator.ArrayIterator; -import ch.njol.util.coll.iterator.IteratorIterable; -/** - * @author Peter Güttinger - */ @Name("Blocks") @Description({"Blocks relative to other blocks or between other blocks. Can be used to get blocks relative to other blocks or for looping.", "Blocks from/to and between will return a straight line whereas blocks within will return a cuboid."}) @@ -57,6 +55,7 @@ "set all blocks within chunk at player to air"}) @Since("1.0, 2.5.1 (within/cuboid/chunk)") public class ExprBlocks extends SimpleExpression<Block> { + static { Skript.registerExpression(ExprBlocks.class, Block.class, ExpressionType.COMBINED, "[(all [[of] the]|the)] blocks %direction% [%locations%]", // TODO doesn't loop all blocks? @@ -66,20 +65,21 @@ public class ExprBlocks extends SimpleExpression<Block> { "[(all [[of] the]|the)] blocks within %location% and %location%", "[(all [[of] the]|the)] blocks (in|within) %chunk%"); } - - private int pattern; - @SuppressWarnings("null") - private Expression<?> from; - @Nullable - private Expression<Location> end; + @Nullable private Expression<Direction> direction; + + @Nullable + private Expression<Location> end; + @Nullable private Expression<Chunk> chunk; - - @SuppressWarnings({"unchecked", "null"}) + private Expression<?> from; + private int pattern; + @Override - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parser) { + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) { this.pattern = matchedPattern; switch (matchedPattern) { case 0: @@ -105,98 +105,99 @@ public boolean init(final Expression<?>[] exprs, final int matchedPattern, final } return true; } - - @SuppressWarnings("null") + @Override @Nullable - protected Block[] get(final Event e) { - final Expression<Direction> direction = this.direction; - if (direction != null && !from.isSingle()) { - final Location[] ls = (Location[]) from.getArray(e); - final Direction d = direction.getSingle(e); - if (ls.length == 0 || d == null) + protected Block[] get(Event event) { + if (this.direction != null && !from.isSingle()) { + Direction direction = this.direction.getSingle(event); + if (direction == null) return new Block[0]; - final Block[] bs = new Block[ls.length]; - for (int i = 0; i < ls.length; i++) { - bs[i] = d.getRelative(ls[i]).getBlock(); - } - return bs; + return from.stream(event) + .filter(Location.class::isInstance) + .map(Location.class::cast) + .map(location -> direction.getRelative(location)) + .toArray(Block[]::new); } - final ArrayList<Block> r = new ArrayList<>(); - final Iterator<Block> iter = iterator(e); - if (iter == null) + Iterator<Block> iterator = iterator(event); + if (iterator == null) return new Block[0]; - for (final Block b : new IteratorIterable<>(iter)) - r.add(b); - return r.toArray(new Block[r.size()]); + return Lists.newArrayList(iterator).toArray(new Block[0]); } - + @Override @Nullable - public Iterator<Block> iterator(final Event e) { + public Iterator<Block> iterator(Event event) { try { - final Expression<Direction> direction = this.direction; if (chunk != null) { - Chunk chunk = this.chunk.getSingle(e); + Chunk chunk = this.chunk.getSingle(event); if (chunk != null) return new AABB(chunk).iterator(); } else if (direction != null) { - if (!from.isSingle()) { - return new ArrayIterator<>(get(e)); - } - final Object o = from.getSingle(e); - if (o == null) + if (!from.isSingle()) + return new ArrayIterator<>(get(event)); + Object object = from.getSingle(event); + if (object == null) return null; - final Location l = o instanceof Location ? (Location) o : ((Block) o).getLocation().add(0.5, 0.5, 0.5); - final Direction d = direction.getSingle(e); - if (d == null) + Location location = object instanceof Location ? (Location) object : ((Block) object).getLocation().add(0.5, 0.5, 0.5); + Direction direction = this.direction.getSingle(event); + if (direction == null) return null; - return new BlockLineIterator(l, o != l ? d.getDirection((Block) o) : d.getDirection(l), SkriptConfig.maxTargetBlockDistance.value()); + Vector vector = object != location ? direction.getDirection((Block) object) : direction.getDirection(location); + // Cannot be zero. + if (vector.getX() == 0 && vector.getY() == 0 && vector.getZ() == 0) + return null; + int distance = SkriptConfig.maxTargetBlockDistance.value(); + if (this.direction instanceof ExprDirection) { + Expression<Number> numberExpression = ((ExprDirection) this.direction).amount; + Number number = numberExpression.getSingle(event); + if (numberExpression != null && number != null) + distance = number.intValue(); + } + return new BlockLineIterator(location, vector, distance); } else { - final Location loc = (Location) from.getSingle(e); + Location loc = (Location) from.getSingle(event); if (loc == null) return null; assert end != null; - final Location loc2 = end.getSingle(e); + Location loc2 = end.getSingle(event); if (loc2 == null || loc2.getWorld() != loc.getWorld()) return null; if (pattern == 4) return new AABB(loc, loc2).iterator(); return new BlockLineIterator(loc.getBlock(), loc2.getBlock()); } - } catch (final IllegalStateException ex) { - if (ex.getMessage().equals("Start block missed in BlockIterator")) + } catch (IllegalStateException e) { + if (e.getMessage().equals("Start block missed in BlockIterator")) return null; - throw ex; + throw e; } return null; } - + @Override public Class<? extends Block> getReturnType() { return Block.class; } - + @Override public boolean isSingle() { return false; } - + @Override - public String toString(final @Nullable Event e, final boolean debug) { - final Expression<Location> end = this.end; + public String toString(@Nullable Event event, boolean debug) { if (chunk != null) { - return "blocks within chunk " + chunk.toString(e, debug); + return "blocks within chunk " + chunk.toString(event, debug); } else if (pattern == 4) { assert end != null; - return "blocks within " + from.toString(e, debug) + " and " + end.toString(e, debug); + return "blocks within " + from.toString(event, debug) + " and " + end.toString(event, debug); } else if (end != null) { - return "blocks from " + from.toString(e, debug) + " to " + end.toString(e, debug); + return "blocks from " + from.toString(event, debug) + " to " + end.toString(event, debug); } else { - final Expression<Direction> direction = this.direction; assert direction != null; - return "block" + (isSingle() ? "" : "s") + " " + direction.toString(e, debug) + " " + from.toString(e, debug); + return "block" + (isSingle() ? "" : "s") + " " + direction.toString(event, debug) + " " + from.toString(event, debug); } } - + } diff --git a/src/main/java/ch/njol/skript/expressions/ExprDirection.java b/src/main/java/ch/njol/skript/expressions/ExprDirection.java index 4dc6c28eec1..06e99d6f4f9 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprDirection.java +++ b/src/main/java/ch/njol/skript/expressions/ExprDirection.java @@ -87,7 +87,7 @@ public class ExprDirection extends SimpleExpression<Direction> { } @Nullable - private Expression<Number> amount; + Expression<Number> amount; @Nullable private Vector direction; diff --git a/src/main/java/ch/njol/skript/util/BlockLineIterator.java b/src/main/java/ch/njol/skript/util/BlockLineIterator.java index 94db7b869e5..afa42b3ecd3 100644 --- a/src/main/java/ch/njol/skript/util/BlockLineIterator.java +++ b/src/main/java/ch/njol/skript/util/BlockLineIterator.java @@ -18,53 +18,49 @@ */ package ch.njol.skript.util; -import ch.njol.skript.bukkitutil.WorldUtils; import org.bukkit.Location; import org.bukkit.block.Block; import org.bukkit.util.BlockIterator; import org.bukkit.util.Vector; import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.Skript; +import ch.njol.skript.bukkitutil.WorldUtils; import ch.njol.util.Math2; import ch.njol.util.NullableChecker; import ch.njol.util.coll.iterator.StoppableIterator; -/** - * @author Peter Güttinger - */ public class BlockLineIterator extends StoppableIterator<Block> { - + /** * @param start * @param end * @throws IllegalStateException randomly (Bukkit bug) */ - public BlockLineIterator(final Block start, final Block end) throws IllegalStateException { + public BlockLineIterator(Block start, Block end) throws IllegalStateException { super(new BlockIterator(start.getWorld(), fitInWorld(start.getLocation().add(0.5, 0.5, 0.5), end.getLocation().subtract(start.getLocation()).toVector()), end.equals(start) ? new Vector(1, 0, 0) : end.getLocation().subtract(start.getLocation()).toVector(), 0, 0), // should prevent an error if start = end new NullableChecker<Block>() { private final double overshotSq = Math.pow(start.getLocation().distance(end.getLocation()) + 2, 2); @Override - public boolean check(final @Nullable Block b) { - assert b != null; - if (b.getLocation().distanceSquared(start.getLocation()) > overshotSq) + public boolean check(@Nullable Block block) { + assert block != null; + if (block.getLocation().distanceSquared(start.getLocation()) > overshotSq) throw new IllegalStateException("BlockLineIterator missed the end block!"); - return b.equals(end); + return block.equals(end); } }, true); } - + /** * @param start - * @param dir - * @param dist + * @param direction + * @param distance * @throws IllegalStateException randomly (Bukkit bug) */ - public BlockLineIterator(final Location start, final Vector dir, final double dist) throws IllegalStateException { - super(new BlockIterator(start.getWorld(), fitInWorld(start, dir), dir, 0, 0), new NullableChecker<Block>() { - private final double distSq = dist * dist; + public BlockLineIterator(Location start, Vector direction, double distance) throws IllegalStateException { + super(new BlockIterator(start.getWorld(), fitInWorld(start, direction), direction, 0, 0), new NullableChecker<Block>() { + private final double distSq = distance * distance; @Override public boolean check(final @Nullable Block b) { @@ -72,25 +68,33 @@ public boolean check(final @Nullable Block b) { } }, false); } - + /** * @param start - * @param dir - * @param dist + * @param direction + * @param distance * @throws IllegalStateException randomly (Bukkit bug) */ - public BlockLineIterator(final Block start, final Vector dir, final double dist) throws IllegalStateException { - this(start.getLocation().add(0.5, 0.5, 0.5), dir, dist); + public BlockLineIterator(Block start, Vector direction, double distance) throws IllegalStateException { + this(start.getLocation().add(0.5, 0.5, 0.5), direction, distance); } - - private static Vector fitInWorld(final Location l, final Vector dir) { - if (0 <= l.getBlockY() && l.getBlockY() < l.getWorld().getMaxHeight()) - return l.toVector(); - final double y = Math2.fit(WorldUtils.getWorldMinHeight(l.getWorld()), l.getY(), l.getWorld().getMaxHeight()); - if (Math.abs(dir.getY()) < Skript.EPSILON) - return new Vector(l.getX(), y, l.getZ()); - final double dy = y - l.getY(); - final double n = dy / dir.getY(); - return l.toVector().add(dir.clone().multiply(n)); + + /** + * Makes the vector fit within the world parameters. + * + * @param location The original starting location. + * @param direction The direction of the vector that will be based on the location. + * @return The newly modified Vector if needed. + */ + private static Vector fitInWorld(Location location, Vector direction) { + int lowest = WorldUtils.getWorldMinHeight(location.getWorld()); + int highest = location.getWorld().getMaxHeight(); + Vector vector = location.toVector(); + int y = location.getBlockY(); + if (y >= lowest && y <= highest) + return vector; + double newY = Math2.fit(lowest, location.getY(), highest); + return new Vector(location.getX(), newY, location.getZ()); } + } diff --git a/src/test/skript/tests/regressions/pull-5566-block iterator being 100 always.sk b/src/test/skript/tests/regressions/pull-5566-block iterator being 100 always.sk new file mode 100644 index 00000000000..3eb14b86d94 --- /dev/null +++ b/src/test/skript/tests/regressions/pull-5566-block iterator being 100 always.sk @@ -0,0 +1,5 @@ + +# Test not related to a made issue. Details at pull request. +test "100 blocks fix": + assert blocks 5 below spawn of world "world" contains air, grass block, dirt block, dirt block, bedrock block and void air with "Failed to get correct blocks" + assert size of blocks 3 below location below spawn of world "world" is 4 with "Failed to match asserted size" From 1ccc6ebf8824d9984b6e21b576140279638a24a4 Mon Sep 17 00:00:00 2001 From: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Date: Sat, 15 Apr 2023 07:08:23 +0200 Subject: [PATCH 300/619] Allow literal-only string parsing (#4667) Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> --- src/main/java/ch/njol/skript/classes/data/JavaClasses.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/ch/njol/skript/classes/data/JavaClasses.java b/src/main/java/ch/njol/skript/classes/data/JavaClasses.java index 1f463e87361..cb19c4fc546 100644 --- a/src/main/java/ch/njol/skript/classes/data/JavaClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/JavaClasses.java @@ -565,11 +565,9 @@ public boolean mustSyncDeserialization() { @Nullable public String parse(String s, ParseContext context) { switch (context) { - case DEFAULT: // in DUMMY, parsing is handled by VariableString - assert false; - return null; case CONFIG: // duh return s; + case DEFAULT: case SCRIPT: case EVENT: if (VariableString.isQuotedCorrectly(s, true)) @@ -584,7 +582,7 @@ public String parse(String s, ParseContext context) { @Override public boolean canParse(ParseContext context) { - return context != ParseContext.DEFAULT; + return true; } @Override From fa8a84226126bbb3bdc2cc14c8fc7c84d83b6b23 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Sat, 15 Apr 2023 12:00:43 -0600 Subject: [PATCH 301/619] Change loading order for BukkitClasses (#5605) * Change loading order for BukkitClasses * Move version checker up in loading order --- src/main/java/ch/njol/skript/Skript.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 4338a557a5d..d165bcc6732 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -471,11 +471,19 @@ public void onEnable() { // Load classes which are always safe to use new JavaClasses(); // These may be needed in configuration - + + // Check server software, Minecraft version, etc. + if (!checkServerPlatform()) { + disabled = true; // Nothing was loaded, nothing needs to be unloaded + setEnabled(false); // Cannot continue; user got errors in console to tell what happened + return; + } + // And then not-so-safe classes Throwable classLoadError = null; try { new SkriptClasses(); + new BukkitClasses(); } catch (Throwable e) { classLoadError = e; } @@ -488,13 +496,6 @@ public void onEnable() { if (TestMode.VERBOSITY != null) SkriptLogger.setVerbosity(Verbosity.valueOf(TestMode.VERBOSITY)); - // Check server software, Minecraft version, etc. - if (!checkServerPlatform()) { - disabled = true; // Nothing was loaded, nothing needs to be unloaded - setEnabled(false); // Cannot continue; user got errors in console to tell what happened - return; - } - // Use the updater, now that it has been configured to (not) do stuff if (updater != null) { CommandSender console = Bukkit.getConsoleSender(); @@ -530,7 +531,6 @@ public void onEnable() { skriptCommand.setTabCompleter(new SkriptCommandTabCompleter()); // Load Bukkit stuff. It is done after platform check, because something might be missing! - new BukkitClasses(); new BukkitEventValues(); new DefaultComparators(); From e2ca1a7cf58168dd82fdb3d29daae8e54edaec6e Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Sat, 22 Apr 2023 03:19:35 -0600 Subject: [PATCH 302/619] Include the Adventure API BungeeComponentSerializer (#5623) Shadow in the BungeeComponentSerializer --- build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.gradle b/build.gradle index 081f1c9f84a..4ed4e1603be 100644 --- a/build.gradle +++ b/build.gradle @@ -27,6 +27,8 @@ allprojects { dependencies { shadow group: 'io.papermc', name: 'paperlib', version: '1.0.8' shadow group: 'org.bstats', name: 'bstats-bukkit', version: '3.0.2' + shadow group: 'net.kyori', name: 'adventure-text-serializer-bungeecord', version: '4.3.0' + implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.19.4-R0.1-SNAPSHOT' implementation group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.700' implementation group: 'com.google.code.findbugs', name: 'findbugs', version: '3.0.1' @@ -87,6 +89,7 @@ tasks.withType(ShadowJar) { include(dependency('io.papermc:paperlib')) include(dependency('org.bstats:bstats-bukkit')) include(dependency('org.bstats:bstats-base')) + include(dependency('net.kyori:adventure-text-serializer-bungeecord')) } relocate 'io.papermc.lib', 'ch.njol.skript.paperlib' relocate 'org.bstats', 'ch.njol.skript.bstats' From 655eb7f1c40bf72824f2cacc6679aae517696b8f Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Tue, 25 Apr 2023 09:00:37 -0600 Subject: [PATCH 303/619] Add support for registering custom VariableStorage (#4873) --- src/main/java/ch/njol/skript/Skript.java | 76 +++++----- .../ch/njol/skript/registrations/Classes.java | 6 +- .../njol/skript/variables/MySQLStorage.java | 56 ++++++++ .../{DatabaseStorage.java => SQLStorage.java} | 133 ++++++------------ .../njol/skript/variables/SQLiteStorage.java | 55 ++++++++ .../ch/njol/skript/variables/Variables.java | 54 +++++-- .../skript/variables/VariablesStorage.java | 17 +-- 7 files changed, 250 insertions(+), 147 deletions(-) create mode 100644 src/main/java/ch/njol/skript/variables/MySQLStorage.java rename src/main/java/ch/njol/skript/variables/{DatabaseStorage.java => SQLStorage.java} (86%) create mode 100644 src/main/java/ch/njol/skript/variables/SQLiteStorage.java diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index d165bcc6732..dab73c2b5a6 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -604,45 +604,51 @@ public void run() { Documentation.generate(); // TODO move to test classes? - if (logNormal()) - info("Loading variables..."); - final long vls = System.currentTimeMillis(); - - LogHandler h = SkriptLogger.startLogHandler(new ErrorDescLogHandler() { - @Override - public LogResult log(final LogEntry entry) { - super.log(entry); - if (entry.level.intValue() >= Level.SEVERE.intValue()) { - logEx(entry.message); // no [Skript] prefix - return LogResult.DO_NOT_LOG; - } else { - return LogResult.LOG; + Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.this, () -> { + if (logNormal()) + info("Loading variables..."); + long vls = System.currentTimeMillis(); + + LogHandler h = SkriptLogger.startLogHandler(new ErrorDescLogHandler() { + @Override + public LogResult log(final LogEntry entry) { + super.log(entry); + if (entry.level.intValue() >= Level.SEVERE.intValue()) { + logEx(entry.message); // no [Skript] prefix + return LogResult.DO_NOT_LOG; + } else { + return LogResult.LOG; + } } - } + + @Override + protected void beforeErrors() { + logEx(); + logEx("===!!!=== Skript variable load error ===!!!==="); + logEx("Unable to load (all) variables:"); + } + + @Override + protected void afterErrors() { + logEx(); + logEx("Skript will work properly, but old variables might not be available at all and new ones may or may not be saved until Skript is able to create a backup of the old file and/or is able to connect to the database (which requires a restart of Skript)!"); + logEx(); + } + }); - @Override - protected void beforeErrors() { - logEx(); - logEx("===!!!=== Skript variable load error ===!!!==="); - logEx("Unable to load (all) variables:"); + try (CountingLogHandler c = new CountingLogHandler(SkriptLogger.SEVERE).start()) { + if (!Variables.load()) + if (c.getCount() == 0) + error("(no information available)"); + } finally { + h.stop(); } - @Override - protected void afterErrors() { - logEx(); - logEx("Skript will work properly, but old variables might not be available at all and new ones may or may not be saved until Skript is able to create a backup of the old file and/or is able to connect to the database (which requires a restart of Skript)!"); - logEx(); - } + long vld = System.currentTimeMillis() - vls; + if (logNormal()) + info("Loaded " + Variables.numVariables() + " variables in " + ((vld / 100) / 10.) + " seconds"); }); - try (CountingLogHandler c = new CountingLogHandler(SkriptLogger.SEVERE).start()) { - if (!Variables.load()) - if (c.getCount() == 0) - error("(no information available)"); - } finally { - h.stop(); - } - // Skript initialization done debug("Early init done"); @@ -745,10 +751,6 @@ protected void afterErrors() { }, shutdownDelay); }, 100); } - - final long vld = System.currentTimeMillis() - vls; - if (logNormal()) - info("Loaded " + Variables.numVariables() + " variables in " + ((vld / 100) / 10.) + " seconds"); // Enable metrics and register custom charts Metrics metrics = new Metrics(Skript.this, 722); // 722 is our bStats plugin ID diff --git a/src/main/java/ch/njol/skript/registrations/Classes.java b/src/main/java/ch/njol/skript/registrations/Classes.java index 126b454efa5..1b316f97dfb 100644 --- a/src/main/java/ch/njol/skript/registrations/Classes.java +++ b/src/main/java/ch/njol/skript/registrations/Classes.java @@ -56,7 +56,7 @@ import ch.njol.skript.log.ParseLogHandler; import ch.njol.skript.log.SkriptLogger; import ch.njol.skript.util.StringMode; -import ch.njol.skript.variables.DatabaseStorage; +import ch.njol.skript.variables.SQLStorage; import ch.njol.skript.variables.SerializedVariable; import ch.njol.skript.variables.Variables; import ch.njol.util.Kleenean; @@ -94,8 +94,8 @@ public static <T> void registerClass(final ClassInfo<T> info) { throw new IllegalArgumentException("Can't register " + info.getC().getName() + " with the code name " + info.getCodeName() + " because that name is already used by " + classInfosByCodeName.get(info.getCodeName())); if (exactClassInfos.containsKey(info.getC())) throw new IllegalArgumentException("Can't register the class info " + info.getCodeName() + " because the class " + info.getC().getName() + " is already registered"); - if (info.getCodeName().length() > DatabaseStorage.MAX_CLASS_CODENAME_LENGTH) - throw new IllegalArgumentException("The codename '" + info.getCodeName() + "' is too long to be saved in a database, the maximum length allowed is " + DatabaseStorage.MAX_CLASS_CODENAME_LENGTH); + if (info.getCodeName().length() > SQLStorage.MAX_CLASS_CODENAME_LENGTH) + throw new IllegalArgumentException("The codename '" + info.getCodeName() + "' is too long to be saved in a database, the maximum length allowed is " + SQLStorage.MAX_CLASS_CODENAME_LENGTH); exactClassInfos.put(info.getC(), info); classInfosByCodeName.put(info.getCodeName(), info); tempClassInfos.add(info); diff --git a/src/main/java/ch/njol/skript/variables/MySQLStorage.java b/src/main/java/ch/njol/skript/variables/MySQLStorage.java new file mode 100644 index 00000000000..d81594c3bc7 --- /dev/null +++ b/src/main/java/ch/njol/skript/variables/MySQLStorage.java @@ -0,0 +1,56 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.variables; + +import ch.njol.skript.config.SectionNode; +import ch.njol.skript.log.SkriptLogger; +import lib.PatPeter.SQLibrary.Database; +import lib.PatPeter.SQLibrary.MySQL; + +public class MySQLStorage extends SQLStorage { + + MySQLStorage(String name) { + super(name, "CREATE TABLE IF NOT EXISTS %s (" + + "rowid BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY," + + "name VARCHAR(" + MAX_VARIABLE_NAME_LENGTH + ") NOT NULL UNIQUE," + + "type VARCHAR(" + MAX_CLASS_CODENAME_LENGTH + ")," + + "value BLOB(" + MAX_VALUE_SIZE + ")," + + "update_guid CHAR(36) NOT NULL" + + ") CHARACTER SET ucs2 COLLATE ucs2_bin"); + } + + @Override + public Database initialize(SectionNode config) { + String host = getValue(config, "host"); + Integer port = getValue(config, "port", Integer.class); + String user = getValue(config, "user"); + String password = getValue(config, "password"); + String database = getValue(config, "database"); + setTableName(config.get("table", "variables21")); + if (host == null || port == null || user == null || password == null || database == null) + return null; + return new MySQL(SkriptLogger.LOGGER, "[Skript]", host, port, database, user, password); + } + + @Override + protected boolean requiresFile() { + return false; + } + +} diff --git a/src/main/java/ch/njol/skript/variables/DatabaseStorage.java b/src/main/java/ch/njol/skript/variables/SQLStorage.java similarity index 86% rename from src/main/java/ch/njol/skript/variables/DatabaseStorage.java rename to src/main/java/ch/njol/skript/variables/SQLStorage.java index 385170ca3cf..877ba4b7576 100644 --- a/src/main/java/ch/njol/skript/variables/DatabaseStorage.java +++ b/src/main/java/ch/njol/skript/variables/SQLStorage.java @@ -26,12 +26,6 @@ import java.util.UUID; import java.util.concurrent.Callable; -import lib.PatPeter.SQLibrary.Database; -import lib.PatPeter.SQLibrary.DatabaseException; -import lib.PatPeter.SQLibrary.MySQL; -import lib.PatPeter.SQLibrary.SQLibrary; -import lib.PatPeter.SQLibrary.SQLite; - import org.bukkit.Bukkit; import org.bukkit.plugin.Plugin; import org.eclipse.jdt.annotation.Nullable; @@ -45,6 +39,9 @@ import ch.njol.skript.util.Task; import ch.njol.skript.util.Timespan; import ch.njol.util.SynchronizedReference; +import lib.PatPeter.SQLibrary.Database; +import lib.PatPeter.SQLibrary.DatabaseException; +import lib.PatPeter.SQLibrary.SQLibrary; /** * TODO create a metadata table to store some properties (e.g. Skript version, Yggdrasil version) -- but what if some variables cannot be converted? move them to a different table? @@ -52,87 +49,42 @@ * * @author Peter Güttinger */ -public class DatabaseStorage extends VariablesStorage { +public abstract class SQLStorage extends VariablesStorage { public final static int MAX_VARIABLE_NAME_LENGTH = 380, // MySQL: 767 bytes max; cannot set max bytes, only max characters MAX_CLASS_CODENAME_LENGTH = 50, // checked when registering a class MAX_VALUE_SIZE = 10000; - private final static String OLD_TABLE_NAME = "variables"; - private final static String SELECT_ORDER = "name, type, value, rowid"; - public static enum Type { - MYSQL("CREATE TABLE IF NOT EXISTS %s (" + - "rowid BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY," + - "name VARCHAR(" + MAX_VARIABLE_NAME_LENGTH + ") NOT NULL UNIQUE," + - "type VARCHAR(" + MAX_CLASS_CODENAME_LENGTH + ")," + - "value BLOB(" + MAX_VALUE_SIZE + ")," + - "update_guid CHAR(36) NOT NULL" + - ") CHARACTER SET ucs2 COLLATE ucs2_bin") {// MySQL treats UTF16 as 4 byte charset, resulting in a short max name length. UCS2 uses 2 bytes. - @Override - @Nullable - protected Object initialise(final DatabaseStorage s, final SectionNode n) { - final String host = s.getValue(n, "host"); - final Integer port = s.getValue(n, "port", Integer.class); - final String user = s.getValue(n, "user"); - final String password = s.getValue(n, "password"); - final String database = s.getValue(n, "database"); - s.setTableName(n.get("table", "variables21")); - if (host == null || port == null || user == null || password == null || database == null) - return null; - return new MySQL(SkriptLogger.LOGGER, "[Skript]", host, port, database, user, password); - } - }, - SQLITE("CREATE TABLE IF NOT EXISTS %s (" + - "name VARCHAR(" + MAX_VARIABLE_NAME_LENGTH + ") NOT NULL PRIMARY KEY," + - "type VARCHAR(" + MAX_CLASS_CODENAME_LENGTH + ")," + - "value BLOB(" + MAX_VALUE_SIZE + ")," + - "update_guid CHAR(36) NOT NULL" + - ")") {// SQLite uses Unicode exclusively - @Override - @Nullable - protected Object initialise(final DatabaseStorage s, final SectionNode config) { - final File f = s.file; - if (f == null) - return null; - s.setTableName(config.get("table", "variables21")); - final String name = f.getName(); - assert name.endsWith(".db"); - return new SQLite(SkriptLogger.LOGGER, "[Skript]", f.getParent(), name.substring(0, name.length() - ".db".length())); - } - }; - - final String createQuery; - - private Type(final String createQuery) { - this.createQuery = createQuery; - } - - @Nullable - protected abstract Object initialise(DatabaseStorage s, SectionNode config); - } + private final static String OLD_TABLE_NAME = "variables"; - private final Type type; - private String tableName; @Nullable private String formattedCreateQuery; + private final String createTableQuery; + private String tableName; final SynchronizedReference<Database> db = new SynchronizedReference<>(null); private boolean monitor = false; long monitor_interval; - private final static String guid = "" + UUID.randomUUID().toString(); + private final static String guid = UUID.randomUUID().toString(); /** * The delay between transactions in milliseconds. */ private final static long TRANSACTION_DELAY = 500; - DatabaseStorage(final String name, final Type type) { + /** + * Creates a SQLStorage with a create table query. + * + * @param name The name to be sent through this constructor when newInstance creates this class. + * @param createTableQuery The create table query to send to the SQL engine. + */ + protected SQLStorage(String name, String createTableQuery) { super(name); - this.type = type; + this.createTableQuery = createTableQuery; this.tableName = "variables21"; } @@ -144,15 +96,23 @@ public void setTableName(String tableName) { this.tableName = tableName; } + /** + * Initializes an SQL database with the user provided configuration section for loading the database. + * + * @param config The configuration from the config.sk that defines this database. + * @return A Database implementation from SQLibrary. + */ + @Nullable + public abstract Database initialize(SectionNode config); + /** * Retrieve the create query with the tableName in it * @return the create query with the tableName in it (%s -> tableName) */ @Nullable - public String getFormattedCreateQuery(){ - if (formattedCreateQuery == null){ - formattedCreateQuery = String.format(type.createQuery, tableName); - } + public String getFormattedCreateQuery() { + if (formattedCreateQuery == null) + formattedCreateQuery = String.format(createTableQuery, tableName); return formattedCreateQuery; } @@ -161,10 +121,10 @@ public String getFormattedCreateQuery(){ * {@link Variables#variableLoaded(String, Object, VariablesStorage)}). */ @Override - protected boolean load_i(final SectionNode n) { + protected boolean load_i(SectionNode n) { synchronized (db) { - final Plugin p = Bukkit.getPluginManager().getPlugin("SQLibrary"); - if (p == null || !(p instanceof SQLibrary)) { + Plugin plugin = Bukkit.getPluginManager().getPlugin("SQLibrary"); + if (plugin == null || !(plugin instanceof SQLibrary)) { Skript.error("You need the plugin SQLibrary in order to use a database with Skript. You can download the latest version from https://dev.bukkit.org/projects/sqlibrary/files/"); return false; } @@ -178,10 +138,10 @@ protected boolean load_i(final SectionNode n) { final Database db; try { - final Object o = type.initialise(this, n); - if (o == null) + Database database = initialize(n); + if (database == null) return false; - this.db.set(db = (Database) o); + this.db.set(db = database); } catch (final RuntimeException e) { if (e instanceof DatabaseException) {// not in a catch clause to not produce a ClassNotFoundException when this class is loaded and SQLibrary is not present Skript.error(e.getLocalizedMessage()); @@ -208,7 +168,7 @@ protected boolean load_i(final SectionNode n) { db.query(getFormattedCreateQuery()); } catch (final SQLException e) { Skript.error("Could not create the variables table '" + tableName + "' in the database '" + databaseName + "': " + e.getLocalizedMessage() + ". " - + "Please create the table yourself using the following query: " + String.format(type.createQuery, tableName).replace(",", ", ").replaceAll("\\s+", " ")); + + "Please create the table yourself using the following query: " + String.format(createTableQuery, tableName).replace(",", ", ").replaceAll("\\s+", " ")); return false; } @@ -293,9 +253,9 @@ protected boolean load_i(final SectionNode n) { @Override public void run() { while (!closed) { - synchronized (DatabaseStorage.this.db) { + synchronized (SQLStorage.this.db) { try { - final Database db = DatabaseStorage.this.db.get(); + final Database db = SQLStorage.this.db.get(); if (db != null) db.query("SELECT * FROM " + getTableName() + " LIMIT 1"); } catch (final SQLException e) {} @@ -322,7 +282,7 @@ public void run() { long lastCommit; while (!closed) { synchronized (db) { - final Database db = DatabaseStorage.this.db.get(); + final Database db = SQLStorage.this.db.get(); try { if (db != null) db.getConnection().commit(); @@ -372,11 +332,6 @@ public void run() { } - @Override - protected boolean requiresFile() { - return type == Type.SQLITE; - } - @Override protected File getFile(String file) { if (!file.endsWith(".db")) @@ -397,7 +352,7 @@ private final boolean connect(final boolean first) { final Database db = this.db.get(); if (db == null || !db.open()) { if (first) - Skript.error("Cannot connect to the database '" + databaseName + "'! Please make sure that all settings are correct" + (type == Type.MYSQL ? " and that the database software is running" : "") + "."); + Skript.error("Cannot connect to the database '" + databaseName + "'! Please make sure that all settings are correct");// + (type == Type.MYSQL ? " and that the database software is running" : "") + "."); else Skript.exception("Cannot reconnect to the database '" + databaseName + "'!"); return false; @@ -576,7 +531,7 @@ public void run() { synchronized (db) { if (closed || db.get() == null) return; - final PreparedStatement monitorCleanUpQuery = DatabaseStorage.this.monitorCleanUpQuery; + final PreparedStatement monitorCleanUpQuery = SQLStorage.this.monitorCleanUpQuery; assert monitorCleanUpQuery != null; monitorCleanUpQuery.setLong(1, lastRowID); monitorCleanUpQuery.executeUpdate(); @@ -629,7 +584,7 @@ public SQLException call() throws Exception { final byte[] value = r.getBytes(i++); // Blob not supported by SQLite lastRowID = r.getLong(i++); if (value == null) { - Variables.variableLoaded(name, null, DatabaseStorage.this); + Variables.variableLoaded(name, null, SQLStorage.this); } else { final ClassInfo<?> c = Classes.getClassInfoNoError(type); @SuppressWarnings("unused") @@ -646,7 +601,7 @@ public SQLException call() throws Exception { Skript.error("Cannot load the variable {" + name + "} from the database '" + databaseName + "', because it cannot be loaded as " + c.getName().withIndefiniteArticle()); continue; } - Variables.variableLoaded(name, d, DatabaseStorage.this); + Variables.variableLoaded(name, d, SQLStorage.this); // } } } @@ -765,7 +720,7 @@ public SQLException call() throws Exception { final String value = r.getString(i++); lastRowID = r.getLong(i++); if (type == null || value == null) { - Variables.variableLoaded(name, null, hadNewTable ? temp : DatabaseStorage.this); + Variables.variableLoaded(name, null, hadNewTable ? temp : SQLStorage.this); } else { final ClassInfo<?> c = Classes.getClassInfoNoError(type); Serializer<?> s; @@ -781,7 +736,7 @@ public SQLException call() throws Exception { Skript.error("Cannot load the variable {" + name + "} from the database, because '" + value + "' cannot be parsed as a " + type); continue; } - Variables.variableLoaded(name, d, DatabaseStorage.this); + Variables.variableLoaded(name, d, SQLStorage.this); // } } } diff --git a/src/main/java/ch/njol/skript/variables/SQLiteStorage.java b/src/main/java/ch/njol/skript/variables/SQLiteStorage.java new file mode 100644 index 00000000000..fc4c54072d6 --- /dev/null +++ b/src/main/java/ch/njol/skript/variables/SQLiteStorage.java @@ -0,0 +1,55 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.variables; + +import java.io.File; + +import ch.njol.skript.config.SectionNode; +import ch.njol.skript.log.SkriptLogger; +import lib.PatPeter.SQLibrary.Database; +import lib.PatPeter.SQLibrary.SQLite; + +public class SQLiteStorage extends SQLStorage { + + SQLiteStorage(String name) { + super(name, "CREATE TABLE IF NOT EXISTS %s (" + + "name VARCHAR(" + MAX_VARIABLE_NAME_LENGTH + ") NOT NULL PRIMARY KEY," + + "type VARCHAR(" + MAX_CLASS_CODENAME_LENGTH + ")," + + "value BLOB(" + MAX_VALUE_SIZE + ")," + + "update_guid CHAR(36) NOT NULL" + + ")"); + } + + @Override + public Database initialize(SectionNode config) { + File f = file; + if (f == null) + return null; + setTableName(config.get("table", "variables21")); + String name = f.getName(); + assert name.endsWith(".db"); + return new SQLite(SkriptLogger.LOGGER, "[Skript]", f.getParent(), name.substring(0, name.length() - ".db".length())); + } + + @Override + protected boolean requiresFile() { + return true; + } + +} diff --git a/src/main/java/ch/njol/skript/variables/Variables.java b/src/main/java/ch/njol/skript/variables/Variables.java index 7ce886d016d..d326286a307 100644 --- a/src/main/java/ch/njol/skript/variables/Variables.java +++ b/src/main/java/ch/njol/skript/variables/Variables.java @@ -29,7 +29,6 @@ import ch.njol.skript.lang.Variable; import ch.njol.skript.log.SkriptLogger; import ch.njol.skript.registrations.Classes; -import ch.njol.skript.variables.DatabaseStorage.Type; import ch.njol.skript.variables.SerializedVariable.Value; import ch.njol.util.Kleenean; import ch.njol.util.NonNullPair; @@ -43,6 +42,10 @@ import org.eclipse.jdt.annotation.Nullable; import org.skriptlang.skript.lang.converter.Converters; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; + +import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -50,6 +53,7 @@ import java.util.Locale; import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; import java.util.Queue; import java.util.TreeMap; import java.util.concurrent.BlockingQueue; @@ -90,8 +94,13 @@ public class Variables { */ private static final String CONFIGURATION_SERIALIZABLE_PREFIX = "ConfigurationSerializable_"; + private final static Multimap<Class<? extends VariablesStorage>, String> TYPES = HashMultimap.create(); + // Register some things with Yggdrasil static { + registerStorage(FlatFileStorage.class, "csv", "file", "flatfile"); + registerStorage(SQLiteStorage.class, "sqlite"); + registerStorage(MySQLStorage.class, "mysql"); yggdrasil.registerSingleClass(Kleenean.class, "Kleenean"); // Register ConfigurationSerializable, Bukkit's serialization system yggdrasil.registerClassResolver(new ConfigurationSerializer<ConfigurationSerializable>() { @@ -130,6 +139,26 @@ public Class<? extends ConfigurationSerializable> getClass(@NonNull String id) { */ static final List<VariablesStorage> STORAGES = new ArrayList<>(); + /** + * Register a VariableStorage class for Skript to create if the user config value matches. + * + * @param <T> A class to extend VariableStorage. + * @param storage The class of the VariableStorage implementation. + * @param names The names used in the config of Skript to select this VariableStorage. + * @return if the operation was successful, or if it's already registered. + */ + public static <T extends VariablesStorage> boolean registerStorage(Class<T> storage, String... names) { + if (TYPES.containsKey(storage)) + return false; + for (String name : names) { + if (TYPES.containsValue(name.toLowerCase(Locale.ENGLISH))) + return false; + } + for (String name : names) + TYPES.put(storage, name.toLowerCase(Locale.ENGLISH)); + return true; + } + /** * Load the variables configuration and all variables. * <p> @@ -191,20 +220,25 @@ public static boolean load() { // Initiate the right VariablesStorage class VariablesStorage variablesStorage; - if (type.equalsIgnoreCase("csv") - || type.equalsIgnoreCase("file") - || type.equalsIgnoreCase("flatfile")) { - variablesStorage = new FlatFileStorage(name); - } else if (type.equalsIgnoreCase("mysql")) { - variablesStorage = new DatabaseStorage(name, Type.MYSQL); - } else if (type.equalsIgnoreCase("sqlite")) { - variablesStorage = new DatabaseStorage(name, Type.SQLITE); - } else { + Optional<?> optional = TYPES.entries().stream() + .filter(entry -> entry.getValue().equalsIgnoreCase(type)) + .map(Entry::getKey) + .findFirst(); + if (!optional.isPresent()) { if (!type.equalsIgnoreCase("disabled") && !type.equalsIgnoreCase("none")) { Skript.error("Invalid database type '" + type + "'"); successful = false; } + continue; + } + try { + @SuppressWarnings("unchecked") + Class<? extends VariablesStorage> storageClass = (Class<? extends VariablesStorage>) optional.get(); + variablesStorage = (VariablesStorage) storageClass.getConstructor(String.class).newInstance(type); + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { + Skript.error("Failed to initalize database type '" + type + "'"); + successful = false; continue; } diff --git a/src/main/java/ch/njol/skript/variables/VariablesStorage.java b/src/main/java/ch/njol/skript/variables/VariablesStorage.java index f7403e3985e..04fca57a0eb 100644 --- a/src/main/java/ch/njol/skript/variables/VariablesStorage.java +++ b/src/main/java/ch/njol/skript/variables/VariablesStorage.java @@ -18,6 +18,14 @@ */ package ch.njol.skript.variables; +import java.io.File; +import java.io.IOException; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import org.eclipse.jdt.annotation.Nullable; + import ch.njol.skript.Skript; import ch.njol.skript.config.SectionNode; import ch.njol.skript.lang.ParseContext; @@ -29,14 +37,6 @@ import ch.njol.skript.util.Timespan; import ch.njol.skript.variables.SerializedVariable.Value; import ch.njol.util.Closeable; -import org.eclipse.jdt.annotation.Nullable; -import org.jetbrains.annotations.Contract; - -import java.io.File; -import java.io.IOException; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; /** * A variable storage is holds the means and methods of storing variables. @@ -100,6 +100,7 @@ public abstract class VariablesStorage implements Closeable { * @param name the name. */ protected VariablesStorage(String name) { + assert name != null; databaseName = name; writeThread = Skript.newThread(() -> { From 1b0224597ebcd08e3cfb0a5dc1a3f82f0daa0314 Mon Sep 17 00:00:00 2001 From: DelayedGaming <72163224+DelayedGaming@users.noreply.github.com> Date: Wed, 26 Apr 2023 09:16:02 +0800 Subject: [PATCH 304/619] ExprVectorRandom - Fix Vectors not Unit & Not True Random Generations (#5621) --- .../skript/expressions/ExprVectorRandom.java | 32 ++++++++----------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorRandom.java b/src/main/java/ch/njol/skript/expressions/ExprVectorRandom.java index d5315ca05e2..77ed5bee951 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorRandom.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorRandom.java @@ -18,12 +18,6 @@ */ package ch.njol.skript.expressions; -import java.util.Random; - -import org.bukkit.event.Event; -import org.bukkit.util.Vector; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; @@ -35,17 +29,19 @@ import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.bukkit.util.Vector; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.Random; -/** - * @author bi0qaw - */ @Name("Vectors - Random Vector") -@Description("Creates a random vector.") -@Examples({"set {_v} to a random vector"}) +@Description("Creates a random unit vector.") +@Examples("set {_v} to a random vector") @Since("2.2-dev28, 2.7 (signed components)") public class ExprVectorRandom extends SimpleExpression<Vector> { - private static final Random random = new Random(); + private static final Random RANDOM = new Random(); static { Skript.registerExpression(ExprVectorRandom.class, Vector.class, ExpressionType.SIMPLE, "[a] random vector"); @@ -57,8 +53,10 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye } @Override - protected Vector[] get(Event e) { - return CollectionUtils.array(new Vector(randomSignedDouble(), randomSignedDouble(), randomSignedDouble())); + protected Vector[] get(Event event) { + // Generating uniform random numbers leads to bias towards the corners of the cube. + // Gaussian distribution is radially symmetric, so it avoids this bias. + return CollectionUtils.array(new Vector(RANDOM.nextGaussian(), RANDOM.nextGaussian(), RANDOM.nextGaussian()).normalize()); } @Override @@ -72,12 +70,8 @@ public Class<? extends Vector> getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { return "random vector"; } - - private static double randomSignedDouble() { - return random.nextDouble() * (random.nextBoolean() ? 1 : -1); - } } From bc3680feea3bb2b32ecef1e1b22f6db70f571172 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Tue, 25 Apr 2023 21:22:34 -0600 Subject: [PATCH 305/619] Fix databases not currently registering (#5643) --- src/main/java/ch/njol/skript/variables/FlatFileStorage.java | 2 +- src/main/java/ch/njol/skript/variables/SQLStorage.java | 2 +- src/main/java/ch/njol/skript/variables/Variables.java | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/njol/skript/variables/FlatFileStorage.java b/src/main/java/ch/njol/skript/variables/FlatFileStorage.java index 04bb514d399..5ab2d37d596 100644 --- a/src/main/java/ch/njol/skript/variables/FlatFileStorage.java +++ b/src/main/java/ch/njol/skript/variables/FlatFileStorage.java @@ -128,7 +128,7 @@ public class FlatFileStorage extends VariablesStorage { * * @param name the name. */ - protected FlatFileStorage(String name) { + FlatFileStorage(String name) { super(name); } diff --git a/src/main/java/ch/njol/skript/variables/SQLStorage.java b/src/main/java/ch/njol/skript/variables/SQLStorage.java index 877ba4b7576..0a53aa3f2b0 100644 --- a/src/main/java/ch/njol/skript/variables/SQLStorage.java +++ b/src/main/java/ch/njol/skript/variables/SQLStorage.java @@ -82,7 +82,7 @@ public abstract class SQLStorage extends VariablesStorage { * @param name The name to be sent through this constructor when newInstance creates this class. * @param createTableQuery The create table query to send to the SQL engine. */ - protected SQLStorage(String name, String createTableQuery) { + public SQLStorage(String name, String createTableQuery) { super(name); this.createTableQuery = createTableQuery; this.tableName = "variables21"; diff --git a/src/main/java/ch/njol/skript/variables/Variables.java b/src/main/java/ch/njol/skript/variables/Variables.java index d326286a307..792e20a5659 100644 --- a/src/main/java/ch/njol/skript/variables/Variables.java +++ b/src/main/java/ch/njol/skript/variables/Variables.java @@ -45,6 +45,7 @@ import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; +import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collections; @@ -235,7 +236,9 @@ public static boolean load() { try { @SuppressWarnings("unchecked") Class<? extends VariablesStorage> storageClass = (Class<? extends VariablesStorage>) optional.get(); - variablesStorage = (VariablesStorage) storageClass.getConstructor(String.class).newInstance(type); + Constructor<?> constructor = storageClass.getDeclaredConstructor(String.class); + constructor.setAccessible(true); + variablesStorage = (VariablesStorage) constructor.newInstance(type); } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { Skript.error("Failed to initalize database type '" + type + "'"); successful = false; From 5fd28027bb757a2ca42a3882d8a4d1973603af3b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Apr 2023 04:26:03 -0600 Subject: [PATCH 306/619] Bump org.gradle.toolchains.foojay-resolver-convention from 0.4.0 to 0.5.0 (#5637) Bump org.gradle.toolchains.foojay-resolver-convention Bumps org.gradle.toolchains.foojay-resolver-convention from 0.4.0 to 0.5.0. --- updated-dependencies: - dependency-name: org.gradle.toolchains.foojay-resolver-convention dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> --- settings.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle b/settings.gradle index f48b9e264c6..0a8d7b2d301 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,6 +1,6 @@ plugins { - id 'org.gradle.toolchains.foojay-resolver-convention' version '0.4.0' + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.5.0' } rootProject.name = 'Skript' From 4789b0662cb299a304135acc1934514e26afaeb2 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Thu, 27 Apr 2023 13:48:57 +0300 Subject: [PATCH 307/619] =?UTF-8?q?=F0=9F=9A=80=20Fix=20`Delete`=20changer?= =?UTF-8?q?=20&=20Add=20`Reset`=20changer=20to=20ExprTarget=20(#4221)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../njol/skript/expressions/ExprTarget.java | 112 +++++++++++------- 1 file changed, 67 insertions(+), 45 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprTarget.java b/src/main/java/ch/njol/skript/expressions/ExprTarget.java index c72c9510597..d3fd4f45235 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTarget.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTarget.java @@ -30,12 +30,14 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.registrations.Classes; +import ch.njol.skript.registrations.EventValues; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; +import org.bukkit.OfflinePlayer; import org.bukkit.entity.Entity; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Mob; +import org.bukkit.entity.Player; import org.bukkit.event.Event; import org.bukkit.event.entity.EntityTargetEvent; import org.bukkit.util.Vector; @@ -43,15 +45,21 @@ import java.util.List; -/** - * @author Peter Güttinger - */ @Name("Target") -@Description("For players this is the entity at the crosshair, while for mobs and experience orbs it represents the entity they are attacking/following (if any).") -@Examples({"on entity target:", - "\tentity's target is a player", - "\tsend \"You're being followed by an %entity%!\" to target of entity"}) -@Since("<i>unknown</i> (before 2.1)") +@Description({ + "For players this is the entity at the crosshair.", + "For mobs and experience orbs this is the entity they are attacking/following (if any)." +}) +@Examples({ + "on entity target:", + "\tif entity's target is a player:", + "\t\tsend \"You're being followed by an %entity%!\" to target of entity", + "", + "reset target of entity # Makes the entity target-less", + "delete targeted entity of player # for players it will delete the target", + "delete target of last spawned zombie # for entities it will make them target-less" +}) +@Since("1.4.2, INSERT VERSION (Reset)") public class ExprTarget extends PropertyExpression<LivingEntity, Entity> { static { @@ -61,10 +69,10 @@ public class ExprTarget extends PropertyExpression<LivingEntity, Entity> { } @Nullable - EntityData<?> type; + private EntityData<?> type; - @SuppressWarnings("unchecked") @Override + @SuppressWarnings("unchecked") public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) { type = exprs[matchedPattern] == null ? null : (EntityData<?>) exprs[matchedPattern].getSingle(null); setExpr((Expression<? extends LivingEntity>) exprs[1 - matchedPattern]); @@ -72,69 +80,83 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye } @Override - protected Entity[] get(Event e, LivingEntity[] source) { - return get(source, en -> { - if (getTime() >= 0 && e instanceof EntityTargetEvent && en.equals(((EntityTargetEvent) e).getEntity()) && !Delay.isDelayed(e)) { - Entity target = ((EntityTargetEvent) e).getTarget(); + protected Entity[] get(Event event, LivingEntity[] source) { + return get(source, entity -> { + if (event instanceof EntityTargetEvent && entity.equals(((EntityTargetEvent) event).getEntity()) && !Delay.isDelayed(event)) { + Entity target = ((EntityTargetEvent) event).getTarget(); if (target == null || type != null && !type.isInstance(target)) return null; return target; } - return getTarget(en, type); + return getTarget(entity, type); }); } @Override - public Class<? extends Entity> getReturnType() { - return type != null ? type.getType() : Entity.class; + @Nullable + public Class<?>[] acceptChange(ChangeMode mode) { + switch (mode) { + case SET: + case RESET: + case DELETE: + return CollectionUtils.array(LivingEntity.class); + default: + return super.acceptChange(mode); + } } @Override - public String toString(@Nullable Event e, boolean debug) { - if (e == null) - return "the target" + (type == null ? "" : "ed " + type) + (getExpr().isDefault() ? "" : " of " + getExpr().toString(e, debug)); - return Classes.getDebugMessage(getAll(e)); + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + if (mode == ChangeMode.SET || mode == ChangeMode.RESET || mode == ChangeMode.DELETE) { + LivingEntity target = delta == null ? null : (LivingEntity) delta[0]; // null will make the entity target-less (reset target) but for players it will remove them. + if (event instanceof EntityTargetEvent) { + EntityTargetEvent targetEvent = (EntityTargetEvent) event; + for (LivingEntity entity : getExpr().getArray(event)) { + if (entity.equals(targetEvent.getEntity())) + targetEvent.setTarget(target); + } + return; + } + for (LivingEntity entity : getExpr().getArray(event)) { + if (entity instanceof Mob) { + ((Mob) entity).setTarget(target); + } else if (entity instanceof Player && mode == ChangeMode.DELETE) { + Entity playerTarget = getTarget(entity, type); + if (playerTarget != null && !(playerTarget instanceof OfflinePlayer)) + playerTarget.remove(); + } + } + } + super.change(event, delta, mode); } @Override public boolean setTime(int time) { - return super.setTime(time, EntityTargetEvent.class, getExpr()); + if (time != EventValues.TIME_PAST) + return super.setTime(time, EntityTargetEvent.class, getExpr()); + return super.setTime(time); } @Override - @Nullable - public Class<?>[] acceptChange(ChangeMode mode) { - if (mode == ChangeMode.SET || mode == ChangeMode.DELETE) - return CollectionUtils.array(LivingEntity.class); - return super.acceptChange(mode); + public Class<? extends Entity> getReturnType() { + return type != null ? type.getType() : Entity.class; } @Override - public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { - if (mode == ChangeMode.SET || mode == ChangeMode.DELETE) { - LivingEntity target = delta == null ? null : (LivingEntity) delta[0]; - for (LivingEntity entity : getExpr().getArray(e)) { - if (getTime() >= 0 && e instanceof EntityTargetEvent && entity.equals(((EntityTargetEvent) e).getEntity()) && !Delay.isDelayed(e)) { - ((EntityTargetEvent) e).setTarget(target); - } else if (entity instanceof Mob) { - ((Mob) entity).setTarget(target); - } - } - return; - } - super.change(e, delta, mode); + public String toString(@Nullable Event event, boolean debug) { + return "target" + (type == null ? "" : "ed " + type) + (getExpr().isDefault() ? "" : " of " + getExpr().toString(event, debug)); } /** * Gets an entity's target. - * + * * @param entity The entity to get the target of - * @param type Can be null for any entity + * @param type The exact EntityData to find. Can be null for any entity. * @return The entity's target */ - //TODO Switch this over to RayTraceResults 1.13+ when 1.12 support is dropped. - @SuppressWarnings("unchecked") + // TODO Switch this over to RayTraceResults 1.13+ when 1.12 support is dropped. @Nullable + @SuppressWarnings("unchecked") public static <T extends Entity> T getTarget(LivingEntity entity, @Nullable EntityData<T> type) { if (entity instanceof Mob) return ((Mob) entity).getTarget() == null || type != null && !type.isInstance(((Mob) entity).getTarget()) ? null : (T) ((Mob) entity).getTarget(); From 4aed5257187e9966903f0dfcd01269462e38fe85 Mon Sep 17 00:00:00 2001 From: DelayedGaming <72163224+DelayedGaming@users.noreply.github.com> Date: Mon, 1 May 2023 17:37:48 +0800 Subject: [PATCH 308/619] ExprBlocks: Fix ArrayStoreException (#5660) --- src/main/java/ch/njol/skript/expressions/ExprBlocks.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprBlocks.java b/src/main/java/ch/njol/skript/expressions/ExprBlocks.java index a8d245fe28b..35dea7f01a3 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprBlocks.java +++ b/src/main/java/ch/njol/skript/expressions/ExprBlocks.java @@ -116,7 +116,8 @@ protected Block[] get(Event event) { return from.stream(event) .filter(Location.class::isInstance) .map(Location.class::cast) - .map(location -> direction.getRelative(location)) + .map(direction::getRelative) + .map(Location::getBlock) .toArray(Block[]::new); } Iterator<Block> iterator = iterator(event); From f2cd6a85f60e37b4d514c63b90f5936364d01fb3 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Mon, 1 May 2023 15:06:02 -0600 Subject: [PATCH 309/619] Revert "Allow literal-only string parsing" (#5654) Revert "Allow literal-only string parsing (#4667)" This reverts commit 1ccc6ebf8824d9984b6e21b576140279638a24a4. --- src/main/java/ch/njol/skript/classes/data/JavaClasses.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/njol/skript/classes/data/JavaClasses.java b/src/main/java/ch/njol/skript/classes/data/JavaClasses.java index cb19c4fc546..1f463e87361 100644 --- a/src/main/java/ch/njol/skript/classes/data/JavaClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/JavaClasses.java @@ -565,9 +565,11 @@ public boolean mustSyncDeserialization() { @Nullable public String parse(String s, ParseContext context) { switch (context) { + case DEFAULT: // in DUMMY, parsing is handled by VariableString + assert false; + return null; case CONFIG: // duh return s; - case DEFAULT: case SCRIPT: case EVENT: if (VariableString.isQuotedCorrectly(s, true)) @@ -582,7 +584,7 @@ public String parse(String s, ParseContext context) { @Override public boolean canParse(ParseContext context) { - return true; + return context != ParseContext.DEFAULT; } @Override From 91f1cf00153a91296efe5c58affb9928605a2a8f Mon Sep 17 00:00:00 2001 From: Fusezion <fusezionstream@gmail.com> Date: Tue, 2 May 2023 01:21:51 -0400 Subject: [PATCH 310/619] [BUG] CondIsPreferredTool invalid checks (#5591) --- .../conditions/CondIsPreferredTool.java | 22 ++++++++----------- .../conditions/CondIsPreferredTool.sk | 20 ++++++++++------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/main/java/ch/njol/skript/conditions/CondIsPreferredTool.java b/src/main/java/ch/njol/skript/conditions/CondIsPreferredTool.java index 301e340e14a..195e8240d93 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsPreferredTool.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsPreferredTool.java @@ -78,20 +78,16 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override public boolean check(Event event) { - for (Object block : blocks.getArray(event)){ - if (block instanceof Block) { - if (!items.check(event, (item) -> ((Block) block).isPreferredTool(item.getRandom()), isNegated())) + return blocks.check(event, block -> + items.check(event, item -> { + if (block instanceof Block) { + return ((Block) block).isPreferredTool(item.getRandom()); + } else if (block instanceof BlockData) { + return ((BlockData) block).isPreferredTool(item.getRandom()); + } else { return false; - } else if (block instanceof BlockData) { - if (!items.check(event, (item) -> ((BlockData) block).isPreferredTool(item.getRandom()), isNegated())) - return false; - } else { - // invalid type - return false; - } - } - // all checks passed - return true; + } + }), isNegated()); } @Override diff --git a/src/test/skript/tests/syntaxes/conditions/CondIsPreferredTool.sk b/src/test/skript/tests/syntaxes/conditions/CondIsPreferredTool.sk index bbb3687e438..3a3e9f5149a 100644 --- a/src/test/skript/tests/syntaxes/conditions/CondIsPreferredTool.sk +++ b/src/test/skript/tests/syntaxes/conditions/CondIsPreferredTool.sk @@ -1,14 +1,14 @@ -test "preferred tool blockdata" when running minecraft "1.19.2": - assert wooden pickaxe is preferred tool for stone with "failed wooden pickaxe for stone blockdata" - assert wooden pickaxe is preferred tool for grass with "failed wooden pickaxe for grass blockdata" - assert wooden pickaxe is not preferred tool for obsidian with "failed wooden pickaxe for obsidian blockdata" +test "CondIsPreferredTool - BlockData" when running minecraft "1.19.2": + assert wooden pickaxe is preferred tool for minecraft:stone[] with "failed wooden pickaxe for stone blockdata" + assert wooden pickaxe is preferred tool for minecraft:grass[] with "failed wooden pickaxe for grass blockdata" + assert wooden pickaxe is not preferred tool for minecraft:obsidian[] with "failed wooden pickaxe for obsidian blockdata" - assert diamond pickaxe is preferred tool for obsidian with "failed diamond pickaxe for obsidian blockdata" + assert diamond pickaxe is preferred tool for minecraft:obsidian[] with "failed diamond pickaxe for obsidian blockdata" - assert wooden axe is not preferred tool for stone with "failed wooden axe for stone blockdata" - assert wooden axe is preferred tool for grass with "failed wooden axe for grass blockdata" + assert wooden axe is not preferred tool for minecraft:stone[] with "failed wooden axe for stone blockdata" + assert wooden axe is preferred tool for minecraft:grass[] with "failed wooden axe for grass blockdata" -test "preferred tool" when running minecraft "1.16.5": +test "CondIsPreferredTool - Block" when running minecraft "1.16.5": set {_block} to block at location(0,0,0, "world") set {_temp} to {_block}'s type set block at {_block} to grass @@ -21,5 +21,9 @@ test "preferred tool" when running minecraft "1.16.5": assert wooden pickaxe is not preferred tool for {_block} with "failed wooden pickaxe for obsidian" assert diamond pickaxe is preferred tool for {_block} with "failed diamond pickaxe for obsidian" + assert {_null} is not preferred tool for {_block} with "failed null is preferred tool for obsidian" + assert diamond pickaxe is not preferred tool for {_null} with "failed diamond pickaxe is preferred tool for null" + assert {_null} is preferred tool for {_null} to fail with "failed null is not preferred tool for null" + assert {_null} is not preferred tool for {_null} with "failed null is preferred tool for null" # leave no trace set block at {_block} to {_temp} From ddcec285c7a6c606707f67f6c4d11e0f63d0bcfc Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Thu, 4 May 2023 14:36:57 -0600 Subject: [PATCH 311/619] Fix error in non default variables (#5649) --- .../java/ch/njol/skript/lang/VariableString.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/java/ch/njol/skript/lang/VariableString.java b/src/main/java/ch/njol/skript/lang/VariableString.java index ddd636814d7..f9ae8f4ab2f 100644 --- a/src/main/java/ch/njol/skript/lang/VariableString.java +++ b/src/main/java/ch/njol/skript/lang/VariableString.java @@ -27,6 +27,7 @@ import org.bukkit.ChatColor; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.NotNull; import org.skriptlang.skript.lang.script.Script; import com.google.common.collect.Lists; @@ -581,20 +582,25 @@ public String toString(@Nullable Event event, boolean debug) { * * @return List<String> of all possible super class code names. */ + @NotNull public List<String> getDefaultVariableNames(String variableName, Event event) { - if (script == null || mode != StringMode.VARIABLE_NAME) { + if (script == null || mode != StringMode.VARIABLE_NAME) return Lists.newArrayList(); - } if (isSimple) { assert simple != null; return Lists.newArrayList(simple, "object"); } - List<StringBuilder> typeHints = Lists.newArrayList(new StringBuilder()); DefaultVariables data = script.getData(DefaultVariables.class); + // Checked in Variable#getRaw already assert data != null : "default variables not present in current script"; + Class<?>[] savedHints = data.get(variableName); + if (savedHints == null || savedHints.length == 0) + return Lists.newArrayList(); + + List<StringBuilder> typeHints = Lists.newArrayList(new StringBuilder()); // Represents the index of which expression in a variable string, example name::%entity%::%object% the index of 0 will be entity. int hintIndex = 0; assert string != null; @@ -604,9 +610,10 @@ public List<String> getDefaultVariableNames(String variableName, Event event) { continue; } StringBuilder[] current = typeHints.toArray(new StringBuilder[0]); - for (ClassInfo<?> classInfo : Classes.getAllSuperClassInfos(data.get(variableName)[hintIndex])) { + for (ClassInfo<?> classInfo : Classes.getAllSuperClassInfos(savedHints[hintIndex])) { for (StringBuilder builder : current) { String hint = builder.toString() + "<" + classInfo.getCodeName() + ">"; + // Has to duplicate the builder as it builds multiple off the last builder. typeHints.add(new StringBuilder(hint)); typeHints.remove(builder); } From 47cf6d6c5e7dbfc56e98c24fbd578f40e3952322 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Thu, 4 May 2023 14:48:43 -0600 Subject: [PATCH 312/619] Add info about clearing your tests in the test README (#5668) --- src/test/skript/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/test/skript/README.md b/src/test/skript/README.md index 77f80457f3e..610ae7e227c 100644 --- a/src/test/skript/README.md +++ b/src/test/skript/README.md @@ -12,6 +12,9 @@ to keep in mind: * Write descriptive assert messages and write comments * Ensure your tests pass with all supported Skript versions * Use standard condition for checking MC version +* When writing tests for your pull requests, please ensure that manipulations are cleaned up afterwards. This includes actions such as removing mobs that were manually spawned and resetting manipulated blocks to their original state. Also, try to avoid spawning hostile mobs, as this can cause issues (e.g. a spawned zombie catching fire from the sun). + +Note: The test world generate as a default super flat. Bedrock, dirt, grass, air, air...+ A good practice is to be using `spawn of world "world"` as a homing location, or anywhere near 0, 0, 0 is reasonable. ## Test Categories Scripts under <code>tests</code> are run on environments. Most of them are From b1fc462e540340e062ca015dfbe1e3fb0bf8c92d Mon Sep 17 00:00:00 2001 From: DelayedGaming <72163224+DelayedGaming@users.noreply.github.com> Date: Fri, 5 May 2023 04:55:43 +0800 Subject: [PATCH 313/619] Fix 'on falling block land' Event, and re-formatting (#5358) --- .../skript/events/EvtEntityBlockChange.java | 43 +++++++++++-------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/src/main/java/ch/njol/skript/events/EvtEntityBlockChange.java b/src/main/java/ch/njol/skript/events/EvtEntityBlockChange.java index 65d8192efb2..c639c4bc5a9 100644 --- a/src/main/java/ch/njol/skript/events/EvtEntityBlockChange.java +++ b/src/main/java/ch/njol/skript/events/EvtEntityBlockChange.java @@ -24,7 +24,7 @@ import ch.njol.skript.lang.SkriptEvent; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Checker; -import org.bukkit.Material; + import org.bukkit.entity.Enderman; import org.bukkit.entity.FallingBlock; import org.bukkit.entity.Sheep; @@ -39,28 +39,33 @@ public class EvtEntityBlockChange extends SkriptEvent { static { Skript.registerEvent("Enderman/Sheep/Silverfish/Falling Block", EvtEntityBlockChange.class, EntityChangeBlockEvent.class, ChangeEvent.patterns) - .description("Called when an enderman places or picks up a block, a sheep eats grass, ", - "a silverfish boops into/out of a block or a falling block lands and turns into a block respectively.") - .examples("on sheep eat:", - "\tkill entity", - "\tbroadcast \"A sheep stole some grass!\"", + .description( + "Called when an enderman places or picks up a block, a sheep eats grass, " + + "a silverfish boops into/out of a block or a falling block lands and turns into a block respectively." + ) + .examples( + "on sheep eat:", + "\tkill event-entity", + "\tbroadcast \"A sheep stole some grass!\"", + "", "on falling block land:", - "\tif event-entity is a falling dirt:", - "\t\tcancel event") + "\tevent-entity is a falling dirt", + "\tcancel event" + ) .since("<i>unknown</i>, 2.5.2 (falling block)"); } private enum ChangeEvent { - ENDERMAN_PLACE("enderman place", e -> e.getEntity() instanceof Enderman && e.getTo() != Material.AIR), - ENDERMAN_PICKUP("enderman pickup", e -> e.getEntity() instanceof Enderman && e.getTo() == Material.AIR), + ENDERMAN_PLACE("enderman place", event -> event.getEntity() instanceof Enderman && !ItemUtils.isAir(event.getTo())), + ENDERMAN_PICKUP("enderman pickup", event -> event.getEntity() instanceof Enderman && ItemUtils.isAir(event.getTo())), - SHEEP_EAT("sheep eat", e -> e.getEntity() instanceof Sheep), + SHEEP_EAT("sheep eat", event -> event.getEntity() instanceof Sheep), - SILVERFISH_ENTER("silverfish enter", e -> e.getEntity() instanceof Silverfish && !ItemUtils.isAir(e.getTo())), - SILVERFISH_EXIT("silverfish exit", e -> e.getEntity() instanceof Silverfish && ItemUtils.isAir(e.getTo())), + SILVERFISH_ENTER("silverfish enter", event -> event.getEntity() instanceof Silverfish && !ItemUtils.isAir(event.getTo())), + SILVERFISH_EXIT("silverfish exit", event -> event.getEntity() instanceof Silverfish && ItemUtils.isAir(event.getTo())), - FALLING_BLOCK_LANDING("falling block land[ing]", e -> e.getEntity() instanceof FallingBlock); + FALLING_BLOCK_LANDING("falling block land[ing]", event -> event.getEntity() instanceof FallingBlock && !ItemUtils.isAir(event.getTo())); private final String pattern; private final Checker<EntityChangeBlockEvent> checker; @@ -89,15 +94,15 @@ public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parser) { } @Override - public boolean check(Event e) { - if (!(e instanceof EntityChangeBlockEvent)) + public boolean check(Event event) { + if (!(event instanceof EntityChangeBlockEvent)) return false; - return event.checker.check((EntityChangeBlockEvent) e); + return this.event.checker.check((EntityChangeBlockEvent) event); } @Override - public String toString(@Nullable Event e, boolean debug) { - return event.name().toLowerCase(Locale.ENGLISH).replace('_', ' '); + public String toString(@Nullable Event event, boolean debug) { + return this.event.name().toLowerCase(Locale.ENGLISH).replace('_', ' '); } } From ad606119396802ff1904ac97859bcb338996f8dc Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Fri, 5 May 2023 00:06:57 +0300 Subject: [PATCH 314/619] Fix ExprDurability (#5017) --- .../skript/expressions/ExprDurability.java | 125 +++++++++--------- .../tests/regressions/3303-item durability.sk | 2 +- 2 files changed, 66 insertions(+), 61 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprDurability.java b/src/main/java/ch/njol/skript/expressions/ExprDurability.java index d0b2d75d42e..f97431d8609 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprDurability.java +++ b/src/main/java/ch/njol/skript/expressions/ExprDurability.java @@ -18,13 +18,13 @@ */ package ch.njol.skript.expressions; -import org.bukkit.block.Block; -import org.bukkit.block.BlockState; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; import org.bukkit.event.Event; import org.bukkit.inventory.ItemStack; import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.Skript; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.bukkitutil.ItemUtils; import ch.njol.skript.classes.Changer.ChangeMode; @@ -36,78 +36,85 @@ import ch.njol.skript.util.slot.Slot; import ch.njol.util.coll.CollectionUtils; -/** - * @author Peter Güttinger - */ -@Name("Data/Damage Value") -@Description({"The data/damage value of an item/block. Data values of blocks are only supported on 1.12.2 and below.", - "You usually don't need this expression as you can check and set items with aliases easily, ", - "but this expression can e.g. be used to \"add 1 to data of <item>\", e.g. for cycling through all wool colors."}) -@Examples({"set damage value of player's tool to 10", - "set data value of target block of player to 3", - "add 1 to the data value of the clicked block", - "reset data value of block at player"}) -@Since("1.2") +@Name("Damage Value/Durability") +@Description("The damage value/durability of an item.") +@Examples({ + "set damage value of player's tool to 10", + "reset the durability of {_item}", + "set durability of player's held item to 0" +}) +@Since("1.2, INSERT VERSION (durability reversed)") public class ExprDurability extends SimplePropertyExpression<Object, Long> { + private boolean durability; + static { - register(ExprDurability.class, Long.class, "((data|damage)[s] [value[s]]|durabilit(y|ies))", "itemtypes/blocks/slots"); + register(ExprDurability.class, Long.class, "(damage[s] [value[s]]|durability:durabilit(y|ies))", "itemtypes/slots"); + } + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + durability = parseResult.hasTag("durability"); + return super.init(exprs, matchedPattern, isDelayed, parseResult); } - + @Override @Nullable - public Long convert(final Object o) { - if (o instanceof Slot) { - final ItemStack i = ((Slot) o).getItem(); - return i == null ? null : (long) ItemUtils.getDamage(i); - } else if (o instanceof ItemType) { - ItemStack item = ((ItemType) o).getRandom(); - return (long) ItemUtils.getDamage(item); + public Long convert(Object object) { + ItemStack itemStack = null; + if (object instanceof Slot) { + itemStack = ((Slot) object).getItem(); + } else if (object instanceof ItemType) { + itemStack = ((ItemType) object).getRandom(); } - return null; + if (itemStack == null) + return null; + long damage = ItemUtils.getDamage(itemStack); + return durability ? itemStack.getType().getMaxDurability() - damage : damage; } - + @Override @Nullable - public Class<?>[] acceptChange(final ChangeMode mode) { + public Class<?>[] acceptChange(ChangeMode mode) { switch (mode) { - case ADD: case SET: - case RESET: + case ADD: case REMOVE: case DELETE: + case RESET: return CollectionUtils.array(Number.class); } return null; } - - @SuppressWarnings("null") + @Override - public void change(final Event e, final @Nullable Object[] delta, final ChangeMode mode) { - int a = delta == null ? 0 : ((Number) delta[0]).intValue(); - final Object[] os = getExpr().getArray(e); - for (final Object o : os) { + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + int i = delta == null ? 0 : ((Number) delta[0]).intValue(); + Object[] objects = getExpr().getArray(event); + for (Object object : objects) { ItemStack itemStack = null; - Block block = null; - - if (o instanceof ItemType) - itemStack = ((ItemType) o).getRandom(); - else if (o instanceof Slot) - itemStack = ((Slot) o).getItem(); - else + + if (object instanceof ItemType) { + itemStack = ((ItemType) object).getRandom(); + } else if (object instanceof Slot) { + itemStack = ((Slot) object).getItem(); + } + if (itemStack == null) return; - - int changeValue = itemStack != null ? ItemUtils.getDamage(itemStack) : block != null ? block.getData() : 0; - + + int changeValue = ItemUtils.getDamage(itemStack); + if (durability) + changeValue = itemStack.getType().getMaxDurability() - changeValue; + switch (mode) { case REMOVE: - a = -a; + i = -i; //$FALL-THROUGH$ case ADD: - changeValue += a; + changeValue += i; break; case SET: - changeValue = a; + changeValue = i; break; case DELETE: case RESET: @@ -116,18 +123,16 @@ else if (o instanceof Slot) case REMOVE_ALL: assert false; } - if (o instanceof ItemType && itemStack != null) { - ItemUtils.setDamage(itemStack,changeValue); - ((ItemType) o).setTo(new ItemType(itemStack)); - } else if (o instanceof Slot) { - ItemUtils.setDamage(itemStack,changeValue); - ((Slot) o).setItem(itemStack); + + if (durability && mode != ChangeMode.RESET && mode != ChangeMode.DELETE) + changeValue = itemStack.getType().getMaxDurability() - changeValue; + + if (object instanceof ItemType) { + ItemUtils.setDamage(itemStack, changeValue); + ((ItemType) object).setTo(new ItemType(itemStack)); } else { - BlockState blockState = ((Block) o).getState(); - try { - blockState.setRawData((byte) Math.max(0, changeValue)); - blockState.update(); - } catch (IllegalArgumentException | NullPointerException ignore) {} // Catch when a user sets the amount too high + ItemUtils.setDamage(itemStack, changeValue); + ((Slot) object).setItem(itemStack); } } } @@ -139,7 +144,7 @@ public Class<? extends Long> getReturnType() { @Override public String getPropertyName() { - return "data"; + return durability ? "durability" : "damage"; } } diff --git a/src/test/skript/tests/regressions/3303-item durability.sk b/src/test/skript/tests/regressions/3303-item durability.sk index 991b7cda983..acaecccaf8d 100644 --- a/src/test/skript/tests/regressions/3303-item durability.sk +++ b/src/test/skript/tests/regressions/3303-item durability.sk @@ -1,7 +1,7 @@ test "item durability": set {_i} to a diamond sword set durability of {_i} to 500 - assert durability of {_i} = 500 with "Durability of item should have been 500" + assert durability of {_i} != damage value of {_i} with "Durability of item should not equal damage value" test "block data value" when running below minecraft "1.13.2": set block at spawn of world "world" to farmland From fd2425b36594d5b407342e2f643e434fa32acd7b Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Fri, 5 May 2023 04:09:26 +0300 Subject: [PATCH 315/619] =?UTF-8?q?=F0=9F=9B=A0=20Fix=20toFormattedString?= =?UTF-8?q?=20for=20null=20nodes=20(#5303)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/ch/njol/skript/log/LogEntry.java | 66 ++++++++++--------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/src/main/java/ch/njol/skript/log/LogEntry.java b/src/main/java/ch/njol/skript/log/LogEntry.java index 65e977db0ca..17d2e5017e3 100644 --- a/src/main/java/ch/njol/skript/log/LogEntry.java +++ b/src/main/java/ch/njol/skript/log/LogEntry.java @@ -22,25 +22,24 @@ import ch.njol.skript.localization.ArgsMessage; import ch.njol.skript.util.Utils; + +import org.bukkit.ChatColor; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; import ch.njol.skript.config.Config; import ch.njol.skript.config.Node; -/** - * @author Peter Güttinger - */ public class LogEntry { - + public final Level level; public final int quality; - + public final String message; - + @Nullable public final Node node; - + private final String from; private final boolean tracked; @@ -55,27 +54,27 @@ public class LogEntry { public LogEntry(Level level, String message) { this(level, ErrorQuality.SEMANTIC_ERROR.quality(), message, SkriptLogger.getNode()); } - + public LogEntry(Level level, int quality, String message) { this(level, quality, message, SkriptLogger.getNode()); } - + public LogEntry(Level level, ErrorQuality quality, String message) { this(level, quality.quality(), message, SkriptLogger.getNode()); } - + public LogEntry(Level level, String message, @Nullable Node node) { this(level, ErrorQuality.SEMANTIC_ERROR.quality(), message, node); } - + public LogEntry(Level level, ErrorQuality quality, String message, Node node) { this(level, quality.quality(), message, node); } - + public LogEntry(Level level, int quality, String message, @Nullable Node node) { this(level, quality, message, node, false); } - + public LogEntry(Level level, int quality, String message, @Nullable Node node, boolean tracked) { this.level = level; this.quality = quality; @@ -84,9 +83,9 @@ public LogEntry(Level level, int quality, String message, @Nullable Node node, b this.tracked = tracked; from = tracked || Skript.debug() ? findCaller() : ""; } - + private static final String skriptLogPackageName = "" + SkriptLogger.class.getPackage().getName(); - + static String findCaller() { StackTraceElement[] es = new Exception().getStackTrace(); for (int i = 0; i < es.length; i++) { @@ -101,38 +100,38 @@ static String findCaller() { } return " (from an unknown source)"; } - + public Level getLevel() { return level; } - + public int getQuality() { return quality; } - + public String getMessage() { return message; } - - private boolean used = false; - + + private boolean used; + void discarded(String info) { used = true; if (tracked) SkriptLogger.LOGGER.warning(" # LogEntry '" + message + "'" + from + " discarded" + findCaller() + "; " + (new Exception()).getStackTrace()[1] + "; " + info); } - + void logged() { used = true; if (tracked) SkriptLogger.LOGGER.warning(" # LogEntry '" + message + "'" + from + " logged" + findCaller()); } - + @Override protected void finalize() { assert used : message + from; } - + @Override public String toString() { if (node == null || level.intValue() < Level.WARNING.intValue()) @@ -143,11 +142,9 @@ public String toString() { } public String toFormattedString() { - if (node == null || level.intValue() < Level.WARNING.intValue()) + if (level.intValue() < Level.WARNING.intValue()) return message; - Config c = node.getConfig(); - ArgsMessage details; ArgsMessage lineInfo = WARNING_LINE_INFO; if (level.intValue() == Level.WARNING.intValue()) { // warnings @@ -159,15 +156,20 @@ public String toFormattedString() { details = OTHER_DETAILS; } - String from = this.from; - if (!from.isEmpty()) - from = "§7 " + from + "\n"; - // Replace configured messages chat styles without user variables String lineInfoMsg = replaceNewline(Utils.replaceEnglishChatStyles(lineInfo.getValue() == null ? lineInfo.key : lineInfo.getValue())); String detailsMsg = replaceNewline(Utils.replaceEnglishChatStyles(details.getValue() == null ? details.key : details.getValue())); String lineDetailsMsg = replaceNewline(Utils.replaceEnglishChatStyles(LINE_DETAILS.getValue() == null ? LINE_DETAILS.key : LINE_DETAILS.getValue())); + if (node == null) + return String.format(detailsMsg.replaceAll("^\\s+", ""), message); // Remove line beginning spaces + + Config c = node.getConfig(); + String from = this.from; + + if (!from.isEmpty()) + from = ChatColor.GRAY + " " + from + "\n"; + return String.format(lineInfoMsg, String.valueOf(node.getLine()), c.getFileName()) + // String.valueOf is to convert the line number (int) to a String String.format(detailsMsg, message.replaceAll("§", "&")) + from + @@ -177,5 +179,5 @@ public String toFormattedString() { private String replaceNewline(String s) { return s.replaceAll("\\\\n", "\n"); } - + } From d211a207385961de0df66327ac03026f644f6398 Mon Sep 17 00:00:00 2001 From: Kiip <25848425+kiip1@users.noreply.github.com> Date: Sat, 6 May 2023 07:51:40 +0200 Subject: [PATCH 316/619] Rewrite Math2 (#5297) --- .../skript/classes/data/DefaultFunctions.java | 2 +- src/main/java/ch/njol/skript/util/AABB.java | 18 +- src/main/java/ch/njol/util/Math2.java | 323 +++++++++--------- 3 files changed, 171 insertions(+), 172 deletions(-) diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java b/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java index d9c16f703af..aeb0fe4735b 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java @@ -401,7 +401,7 @@ public Date[] executeSimple(Object[][] params) { Number n = (Number) params[i][0]; double value = n.doubleValue() * scale[i] + offsets[i] + carry; - int v = Math2.floorI(value); + int v = (int) Math2.floor(value); carry = (value - v) * relations[i]; //noinspection MagicConstant c.set(field, v); diff --git a/src/main/java/ch/njol/skript/util/AABB.java b/src/main/java/ch/njol/skript/util/AABB.java index 34191316fdf..738df55dd8e 100644 --- a/src/main/java/ch/njol/skript/util/AABB.java +++ b/src/main/java/ch/njol/skript/util/AABB.java @@ -109,16 +109,16 @@ public World getWorld() { @Override public Iterator<Block> iterator() { return new Iterator<Block>() { - private final int minX = Math2.ceilI(lowerBound.getX() - Skript.EPSILON), - minY = Math2.ceilI(lowerBound.getY() - Skript.EPSILON), - minZ = Math2.ceilI(lowerBound.getZ() - Skript.EPSILON); - private final int maxX = Math2.floorI(upperBound.getX() + Skript.EPSILON), - maxY = Math2.floorI(upperBound.getY() + Skript.EPSILON), - maxZ = Math2.floorI(upperBound.getZ() + Skript.EPSILON); + private final int minX = (int) Math2.ceil(lowerBound.getX()); + private final int minY = (int) Math2.ceil(lowerBound.getY()); + private final int minZ = (int) Math2.ceil(lowerBound.getZ()); + private final int maxX = (int) Math2.floor(upperBound.getX()); + private final int maxY = (int) Math2.floor(upperBound.getY()); + private final int maxZ = (int) Math2.floor(upperBound.getZ()); - private int x = minX - 1,// next() increases x by one immediately - y = minY, - z = minZ; + private int x = minX - 1; // next() increases x by one immediately + private int y = minY; + private int z = minZ; @Override public boolean hasNext() { diff --git a/src/main/java/ch/njol/util/Math2.java b/src/main/java/ch/njol/util/Math2.java index f7e1b110846..b8b5179ff22 100644 --- a/src/main/java/ch/njol/util/Math2.java +++ b/src/main/java/ch/njol/util/Math2.java @@ -19,229 +19,228 @@ package ch.njol.util; import ch.njol.skript.Skript; +import ch.njol.skript.util.MarkedForRemoval; +import org.jetbrains.annotations.ApiStatus; -public abstract class Math2 { - - public static int min(int a, int b, int c) { - return a <= b ? (a <= c ? a : c) : (b <= c ? b : c); - } - - public static int min(int... nums) { - if (nums == null || nums.length == 0) { - assert false; - return 0; - } - int min = nums[0]; - for (int i = 1; i < nums.length; i++) { - if (nums[i] < min) - min = nums[i]; - } - return min; - } - - public static int max(int a, int b, int c) { - return a >= b ? (a >= c ? a : c) : (b >= c ? b : c); - } - - public static int max(int... nums) { - if (nums == null || nums.length == 0) { - assert false; - return 0; - } - int max = nums[0]; - for (int i = 1; i < nums.length; i++) { - if (nums[i] > max) - max = nums[i]; - } - return max; - } - - public static double min(double a, double b, double c) { - return a <= b ? (a <= c ? a : c) : (b <= c ? b : c); - } - - public static double min(double... nums) { - if (nums == null || nums.length == 0) { - assert false; - return Double.NaN; - } - double min = nums[0]; - for (int i = 1; i < nums.length; i++) { - if (nums[i] < min) - min = nums[i]; - } - return min; - } +import java.util.Arrays; + +/** + * This class is not to be used by addons. In the future methods may + * change signature, contract and/or get removed without warning. + * <p> + * Behaviour for an edge case like NaN or infinite is undefined. + */ +@ApiStatus.Internal +public final class Math2 { - public static double max(double a, double b, double c) { - return a >= b ? (a >= c ? a : c) : (b >= c ? b : c); - } + private Math2() {} - public static double max(double... nums) { - if (nums == null || nums.length == 0) { - assert false; - return Double.NaN; - } - double max = nums[0]; - for (int i = 1; i < nums.length; i++) { - if (nums[i] > max) - max = nums[i]; - } - return max; + /** + * Fits an int into the given interval. The method's behaviour when min > max is unspecified. + * + * @return An int in between min and max + */ + public static int fit(int min, int value, int max) { + assert min <= max : min + "," + value + "," + max; + return Math.min(Math.max(value, min), max); } /** - * Fits a number into the given interval. The method's behaviour when min > max is unspecified. - * - * @return <tt>x <= min ? min : x >= max ? max : x</tt> + * Fits a long into the given interval. The method's behaviour when min > max is unspecified. + * + * @return A long in between min and max */ - public static int fit(int min, int x, int max) { - assert min <= max : min + "," + x + "," + max; - return x <= min ? min : x >= max ? max : x; + public static long fit(long min, long value, long max) { + assert min <= max : min + "," + value + "," + max; + return Math.min(Math.max(value, min), max); } /** - * Fits a number into the given interval. The method's behaviour when min > max is unspecified. - * - * @return <tt>x <= min ? min : x >= max ? max : x</tt> + * Fits a float into the given interval. The method's behaviour when min > max is unspecified. + * + * @return A float in between min and max */ - public static float fit(float min, float x, float max) { - assert min <= max : min + "," + x + "," + max; - return x <= min ? min : x >= max ? max : x; + public static float fit(float min, float value, float max) { + assert min <= max : min + "," + value + "," + max; + return Math.min(Math.max(value, min), max); } /** - * Fits a number into the given interval. The method's behaviour when min > max is unspecified. + * Fits a double into the given interval. The method's behaviour when min > max is unspecified. * - * @return <tt>x <= min ? min : x >= max ? max : x</tt> + * @return A double in between min and max */ - public static double fit(double min, double x, double max) { - assert min <= max : min + "," + x + "," + max; - return x <= min ? min : x >= max ? max : x; + public static double fit(double min, double value, double max) { + assert min <= max : min + "," + value + "," + max; + return Math.min(Math.max(value, min), max); } /** * Modulo that returns positive values even for negative arguments. * - * @return <tt>d%m < 0 ? d%m + m : d%m</tt> + * @return Int result of value modulo mod */ - public static double mod(double d, double m) { - double r = d % m; - return r < 0 ? r + m : r; + public static int mod(int value, int mod) { + return (value % mod + mod) % mod; } /** * Modulo that returns positive values even for negative arguments. * - * @return <tt>d%m < 0 ? d%m + m : d%m</tt> + * @return Long result of value modulo mod */ - public static float mod(float d, float m) { - float r = d % m; - return r < 0 ? r + m : r; + public static long mod(long value, long mod) { + return (value % mod + mod) % mod; } /** * Modulo that returns positive values even for negative arguments. * - * @return <tt>d%m < 0 ? d%m + m : d%m</tt> + * @return Float result of value modulo mod */ - public static int mod(int d, int m) { - int r = d % m; - return r < 0 ? r + m : r % m; + public static float mod(float value, float mod) { + return (value % mod + mod) % mod; } /** * Modulo that returns positive values even for negative arguments. * - * @return <tt>d%m < 0 ? d%m + m : d%m</tt> + * @return Double result of value modulo mod + */ + public static double mod(double value, double mod) { + return (value % mod + mod) % mod; + } + + /** + * Ceils the given float and returns the result as an int. */ - public static long mod(long d, long m) { - long r = d % m; - return r < 0 ? r + m : r % m; + public static int ceil(float value) { + return (int) Math.ceil(value - Skript.EPSILON); + } + + /** + * Rounds the given float (where .5 is rounded up) and returns the result as an int. + */ + public static int round(float value) { + return (int) Math.round(value + Skript.EPSILON); } /** * Floors the given double and returns the result as a long. - * <p> - * This method can be up to 20 times faster than the default {@link Math#floor(double)} (both with and without casting to long). */ - public static long floor(double d) { - d += Skript.EPSILON; - long l = (long) d; - if (!(d < 0)) // d >= 0 || d == NaN - return l; - if (l == Long.MIN_VALUE) - return Long.MIN_VALUE; - return d == l ? l : l - 1; + public static long floor(double value) { + return (long) Math.floor(value + Skript.EPSILON); } /** * Ceils the given double and returns the result as a long. - * <p> - * This method can be up to 20 times faster than the default {@link Math#ceil(double)} (both with and without casting to long). */ - public static long ceil(double d) { - d -= Skript.EPSILON; - long l = (long) d; - if (!(d > 0)) // d <= 0 || d == NaN - return l; - if (l == Long.MAX_VALUE) - return Long.MAX_VALUE; - return d == l ? l : l + 1; + public static long ceil(double value) { + return (long) Math.ceil(value - Skript.EPSILON); } /** * Rounds the given double (where .5 is rounded up) and returns the result as a long. - * <p> - * This method is more exact and faster than {@link Math#round(double)} of Java 7 and older. */ - public static long round(double d) { - d += Skript.EPSILON; - if (Math.getExponent(d) >= 52) - return (long) d; - return floor(d + 0.5); - } - - public static int floorI(double d) { - d += Skript.EPSILON; - int i = (int) d; - if (!(d < 0)) // d >= 0 || d == NaN - return i; - if (i == Integer.MIN_VALUE) - return Integer.MIN_VALUE; - return d == i ? i : i - 1; - } - - public static int ceilI(double d) { - d -= Skript.EPSILON; - int i = (int) d; - if (!(d > 0)) // d <= 0 || d == NaN - return i; - if (i == Integer.MAX_VALUE) - return Integer.MAX_VALUE; - return d == i ? i : i + 1; - } - - public static long floor(float f) { - f += Skript.EPSILON; - long l = (long) f; - if (!(f < 0)) // f >= 0 || f == NaN - return l; - if (l == Long.MIN_VALUE) - return Long.MIN_VALUE; - return f == l ? l : l - 1; + public static long round(double value) { + return Math.round(value + Skript.EPSILON); } - + /** - * Guarantees a float is neither NaN nor INF. + * Guarantees a float is neither NaN nor infinite. * Useful for situations when safe floats are required. * - * @return 0 if f is NaN or INF, otherwise f + * @return 0 if value is NaN or infinite, otherwise value */ - public static float safe(float f) { - if (f != f || Float.isInfinite(f)) //NaN or INF + public static float safe(float value) { + return Float.isFinite(value) ? value : 0; + } + + @Deprecated + @MarkedForRemoval + public static int floorI(double value) { + return (int) Math.floor(value + Skript.EPSILON); + } + + @Deprecated + @MarkedForRemoval + public static int ceilI(double value) { + return (int) Math.ceil(value - Skript.EPSILON); + } + + // Change signature to return int instead of removing. + @Deprecated + @MarkedForRemoval + public static long floor(float value) { + return (long) Math.floor(value + Skript.EPSILON); + } + + @Deprecated + @MarkedForRemoval + public static int min(int a, int b, int c) { + return Math.min(a, Math.min(b, c)); + } + + @Deprecated + @MarkedForRemoval + public static int min(int... numbers) { + if (numbers.length == 0) return 0; - return f; + + return Arrays.stream(numbers) + .min() + .getAsInt(); + } + + @Deprecated + @MarkedForRemoval + public static int max(int a, int b, int c) { + return Math.max(a, Math.max(b, c)); + } + + @Deprecated + @MarkedForRemoval + public static int max(int... numbers) { + if (numbers.length == 0) + return 0; + + return Arrays.stream(numbers) + .max() + .getAsInt(); + } + + @Deprecated + @MarkedForRemoval + public static double min(double a, double b, double c) { + return Math.min(a, Math.min(b, c)); + } + + @Deprecated + @MarkedForRemoval + public static double min(double... numbers) { + if (numbers.length == 0) + return Double.NaN; + + return Arrays.stream(numbers) + .min() + .getAsDouble(); + } + + @Deprecated + @MarkedForRemoval + public static double max(double a, double b, double c) { + return Math.max(a, Math.max(b, c)); + } + + @Deprecated + @MarkedForRemoval + public static double max(double... numbers) { + if (numbers.length == 0) + return Double.NaN; + + return Arrays.stream(numbers) + .max() + .getAsDouble(); } } From 56cfb2d6e60b0ff4a24432c0289cb706fef25d52 Mon Sep 17 00:00:00 2001 From: iPlexy <61617739+iPlexy@users.noreply.github.com> Date: Sat, 6 May 2023 08:10:09 +0200 Subject: [PATCH 317/619] Better chest inventory syntax (#5099) Co-authored-by: Dennis DiCarlo <d.dicarlo@eurokey.de> Co-authored-by: Kenzie <admin@moderocky.com> Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> --- .../java/ch/njol/skript/expressions/ExprChestInventory.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprChestInventory.java b/src/main/java/ch/njol/skript/expressions/ExprChestInventory.java index 7f9415fe4b1..fdcd07b63be 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprChestInventory.java +++ b/src/main/java/ch/njol/skript/expressions/ExprChestInventory.java @@ -44,8 +44,8 @@ public class ExprChestInventory extends SimpleExpression<Inventory> { static { Skript.registerExpression(ExprChestInventory.class, Inventory.class, ExpressionType.COMBINED, - "[a [new]] chest inventory (named|with name) %string% [with %-number% row[s]]", - "[a [new]] chest inventory with %number% row[s] [(named|with name) %-string%]"); + "[a] [new] chest inventory (named|with name) %string% [with %-number% row[s]]", + "[a] [new] chest inventory with %number% row[s] [(named|with name) %-string%]"); } private static final String DEFAULT_CHEST_TITLE = InventoryType.CHEST.getDefaultTitle(); From a4f1a088896a29dd0c67cc7815929e754cd8d78d Mon Sep 17 00:00:00 2001 From: oskarkk <16339637+oskarkk@users.noreply.github.com> Date: Sat, 6 May 2023 08:28:48 +0200 Subject: [PATCH 318/619] Clarify error message about leftclick on entity (#4696) --- .../java/ch/njol/skript/events/EvtClick.java | 90 ++++++++++--------- 1 file changed, 50 insertions(+), 40 deletions(-) diff --git a/src/main/java/ch/njol/skript/events/EvtClick.java b/src/main/java/ch/njol/skript/events/EvtClick.java index 9b16637e587..9f43dddea42 100644 --- a/src/main/java/ch/njol/skript/events/EvtClick.java +++ b/src/main/java/ch/njol/skript/events/EvtClick.java @@ -21,6 +21,7 @@ import org.bukkit.block.Block; import org.bukkit.entity.ArmorStand; import org.bukkit.entity.Entity; +import org.bukkit.entity.Vehicle; import org.bukkit.event.Event; import org.bukkit.event.block.Action; import org.bukkit.event.player.PlayerEvent; @@ -31,46 +32,43 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.comparator.Relation; import ch.njol.skript.Skript; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.bukkitutil.ClickEventTracker; -import org.skriptlang.skript.lang.comparator.Relation; import ch.njol.skript.classes.data.DefaultComparators; import ch.njol.skript.entity.EntityData; import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptEvent; import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.log.ErrorQuality; import ch.njol.util.Checker; import ch.njol.util.coll.CollectionUtils; -@SuppressWarnings("unchecked") public class EvtClick extends SkriptEvent { /** * Click types. */ private final static int RIGHT = 1, LEFT = 2, ANY = RIGHT | LEFT; - + /** * Tracks PlayerInteractEvents to deduplicate them. */ public static final ClickEventTracker interactTracker = new ClickEventTracker(Skript.getInstance()); - + /** * Tracks PlayerInteractEntityEvents to deduplicate them. */ private static final ClickEventTracker entityInteractTracker = new ClickEventTracker(Skript.getInstance()); - + static { Class<? extends PlayerEvent>[] eventTypes = CollectionUtils.array( PlayerInteractEvent.class, PlayerInteractEntityEvent.class, PlayerInteractAtEntityEvent.class ); - Skript.registerEvent("Click", EvtClick.class, eventTypes, - "[(" + RIGHT + "¦right|" + LEFT + "¦left)(| |-)][mouse(| |-)]click[ing] [on %-entitydata/itemtype%] [(with|using|holding) %-itemtype%]", - "[(" + RIGHT + "¦right|" + LEFT + "¦left)(| |-)][mouse(| |-)]click[ing] (with|using|holding) %itemtype% on %entitydata/itemtype%") + "[(" + RIGHT + ":right|" + LEFT + ":left)(| |-)][mouse(| |-)]click[ing] [on %-entitydata/itemtype%] [(with|using|holding) %-itemtype%]", + "[(" + RIGHT + ":right|" + LEFT + ":left)(| |-)][mouse(| |-)]click[ing] (with|using|holding) %itemtype% on %entitydata/itemtype%") .description("Called when a user clicks on a block, an entity or air with or without an item in their hand.", "Please note that rightclick events with an empty hand while not looking at a block are not sent to the server, so there's no way to detect them.", "Also note that a leftclick on an entity is an attack and thus not covered by the 'click' event, but the 'damage' event.") @@ -81,47 +79,59 @@ public class EvtClick extends SkriptEvent { "on click with a sword:") .since("1.0"); } - + /** * Only trigger when one of these is interacted with. */ @Nullable - private Literal<?> types = null; - + private Literal<?> type; + /** * Only trigger when then item player clicks with is one of these. */ @Nullable private Literal<ItemType> tools; - + /** * Click types to trigger. */ private int click = ANY; - + @Override - public boolean init(final Literal<?>[] args, final int matchedPattern, final ParseResult parser) { - click = parser.mark == 0 ? ANY : parser.mark; - types = args[matchedPattern]; - if (types != null && !ItemType.class.isAssignableFrom(types.getReturnType())) { + @SuppressWarnings("unchecked") + public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult) { + click = parseResult.mark == 0 ? ANY : parseResult.mark; + type = args[matchedPattern]; + if (type != null && !ItemType.class.isAssignableFrom(type.getReturnType())) { + Literal<EntityData<?>> entitydata = (Literal<EntityData<?>>) type; if (click == LEFT) { - Skript.error("A leftclick on an entity is an attack and thus not covered by the 'click' event, but the 'damage' event.", ErrorQuality.SEMANTIC_ERROR); + if (Vehicle.class.isAssignableFrom(entitydata.getSingle().getType())) { + Skript.error("A leftclick on an entity is an attack and thus not covered by the 'click' event, but the 'vehicle damage' event."); + } else { + Skript.error("A leftclick on an entity is an attack and thus not covered by the 'click' event, but the 'damage' event."); + } return false; } else if (click == ANY) { - Skript.warning("A leftclick on an entity is an attack and thus not covered by the 'click' event, but the 'damage' event. Change this event to a rightclick to disable this warning message."); + if (Vehicle.class.isAssignableFrom(entitydata.getSingle().getType())) { + Skript.error("A leftclick on an entity is an attack and thus not covered by the 'click' event, but the 'vehicle damage' event. " + + "Change this event to a rightclick to fix this warning message."); + } else { + Skript.error("A leftclick on an entity is an attack and thus not covered by the 'click' event, but the 'damage' event. " + + "Change this event to a rightclick to fix this warning message."); + } } } tools = (Literal<ItemType>) args[1 - matchedPattern]; return true; } - + @Override - public boolean check(final Event e) { - final Block block; - final Entity entity; + public boolean check(Event event) { + Block block; + Entity entity; - if (e instanceof PlayerInteractEntityEvent) { - PlayerInteractEntityEvent clickEvent = ((PlayerInteractEntityEvent) e); + if (event instanceof PlayerInteractEntityEvent) { + PlayerInteractEntityEvent clickEvent = ((PlayerInteractEntityEvent) event); Entity clicked = clickEvent.getRightClicked(); // Usually, don't handle these events @@ -136,7 +146,7 @@ public boolean check(final Event e) { return false; // PlayerInteractAtEntityEvent called only once for armor stands - if (!(e instanceof PlayerInteractAtEntityEvent)) { + if (!(event instanceof PlayerInteractAtEntityEvent)) { if (!entityInteractTracker.checkEvent(clickEvent.getPlayer(), clickEvent, clickEvent.getHand())) { return false; // Not first event this tick } @@ -144,8 +154,8 @@ public boolean check(final Event e) { entity = clicked; block = null; - } else if (e instanceof PlayerInteractEvent) { - PlayerInteractEvent clickEvent = ((PlayerInteractEvent) e); + } else if (event instanceof PlayerInteractEvent) { + PlayerInteractEvent clickEvent = ((PlayerInteractEvent) event); // Figure out click type, filter non-click events Action a = clickEvent.getAction(); @@ -179,14 +189,14 @@ public boolean check(final Event e) { return false; } - if (tools != null && !tools.check(e, new Checker<ItemType>() { + if (tools != null && !tools.check(event, new Checker<ItemType>() { @Override public boolean check(final ItemType t) { - if (e instanceof PlayerInteractEvent) { - return t.isOfType(((PlayerInteractEvent) e).getItem()); + if (event instanceof PlayerInteractEvent) { + return t.isOfType(((PlayerInteractEvent) event).getItem()); } else { // PlayerInteractEntityEvent doesn't have item associated with it - PlayerInventory invi = ((PlayerInteractEntityEvent) e).getPlayer().getInventory(); - ItemStack item = ((PlayerInteractEntityEvent) e).getHand() == EquipmentSlot.HAND + PlayerInventory invi = ((PlayerInteractEntityEvent) event).getPlayer().getInventory(); + ItemStack item = ((PlayerInteractEntityEvent) event).getHand() == EquipmentSlot.HAND ? invi.getItemInMainHand() : invi.getItemInOffHand(); return t.isOfType(item); } @@ -195,8 +205,8 @@ public boolean check(final ItemType t) { return false; } - if (types != null) { - return types.check(e, new Checker<Object>() { + if (type != null) { + return type.check(event, new Checker<Object>() { @Override public boolean check(final Object o) { if (entity != null) { @@ -209,10 +219,10 @@ public boolean check(final Object o) { } return true; } - + @Override - public String toString(final @Nullable Event e, final boolean debug) { - return (click == LEFT ? "left" : click == RIGHT ? "right" : "") + "click" + (types != null ? " on " + types.toString(e, debug) : "") + (tools != null ? " holding " + tools.toString(e, debug) : ""); + public String toString(@Nullable Event e, boolean debug) { + return (click == LEFT ? "left" : click == RIGHT ? "right" : "") + "click" + (type != null ? " on " + type.toString(e, debug) : "") + (tools != null ? " holding " + tools.toString(e, debug) : ""); } - + } From 4769a9d0dee49d16d1ded0ca703855cc9f8da92a Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Tue, 9 May 2023 16:39:03 -0600 Subject: [PATCH 319/619] Fixes IllegalArgumentException throwing with unloaded worlds in location serialization (#5673) --- .../skript/classes/data/BukkitClasses.java | 47 ++++++++++--------- 1 file changed, 26 insertions(+), 21 deletions(-) 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 99958b74a10..35237bcb7ab 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -28,18 +28,6 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; -import ch.njol.util.coll.iterator.ArrayIterator; -import ch.njol.skript.classes.ClassInfo; -import ch.njol.skript.classes.ConfigurationSerializer; -import ch.njol.skript.classes.EnumClassInfo; -import ch.njol.skript.classes.Parser; -import ch.njol.skript.classes.Serializer; -import ch.njol.skript.lang.util.SimpleLiteral; -import ch.njol.skript.util.BlockUtils; -import ch.njol.skript.util.EnchantmentType; -import ch.njol.skript.util.PotionEffectUtils; -import ch.njol.skript.util.StringMode; -import io.papermc.paper.world.MoonPhase; import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.Difficulty; @@ -92,14 +80,25 @@ import ch.njol.skript.aliases.ItemType; import ch.njol.skript.bukkitutil.EnchantmentUtils; import ch.njol.skript.bukkitutil.ItemUtils; +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.classes.ConfigurationSerializer; +import ch.njol.skript.classes.EnumClassInfo; +import ch.njol.skript.classes.Parser; +import ch.njol.skript.classes.Serializer; import ch.njol.skript.entity.EntityData; import ch.njol.skript.expressions.ExprDamageCause; import ch.njol.skript.expressions.base.EventValueExpression; import ch.njol.skript.lang.ParseContext; +import ch.njol.skript.lang.util.SimpleLiteral; import ch.njol.skript.localization.Language; import ch.njol.skript.registrations.Classes; +import ch.njol.skript.util.BlockUtils; +import ch.njol.skript.util.EnchantmentType; +import ch.njol.skript.util.PotionEffectUtils; +import ch.njol.skript.util.StringMode; import ch.njol.util.StringUtils; import ch.njol.yggdrasil.Fields; +import io.papermc.paper.world.MoonPhase; /** * @author Peter Güttinger @@ -390,15 +389,21 @@ public String getDebugMessage(final Location l) { } }).serializer(new Serializer<Location>() { @Override - public Fields serialize(final Location l) { - final Fields f = new Fields(); - f.putObject("world", l.getWorld()); - f.putPrimitive("x", l.getX()); - f.putPrimitive("y", l.getY()); - f.putPrimitive("z", l.getZ()); - f.putPrimitive("yaw", l.getYaw()); - f.putPrimitive("pitch", l.getPitch()); - return f; + public Fields serialize(Location location) { + Fields fields = new Fields(); + World world = null; + try { + world = location.getWorld(); + } catch (IllegalArgumentException exception) { + Skript.warning("A location failed to serialize with its defined world, as the world was unloaded."); + } + fields.putObject("world", world); + fields.putPrimitive("x", location.getX()); + fields.putPrimitive("y", location.getY()); + fields.putPrimitive("z", location.getZ()); + fields.putPrimitive("yaw", location.getYaw()); + fields.putPrimitive("pitch", location.getPitch()); + return fields; } @Override From 6724b012792ef7feab2a9645ede2bbfa46941016 Mon Sep 17 00:00:00 2001 From: DelayedGaming <72163224+DelayedGaming@users.noreply.github.com> Date: Wed, 10 May 2023 06:46:04 +0800 Subject: [PATCH 320/619] Fix ExprVersionString Unusable in Server List Ping Event (#5609) --- .../skript/expressions/ExprVersionString.java | 61 ++++++++++--------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprVersionString.java b/src/main/java/ch/njol/skript/expressions/ExprVersionString.java index e94202afe4b..32427bc0747 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVersionString.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVersionString.java @@ -18,11 +18,6 @@ */ package ch.njol.skript.expressions; -import org.bukkit.event.Event; -import org.bukkit.event.server.ServerListPingEvent; -import org.eclipse.jdt.annotation.Nullable; - -import com.destroystokyo.paper.event.server.PaperServerListPingEvent; import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; @@ -37,32 +32,39 @@ 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 org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; @Name("Version String") -@Description({"The text to show if the protocol version of the server doesn't match with protocol version of the client. " + - "You can check the <a href='#ExprProtocolVersion'>protocol version</a> expression for more information about this.", - "This can only be set in a <a href='events.html#server_list_ping'>server list ping</a> event."}) -@Examples({"on server list ping:", - " set the protocol version to 0 # 13w41a (1.7), so it will show the version string always", - " set the version string to \"<light green>Version: <orange>%minecraft version%\""}) +@Description({ + "The text to show if the protocol version of the server doesn't match with protocol version of the client. " + + "You can check the <a href='#ExprProtocolVersion'>protocol version</a> expression for more information about this.", + "This can only be set in a <a href='events.html#server_list_ping'>server list ping</a> event." +}) +@Examples({ + "on server list ping:", + "\tset the protocol version to 0 # 13w41a (1.7), so it will show the version string always", + "\tset the version string to \"<light green>Version: <orange>%minecraft version%\"" +}) @Since("2.3") -@RequiredPlugins("Paper 1.12.2 or newer") -@Events("server list ping") +@RequiredPlugins("Paper 1.12.2+") +@Events("Server List Ping") public class ExprVersionString extends SimpleExpression<String> { + private static final boolean PAPER_EVENT_EXISTS = Skript.classExists("com.destroystokyo.paper.event.server.PaperServerListPingEvent"); + static { - Skript.registerExpression(ExprVersionString.class, String.class, ExpressionType.SIMPLE, "[the] [(shown|custom)] version [(string|text)]"); + Skript.registerExpression(ExprVersionString.class, String.class, ExpressionType.SIMPLE, "[the] [shown|custom] version [string|text]"); } - private static final boolean PAPER_EVENT_EXISTS = Skript.classExists("com.destroystokyo.paper.event.server.PaperServerListPingEvent"); - @Override public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - if (getParser().isCurrentEvent(ServerListPingEvent.class)) { - Skript.error("The version string expression requires Paper 1.12.2 or newer"); + if (!PAPER_EVENT_EXISTS) { + Skript.error("The 'version string' expression requires Paper 1.12.2+"); return false; - } else if (!(PAPER_EVENT_EXISTS && getParser().isCurrentEvent(PaperServerListPingEvent.class))) { - Skript.error("The version string expression can't be used outside of a server list ping event"); + } else if (!getParser().isCurrentEvent(PaperServerListPingEvent.class)) { + Skript.error("The 'version string' expression can't be used outside of a 'server list ping' event"); return false; } return true; @@ -70,10 +72,10 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable - public String[] get(Event e) { - if (!(e instanceof PaperServerListPingEvent)) - return null; - return CollectionUtils.array(((PaperServerListPingEvent) e).getVersion()); + public String[] get(Event event) { + if (!(event instanceof PaperServerListPingEvent)) + return new String[0]; + return CollectionUtils.array(((PaperServerListPingEvent) event).getVersion()); } @Override @@ -88,13 +90,12 @@ public Class<?>[] acceptChange(ChangeMode mode) { return null; } - @SuppressWarnings("null") @Override - public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { - if (!(e instanceof PaperServerListPingEvent)) + @SuppressWarnings("null") + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + if (!(event instanceof PaperServerListPingEvent)) return; - - ((PaperServerListPingEvent) e).setVersion(((String) delta[0])); + ((PaperServerListPingEvent) event).setVersion(((String) delta[0])); } @Override @@ -108,7 +109,7 @@ public Class<? extends String> getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { return "the version string"; } From 20eb553c2a3826dde39334baf97dcea2b8957ded Mon Sep 17 00:00:00 2001 From: DelayedGaming <72163224+DelayedGaming@users.noreply.github.com> Date: Wed, 17 May 2023 06:24:27 +0800 Subject: [PATCH 321/619] New Event: Falling Block Fall (#5607) --- .../java/ch/njol/skript/events/EvtEntityBlockChange.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/njol/skript/events/EvtEntityBlockChange.java b/src/main/java/ch/njol/skript/events/EvtEntityBlockChange.java index c639c4bc5a9..517471c0dcb 100644 --- a/src/main/java/ch/njol/skript/events/EvtEntityBlockChange.java +++ b/src/main/java/ch/njol/skript/events/EvtEntityBlockChange.java @@ -65,14 +65,15 @@ private enum ChangeEvent { SILVERFISH_ENTER("silverfish enter", event -> event.getEntity() instanceof Silverfish && !ItemUtils.isAir(event.getTo())), SILVERFISH_EXIT("silverfish exit", event -> event.getEntity() instanceof Silverfish && ItemUtils.isAir(event.getTo())), + FALLING_BLOCK_FALLING("falling block fall[ing]", event -> event.getEntity() instanceof FallingBlock && ItemUtils.isAir(event.getTo())), FALLING_BLOCK_LANDING("falling block land[ing]", event -> event.getEntity() instanceof FallingBlock && !ItemUtils.isAir(event.getTo())); private final String pattern; private final Checker<EntityChangeBlockEvent> checker; - ChangeEvent(String pattern, Checker<EntityChangeBlockEvent> c) { + ChangeEvent(String pattern, Checker<EntityChangeBlockEvent> checker) { this.pattern = pattern; - checker = c; + this.checker = checker; } private static final String[] patterns; From 8726c08d68a88623109dd7d4bb935d475767af4f Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Fri, 19 May 2023 18:03:38 -0600 Subject: [PATCH 322/619] Fix for exact event values in the event value expression (#5579) --- .../ch/njol/skript/expressions/ExprEgg.java | 2 +- .../base/EventValueExpression.java | 32 ++++++++++-- .../skript/registrations/EventValues.java | 51 ++++++++++++++----- 3 files changed, 68 insertions(+), 17 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprEgg.java b/src/main/java/ch/njol/skript/expressions/ExprEgg.java index 180fb1cf5a9..3ca80a02e33 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprEgg.java +++ b/src/main/java/ch/njol/skript/expressions/ExprEgg.java @@ -42,7 +42,7 @@ public class ExprEgg extends EventValueExpression<Egg> { } public ExprEgg() { - super(Egg.class); + super(Egg.class, true); } @Override diff --git a/src/main/java/ch/njol/skript/expressions/base/EventValueExpression.java b/src/main/java/ch/njol/skript/expressions/base/EventValueExpression.java index a08757d33e2..9781eade3dd 100644 --- a/src/main/java/ch/njol/skript/expressions/base/EventValueExpression.java +++ b/src/main/java/ch/njol/skript/expressions/base/EventValueExpression.java @@ -57,7 +57,6 @@ * } * </pre> * - * @author Peter Güttinger * @see Classes#registerClass(ClassInfo) * @see ClassInfo#defaultExpression(DefaultExpression) * @see DefaultExpression @@ -70,14 +69,30 @@ public class EventValueExpression<T> extends SimpleExpression<T> implements Defa private Changer<? super T> changer; private final Map<Class<? extends Event>, Getter<? extends T, ?>> getters = new HashMap<>(); private final boolean single; + private final boolean exact; public EventValueExpression(Class<? extends T> c) { this(c, null); } + /** + * Construct an event value expression. + * + * @param c The class that this event value represents. + * @param exact If false, the event value can be a subclass or a converted event value. + */ + public EventValueExpression(Class<? extends T> c, boolean exact) { + this(c, null, exact); + } + public EventValueExpression(Class<? extends T> c, @Nullable Changer<? super T> changer) { + this(c, changer, false); + } + + public EventValueExpression(Class<? extends T> c, @Nullable Changer<? super T> changer, boolean exact) { assert c != null; this.c = c; + this.exact = exact; this.changer = changer; single = !c.isArray(); componentType = single ? c : c.getComponentType(); @@ -143,7 +158,12 @@ public boolean init() { hasValue = getters.get(event) != null; continue; } - Getter<? extends T, ?> getter = EventValues.getEventValueGetter(event, c, getTime()); + Getter<? extends T, ?> getter; + if (exact) { + getter = EventValues.getExactEventValueGetter(event, c, getTime()); + } else { + getter = EventValues.getEventValueGetter(event, c, getTime()); + } if (getter != null) { getters.put(event, getter); hasValue = true; @@ -203,7 +223,13 @@ public boolean setTime(int time) { } for (Class<? extends Event> event : events) { assert event != null; - if (EventValues.doesEventValueHaveTimeStates(event, c)) { + boolean has; + if (exact) { + has = EventValues.doesExactEventValueHaveTimeStates(event, c); + } else { + has = EventValues.doesEventValueHaveTimeStates(event, c); + } + if (has) { super.setTime(time); // Since the time was changed, we now need to re-initalize the getters we already got. START getters.clear(); diff --git a/src/main/java/ch/njol/skript/registrations/EventValues.java b/src/main/java/ch/njol/skript/registrations/EventValues.java index 741502a022c..255dbfcb6a8 100644 --- a/src/main/java/ch/njol/skript/registrations/EventValues.java +++ b/src/main/java/ch/njol/skript/registrations/EventValues.java @@ -198,11 +198,9 @@ public static <T, E extends Event> T getEventValue(E e, Class<T> c, int time) { return null; return getter.get(e); } - + /** - * Returns a getter to get a value from in an event. - * <p> - * Can print an error if the event value is blocked for the given event. + * Checks that a getter exists for the exact type. No converting or subclass checking. * * @param event the event class the getter will be getting from * @param c type of getter @@ -212,13 +210,8 @@ public static <T, E extends Event> T getEventValue(E e, Class<T> c, int time) { * @see EventValueExpression#EventValueExpression(Class) */ @Nullable - public static <T, E extends Event> Getter<? extends T, ? super E> getEventValueGetter(Class<E> event, Class<T> c, int time) { - return getEventValueGetter(event, c, time, true); - } - @SuppressWarnings("unchecked") - @Nullable - private static <T, E extends Event> Getter<? extends T, ? super E> getEventValueGetter(Class<E> event, Class<T> c, int time, boolean allowDefault) { + public static <T, E extends Event> Getter<? extends T, ? super E> getExactEventValueGetter(Class<E> event, Class<T> c, int time) { List<EventValueInfo<?, ?>> eventValues = getEventValuesList(time); // First check for exact classes matching the parameters. for (EventValueInfo<?, ?> eventValueInfo : eventValues) { @@ -229,6 +222,34 @@ public static <T, E extends Event> T getEventValue(E e, Class<T> c, int time) { if (eventValueInfo.event.isAssignableFrom(event)) return (Getter<? extends T, ? super E>) eventValueInfo.getter; } + return null; + } + + /** + * Returns a getter to get a value from in an event. + * <p> + * Can print an error if the event value is blocked for the given event. + * + * @param event the event class the getter will be getting from + * @param c type of getter + * @param time the event-value's time + * @return A getter to get values for a given type of events + * @see #registerEventValue(Class, Class, Getter, int) + * @see EventValueExpression#EventValueExpression(Class) + */ + @Nullable + public static <T, E extends Event> Getter<? extends T, ? super E> getEventValueGetter(Class<E> event, Class<T> c, int time) { + return getEventValueGetter(event, c, time, true); + } + + @Nullable + @SuppressWarnings("unchecked") + private static <T, E extends Event> Getter<? extends T, ? super E> getEventValueGetter(Class<E> event, Class<T> c, int time, boolean allowDefault) { + List<EventValueInfo<?, ?>> eventValues = getEventValuesList(time); + // First check for exact classes matching the parameters. + Getter<? extends T, ? super E> exact = (Getter<? extends T, ? super E>) getExactEventValueGetter(event, c, time); + if (exact != null) + return exact; // Second check for assignable subclasses. for (EventValueInfo<?, ?> eventValueInfo : eventValues) { if (!c.isAssignableFrom(eventValueInfo.c)) @@ -351,9 +372,13 @@ public T get(E e) { } }; } - - public static boolean doesEventValueHaveTimeStates(Class<? extends Event> e, Class<?> c) { - return getEventValueGetter(e, c, -1, false) != null || getEventValueGetter(e, c, 1, false) != null; + + public static boolean doesExactEventValueHaveTimeStates(Class<? extends Event> event, Class<?> c) { + return getExactEventValueGetter(event, c, TIME_PAST) != null || getExactEventValueGetter(event, c, TIME_FUTURE) != null; + } + + public static boolean doesEventValueHaveTimeStates(Class<? extends Event> event, Class<?> c) { + return getEventValueGetter(event, c, TIME_PAST, false) != null || getEventValueGetter(event, c, TIME_FUTURE, false) != null; } } From 1f4ffeec8d9d0e69bf12a2258808132af7991494 Mon Sep 17 00:00:00 2001 From: DelayedGaming <simmz0403@gmail.com> Date: Wed, 24 May 2023 00:56:18 +0800 Subject: [PATCH 323/619] Initial (pt 1) --- src/main/java/ch/njol/skript/events/EvtMove.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/events/EvtMove.java b/src/main/java/ch/njol/skript/events/EvtMove.java index 58dfb2f2351..6e22096c85b 100644 --- a/src/main/java/ch/njol/skript/events/EvtMove.java +++ b/src/main/java/ch/njol/skript/events/EvtMove.java @@ -43,7 +43,7 @@ public class EvtMove extends SkriptEvent { else events = CollectionUtils.array(PlayerMoveEvent.class); - Skript.registerEvent("Move", EvtMove.class, events, "%entitydata% (move|walk|step)") + Skript.registerEvent("Move", EvtMove.class, events, "%entitydata% (move|walk|step|:rotate)") .description("Called when a player or entity moves.", "NOTE: Move event will only be called when the entity/player moves position, not orientation (ie: looking around).", "NOTE: These events can be performance heavy as they are called quite often.", @@ -60,11 +60,13 @@ public class EvtMove extends SkriptEvent { private EntityData<?> type; private boolean isPlayer; + private boolean isRotate; @Override public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult) { type = ((Literal<EntityData<?>>) args[0]).getSingle(); isPlayer = Player.class.isAssignableFrom(type.getType()); + isRotate = parseResult.hasTag("rotate"); if (!HAS_ENTITY_MOVE && !isPlayer) { Skript.error("Entity move event requires Paper 1.16.5+", ErrorQuality.SEMANTIC_ERROR); @@ -77,10 +79,14 @@ public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResu public boolean check(Event event) { if (isPlayer && event instanceof PlayerMoveEvent) { PlayerMoveEvent playerEvent = (PlayerMoveEvent) event; + if (isRotate) + return rotateCheck(playerEvent.getFrom(), playerEvent.getTo()); return moveCheck(playerEvent.getFrom(), playerEvent.getTo()); } else if (HAS_ENTITY_MOVE && event instanceof EntityMoveEvent) { EntityMoveEvent entityEvent = (EntityMoveEvent) event; if (type.isInstance(entityEvent.getEntity())) { + if (isRotate) + return rotateCheck(entityEvent.getFrom(), entityEvent.getTo()); return moveCheck(entityEvent.getFrom(), entityEvent.getTo()); } } @@ -109,4 +115,8 @@ private static boolean moveCheck(Location from, Location to) { return from.getX() != to.getX() || from.getY() != to.getY() || from.getZ() != to.getZ() || from.getWorld() != to.getWorld(); } + private static boolean rotateCheck(Location from, Location to) { + return !moveCheck(from, to) && from.getYaw() != to.getYaw() || from.getPitch() != to.getPitch(); + } + } From fba2fb714984334ebd6d87929a3f8c79e232f786 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Wed, 24 May 2023 08:40:32 +0300 Subject: [PATCH 324/619] =?UTF-8?q?=F0=9F=9B=A0=20Fix=20docs=20examples=20?= =?UTF-8?q?non=20HTML=20tags=20not=20escaped=20=20(#5685)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/ch/njol/skript/doc/Documentation.java | 17 ++++++++++++----- .../java/ch/njol/skript/doc/HTMLGenerator.java | 12 ++++++------ 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/main/java/ch/njol/skript/doc/Documentation.java b/src/main/java/ch/njol/skript/doc/Documentation.java index 3916d5f1d5c..5da07aefb09 100644 --- a/src/main/java/ch/njol/skript/doc/Documentation.java +++ b/src/main/java/ch/njol/skript/doc/Documentation.java @@ -476,16 +476,23 @@ private static String validateHTML(@Nullable String html, final String baseURL) return html; } - private static String escapeSQL(final String s) { - return "" + s.replace("'", "\\'").replace("\"", "\\\""); + private static String escapeSQL(String value) { + return "" + value.replace("'", "\\'").replace("\"", "\\\""); } - public static String escapeHTML(final @Nullable String s) { - if (s == null) { + public static String escapeHTML(@Nullable String value) { + if (value == null) { assert false; return ""; } - return "" + s.replace("&", "&").replace("<", "<").replace(">", ">").replace("\"", """); + return "" + value.replace("&", "&").replace("<", "<").replace(">", ">"); + } + + public static String[] escapeHTML(@Nullable String[] values) { + for (int i = 0; i < values.length; i++) { + values[i] = escapeHTML(values[i]); + } + return values; } } diff --git a/src/main/java/ch/njol/skript/doc/HTMLGenerator.java b/src/main/java/ch/njol/skript/doc/HTMLGenerator.java index 0da3679904c..51cc5102f35 100644 --- a/src/main/java/ch/njol/skript/doc/HTMLGenerator.java +++ b/src/main/java/ch/njol/skript/doc/HTMLGenerator.java @@ -466,8 +466,8 @@ private String generateAnnotated(String descTemp, SyntaxElementInfo<?> info, @Nu // Examples Examples examples = c.getAnnotation(Examples.class); - desc = desc.replace("${element.examples}", Joiner.on("<br>").join(getDefaultIfNullOrEmpty((examples != null ? examples.value() : null), "Missing examples."))); - desc = desc.replace("${element.examples-safe}", Joiner.on("\\n").join(getDefaultIfNullOrEmpty((examples != null ? examples.value() : null), "Missing examples.")) + desc = desc.replace("${element.examples}", Joiner.on("<br>").join(getDefaultIfNullOrEmpty((examples != null ? Documentation.escapeHTML(examples.value()) : null), "Missing examples."))); + desc = desc.replace("${element.examples-safe}", Joiner.on("\\n").join(getDefaultIfNullOrEmpty((examples != null ? Documentation.escapeHTML(examples.value()) : null), "Missing examples.")) .replace("\\", "\\\\").replace("\"", "\\\"").replace("\t", " ")); // Documentation ID @@ -595,7 +595,7 @@ private String generateEvent(String descTemp, SkriptEventInfo<?> info, @Nullable // Examples String[] examples = getDefaultIfNullOrEmpty(info.getExamples(), "Missing examples."); - desc = desc.replace("${element.examples}", Joiner.on("\n<br>").join(examples)); + desc = desc.replace("${element.examples}", Joiner.on("\n<br>").join(Documentation.escapeHTML(examples))); desc = desc .replace("${element.examples-safe}", Joiner.on("\\n").join(examples) .replace("\\", "\\\\").replace("\"", "\\\"").replace("\t", " ")); @@ -703,8 +703,8 @@ private String generateClass(String descTemp, ClassInfo<?> info, @Nullable Strin // Examples String[] examples = getDefaultIfNullOrEmpty(info.getExamples(), "Missing examples."); - desc = desc.replace("${element.examples}", Joiner.on("\n<br>").join(examples)); - desc = desc.replace("${element.examples-safe}", Joiner.on("\\n").join(examples) + desc = desc.replace("${element.examples}", Joiner.on("\n<br>").join(Documentation.escapeHTML(examples))); + desc = desc.replace("${element.examples-safe}", Joiner.on("\\n").join(Documentation.escapeHTML(examples)) .replace("\\", "\\\\").replace("\"", "\\\"").replace("\t", " ")); // Documentation ID @@ -808,7 +808,7 @@ private String generateFunction(String descTemp, JavaFunction<?> info) { // Examples String[] examples = getDefaultIfNullOrEmpty(info.getExamples(), "Missing examples."); - desc = desc.replace("${element.examples}", Joiner.on("\n<br>").join(examples)); + desc = desc.replace("${element.examples}", Joiner.on("\n<br>").join(Documentation.escapeHTML(examples))); desc = desc .replace("${element.examples-safe}", Joiner.on("\\n").join(examples) .replace("\\", "\\\\").replace("\"", "\\\"").replace("\t", " ")); From e9b5d99f5dcf32df5f594409a34a579017389e69 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Fri, 26 May 2023 12:15:03 -0600 Subject: [PATCH 325/619] Apply entity data changes before calling consumer (#5712) --- .../ch/njol/skript/entity/EntityData.java | 33 ++++++++++--------- .../expressions/ExprTotalExperience.sk | 4 +-- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/main/java/ch/njol/skript/entity/EntityData.java b/src/main/java/ch/njol/skript/entity/EntityData.java index 8adb5f4043f..d49668a8af3 100644 --- a/src/main/java/ch/njol/skript/entity/EntityData.java +++ b/src/main/java/ch/njol/skript/entity/EntityData.java @@ -434,23 +434,26 @@ public final E spawn(Location loc) { return spawn(loc, null); } - @SuppressWarnings("unchecked") + private E apply(E entity) { + if (baby.isTrue()) { + EntityUtils.setBaby(entity); + } else if (baby.isFalse()) { + EntityUtils.setAdult(entity); + } + set(entity); + return entity; + } + @Nullable - public E spawn(Location loc, @Nullable Consumer<E> consumer) { - assert loc != null; + @SuppressWarnings("unchecked") + public E spawn(Location location, @Nullable Consumer<E> consumer) { + assert location != null; try { - E e; - if (consumer != null) - e = loc.getWorld().spawn(loc, (Class<E>) getType(), consumer); - else - e = loc.getWorld().spawn(loc, getType()); - - if (baby.isTrue()) - EntityUtils.setBaby(e); - else if (baby.isFalse()) - EntityUtils.setAdult(e); - set(e); - return e; + if (consumer != null) { + return location.getWorld().spawn(location, (Class<E>) getType(), e -> consumer.accept(apply(e))); + } else { + return apply(location.getWorld().spawn(location, getType())); + } } catch (IllegalArgumentException e) { if (Skript.testing()) Skript.error("Can't spawn " + getType().getName()); diff --git a/src/test/skript/tests/syntaxes/expressions/ExprTotalExperience.sk b/src/test/skript/tests/syntaxes/expressions/ExprTotalExperience.sk index 9291fb18eb9..e4e618e923a 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprTotalExperience.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprTotalExperience.sk @@ -1,6 +1,6 @@ test "total experience": spawn 10 xp at spawn of world "world": - assert experience of entity is 0 with "spawned orb should have 0 experience in section" + assert experience of entity is 10 with "spawned orb should have 10 experience in section" set experience of entity to 100 assert experience of entity is 100 with "set experience of entity did not work" reset experience of entity @@ -11,5 +11,5 @@ test "total experience": assert experience of entity is 25 with "remove experience from entity did not work" remove 50 from experience of entity assert experience of entity is 0 with "remove too much experience from entity did not work" - assert experience of last spawned entity is 10 with "entity should have 10 experience after section" + assert experience of last spawned entity is 0 with "entity should have 0 experience after section" delete last spawned entity From ac11f943cbcf5d50ff5e214bafd84b2fb51ee959 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Fri, 26 May 2023 14:44:52 -0400 Subject: [PATCH 326/619] Fix Item Comparisons For "Base" ItemTypes (#5694) Apply proper ItemFlags for ItemData#aliasCopy Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> --- .../java/ch/njol/skript/aliases/ItemData.java | 30 ++----------------- .../java/ch/njol/skript/aliases/ItemType.java | 6 ++-- .../regressions/3419-item comparisons.sk | 10 +++++++ 3 files changed, 15 insertions(+), 31 deletions(-) diff --git a/src/main/java/ch/njol/skript/aliases/ItemData.java b/src/main/java/ch/njol/skript/aliases/ItemData.java index 89c71cac05a..47fa59dadb0 100644 --- a/src/main/java/ch/njol/skript/aliases/ItemData.java +++ b/src/main/java/ch/njol/skript/aliases/ItemData.java @@ -593,6 +593,8 @@ public ItemData aliasCopy() { if (stack.hasItemMeta()) { ItemMeta meta = stack.getItemMeta(); // Creates a copy meta.setDisplayName(null); // Clear display name + if (!itemFactory.getItemMeta(type).equals(meta)) // there may be different tags (e.g. potions) + data.itemFlags |= ItemFlags.CHANGED_TAGS; data.stack.setItemMeta(meta); } ItemUtils.setDamage(data.stack, 0); // Set to undamaged @@ -602,34 +604,6 @@ public ItemData aliasCopy() { data.itemForm = itemForm; return data; } - - /** - * Applies an item meta to this item. Currently, it copies the following, - * provided that they exist in given meta: - * <ul> - * <li>Lore - * <li>Display name - * <li>Enchantments - * <li>Item flags - * </ul> - * @param meta Item meta. - */ - public void applyMeta(ItemMeta meta) { - ItemMeta our = getItemMeta(); - if (meta.hasLore()) - our.setLore(meta.getLore()); - if (meta.hasDisplayName()) - our.setDisplayName(meta.getDisplayName()); - if (meta.hasEnchants()) { - for (Map.Entry<Enchantment, Integer> entry : meta.getEnchants().entrySet()) { - our.addEnchant(entry.getKey(), entry.getValue(), true); - } - } - for (ItemFlag flag : meta.getItemFlags()) { - our.addItemFlags(flag); - } - setItemMeta(meta); - } /** * Applies tags to this item. diff --git a/src/main/java/ch/njol/skript/aliases/ItemType.java b/src/main/java/ch/njol/skript/aliases/ItemType.java index bc83592a596..7c189f72bf6 100644 --- a/src/main/java/ch/njol/skript/aliases/ItemType.java +++ b/src/main/java/ch/njol/skript/aliases/ItemType.java @@ -165,7 +165,7 @@ void setBlock(final @Nullable ItemType block) { this.block = block; } } - + public ItemType() {} public ItemType(Material id) { @@ -1298,7 +1298,7 @@ public void setItemMeta(ItemMeta meta) { // Apply new meta to all datas for (ItemData data : types) { - data.applyMeta(meta); + data.setItemMeta(meta); } } @@ -1326,7 +1326,7 @@ public Material getMaterial() { public ItemType getBaseType() { ItemType copy = new ItemType(); for (ItemData data : types) { - copy.add(data.aliasCopy()); + copy.add_(data.aliasCopy()); } return copy; } diff --git a/src/test/skript/tests/regressions/3419-item comparisons.sk b/src/test/skript/tests/regressions/3419-item comparisons.sk index 8f6e59e2870..fdbba9ef835 100644 --- a/src/test/skript/tests/regressions/3419-item comparisons.sk +++ b/src/test/skript/tests/regressions/3419-item comparisons.sk @@ -151,3 +151,13 @@ test "item comparisons": remove all items from {_inventory} set block at spawn of world "world" to air + + # type comparisons (see https://github.com/SkriptLang/Skript/issues/5693) + + set {_fire-resist} to type of potion of fire resistance + set {_water-breathing} to type of water breathing potion + set {_water-bottle} to type of water bottle + assert {_fire-resist} is {_fire-resist} with "fire resist is not fire resist" + assert {_fire-resist} is not {_water-breathing} with "fire resist is water breathing" + assert {_fire-resist} is not {_water-bottle} with "fire resist is water bottle" + assert {_water-bottle} is not {_water-breathing} with "water bottle is water breathing" From aa25621c68e6ace67a5fde4ed1a3481394b2efc4 Mon Sep 17 00:00:00 2001 From: sovdee <ethansovde@gmail.com> Date: Fri, 26 May 2023 11:58:46 -0700 Subject: [PATCH 327/619] Rework Event-Trigger Matching to use MultiMap instead of List of Pairs (#5577) --- .../ch/njol/skript/SkriptEventHandler.java | 116 +++++++++--------- 1 file changed, 60 insertions(+), 56 deletions(-) diff --git a/src/main/java/ch/njol/skript/SkriptEventHandler.java b/src/main/java/ch/njol/skript/SkriptEventHandler.java index 33ff353579b..144eca18721 100644 --- a/src/main/java/ch/njol/skript/SkriptEventHandler.java +++ b/src/main/java/ch/njol/skript/SkriptEventHandler.java @@ -18,9 +18,11 @@ */ package ch.njol.skript; +import ch.njol.skript.lang.SkriptEvent; import ch.njol.skript.lang.Trigger; import ch.njol.skript.timings.SkriptTimings; -import ch.njol.util.NonNullPair; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; import org.bukkit.Bukkit; import org.bukkit.event.Cancellable; import org.bukkit.event.Event; @@ -36,13 +38,14 @@ import java.lang.ref.WeakReference; import java.lang.reflect.Method; -import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; +import java.util.stream.Collectors; public final class SkriptEventHandler { @@ -79,55 +82,52 @@ public PriorityListener(EventPriority priority) { } /** - * A list tracking what Triggers are paired with what Events. + * A Multimap tracking what Triggers are paired with what Events. + * Each Event effectively maps to an ArrayList of Triggers. */ - private static final List<NonNullPair<Class<? extends Event>, Trigger>> triggers = new ArrayList<>(); + private static final Multimap<Class<? extends Event>, Trigger> triggers = ArrayListMultimap.create(); /** - * A utility method to get all Triggers paired with the provided Event class. + * A utility method to get all Triggers registered under the provided Event class. * @param event The event to find pairs from. - * @return An iterator containing all Triggers paired with the provided Event class. + * @return A List containing all Triggers registered under the provided Event class. */ - private static Iterator<Trigger> getTriggers(Class<? extends Event> event) { + private static List<Trigger> getTriggers(Class<? extends Event> event) { HandlerList eventHandlerList = getHandlerList(event); assert eventHandlerList != null; // It had one at some point so this should remain true - return new ArrayList<>(triggers).stream() - .filter(pair -> pair.getFirst().isAssignableFrom(event) && eventHandlerList == getHandlerList(pair.getFirst())) - .map(NonNullPair::getSecond) - .iterator(); + return triggers.asMap().entrySet().stream() + .filter(entry -> entry.getKey().isAssignableFrom(event) && getHandlerList(entry.getKey()) == eventHandlerList) + .flatMap(entry -> entry.getValue().stream()) + .collect(Collectors.toList()); // forces evaluation now and prevents us from having to call getTriggers again if very high logging is enabled } /** * This method is used for validating that the provided Event may be handled by Skript. * If validation is successful, all Triggers associated with the provided Event are executed. * A Trigger will only be executed if its priority matches the provided EventPriority. - * @param e The Event to check. + * @param event The Event to check. * @param priority The priority of the Event. */ - private static void check(Event e, EventPriority priority) { - Iterator<Trigger> ts = getTriggers(e.getClass()); - if (!ts.hasNext()) - return; + private static void check(Event event, EventPriority priority) { + List<Trigger> triggers = getTriggers(event.getClass()); if (Skript.logVeryHigh()) { boolean hasTrigger = false; - while (ts.hasNext()) { - Trigger trigger = ts.next(); - if (trigger.getEvent().getEventPriority() == priority && trigger.getEvent().check(e)) { + for (Trigger trigger : triggers) { + SkriptEvent triggerEvent = trigger.getEvent(); + if (triggerEvent.getEventPriority() == priority && triggerEvent.check(event)) { hasTrigger = true; break; } } if (!hasTrigger) return; - Class<? extends Event> c = e.getClass(); - ts = getTriggers(c); - logEventStart(e); + logEventStart(event); } - boolean isCancelled = e instanceof Cancellable && ((Cancellable) e).isCancelled() && !listenCancelled.contains(e.getClass()); - boolean isResultDeny = !(e instanceof PlayerInteractEvent && (((PlayerInteractEvent) e).getAction() == Action.LEFT_CLICK_AIR || ((PlayerInteractEvent) e).getAction() == Action.RIGHT_CLICK_AIR) && ((PlayerInteractEvent) e).useItemInHand() != Result.DENY); + boolean isCancelled = event instanceof Cancellable && ((Cancellable) event).isCancelled() && !listenCancelled.contains(event.getClass()); + boolean isResultDeny = !(event instanceof PlayerInteractEvent && (((PlayerInteractEvent) event).getAction() == Action.LEFT_CLICK_AIR || ((PlayerInteractEvent) event).getAction() == Action.RIGHT_CLICK_AIR) && ((PlayerInteractEvent) event).useItemInHand() != Result.DENY); if (isCancelled && isResultDeny) { if (Skript.logVeryHigh()) @@ -135,18 +135,18 @@ private static void check(Event e, EventPriority priority) { return; } - while (ts.hasNext()) { - Trigger t = ts.next(); - if (t.getEvent().getEventPriority() != priority || !t.getEvent().check(e)) + for (Trigger trigger : triggers) { + SkriptEvent triggerEvent = trigger.getEvent(); + if (triggerEvent.getEventPriority() != priority || !triggerEvent.check(event)) continue; - logTriggerStart(t); - Object timing = SkriptTimings.start(t.getDebugLabel()); + logTriggerStart(trigger); + Object timing = SkriptTimings.start(trigger.getDebugLabel()); - t.execute(e); + trigger.execute(event); SkriptTimings.stop(timing); - logTriggerEnd(t); + logTriggerEnd(trigger); } logEventEnd(); @@ -236,7 +236,7 @@ public static void registerBukkitEvent(Trigger trigger, Class<? extends Event> e if (handlerList == null) return; - triggers.add(new NonNullPair<>(event, trigger)); + triggers.put(event, trigger); EventPriority priority = trigger.getEvent().getEventPriority(); @@ -251,35 +251,39 @@ public static void registerBukkitEvent(Trigger trigger, Class<? extends Event> e * @param trigger The Trigger to unregister events for. */ public static void unregisterBukkitEvents(Trigger trigger) { - triggers.removeIf(pair -> { - if (pair.getSecond() != trigger) - return false; + Iterator<Entry<Class<? extends Event>, Trigger>> entryIterator = triggers.entries().iterator(); + entryLoop: while (entryIterator.hasNext()) { + Entry<Class<? extends Event>, Trigger> entry = entryIterator.next(); + if (entry.getValue() != trigger) + continue; + Class<? extends Event> event = entry.getKey(); - HandlerList handlerList = getHandlerList(pair.getFirst()); - assert handlerList != null; + // Remove the trigger from the map + entryIterator.remove(); + // check if we can unregister the listener EventPriority priority = trigger.getEvent().getEventPriority(); - if (triggers.stream().noneMatch(pair2 -> - trigger != pair2.getSecond() // Don't match the trigger we are unregistering - && pair2.getFirst().isAssignableFrom(pair.getFirst()) // Basic similarity check - && priority == pair2.getSecond().getEvent().getEventPriority() // Ensure same priority - && handlerList == getHandlerList(pair2.getFirst()) // Ensure same handler list - )) { // We can attempt to unregister this listener - Skript skript = Skript.getInstance(); - for (RegisteredListener registeredListener : handlerList.getRegisteredListeners()) { - Listener listener = registeredListener.getListener(); - if ( - registeredListener.getPlugin() == skript - && listener instanceof PriorityListener - && ((PriorityListener) listener).priority == priority - ) { - handlerList.unregister(listener); - } - } + for (Trigger eventTrigger : triggers.get(event)) { + if (eventTrigger.getEvent().getEventPriority() == priority) + continue entryLoop; } - return true; - }); + // We can attempt to unregister this listener + HandlerList handlerList = getHandlerList(event); + if (handlerList == null) + continue; + Skript skript = Skript.getInstance(); + for (RegisteredListener registeredListener : handlerList.getRegisteredListeners()) { + Listener listener = registeredListener.getListener(); + if ( + registeredListener.getPlugin() == skript + && listener instanceof PriorityListener + && ((PriorityListener) listener).priority == priority + ) { + handlerList.unregister(listener); + } + } + } } /** From 951520686ad6674bfeb48959e84eeeca41bc4a5b Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Fri, 26 May 2023 22:11:24 +0300 Subject: [PATCH 328/619] =?UTF-8?q?=F0=9F=9B=A0=20Fix=20fireworkeffect=20c?= =?UTF-8?q?lassinfo=20doesn't=20have=20usage=20(#5684)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../njol/skript/classes/data/BukkitClasses.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) 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 35237bcb7ab..ccf12240d29 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -1352,14 +1352,17 @@ public String toVariableNameString(final CachedServerIcon o) { Classes.registerClass(new ClassInfo<>(FireworkEffect.class, "fireworkeffect") .user("firework ?effects?") .name("Firework Effect") - .description("A configuration of effects that defines the firework when exploded", + .usage("See <a href='/classes.html#FireworkType'>Firework Types</a>") + .description( + "A configuration of effects that defines the firework when exploded", "which can be used in the <a href='effects.html#EffFireworkLaunch'>launch firework</a> effect.", - "See the <a href='expressions.html#ExprFireworkEffect'>firework effect</a> expression for detailed patterns.") - .defaultExpression(new EventValueExpression<>(FireworkEffect.class)) - .examples("launch flickering trailing burst firework colored blue and green at player", + "See the <a href='expressions.html#ExprFireworkEffect'>firework effect</a> expression for detailed patterns." + ).defaultExpression(new EventValueExpression<>(FireworkEffect.class)) + .examples( + "launch flickering trailing burst firework colored blue and green at player", "launch trailing flickering star colored purple, yellow, blue, green and red fading to pink at target entity", - "launch ball large colored red, purple and white fading to light green and black at player's location with duration 1") - .since("2.4") + "launch ball large colored red, purple and white fading to light green and black at player's location with duration 1" + ).since("2.4") .parser(new Parser<FireworkEffect>() { @Override @Nullable From 728a306f686848d4b14c90f3efac9f21dfefc30e Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Fri, 26 May 2023 22:31:40 +0300 Subject: [PATCH 329/619] =?UTF-8?q?=F0=9F=9B=A0=20Fix=20docs=20searching?= =?UTF-8?q?=20keywords=20not=20included=20in=20classinfos=20and=20function?= =?UTF-8?q?s=20(#5688)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ch/njol/skript/doc/HTMLGenerator.java | 6 ++++++ .../skript/lang/function/JavaFunction.java | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/main/java/ch/njol/skript/doc/HTMLGenerator.java b/src/main/java/ch/njol/skript/doc/HTMLGenerator.java index 51cc5102f35..363b9f0aa3a 100644 --- a/src/main/java/ch/njol/skript/doc/HTMLGenerator.java +++ b/src/main/java/ch/njol/skript/doc/HTMLGenerator.java @@ -707,6 +707,9 @@ private String generateClass(String descTemp, ClassInfo<?> info, @Nullable Strin desc = desc.replace("${element.examples-safe}", Joiner.on("\\n").join(Documentation.escapeHTML(examples)) .replace("\\", "\\\\").replace("\"", "\\\"").replace("\t", " ")); + Keywords keywords = c.getAnnotation(Keywords.class); + desc = desc.replace("${element.keywords}", keywords == null ? "" : Joiner.on(", ").join(keywords.value())); + // Documentation ID String ID = info.getDocumentationID() != null ? info.getDocumentationID() : info.getCodeName(); // Fix duplicated IDs @@ -813,6 +816,9 @@ private String generateFunction(String descTemp, JavaFunction<?> info) { .replace("${element.examples-safe}", Joiner.on("\\n").join(examples) .replace("\\", "\\\\").replace("\"", "\\\"").replace("\t", " ")); + String[] keywords = info.getKeywords(); + desc = desc.replace("${element.keywords}", keywords == null ? "" : Joiner.on(", ").join(keywords)); + // Documentation ID desc = desc.replace("${element.id}", info.getName()); diff --git a/src/main/java/ch/njol/skript/lang/function/JavaFunction.java b/src/main/java/ch/njol/skript/lang/function/JavaFunction.java index 59c0d9e4279..a46bdc17b02 100644 --- a/src/main/java/ch/njol/skript/lang/function/JavaFunction.java +++ b/src/main/java/ch/njol/skript/lang/function/JavaFunction.java @@ -44,6 +44,8 @@ public JavaFunction(String name, Parameter<?>[] parameters, ClassInfo<T> returnT @Nullable private String[] examples = null; @Nullable + private String[] keywords; + @Nullable private String since = null; /** @@ -67,6 +69,18 @@ public JavaFunction<T> examples(final String... examples) { this.examples = examples; return this; } + + /** + * Only used for Skript's documentation. + * + * @param keywords + * @return This JavaFunction object + */ + public JavaFunction<T> keywords(final String... keywords) { + assert this.keywords == null; + this.keywords = keywords; + return this; + } /** * Only used for Skript's documentation. @@ -88,6 +102,11 @@ public String[] getDescription() { public String[] getExamples() { return examples; } + + @Nullable + public String[] getKeywords() { + return keywords; + } @Nullable public String getSince() { From f015a19c4823579e0edde66a1e65b49b9476c537 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Fri, 26 May 2023 22:39:36 +0300 Subject: [PATCH 330/619] =?UTF-8?q?=F0=9F=9B=A0=20Fix=20docs=20returntype?= =?UTF-8?q?=20check=20inverted=20(#5686)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/ch/njol/skript/doc/HTMLGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/doc/HTMLGenerator.java b/src/main/java/ch/njol/skript/doc/HTMLGenerator.java index 363b9f0aa3a..9a4adca6d7b 100644 --- a/src/main/java/ch/njol/skript/doc/HTMLGenerator.java +++ b/src/main/java/ch/njol/skript/doc/HTMLGenerator.java @@ -920,7 +920,7 @@ private String replaceReturnType(String desc, @Nullable ClassInfo<?> returnType) if (returnType == null) return handleIf(desc, "${if return-type}", false); - boolean noDoc = returnType.hasDocs(); + boolean noDoc = !returnType.hasDocs(); String returnTypeName = noDoc ? returnType.getCodeName() : returnType.getDocName(); String returnTypeLink = noDoc ? "" : "$1" + getDefaultIfNullOrEmpty(returnType.getDocumentationID(), returnType.getCodeName()); From 54937c0eb89a37b99229040214edc80ccf885070 Mon Sep 17 00:00:00 2001 From: "Mr. Darth" <66969562+Mr-Darth@users.noreply.github.com> Date: Sat, 27 May 2023 09:07:15 +0300 Subject: [PATCH 331/619] Implement serial comma support in lists (#5062) --- .../java/ch/njol/skript/lang/SkriptParser.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/java/ch/njol/skript/lang/SkriptParser.java b/src/main/java/ch/njol/skript/lang/SkriptParser.java index d227e337811..d5f8ab99732 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptParser.java +++ b/src/main/java/ch/njol/skript/lang/SkriptParser.java @@ -698,12 +698,13 @@ public final <T> Expression<? extends T> parseExpression(final Class<? extends T isLiteralList &= t instanceof Literal; ts.add(t); if (b != 0) { - final String d = expr.substring(pieces.get(b - 1)[1], x).trim(); - if (!d.equals(",")) { + String delimiter = expr.substring(pieces.get(b - 1)[1], x).trim().toLowerCase(Locale.ENGLISH); + if (!delimiter.equals(",")) { + boolean or = !delimiter.contains("nor") && delimiter.endsWith("or"); if (and.isUnknown()) { - and = Kleenean.get(!d.equalsIgnoreCase("or")); // nor is and + and = Kleenean.get(!or); // nor is and } else { - if (and != Kleenean.get(!d.equalsIgnoreCase("or"))) { + if (and != Kleenean.get(!or)) { Skript.warning(MULTIPLE_AND_OR + " List: " + expr); and = Kleenean.TRUE; } @@ -828,12 +829,13 @@ public final Expression<?> parseExpression(final ExprInfo vi) { isLiteralList &= t instanceof Literal; ts.add(t); if (b != 0) { - final String d = expr.substring(pieces.get(b - 1)[1], x).trim(); - if (!d.equals(",")) { + String delimiter = expr.substring(pieces.get(b - 1)[1], x).trim().toLowerCase(Locale.ENGLISH); + if (!delimiter.equals(",")) { + boolean or = !delimiter.contains("nor") && delimiter.endsWith("or"); if (and.isUnknown()) { - and = Kleenean.get(!d.equalsIgnoreCase("or")); // nor is and + and = Kleenean.get(!or); // nor is and } else { - if (and != Kleenean.get(!d.equalsIgnoreCase("or"))) { + if (and != Kleenean.get(!or)) { Skript.warning(MULTIPLE_AND_OR + " List: " + expr); and = Kleenean.TRUE; } From e738f5f96c089897c1461c53ba9823b5be8d3bcc Mon Sep 17 00:00:00 2001 From: Ilari Suhonen <ilari.suhonen@gmail.com> Date: Thu, 1 Jun 2023 21:01:34 +0300 Subject: [PATCH 332/619] Make aliases faster (#5667) --- .../ch/njol/skript/aliases/AliasesParser.java | 57 ++++++++++++++++--- .../njol/skript/aliases/AliasesProvider.java | 10 ++-- .../java/ch/njol/skript/aliases/ItemData.java | 3 +- 3 files changed, 57 insertions(+), 13 deletions(-) diff --git a/src/main/java/ch/njol/skript/aliases/AliasesParser.java b/src/main/java/ch/njol/skript/aliases/AliasesParser.java index 2faf25dd891..b319a230b5a 100644 --- a/src/main/java/ch/njol/skript/aliases/AliasesParser.java +++ b/src/main/java/ch/njol/skript/aliases/AliasesParser.java @@ -702,18 +702,59 @@ protected void loadSingleAlias(Map<String, Variation> variations, String item) { /** * Fixes an alias name by trimming it and removing all extraneous spaces - * between the words. + * between the words or before broken pipe characters (¦). * @param name Name to be fixed. * @return Name fixed. */ protected String fixName(String name) { - String result = org.apache.commons.lang.StringUtils.normalizeSpace(name); - - int i = result.indexOf('¦'); - - if (i != -1 && Character.isWhitespace(result.codePointBefore(i))) - result = result.substring(0, i - 1) + result.substring(i); - return result; + /* + * General logic: + * + * We rebuild the string from scratch, but skip any whitespace if + * 1. The previous character was also whitespace + * 2. The next character is a broken pipe (¦) + * - The broken pipe is used to separate the singular and plural forms of the alias + * - We want to allow a space before it for readability + * - We also don't want to literally include the space in the alias + * + * We also keep track of the first and last non-whitespace characters to trim the string manually. + * Since we're also skipping whitespace, we need to keep track of how many characters we've skipped + * so that our indices for those characters aren't misaligned. + * */ + + StringBuilder sb = new StringBuilder(name.length()); + + int firstNonWhitespace = -1; + int lastNonWhitespace = -1; + int lastWhitespace = -1; + int stripped = 0; + + for (int i = 0; i < name.length(); ++i) { + char c = name.charAt(i); + if (c > ' ') { + // The index will be off by the number of characters we've stripped so far + int adjustedIndex = i - stripped; + + if (firstNonWhitespace == -1) + firstNonWhitespace = adjustedIndex; + lastNonWhitespace = adjustedIndex; + } else { + int oldLastWhitespace = lastWhitespace; + lastWhitespace = i; + + if ( + (oldLastWhitespace != -1 && oldLastWhitespace == i - 1) + || (i < name.length() - 1 && name.charAt(i + 1) == '¦') + ) { + stripped++; + continue; + } + } + + sb.append(c); + } + + return sb.substring(firstNonWhitespace, lastNonWhitespace + 1); } public void registerCondition(String name, Function<String, Boolean> condition) { diff --git a/src/main/java/ch/njol/skript/aliases/AliasesProvider.java b/src/main/java/ch/njol/skript/aliases/AliasesProvider.java index d1bac43db39..1c6e73184c4 100644 --- a/src/main/java/ch/njol/skript/aliases/AliasesProvider.java +++ b/src/main/java/ch/njol/skript/aliases/AliasesProvider.java @@ -21,9 +21,11 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.Set; import java.util.List; import java.util.Map; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import org.eclipse.jdt.annotation.Nullable; @@ -56,7 +58,7 @@ public class AliasesProvider { /** * All materials that are currently loaded by this provider. */ - private final List<Material> materials; + private final Set<Material> materials; /** * Tags are in JSON format. We may need GSON when merging tags @@ -171,7 +173,7 @@ public AliasesProvider(int expectedCount, @Nullable AliasesProvider parent) { this.aliases = new HashMap<>(expectedCount); this.variations = new HashMap<>(expectedCount / 20); this.aliasesMap = new AliasesMap(); - this.materials = new ArrayList<>(); + this.materials = new ObjectOpenHashSet<>(); this.gson = new Gson(); } @@ -265,8 +267,8 @@ public void addAlias(AliasName name, String id, @Nullable Map<String, Object> ta if (material == null) { // If server doesn't recognize id, do not proceed throw new InvalidMinecraftIdException(id); } - if (!materials.contains(material)) - materials.add(material); + + materials.add(material); // Hacky: get related entity from block states String entityName = blockStates.remove("relatedEntity"); diff --git a/src/main/java/ch/njol/skript/aliases/ItemData.java b/src/main/java/ch/njol/skript/aliases/ItemData.java index 47fa59dadb0..b6f90b30f42 100644 --- a/src/main/java/ch/njol/skript/aliases/ItemData.java +++ b/src/main/java/ch/njol/skript/aliases/ItemData.java @@ -308,6 +308,7 @@ public MatchQuality matchAlias(ItemData item) { if (item.getType() != getType()) { return MatchQuality.DIFFERENT; } + BlockValues values = blockValues; // Items (held in inventories) don't have block values // If this is an item, given item must not have them either @@ -341,7 +342,7 @@ public MatchQuality matchAlias(ItemData item) { } // See if we need to compare item metas (excluding durability) - if (quality.isAtLeast(MatchQuality.SAME_ITEM)) { // Item meta checks could lower this + if (quality.isAtLeast(MatchQuality.SAME_ITEM) && stack.hasItemMeta() || item.stack.hasItemMeta()) { // Item meta checks could lower this MatchQuality metaQuality = compareItemMetas(getItemMeta(), item.getItemMeta()); // If given item doesn't care about meta, promote to SAME_ITEM From 407a68033c640597e97051897a70e27b1f3d03a2 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Thu, 1 Jun 2023 23:28:24 -0600 Subject: [PATCH 333/619] Fix the legendary compare issue with two same literals (#5503) --- .../njol/skript/conditions/CondCompare.java | 169 +++++++++++------- .../ch/njol/skript/registrations/Classes.java | 6 +- .../ch/njol/skript/test/runner/EffAssert.java | 5 +- .../njol/skript/test/runner/EvtTestCase.java | 58 +++--- .../skript/test/runner/SkriptJUnitTest.java | 10 +- .../njol/skript/test/runner/TestTracker.java | 8 + src/test/skript/tests/misc/dummy.sk | 2 +- .../tests/regressions/2711-item-compares.sk | 5 + .../regressions/4769-fire-visualeffect.sk | 15 ++ .../4773-composter-the-imposter.sk | 5 + ...ll-5566-block iterator being 100 always.sk | 2 +- .../syntaxes/expressions/ExprBlockData.sk | 2 +- 12 files changed, 190 insertions(+), 97 deletions(-) create mode 100644 src/test/skript/tests/regressions/2711-item-compares.sk create mode 100644 src/test/skript/tests/regressions/4769-fire-visualeffect.sk create mode 100644 src/test/skript/tests/regressions/4773-composter-the-imposter.sk diff --git a/src/main/java/ch/njol/skript/conditions/CondCompare.java b/src/main/java/ch/njol/skript/conditions/CondCompare.java index dbcb583436b..707f2626e83 100644 --- a/src/main/java/ch/njol/skript/conditions/CondCompare.java +++ b/src/main/java/ch/njol/skript/conditions/CondCompare.java @@ -22,8 +22,14 @@ import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; +import ch.njol.skript.classes.ClassInfo; + import org.skriptlang.skript.lang.comparator.Comparator; +import org.skriptlang.skript.lang.comparator.ComparatorInfo; import org.skriptlang.skript.lang.comparator.Relation; +import org.skriptlang.skript.lang.converter.ConverterInfo; +import org.skriptlang.skript.lang.converter.Converters; + import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; @@ -31,6 +37,7 @@ import ch.njol.skript.lang.Condition; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionList; +import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.ParseContext; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.UnparsedLiteral; @@ -39,15 +46,13 @@ import ch.njol.skript.log.RetainingLogHandler; import ch.njol.skript.log.SkriptLogger; import ch.njol.skript.registrations.Classes; + import org.skriptlang.skript.lang.comparator.Comparators; import ch.njol.skript.util.Patterns; import ch.njol.skript.util.Utils; import ch.njol.util.Checker; import ch.njol.util.Kleenean; -/** - * @author Peter Güttinger - */ @Name("Comparison") @Description({"A very general condition, it simply compares two values. Usually you can only compare for equality (e.g. block is/isn't of <type>), " + "but some values can also be compared using greater than/less than. In that case you can also test for whether an object is between two others.", @@ -93,19 +98,18 @@ public class CondCompare extends Condition { Skript.registerCondition(CondCompare.class, patterns.getPatterns()); } - @SuppressWarnings("null") private Expression<?> first; - @SuppressWarnings("null") private Expression<?> second; + @Nullable private Expression<?> third; - @SuppressWarnings("null") + private Relation relation; - @SuppressWarnings("rawtypes") + @Nullable - private Comparator comp; + @SuppressWarnings("rawtypes") + private Comparator comparator; - @SuppressWarnings("null") @Override public boolean init(final Expression<?>[] vars, final int matchedPattern, final Kleenean isDelayed, final ParseResult parser) { first = vars[0]; @@ -134,7 +138,7 @@ public boolean init(final Expression<?>[] vars, final int matchedPattern, final } } @SuppressWarnings("rawtypes") - final Comparator comp = this.comp; + final Comparator comp = this.comparator; if (comp != null) { if (third == null) { if (!relation.isImpliedBy(Relation.EQUAL, Relation.NOT_EQUAL) && !comp.supportsOrdering()) { @@ -158,60 +162,59 @@ public static String f(final Expression<?> e) { return Classes.getSuperClassInfo(e.getReturnType()).getName().withIndefiniteArticle(); } - @SuppressWarnings({"unchecked"}) + @SuppressWarnings("unchecked") private boolean init(String expr) { - final RetainingLogHandler log = SkriptLogger.startRetainingLog(); + RetainingLogHandler log = SkriptLogger.startRetainingLog(); Expression<?> third = this.third; try { if (first.getReturnType() == Object.class) { - final Expression<?> e = first.getConvertedExpression(Object.class); - if (e == null) { + Expression<?> expression = null; + if (first instanceof UnparsedLiteral) + expression = attemptReconstruction((UnparsedLiteral) first, second); + if (expression == null) + expression = first.getConvertedExpression(Object.class); + if (expression == null) { log.printErrors(); return false; } - first = e; + first = expression; } if (second.getReturnType() == Object.class) { - final Expression<?> e = second.getConvertedExpression(Object.class); - if (e == null) { + Expression<?> expression = null; + if (second instanceof UnparsedLiteral) + expression = attemptReconstruction((UnparsedLiteral) second, first); + if (expression == null) + expression = second.getConvertedExpression(Object.class); + if (expression == null) { log.printErrors(); return false; } - second = e; + second = expression; } if (third != null && third.getReturnType() == Object.class) { - final Expression<?> e = third.getConvertedExpression(Object.class); - if (e == null) { + Expression<?> expression = null; + if (third instanceof UnparsedLiteral) + expression = attemptReconstruction((UnparsedLiteral) third, first); + if (expression == null) + expression = third.getConvertedExpression(Object.class); + if (expression == null) { log.printErrors(); return false; } - this.third = third = e; + this.third = third = expression; } log.printLog(); } finally { log.stop(); } - final Class<?> f = first.getReturnType(), s = third == null ? second.getReturnType() : Utils.getSuperType(second.getReturnType(), third.getReturnType()); - if (f == Object.class || s == Object.class) + Class<?> firstReturnType = first.getReturnType(); + Class<?> secondReturnType = third == null ? second.getReturnType() : Utils.getSuperType(second.getReturnType(), third.getReturnType()); + if (firstReturnType == Object.class || secondReturnType == Object.class) return true; - /* - * https://github.com/Mirreski/Skript/issues/10 - * - if(Entity.class.isAssignableFrom(s)){ - String[] split = expr.split(" "); - System.out.println(expr); - if(!split[split.length - 1].equalsIgnoreCase("player") && EntityData.parseWithoutIndefiniteArticle(split[split.length - 1]) != null){ - comp = Comparators.getComparator(f, EntityData.class); - second = SkriptParser.parseLiteral(split[split.length - 1], EntityData.class, ParseContext.DEFAULT); - }else - comp = Comparators.getComparator(f, s); - - } - *///else - - comp = Comparators.getComparator(f, s); - - if (comp == null) { // Try to re-parse with more context + + comparator = Comparators.getComparator(firstReturnType, secondReturnType); + + if (comparator == null) { // Try to re-parse with more context /* * SkriptParser sees that CondCompare takes two objects. Most of the time, * this works fine. However, when there are multiple conflicting literals, @@ -224,23 +227,23 @@ private boolean init(String expr) { * Some damage types not working (issue #2184) would be a good example * of issues that SkriptParser's lack of context can cause. */ - SimpleLiteral<?> reparsedSecond = reparseLiteral(first.getReturnType(), second); + SimpleLiteral<?> reparsedSecond = reparseLiteral(firstReturnType, second); if (reparsedSecond != null) { second = reparsedSecond; - comp = Comparators.getComparator(f, second.getReturnType()); + comparator = Comparators.getComparator(firstReturnType, second.getReturnType()); } else { SimpleLiteral<?> reparsedFirst = reparseLiteral(second.getReturnType(), first); if (reparsedFirst != null) { first = reparsedFirst; - comp = Comparators.getComparator(first.getReturnType(), s); + comparator = Comparators.getComparator(first.getReturnType(), secondReturnType); } } } - - return comp != null; + + return comparator != null; } - + /** * Attempts to parse given expression again as a literal of given type. * This will only work if the expression is a literal and its unparsed @@ -251,22 +254,58 @@ private boolean init(String expr) { * @return A literal value, or null if parsing failed. */ @Nullable - private <T> SimpleLiteral<T> reparseLiteral(Class<T> type, Expression<?> expr) { - if (expr instanceof SimpleLiteral) { // Only works for simple literals - Expression<?> source = expr.getSource(); - - // Try to get access to unparsed content of it - if (source instanceof UnparsedLiteral) { - String unparsed = ((UnparsedLiteral) source).getData(); - T data = Classes.parse(unparsed, type, ParseContext.DEFAULT); - if (data != null) { // Success, let's make a literal of it - return new SimpleLiteral<>(data, false, new UnparsedLiteral(unparsed)); - } + private <T> SimpleLiteral<T> reparseLiteral(Class<T> type, Expression<?> expression) { + Expression<?> source = expression; + if (expression instanceof SimpleLiteral) // Only works for simple literals + source = expression.getSource(); + + // Try to get access to unparsed content of it + if (source instanceof UnparsedLiteral) { + String unparsed = ((UnparsedLiteral) source).getData(); + T data = Classes.parse(unparsed, type, ParseContext.DEFAULT); + if (data != null) { // Success, let's make a literal of it + return new SimpleLiteral<>(data, false, new UnparsedLiteral(unparsed)); } } return null; // Context-sensitive parsing failed; can't really help it } - + + /** + * Attempts to transform an UnparsedLiteral into a type that is comparable to another. + * For example 'fire' will be VisualEffect without this, but if the user attempts to compare 'fire' + * with a block. This method will see if 'fire' can be compared to the block, and it will find ItemType. + * Essentially solving something a human sees as comparable, but Skript doesn't understand. + * + * @param one The UnparsedLiteral expression to attempt to reconstruct. + * @param two any expression to grab the return type from. + * @return The newly formed Literal, will be SimpleLiteral in most cases. + */ + @SuppressWarnings("unchecked") + private Literal<?> attemptReconstruction(UnparsedLiteral one, Expression<?> two) { + Expression<?> expression = null; + // Must handle numbers first. + expression = one.getConvertedExpression(Number.class); + if (expression == null) { + for (ClassInfo<?> classinfo : Classes.getClassInfos()) { + if (classinfo.getParser() == null) + continue; + ComparatorInfo<?, ?> comparator = Comparators.getComparatorInfo(two.getReturnType(), classinfo.getC()); + if (comparator == null) + continue; + // We don't care about comparators that take in an object. This just causes more issues accepting and increases iterations by half. + // Let getConvertedExpression deal with it in the end if no other possible reparses against the remaining classinfos exist. + if (comparator.getFirstType() == Object.class) + continue; + expression = reparseLiteral(classinfo.getC(), one); + if (expression != null) + break; + } + } + if (expression == null) + expression = one.getConvertedExpression(two.getReturnType()); + return (Literal<?>) expression; + } + /* * # := condition (e.g. is, is less than, contains, is enchanted with, has permission, etc.) * !# := not # @@ -302,20 +341,20 @@ private <T> SimpleLiteral<T> reparseLiteral(Class<T> type, Expression<?> expr) { * neither a nor b # x or y === a !# x or y && b !# x or y // nor = and */ @Override - @SuppressWarnings({"null", "unchecked"}) + @SuppressWarnings("unchecked") public boolean check(final Event e) { final Expression<?> third = this.third; return first.check(e, (Checker<Object>) o1 -> second.check(e, (Checker<Object>) o2 -> { if (third == null) - return relation.isImpliedBy(comp != null ? comp.compare(o1, o2) : Comparators.compare(o1, o2)); + return relation.isImpliedBy(comparator != null ? comparator.compare(o1, o2) : Comparators.compare(o1, o2)); return third.check(e, (Checker<Object>) o3 -> { boolean isBetween; - if (comp != null) { + if (comparator != null) { isBetween = - (Relation.GREATER_OR_EQUAL.isImpliedBy(comp.compare(o1, o2)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(comp.compare(o1, o3))) + (Relation.GREATER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o2)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o3))) // Check OPPOSITE (switching o2 / o3) - || (Relation.GREATER_OR_EQUAL.isImpliedBy(comp.compare(o1, o3)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(comp.compare(o1, o2))); + || (Relation.GREATER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o3)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o2))); } else { isBetween = (Relation.GREATER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o2)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o3))) @@ -337,7 +376,7 @@ public String toString(final @Nullable Event e, final boolean debug) { else s = first.toString(e, debug) + " is " + (isNegated() ? "not " : "") + "between " + second.toString(e, debug) + " and " + third.toString(e, debug); if (debug) - s += " (comparator: " + comp + ")"; + s += " (comparator: " + comparator + ")"; return s; } diff --git a/src/main/java/ch/njol/skript/registrations/Classes.java b/src/main/java/ch/njol/skript/registrations/Classes.java index 1b316f97dfb..a2958b4cd04 100644 --- a/src/main/java/ch/njol/skript/registrations/Classes.java +++ b/src/main/java/ch/njol/skript/registrations/Classes.java @@ -504,9 +504,9 @@ public static <T> T parse(final String s, final Class<T> c, final ParseContext c continue; if (c.isAssignableFrom(conv.getTo())) { log.clear(); - final Object o = parseSimple(s, conv.getFrom(), context); - if (o != null) { - t = (T) ((Converter) conv.getConverter()).convert(o); + Object object = parseSimple(s, conv.getFrom(), context); + if (object != null) { + t = (T) ((Converter) conv.getConverter()).convert(object); if (t != null) { log.printLog(); return t; diff --git a/src/main/java/ch/njol/skript/test/runner/EffAssert.java b/src/main/java/ch/njol/skript/test/runner/EffAssert.java index 4f78665295d..d61a19dc9bd 100644 --- a/src/main/java/ch/njol/skript/test/runner/EffAssert.java +++ b/src/main/java/ch/njol/skript/test/runner/EffAssert.java @@ -20,6 +20,7 @@ import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.script.Script; import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; @@ -46,6 +47,7 @@ public class EffAssert extends Effect { @Nullable private Condition condition; + private Script script; private Expression<String> errorMsg; private boolean shouldFail; @@ -56,6 +58,7 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye String conditionString = parseResult.regexes.get(0).group(); errorMsg = (Expression<String>) exprs[0]; shouldFail = parseResult.mark != 0; + script = getParser().getCurrentScript(); ParseLogHandler logHandler = SkriptLogger.startParseLogHandler(); try { @@ -91,7 +94,7 @@ public TriggerItem walk(Event event) { if (SkriptJUnitTest.getCurrentJUnitTest() != null) { TestTracker.junitTestFailed(SkriptJUnitTest.getCurrentJUnitTest(), message); } else { - TestTracker.testFailed(message); + TestTracker.testFailed(message, script); } return null; } diff --git a/src/main/java/ch/njol/skript/test/runner/EvtTestCase.java b/src/main/java/ch/njol/skript/test/runner/EvtTestCase.java index eb6bbd93b4f..cec92f1dadc 100644 --- a/src/main/java/ch/njol/skript/test/runner/EvtTestCase.java +++ b/src/main/java/ch/njol/skript/test/runner/EvtTestCase.java @@ -18,6 +18,8 @@ */ package ch.njol.skript.test.runner; +import org.bukkit.Location; +import org.bukkit.block.Block; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @@ -27,25 +29,41 @@ import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptEvent; import ch.njol.skript.lang.SkriptParser; +import ch.njol.skript.registrations.EventValues; +import ch.njol.skript.util.Getter; public class EvtTestCase extends SkriptEvent { - + static { - if (TestMode.ENABLED) + if (TestMode.ENABLED) { Skript.registerEvent("Test Case", EvtTestCase.class, SkriptTestEvent.class, "test %string% [when <.+>]") - .description("Contents represent one test case.") - .examples("") - .since("2.5"); + .description("Contents represent one test case.") + .examples("") + .since("2.5"); + EventValues.registerEventValue(SkriptTestEvent.class, Block.class, new Getter<Block, SkriptTestEvent>() { + @Override + @Nullable + public Block get(SkriptTestEvent ignored) { + return SkriptJUnitTest.getBlock(); + } + }, EventValues.TIME_NOW); + EventValues.registerEventValue(SkriptTestEvent.class, Location.class, new Getter<Location, SkriptTestEvent>() { + @Override + @Nullable + public Location get(SkriptTestEvent ignored) { + return SkriptJUnitTest.getTestLocation(); + } + }, EventValues.TIME_NOW); + } } - - @SuppressWarnings("null") + private Expression<String> name; - + @Nullable private Condition condition; - - @SuppressWarnings({"null", "unchecked"}) + @Override + @SuppressWarnings("unchecked") public boolean init(Literal<?>[] args, int matchedPattern, SkriptParser.ParseResult parseResult) { name = (Expression<String>) args[0]; if (!parseResult.regexes.isEmpty()) { // Do not parse or run unless condition is met @@ -54,27 +72,27 @@ public boolean init(Literal<?>[] args, int matchedPattern, SkriptParser.ParseRes } return true; } - + @Override - public boolean check(Event e) { - String n = name.getSingle(e); - if (n == null) { + public boolean check(Event event) { + String n = name.getSingle(event); + if (n == null) return false; - } Skript.info("Running test case " + n); TestTracker.testStarted(n); return true; } - + @Override public boolean shouldLoadEvent() { return condition != null ? condition.check(new SkriptTestEvent()) : true; } - + @Override - public String toString(@Nullable Event e, boolean debug) { - if (e != null) - return "test " + name.getSingle(e); + public String toString(@Nullable Event event, boolean debug) { + if (event != null) + return "test " + name.getSingle(event); return "test case"; } + } diff --git a/src/main/java/ch/njol/skript/test/runner/SkriptJUnitTest.java b/src/main/java/ch/njol/skript/test/runner/SkriptJUnitTest.java index ed9fb6d026e..8431dc9a6a3 100644 --- a/src/main/java/ch/njol/skript/test/runner/SkriptJUnitTest.java +++ b/src/main/java/ch/njol/skript/test/runner/SkriptJUnitTest.java @@ -86,14 +86,14 @@ public final void cleanup() { /** * @return the test world. */ - protected World getTestWorld() { + public static World getTestWorld() { return Bukkit.getWorlds().get(0); } /** * @return the testing location at the spawn of the testing world. */ - protected Location getTestLocation() { + public static Location getTestLocation() { return getTestWorld().getSpawnLocation().add(0, 1, 0); } @@ -102,7 +102,7 @@ protected Location getTestLocation() { * * @return Pig that has been spawned. */ - protected Pig spawnTestPig() { + public static Pig spawnTestPig() { if (delay <= 0D) delay = 1; // A single tick allows the piggy to spawn before server shutdown. return (Pig) getTestWorld().spawnEntity(getTestLocation(), EntityType.PIG); @@ -114,7 +114,7 @@ protected Pig spawnTestPig() { * @param material The material to set the block to. * @return the Block after it has been updated. */ - protected Block setBlock(Material material) { + public static Block setBlock(Material material) { Block block = getBlock(); block.setType(material); return block; @@ -125,7 +125,7 @@ protected Block setBlock(Material material) { * * @return the Block after it has been updated. */ - protected Block getBlock() { + public static Block getBlock() { return getTestWorld().getSpawnLocation().add(10, 1, 0).getBlock(); } diff --git a/src/main/java/ch/njol/skript/test/runner/TestTracker.java b/src/main/java/ch/njol/skript/test/runner/TestTracker.java index fb6b2a0df40..dde781c344f 100644 --- a/src/main/java/ch/njol/skript/test/runner/TestTracker.java +++ b/src/main/java/ch/njol/skript/test/runner/TestTracker.java @@ -18,12 +18,14 @@ */ package ch.njol.skript.test.runner; +import java.io.File; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.script.Script; import ch.njol.skript.test.utils.TestResults; @@ -54,6 +56,12 @@ public static void testFailed(String msg) { failedTests.put(currentTest, msg); } + public static void testFailed(String msg, Script script) { + String file = script.getConfig().getFileName(); + file = file.substring(file.lastIndexOf(File.separator) + 1); + failedTests.put(currentTest, msg + " [" + file + "]"); + } + public static void junitTestFailed(String junit, String msg) { failedTests.put(junit, msg); } diff --git a/src/test/skript/tests/misc/dummy.sk b/src/test/skript/tests/misc/dummy.sk index abf6c02d80e..d31e8fd53b7 100644 --- a/src/test/skript/tests/misc/dummy.sk +++ b/src/test/skript/tests/misc/dummy.sk @@ -1,2 +1,2 @@ test "redundant": - assert "true" is "true" with "logic failed" \ No newline at end of file + assert "true" is "true" with "logic failed" diff --git a/src/test/skript/tests/regressions/2711-item-compares.sk b/src/test/skript/tests/regressions/2711-item-compares.sk new file mode 100644 index 00000000000..064f6cc7cff --- /dev/null +++ b/src/test/skript/tests/regressions/2711-item-compares.sk @@ -0,0 +1,5 @@ +test "item comparison": + set {_test} to a dragon head + assert {_test} is a dragon head with "failed to compare against a head" + set {_test} to a door + assert {_test} is any door, any wood door, birch door or a closed birch door with "failed to compare against a door" diff --git a/src/test/skript/tests/regressions/4769-fire-visualeffect.sk b/src/test/skript/tests/regressions/4769-fire-visualeffect.sk new file mode 100644 index 00000000000..3965e6c877f --- /dev/null +++ b/src/test/skript/tests/regressions/4769-fire-visualeffect.sk @@ -0,0 +1,15 @@ +test "fire visual effect comparison": + assert a block is a block with "failed to compare block classinfo against block classinfo" + assert an itemtype is an itemtype with "failed to compare itemtype classinfo against itemtype classinfo" + assert a diamond is an itemtype with "failed to compare itemtype 'diamond' against itemtype classinfo" + set {_block} to type of block at spawn of world "world" + set block at spawn of world "world" to fire + assert block at spawn of world "world" is fire with "failed to compare fire (itemtype) with a block" + set block at spawn of world "world" to {_block} + play 5 fire at spawn of world "world" + assert "fire" parsed as visual effect is fire with "failed to compare visual effects" + assert "fire" parsed as visual effect is "fire" parsed as itemtype to fail with "failed to compare visual effects ##2" + spawn a chicken at spawn of world "world": + assert event-entity is a chicken with "failed to compare a chicken" + assert event-entity is a "chicken" parsed as itemtype to fail with "failed to compare a chicken ##2" + clear event-entity diff --git a/src/test/skript/tests/regressions/4773-composter-the-imposter.sk b/src/test/skript/tests/regressions/4773-composter-the-imposter.sk new file mode 100644 index 00000000000..7fb0fdc677e --- /dev/null +++ b/src/test/skript/tests/regressions/4773-composter-the-imposter.sk @@ -0,0 +1,5 @@ +test "composter the imposter" when running minecraft "1.14.4": + set {_block} to type of block at spawn of world "world" + set block at spawn of world "world" to composter + assert block at spawn of world "world" is composter with "failed to compare composter (itemtype) with a block" + set block at spawn of world "world" to {_block} diff --git a/src/test/skript/tests/regressions/pull-5566-block iterator being 100 always.sk b/src/test/skript/tests/regressions/pull-5566-block iterator being 100 always.sk index 3eb14b86d94..ef77c2955f5 100644 --- a/src/test/skript/tests/regressions/pull-5566-block iterator being 100 always.sk +++ b/src/test/skript/tests/regressions/pull-5566-block iterator being 100 always.sk @@ -1,5 +1,5 @@ # Test not related to a made issue. Details at pull request. test "100 blocks fix": - assert blocks 5 below spawn of world "world" contains air, grass block, dirt block, dirt block, bedrock block and void air with "Failed to get correct blocks" + assert blocks 5 below spawn of world "world" contains air, grass block, dirt block, dirt block, bedrock block and void air with "Failed to get correct blocks (got '%blocks 5 below spawn of world "world"%')" assert size of blocks 3 below location below spawn of world "world" is 4 with "Failed to match asserted size" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprBlockData.sk b/src/test/skript/tests/syntaxes/expressions/ExprBlockData.sk index 1916c370f95..8fd992f8ce1 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprBlockData.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprBlockData.sk @@ -1,4 +1,4 @@ -test "block data" when running minecraft "1.15.2": +test "block data" when running minecraft "1.14.4": set {_b} to block at spawn of world "world" set block at {_b} to campfire[lit=false;waterlogged=true] assert block at {_b} is an unlit campfire with "block at spawn should be an unlit campfire" From baf5bdd5671cfa085e5892f1f5b59304caa096f3 Mon Sep 17 00:00:00 2001 From: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> Date: Sat, 3 Jun 2023 12:10:42 -0500 Subject: [PATCH 334/619] Improve documentation workflows (#5714) * Create action.yml * Update docs.yml * Update docs.yml * Update docs.yml * Update docs.yml * Update action.yml * Update docs.yml * Update action.yml * Update action.yml * Update action.yml * Update action.yml * Create action.yml * Update action.yml * Rename action.yml to action.yml * Update action.yml * Update action.yml * Update action.yml * Update action.yml * Update docs.yml * Update action.yml * Update action.yml * Update action.yml * Update action.yml * Create action.yml * Update docs.yml * Update docs.yml * Update action.yml * Update docs.yml * Update docs.yml * Update action.yml * Update docs.yml * Update docs.yml * Update action.yml * Update docs.yml * Update docs.yml * Update action.yml * Update action.yml * Update action.yml * Update and rename docs.yml to nightlydocs.yml * Rename nightlydocs.yml to nightly-docs.yml * Update action.yml * Create release-docs.yml * Update release-docs.yml * Create archive-docs.yml * Update release-docs.yml * Update build.gradle * Update build.gradle * Update action.yml * Update action.yml * Update archive-docs.yml * Update release-docs.yml * Update release-docs.yml * Update archive-docs.yml * Update nightly-docs.yml * Update action.yml * Update release-docs.yml * Delete archive-docs.yml * Update release-docs.yml * Update release-docs.yml * Update release-docs.yml * Update nightly-docs.yml * Update action.yml * Update release-docs.yml * Update action.yml * Update build.gradle * Update action.yml * Update action.yml * Update build.gradle * Update nightly-docs.yml * Update build.gradle * Update PlatformMain.java * Update PlatformMain.java * Update nightly-docs.yml * Update nightly-docs.yml * Update nightly-docs.yml * Update nightly-docs.yml * Update nightly-docs.yml * Update nightly-docs.yml * Update nightly-docs.yml * Update Environment.java * Update nightly-docs.yml * Update release-docs.yml * Update nightly-docs.yml * Update .github/workflows/docs/generate-docs/action.yml Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> * Update action.yml * Update build.gradle * Update TestResults.java --------- Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> --- .github/workflows/docs.yml | 59 ------------- .../workflows/docs/generate-docs/action.yml | 41 ++++++++++ .github/workflows/docs/push-docs/action.yml | 41 ++++++++++ .github/workflows/docs/setup-docs/action.yml | 44 ++++++++++ .github/workflows/nightly-docs.yml | 46 +++++++++++ .github/workflows/release-docs.yml | 82 +++++++++++++++++++ build.gradle | 32 ++++++-- .../skript/test/platform/Environment.java | 2 +- .../njol/skript/test/utils/TestResults.java | 2 +- 9 files changed, 283 insertions(+), 66 deletions(-) delete mode 100644 .github/workflows/docs.yml create mode 100644 .github/workflows/docs/generate-docs/action.yml create mode 100644 .github/workflows/docs/push-docs/action.yml create mode 100644 .github/workflows/docs/setup-docs/action.yml create mode 100644 .github/workflows/nightly-docs.yml create mode 100644 .github/workflows/release-docs.yml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index e41c4bf7692..00000000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: Nightly Docs - -on: - push: - branches: - - master - - 'dev/**' - -jobs: - docs: - if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - submodules: recursive - path: skript - - name: Set up JDK 17 - uses: actions/setup-java@v3 - with: - java-version: '17' - distribution: 'adopt' - cache: gradle - - name: Grant execute permission for gradlew - run: chmod +x ./skript/gradlew - - name: Checkout skript-docs - uses: actions/checkout@v3 - with: - repository: 'SkriptLang/skript-docs' - path: skript-docs - ssh-key: ${{ secrets.DOCS_DEPLOY_KEY }} - - name: Setup documentation environment - env: - DOCS_DEPLOY_KEY: ${{ secrets.DOCS_DEPLOY_KEY }} - run: | - eval `ssh-agent` - DOC_DIR="${GITHUB_WORKSPACE}/skript-docs/docs/nightly/${GITHUB_REF#refs/heads/}" - rm -r ${DOC_DIR}/* || true - echo "DOC_DIR=${DOC_DIR}" >> $GITHUB_ENV - echo "SKRIPT_DOCS_TEMPLATE_DIR=${GITHUB_WORKSPACE}/skript-docs/doc-templates" >> $GITHUB_ENV - echo "SKRIPT_DOCS_OUTPUT_DIR=${DOC_DIR}/" >> $GITHUB_ENV - echo "$DOCS_DEPLOY_KEY" | tr -d '\r' | ssh-add - > /dev/null - mkdir ~/.ssh - ssh-keyscan www.github.com >> ~/.ssh/known_hosts - - name: Build Skript and generate docs - run: | - cd ./skript - ./gradlew clean genDocs javadoc - mv "${GITHUB_WORKSPACE}/skript/build/docs/javadoc" "${DOC_DIR}/javadocs" - cd .. - - name: Push nightly docs - if: success() - run: | - cd ./skript-docs - git config user.email "nightlydocs@skriptlang.org" - git config user.name "Nightly Docs Bot" - git add docs/nightly - git commit -m "Update nightly docs" || (echo "Nothing to push!" && exit 0) - git push origin main diff --git a/.github/workflows/docs/generate-docs/action.yml b/.github/workflows/docs/generate-docs/action.yml new file mode 100644 index 00000000000..f84023a60df --- /dev/null +++ b/.github/workflows/docs/generate-docs/action.yml @@ -0,0 +1,41 @@ +name: Generate documentation + +inputs: + docs_output_dir: + description: "The directory to generate the documentation into" + required: true + type: string + docs_repo_dir: + description: "The skript-docs repository directory" + required: true + type: string + skript_repo_dir: + description: "The skript repository directory" + required: true + type: string + is_release: + description: "Designates whether to generate nightly or release documentation" + required: false + default: false + type: boolean + +runs: + using: 'composite' + steps: + - name: generate-docs + shell: bash + env: + DOCS_OUTPUT_DIR: ${{ inputs.docs_output_dir }} + DOCS_REPO_DIR: ${{ inputs.docs_repo_dir }} + SKRIPT_REPO_DIR: ${{ inputs.skript_repo_dir }} + IS_RELEASE: ${{ inputs.is_release }} + run: | + export SKRIPT_DOCS_TEMPLATE_DIR=${DOCS_REPO_DIR}/doc-templates + export SKRIPT_DOCS_OUTPUT_DIR=${DOCS_OUTPUT_DIR}/ + cd $SKRIPT_REPO_DIR + if [[ "${IS_RELEASE}" == "true" ]]; then + ./gradlew genReleaseDocs releaseJavadoc + else + ./gradlew genNightlyDocs javadoc + fi + cp -a "./build/docs/javadoc/." "${DOCS_OUTPUT_DIR}/javadocs" diff --git a/.github/workflows/docs/push-docs/action.yml b/.github/workflows/docs/push-docs/action.yml new file mode 100644 index 00000000000..bcc296a85c3 --- /dev/null +++ b/.github/workflows/docs/push-docs/action.yml @@ -0,0 +1,41 @@ +name: Generate documentation + +inputs: + docs_output_dir: + description: "The directory to generate the documentation into" + required: true + type: string + docs_repo_dir: + description: "The skript-docs repository directory" + required: true + type: string + git_email: + description: "The email to use for the Git commit" + required: true + type: string + git_name: + description: "The name to use for the Git commit" + required: true + type: string + git_commit_message: + description: "The message to use for the Git commit" + required: true + type: string + +runs: + using: 'composite' + steps: + - shell: bash + if: success() + env: + DOCS_REPO_DIR: ${{ inputs.docs_repo_dir }} + GIT_EMAIL: ${{ inputs.git_email }} + GIT_NAME: ${{ inputs.git_name }} + GIT_COMMIT_MESSAGE: ${{ inputs.git_commit_message }} + run: | + cd "${DOCS_REPO_DIR}" + git config user.name "${GIT_NAME}" + git config user.email "${GIT_EMAIL}" + git add -A + git commit -m "${GIT_COMMIT_MESSAGE}" || (echo "Nothing to push!" && exit 0) + git push origin main diff --git a/.github/workflows/docs/setup-docs/action.yml b/.github/workflows/docs/setup-docs/action.yml new file mode 100644 index 00000000000..1feedcd8f10 --- /dev/null +++ b/.github/workflows/docs/setup-docs/action.yml @@ -0,0 +1,44 @@ +name: Setup documentation environment + +inputs: + docs_deploy_key: + description: "Deploy key for the skript-docs repo" + required: true + type: string + docs_output_dir: + description: "The directory to generate the documentation into" + required: true + type: string + cleanup_pattern: + description: "A pattern designating which files to delete when cleaning the documentation output directory" + required: false + default: "*" + type: string + +runs: + using: 'composite' + steps: + - name: Checkout skript-docs + uses: actions/checkout@v3 + with: + repository: 'SkriptLang/skript-docs' + path: skript-docs + ssh-key: ${{ inputs.docs_deploy_key }} + - uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'adopt' + cache: gradle + - shell: bash + run: chmod +x ./skript/gradlew + - shell: bash + env: + DOCS_DEPLOY_KEY: ${{ inputs.docs_deploy_key }} + DOCS_OUTPUT_DIR: ${{ inputs.docs_output_dir }} + CLEANUP_PATTERN: ${{ inputs.cleanup_pattern }} + run: | + eval `ssh-agent` + rm -rf ${DOCS_OUTPUT_DIR}/${CLEANUP_PATTERN} || true + echo "$DOCS_DEPLOY_KEY" | tr -d '\r' | ssh-add - > /dev/null + mkdir ~/.ssh + ssh-keyscan www.github.com >> ~/.ssh/known_hosts diff --git a/.github/workflows/nightly-docs.yml b/.github/workflows/nightly-docs.yml new file mode 100644 index 00000000000..2a6841c3771 --- /dev/null +++ b/.github/workflows/nightly-docs.yml @@ -0,0 +1,46 @@ +name: Nightly documentation + +on: + push: + branches: + - '**' + tags-ignore: + - '**' + +jobs: + nightly-docs: + if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" + runs-on: ubuntu-latest + steps: + - name: Configure workflow + id: configuration + run: | + BRANCH_NAME="${GITHUB_REF#refs/*/}" + echo "BRANCH_NAME=${BRANCH_NAME}" >> $GITHUB_OUTPUT + echo "DOCS_OUTPUT_DIR=${GITHUB_WORKSPACE}/skript-docs/docs/nightly/${BRANCH_NAME}" >> $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@v3 + 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 }} + - name: Push nightly documentation + uses: ./skript/.github/workflows/docs/push-docs + with: + docs_output_dir: ${{ steps.configuration.outputs.DOCS_OUTPUT_DIR }} + docs_repo_dir: ${{ steps.configuration.outputs.DOCS_REPO_DIR }} + git_name: Nightly Docs Bot + git_email: nightlydocs@skriptlang.org + git_commit_message: "Update ${{ steps.configuration.outputs.BRANCH_NAME }} branch nightly docs to ${{ github.repository }}@${{ github.sha }}" diff --git a/.github/workflows/release-docs.yml b/.github/workflows/release-docs.yml new file mode 100644 index 00000000000..9ec5a5ad257 --- /dev/null +++ b/.github/workflows/release-docs.yml @@ -0,0 +1,82 @@ +name: Release documentation + +on: + release: + types: [published] + +jobs: + release-docs: + if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" + 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/" >> $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@v3 + 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 }} + cleanup_pattern: "!(nightly|archives)" + - 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 + - name: Push release documentation + uses: ./skript/.github/workflows/docs/push-docs + with: + docs_output_dir: ${{ steps.configuration.outputs.DOCS_OUTPUT_DIR }} + docs_repo_dir: ${{ steps.configuration.outputs.DOCS_REPO_DIR }} + git_name: Release Docs Bot + git_email: releasedocs@skriptlang.org + 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@v3 + 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 + - name: Push archive documentation + uses: ./skript/.github/workflows/docs/push-docs + with: + docs_output_dir: ${{ steps.configuration.outputs.DOCS_OUTPUT_DIR }} + docs_repo_dir: ${{ steps.configuration.outputs.DOCS_REPO_DIR }} + git_name: 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 4ed4e1603be..c6e089cadac 100644 --- a/build.gradle +++ b/build.gradle @@ -157,6 +157,15 @@ javadoc { options.addStringOption('Xdoclint:none', '-quiet') } +task releaseJavadoc(type: Javadoc) { + title = 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 { @@ -181,17 +190,29 @@ tasks.register('testNaming') { } enum Modifiers { - DEV_MODE, GEN_DOCS, DEBUG, PROFILE, JUNIT + DEV_MODE, GEN_NIGHTLY_DOCS, GEN_RELEASE_DOCS, DEBUG, PROFILE, JUNIT } // Create a test task with given name, environments dir/file, dev mode and java version. void createTestTask(String name, String desc, String environments, int javaVersion, Modifiers... modifiers) { boolean junit = modifiers.contains(Modifiers.JUNIT) + boolean releaseDocs = modifiers.contains(Modifiers.GEN_RELEASE_DOCS) + boolean docs = modifiers.contains(Modifiers.GEN_NIGHTLY_DOCS) || releaseDocs + def artifact = 'build' + File.separator + 'libs' + File.separator + if (junit) { + artifact += 'Skript-JUnit.jar' + } else if (releaseDocs) { + artifact += 'Skript-github.jar' + } else { + artifact += 'Skript-nightly.jar' + } tasks.register(name, JavaExec) { - description = desc; + description = desc dependsOn licenseTest if (junit) { dependsOn testJar + } else if (releaseDocs) { + dependsOn githubRelease, testNaming } else { dependsOn nightlyRelease, testNaming } @@ -203,7 +224,7 @@ void createTestTask(String name, String desc, String environments, int javaVersi } group = 'execution' classpath = files([ - 'build' + File.separator + 'libs' + File.separator + (junit ? 'Skript-JUnit.jar' : 'Skript-nightly.jar'), + artifact, project.configurations.runtimeClasspath.find { it.name.startsWith('gson') }, sourceSets.main.runtimeClasspath ]) @@ -214,7 +235,7 @@ void createTestTask(String name, String desc, String environments, int javaVersi 'src/test/resources/runner_data', environments, modifiers.contains(Modifiers.DEV_MODE), - modifiers.contains(Modifiers.GEN_DOCS), + docs, junit, modifiers.contains(Modifiers.DEBUG), project.findProperty('verbosity') ?: "null" @@ -257,7 +278,8 @@ createTestTask('skriptTestJava17', 'Runs tests on all Java 17 environments.', en createTestTask('skriptTestJava8', 'Runs tests on all Java 8 environments.', environments + 'java8', oldestJava) createTestTask('skriptTestDev', 'Runs testing server and uses \'system.in\' for command input, stop server to finish.', environments + env, envJava, Modifiers.DEV_MODE, Modifiers.DEBUG) createTestTask('skriptProfile', 'Starts the testing server with JProfiler support.', environments + latestEnv, latestJava, Modifiers.PROFILE) -createTestTask('genDocs', 'Generates the Skript documentation website html files.', environments + env, envJava, Modifiers.GEN_DOCS) +createTestTask('genNightlyDocs', 'Generates the Skript documentation website html files.', environments + env, envJava, Modifiers.GEN_NIGHTLY_DOCS) +createTestTask('genReleaseDocs', 'Generates the Skript documentation website html files for a release.', environments + env, envJava, Modifiers.GEN_RELEASE_DOCS) tasks.register('skriptTest') { description = 'Runs tests on all environments.' dependsOn skriptTestJava8, skriptTestJava17 diff --git a/src/main/java/ch/njol/skript/test/platform/Environment.java b/src/main/java/ch/njol/skript/test/platform/Environment.java index f2e9cf38cd2..6866db6e0c0 100644 --- a/src/main/java/ch/njol/skript/test/platform/Environment.java +++ b/src/main/java/ch/njol/skript/test/platform/Environment.java @@ -249,7 +249,7 @@ public TestResults runTests(Path runnerRoot, Path testsRoot, boolean devMode, bo if (!verbosity.equalsIgnoreCase("null")) args.add("-Dskript.testing.verbosity=" + verbosity); if (genDocs) - args.add("-Dskript.forceregisterhooks"); + args.add("-Dskript.forceregisterhooks=true"); args.add("-Dskript.testing.results=test_results.json"); args.add("-Ddisable.watchdog=true"); if (debug) diff --git a/src/main/java/ch/njol/skript/test/utils/TestResults.java b/src/main/java/ch/njol/skript/test/utils/TestResults.java index 53586491992..d8e9c036538 100644 --- a/src/main/java/ch/njol/skript/test/utils/TestResults.java +++ b/src/main/java/ch/njol/skript/test/utils/TestResults.java @@ -38,7 +38,7 @@ public class TestResults { private final Map<String, String> failed; /** - * If the docs failed to generate when running gradle genDocs command. + * If the docs failed to generate when running gradle doc gen commands. */ private final boolean docsFailed; From ce81e581467455bfcf830d45f41398ff78191903 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Mon, 5 Jun 2023 02:28:20 -0600 Subject: [PATCH 335/619] Bump version to 2.7.0-beta3 (#5574) --- gradle.properties | 2 +- src/main/java/ch/njol/skript/expressions/ExprDurability.java | 2 +- src/main/java/ch/njol/skript/expressions/ExprTarget.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 220737a816b..234314da00c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ groupid=ch.njol name=skript -version=2.7.0-beta2 +version=2.7.0-beta3 jarName=Skript.jar testEnv=java17/paper-1.19.4 testEnvJavaVersion=17 diff --git a/src/main/java/ch/njol/skript/expressions/ExprDurability.java b/src/main/java/ch/njol/skript/expressions/ExprDurability.java index f97431d8609..f592fd882f3 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprDurability.java +++ b/src/main/java/ch/njol/skript/expressions/ExprDurability.java @@ -43,7 +43,7 @@ "reset the durability of {_item}", "set durability of player's held item to 0" }) -@Since("1.2, INSERT VERSION (durability reversed)") +@Since("1.2, 2.7 (durability reversed)") public class ExprDurability extends SimplePropertyExpression<Object, Long> { private boolean durability; diff --git a/src/main/java/ch/njol/skript/expressions/ExprTarget.java b/src/main/java/ch/njol/skript/expressions/ExprTarget.java index d3fd4f45235..33d1a151f76 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTarget.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTarget.java @@ -59,7 +59,7 @@ "delete targeted entity of player # for players it will delete the target", "delete target of last spawned zombie # for entities it will make them target-less" }) -@Since("1.4.2, INSERT VERSION (Reset)") +@Since("1.4.2, 2.7 (Reset)") public class ExprTarget extends PropertyExpression<LivingEntity, Entity> { static { From 46770ebe697afb497d2d3f14a0d8bd9d3399f230 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Mon, 5 Jun 2023 03:12:16 -0600 Subject: [PATCH 336/619] Remove hidden files from tab complete (#5729) --- .../njol/skript/SkriptCommandTabCompleter.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/java/ch/njol/skript/SkriptCommandTabCompleter.java b/src/main/java/ch/njol/skript/SkriptCommandTabCompleter.java index 7f01510e6db..19588312957 100644 --- a/src/main/java/ch/njol/skript/SkriptCommandTabCompleter.java +++ b/src/main/java/ch/njol/skript/SkriptCommandTabCompleter.java @@ -60,24 +60,28 @@ public List<String> onTabComplete(CommandSender sender, Command command, String // TODO Find a better way for caching, it isn't exactly ideal to be calling this method constantly try (Stream<Path> files = Files.walk(scripts.toPath())) { files.map(Path::toFile) - .forEach(f -> { - if (!(enable ? ScriptLoader.getDisabledScriptsFilter() : ScriptLoader.getLoadedScriptsFilter()).accept(f)) + .forEach(file -> { + if (!(enable ? ScriptLoader.getDisabledScriptsFilter() : ScriptLoader.getLoadedScriptsFilter()).accept(file)) return; - String fileString = f.toString().substring(scriptsPathLength); + // Ignore hidden files like .git/ for users that use git source control. + if (file.isHidden()) + return; + + String fileString = file.toString().substring(scriptsPathLength); if (fileString.isEmpty()) return; - if (f.isDirectory()) { + if (file.isDirectory()) { fileString = fileString + fs; // Add file separator at the end of directories - } else if (f.getParentFile().toPath().toString().equals(scriptsPathString)) { + } else if (file.getParentFile().toPath().toString().equals(scriptsPathString)) { fileString = fileString.substring(1); // Remove file separator from the beginning of files or directories in root only if (fileString.isEmpty()) return; } // Make sure the user's argument matches with the file's name or beginning of file path - if (scriptArg.length() > 0 && !f.getName().startsWith(scriptArg) && !fileString.startsWith(scriptArg)) + if (scriptArg.length() > 0 && !file.getName().startsWith(scriptArg) && !fileString.startsWith(scriptArg)) return; // Trim off previous arguments if needed From 1a21df9e8c3a8641ab3a604d91334cd11032a067 Mon Sep 17 00:00:00 2001 From: TheLimeGlass <seantgrover@gmail.com> Date: Mon, 5 Jun 2023 23:01:59 -0600 Subject: [PATCH 337/619] 2.7.0 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 234314da00c..6a846dcfc84 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ groupid=ch.njol name=skript -version=2.7.0-beta3 +version=2.7.0 jarName=Skript.jar testEnv=java17/paper-1.19.4 testEnvJavaVersion=17 From aeaee7abfef32d52ce154cb29064c9201ae10098 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Mon, 5 Jun 2023 23:37:16 -0600 Subject: [PATCH 338/619] Add is climbing (#5618) --- .../skript/conditions/CondIsClimbing.java | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 src/main/java/ch/njol/skript/conditions/CondIsClimbing.java diff --git a/src/main/java/ch/njol/skript/conditions/CondIsClimbing.java b/src/main/java/ch/njol/skript/conditions/CondIsClimbing.java new file mode 100644 index 00000000000..af56f90379f --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondIsClimbing.java @@ -0,0 +1,58 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.conditions; + +import org.bukkit.entity.LivingEntity; + +import ch.njol.skript.Skript; +import ch.njol.skript.conditions.base.PropertyCondition; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; + +@Name("Is Climbing") +@Description("Whether a living entity is climbing, such as a spider up a wall or a player on a ladder.") +@Examples({ + "spawn a spider at location of spawn", + "wait a second", + "if the last spawned spider is climbing:", + "\tmessage\"The spider is now climbing!\"" +}) +@RequiredPlugins("Minecraft 1.17+") +@Since("INSERT VERSION") +public class CondIsClimbing extends PropertyCondition<LivingEntity> { + + static { + if (Skript.methodExists(LivingEntity.class, "isClimbing")) + register(CondIsClimbing.class, "climbing", "livingentities"); + } + + @Override + public boolean check(LivingEntity entity) { + return entity.isClimbing(); + } + + @Override + protected String getPropertyName() { + return "climbing"; + } + +} From b91b3a825c89f4b51ce8719ffdcd512c010ebb49 Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Tue, 6 Jun 2023 08:57:11 +0300 Subject: [PATCH 339/619] Add Vector Projection Expression (#5482) add vector projection expression Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> --- .../expressions/ExprVectorProjection.java | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprVectorProjection.java diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorProjection.java b/src/main/java/ch/njol/skript/expressions/ExprVectorProjection.java new file mode 100644 index 00000000000..1cd10d2ddd5 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorProjection.java @@ -0,0 +1,81 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.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 org.bukkit.event.Event; +import org.bukkit.util.Vector; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Vectors - Vector Projection") +@Description("An expression to get the vector projection of two vectors.") +@Examples("set {_projection} to vector projection of vector(1, 2, 3) onto vector(4, 4, 4)") +@Since("INSERT VERSION") +public class ExprVectorProjection extends SimpleExpression<Vector> { + + static { + Skript.registerExpression(ExprVectorProjection.class, Vector.class, ExpressionType.COMBINED, "[vector] projection [of] %vector% on[to] %vector%"); + } + + private Expression<Vector> left, right; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + this.left = (Expression<Vector>) exprs[0]; + this.right = (Expression<Vector>) exprs[1]; + return true; + } + + @Override + @Nullable + protected Vector[] get(Event event) { + Vector left = this.left.getOptionalSingle(event).orElse(new Vector()); + Vector right = this.right.getOptionalSingle(event).orElse(new Vector()); + double dot = left.dot(right); + double length = right.lengthSquared(); + double scalar = dot / length; + return new Vector[] {right.clone().multiply(scalar)}; + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class<? extends Vector> getReturnType() { + return Vector.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "vector projection of " + left.toString(event, debug) + " onto " + right.toString(event, debug); + } + +} From 97caf938a7a6c78dba44327005bc3360a776ffb6 Mon Sep 17 00:00:00 2001 From: DelayedGaming <72163224+DelayedGaming@users.noreply.github.com> Date: Mon, 12 Jun 2023 23:21:58 +0800 Subject: [PATCH 340/619] Condition LivingEntity is Jumping (#5359) --- .../njol/skript/conditions/CondIsJumping.java | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 src/main/java/ch/njol/skript/conditions/CondIsJumping.java diff --git a/src/main/java/ch/njol/skript/conditions/CondIsJumping.java b/src/main/java/ch/njol/skript/conditions/CondIsJumping.java new file mode 100644 index 00000000000..9a8c1124fd3 --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondIsJumping.java @@ -0,0 +1,71 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.conditions; + +import ch.njol.skript.conditions.base.PropertyCondition; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Since; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.Skript; +import ch.njol.util.Kleenean; + +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.LivingEntity; + +@Name("Is Jumping") +@Description("Checks whether a living entity is jumping. This condition does not work on players.") +@Examples({ + "on spawn of zombie:", + "\twhile event-entity is not jumping:", + "\t\twait 5 ticks", + "\tpush event-entity upwards" +}) +@Since("INSERT VERSION") +@RequiredPlugins("Paper 1.15+") +public class CondIsJumping extends PropertyCondition<LivingEntity> { + + static { + if (Skript.methodExists(LivingEntity.class, "isJumping")) + register(CondIsJumping.class, "jumping", "livingentities"); + } + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (HumanEntity.class.isAssignableFrom(exprs[0].getReturnType())) { + Skript.error("The 'is jumping' condition only works on mobs."); + return false; + } + return super.init(exprs, matchedPattern, isDelayed, parseResult); + } + + @Override + public boolean check(LivingEntity livingEntity) { + return livingEntity.isJumping(); + } + + @Override + protected String getPropertyName() { + return "jumping"; + } + +} From 2c427faeb1c1deb17aa012a35d547eeb2f508f87 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Jun 2023 03:50:02 -0600 Subject: [PATCH 341/619] Bump io.papermc.paper:paper-api from 1.19.4-R0.1-SNAPSHOT to 1.20-R0.1-SNAPSHOT (#5739) * Bump io.papermc.paper:paper-api Bumps io.papermc.paper:paper-api from 1.19.4-R0.1-SNAPSHOT to 1.20-R0.1-SNAPSHOT. --- updated-dependencies: - dependency-name: io.papermc.paper:paper-api dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> * Update and rename paper-1.19.3.json to paper-1.20.json * Update build.gradle * Update gradle.properties * Update default.lang --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> --- build.gradle | 4 ++-- gradle.properties | 2 +- src/main/resources/lang/default.lang | 2 ++ .../java17/{paper-1.19.3.json => paper-1.20.json} | 4 ++-- 4 files changed, 7 insertions(+), 5 deletions(-) rename src/test/skript/environments/java17/{paper-1.19.3.json => paper-1.20.json} (85%) diff --git a/build.gradle b/build.gradle index c6e089cadac..f05c899d0b2 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ dependencies { shadow group: 'org.bstats', name: 'bstats-bukkit', version: '3.0.2' shadow group: 'net.kyori', name: 'adventure-text-serializer-bungeecord', version: '4.3.0' - implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.19.4-R0.1-SNAPSHOT' + implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.20-R0.1-SNAPSHOT' implementation group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.700' implementation group: 'com.google.code.findbugs', name: 'findbugs', version: '3.0.1' implementation group: 'com.sk89q.worldguard', name: 'worldguard-legacy', version: '7.0.0-SNAPSHOT' @@ -254,7 +254,7 @@ void createTestTask(String name, String desc, String environments, int javaVersi } } -def latestEnv = 'java17/paper-1.19.4.json' +def latestEnv = 'java17/paper-1.20.json' def latestJava = 17 def oldestJava = 8 diff --git a/gradle.properties b/gradle.properties index 234314da00c..22cf891c6cb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,5 +2,5 @@ groupid=ch.njol name=skript version=2.7.0-beta3 jarName=Skript.jar -testEnv=java17/paper-1.19.4 +testEnv=java17/paper-1.20 testEnvJavaVersion=17 diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 03307a009d0..e86820ed0c2 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -1299,6 +1299,8 @@ damage causes: dryout: dryout custom: unknown, custom, plugin, a plugin sonic_boom: sonic boom + kill: kill, killed + world_border: world border # -- Teleport Causes -- teleport causes: diff --git a/src/test/skript/environments/java17/paper-1.19.3.json b/src/test/skript/environments/java17/paper-1.20.json similarity index 85% rename from src/test/skript/environments/java17/paper-1.19.3.json rename to src/test/skript/environments/java17/paper-1.20.json index 731f032034e..73c81a4018c 100644 --- a/src/test/skript/environments/java17/paper-1.19.3.json +++ b/src/test/skript/environments/java17/paper-1.20.json @@ -1,11 +1,11 @@ { - "name": "paper-1.19.3", + "name": "paper-1.20", "resources": [ {"source": "server.properties.generic", "target": "server.properties"} ], "paperDownloads": [ { - "version": "1.19.3", + "version": "1.20", "target": "paperclip.jar" } ], From 3dd5394e53e247face129d726be2d5e709e90fd4 Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Fri, 23 Jun 2023 11:25:58 +0300 Subject: [PATCH 342/619] Add InventoryDragEvent (#5474) --- .../classes/data/BukkitEventValues.java | 82 +++++++++++++++++++ .../ch/njol/skript/events/SimpleEvents.java | 15 +++- 2 files changed, 95 insertions(+), 2 deletions(-) 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 314fd98bab7..b628c56a887 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -101,9 +101,11 @@ import org.bukkit.event.hanging.HangingPlaceEvent; import org.bukkit.event.inventory.ClickType; import org.bukkit.event.inventory.CraftItemEvent; +import org.bukkit.event.inventory.DragType; import org.bukkit.event.inventory.InventoryAction; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.inventory.InventoryDragEvent; import org.bukkit.event.inventory.InventoryOpenEvent; import org.bukkit.event.inventory.InventoryPickupItemEvent; import org.bukkit.event.inventory.PrepareAnvilEvent; @@ -145,13 +147,17 @@ import org.bukkit.event.world.WorldEvent; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryView; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; import org.bukkit.inventory.Recipe; import org.bukkit.potion.PotionEffectType; import org.eclipse.jdt.annotation.Nullable; +import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * @author Peter Güttinger @@ -1087,6 +1093,82 @@ public Inventory get(final InventoryClickEvent e) { return e.getClickedInventory(); } }, 0); + // InventoryDragEvent + EventValues.registerEventValue(InventoryDragEvent.class, Player.class, new Getter<Player, InventoryDragEvent>() { + @Override + @Nullable + public Player get(InventoryDragEvent event) { + return event.getWhoClicked() instanceof Player ? (Player) event.getWhoClicked() : null; + } + }, EventValues.TIME_NOW); + EventValues.registerEventValue(InventoryDragEvent.class, World.class, new Getter<World, InventoryDragEvent>() { + @Override + @Nullable + public World get(InventoryDragEvent event) { + return event.getWhoClicked().getWorld(); + } + }, EventValues.TIME_NOW); + EventValues.registerEventValue(InventoryDragEvent.class, ItemStack.class, new Getter<ItemStack, InventoryDragEvent>() { + @Override + @Nullable + public ItemStack get(InventoryDragEvent event) { + return event.getOldCursor(); + } + }, EventValues.TIME_PAST); + EventValues.registerEventValue(InventoryDragEvent.class, ItemStack.class, new Getter<ItemStack, InventoryDragEvent>() { + @Override + @Nullable + public ItemStack get(InventoryDragEvent event) { + return event.getCursor(); + } + }, EventValues.TIME_NOW); + EventValues.registerEventValue(InventoryDragEvent.class, ItemStack[].class, new Getter<ItemStack[], InventoryDragEvent>() { + @Override + @Nullable + public ItemStack[] get(InventoryDragEvent event) { + return event.getNewItems().values().toArray(new ItemStack[0]); + } + }, EventValues.TIME_NOW); + EventValues.registerEventValue(InventoryDragEvent.class, Slot[].class, new Getter<Slot[], InventoryDragEvent>() { + @Override + @Nullable + public Slot[] get(InventoryDragEvent event) { + List<Slot> slots = new ArrayList<>(event.getRawSlots().size()); + InventoryView view = event.getView(); + for (Integer rawSlot : event.getRawSlots()) { + Inventory inventory = view.getInventory(rawSlot); + int slot = view.convertSlot(rawSlot); + if (inventory == null) + continue; + // Not all indices point to inventory slots. Equipment, for example + if (inventory instanceof PlayerInventory && slot >= 36) { + slots.add(new ch.njol.skript.util.slot.EquipmentSlot(((PlayerInventory) view.getBottomInventory()).getHolder(), slot)); + } else { + slots.add(new InventorySlot(inventory, view.convertSlot(rawSlot))); + } + } + return slots.toArray(new Slot[0]); + } + }, EventValues.TIME_NOW); + EventValues.registerEventValue(InventoryDragEvent.class, ClickType.class, new Getter<ClickType, InventoryDragEvent>() { + @Override + @Nullable + public ClickType get(InventoryDragEvent event) { + return event.getType() == DragType.EVEN ? ClickType.LEFT : ClickType.RIGHT; + } + }, EventValues.TIME_NOW); + EventValues.registerEventValue(InventoryDragEvent.class, Inventory[].class, new Getter<Inventory[], InventoryDragEvent>() { + @Override + @Nullable + public Inventory[] get(InventoryDragEvent event) { + Set<Inventory> inventories = new HashSet<>(); + InventoryView view = event.getView(); + for (Integer rawSlot : event.getRawSlots()) { + inventories.add(view.getInventory(rawSlot)); + } + return inventories.toArray(new Inventory[0]); + } + }, EventValues.TIME_NOW); // PrepareAnvilEvent EventValues.registerEventValue(PrepareAnvilEvent.class, ItemStack.class, new Getter<ItemStack, PrepareAnvilEvent>() { @Override diff --git a/src/main/java/ch/njol/skript/events/SimpleEvents.java b/src/main/java/ch/njol/skript/events/SimpleEvents.java index 6e1aa2b7004..bcb301489e1 100644 --- a/src/main/java/ch/njol/skript/events/SimpleEvents.java +++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java @@ -59,6 +59,7 @@ import org.bukkit.event.inventory.FurnaceBurnEvent; import org.bukkit.event.inventory.FurnaceSmeltEvent; import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.inventory.InventoryDragEvent; import org.bukkit.event.inventory.InventoryOpenEvent; import org.bukkit.event.inventory.InventoryPickupItemEvent; import org.bukkit.event.inventory.PrepareAnvilEvent; @@ -670,7 +671,7 @@ public class SimpleEvents { "Use <a href='./expressions.html#ExprChatFormat'>chat format</a> to change message format.", "Use <a href='./expressions.html#ExprChatRecipients'>chat recipients</a> to edit chat recipients." ) - .examples( + .examples( "on chat:", "\tif player has permission \"owner\":", "\t\tset chat format to \"<red>[player]<light gray>: <light red>[message]\"", @@ -679,7 +680,7 @@ public class SimpleEvents { "\telse: #default message format", "\t\tset chat format to \"<orange>[player]<light gray>: <white>[message]\"" ) - .since("1.4.1"); + .since("1.4.1"); if (Skript.classExists("org.bukkit.event.world.LootGenerateEvent")) { Skript.registerEvent("Loot Generate", SimpleEvent.class, LootGenerateEvent.class, "loot generat(e|ing)") .description( @@ -709,6 +710,16 @@ public class SimpleEvents { .requiredPlugins("Paper 1.16+"); } + Skript.registerEvent("Inventory Drag", SimpleEvent.class, InventoryDragEvent.class, "inventory drag[ging]") + .description("Called when a player drags an item in their cursor across the inventory.") + .examples( + "on inventory drag:", + "\tif player's current inventory is {_gui}:", + "\t\tsend \"You can't drag your items here!\" to player", + "\t\tcancel event" + ) + .since("INSERT VERSION"); + } } From a5f0f4fc3bbe8633f154f12f1842734869dfcad3 Mon Sep 17 00:00:00 2001 From: sovdee <ethansovde@gmail.com> Date: Fri, 23 Jun 2023 19:53:05 -0700 Subject: [PATCH 343/619] Adds EffToggleCanPickUpItems + CondCanPickUpItems (#5357) --- .../skript/conditions/CondCanPickUpItems.java | 60 ++++++++++++++ .../effects/EffToggleCanPickUpItems.java | 79 +++++++++++++++++++ .../effects/EffToggleCanPickUpItems.sk | 7 ++ 3 files changed, 146 insertions(+) create mode 100644 src/main/java/ch/njol/skript/conditions/CondCanPickUpItems.java create mode 100644 src/main/java/ch/njol/skript/effects/EffToggleCanPickUpItems.java create mode 100644 src/test/skript/tests/syntaxes/effects/EffToggleCanPickUpItems.sk diff --git a/src/main/java/ch/njol/skript/conditions/CondCanPickUpItems.java b/src/main/java/ch/njol/skript/conditions/CondCanPickUpItems.java new file mode 100644 index 00000000000..d5a0aaaa591 --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondCanPickUpItems.java @@ -0,0 +1,60 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.conditions; + +import ch.njol.skript.conditions.base.PropertyCondition; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import org.bukkit.entity.LivingEntity; + +@Name("Can Pick Up Items") +@Description("Whether living entities are able to pick up items off the ground or not.") +@Examples({ + "if player can pick items up:", + "\tsend \"You can pick up items!\" to player", + "", + "on drop:", + "\tif player can't pick up items:", + "\t\tsend \"Be careful, you won't be able to pick that up!\" to player" +}) +@Since("INSERT VERSION") +public class CondCanPickUpItems extends PropertyCondition<LivingEntity> { + + static { + register(CondCanPickUpItems.class, PropertyType.CAN, "pick([ ]up items| items up)", "livingentities"); + } + + @Override + public boolean check(LivingEntity livingEntity) { + return livingEntity.getCanPickupItems(); + } + + @Override + protected PropertyType getPropertyType() { + return PropertyType.CAN; + } + + @Override + protected String getPropertyName() { + return "pick up items"; + } + +} diff --git a/src/main/java/ch/njol/skript/effects/EffToggleCanPickUpItems.java b/src/main/java/ch/njol/skript/effects/EffToggleCanPickUpItems.java new file mode 100644 index 00000000000..6c3b3926988 --- /dev/null +++ b/src/main/java/ch/njol/skript/effects/EffToggleCanPickUpItems.java @@ -0,0 +1,79 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.effects; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Toggle Picking Up Items") +@Description("Determines whether living entities are able to pick up items or not") +@Examples({ + "forbid player from picking up items", + "send \"You can no longer pick up items!\" to player", + "", + "on drop:", + "\tif player can't pick up items:", + "\t\tallow player to pick up items" +}) +@Since("INSERT VERSION") +public class EffToggleCanPickUpItems extends Effect { + + static { + Skript.registerEffect(EffToggleCanPickUpItems.class, + "allow %livingentities% to pick([ ]up items| items up)", + "(forbid|disallow) %livingentities% (from|to) pick([ing | ]up items|[ing] items up)"); + } + + private Expression<LivingEntity> entities; + private boolean allowPickUp; + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + entities = (Expression<LivingEntity>) exprs[0]; + allowPickUp = matchedPattern == 0; + return true; + } + + @Override + protected void execute(Event event) { + for (LivingEntity entity : entities.getArray(event)) { + entity.setCanPickupItems(allowPickUp); + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + if (allowPickUp) { + return "allow " + entities.toString(event, debug) + " to pick up items"; + } else { + return "forbid " + entities.toString(event, debug) + " from picking up items"; + } + } + +} diff --git a/src/test/skript/tests/syntaxes/effects/EffToggleCanPickUpItems.sk b/src/test/skript/tests/syntaxes/effects/EffToggleCanPickUpItems.sk new file mode 100644 index 00000000000..308a07261b3 --- /dev/null +++ b/src/test/skript/tests/syntaxes/effects/EffToggleCanPickUpItems.sk @@ -0,0 +1,7 @@ +test "entity can pick up items": + spawn a zombie at spawn of "world": + allow event-entity to pick up items + assert event-entity can pick up items with "failed to allow zombie to pick up items" + forbid event-entity from picking items up + assert event-entity can't pick up items with "failed to disallow zombie to pick up items" + delete event-entity From 1949a237ce4ffe7137896496864f033d614efae4 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Sat, 24 Jun 2023 23:43:23 +0300 Subject: [PATCH 344/619] =?UTF-8?q?=F0=9F=9A=80=20Add=20anvil=20repair=20c?= =?UTF-8?q?ost=20expression=20(#4617)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../expressions/ExprAnvilRepairCost.java | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprAnvilRepairCost.java diff --git a/src/main/java/ch/njol/skript/expressions/ExprAnvilRepairCost.java b/src/main/java/ch/njol/skript/expressions/ExprAnvilRepairCost.java new file mode 100644 index 00000000000..dd47b9af14f --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprAnvilRepairCost.java @@ -0,0 +1,119 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.bukkit.inventory.AnvilInventory; +import org.bukkit.inventory.Inventory; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Anvil Repair Cost") +@Description({ + "Returns the experience cost (in levels) to complete the current repair or the maximum experience cost (in levels) to be allowed by the current repair.", + "The default value of max cost set by vanilla Minecraft is 40." +}) +@Examples({ + "on inventory click:", + "\tif {AnvilRepairSaleActive} = true:", + "\t\twait a tick # recommended, to avoid client bugs", + "\t\tset anvil repair cost to anvil repair cost * 50%", + "\t\tsend \"Anvil repair sale is ON!\" to player", + "", + "on inventory click:", + "\tplayer have permission \"anvil.repair.max.bypass\"", + "\tset max repair cost of event-inventory to 99999" +}) +@Since("INSERT VERSION") +public class ExprAnvilRepairCost extends SimplePropertyExpression<Inventory, Integer> { + + static { + registerDefault(ExprAnvilRepairCost.class, Integer.class, "[anvil] [item] [:max[imum]] repair cost", "inventories"); + } + + private boolean isMax; + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + isMax = parseResult.hasTag("max"); + return super.init(exprs, matchedPattern, isDelayed, parseResult); + } + + @Override + @Nullable + public Integer convert(Inventory inventory) { + if (!(inventory instanceof AnvilInventory)) + return null; + + AnvilInventory anvilInventory = (AnvilInventory) inventory; + return isMax ? anvilInventory.getMaximumRepairCost() : anvilInventory.getRepairCost(); + } + + @Override + @Nullable + public Class<?>[] acceptChange(ChangeMode mode) { + switch (mode) { + case ADD: + case REMOVE: + case SET: + return CollectionUtils.array(Number.class); + default: + return null; + } + } + + @Override + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + int value = ((Number) delta[0]).intValue() * (mode == ChangeMode.REMOVE ? -1 : 1); + for (Inventory inventory : getExpr().getArray(event)) { + if (inventory instanceof AnvilInventory) { + AnvilInventory anvilInventory = (AnvilInventory) inventory; + int change = mode == ChangeMode.SET ? 0 : (isMax ? anvilInventory.getMaximumRepairCost() : anvilInventory.getRepairCost()); + int newValue = Math.max((change + value), 0); + + if (isMax) + anvilInventory.setMaximumRepairCost(newValue); + else + anvilInventory.setRepairCost(newValue); + } + } + } + + @Override + public Class<? extends Integer> getReturnType() { + return Integer.class; + } + + @Override + public String getPropertyName() { + return "anvil item" + (isMax ? " max" : "") + " repair cost"; + } + +} From 302f8f9ac7cac1d1a1a68ce85cb75fad4fafd4a1 Mon Sep 17 00:00:00 2001 From: sovdee <ethansovde@gmail.com> Date: Sun, 25 Jun 2023 00:01:30 -0700 Subject: [PATCH 345/619] Adds Clamp Function (#5573) --- .../skript/classes/data/DefaultFunctions.java | 53 ++++++++++++++----- .../skript/tests/syntaxes/functions/clamp.sk | 43 +++++++++++++++ 2 files changed, 84 insertions(+), 12 deletions(-) create mode 100644 src/test/skript/tests/syntaxes/functions/clamp.sk diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java b/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java index aeb0fe4735b..eee9206b0a0 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java @@ -18,19 +18,10 @@ */ package ch.njol.skript.classes.data; -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.util.Calendar; - -import ch.njol.skript.lang.function.FunctionEvent; -import ch.njol.skript.lang.function.JavaFunction; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.util.Vector; - import ch.njol.skript.expressions.base.EventValueExpression; +import ch.njol.skript.lang.function.FunctionEvent; import ch.njol.skript.lang.function.Functions; +import ch.njol.skript.lang.function.JavaFunction; import ch.njol.skript.lang.function.Parameter; import ch.njol.skript.lang.function.SimpleJavaFunction; import ch.njol.skript.lang.util.SimpleLiteral; @@ -41,8 +32,16 @@ import ch.njol.util.Math2; import ch.njol.util.StringUtils; import ch.njol.util.coll.CollectionUtils; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.util.Vector; import org.eclipse.jdt.annotation.Nullable; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.Calendar; + public class DefaultFunctions { private static String str(double n) { @@ -305,7 +304,37 @@ public Number[] executeSimple(Object[][] params) { }.description("Returns the minimum number from a list of numbers.") .examples("min(1) = 1", "min(1, 2, 3, 4) = 1", "min({some list variable::*})") .since("2.2")); - + + Functions.registerFunction(new SimpleJavaFunction<Number>("clamp", new Parameter[]{ + new Parameter<>("values", DefaultClasses.NUMBER, false, null), + new Parameter<>("min", DefaultClasses.NUMBER, true, null), + new Parameter<>("max", DefaultClasses.NUMBER, true, null) + }, DefaultClasses.NUMBER, false) { + @Override + public @Nullable Number[] executeSimple(Object[][] params) { + Number[] values = (Number[]) params[0]; + Double[] clampedValues = new Double[values.length]; + double min = ((Number) params[1][0]).doubleValue(); + double max = ((Number) params[2][0]).doubleValue(); + // we'll be nice and swap them if they're in the wrong order + double trueMin = Math.min(min, max); + double trueMax = Math.max(min, max); + for (int i = 0; i < values.length; i++) { + double value = values[i].doubleValue(); + clampedValues[i] = Math.max(Math.min(value, trueMax), trueMin); + } + return clampedValues; + } + }).description("Clamps one or more values between two numbers.") + .examples( + "clamp(5, 0, 10) = 5", + "clamp(5.5, 0, 5) = 5", + "clamp(0.25, 0, 0.5) = 0.25", + "clamp(5, 7, 10) = 7", + "clamp((5, 0, 10, 9, 13), 7, 10) = (7, 7, 10, 9, 10)", + "set {_clamped::*} to clamp({_values::*}, 0, 10)") + .since("INSERT VERSION"); + // misc Functions.registerFunction(new SimpleJavaFunction<World>("world", new Parameter[] { diff --git a/src/test/skript/tests/syntaxes/functions/clamp.sk b/src/test/skript/tests/syntaxes/functions/clamp.sk new file mode 100644 index 00000000000..98b9ad85dc5 --- /dev/null +++ b/src/test/skript/tests/syntaxes/functions/clamp.sk @@ -0,0 +1,43 @@ +test "clamp numbers": + # Normal Cases + assert clamp(1, 0, 2) is 1 with "(single ints) min < value < max" + assert clamp(1, 1, 2) is 1 with "(single ints) min = value < max" + assert clamp(2, 1, 2) is 2 with "(single ints) min < value = max" + assert clamp(0, 1, 2) is 1 with "(single ints) value < min < max" + assert clamp(3, 1, 2) is 2 with "(single ints) min < max < value" + assert clamp(3, 2, 1) is 2 with "(single ints) max < min < value" + + assert clamp(1.999, 0.0, 2.0) is 1.999 with "(single floats) min < value < max" + assert clamp(1.999, 1.999, 2.0) is 1.999 with "(single floats) min = value < max" + assert clamp(2.0, 1.999, 2.0) is 2.0 with "(single floats) min < value = max" + assert clamp(0.0, 1.999, 2.0) is 1.999 with "(single floats) value < min < max" + assert clamp(3.0, 1.999, 2.0) is 2.0 with "(single floats) min < max < value" + assert clamp(2.999, 2.0, 1.999) is 2.0 with "(single floats) max < min < value" + + # Lists + set {_expected::*} to (0, 0, 1, 2, 2, and 2) + # this is dumb but comparing the lists directly didn't work + set {_got::*} to clamp((-1, 0, 1, 2, 3, and 4), 0, 2) + loop {_expected::*}: + assert {_got::%loop-index%} is loop-value with "(multiple ints)" + set {_got::*} to clamp((-1.999, 0.0, 1.0, 2.0, 3.0, and 4.0), 0.0, 2.0) + loop {_expected::*}: + assert {_got::%loop-index%} is loop-value with "(multiple floats)" + + # Edge Cases + assert clamp(1, {_null}, 2) is not set with "(single ints) min = null" + assert clamp(2, 1, {_null}) is not set with "(single ints) max = null" + assert clamp({_null}, 1, 2) is not set with "(single ints) value = null" + assert clamp(1, 0, NaN value) is not clamp(1, 0, NaN value) with "(single ints) min < value < NaN" + assert clamp(1, NaN value, 2) is not clamp(1, NaN value, 2) with "(single ints) NaN < value < max" + assert clamp(NaN value, 1, 2) is not clamp(NaN value, 1, 2) with "(single ints) min < NaN < max" + assert clamp(infinity value, 1, 2) is 2 with "(single ints) min < infinity < max" + assert clamp(-infinity value, 1, 2) is 1 with "(single ints) min < -infinity < max" + assert clamp(1, 0, infinity value) is 1 with "(single ints) min < value < infinity" + assert clamp(1, -infinity value, 2) is 1 with "(single ints) -infinity < value < max" + + set {_expected::*} to (NaN value, 0.0, and 2.0) + set {_got::*} to clamp(({_null}, NaN value, -infinity value, infinity value), 0.0, 2.0) + assert number within {_got::1} is not number within {_got::1} with "(edge cases list) NaN" # need within because the variables weren't cooperating + assert {_got::2} is {_expected::2} with "(edge cases list) -infinity" + assert {_got::3} is {_expected::3} with "(edge cases list) infinity" From 2e5dc2ea2361f346dc6b42863f12fc216ef68156 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 25 Jun 2023 02:58:32 -0600 Subject: [PATCH 346/619] Bump io.papermc.paper:paper-api from 1.20-R0.1-SNAPSHOT to 1.20.1-R0.1-SNAPSHOT (#5746) * Bump io.papermc.paper:paper-api Bumps io.papermc.paper:paper-api from 1.20-R0.1-SNAPSHOT to 1.20.1-R0.1-SNAPSHOT. --- updated-dependencies: - dependency-name: io.papermc.paper:paper-api dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> * Update build.gradle * Update gradle.properties * Update and rename paper-1.20.json to paper-1.20.1.json --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> --- build.gradle | 4 ++-- gradle.properties | 2 +- .../java17/{paper-1.20.json => paper-1.20.1.json} | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) rename src/test/skript/environments/java17/{paper-1.20.json => paper-1.20.1.json} (85%) diff --git a/build.gradle b/build.gradle index f05c899d0b2..5920cb95ac8 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ dependencies { shadow group: 'org.bstats', name: 'bstats-bukkit', version: '3.0.2' shadow group: 'net.kyori', name: 'adventure-text-serializer-bungeecord', version: '4.3.0' - implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.20-R0.1-SNAPSHOT' + implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.20.1-R0.1-SNAPSHOT' implementation group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.700' implementation group: 'com.google.code.findbugs', name: 'findbugs', version: '3.0.1' implementation group: 'com.sk89q.worldguard', name: 'worldguard-legacy', version: '7.0.0-SNAPSHOT' @@ -254,7 +254,7 @@ void createTestTask(String name, String desc, String environments, int javaVersi } } -def latestEnv = 'java17/paper-1.20.json' +def latestEnv = 'java17/paper-1.20.1.json' def latestJava = 17 def oldestJava = 8 diff --git a/gradle.properties b/gradle.properties index 22cf891c6cb..815459f135e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,5 +2,5 @@ groupid=ch.njol name=skript version=2.7.0-beta3 jarName=Skript.jar -testEnv=java17/paper-1.20 +testEnv=java17/paper-1.20.1 testEnvJavaVersion=17 diff --git a/src/test/skript/environments/java17/paper-1.20.json b/src/test/skript/environments/java17/paper-1.20.1.json similarity index 85% rename from src/test/skript/environments/java17/paper-1.20.json rename to src/test/skript/environments/java17/paper-1.20.1.json index 73c81a4018c..3a117b97397 100644 --- a/src/test/skript/environments/java17/paper-1.20.json +++ b/src/test/skript/environments/java17/paper-1.20.1.json @@ -1,11 +1,11 @@ { - "name": "paper-1.20", + "name": "paper-1.20.1", "resources": [ {"source": "server.properties.generic", "target": "server.properties"} ], "paperDownloads": [ { - "version": "1.20", + "version": "1.20.1", "target": "paperclip.jar" } ], From ce1888c4b5435ff0fa13e5fd8072c8da0c79fc56 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Sun, 25 Jun 2023 18:10:47 -0600 Subject: [PATCH 347/619] Add quit reason (#5763) * Add quit reason * Cleanup * Versioning --- .../skript/classes/data/BukkitClasses.java | 68 ++++++++++-------- .../classes/data/BukkitEventValues.java | 70 +++++++++++-------- .../skript/expressions/ExprQuitReason.java | 69 ++++++++++++++++++ src/main/resources/lang/default.lang | 8 +++ 4 files changed, 157 insertions(+), 58 deletions(-) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprQuitReason.java 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 ccf12240d29..5caa14f02e2 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -62,6 +62,7 @@ import org.bukkit.event.inventory.ClickType; import org.bukkit.event.inventory.InventoryAction; import org.bukkit.event.inventory.InventoryType; +import org.bukkit.event.player.PlayerQuitEvent.QuitReason; import org.bukkit.event.player.PlayerResourcePackStatusEvent.Status; import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; import org.bukkit.inventory.Inventory; @@ -1458,29 +1459,29 @@ public String toVariableNameString(GameRule o) { ); Classes.registerClass(new ClassInfo<>(EnchantmentOffer.class, "enchantmentoffer") - .user("enchant[ment][ ]offers?") - .name("Enchantment Offer") - .description("The enchantmentoffer in an enchant prepare event.") - .examples("on enchant prepare:", - "\tset enchant offer 1 to sharpness 1", - "\tset the cost of enchant offer 1 to 10 levels") - .since("2.5") - .parser(new Parser<EnchantmentOffer>() { - @Override - public boolean canParse(ParseContext context) { - return false; - } - - @Override - public String toString(EnchantmentOffer eo, int flags) { - return EnchantmentType.toString(eo.getEnchantment(), flags) + " " + eo.getEnchantmentLevel(); - } - - @Override - public String toVariableNameString(EnchantmentOffer eo) { - return "offer:" + EnchantmentType.toString(eo.getEnchantment()) + "=" + eo.getEnchantmentLevel(); - } - })); + .user("enchant[ment][ ]offers?") + .name("Enchantment Offer") + .description("The enchantmentoffer in an enchant prepare event.") + .examples("on enchant prepare:", + "\tset enchant offer 1 to sharpness 1", + "\tset the cost of enchant offer 1 to 10 levels") + .since("2.5") + .parser(new Parser<EnchantmentOffer>() { + @Override + public boolean canParse(ParseContext context) { + return false; + } + + @Override + public String toString(EnchantmentOffer eo, int flags) { + return EnchantmentType.toString(eo.getEnchantment(), flags) + " " + eo.getEnchantmentLevel(); + } + + @Override + public String toVariableNameString(EnchantmentOffer eo) { + return "offer:" + EnchantmentType.toString(eo.getEnchantment()) + "=" + eo.getEnchantmentLevel(); + } + })); Classes.registerClass(new EnumClassInfo<>(Attribute.class, "attributetype", "attribute types") .user("attribute ?types?") @@ -1495,13 +1496,20 @@ public String toVariableNameString(EnchantmentOffer eo) { .description("Represents the environment of a world.") .since("2.7")); - if (Skript.classExists("io.papermc.paper.world.MoonPhase")) { + if (Skript.classExists("io.papermc.paper.world.MoonPhase")) Classes.registerClass(new EnumClassInfo<>(MoonPhase.class, "moonphase", "moon phases") - .user("(lunar|moon) ?phases?") - .name("Moon Phase") - .description("Represents the phase of a moon.") - .since("2.7") - .requiredPlugins("Paper 1.16+")); - } + .user("(lunar|moon) ?phases?") + .name("Moon Phase") + .description("Represents the phase of a moon.") + .requiredPlugins("Paper 1.16+") + .since("2.7")); + + if (Skript.classExists("org.bukkit.event.player.PlayerQuitEvent$QuitReason")) + Classes.registerClass(new EnumClassInfo<>(QuitReason.class, "quitreason", "quit reasons") + .user("(quit|disconnect) ?(reason|cause)s?") + .name("Quit Reason") + .description("Represents a quit reason from a player quit server event.") + .requiredPlugins("Paper 1.16.5+") + .since("INSERT VERSION")); } } 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 b628c56a887..9bf77216c51 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -18,40 +18,24 @@ */ package ch.njol.skript.classes.data; -import ch.njol.skript.Skript; -import ch.njol.skript.aliases.Aliases; -import ch.njol.skript.aliases.ItemType; -import ch.njol.skript.command.CommandEvent; -import ch.njol.skript.events.bukkit.ScriptEvent; -import ch.njol.skript.events.bukkit.SkriptStartEvent; -import ch.njol.skript.events.bukkit.SkriptStopEvent; -import ch.njol.skript.registrations.EventValues; -import ch.njol.skript.util.BlockStateBlock; -import ch.njol.skript.util.BlockUtils; -import ch.njol.skript.util.DelayedChangeBlock; -import ch.njol.skript.util.Direction; -import ch.njol.skript.util.EnchantmentType; -import ch.njol.skript.util.Getter; -import ch.njol.skript.util.slot.InventorySlot; -import ch.njol.skript.util.slot.Slot; -import com.destroystokyo.paper.event.block.AnvilDamagedEvent; -import com.destroystokyo.paper.event.entity.ProjectileCollideEvent; -import com.destroystokyo.paper.event.player.PlayerArmorChangeEvent; -import io.papermc.paper.event.entity.EntityMoveEvent; -import io.papermc.paper.event.player.PlayerInventorySlotChangeEvent; -import io.papermc.paper.event.player.PlayerTradeEvent; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.FireworkEffect; +import org.bukkit.GameMode; import org.bukkit.Keyed; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; -import org.bukkit.GameMode; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.block.BlockState; import org.bukkit.command.CommandSender; +import org.bukkit.entity.AbstractVillager; import org.bukkit.entity.Egg; import org.bukkit.entity.Entity; import org.bukkit.entity.Firework; @@ -62,7 +46,6 @@ import org.bukkit.entity.Player; import org.bukkit.entity.Projectile; import org.bukkit.entity.Vehicle; -import org.bukkit.entity.AbstractVillager; import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.block.BlockCanBuildEvent; import org.bukkit.event.block.BlockDamageEvent; @@ -127,6 +110,8 @@ import org.bukkit.event.player.PlayerItemMendEvent; import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerPickupItemEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerQuitEvent.QuitReason; import org.bukkit.event.player.PlayerRiptideEvent; import org.bukkit.event.player.PlayerShearEntityEvent; import org.bukkit.event.player.PlayerTeleportEvent; @@ -154,10 +139,29 @@ import org.bukkit.potion.PotionEffectType; import org.eclipse.jdt.annotation.Nullable; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import com.destroystokyo.paper.event.block.AnvilDamagedEvent; +import com.destroystokyo.paper.event.entity.ProjectileCollideEvent; +import com.destroystokyo.paper.event.player.PlayerArmorChangeEvent; + +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.Aliases; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.command.CommandEvent; +import ch.njol.skript.events.bukkit.ScriptEvent; +import ch.njol.skript.events.bukkit.SkriptStartEvent; +import ch.njol.skript.events.bukkit.SkriptStopEvent; +import ch.njol.skript.registrations.EventValues; +import ch.njol.skript.util.BlockStateBlock; +import ch.njol.skript.util.BlockUtils; +import ch.njol.skript.util.DelayedChangeBlock; +import ch.njol.skript.util.Direction; +import ch.njol.skript.util.EnchantmentType; +import ch.njol.skript.util.Getter; +import ch.njol.skript.util.slot.InventorySlot; +import ch.njol.skript.util.slot.Slot; +import io.papermc.paper.event.entity.EntityMoveEvent; +import io.papermc.paper.event.player.PlayerInventorySlotChangeEvent; +import io.papermc.paper.event.player.PlayerTradeEvent; /** * @author Peter Güttinger @@ -1644,5 +1648,15 @@ public Location get(LootGenerateEvent event) { } }, EventValues.TIME_NOW); } + + //PlayerQuitEvent + if (Skript.classExists("org.bukkit.event.player.PlayerQuitEvent$QuitReason")) + EventValues.registerEventValue(PlayerQuitEvent.class, QuitReason.class, new Getter<QuitReason, PlayerQuitEvent>() { + @Override + @Nullable + public QuitReason get(PlayerQuitEvent event) { + return event.getReason(); + } + }, EventValues.TIME_NOW); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprQuitReason.java b/src/main/java/ch/njol/skript/expressions/ExprQuitReason.java new file mode 100644 index 00000000000..6d5fd5cf751 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprQuitReason.java @@ -0,0 +1,69 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import org.bukkit.event.Event; +import org.bukkit.event.player.PlayerQuitEvent.QuitReason; +import org.eclipse.jdt.annotation.Nullable; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.EventValueExpression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.registrations.EventValues; + +@Name("Quit Reason") +@Description("The <a href='classes.html#quitreason'>quit reason</a> as to why a player disconnected in a <a href='events.html#quit'>quit</a> event.") +@Examples({ + "on quit:", + "\tquit reason was kicked", + "\tplayer is banned", + "\tclear {server::player::%uuid of player%::*}" +}) +@RequiredPlugins("Paper 1.16.5+") +@Since("INSERT VERSION") +public class ExprQuitReason extends EventValueExpression<QuitReason> { + + static { + if (Skript.classExists("org.bukkit.event.player.PlayerQuitEvent$QuitReason")) + Skript.registerExpression(ExprQuitReason.class, QuitReason.class, ExpressionType.SIMPLE, "[the] (quit|disconnect) (cause|reason)"); + } + + public ExprQuitReason() { + super(QuitReason.class); + } + + // Allow for 'the quit reason was ...' as that's proper grammar support for this event value. + @Override + public boolean setTime(int time) { + if (time == EventValues.TIME_FUTURE) + return super.setTime(time); + return true; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "quit reason"; + } + +} diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index e86820ed0c2..02909f887b2 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -1932,6 +1932,13 @@ moon phases: waxing_crescent: waxing crescent waxing_gibbous: waxing gibbous +# -- Quit Reasons -- +quit reasons: + disconnected: disconnected, quit + erroneous_state: erroneous, erroneous state + kicked: kicked + timed_out: timed out + # -- Boolean -- boolean: true: @@ -2000,6 +2007,7 @@ types: resourcepackstate: resource pack state¦s @a gene: panda gene¦s @a gamerulevalue: gamerule value¦s @a + quitreason: quit reason¦s @a # Skript weathertype: weather type¦s @a From 9d06f0206557c6b47f2dda81ccc0d2a9a34c1f34 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Mon, 26 Jun 2023 04:17:49 +0300 Subject: [PATCH 348/619] =?UTF-8?q?=F0=9F=9A=80=20Add=20`loop-counter`=20&?= =?UTF-8?q?=20Improve=20loops=20(#4595)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ch/njol/skript/effects/EffContinue.java | 48 ++++---- .../java/ch/njol/skript/effects/EffExit.java | 72 ++++++----- .../ch/njol/skript/effects/EffReturn.java | 56 ++++----- .../skript/expressions/ExprLoopIteration.java | 115 ++++++++++++++++++ .../skript/expressions/ExprLoopValue.java | 55 +++++---- .../java/ch/njol/skript/lang/LoopSection.java | 57 +++++++++ .../java/ch/njol/skript/sections/SecLoop.java | 36 +++--- .../ch/njol/skript/sections/SecWhile.java | 32 ++--- .../tests/regressions/4595-loop-iteration.sk | 28 +++++ 9 files changed, 359 insertions(+), 140 deletions(-) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprLoopIteration.java create mode 100644 src/main/java/ch/njol/skript/lang/LoopSection.java create mode 100644 src/test/skript/tests/regressions/4595-loop-iteration.sk diff --git a/src/main/java/ch/njol/skript/effects/EffContinue.java b/src/main/java/ch/njol/skript/effects/EffContinue.java index 263ffb39059..026ca015d86 100644 --- a/src/main/java/ch/njol/skript/effects/EffContinue.java +++ b/src/main/java/ch/njol/skript/effects/EffContinue.java @@ -25,25 +25,33 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Effect; import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.LoopSection; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.TriggerItem; -import ch.njol.skript.lang.TriggerSection; -import ch.njol.skript.lang.parser.ParserInstance; -import ch.njol.skript.sections.SecLoop; -import ch.njol.skript.sections.SecWhile; import ch.njol.util.Kleenean; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; import java.util.List; -import java.util.stream.Collectors; @Name("Continue") -@Description("Skips the value currently being looped, moving on to the next value if it exists.") -@Examples({"loop all players:", +@Description("Immediately moves the (while) loop on to the next iteration.") +@Examples({ + "# Broadcast online moderators", + "loop all players:", "\tif loop-value does not have permission \"moderator\":", - "\t\tcontinue # filter out non moderators", - "\tbroadcast \"%loop-player% is a moderator!\" # Only moderators get broadcast"}) + "\t\tcontinue # filter out non moderators", + "\tbroadcast \"%loop-player% is a moderator!\" # Only moderators get broadcast", + " ", + "# Game starting counter", + "set {_counter} to 11", + "while {_counter} > 0:", + "\tremove 1 from {_counter}", + "\twait a second", + "\tif {_counter} != 1, 2, 3, 5 or 10:", + "\t\tcontinue # only print when counter is 1, 2, 3, 5 or 10", + "\tbroadcast \"Game starting in %{_counter}% second(s)\"", +}) @Since("2.2-dev37, 2.7 (while loops)") public class EffContinue extends Effect { @@ -52,36 +60,34 @@ public class EffContinue extends Effect { } @SuppressWarnings("NotNullFieldNotInitialized") - private TriggerSection section; + private LoopSection loop; @Override public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - List<TriggerSection> currentSections = ParserInstance.get().getCurrentSections().stream() - .filter(s -> s instanceof SecLoop || s instanceof SecWhile) - .collect(Collectors.toList()); + List<LoopSection> currentLoops = getParser().getCurrentSections(LoopSection.class); - if (currentSections.isEmpty()) { - Skript.error("Continue may only be used in while or loops"); + if (currentLoops.isEmpty()) { + Skript.error("The 'continue' effect may only be used in while and regular loops"); return false; } - section = currentSections.get(currentSections.size() - 1); + loop = currentLoops.get(currentLoops.size() - 1); return true; } @Override - protected void execute(Event e) { + protected void execute(Event event) { throw new UnsupportedOperationException(); } - @Nullable @Override - protected TriggerItem walk(Event e) { - return section; + @Nullable + protected TriggerItem walk(Event event) { + return loop; } @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { return "continue"; } diff --git a/src/main/java/ch/njol/skript/effects/EffExit.java b/src/main/java/ch/njol/skript/effects/EffExit.java index 64f7fab4cdb..eecbb7efec1 100644 --- a/src/main/java/ch/njol/skript/effects/EffExit.java +++ b/src/main/java/ch/njol/skript/effects/EffExit.java @@ -25,50 +25,51 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Effect; import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.LoopSection; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.TriggerItem; import ch.njol.skript.lang.TriggerSection; import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.skript.log.ErrorQuality; import ch.njol.skript.sections.SecConditional; -import ch.njol.skript.sections.SecLoop; -import ch.njol.skript.sections.SecWhile; import ch.njol.util.Kleenean; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; import java.util.List; -/** - * @author Peter Güttinger - */ @Name("Exit") @Description("Exits a given amount of loops and conditionals, or the entire trigger.") -@Examples({"if player has any ore:", - " stop", - "message \"%player% has no ores!\"", - "loop blocks above the player:", - " loop-block is not air:", - " exit 2 sections", - " set loop-block to water"}) +@Examples({ + "if player has any ore:", + "\tstop", + "message \"%player% has no ores!\"", + "loop blocks above the player:", + "\tloop-block is not air:", + "\t\texit 2 sections", + "\tset loop-block to water" +}) @Since("<i>unknown</i> (before 2.1)") public class EffExit extends Effect { // TODO [code style] warn user about code after a stop effect + static { Skript.registerEffect(EffExit.class, "(exit|stop) [trigger]", - "(exit|stop) [(1|a|the|this)] (0¦section|1¦loop|2¦conditional)", - "(exit|stop) <\\d+> (0¦section|1¦loop|2¦conditional)s", - "(exit|stop) all (0¦section|1¦loop|2¦conditional)s"); + "(exit|stop) [(1|a|the|this)] (section|1:loop|2:conditional)", + "(exit|stop) <\\d+> (section|1:loop|2:conditional)s", + "(exit|stop) all (section|1:loop|2:conditional)s"); } private int breakLevels; - private final static int EVERYTHING = 0, LOOPS = 1, CONDITIONALS = 2; - private final static String[] names = {"sections", "loops", "conditionals"}; + private static final int EVERYTHING = 0; + private static final int LOOPS = 1; + private static final int CONDITIONALS = 2; + private static final String[] names = {"sections", "loops", "conditionals"}; private int type; @Override - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parser) { + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) { switch (matchedPattern) { case 0: breakLevels = getParser().getCurrentSections().size() + 1; @@ -102,44 +103,41 @@ private static int numLevels(int type) { List<TriggerSection> currentSections = ParserInstance.get().getCurrentSections(); if (type == EVERYTHING) return currentSections.size(); - int r = 0; - for (TriggerSection s : currentSections) { - if (type == CONDITIONALS ? s instanceof SecConditional : s instanceof SecLoop || s instanceof SecWhile) - r++; + int level = 0; + for (TriggerSection section : currentSections) { + if (type == CONDITIONALS ? section instanceof SecConditional : section instanceof LoopSection) + level++; } - return r; + return level; } @Override @Nullable - protected TriggerItem walk(final Event e) { - debug(e, false); - TriggerItem n = this; + protected TriggerItem walk(Event event) { + debug(event, false); + TriggerItem node = this; for (int i = breakLevels; i > 0;) { - n = n.getParent(); - if (n == null) { + node = node.getParent(); + if (node == null) { assert false : this; return null; } - if (n instanceof SecLoop) { - ((SecLoop) n).exit(e); - } else if (n instanceof SecWhile) { - ((SecWhile) n).reset(); - } + if (node instanceof LoopSection) + ((LoopSection) node).exit(event); - if (type == EVERYTHING || type == CONDITIONALS && n instanceof SecConditional || type == LOOPS && (n instanceof SecLoop || n instanceof SecWhile)) + if (type == EVERYTHING || type == CONDITIONALS && node instanceof SecConditional || type == LOOPS && (node instanceof LoopSection)) i--; } - return n instanceof SecLoop ? ((SecLoop) n).getActualNext() : n instanceof SecWhile ? ((SecWhile) n).getActualNext() : n.getNext(); + return node instanceof LoopSection ? ((LoopSection) node).getActualNext() : node.getNext(); } @Override - protected void execute(final Event e) { + protected void execute(Event event) { assert false; } @Override - public String toString(final @Nullable Event e, final boolean debug) { + public String toString(@Nullable Event event, boolean debug) { return "stop " + breakLevels + " " + names[type]; } diff --git a/src/main/java/ch/njol/skript/effects/EffReturn.java b/src/main/java/ch/njol/skript/effects/EffReturn.java index 20cf0bdfb0e..2240c509767 100644 --- a/src/main/java/ch/njol/skript/effects/EffReturn.java +++ b/src/main/java/ch/njol/skript/effects/EffReturn.java @@ -26,6 +26,7 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Effect; import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.LoopSection; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.TriggerItem; import ch.njol.skript.lang.TriggerSection; @@ -34,19 +35,16 @@ import ch.njol.skript.lang.function.ScriptFunction; import ch.njol.skript.log.RetainingLogHandler; import ch.njol.skript.log.SkriptLogger; -import ch.njol.skript.sections.SecLoop; -import ch.njol.skript.sections.SecWhile; import ch.njol.util.Kleenean; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; -/** - * @author Peter Güttinger - */ @Name("Return") @Description("Makes a function return a value") -@Examples({"function double(i: number) :: number:", - " return 2 * {_i}"}) +@Examples({ + "function double(i: number) :: number:", + "\treturn 2 * {_i}" +}) @Since("2.2") public class EffReturn extends Effect { @@ -75,18 +73,18 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye } function = f; - ClassInfo<?> rt = function.getReturnType(); - if (rt == null) { + ClassInfo<?> returnType = function.getReturnType(); + if (returnType == null) { Skript.error("This function doesn't return any value. Please use 'stop' or 'exit' if you want to stop the function."); return false; } RetainingLogHandler log = SkriptLogger.startRetainingLog(); - Expression<?> v; + Expression<?> convertedExpr; try { - v = exprs[0].getConvertedExpression(rt.getC()); - if (v == null) { - log.printErrors("This function is declared to return " + rt.getName().withIndefiniteArticle() + ", but " + exprs[0].toString(null, false) + " is not of that type."); + convertedExpr = exprs[0].getConvertedExpression(returnType.getC()); + 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."); return false; } log.printLog(); @@ -94,33 +92,31 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye log.stop(); } - if (f.isSingle() && !v.isSingle()) { - Skript.error("This function is defined to only return a single " + rt.toString() + ", but this return statement can return multiple values."); + 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."); return false; } - value = v; + value = convertedExpr; return true; } - @SuppressWarnings({"unchecked", "rawtypes"}) @Override @Nullable - protected TriggerItem walk(final Event e) { - debug(e, false); - if (e instanceof FunctionEvent) { - ((ScriptFunction) function).setReturnValue(value.getArray(e)); + @SuppressWarnings({"unchecked", "rawtypes"}) + protected TriggerItem walk(Event event) { + debug(event, false); + if (event instanceof FunctionEvent) { + ((ScriptFunction) function).setReturnValue(value.getArray(event)); } else { - assert false : e; + assert false : event; } TriggerSection parent = getParent(); while (parent != null) { - if (parent instanceof SecLoop) { - ((SecLoop) parent).exit(e); - } else if (parent instanceof SecWhile) { - ((SecWhile) parent).reset(); - } + if (parent instanceof LoopSection) + ((LoopSection) parent).exit(event); + parent = parent.getParent(); } @@ -128,13 +124,13 @@ protected TriggerItem walk(final Event e) { } @Override - protected void execute(Event e) { + protected void execute(Event event) { assert false; } @Override - public String toString(@Nullable Event e, boolean debug) { - return "return " + value.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "return " + value.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprLoopIteration.java b/src/main/java/ch/njol/skript/expressions/ExprLoopIteration.java new file mode 100644 index 00000000000..b95471a2cba --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprLoopIteration.java @@ -0,0 +1,115 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.LoopSection; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.TriggerSection; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Loop Iteration") +@Description("Returns the loop's current iteration count (for both normal and while loops).") +@Examples({ + "while player is online:", + "\tgive player 1 stone", + "\twait 5 ticks", + "\tif loop-counter > 30:", + "\t\tstop loop", + "", + "loop {top-balances::*}:", + "\tif loop-iteration <= 10:", + "\t\tbroadcast \"##%loop-iteration% %loop-index% has $%loop-value%\"", +}) +@Since("INSERT VERSION") +public class ExprLoopIteration extends SimpleExpression<Long> { + + static { + Skript.registerExpression(ExprLoopIteration.class, Long.class, ExpressionType.SIMPLE, "[the] loop(-| )(counter|iteration)[-%-*number%]"); + } + + @SuppressWarnings("NotNullFieldNotInitialized") + private LoopSection loop; + + private int loopNumber; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + loopNumber = -1; + if (exprs[0] != null) + loopNumber = ((Literal<Number>) exprs[0]).getSingle().intValue(); + + int i = 1; + LoopSection loop = null; + + for (LoopSection l : getParser().getCurrentSections(LoopSection.class)) { + if (i < loopNumber) { + i++; + continue; + } + if (loop != null) { + Skript.error("There are multiple loops. Use loop-iteration-1/2/3/etc. to specify which loop-iteration you want."); + return false; + } + loop = l; + if (i == loopNumber) + break; + } + + if (loop == null) { + Skript.error("The loop iteration expression must be used in a loop"); + return false; + } + + this.loop = loop; + return true; + } + + @Override + protected Long[] get(Event event) { + return new Long[]{loop.getLoopCounter(event)}; + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class<? extends Long> getReturnType() { + return Long.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "loop-iteration" + (loopNumber != -1 ? ("-" + loopNumber) : ""); + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprLoopValue.java b/src/main/java/ch/njol/skript/expressions/ExprLoopValue.java index 4ef78dd68c6..5767d6f3352 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprLoopValue.java +++ b/src/main/java/ch/njol/skript/expressions/ExprLoopValue.java @@ -31,7 +31,6 @@ import ch.njol.skript.lang.Variable; import ch.njol.skript.lang.util.ConvertedExpression; import ch.njol.skript.lang.util.SimpleExpression; -import ch.njol.skript.log.ErrorQuality; import ch.njol.skript.registrations.Classes; import org.skriptlang.skript.lang.converter.Converters; import ch.njol.skript.sections.SecLoop; @@ -47,60 +46,69 @@ /** * Used to access a loop's current value. - * <p> - * TODO expression to get the current # of execution (e.g. loop-index/number/count/etc (not number though)); - * - * @author Peter Güttinger */ @Name("Loop value") -@Description("The currently looped value.") -@Examples({"# countdown:", - "loop 10 times:", - " message \"%11 - loop-number%\"", - " wait a second", - "# generate a 10x10 floor made of randomly colored wool below the player:", - "loop blocks from the block below the player to the block 10 east of the block below the player:", - " loop blocks from the loop-block to the block 10 north of the loop-block:", - " set loop-block-2 to any wool"}) -@Since("1.0") +@Description("Returns the currently looped value.") +@Examples({ + "# Countdown", + "loop 10 times:", + "\tmessage \"%11 - loop-number%\"", + "\twait a second", + "", + "# Generate a 10x10 floor made of randomly colored wool below the player", + "loop blocks from the block below the player to the block 10 east of the block below the player:", + "\tloop blocks from the loop-block to the block 10 north of the loop-block:", + "\t\tset loop-block-2 to any wool", + "", + "loop {top-balances::*}:", + "\tloop-iteration <= 10", + "\tsend \"##%loop-iteration% %loop-index% has $%loop-value%\"", +}) +@Since("1.0, INSERT VERSION (loop-counter)") public class ExprLoopValue extends SimpleExpression<Object> { static { Skript.registerExpression(ExprLoopValue.class, Object.class, ExpressionType.SIMPLE, "[the] loop-<.+>"); } - @SuppressWarnings("null") + @SuppressWarnings("NotNullFieldNotInitialized") private String name; - @SuppressWarnings("null") + @SuppressWarnings("NotNullFieldNotInitialized") private SecLoop loop; // whether this loops a variable boolean isVariableLoop = false; // if this loops a variable and isIndex is true, return the index of the variable instead of the value boolean isIndex = false; - + + private static final Pattern LOOP_PATTERN = Pattern.compile("^(.+)-(\\d+)$"); + @Override public boolean init(Expression<?>[] vars, int matchedPattern, Kleenean isDelayed, ParseResult parser) { name = parser.expr; String s = "" + parser.regexes.get(0).group(); int i = -1; - Matcher m = Pattern.compile("^(.+)-(\\d+)$").matcher(s); + Matcher m = LOOP_PATTERN.matcher(s); if (m.matches()) { s = "" + m.group(1); i = Utils.parseInt("" + m.group(2)); } + + if ("counter".equalsIgnoreCase(s) || "iteration".equalsIgnoreCase(s)) // ExprLoopIteration - in case of classinfo conflicts + return false; + Class<?> c = Classes.getClassFromUserInput(s); int j = 1; SecLoop loop = null; for (SecLoop l : getParser().getCurrentSections(SecLoop.class)) { - if ((c != null && c.isAssignableFrom(l.getLoopedExpression().getReturnType())) || "value".equals(s) || l.getLoopedExpression().isLoopOf(s)) { + if ((c != null && c.isAssignableFrom(l.getLoopedExpression().getReturnType())) || "value".equalsIgnoreCase(s) || l.getLoopedExpression().isLoopOf(s)) { if (j < i) { j++; continue; } if (loop != null) { - Skript.error("There are multiple loops that match loop-" + s + ". Use loop-" + s + "-1/2/3/etc. to specify which loop's value you want.", ErrorQuality.SEMANTIC_ERROR); + Skript.error("There are multiple loops that match loop-" + s + ". Use loop-" + s + "-1/2/3/etc. to specify which loop's value you want."); return false; } loop = l; @@ -109,7 +117,7 @@ public boolean init(Expression<?>[] vars, int matchedPattern, Kleenean isDelayed } } if (loop == null) { - Skript.error("There's no loop that matches 'loop-" + s + "'", ErrorQuality.SEMANTIC_ERROR); + Skript.error("There's no loop that matches 'loop-" + s + "'"); return false; } if (loop.getLoopedExpression() instanceof Variable) { @@ -126,9 +134,9 @@ public boolean isSingle() { return true; } - @SuppressWarnings("unchecked") @Override @Nullable + @SuppressWarnings("unchecked") protected <R> ConvertedExpression<Object, ? extends R> getConvertedExpr(Class<R>... to) { if (isVariableLoop && !isIndex) { Class<R> superType = (Class<R>) Utils.getSuperType(to); @@ -165,6 +173,7 @@ protected Object[] get(Event e) { one[0] = current.getValue(); return one; } + Object[] one = (Object[]) Array.newInstance(getReturnType(), 1); one[0] = loop.getCurrent(e); return one; diff --git a/src/main/java/ch/njol/skript/lang/LoopSection.java b/src/main/java/ch/njol/skript/lang/LoopSection.java new file mode 100644 index 00000000000..f733ad2eaaf --- /dev/null +++ b/src/main/java/ch/njol/skript/lang/LoopSection.java @@ -0,0 +1,57 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.lang; + +import org.bukkit.event.Event; + +import java.util.Map; +import java.util.WeakHashMap; + +/** + * Represents a loop section. + * + * @see ch.njol.skript.sections.SecWhile + * @see ch.njol.skript.sections.SecLoop + */ +public abstract class LoopSection extends Section implements SyntaxElement, Debuggable { + + protected final transient Map<Event, Long> currentLoopCounter = new WeakHashMap<>(); + + /** + * @param event The event where the loop is used to return its loop iterations + * @return The loop iteration number + */ + public long getLoopCounter(Event event) { + return currentLoopCounter.getOrDefault(event, 1L); + } + + /** + * @return The next {@link TriggerItem} after the loop + */ + public abstract TriggerItem getActualNext(); + + /** + * Exit the loop, used to reset the loop properties such as iterations counter + * @param event The event where the loop is used to reset its relevant properties + */ + public void exit(Event event) { + currentLoopCounter.remove(event); + } + +} diff --git a/src/main/java/ch/njol/skript/sections/SecLoop.java b/src/main/java/ch/njol/skript/sections/SecLoop.java index 1b3826f81ce..8f0682d2bea 100644 --- a/src/main/java/ch/njol/skript/sections/SecLoop.java +++ b/src/main/java/ch/njol/skript/sections/SecLoop.java @@ -26,7 +26,7 @@ import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.Section; +import ch.njol.skript.lang.LoopSection; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.TriggerItem; import ch.njol.skript.lang.Variable; @@ -80,7 +80,7 @@ "the player and loop-value is the actually coins value such as 200" }) @Since("1.0") -public class SecLoop extends Section { +public class SecLoop extends LoopSection { static { Skript.registerSection(SecLoop.class, "loop %objects%"); @@ -96,6 +96,7 @@ public class SecLoop extends Section { private TriggerItem actualNext; @Override + @SuppressWarnings("unchecked") public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, @@ -116,7 +117,7 @@ public boolean init(Expression<?>[] exprs, } if (expr.isSingle()) { - Skript.error("Can't loop " + expr + " because it's only a single value"); + Skript.error("Can't loop '" + expr + "' because it's only a single value"); return false; } @@ -128,35 +129,36 @@ public boolean init(Expression<?>[] exprs, @Override @Nullable - protected TriggerItem walk(Event e) { - Iterator<?> iter = currentIter.get(e); + protected TriggerItem walk(Event event) { + Iterator<?> iter = currentIter.get(event); if (iter == null) { - iter = expr instanceof Variable ? ((Variable<?>) expr).variablesIterator(e) : expr.iterator(e); + iter = expr instanceof Variable ? ((Variable<?>) expr).variablesIterator(event) : expr.iterator(event); if (iter != null) { if (iter.hasNext()) - currentIter.put(e, iter); + currentIter.put(event, iter); else iter = null; } } if (iter == null || !iter.hasNext()) { - exit(e); - debug(e, false); + exit(event); + debug(event, false); return actualNext; } else { - current.put(e, iter.next()); - return walk(e, true); + current.put(event, iter.next()); + currentLoopCounter.put(event, (currentLoopCounter.getOrDefault(event, 0L)) + 1); + return walk(event, true); } } @Override - public String toString(@Nullable Event e, boolean debug) { - return "loop " + expr.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "loop " + expr.toString(event, debug); } @Nullable - public Object getCurrent(Event e) { - return current.get(e); + public Object getCurrent(Event event) { + return current.get(event); } public Expression<?> getLoopedExpression() { @@ -170,12 +172,16 @@ public SecLoop setNext(@Nullable TriggerItem next) { } @Nullable + @Override public TriggerItem getActualNext() { return actualNext; } + @Override public void exit(Event event) { current.remove(event); currentIter.remove(event); + super.exit(event); } + } diff --git a/src/main/java/ch/njol/skript/sections/SecWhile.java b/src/main/java/ch/njol/skript/sections/SecWhile.java index 102d6b620f3..18b7c42634a 100644 --- a/src/main/java/ch/njol/skript/sections/SecWhile.java +++ b/src/main/java/ch/njol/skript/sections/SecWhile.java @@ -26,7 +26,7 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Condition; import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.Section; +import ch.njol.skript.lang.LoopSection; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.TriggerItem; import ch.njol.util.Kleenean; @@ -34,6 +34,8 @@ import org.eclipse.jdt.annotation.Nullable; import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; @Name("While Loop") @Description("While Loop sections are loops that will just keep repeating as long as a condition is met.") @@ -54,10 +56,10 @@ "\twait 1 second # without using a delay effect the server will crash", }) @Since("2.0, 2.6 (do while)") -public class SecWhile extends Section { +public class SecWhile extends LoopSection { static { - Skript.registerSection(SecWhile.class, "[(1¦do)] while <.+>"); + Skript.registerSection(SecWhile.class, "[(:do)] while <.+>"); } @SuppressWarnings("NotNullFieldNotInitialized") @@ -81,23 +83,23 @@ public boolean init(Expression<?>[] exprs, condition = Condition.parse(expr, "Can't understand this condition: " + expr); if (condition == null) return false; - doWhile = parseResult.mark == 1; - loadOptionalCode(sectionNode); + doWhile = parseResult.hasTag("do"); + loadOptionalCode(sectionNode); super.setNext(this); - return true; } @Nullable @Override - protected TriggerItem walk(Event e) { - if ((doWhile && !ranDoWhile) || condition.check(e)) { + protected TriggerItem walk(Event event) { + if ((doWhile && !ranDoWhile) || condition.check(event)) { ranDoWhile = true; - return walk(e, true); + currentLoopCounter.put(event, (currentLoopCounter.getOrDefault(event, 0L)) + 1); + return walk(event, true); } else { - reset(); - debug(e, false); + exit(event); + debug(event, false); return actualNext; } } @@ -114,12 +116,14 @@ public TriggerItem getActualNext() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return (doWhile ? "do " : "") + "while " + condition.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return (doWhile ? "do " : "") + "while " + condition.toString(event, debug); } - public void reset() { + @Override + public void exit(Event event) { ranDoWhile = false; + super.exit(event); } } diff --git a/src/test/skript/tests/regressions/4595-loop-iteration.sk b/src/test/skript/tests/regressions/4595-loop-iteration.sk new file mode 100644 index 00000000000..cca967abf79 --- /dev/null +++ b/src/test/skript/tests/regressions/4595-loop-iteration.sk @@ -0,0 +1,28 @@ +test "loop-iteration": + + # Test without variables + loop 2 times: + add loop-iteration to {_1} + loop 2 times: + add loop-iteration-2 to {_2} + loop 2 times: + add loop-iteration-3 to {_3} + + assert {_1} = 3 with "loop-iteration-1 not equal to 3 (value: %{_1}%)" + assert {_2} = 6 with "loop-iteration-2 not equal to 6 (value: %{_2}%)" + assert {_3} = 12 with "loop-iteration-3 not equal to 12 (value: %{_3}%)" + + delete {_1}, {_2} and {_3} # reset + + # Test with variables + add "a" and "b" to {_a::*} and {_b::*} and {_c::*} + loop {_a::*}: + add loop-iteration to {_1} + loop {_b::*}: + add loop-iteration-2 to {_2} + loop {_c::*}: + add loop-iteration-3 to {_3} + + assert {_1} = 3 with "loop-iteration-1 not equal to 3 (variables) (value: %{_1}%)" + assert {_2} = 6 with "loop-iteration-2 not equal to 6 (variables) (value: %{_2}%)" + assert {_3} = 12 with "loop-iteration-3 not equal to 12 (variables) (value: %{_3}%)" From e08ec477cfbc8a748aa72aac2ee51e0541bb8d4b Mon Sep 17 00:00:00 2001 From: sovdee <ethansovde@gmail.com> Date: Sun, 25 Jun 2023 23:21:15 -0700 Subject: [PATCH 349/619] Add CondIsLeftHanded and EffHandedness (#5494) --- .../skript/conditions/CondIsLeftHanded.java | 87 +++++++++++++++++++ .../ch/njol/skript/effects/EffHandedness.java | 77 ++++++++++++++++ .../tests/syntaxes/effects/EffHandedness.sk | 11 +++ 3 files changed, 175 insertions(+) create mode 100644 src/main/java/ch/njol/skript/conditions/CondIsLeftHanded.java create mode 100644 src/main/java/ch/njol/skript/effects/EffHandedness.java create mode 100644 src/test/skript/tests/syntaxes/effects/EffHandedness.sk diff --git a/src/main/java/ch/njol/skript/conditions/CondIsLeftHanded.java b/src/main/java/ch/njol/skript/conditions/CondIsLeftHanded.java new file mode 100644 index 00000000000..ab2f4b2e26b --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondIsLeftHanded.java @@ -0,0 +1,87 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.conditions; + +import ch.njol.skript.Skript; +import ch.njol.skript.conditions.base.PropertyCondition; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Mob; +import org.bukkit.inventory.MainHand; + +@Name("Left Handed") +@Description({ + "Checks if living entities or players are left or right-handed. Armor stands are neither right nor left-handed.", + "Paper 1.17.1+ is required for non-player entities." +}) +@Examples({ + "on damage of player:", + "\tif victim is left handed:", + "\t\tcancel event" +}) +@Since("INSERT VERSION") +@RequiredPlugins("Paper 1.17.1+ (entities)") +public class CondIsLeftHanded extends PropertyCondition<LivingEntity> { + + private static final boolean CAN_USE_ENTITIES = Skript.methodExists(Mob.class, "isLeftHanded"); + + static { + if (CAN_USE_ENTITIES) { + register(CondIsLeftHanded.class, PropertyType.BE, "(:left|right)( |-)handed", "livingentities"); + } else { + register(CondIsLeftHanded.class, PropertyType.BE, "(:left|right)( |-)handed", "players"); + } + } + + private MainHand hand; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + hand = parseResult.hasTag("left") ? MainHand.LEFT : MainHand.RIGHT; + return super.init(exprs, matchedPattern, isDelayed, parseResult); + } + + @Override + public boolean check(LivingEntity livingEntity) { + // check if entity is a mob and if the method exists + if (CAN_USE_ENTITIES && livingEntity instanceof Mob) + return ((Mob) livingEntity).isLeftHanded() == (hand == MainHand.LEFT); + + // check if entity is a player + if (livingEntity instanceof HumanEntity) + return ((HumanEntity) livingEntity).getMainHand() == hand; + + // invalid entity + return false; + } + + @Override + protected String getPropertyName() { + return (hand == MainHand.LEFT ? "left" : "right") + " handed"; + } + +} diff --git a/src/main/java/ch/njol/skript/effects/EffHandedness.java b/src/main/java/ch/njol/skript/effects/EffHandedness.java new file mode 100644 index 00000000000..ddb8a8d0787 --- /dev/null +++ b/src/main/java/ch/njol/skript/effects/EffHandedness.java @@ -0,0 +1,77 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.effects; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Mob; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Handedness") +@Description("Make mobs left or right-handed. This does not affect players.") +@Examples({ + "spawn skeleton at spawn of world \"world\":", + "\tmake entity left handed", + "", + "make all zombies in radius 10 of player right handed" +}) +@Since("INSERT VERSION") +@RequiredPlugins("Paper 1.17.1+") +public class EffHandedness extends Effect { + + static { + if (Skript.methodExists(Mob.class, "setLeftHanded", boolean.class)) + Skript.registerEffect(EffHandedness.class, "make %livingentities% (:left|right)( |-)handed"); + } + + private boolean leftHanded; + private Expression<LivingEntity> livingEntities; + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + leftHanded = parseResult.hasTag("left"); + livingEntities = (Expression<LivingEntity>) exprs[0]; + return true; + } + + @Override + protected void execute(Event event) { + for (LivingEntity livingEntity : livingEntities.getArray(event)) { + if (livingEntity instanceof Mob) { + ((Mob) livingEntity).setLeftHanded(leftHanded); + } + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "make " + livingEntities.toString(event, debug) + " " + (leftHanded ? "left" : "right") + " handed"; + } + +} diff --git a/src/test/skript/tests/syntaxes/effects/EffHandedness.sk b/src/test/skript/tests/syntaxes/effects/EffHandedness.sk new file mode 100644 index 00000000000..f4df6141be5 --- /dev/null +++ b/src/test/skript/tests/syntaxes/effects/EffHandedness.sk @@ -0,0 +1,11 @@ +test "left handedness" when running minecraft "1.17.1": + spawn skeleton at spawn of world "world": + make entity right handed + assert entity is not left handed with "zombie is left handed after being made right handed" + assert entity is right handed with "zombie is not right handed after being made right handed" + make entity left handed + assert entity is not right handed with "zombie is right handed after being made left handed" + assert entity is left handed with "zombie is not left handed after being made left handed" + make entity left handed + assert entity is left handed with "zombie is not left handed after being made left handed again" + delete entity From 4e1f1dfc88e9517a15fc74fa603d68fbada5054b Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Mon, 26 Jun 2023 19:47:57 -0600 Subject: [PATCH 350/619] [2.7.0 target] Add InventoryDragEvent (#5474) to 2.7.0 release (#5757) --- .../classes/data/BukkitEventValues.java | 82 +++++++++++++++++++ .../ch/njol/skript/events/SimpleEvents.java | 15 +++- 2 files changed, 95 insertions(+), 2 deletions(-) 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 314fd98bab7..b628c56a887 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -101,9 +101,11 @@ import org.bukkit.event.hanging.HangingPlaceEvent; import org.bukkit.event.inventory.ClickType; import org.bukkit.event.inventory.CraftItemEvent; +import org.bukkit.event.inventory.DragType; import org.bukkit.event.inventory.InventoryAction; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.inventory.InventoryDragEvent; import org.bukkit.event.inventory.InventoryOpenEvent; import org.bukkit.event.inventory.InventoryPickupItemEvent; import org.bukkit.event.inventory.PrepareAnvilEvent; @@ -145,13 +147,17 @@ import org.bukkit.event.world.WorldEvent; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryView; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; import org.bukkit.inventory.Recipe; import org.bukkit.potion.PotionEffectType; import org.eclipse.jdt.annotation.Nullable; +import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * @author Peter Güttinger @@ -1087,6 +1093,82 @@ public Inventory get(final InventoryClickEvent e) { return e.getClickedInventory(); } }, 0); + // InventoryDragEvent + EventValues.registerEventValue(InventoryDragEvent.class, Player.class, new Getter<Player, InventoryDragEvent>() { + @Override + @Nullable + public Player get(InventoryDragEvent event) { + return event.getWhoClicked() instanceof Player ? (Player) event.getWhoClicked() : null; + } + }, EventValues.TIME_NOW); + EventValues.registerEventValue(InventoryDragEvent.class, World.class, new Getter<World, InventoryDragEvent>() { + @Override + @Nullable + public World get(InventoryDragEvent event) { + return event.getWhoClicked().getWorld(); + } + }, EventValues.TIME_NOW); + EventValues.registerEventValue(InventoryDragEvent.class, ItemStack.class, new Getter<ItemStack, InventoryDragEvent>() { + @Override + @Nullable + public ItemStack get(InventoryDragEvent event) { + return event.getOldCursor(); + } + }, EventValues.TIME_PAST); + EventValues.registerEventValue(InventoryDragEvent.class, ItemStack.class, new Getter<ItemStack, InventoryDragEvent>() { + @Override + @Nullable + public ItemStack get(InventoryDragEvent event) { + return event.getCursor(); + } + }, EventValues.TIME_NOW); + EventValues.registerEventValue(InventoryDragEvent.class, ItemStack[].class, new Getter<ItemStack[], InventoryDragEvent>() { + @Override + @Nullable + public ItemStack[] get(InventoryDragEvent event) { + return event.getNewItems().values().toArray(new ItemStack[0]); + } + }, EventValues.TIME_NOW); + EventValues.registerEventValue(InventoryDragEvent.class, Slot[].class, new Getter<Slot[], InventoryDragEvent>() { + @Override + @Nullable + public Slot[] get(InventoryDragEvent event) { + List<Slot> slots = new ArrayList<>(event.getRawSlots().size()); + InventoryView view = event.getView(); + for (Integer rawSlot : event.getRawSlots()) { + Inventory inventory = view.getInventory(rawSlot); + int slot = view.convertSlot(rawSlot); + if (inventory == null) + continue; + // Not all indices point to inventory slots. Equipment, for example + if (inventory instanceof PlayerInventory && slot >= 36) { + slots.add(new ch.njol.skript.util.slot.EquipmentSlot(((PlayerInventory) view.getBottomInventory()).getHolder(), slot)); + } else { + slots.add(new InventorySlot(inventory, view.convertSlot(rawSlot))); + } + } + return slots.toArray(new Slot[0]); + } + }, EventValues.TIME_NOW); + EventValues.registerEventValue(InventoryDragEvent.class, ClickType.class, new Getter<ClickType, InventoryDragEvent>() { + @Override + @Nullable + public ClickType get(InventoryDragEvent event) { + return event.getType() == DragType.EVEN ? ClickType.LEFT : ClickType.RIGHT; + } + }, EventValues.TIME_NOW); + EventValues.registerEventValue(InventoryDragEvent.class, Inventory[].class, new Getter<Inventory[], InventoryDragEvent>() { + @Override + @Nullable + public Inventory[] get(InventoryDragEvent event) { + Set<Inventory> inventories = new HashSet<>(); + InventoryView view = event.getView(); + for (Integer rawSlot : event.getRawSlots()) { + inventories.add(view.getInventory(rawSlot)); + } + return inventories.toArray(new Inventory[0]); + } + }, EventValues.TIME_NOW); // PrepareAnvilEvent EventValues.registerEventValue(PrepareAnvilEvent.class, ItemStack.class, new Getter<ItemStack, PrepareAnvilEvent>() { @Override diff --git a/src/main/java/ch/njol/skript/events/SimpleEvents.java b/src/main/java/ch/njol/skript/events/SimpleEvents.java index 6e1aa2b7004..bcb301489e1 100644 --- a/src/main/java/ch/njol/skript/events/SimpleEvents.java +++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java @@ -59,6 +59,7 @@ import org.bukkit.event.inventory.FurnaceBurnEvent; import org.bukkit.event.inventory.FurnaceSmeltEvent; import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.inventory.InventoryDragEvent; import org.bukkit.event.inventory.InventoryOpenEvent; import org.bukkit.event.inventory.InventoryPickupItemEvent; import org.bukkit.event.inventory.PrepareAnvilEvent; @@ -670,7 +671,7 @@ public class SimpleEvents { "Use <a href='./expressions.html#ExprChatFormat'>chat format</a> to change message format.", "Use <a href='./expressions.html#ExprChatRecipients'>chat recipients</a> to edit chat recipients." ) - .examples( + .examples( "on chat:", "\tif player has permission \"owner\":", "\t\tset chat format to \"<red>[player]<light gray>: <light red>[message]\"", @@ -679,7 +680,7 @@ public class SimpleEvents { "\telse: #default message format", "\t\tset chat format to \"<orange>[player]<light gray>: <white>[message]\"" ) - .since("1.4.1"); + .since("1.4.1"); if (Skript.classExists("org.bukkit.event.world.LootGenerateEvent")) { Skript.registerEvent("Loot Generate", SimpleEvent.class, LootGenerateEvent.class, "loot generat(e|ing)") .description( @@ -709,6 +710,16 @@ public class SimpleEvents { .requiredPlugins("Paper 1.16+"); } + Skript.registerEvent("Inventory Drag", SimpleEvent.class, InventoryDragEvent.class, "inventory drag[ging]") + .description("Called when a player drags an item in their cursor across the inventory.") + .examples( + "on inventory drag:", + "\tif player's current inventory is {_gui}:", + "\t\tsend \"You can't drag your items here!\" to player", + "\t\tcancel event" + ) + .since("INSERT VERSION"); + } } From 59b4bb594be76c179099e56842dcf7499d0868bb Mon Sep 17 00:00:00 2001 From: DelayedGaming <72163224+DelayedGaming@users.noreply.github.com> Date: Tue, 27 Jun 2023 10:08:51 +0800 Subject: [PATCH 351/619] New Event: PlayerPickupArrowEvent (#5588) --- .../classes/data/BukkitEventValues.java | 22 +++++++++++++++++++ .../ch/njol/skript/events/SimpleEvents.java | 11 ++++++++++ 2 files changed, 33 insertions(+) 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 9bf77216c51..2ed28445766 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -109,6 +109,7 @@ import org.bukkit.event.player.PlayerItemDamageEvent; import org.bukkit.event.player.PlayerItemMendEvent; import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerPickupArrowEvent; import org.bukkit.event.player.PlayerPickupItemEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerQuitEvent.QuitReason; @@ -1649,6 +1650,25 @@ public Location get(LootGenerateEvent event) { }, EventValues.TIME_NOW); } + // PlayerPickupArrowEvent + // This event value is restricted to MC 1.14+ due to an API change which has the return type changed + // which throws a NoSuchMethodError if used in a 1.13 server. + if (Skript.isRunningMinecraft(1, 14)) + EventValues.registerEventValue(PlayerPickupArrowEvent.class, Projectile.class, new Getter<Projectile, PlayerPickupArrowEvent>() { + @Override + public Projectile get(PlayerPickupArrowEvent event) { + return event.getArrow(); + } + }, EventValues.TIME_NOW); + + EventValues.registerEventValue(PlayerPickupArrowEvent.class, ItemStack.class, new Getter<ItemStack, PlayerPickupArrowEvent>() { + @Override + @Nullable + public ItemStack get(PlayerPickupArrowEvent event) { + return event.getItem().getItemStack(); + } + }, EventValues.TIME_NOW); + //PlayerQuitEvent if (Skript.classExists("org.bukkit.event.player.PlayerQuitEvent$QuitReason")) EventValues.registerEventValue(PlayerQuitEvent.class, QuitReason.class, new Getter<QuitReason, PlayerQuitEvent>() { @@ -1658,5 +1678,7 @@ public QuitReason get(PlayerQuitEvent event) { return event.getReason(); } }, EventValues.TIME_NOW); + } + } diff --git a/src/main/java/ch/njol/skript/events/SimpleEvents.java b/src/main/java/ch/njol/skript/events/SimpleEvents.java index bcb301489e1..60753f6969d 100644 --- a/src/main/java/ch/njol/skript/events/SimpleEvents.java +++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java @@ -80,6 +80,7 @@ import org.bukkit.event.player.PlayerKickEvent; import org.bukkit.event.player.PlayerLocaleChangeEvent; import org.bukkit.event.player.PlayerLoginEvent; +import org.bukkit.event.player.PlayerPickupArrowEvent; import org.bukkit.event.player.PlayerPortalEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerRespawnEvent; @@ -710,6 +711,16 @@ public class SimpleEvents { .requiredPlugins("Paper 1.16+"); } + Skript.registerEvent("Player Pickup Arrow", SimpleEvent.class, PlayerPickupArrowEvent.class, "[player] (pick[ing| ]up [an] arrow|arrow pick[ing| ]up)") + .description("Called when a player picks up an arrow from the ground.") + .examples( + "on arrow pickup:", + "\tcancel the event", + "\tteleport event-projectile to block 5 above event-projectile" + ) + .since("INSERT VERSION") + .requiredPlugins("Minecraft 1.14+ (event-projectile)"); + Skript.registerEvent("Inventory Drag", SimpleEvent.class, InventoryDragEvent.class, "inventory drag[ging]") .description("Called when a player drags an item in their cursor across the inventory.") .examples( From bb87694b569492dc3233db4dcf56cd0c5e7fc2f4 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Mon, 26 Jun 2023 20:20:47 -0600 Subject: [PATCH 352/619] Add time states for PlayerItemHeldEvent and hotbar slot (#5271) --- .../classes/data/BukkitEventValues.java | 17 +++ .../skript/expressions/ExprHotbarSlot.java | 112 ++++++++++++------ 2 files changed, 96 insertions(+), 33 deletions(-) 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 2ed28445766..bc758bb6c6e 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -107,6 +107,7 @@ import org.bukkit.event.player.PlayerItemBreakEvent; import org.bukkit.event.player.PlayerItemConsumeEvent; import org.bukkit.event.player.PlayerItemDamageEvent; +import org.bukkit.event.player.PlayerItemHeldEvent; import org.bukkit.event.player.PlayerItemMendEvent; import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerPickupArrowEvent; @@ -1650,6 +1651,22 @@ public Location get(LootGenerateEvent event) { }, EventValues.TIME_NOW); } + // PlayerItemHeldEvent + EventValues.registerEventValue(PlayerItemHeldEvent.class, Slot.class, new Getter<Slot, PlayerItemHeldEvent>() { + @Override + @Nullable + public Slot get(PlayerItemHeldEvent event) { + return new InventorySlot(event.getPlayer().getInventory(), event.getNewSlot()); + } + }, EventValues.TIME_NOW); + EventValues.registerEventValue(PlayerItemHeldEvent.class, Slot.class, new Getter<Slot, PlayerItemHeldEvent>() { + @Override + @Nullable + public Slot get(PlayerItemHeldEvent event) { + return new InventorySlot(event.getPlayer().getInventory(), event.getPreviousSlot()); + } + }, EventValues.TIME_PAST); + // PlayerPickupArrowEvent // This event value is restricted to MC 1.14+ due to an API change which has the return type changed // which throws a NoSuchMethodError if used in a 1.13 server. diff --git a/src/main/java/ch/njol/skript/expressions/ExprHotbarSlot.java b/src/main/java/ch/njol/skript/expressions/ExprHotbarSlot.java index 0c4e497ff46..ced8c4df34f 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprHotbarSlot.java +++ b/src/main/java/ch/njol/skript/expressions/ExprHotbarSlot.java @@ -20,70 +20,116 @@ import org.bukkit.entity.Player; import org.bukkit.event.Event; +import org.bukkit.event.player.PlayerItemHeldEvent; import org.bukkit.inventory.PlayerInventory; import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.classes.Changer; +import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; -import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.expressions.base.PropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.registrations.EventValues; +import ch.njol.skript.util.Getter; import ch.njol.skript.util.slot.InventorySlot; import ch.njol.skript.util.slot.Slot; +import ch.njol.util.Kleenean; @Name("Hotbar Slot") -@Description({"The currently selected hotbar <a href='./classes.html#slot'>slot</a>. To retrieve its number use <a href='#ExprSlotIndex'>Slot Index</a> expression."}) -@Examples({"message \"%player's current hotbar slot%\"", - "set player's selected hotbar slot to slot 4 of player", - "send \"index of player's current hotbar slot = 1\" # second slot from the left"}) +@Description({ + "The currently selected hotbar <a href='./classes.html#slot'>slot</a>.", + "To retrieve its number use <a href='#ExprSlotIndex'>Slot Index</a> expression.", + "Use future and past tense to grab the previous slot in an item change event, see example." +}) +@Examples({ + "message \"%player's current hotbar slot%\"", + "set player's selected hotbar slot to slot 4 of player", + "", + "send \"index of player's current hotbar slot = 1\" # second slot from the left", + "", + "on item held change:", + "\tif the selected hotbar slot was a diamond:", + "\t\tset the currently selected hotbar slot to slot 5 of player" +}) @Since("2.2-dev36") -public class ExprHotbarSlot extends SimplePropertyExpression<Player, Slot> { +public class ExprHotbarSlot extends PropertyExpression<Player, Slot> { static { - register(ExprHotbarSlot.class, Slot.class, "[([currently] selected|current)] hotbar slot", "players"); + registerDefault(ExprHotbarSlot.class, Slot.class, "[the] [([current:currently] selected|current:current)] hotbar slot[s]", "players"); } - - @Override - @Nullable - public Slot convert(Player p) { - PlayerInventory invi = p.getInventory(); - assert invi != null; - return new InventorySlot(invi, invi.getHeldItemSlot()); - } - + + // This exists because time states should not register when the 'currently' tag of the syntax is present. + private boolean current; + @Override - protected String getPropertyName() { - return "hotbar slot"; + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + setExpr((Expression<? extends Player>) exprs[0]); + current = parseResult.hasTag("current"); + return true; } - + @Override - public Class<? extends Slot> getReturnType() { - return Slot.class; + protected Slot[] get(Event event, Player[] source) { + return get(source, new Getter<Slot, Player>() { + @Override + @Nullable + public Slot get(Player player) { + int time = getTime(); + PlayerInventory inventory = player.getInventory(); + if (event instanceof PlayerItemHeldEvent && time != EventValues.TIME_NOW) { + PlayerItemHeldEvent switchEvent = (PlayerItemHeldEvent) event; + if (time == EventValues.TIME_FUTURE) + return new InventorySlot(inventory, switchEvent.getNewSlot()); + if (time == EventValues.TIME_PAST) + return new InventorySlot(inventory, switchEvent.getPreviousSlot()); + } + return new InventorySlot(inventory, inventory.getHeldItemSlot()); + } + }); } - + @Override @Nullable - public Class<?>[] acceptChange(Changer.ChangeMode mode) { - if (mode == Changer.ChangeMode.SET) + public Class<?>[] acceptChange(ChangeMode mode) { + if (mode == ChangeMode.SET) return new Class[] {Slot.class}; return null; } - + @Override - public void change(Event e, @Nullable Object[] delta, Changer.ChangeMode mode) { + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { assert delta != null; Slot slot = (Slot) delta[0]; if (!(slot instanceof InventorySlot)) return; // Only inventory slots can be hotbar slots - + int index = ((InventorySlot) slot).getIndex(); if (index > 8) // Only slots in hotbar can be current hotbar slot return; - - for (Player p : getExpr().getArray(e)) { - p.getInventory().setHeldItemSlot(index); - } + + for (Player player : getExpr().getArray(event)) + player.getInventory().setHeldItemSlot(index); } - + + @Override + public boolean setTime(int time) { + if (current) + return super.setTime(time); + return super.setTime(time, getExpr(), PlayerItemHeldEvent.class); + } + + @Override + public Class<? extends Slot> getReturnType() { + return Slot.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "hotbar slot of " + getExpr().toString(event, debug); + } + } From 8ccadc713a61169dad61811c6faa5436ff4e8436 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Tue, 27 Jun 2023 18:03:49 -0600 Subject: [PATCH 353/619] [2.7.0 target] Update 2.7.0 to Spigot 1.20.1 (#5760) Update 2.7.0 to Spigot 1.20.1 --- build.gradle | 4 ++-- gradle.properties | 2 +- src/main/resources/lang/default.lang | 2 ++ .../java17/{paper-1.19.3.json => paper-1.20.1.json} | 4 ++-- 4 files changed, 7 insertions(+), 5 deletions(-) rename src/test/skript/environments/java17/{paper-1.19.3.json => paper-1.20.1.json} (85%) diff --git a/build.gradle b/build.gradle index c6e089cadac..5920cb95ac8 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ dependencies { shadow group: 'org.bstats', name: 'bstats-bukkit', version: '3.0.2' shadow group: 'net.kyori', name: 'adventure-text-serializer-bungeecord', version: '4.3.0' - implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.19.4-R0.1-SNAPSHOT' + implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.20.1-R0.1-SNAPSHOT' implementation group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.700' implementation group: 'com.google.code.findbugs', name: 'findbugs', version: '3.0.1' implementation group: 'com.sk89q.worldguard', name: 'worldguard-legacy', version: '7.0.0-SNAPSHOT' @@ -254,7 +254,7 @@ void createTestTask(String name, String desc, String environments, int javaVersi } } -def latestEnv = 'java17/paper-1.19.4.json' +def latestEnv = 'java17/paper-1.20.1.json' def latestJava = 17 def oldestJava = 8 diff --git a/gradle.properties b/gradle.properties index 6a846dcfc84..62d7b588c56 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,5 +2,5 @@ groupid=ch.njol name=skript version=2.7.0 jarName=Skript.jar -testEnv=java17/paper-1.19.4 +testEnv=java17/paper-1.20.1 testEnvJavaVersion=17 diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 03307a009d0..e86820ed0c2 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -1299,6 +1299,8 @@ damage causes: dryout: dryout custom: unknown, custom, plugin, a plugin sonic_boom: sonic boom + kill: kill, killed + world_border: world border # -- Teleport Causes -- teleport causes: diff --git a/src/test/skript/environments/java17/paper-1.19.3.json b/src/test/skript/environments/java17/paper-1.20.1.json similarity index 85% rename from src/test/skript/environments/java17/paper-1.19.3.json rename to src/test/skript/environments/java17/paper-1.20.1.json index 731f032034e..3a117b97397 100644 --- a/src/test/skript/environments/java17/paper-1.19.3.json +++ b/src/test/skript/environments/java17/paper-1.20.1.json @@ -1,11 +1,11 @@ { - "name": "paper-1.19.3", + "name": "paper-1.20.1", "resources": [ {"source": "server.properties.generic", "target": "server.properties"} ], "paperDownloads": [ { - "version": "1.19.3", + "version": "1.20.1", "target": "paperclip.jar" } ], From f6e4ccf18a1924dd498965e97da6e69ac9def609 Mon Sep 17 00:00:00 2001 From: sovdee <ethansovde@gmail.com> Date: Tue, 27 Jun 2023 17:15:19 -0700 Subject: [PATCH 354/619] Vector from direction (#5502) --- .../expressions/ExprVectorFromDirection.java | 94 +++++++++++++++++++ .../java/ch/njol/skript/util/Direction.java | 44 +++++---- 2 files changed, 119 insertions(+), 19 deletions(-) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprVectorFromDirection.java diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorFromDirection.java b/src/main/java/ch/njol/skript/expressions/ExprVectorFromDirection.java new file mode 100644 index 00000000000..38367f92720 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorFromDirection.java @@ -0,0 +1,94 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.skript.util.Direction; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.bukkit.util.Vector; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Vectors - Create from Direction") +@Description({ + "Creates vectors from given directions.", + "Relative directions are relative to the origin, (0, 0, 0). Therefore, the vector from the direction 'forwards' is (0, 0, 1)." +}) +@Examples({ + "set {_v} to vector from direction upwards", + "set {_v} to vector in direction of player", + "set {_v} to vector in horizontal direction of player", + "set {_v} to vector from facing of player", + "set {_v::*} to vectors from north, south, east, and west" +}) +@Since("INSERT VERSION") +public class ExprVectorFromDirection extends SimpleExpression<Vector> { + + static { + Skript.registerExpression(ExprVectorFromDirection.class, Vector.class, ExpressionType.SIMPLE, + "vector[s] [from] %directions%", + "%directions% vector[s]"); + } + + private Expression<Direction> direction; + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + direction = (Expression<Direction>) exprs[0]; + if (matchedPattern == 1) { + if (!(direction instanceof ExprDirection)) { + Skript.error("The direction in '%directions% vector[s]' can not be a variable. Use the direction expression instead: 'northwards vector'."); + return false; + } + } + return true; + } + + @Override + @Nullable + protected Vector[] get(Event event) { + return direction.stream(event) + .map(Direction::getDirection) + .toArray(Vector[]::new); + } + + @Override + public boolean isSingle() { + return direction.isSingle(); + } + + @Override + public Class<? extends Vector> getReturnType() { + return Vector.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "vector " + direction.toString(event, debug); + } + +} diff --git a/src/main/java/ch/njol/skript/util/Direction.java b/src/main/java/ch/njol/skript/util/Direction.java index eb8a5f8142f..b50d7d431ef 100644 --- a/src/main/java/ch/njol/skript/util/Direction.java +++ b/src/main/java/ch/njol/skript/util/Direction.java @@ -18,24 +18,6 @@ */ package ch.njol.skript.util; -import java.io.StreamCorruptedException; -import java.lang.reflect.Field; -import java.util.Arrays; -import java.util.Locale; - -import ch.njol.skript.Skript; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.block.data.BlockData; -import org.bukkit.entity.Entity; -import org.bukkit.event.Event; -import org.bukkit.material.Directional; -import org.bukkit.util.Vector; -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptParser.ParseResult; @@ -47,6 +29,20 @@ import ch.njol.util.Kleenean; import ch.njol.yggdrasil.Fields.FieldContext; import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilRobustSerializable; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.Entity; +import org.bukkit.event.Event; +import org.bukkit.util.Vector; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; + +import java.io.StreamCorruptedException; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Locale; /** * @author Peter Güttinger @@ -128,7 +124,17 @@ public Location getRelative(final Entity e) { public Location getRelative(final Block b) { return b.getLocation().add(getDirection(b)); } - + + /* + * Used to get a vector from a direction without anything to be relative to. + * Any relative directions will be relative to 0 degrees pitch and yaw. + */ + public Vector getDirection() { + if (!relative) + return new Vector(pitchOrX, yawOrY, lengthOrZ); + return getDirection(0, 0); + } + public Vector getDirection(final Location l) { if (!relative) return new Vector(pitchOrX, yawOrY, lengthOrZ); From 011c91eb06c0fb8d5bfc0df385ee3d5dc9a0ef9c Mon Sep 17 00:00:00 2001 From: sovdee <ethansovde@gmail.com> Date: Tue, 27 Jun 2023 17:21:07 -0700 Subject: [PATCH 355/619] Add event-item and event-slot to Resurrect Event (#5683) --- .../classes/data/BukkitEventValues.java | 17 ++++++++++++++++ .../ch/njol/skript/events/SimpleEvents.java | 20 +++++++++---------- 2 files changed, 27 insertions(+), 10 deletions(-) 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 bc758bb6c6e..79072ed5454 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -70,6 +70,7 @@ import org.bukkit.event.entity.EntityDeathEvent; import org.bukkit.event.entity.EntityEvent; import org.bukkit.event.entity.EntityPickupItemEvent; +import org.bukkit.event.entity.EntityResurrectEvent; import org.bukkit.event.entity.EntityTameEvent; import org.bukkit.event.entity.FireworkExplodeEvent; import org.bukkit.event.entity.HorseJumpEvent; @@ -132,6 +133,7 @@ import org.bukkit.event.world.PortalCreateEvent; import org.bukkit.event.world.StructureGrowEvent; import org.bukkit.event.world.WorldEvent; +import org.bukkit.inventory.EntityEquipment; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryView; @@ -1651,6 +1653,21 @@ public Location get(LootGenerateEvent event) { }, EventValues.TIME_NOW); } + // EntityResurrectEvent + EventValues.registerEventValue(EntityResurrectEvent.class, Slot.class, new Getter<Slot, EntityResurrectEvent>() { + @Override + @Nullable + public Slot get(EntityResurrectEvent event) { + EquipmentSlot hand = event.getHand(); + EntityEquipment equipment = event.getEntity().getEquipment(); + if (equipment == null || hand == null) + return null; + return new ch.njol.skript.util.slot.EquipmentSlot(equipment, + (hand == EquipmentSlot.HAND) ? ch.njol.skript.util.slot.EquipmentSlot.EquipSlot.TOOL + : ch.njol.skript.util.slot.EquipmentSlot.EquipSlot.OFF_HAND); + } + }, EventValues.TIME_NOW); + // PlayerItemHeldEvent EventValues.registerEventValue(PlayerItemHeldEvent.class, Slot.class, new Getter<Slot, PlayerItemHeldEvent>() { @Override diff --git a/src/main/java/ch/njol/skript/events/SimpleEvents.java b/src/main/java/ch/njol/skript/events/SimpleEvents.java index 60753f6969d..36a9b2e4fd8 100644 --- a/src/main/java/ch/njol/skript/events/SimpleEvents.java +++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java @@ -472,16 +472,16 @@ public class SimpleEvents { .description("Called when a slime splits. Usually this happens when a big slime dies.") .examples("on slime split:") .since("2.2-dev26"); - if (Skript.classExists("org.bukkit.event.entity.EntityResurrectEvent")) { - Skript.registerEvent("Resurrect Attempt", SimpleEvent.class, EntityResurrectEvent.class, "[entity] resurrect[ion] [attempt]") - .description("Called when an entity dies, always. If they are not holding a totem, this is cancelled - you can, however, uncancel it.") - .examples("on resurrect attempt:", - " entity is player", - " entity has permission \"admin.undying\"", - " uncancel the event") - .since("2.2-dev28"); - SkriptEventHandler.listenCancelled.add(EntityResurrectEvent.class); // Listen this even when cancelled - } + Skript.registerEvent("Resurrect Attempt", SimpleEvent.class, EntityResurrectEvent.class, "[entity] resurrect[ion] [attempt]") + .description("Called when an entity dies, always. If they are not holding a totem, this is cancelled - you can, however, uncancel it.") + .examples( + "on resurrect attempt:", + "\tentity is player", + "\tentity has permission \"admin.undying\"", + "\tuncancel the event" + ) + .since("2.2-dev28"); + SkriptEventHandler.listenCancelled.add(EntityResurrectEvent.class); // Listen this even when cancelled Skript.registerEvent("Player World Change", SimpleEvent.class, PlayerChangedWorldEvent.class, "[player] world chang(ing|e[d])") .description("Called when a player enters a world. Does not work with other entities!") .examples("on player world change:", From 549ebc380b34dd544c760d7a2c23faa8973a12f7 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Thu, 6 Jul 2023 11:18:08 -0600 Subject: [PATCH 356/619] Fix null pointer exception in ExprBlocks when using the ExprDirection without a number defined. (#5790) Update ExprBlocks.java --- src/main/java/ch/njol/skript/expressions/ExprBlocks.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprBlocks.java b/src/main/java/ch/njol/skript/expressions/ExprBlocks.java index 35dea7f01a3..5eccfbb027b 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprBlocks.java +++ b/src/main/java/ch/njol/skript/expressions/ExprBlocks.java @@ -151,9 +151,11 @@ public Iterator<Block> iterator(Event event) { int distance = SkriptConfig.maxTargetBlockDistance.value(); if (this.direction instanceof ExprDirection) { Expression<Number> numberExpression = ((ExprDirection) this.direction).amount; - Number number = numberExpression.getSingle(event); - if (numberExpression != null && number != null) - distance = number.intValue(); + if (numberExpression != null) { + Number number = numberExpression.getSingle(event); + if (number != null) + distance = number.intValue(); + } } return new BlockLineIterator(location, vector, distance); } else { From 843774d0d9e6eb27ae252f116b119bd90388123b Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Fri, 7 Jul 2023 22:41:17 +0300 Subject: [PATCH 357/619] Fix ExprRawString using a Java 9 feature (#5795) Replace Java 9 replace method with Skript's method --- src/main/java/ch/njol/skript/expressions/ExprRawString.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprRawString.java b/src/main/java/ch/njol/skript/expressions/ExprRawString.java index 6b9d907da1a..f96b0e3e3ca 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprRawString.java +++ b/src/main/java/ch/njol/skript/expressions/ExprRawString.java @@ -31,6 +31,7 @@ import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.skript.util.SkriptColor; import ch.njol.util.Kleenean; +import ch.njol.util.StringUtils; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @@ -82,7 +83,7 @@ protected String[] get(Event event) { for (String string : message.getArray(event)) { String raw = SkriptColor.replaceColorChar(string); if (raw.toLowerCase().contains("&x")) { - raw = HEX_PATTERN.matcher(raw).replaceAll(matchResult -> + raw = StringUtils.replaceAll(raw, HEX_PATTERN, matchResult -> "<#" + matchResult.group(1).replace("&", "") + '>'); } strings.add(raw); From 6caf20c5fe8acd49179e385e1819bd8ca0e57c7e Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Sun, 9 Jul 2023 17:49:34 -0600 Subject: [PATCH 358/619] Fixes error not properly being caught (#5798) Update Utils.java --- src/main/java/ch/njol/skript/util/Utils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/util/Utils.java b/src/main/java/ch/njol/skript/util/Utils.java index 12255d4d8d6..351574c7361 100644 --- a/src/main/java/ch/njol/skript/util/Utils.java +++ b/src/main/java/ch/njol/skript/util/Utils.java @@ -199,7 +199,7 @@ public static Class<?>[] getClasses(Plugin plugin, String basePackage, String... for (String c : classNames) { try { classes.add(Class.forName(c, true, plugin.getClass().getClassLoader())); - } catch (ClassNotFoundException ex) { + } catch (ClassNotFoundException | NoClassDefFoundError ex) { Skript.exception(ex, "Cannot load class " + c); } catch (ExceptionInInitializerError err) { Skript.exception(err.getCause(), "class " + c + " generated an exception while loading"); From 2d9207fb8611d4aa665cba3fd6bdec500340d9be Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Sun, 9 Jul 2023 17:53:02 -0600 Subject: [PATCH 359/619] [2.7.0 target cherry pick] Fix the stop sound effect pattern (#5759) Fix the stop sound effect pattern --- .../ch/njol/skript/effects/EffStopSound.java | 49 ++++++++----------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffStopSound.java b/src/main/java/ch/njol/skript/effects/EffStopSound.java index 46a22c22795..fe0af9ef26c 100644 --- a/src/main/java/ch/njol/skript/effects/EffStopSound.java +++ b/src/main/java/ch/njol/skript/effects/EffStopSound.java @@ -49,33 +49,31 @@ @Examples({ "stop sound \"block.chest.open\" for the player", "stop playing sounds \"ambient.underwater.loop\" and \"ambient.underwater.loop.additions\" to the player", - "stop all sound for all players", - "stop sound in record category" + "stop all sounds for all players", + "stop sound in the record category" }) @Since("2.4, 2.7 (stop all sounds)") @RequiredPlugins("MC 1.17.1 (stop all sounds)") public class EffStopSound extends Effect { - + private static final boolean STOP_ALL_SUPPORTED = Skript.methodExists(Player.class, "stopAllSounds"); private static final Pattern KEY_PATTERN = Pattern.compile("([a-z0-9._-]+:)?[a-z0-9/._-]+"); - static { - String stopPattern = STOP_ALL_SUPPORTED ? "(all:all sound[s]|sound[s] %strings%)" : "sound[s] %strings%"; - + String stopPattern = STOP_ALL_SUPPORTED ? "(all:all sound[s]|sound[s] %-strings%)" : "sound[s] %strings%"; Skript.registerEffect(EffStopSound.class, - "stop " + stopPattern + " [(in|from) %-soundcategory%] [(from playing to|for) %players%]", - "stop playing sound[s] %strings% [(in|from) %-soundcategory%] [(to|for) %players%]" + "stop " + stopPattern + " [(in [the]|from) %-soundcategory%] [(from playing to|for) %players%]", + "stop playing sound[s] %strings% [(in [the]|from) %-soundcategory%] [(to|for) %players%]" ); } - - @SuppressWarnings("NotNullFieldNotInitialized") - private Expression<String> sounds; + @Nullable private Expression<SoundCategory> category; - @SuppressWarnings("NotNullFieldNotInitialized") + + @Nullable + private Expression<String> sounds; + private Expression<Player> players; - private boolean allSounds; @Override @@ -90,7 +88,6 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye category = (Expression<SoundCategory>) exprs[1]; players = (Expression<Player>) exprs[2]; } - return true; } @@ -98,37 +95,31 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye protected void execute(Event event) { // All sounds pattern wants explicitly defined master category SoundCategory category = this.category == null ? null : this.category.getOptionalSingle(event) - .orElse(allSounds ? null : SoundCategory.MASTER); + .orElse(allSounds ? null : SoundCategory.MASTER); + Player[] targets = players.getArray(event); - if (allSounds) { if (category == null) { - for (Player player : targets) { + for (Player player : targets) player.stopAllSounds(); - } } else { - for (Player player : targets) { + for (Player player : targets) player.stopSound(category); - } } - } else { + } else if (sounds != null) { for (String sound : sounds.getArray(event)) { try { Sound soundEnum = Sound.valueOf(sound.toUpperCase(Locale.ENGLISH)); - for (Player player : targets) { + for (Player player : targets) player.stopSound(soundEnum, category); - } - + continue; - } catch (IllegalArgumentException ignored) { } - + } catch (IllegalArgumentException ignored) {} sound = sound.toLowerCase(Locale.ENGLISH); if (!KEY_PATTERN.matcher(sound).matches()) continue; - - for (Player player : targets) { + for (Player player : targets) player.stopSound(sound, category); - } } } } From 230fabb2590ee0cacd0917805f66d9e03ccff7b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Jul 2023 20:28:39 +0300 Subject: [PATCH 360/619] Bump org.gradle.toolchains.foojay-resolver-convention from 0.5.0 to 0.6.0 (#5810) --- settings.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle b/settings.gradle index 0a8d7b2d301..db69555628c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,6 +1,6 @@ plugins { - id 'org.gradle.toolchains.foojay-resolver-convention' version '0.5.0' + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.6.0' } rootProject.name = 'Skript' From 82749a5d820baa6bbadc7f9883dca0a2e7ea9c3e Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Wed, 12 Jul 2023 14:28:42 -0600 Subject: [PATCH 361/619] Allow on pickup to be found easier on docs (#5823) --- src/main/java/ch/njol/skript/events/EvtItem.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/events/EvtItem.java b/src/main/java/ch/njol/skript/events/EvtItem.java index 30ae63703cc..4d5e31cb10e 100644 --- a/src/main/java/ch/njol/skript/events/EvtItem.java +++ b/src/main/java/ch/njol/skript/events/EvtItem.java @@ -90,7 +90,7 @@ public class EvtItem extends SkriptEvent { .description("Called when a player/entity picks up an item. Please note that the item is still on the ground when this event is called.") .examples("on pick up:", "on entity pickup of wheat:") .since("<i>unknown</i> (before 2.1), 2.5 (entity)") - .requiredPlugins("1.12.2+ for entity"); + .keywords("pickup"); } else { Skript.registerEvent("Pick Up", EvtItem.class, PlayerPickupItemEvent.class, "[player] (pick[ ]up|picking up) [[of] %-itemtypes%]") .description("Called when a player picks up an item. Please note that the item is still on the ground when this event is called.") From 5a44852ebbb6243f9b211d4f7880fd65ba0315b4 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Wed, 12 Jul 2023 18:36:15 -0600 Subject: [PATCH 362/619] Fix loot generate example (#5816) --- .../ch/njol/skript/events/SimpleEvents.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/java/ch/njol/skript/events/SimpleEvents.java b/src/main/java/ch/njol/skript/events/SimpleEvents.java index 36a9b2e4fd8..d16254ded31 100644 --- a/src/main/java/ch/njol/skript/events/SimpleEvents.java +++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java @@ -684,18 +684,18 @@ public class SimpleEvents { .since("1.4.1"); if (Skript.classExists("org.bukkit.event.world.LootGenerateEvent")) { Skript.registerEvent("Loot Generate", SimpleEvent.class, LootGenerateEvent.class, "loot generat(e|ing)") - .description( - "Called when a loot table of an inventory is generated in the world.", - "For example, when opening a shipwreck chest." - ) - .examples( - "on loot generate:", - "\tchance of %10", - "\tadd 64 diamonds", - "\tsend \"You hit the jackpot!!\"" - ) - .since("2.7") - .requiredPlugins("MC 1.16+"); + .description( + "Called when a loot table of an inventory is generated in the world.", + "For example, when opening a shipwreck chest." + ) + .examples( + "on loot generate:", + "\tchance of 10%", + "\tadd 64 diamonds to the loot", + "\tsend \"You hit the jackpot at %event-location%!\"" + ) + .since("2.7") + .requiredPlugins("MC 1.16+"); } if (Skript.classExists("io.papermc.paper.event.player.PlayerDeepSleepEvent")) { Skript.registerEvent("Player Deep Sleep", SimpleEvent.class, PlayerDeepSleepEvent.class, "[player] deep sleep[ing]") From 5c50a3f831bbad0d82104c6cac62ab758c470170 Mon Sep 17 00:00:00 2001 From: sovdee <ethansovde@gmail.com> Date: Fri, 14 Jul 2023 12:48:57 -0700 Subject: [PATCH 363/619] Adds ExprPortalCooldown (#5356) --- .../expressions/ExprPortalCooldown.java | 128 ++++++++++++++++++ .../expressions/ExprPortalCooldown.sk | 16 +++ 2 files changed, 144 insertions(+) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprPortalCooldown.java create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprPortalCooldown.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprPortalCooldown.java b/src/main/java/ch/njol/skript/expressions/ExprPortalCooldown.java new file mode 100644 index 00000000000..e4b98afb7ed --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprPortalCooldown.java @@ -0,0 +1,128 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.util.Timespan; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.GameMode; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Portal Cooldown") +@Description({ + "The amount of time before an entity can use a portal. By default, it is 15 seconds after exiting a nether portal or end gateway.", + "Players in survival/adventure get a cooldown of 0.5 seconds, while those in creative get no cooldown.", + "Resetting will set the cooldown back to the default 15 seconds for non-player entities and 0.5 seconds for players." +}) +@Examples({ + "on portal:", + "\twait 1 tick", + "\tset portal cooldown of event-entity to 5 seconds" +}) +@Since("INSERT VERSION") +public class ExprPortalCooldown extends SimplePropertyExpression<Entity, Timespan> { + + static { + register(ExprPortalCooldown.class, Timespan.class, "portal cooldown", "entities"); + } + + // Default cooldown for nether portals is 15 seconds: + // https://minecraft.fandom.com/wiki/Nether_portal#Behavior + private static final int DEFAULT_COOLDOWN = 15 * 20; + // Players only get a 0.5 second cooldown in survival/adventure: + private static final int DEFAULT_COOLDOWN_PLAYER = 10; + + @Override + @Nullable + public Timespan convert(Entity entity) { + return Timespan.fromTicks(entity.getPortalCooldown()); + } + + @Override + @Nullable + public Class<?>[] acceptChange(ChangeMode mode) { + switch (mode) { + case SET: + case ADD: + case RESET: + case DELETE: + case REMOVE: + return CollectionUtils.array(Timespan.class); + default: + return null; + } + } + + @Override + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + Entity[] entities = getExpr().getArray(event); + int change = delta == null ? 0 : (int) ((Timespan) delta[0]).getTicks_i(); + switch (mode) { + case REMOVE: + change = -change; // allow fall-through to avoid duplicate code + case ADD: + for (Entity entity : entities) { + entity.setPortalCooldown(Math.max(entity.getPortalCooldown() + change, 0)); + } + break; + case RESET: + for (Entity entity : entities) { + // Players in survival/adventure get a 0.5 second cooldown, while those in creative get no cooldown + if (entity instanceof Player) { + if (((Player) entity).getGameMode() == GameMode.CREATIVE) { + entity.setPortalCooldown(0); + } else { + entity.setPortalCooldown(DEFAULT_COOLDOWN_PLAYER); + } + // Non-player entities get a 15 second cooldown + } else { + entity.setPortalCooldown(DEFAULT_COOLDOWN); + } + } + break; + case DELETE: + case SET: + for (Entity entity : entities) { + entity.setPortalCooldown(Math.max(change, 0)); + } + break; + default: + assert false; + } + } + + @Override + public Class<? extends Timespan> getReturnType() { + return Timespan.class; + } + + @Override + protected String getPropertyName() { + return "portal cooldown"; + } + +} diff --git a/src/test/skript/tests/syntaxes/expressions/ExprPortalCooldown.sk b/src/test/skript/tests/syntaxes/expressions/ExprPortalCooldown.sk new file mode 100644 index 00000000000..7d919d788da --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprPortalCooldown.sk @@ -0,0 +1,16 @@ +test "portal cooldown": + spawn zombie at spawn of world "world": + assert event-entity's portal cooldown is 0 seconds with "entity spawned with a portal cooldown" + set event-entity's portal cooldown to 25 ticks + assert event-entity's portal cooldown is 25 ticks with "portal cooldown set failed" + add 5 seconds to event-entity's portal cooldown + assert event-entity's portal cooldown is 125 ticks with "portal cooldown add ##1 failed" + remove 12 ticks from event-entity's portal cooldown + assert event-entity's portal cooldown is 113 ticks with "portal cooldown remove ##1 failed" + remove 999 ticks from event-entity's portal cooldown + assert event-entity's portal cooldown is 0 ticks with "portal cooldown remove ##2 failed" + delete event-entity's portal cooldown + assert event-entity's portal cooldown is 0 ticks with "portal cooldown delete failed" + reset event-entity's portal cooldown + assert event-entity's portal cooldown is 15 seconds with "portal cooldown reset failed" + delete event-entity From ebc09298698c57cf33cce4ad7b79273a2ec9673c Mon Sep 17 00:00:00 2001 From: Fusezion <fusezionstream@gmail.com> Date: Mon, 17 Jul 2023 02:01:35 -0400 Subject: [PATCH 364/619] Fix EffActionBar NullPointException when message is null (#5833) --- .../ch/njol/skript/effects/EffActionBar.java | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffActionBar.java b/src/main/java/ch/njol/skript/effects/EffActionBar.java index 09d62e9044c..db2f46f0a57 100644 --- a/src/main/java/ch/njol/skript/effects/EffActionBar.java +++ b/src/main/java/ch/njol/skript/effects/EffActionBar.java @@ -29,7 +29,7 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Effect; import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.SkriptParser; +import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.util.chat.BungeeConverter; import ch.njol.skript.util.chat.ChatMessages; import ch.njol.util.Kleenean; @@ -43,36 +43,35 @@ public class EffActionBar extends Effect { static { - Skript.registerEffect(EffActionBar.class, "send [the] action[ ]bar [with text] %string% to %players%"); + Skript.registerEffect(EffActionBar.class, "send [the] action[ ]bar [with text] %string% [to %players%]"); } - @SuppressWarnings("null") private Expression<String> message; - @SuppressWarnings("null") private Expression<Player> recipients; - @SuppressWarnings({"unchecked", "null"}) @Override - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final SkriptParser.ParseResult parser) { + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { message = (Expression<String>) exprs[0]; recipients = (Expression<Player>) exprs[1]; return true; } - @SuppressWarnings("deprecation") @Override - protected void execute(final Event e) { - String msg = message.getSingle(e); - assert msg != null; + @SuppressWarnings("deprecation") + protected void execute(Event event) { + String msg = message.getSingle(event); + if (msg == null) + return; BaseComponent[] components = BungeeConverter.convert(ChatMessages.parseToArray(msg)); - for (Player player : recipients.getArray(e)) + for (Player player : recipients.getArray(event)) player.spigot().sendMessage(ChatMessageType.ACTION_BAR, components); } @Override - public String toString(final @Nullable Event e, final boolean debug) { - return "send action bar " + message.toString(e, debug) + " to " + recipients.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "send action bar " + message.toString(event, debug) + " to " + recipients.toString(event, debug); } } From a11a98a9bf6035329da69d6b531d1521d67d26b9 Mon Sep 17 00:00:00 2001 From: eult <96553037+ahmadMsaleem@users.noreply.github.com> Date: Mon, 17 Jul 2023 08:13:17 +0200 Subject: [PATCH 365/619] Update the return function regex (#5814) --- src/main/java/ch/njol/skript/effects/EffReturn.java | 7 +++++-- .../java/ch/njol/skript/structures/StructFunction.java | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffReturn.java b/src/main/java/ch/njol/skript/effects/EffReturn.java index 2240c509767..13b563fefee 100644 --- a/src/main/java/ch/njol/skript/effects/EffReturn.java +++ b/src/main/java/ch/njol/skript/effects/EffReturn.java @@ -43,9 +43,12 @@ @Description("Makes a function return a value") @Examples({ "function double(i: number) :: number:", - "\treturn 2 * {_i}" + "\treturn 2 * {_i}", + "", + "function divide(i: number) returns number:", + "\treturn {_i} / 2" }) -@Since("2.2") +@Since("2.2, INSERT VERSION (returns aliases)") public class EffReturn extends Effect { static { diff --git a/src/main/java/ch/njol/skript/structures/StructFunction.java b/src/main/java/ch/njol/skript/structures/StructFunction.java index 9b52d4fd94e..ee0fbf5f76e 100644 --- a/src/main/java/ch/njol/skript/structures/StructFunction.java +++ b/src/main/java/ch/njol/skript/structures/StructFunction.java @@ -48,7 +48,10 @@ "\tbroadcast {_message} # our message argument is available in '{_message}'", "", "local function giveApple(amount: number) :: item:", - "\treturn {_amount} of apple" + "\treturn {_amount} of apple", + "", + "function getPoints(p: player) returns number:", + "\treturn {points::%{_p}%}" }) @Since("2.2, 2.7 (local functions)") public class StructFunction extends Structure { @@ -59,7 +62,7 @@ public class StructFunction extends Structure { static { Skript.registerStructure(StructFunction.class, - "[:local] function <(" + Functions.functionNamePattern + ")\\((.*)\\)(?:\\s*::\\s*(.+))?>" + "[:local] function <(" + Functions.functionNamePattern + ")\\((.*)\\)(?:\\s*(?:::| returns )\\s*(.+))?>" ); } From f0594a34570e5c098fca3d06ca08d1b738e69734 Mon Sep 17 00:00:00 2001 From: 3meraldK <48335651+3meraldK@users.noreply.github.com> Date: Wed, 19 Jul 2023 16:09:30 +0200 Subject: [PATCH 366/619] Catch the exception when pushing entity by non finite vector (#5765) --- src/main/java/ch/njol/skript/effects/EffPush.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/ch/njol/skript/effects/EffPush.java b/src/main/java/ch/njol/skript/effects/EffPush.java index c248a51acf4..8cda4d62ffe 100644 --- a/src/main/java/ch/njol/skript/effects/EffPush.java +++ b/src/main/java/ch/njol/skript/effects/EffPush.java @@ -77,6 +77,10 @@ protected void execute(final Event e) { final Vector mod = d.getDirection(en); if (v != null) mod.normalize().multiply(v.doubleValue()); + if (!(Double.isFinite(mod.getX()) && Double.isFinite(mod.getY()) && Double.isFinite(mod.getZ()))) { + // Some component of the mod vector is not finite, so just stop + return; + } en.setVelocity(en.getVelocity().add(mod)); // REMIND add NoCheatPlus exception to players } } From f3eeac0876536dd2a87c227a3dc795dc5a6a1e68 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Sat, 22 Jul 2023 10:09:00 -0600 Subject: [PATCH 367/619] [2.7.0 target] Fix ExprRawString using a Java 9 feature (#5799) Fix ExprRawString using a Java 9 feature (#5795) (cherry picked from commit 843774d0d9e6eb27ae252f116b119bd90388123b) Co-authored-by: _tud <mmbakkar06@gmail.com> --- src/main/java/ch/njol/skript/expressions/ExprRawString.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprRawString.java b/src/main/java/ch/njol/skript/expressions/ExprRawString.java index 6b9d907da1a..f96b0e3e3ca 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprRawString.java +++ b/src/main/java/ch/njol/skript/expressions/ExprRawString.java @@ -31,6 +31,7 @@ import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.skript.util.SkriptColor; import ch.njol.util.Kleenean; +import ch.njol.util.StringUtils; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @@ -82,7 +83,7 @@ protected String[] get(Event event) { for (String string : message.getArray(event)) { String raw = SkriptColor.replaceColorChar(string); if (raw.toLowerCase().contains("&x")) { - raw = HEX_PATTERN.matcher(raw).replaceAll(matchResult -> + raw = StringUtils.replaceAll(raw, HEX_PATTERN, matchResult -> "<#" + matchResult.group(1).replace("&", "") + '>'); } strings.add(raw); From 457a47d66c48c1d29ad43d960537ddba868b8343 Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Sat, 22 Jul 2023 19:23:21 +0300 Subject: [PATCH 368/619] Fix bugs in the random number expression (#5748) --- .../skript/expressions/ExprRandomNumber.java | 19 +++++++++++++------ ...ix bugs in the random number expression.sk | 8 ++++++++ 2 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 src/test/skript/tests/regressions/pull-5748-fix bugs in the random number expression.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprRandomNumber.java b/src/main/java/ch/njol/skript/expressions/ExprRandomNumber.java index 79ed6b650b8..d774cbd9848 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprRandomNumber.java +++ b/src/main/java/ch/njol/skript/expressions/ExprRandomNumber.java @@ -53,7 +53,6 @@ public class ExprRandomNumber extends SimpleExpression<Number> { "[a] random (:integer|number) (from|between) %number% (to|and) %number%"); } - private final Random random = ThreadLocalRandom.current(); private Expression<Number> from, to; private boolean isInteger; @@ -72,19 +71,27 @@ protected Number[] get(Event event) { Number from = this.from.getSingle(event); Number to = this.to.getSingle(event); - if (to == null || from == null) + if (to == null || from == null || !Double.isFinite(from.doubleValue()) || !Double.isFinite(to.doubleValue())) return new Number[0]; + Random random = ThreadLocalRandom.current(); double min = Math.min(from.doubleValue(), to.doubleValue()); double max = Math.max(from.doubleValue(), to.doubleValue()); if (isInteger) { - if (max - min < 1) + long inf = Math2.ceil(min); + long sup = Math2.floor(max); + if (max - min < 1 && inf - sup <= 1) { + if (sup == inf || min == inf) + return new Long[] {inf}; + if (max == sup) + return new Long[] {sup}; return new Long[0]; - return new Long[] {random.nextLong(Math2.ceil(min), Math2.floor(max) + 1)}; - } else { - return new Double[] {min + random.nextDouble() * (max - min)}; + } + return new Long[] {random.nextLong(inf, sup + 1)}; } + + return new Double[] {min + random.nextDouble() * (max - min)}; } @Override diff --git a/src/test/skript/tests/regressions/pull-5748-fix bugs in the random number expression.sk b/src/test/skript/tests/regressions/pull-5748-fix bugs in the random number expression.sk new file mode 100644 index 00000000000..504d87ead8c --- /dev/null +++ b/src/test/skript/tests/regressions/pull-5748-fix bugs in the random number expression.sk @@ -0,0 +1,8 @@ + +# Test not related to a made issue. Details at pull request. +test "random integer test": + assert random integer between 1 and 1 is 1 with "Random integer between 1 and 1 should be 1" + assert random integer between 1.2 and 1.2 is not set with "Random integer between 1.2 and 1.2 shouldn't return anything" + assert random integer between 1.2 and 1.3 is not set with "Random integer between 1.2 and 1.3 shouldn't return anything" + assert random integer between 1.9 and 2.1 is 2 with "Random integer between 1.9 and 2.1 should be 2" + assert random integer between 1.5 and 2 is 2 with "Random integer between 1 and 2 should be 2" From 7be44daf7b3292cc9c1175ef7bb9ec1db93ffbc7 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Tue, 25 Jul 2023 01:05:48 +0300 Subject: [PATCH 369/619] =?UTF-8?q?=F0=9F=9B=A0=20Fix=20ExprTarget=20UOE?= =?UTF-8?q?=20(#5844)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../njol/skript/expressions/ExprTarget.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprTarget.java b/src/main/java/ch/njol/skript/expressions/ExprTarget.java index 33d1a151f76..8324df70070 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTarget.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTarget.java @@ -115,17 +115,18 @@ public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { if (entity.equals(targetEvent.getEntity())) targetEvent.setTarget(target); } - return; - } - for (LivingEntity entity : getExpr().getArray(event)) { - if (entity instanceof Mob) { - ((Mob) entity).setTarget(target); - } else if (entity instanceof Player && mode == ChangeMode.DELETE) { - Entity playerTarget = getTarget(entity, type); - if (playerTarget != null && !(playerTarget instanceof OfflinePlayer)) - playerTarget.remove(); + } else { + for (LivingEntity entity : getExpr().getArray(event)) { + if (entity instanceof Mob) { + ((Mob) entity).setTarget(target); + } else if (entity instanceof Player && mode == ChangeMode.DELETE) { + Entity playerTarget = getTarget(entity, type); + if (playerTarget != null && !(playerTarget instanceof OfflinePlayer)) + playerTarget.remove(); + } } } + return; } super.change(event, delta, mode); } From 18674a9cffab6d5913c296908685ae39525d86bd Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Tue, 25 Jul 2023 18:02:23 -0600 Subject: [PATCH 370/619] [2.7.0 target] Fix ExprTarget UOE (#5856) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🛠 Fix ExprTarget UOE (#5844) (cherry picked from commit 7be44daf7b3292cc9c1175ef7bb9ec1db93ffbc7) Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> --- .../njol/skript/expressions/ExprTarget.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprTarget.java b/src/main/java/ch/njol/skript/expressions/ExprTarget.java index 33d1a151f76..8324df70070 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTarget.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTarget.java @@ -115,17 +115,18 @@ public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { if (entity.equals(targetEvent.getEntity())) targetEvent.setTarget(target); } - return; - } - for (LivingEntity entity : getExpr().getArray(event)) { - if (entity instanceof Mob) { - ((Mob) entity).setTarget(target); - } else if (entity instanceof Player && mode == ChangeMode.DELETE) { - Entity playerTarget = getTarget(entity, type); - if (playerTarget != null && !(playerTarget instanceof OfflinePlayer)) - playerTarget.remove(); + } else { + for (LivingEntity entity : getExpr().getArray(event)) { + if (entity instanceof Mob) { + ((Mob) entity).setTarget(target); + } else if (entity instanceof Player && mode == ChangeMode.DELETE) { + Entity playerTarget = getTarget(entity, type); + if (playerTarget != null && !(playerTarget instanceof OfflinePlayer)) + playerTarget.remove(); + } } } + return; } super.change(event, delta, mode); } From 79af43464e7ff49443d700cda974d7ecca2d17bd Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Tue, 25 Jul 2023 19:33:49 -0600 Subject: [PATCH 371/619] [2.7.0 target] Fix bugs in the random number expression (#5748) (#5853) Fix bugs in the random number expression (#5748) (cherry picked from commit 457a47d66c48c1d29ad43d960537ddba868b8343) Co-authored-by: _tud <mmbakkar06@gmail.com> --- .../skript/expressions/ExprRandomNumber.java | 19 +++++++++++++------ ...ix bugs in the random number expression.sk | 8 ++++++++ 2 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 src/test/skript/tests/regressions/pull-5748-fix bugs in the random number expression.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprRandomNumber.java b/src/main/java/ch/njol/skript/expressions/ExprRandomNumber.java index 79ed6b650b8..d774cbd9848 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprRandomNumber.java +++ b/src/main/java/ch/njol/skript/expressions/ExprRandomNumber.java @@ -53,7 +53,6 @@ public class ExprRandomNumber extends SimpleExpression<Number> { "[a] random (:integer|number) (from|between) %number% (to|and) %number%"); } - private final Random random = ThreadLocalRandom.current(); private Expression<Number> from, to; private boolean isInteger; @@ -72,19 +71,27 @@ protected Number[] get(Event event) { Number from = this.from.getSingle(event); Number to = this.to.getSingle(event); - if (to == null || from == null) + if (to == null || from == null || !Double.isFinite(from.doubleValue()) || !Double.isFinite(to.doubleValue())) return new Number[0]; + Random random = ThreadLocalRandom.current(); double min = Math.min(from.doubleValue(), to.doubleValue()); double max = Math.max(from.doubleValue(), to.doubleValue()); if (isInteger) { - if (max - min < 1) + long inf = Math2.ceil(min); + long sup = Math2.floor(max); + if (max - min < 1 && inf - sup <= 1) { + if (sup == inf || min == inf) + return new Long[] {inf}; + if (max == sup) + return new Long[] {sup}; return new Long[0]; - return new Long[] {random.nextLong(Math2.ceil(min), Math2.floor(max) + 1)}; - } else { - return new Double[] {min + random.nextDouble() * (max - min)}; + } + return new Long[] {random.nextLong(inf, sup + 1)}; } + + return new Double[] {min + random.nextDouble() * (max - min)}; } @Override diff --git a/src/test/skript/tests/regressions/pull-5748-fix bugs in the random number expression.sk b/src/test/skript/tests/regressions/pull-5748-fix bugs in the random number expression.sk new file mode 100644 index 00000000000..504d87ead8c --- /dev/null +++ b/src/test/skript/tests/regressions/pull-5748-fix bugs in the random number expression.sk @@ -0,0 +1,8 @@ + +# Test not related to a made issue. Details at pull request. +test "random integer test": + assert random integer between 1 and 1 is 1 with "Random integer between 1 and 1 should be 1" + assert random integer between 1.2 and 1.2 is not set with "Random integer between 1.2 and 1.2 shouldn't return anything" + assert random integer between 1.2 and 1.3 is not set with "Random integer between 1.2 and 1.3 shouldn't return anything" + assert random integer between 1.9 and 2.1 is 2 with "Random integer between 1.9 and 2.1 should be 2" + assert random integer between 1.5 and 2 is 2 with "Random integer between 1 and 2 should be 2" From 7454a8449b66ebf83ce51ba67b15bd99fb28df0a Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Wed, 26 Jul 2023 16:36:28 -0600 Subject: [PATCH 372/619] [2.7.0 target] Fix EffActionBar NullPointException when message is null (#5840) Fix EffActionBar NullPointException when message is null (#5833) (cherry picked from commit ebc09298698c57cf33cce4ad7b79273a2ec9673c) Co-authored-by: Fusezion <fusezionstream@gmail.com> --- .../ch/njol/skript/effects/EffActionBar.java | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffActionBar.java b/src/main/java/ch/njol/skript/effects/EffActionBar.java index 09d62e9044c..db2f46f0a57 100644 --- a/src/main/java/ch/njol/skript/effects/EffActionBar.java +++ b/src/main/java/ch/njol/skript/effects/EffActionBar.java @@ -29,7 +29,7 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Effect; import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.SkriptParser; +import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.util.chat.BungeeConverter; import ch.njol.skript.util.chat.ChatMessages; import ch.njol.util.Kleenean; @@ -43,36 +43,35 @@ public class EffActionBar extends Effect { static { - Skript.registerEffect(EffActionBar.class, "send [the] action[ ]bar [with text] %string% to %players%"); + Skript.registerEffect(EffActionBar.class, "send [the] action[ ]bar [with text] %string% [to %players%]"); } - @SuppressWarnings("null") private Expression<String> message; - @SuppressWarnings("null") private Expression<Player> recipients; - @SuppressWarnings({"unchecked", "null"}) @Override - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final SkriptParser.ParseResult parser) { + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { message = (Expression<String>) exprs[0]; recipients = (Expression<Player>) exprs[1]; return true; } - @SuppressWarnings("deprecation") @Override - protected void execute(final Event e) { - String msg = message.getSingle(e); - assert msg != null; + @SuppressWarnings("deprecation") + protected void execute(Event event) { + String msg = message.getSingle(event); + if (msg == null) + return; BaseComponent[] components = BungeeConverter.convert(ChatMessages.parseToArray(msg)); - for (Player player : recipients.getArray(e)) + for (Player player : recipients.getArray(event)) player.spigot().sendMessage(ChatMessageType.ACTION_BAR, components); } @Override - public String toString(final @Nullable Event e, final boolean debug) { - return "send action bar " + message.toString(e, debug) + " to " + recipients.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "send action bar " + message.toString(event, debug) + " to " + recipients.toString(event, debug); } } From 2c2c055985c23a98ecc999255ae390da7aa2723c Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Wed, 26 Jul 2023 16:45:39 -0600 Subject: [PATCH 373/619] Add warning to notify the user about using teleport in the spawn section (#5813) --- .../ch/njol/skript/effects/EffTeleport.java | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffTeleport.java b/src/main/java/ch/njol/skript/effects/EffTeleport.java index fd253af937b..3ba06d7d150 100644 --- a/src/main/java/ch/njol/skript/effects/EffTeleport.java +++ b/src/main/java/ch/njol/skript/effects/EffTeleport.java @@ -19,6 +19,8 @@ package ch.njol.skript.effects; import ch.njol.skript.Skript; +import ch.njol.skript.sections.EffSecSpawn; +import ch.njol.skript.sections.EffSecSpawn.SpawnEvent; import ch.njol.skript.bukkitutil.EntityUtils; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; @@ -29,6 +31,7 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.Trigger; import ch.njol.skript.lang.TriggerItem; +import ch.njol.skript.lang.TriggerSection; import ch.njol.skript.timings.SkriptTimings; import ch.njol.skript.util.Direction; import ch.njol.skript.variables.Variables; @@ -43,12 +46,16 @@ import org.eclipse.jdt.annotation.Nullable; @Name("Teleport") -@Description({"Teleport an entity to a specific location. ", - "This effect is delayed by default on Paper, meaning certain syntax such as the return effect for functions cannot be used after this effect.", - "The keyword 'force' indicates this effect will not be delayed, ", - "which may cause lag spikes or server crashes when using this effect to teleport entities to unloaded chunks."}) -@Examples({"teleport the player to {homes.%player%}", - "teleport the attacker to the victim"}) +@Description({ + "Teleport an entity to a specific location. ", + "This effect is delayed by default on Paper, meaning certain syntax such as the return effect for functions cannot be used after this effect.", + "The keyword 'force' indicates this effect will not be delayed, ", + "which may cause lag spikes or server crashes when using this effect to teleport entities to unloaded chunks." +}) +@Examples({ + "teleport the player to {homes.%player%}", + "teleport the attacker to the victim" +}) @Since("1.0") public class EffTeleport extends Effect { @@ -65,18 +72,23 @@ public class EffTeleport extends Effect { private boolean isAsync; - @SuppressWarnings({"unchecked", "null"}) @Override + @SuppressWarnings("unchecked") public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { entities = (Expression<Entity>) exprs[0]; location = Direction.combine((Expression<? extends Direction>) exprs[1], (Expression<? extends Location>) exprs[2]); isAsync = CAN_RUN_ASYNC && parseResult.mark == 0; + if (getParser().isCurrentEvent(SpawnEvent.class)) { + Skript.error("You cannot be teleporting an entity that hasn't spawned yet. Ensure you're using the location expression from the spawn section pattern."); + return false; + } + if (isAsync) getParser().setHasDelayBefore(Kleenean.UNKNOWN); // UNKNOWN because it isn't async if the chunk is already loaded. return true; } - + @Nullable @Override protected TriggerItem walk(Event e) { From 1460fdd981749904613d8d68fc384d337922e565 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Wed, 26 Jul 2023 16:58:08 -0600 Subject: [PATCH 374/619] Add proper error for BlockData failing (#5808) --- .../skript/classes/data/BukkitClasses.java | 131 +++++++++--------- .../java/ch/njol/skript/util/BlockUtils.java | 10 +- 2 files changed, 73 insertions(+), 68 deletions(-) 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 5caa14f02e2..a89c6aa78b4 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -284,73 +284,72 @@ public Block deserialize(final String s) { })); Classes.registerClass(new ClassInfo<>(BlockData.class, "blockdata") - .user("block ?datas?") - .name("Block Data") - .description("Block data is the detailed information about a block, referred to in Minecraft as BlockStates, " + - "allowing for the manipulation of different aspects of the block, including shape, waterlogging, direction the block is facing, " + - "and so much more. Information regarding each block's optional data can be found on Minecraft's Wiki. Find the block you're " + - "looking for and scroll down to 'Block States'. Different states must be separated by a semicolon (see examples). " + - "The 'minecraft:' namespace is optional, as well as are underscores.") - .examples("set block at player to campfire[lit=false]", - "set target block of player to oak stairs[facing=north;waterlogged=true]", - "set block at player to grass_block[snowy=true]", - "set loop-block to minecraft:chest[facing=north]", - "set block above player to oak_log[axis=y]", - "set target block of player to minecraft:oak_leaves[distance=2;persistent=false]") - .after("itemtype") - .requiredPlugins("Minecraft 1.13+") - .since("2.5") - .parser(new Parser<BlockData>() { - @Nullable - @Override - public BlockData parse(String s, ParseContext context) { - return BlockUtils.createBlockData(s); - } - - @Override - public String toString(BlockData o, int flags) { - return o.getAsString().replace(",", ";"); - } - - @Override - public String toVariableNameString(BlockData o) { - return "blockdata:" + o.getAsString(); - } - }) - .serializer(new Serializer<BlockData>() { - @Override - public Fields serialize(BlockData o) { - Fields f = new Fields(); - f.putObject("blockdata", o.getAsString()); - return f; - } - - @Override - public void deserialize(BlockData o, Fields f) { - assert false; - } - - @Override - protected BlockData deserialize(Fields f) throws StreamCorruptedException { - String data = f.getObject("blockdata", String.class); - assert data != null; - try { - return Bukkit.createBlockData(data); - } catch (IllegalArgumentException ex) { - throw new StreamCorruptedException("Invalid block data: " + data); + .user("block ?datas?") + .name("Block Data") + .description("Block data is the detailed information about a block, referred to in Minecraft as BlockStates, " + + "allowing for the manipulation of different aspects of the block, including shape, waterlogging, direction the block is facing, " + + "and so much more. Information regarding each block's optional data can be found on Minecraft's Wiki. Find the block you're " + + "looking for and scroll down to 'Block States'. Different states must be separated by a semicolon (see examples). " + + "The 'minecraft:' namespace is optional, as well as are underscores.") + .examples("set block at player to campfire[lit=false]", + "set target block of player to oak stairs[facing=north;waterlogged=true]", + "set block at player to grass_block[snowy=true]", + "set loop-block to minecraft:chest[facing=north]", + "set block above player to oak_log[axis=y]", + "set target block of player to minecraft:oak_leaves[distance=2;persistent=false]") + .after("itemtype") + .since("2.5") + .parser(new Parser<BlockData>() { + @Nullable + @Override + public BlockData parse(String input, ParseContext context) { + return BlockUtils.createBlockData(input); } - } - - @Override - public boolean mustSyncDeserialization() { - return true; - } - - @Override - protected boolean canBeInstantiated() { - return false; - } - })); + + @Override + public String toString(BlockData o, int flags) { + return o.getAsString().replace(",", ";"); + } + + @Override + public String toVariableNameString(BlockData o) { + return "blockdata:" + o.getAsString(); + } + }) + .serializer(new Serializer<BlockData>() { + @Override + public Fields serialize(BlockData o) { + Fields f = new Fields(); + f.putObject("blockdata", o.getAsString()); + return f; + } + + @Override + public void deserialize(BlockData o, Fields f) { + assert false; + } + + @Override + protected BlockData deserialize(Fields f) throws StreamCorruptedException { + String data = f.getObject("blockdata", String.class); + assert data != null; + try { + return Bukkit.createBlockData(data); + } catch (IllegalArgumentException ex) { + throw new StreamCorruptedException("Invalid block data: " + data); + } + } + + @Override + public boolean mustSyncDeserialization() { + return true; + } + + @Override + protected boolean canBeInstantiated() { + return false; + } + })); Classes.registerClass(new ClassInfo<>(Location.class, "location") .user("locations?") diff --git a/src/main/java/ch/njol/skript/util/BlockUtils.java b/src/main/java/ch/njol/skript/util/BlockUtils.java index f9292b6b46b..1e9c12fed01 100644 --- a/src/main/java/ch/njol/skript/util/BlockUtils.java +++ b/src/main/java/ch/njol/skript/util/BlockUtils.java @@ -18,6 +18,7 @@ */ package ch.njol.skript.util; +import ch.njol.skript.Skript; import ch.njol.skript.aliases.Aliases; import ch.njol.skript.aliases.ItemData; import ch.njol.skript.aliases.ItemType; @@ -103,7 +104,9 @@ public static BlockData createBlockData(String dataString) { data = data.replaceAll("\\s+\\[", "["); // And replace white space between namespace with underscores data = data.replace(" ", "_"); - + + String errorData = new String(data); + try { return Bukkit.createBlockData(data.startsWith("minecraft:") ? data : "minecraft:" + data); } catch (IllegalArgumentException ignored) { @@ -115,7 +118,10 @@ public static BlockData createBlockData(String dataString) { if (type == null) return null; return Bukkit.createBlockData(type.getMaterial(), data); - } catch (IllegalArgumentException | StringIndexOutOfBoundsException alsoIgnored) { + } catch (StringIndexOutOfBoundsException alsoIgnored) { + return null; + } catch (IllegalArgumentException alsoIgnored) { + Skript.error("Block data '" + errorData + "' is not valid for this material"); return null; } } From 1bd822928c484683d74e061f3dfe22f01fc606f3 Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Thu, 27 Jul 2023 03:24:13 +0300 Subject: [PATCH 375/619] Delete CondIsWithinLocation in favor of CondIsWithin (#5803) --- .../conditions/CondIsWithinLocation.java | 79 ------------------- 1 file changed, 79 deletions(-) delete mode 100644 src/main/java/ch/njol/skript/conditions/CondIsWithinLocation.java diff --git a/src/main/java/ch/njol/skript/conditions/CondIsWithinLocation.java b/src/main/java/ch/njol/skript/conditions/CondIsWithinLocation.java deleted file mode 100644 index bb3083ffd8b..00000000000 --- a/src/main/java/ch/njol/skript/conditions/CondIsWithinLocation.java +++ /dev/null @@ -1,79 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.conditions; - -import org.bukkit.Location; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.conditions.base.PropertyCondition; -import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Examples; -import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.Since; -import ch.njol.skript.lang.Condition; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.util.AABB; -import ch.njol.util.Kleenean; - -@Name("Is Within Location") -@Description({ - "Whether a location is within two other locations forming a cuboid.", - "Using the <a href='conditions.html#CondCompare'>is between</a> condition will refer to a straight line between locations." -}) -@Examples({ - "if player's location is within {_loc1} and {_loc2}:", - "\tsend \"You are in a PvP zone!\" to player" -}) -@Since("2.7") -public class CondIsWithinLocation extends Condition { - - static { - PropertyCondition.register(CondIsWithinLocation.class, "within %location% and %location%", "locations"); - } - - private Expression<Location> locsToCheck, loc1, loc2; - - @Override - @SuppressWarnings("unchecked") - public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - setNegated(matchedPattern == 1); - locsToCheck = (Expression<Location>) exprs[0]; - loc1 = (Expression<Location>) exprs[1]; - loc2 = (Expression<Location>) exprs[2]; - return true; - } - - @Override - public boolean check(Event event) { - Location one = loc1.getSingle(event); - Location two = loc2.getSingle(event); - if (one == null || two == null || one.getWorld() != two.getWorld()) - return false; - AABB box = new AABB(one, two); - return locsToCheck.check(event, box::contains, isNegated()); - } - - @Override - public String toString(@Nullable Event event, boolean debug) { - return locsToCheck.toString(event, debug) + " is within " + loc1.toString(event, debug) + " and " + loc2.toString(event, debug); - } - -} From 3d9d0dfcdc8070206dec253af14bdb64e558292f Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Wed, 26 Jul 2023 18:42:44 -0600 Subject: [PATCH 376/619] [2.7.0 target] Fix null pointer exception in ExprBlocks when using the ExprDirection without a number defined (#5793) Fix null pointer exception in ExprBlocks when using the ExprDirection without a number defined. (#5790) Update ExprBlocks.java (cherry picked from commit 549ebc380b34dd544c760d7a2c23faa8973a12f7) --- src/main/java/ch/njol/skript/expressions/ExprBlocks.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprBlocks.java b/src/main/java/ch/njol/skript/expressions/ExprBlocks.java index 35dea7f01a3..5eccfbb027b 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprBlocks.java +++ b/src/main/java/ch/njol/skript/expressions/ExprBlocks.java @@ -151,9 +151,11 @@ public Iterator<Block> iterator(Event event) { int distance = SkriptConfig.maxTargetBlockDistance.value(); if (this.direction instanceof ExprDirection) { Expression<Number> numberExpression = ((ExprDirection) this.direction).amount; - Number number = numberExpression.getSingle(event); - if (numberExpression != null && number != null) - distance = number.intValue(); + if (numberExpression != null) { + Number number = numberExpression.getSingle(event); + if (number != null) + distance = number.intValue(); + } } return new BlockLineIterator(location, vector, distance); } else { From bdfde15fc33b8e7abd23fa1354e7c8ac88d0b324 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Wed, 26 Jul 2023 19:16:38 -0600 Subject: [PATCH 377/619] [2.7.0 target] Fixes error not properly being caught (#5798) (#5806) Fixes error not properly being caught (#5798) Update Utils.java (cherry picked from commit 6caf20c5fe8acd49179e385e1819bd8ca0e57c7e) --- src/main/java/ch/njol/skript/util/Utils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/util/Utils.java b/src/main/java/ch/njol/skript/util/Utils.java index 12255d4d8d6..351574c7361 100644 --- a/src/main/java/ch/njol/skript/util/Utils.java +++ b/src/main/java/ch/njol/skript/util/Utils.java @@ -199,7 +199,7 @@ public static Class<?>[] getClasses(Plugin plugin, String basePackage, String... for (String c : classNames) { try { classes.add(Class.forName(c, true, plugin.getClass().getClassLoader())); - } catch (ClassNotFoundException ex) { + } catch (ClassNotFoundException | NoClassDefFoundError ex) { Skript.exception(ex, "Cannot load class " + c); } catch (ExceptionInInitializerError err) { Skript.exception(err.getCause(), "class " + c + " generated an exception while loading"); From 755f59cabdb009df39b597a2ace0b2e28d71f271 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Thu, 27 Jul 2023 05:14:17 -0600 Subject: [PATCH 378/619] [2.7.0 target] delete cond is within location (#5860) --- .../conditions/CondIsWithinLocation.java | 79 ------------------- 1 file changed, 79 deletions(-) delete mode 100644 src/main/java/ch/njol/skript/conditions/CondIsWithinLocation.java diff --git a/src/main/java/ch/njol/skript/conditions/CondIsWithinLocation.java b/src/main/java/ch/njol/skript/conditions/CondIsWithinLocation.java deleted file mode 100644 index bb3083ffd8b..00000000000 --- a/src/main/java/ch/njol/skript/conditions/CondIsWithinLocation.java +++ /dev/null @@ -1,79 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.conditions; - -import org.bukkit.Location; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.conditions.base.PropertyCondition; -import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Examples; -import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.Since; -import ch.njol.skript.lang.Condition; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.util.AABB; -import ch.njol.util.Kleenean; - -@Name("Is Within Location") -@Description({ - "Whether a location is within two other locations forming a cuboid.", - "Using the <a href='conditions.html#CondCompare'>is between</a> condition will refer to a straight line between locations." -}) -@Examples({ - "if player's location is within {_loc1} and {_loc2}:", - "\tsend \"You are in a PvP zone!\" to player" -}) -@Since("2.7") -public class CondIsWithinLocation extends Condition { - - static { - PropertyCondition.register(CondIsWithinLocation.class, "within %location% and %location%", "locations"); - } - - private Expression<Location> locsToCheck, loc1, loc2; - - @Override - @SuppressWarnings("unchecked") - public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - setNegated(matchedPattern == 1); - locsToCheck = (Expression<Location>) exprs[0]; - loc1 = (Expression<Location>) exprs[1]; - loc2 = (Expression<Location>) exprs[2]; - return true; - } - - @Override - public boolean check(Event event) { - Location one = loc1.getSingle(event); - Location two = loc2.getSingle(event); - if (one == null || two == null || one.getWorld() != two.getWorld()) - return false; - AABB box = new AABB(one, two); - return locsToCheck.check(event, box::contains, isNegated()); - } - - @Override - public String toString(@Nullable Event event, boolean debug) { - return locsToCheck.toString(event, debug) + " is within " + loc1.toString(event, debug) + " and " + loc2.toString(event, debug); - } - -} From e7ec6c9d89cc02e769ff54e4a00dda20fe0b0733 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Sat, 29 Jul 2023 15:47:59 -0600 Subject: [PATCH 379/619] Fix example in ExprTeleportCause (#5807) --- .../ch/njol/skript/expressions/ExprTeleportCause.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprTeleportCause.java b/src/main/java/ch/njol/skript/expressions/ExprTeleportCause.java index 4eb866f31bd..3fd20798e94 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTeleportCause.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTeleportCause.java @@ -32,8 +32,10 @@ @Name("Teleport Cause") @Description("The <a href='classes.html#teleportcause'>teleport cause</a> within a player <a href='events.html#teleport'>teleport</a> event.") -@Examples({"on teleport", - "\tteleport cause is nether portal, end portal or end gateway"}) +@Examples({ + "on teleport:", + "\tteleport cause is nether portal, end portal or end gateway" +}) @Since("2.2-dev35") public class ExprTeleportCause extends EventValueExpression<TeleportCause> { @@ -46,8 +48,8 @@ public ExprTeleportCause() { } @Override - public String toString(final @Nullable Event e, final boolean debug) { - return "the teleport cause"; + public String toString(@Nullable Event event, boolean debug) { + return "teleport cause"; } } From 4d6f42a76127444d2e8d4097f183f29594963396 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Mon, 31 Jul 2023 10:25:18 +0300 Subject: [PATCH 380/619] =?UTF-8?q?=F0=9F=9A=80=20Item=20cooldown=20expres?= =?UTF-8?q?sion=20+=20condition=20(#4198)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/ch/njol/skript/aliases/ItemType.java | 8 + .../conditions/CondHasItemCooldown.java | 81 ++++++++++ .../skript/expressions/ExprItemCooldown.java | 144 ++++++++++++++++++ 3 files changed, 233 insertions(+) create mode 100644 src/main/java/ch/njol/skript/conditions/CondHasItemCooldown.java create mode 100644 src/main/java/ch/njol/skript/expressions/ExprItemCooldown.java diff --git a/src/main/java/ch/njol/skript/aliases/ItemType.java b/src/main/java/ch/njol/skript/aliases/ItemType.java index 7c189f72bf6..83d58f1ca4c 100644 --- a/src/main/java/ch/njol/skript/aliases/ItemType.java +++ b/src/main/java/ch/njol/skript/aliases/ItemType.java @@ -345,6 +345,14 @@ public boolean hasBlock() { } return false; } + + /** + * Useful for checking if materials represent an item or a block. Materials that are not items don't have ItemData + * @return Whether this ItemType has at least one ItemData that represents it whether it's a block or an item + */ + public boolean hasType() { + return !types.isEmpty(); + } /** * Sets the given block to this ItemType diff --git a/src/main/java/ch/njol/skript/conditions/CondHasItemCooldown.java b/src/main/java/ch/njol/skript/conditions/CondHasItemCooldown.java new file mode 100644 index 00000000000..be79e48220b --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondHasItemCooldown.java @@ -0,0 +1,81 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.conditions; + +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.conditions.base.PropertyCondition; +import ch.njol.skript.conditions.base.PropertyCondition.PropertyType; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Condition; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Has Item Cooldown") +@Description("Check whether a cooldown is active on the specified material for a specific player.") +@Examples({ + "if player has player's tool on cooldown:", + "\tsend \"You can't use this item right now. Wait %item cooldown of player's tool for player%\"" +}) +@Since("INSERT VERSION") +public class CondHasItemCooldown extends Condition { + + static { + Skript.registerCondition(CondHasItemCooldown.class, + "%players% (has|have) [([an] item|a)] cooldown (on|for) %itemtypes%", + "%players% (has|have) %itemtypes% on [(item|a)] cooldown", + "%players% (doesn't|does not|do not|don't) have [([an] item|a)] cooldown (on|for) %itemtypes%", + "%players% (doesn't|does not|do not|don't) have %itemtypes% on [(item|a)] cooldown"); + } + + private Expression<Player> players; + private Expression<ItemType> itemtypes; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + players = (Expression<Player>) exprs[0]; + itemtypes = (Expression<ItemType>) exprs[1]; + setNegated(matchedPattern > 1); + return true; + } + + @Override + public boolean check(Event event) { + return players.check(event, (player) -> + itemtypes.check(event, (itemType) -> + itemType.hasType() && player.hasCooldown(itemType.getMaterial()) + ) + ); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return PropertyCondition.toString(this, PropertyType.HAVE, event, debug, players, + itemtypes.toString(event, debug) + " on cooldown"); + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprItemCooldown.java b/src/main/java/ch/njol/skript/expressions/ExprItemCooldown.java new file mode 100644 index 00000000000..72bcd192d56 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprItemCooldown.java @@ -0,0 +1,144 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.skript.util.Timespan; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.List; +import java.util.stream.Collectors; + +@Name("Item Cooldown") +@Description("Change the cooldown of a specific material to a certain amount of <a href='./classes.html#timespan'>Timespan</a>.") +@Examples({ + "on right click using stick:", + "\tset item cooldown of player's tool for player to 1 minute", + "\tset item cooldown of stone and grass for all players to 20 seconds", + "\treset item cooldown of cobblestone and dirt for all players" +}) +@Since("INSERT VERSION") +public class ExprItemCooldown extends SimpleExpression<Timespan> { + + static { + Skript.registerExpression(ExprItemCooldown.class, Timespan.class, ExpressionType.COMBINED, + "[the] [item] cooldown of %itemtypes% for %players%", + "%players%'[s] [item] cooldown for %itemtypes%"); + } + + @SuppressWarnings("NotNullFieldNotInitialized") + private Expression<Player> players; + @SuppressWarnings("NotNullFieldNotInitialized") + private Expression<ItemType> itemtypes; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + players = (Expression<Player>) exprs[matchedPattern ^ 1]; + itemtypes = (Expression<ItemType>) exprs[matchedPattern]; + return true; + } + + @Override + protected Timespan[] get(Event event) { + Player[] players = this.players.getArray(event); + + List<ItemType> itemTypes = this.itemtypes.stream(event) + .filter(ItemType::hasType) + .collect(Collectors.toList()); + + Timespan[] timespan = new Timespan[players.length * itemTypes.size()]; + + int i = 0; + for (Player player : players) { + for (ItemType itemType : itemTypes) { + timespan[i++] = Timespan.fromTicks_i(player.getCooldown(itemType.getMaterial())); + } + } + return timespan; + } + + @Override + @Nullable + public Class<?>[] acceptChange(ChangeMode mode) { + return mode == ChangeMode.REMOVE_ALL ? null : CollectionUtils.array(Timespan.class); + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + if (mode != ChangeMode.RESET && mode != ChangeMode.DELETE && delta == null) + return; + + int ticks = delta != null ? (int) ((Timespan) delta[0]).getTicks_i() : 0; // 0 for DELETE/RESET + Player[] players = this.players.getArray(event); + List<ItemType> itemTypes = this.itemtypes.stream(event) + .filter(ItemType::hasType) + .collect(Collectors.toList()); + + for (Player player : players) { + for (ItemType itemtype : itemTypes) { + Material material = itemtype.getMaterial(); + switch (mode) { + case RESET: + case DELETE: + case SET: + player.setCooldown(material, ticks); + break; + case REMOVE: + player.setCooldown(material, Math.max(player.getCooldown(material) - ticks, 0)); + break; + case ADD: + player.setCooldown(material, player.getCooldown(material) + ticks); + break; + } + } + } + } + + @Override + public boolean isSingle() { + return players.isSingle() && itemtypes.isSingle(); + } + + @Override + public Class<? extends Timespan> getReturnType() { + return Timespan.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "cooldown of " + itemtypes.toString(event, debug) + " for " + players.toString(event, debug); + } + +} From 21d242003713dc21734a11ca3e078d7ad537af53 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Mon, 31 Jul 2023 01:16:19 -0700 Subject: [PATCH 381/619] Adds CondHasLineOfSight (#5562) --- .../skript/conditions/CondHasLineOfSight.java | 82 +++++++++++++++++++ .../syntaxes/conditions/CondHasLineOfSight.sk | 18 ++++ 2 files changed, 100 insertions(+) create mode 100644 src/main/java/ch/njol/skript/conditions/CondHasLineOfSight.java create mode 100644 src/test/skript/tests/syntaxes/conditions/CondHasLineOfSight.sk diff --git a/src/main/java/ch/njol/skript/conditions/CondHasLineOfSight.java b/src/main/java/ch/njol/skript/conditions/CondHasLineOfSight.java new file mode 100644 index 00000000000..dd4a18f9ab0 --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondHasLineOfSight.java @@ -0,0 +1,82 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.conditions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Condition; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Has Line of Sight") +@Description("Checks whether living entities have an unobstructed line of sight to other entities or locations.") +@Examples({ + "player has direct line of sight to location 5 blocks to the right of player", + "victim has line of sight to attacker", + "player has no line of sight to location 100 blocks in front of player" +}) +@Since("INSERT VERSION") +public class CondHasLineOfSight extends Condition { + + static { + Skript.registerCondition(CondHasLineOfSight.class, + "%livingentities% (has|have) [a] [direct] line of sight to %entities/locations%", + "%livingentities% does(n't| not) have [a] [direct] line of sight to %entities/locations%", + "%livingentities% (has|have) no [direct] line of sight to %entities/locations%"); + } + + private Expression<LivingEntity> viewers; + private Expression<?> targets; + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + viewers = (Expression<LivingEntity>) exprs[0]; + targets = exprs[1]; + setNegated(matchedPattern > 0); + return true; + } + + @Override + public boolean check(Event event) { + return targets.check(event, (target) -> { + if (target instanceof Entity) { + return viewers.check(event, (viewer) -> viewer.hasLineOfSight((Entity) target)); + } else if (target instanceof Location) { + return viewers.check(event, (viewer) -> viewer.hasLineOfSight((Location) target)); + } else { + return false; + } + }, isNegated()); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return viewers.toString(event, debug) + " has" + (isNegated() ? " no" : "") + " line of sight to " + targets.toString(event,debug); + } + +} diff --git a/src/test/skript/tests/syntaxes/conditions/CondHasLineOfSight.sk b/src/test/skript/tests/syntaxes/conditions/CondHasLineOfSight.sk new file mode 100644 index 00000000000..63f8faff16e --- /dev/null +++ b/src/test/skript/tests/syntaxes/conditions/CondHasLineOfSight.sk @@ -0,0 +1,18 @@ +test "line of sight": + set {_a} to (spawn of world "world") + set {_b} to {_a} ~ vector(0, 10, 0) + loop blocks between {_a} and {_b}: + set {_block::%loop-block%} to type of loop-block + set loop-block to air + spawn pig at spawn of world "world" + set {_pig1} to last spawned entity + spawn pig at {_b}: + assert pig has line of sight to {_pig1} with "Pigs should have line of sight to each other 1" + assert {_pig1} has line of sight to pig with "Pigs should have line of sight to each other 2" + set block at ({_a} ~ vector(0, 5, 0)) to stone + assert pig has no line of sight to {_pig1} with "Pigs should not have line of sight to each other 1" + assert {_pig1} has no line of sight to pig with "Pigs should not have line of sight to each other 2" + delete pig + delete entity within {_pig1} + loop blocks between {_a} and {_b}: + set loop-block to {_block::%loop-block%} From 996b57e3c68466ef0681c68b6809a91d86cbe54b Mon Sep 17 00:00:00 2001 From: DelayedGaming <72163224+DelayedGaming@users.noreply.github.com> Date: Tue, 1 Aug 2023 06:11:49 +0800 Subject: [PATCH 382/619] New Expression: Loaded Chunks of World (#5662) * Initial * Support plural * Merge with ExprChunk * Fix * Fix 2 * Cleanup class to obey code convention * Requested changes * Fix indent * Indent docs and improved pattern --------- Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> --- .../ch/njol/skript/expressions/ExprChunk.java | 106 +++++++++++------- 1 file changed, 66 insertions(+), 40 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprChunk.java b/src/main/java/ch/njol/skript/expressions/ExprChunk.java index a1d384edf10..d047a73c7ad 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprChunk.java +++ b/src/main/java/ch/njol/skript/expressions/ExprChunk.java @@ -18,83 +18,109 @@ */ package ch.njol.skript.expressions; +import java.util.Arrays; + import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; -import ch.njol.skript.expressions.base.PropertyExpression; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.skript.util.Direction; import ch.njol.util.Kleenean; import org.bukkit.Chunk; import org.bukkit.Location; +import org.bukkit.World; import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; -/** - * @author Peter Güttinger - */ @Name("Chunk") -@Description("The <a href='./classes.html#chunk'>chunk</a> a block, location or entity is in.") -@Examples("add the chunk at the player to {protected chunks::*}") -@Since("2.0") -public class ExprChunk extends PropertyExpression<Location, Chunk> { +@Description("Returns the <a href='./classes.html#chunk'>chunk</a> of a block, location or entity is in, or a list of the loaded chunks of a world.") +@Examples({ + "add the chunk at the player to {protected chunks::*}", + "set {_chunks::*} to the loaded chunks of the player's world" +}) +@Since("2.0, INSERT VERSION (loaded chunks)") +public class ExprChunk extends SimpleExpression<Chunk> { static { - Skript.registerExpression(ExprChunk.class, Chunk.class, ExpressionType.PROPERTY, "[the] chunk[s] (of|%-directions%) %locations%", "%locations%'[s] chunk[s]"); + Skript.registerExpression(ExprChunk.class, Chunk.class, ExpressionType.COMBINED, + "[(all [[of] the]|the)] chunk[s] (of|%-directions%) %locations%", + "%locations%'[s] chunk[s]", + "[(all [[of] the]|the)] loaded chunks (of|in) %worlds%" + ); } - - @SuppressWarnings("null") + + private int pattern; private Expression<Location> locations; - - @SuppressWarnings({"unchecked", "null"}) + private Expression<World> worlds; + @Override - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { - if (matchedPattern == 0) { + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + pattern = matchedPattern; + if (pattern == 0) { locations = (Expression<Location>) exprs[1]; - if (exprs[0] != null) + if (exprs[0] != null) { locations = Direction.combine((Expression<? extends Direction>) exprs[0], locations); - } else { + } + } else if (pattern == 1) { locations = (Expression<Location>) exprs[0]; + } else { + worlds = ((Expression<World>) exprs[0]); } - setExpr(locations); return true; } - - @Override - protected Chunk[] get(final Event e, final Location[] source) { - return get(source, Location::getChunk); - } - - @Override - public Class<? extends Chunk> getReturnType() { - return Chunk.class; - } - + @Override - public String toString(final @Nullable Event e, final boolean debug) { - return "the chunk at " + locations.toString(e, debug); + protected Chunk[] get(Event event) { + if (pattern != 2) { + return locations.stream(event) + .map(Location::getChunk) + .toArray(Chunk[]::new); + } + return worlds.stream(event) + .flatMap(world -> Arrays.stream(world.getLoadedChunks())) + .toArray(Chunk[]::new); } - + @Override @Nullable - public Class<?>[] acceptChange(final ChangeMode mode) { + public Class<?>[] acceptChange(ChangeMode mode) { if (mode == ChangeMode.RESET) return new Class[0]; return null; } - + @Override - public void change(final Event e, final @Nullable Object[] delta, final ChangeMode mode) { + @SuppressWarnings("deprecation") + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { assert mode == ChangeMode.RESET; - - final Chunk[] cs = getArray(e); - for (final Chunk c : cs) - c.getWorld().regenerateChunk(c.getX(), c.getZ()); + for (Chunk chunk : get(event)) + chunk.getWorld().regenerateChunk(chunk.getX(), chunk.getZ()); + } + + @Override + public boolean isSingle() { + if (pattern == 2) + return false; + return locations.isSingle(); + } + + @Override + public Class<? extends Chunk> getReturnType() { + return Chunk.class; } + @Override + public String toString(@Nullable Event event, boolean debug) { + if (pattern == 2) + return "loaded chunks of " + worlds.toString(event, debug); + return "chunk at " + locations.toString(event, debug); + } + } From a580caffc32440139082217859bcebf3a0008af7 Mon Sep 17 00:00:00 2001 From: DelayedGaming <72163224+DelayedGaming@users.noreply.github.com> Date: Tue, 1 Aug 2023 17:17:17 +0800 Subject: [PATCH 383/619] Adds InventoryMoveItemEvent (#5462) --- .../classes/data/BukkitEventValues.java | 46 ++++++------- .../java/ch/njol/skript/events/EvtItem.java | 19 ++++- .../skript/expressions/ExprEvtInitiator.java | 69 +++++++++++++++++++ 3 files changed, 108 insertions(+), 26 deletions(-) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprEvtInitiator.java 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 79072ed5454..ac6159e14b5 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -23,6 +23,28 @@ import java.util.List; import java.util.Set; +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.Aliases; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.command.CommandEvent; +import ch.njol.skript.events.bukkit.ScriptEvent; +import ch.njol.skript.events.bukkit.SkriptStartEvent; +import ch.njol.skript.events.bukkit.SkriptStopEvent; +import ch.njol.skript.registrations.EventValues; +import ch.njol.skript.util.BlockStateBlock; +import ch.njol.skript.util.BlockUtils; +import ch.njol.skript.util.DelayedChangeBlock; +import ch.njol.skript.util.Direction; +import ch.njol.skript.util.EnchantmentType; +import ch.njol.skript.util.Getter; +import ch.njol.skript.util.slot.InventorySlot; +import ch.njol.skript.util.slot.Slot; +import com.destroystokyo.paper.event.block.AnvilDamagedEvent; +import com.destroystokyo.paper.event.entity.ProjectileCollideEvent; +import com.destroystokyo.paper.event.player.PlayerArmorChangeEvent; +import io.papermc.paper.event.entity.EntityMoveEvent; +import io.papermc.paper.event.player.PlayerInventorySlotChangeEvent; +import io.papermc.paper.event.player.PlayerTradeEvent; import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.FireworkEffect; @@ -143,30 +165,6 @@ import org.bukkit.potion.PotionEffectType; import org.eclipse.jdt.annotation.Nullable; -import com.destroystokyo.paper.event.block.AnvilDamagedEvent; -import com.destroystokyo.paper.event.entity.ProjectileCollideEvent; -import com.destroystokyo.paper.event.player.PlayerArmorChangeEvent; - -import ch.njol.skript.Skript; -import ch.njol.skript.aliases.Aliases; -import ch.njol.skript.aliases.ItemType; -import ch.njol.skript.command.CommandEvent; -import ch.njol.skript.events.bukkit.ScriptEvent; -import ch.njol.skript.events.bukkit.SkriptStartEvent; -import ch.njol.skript.events.bukkit.SkriptStopEvent; -import ch.njol.skript.registrations.EventValues; -import ch.njol.skript.util.BlockStateBlock; -import ch.njol.skript.util.BlockUtils; -import ch.njol.skript.util.DelayedChangeBlock; -import ch.njol.skript.util.Direction; -import ch.njol.skript.util.EnchantmentType; -import ch.njol.skript.util.Getter; -import ch.njol.skript.util.slot.InventorySlot; -import ch.njol.skript.util.slot.Slot; -import io.papermc.paper.event.entity.EntityMoveEvent; -import io.papermc.paper.event.player.PlayerInventorySlotChangeEvent; -import io.papermc.paper.event.player.PlayerTradeEvent; - /** * @author Peter Güttinger */ diff --git a/src/main/java/ch/njol/skript/events/EvtItem.java b/src/main/java/ch/njol/skript/events/EvtItem.java index 4d5e31cb10e..b8c891f7cc2 100644 --- a/src/main/java/ch/njol/skript/events/EvtItem.java +++ b/src/main/java/ch/njol/skript/events/EvtItem.java @@ -18,6 +18,7 @@ */ package ch.njol.skript.events; +import ch.njol.skript.lang.util.SimpleEvent; import ch.njol.skript.sections.EffSecSpawn; import org.bukkit.event.Event; import org.bukkit.event.block.BlockDispenseEvent; @@ -28,6 +29,7 @@ import org.bukkit.event.entity.ItemSpawnEvent; import org.bukkit.event.inventory.CraftItemEvent; import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryMoveItemEvent; import org.bukkit.event.inventory.PrepareItemCraftEvent; import org.bukkit.event.player.PlayerDropItemEvent; import org.bukkit.event.player.PlayerItemConsumeEvent; @@ -127,6 +129,17 @@ public class EvtItem extends SkriptEvent { .examples("on item merge of gold blocks:", " cancel event") .since("2.2-dev35"); + Skript.registerEvent("Inventory Item Move", SimpleEvent.class, InventoryMoveItemEvent.class, "inventory item (move|transport)") + .description( + "Called when an entity or block (e.g. hopper) tries to move items directly from one inventory to another.", + "When this event is called, the initiator may have already removed the item from the source inventory and is ready to move it into the destination inventory.", + "If this event is cancelled, the items will be returned to the source inventory." + ) + .examples( + "on inventory item move:", + "\tbroadcast \"%holder of past event-inventory% is transporting %event-item% to %holder of event-inventory%!\"" + ) + .since("INSERT VERSION"); } @Nullable @@ -184,6 +197,8 @@ public boolean check(final Event event) { is = ((ItemDespawnEvent) event).getEntity().getItemStack(); } else if (event instanceof ItemMergeEvent) { is = ((ItemMergeEvent) event).getTarget().getItemStack(); + } else if (event instanceof InventoryMoveItemEvent) { + is = ((InventoryMoveItemEvent) event).getItem(); } else { assert false; return false; @@ -201,8 +216,8 @@ public boolean check(final ItemType t) { } @Override - public String toString(final @Nullable Event e, final boolean debug) { - return "dispense/spawn/drop/craft/pickup/consume/break/despawn/merge" + (types == null ? "" : " of " + types); + public String toString(@Nullable Event event, boolean debug) { + return "dispense/spawn/drop/craft/pickup/consume/break/despawn/merge/move" + (types == null ? "" : " of " + types); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprEvtInitiator.java b/src/main/java/ch/njol/skript/expressions/ExprEvtInitiator.java new file mode 100644 index 00000000000..678c53037d2 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprEvtInitiator.java @@ -0,0 +1,69 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Events; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.EventValueExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; + +import org.bukkit.event.inventory.InventoryMoveItemEvent; +import org.bukkit.inventory.Inventory; + +@Name("Initiator Inventory") +@Description("Returns the initiator inventory in an on <a href=\"./events.html?search=#inventory_item_move\">inventory item move</a> event.") +@Examples({ + "on inventory item move:", + "\tif holder of event-initiator-inventory is a chest:", + "broadcast \"Item transport requested at %location at holder of event-initiator-inventory%...\"" +}) +@Events("Inventory Item Move") +@Since("INSERT VERSION") +public class ExprEvtInitiator extends EventValueExpression<Inventory> { + + static { + Skript.registerExpression(ExprEvtInitiator.class, Inventory.class, ExpressionType.SIMPLE, "[the] [event-]initiator[( |-)inventory]"); + } + + public ExprEvtInitiator() { + super(Inventory.class); + } + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(InventoryMoveItemEvent.class)) { + Skript.error("'event-initiator' can only be used in an 'inventory item move' event."); + return false; + } + return super.init(exprs, matchedPattern, isDelayed, parseResult); + } + + @Override + public String toString() { + return "event-initiator-inventory"; + } + +} From 0c4a190b0d1d54a0a5ce4a81127b744cefd38cc9 Mon Sep 17 00:00:00 2001 From: DelayedGaming <72163224+DelayedGaming@users.noreply.github.com> Date: Wed, 2 Aug 2023 17:07:57 +0800 Subject: [PATCH 384/619] New Expression: Attached Block of Arrow (#5589) --- .../skript/expressions/ExprAttachedBlock.java | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprAttachedBlock.java diff --git a/src/main/java/ch/njol/skript/expressions/ExprAttachedBlock.java b/src/main/java/ch/njol/skript/expressions/ExprAttachedBlock.java new file mode 100644 index 00000000000..3745940fe82 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprAttachedBlock.java @@ -0,0 +1,68 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import org.bukkit.block.Block; +import org.bukkit.entity.AbstractArrow; +import org.bukkit.entity.Arrow; +import org.bukkit.entity.Projectile; +import org.jetbrains.annotations.Nullable; + +@Name("Arrow Attached Block") +@Description("Returns the attached block of an arrow.") +@Examples("set hit block of last shot arrow to diamond block") +@Since("INSERT VERSION") +public class ExprAttachedBlock extends SimplePropertyExpression<Projectile, Block> { + + private static final boolean HAS_ABSTRACT_ARROW = Skript.classExists("org.bukkit.entity.AbstractArrow"); + + static { + register(ExprAttachedBlock.class, Block.class, "(attached|hit) block", "projectiles"); + } + + @Override + @Nullable + public Block convert(Projectile projectile) { + if (HAS_ABSTRACT_ARROW) { + if (projectile instanceof AbstractArrow) { + return ((AbstractArrow) projectile).getAttachedBlock(); + } + } else if (projectile instanceof Arrow) { + return ((Arrow) projectile).getAttachedBlock(); + } + return null; + } + + @Override + public Class<? extends Block> getReturnType() { + return Block.class; + } + + @Override + public String getPropertyName() { + return "attached block"; + } + +} From 7632dd8fb367778bf9f28b59e4f147f091d37302 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Thu, 3 Aug 2023 15:48:51 -0600 Subject: [PATCH 385/619] Bump master to 2.8.0-dev (#5734) --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 815459f135e..2d552275498 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ groupid=ch.njol name=skript -version=2.7.0-beta3 +version=2.8.0-dev jarName=Skript.jar testEnv=java17/paper-1.20.1 testEnvJavaVersion=17 From aa2e8f25fa9619f0c8dead3a2840aa78ea206a0f Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Tue, 8 Aug 2023 01:30:50 +0300 Subject: [PATCH 386/619] =?UTF-8?q?=F0=9F=9A=80=20Add=20Teleport=20Cause?= =?UTF-8?q?=20EXIT=5FBED=20(#5887)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/lang/default.lang | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 02909f887b2..cc84e272f48 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -1314,6 +1314,7 @@ teleport causes: spectate: spectate, spectator unknown: unknown dismount: dismount, dismounted + exit_bed: exit bed, exiting bed, bed exit # -- Game Modes -- game modes: From c0c13e55353422bd36de16f1d5f913a0a1e19eb4 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Tue, 8 Aug 2023 02:26:06 +0300 Subject: [PATCH 387/619] =?UTF-8?q?[2.7]=20=F0=9F=9A=80=20Add=20Teleport?= =?UTF-8?q?=20Cause=20EXIT=5FBED=20(#5888)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add exit bed teleport cause --- src/main/resources/lang/default.lang | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index e86820ed0c2..00236cd6d6b 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -1314,6 +1314,7 @@ teleport causes: spectate: spectate, spectator unknown: unknown dismount: dismount, dismounted + exit_bed: exit bed, exiting bed, bed exit # -- Game Modes -- game modes: From 6fdafdc5f19298e9a9a63b827cf9402335b57106 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Tue, 8 Aug 2023 14:39:05 -0400 Subject: [PATCH 388/619] Address Variable Loading Changes (#5892) Remove delayed task call around variable loading It is already in a delayed task. --- src/main/java/ch/njol/skript/Skript.java | 85 ++++++++++++------------ 1 file changed, 42 insertions(+), 43 deletions(-) diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index dab73c2b5a6..45d076b7846 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -603,52 +603,51 @@ public void run() { Documentation.generate(); // TODO move to test classes? - - Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.this, () -> { - if (logNormal()) - info("Loading variables..."); - long vls = System.currentTimeMillis(); - - LogHandler h = SkriptLogger.startLogHandler(new ErrorDescLogHandler() { - @Override - public LogResult log(final LogEntry entry) { - super.log(entry); - if (entry.level.intValue() >= Level.SEVERE.intValue()) { - logEx(entry.message); // no [Skript] prefix - return LogResult.DO_NOT_LOG; - } else { - return LogResult.LOG; - } - } - - @Override - protected void beforeErrors() { - logEx(); - logEx("===!!!=== Skript variable load error ===!!!==="); - logEx("Unable to load (all) variables:"); - } - - @Override - protected void afterErrors() { - logEx(); - logEx("Skript will work properly, but old variables might not be available at all and new ones may or may not be saved until Skript is able to create a backup of the old file and/or is able to connect to the database (which requires a restart of Skript)!"); - logEx(); + + // Variable loading + if (logNormal()) + info("Loading variables..."); + long vls = System.currentTimeMillis(); + + LogHandler h = SkriptLogger.startLogHandler(new ErrorDescLogHandler() { + @Override + public LogResult log(final LogEntry entry) { + super.log(entry); + if (entry.level.intValue() >= Level.SEVERE.intValue()) { + logEx(entry.message); // no [Skript] prefix + return LogResult.DO_NOT_LOG; + } else { + return LogResult.LOG; } - }); - - try (CountingLogHandler c = new CountingLogHandler(SkriptLogger.SEVERE).start()) { - if (!Variables.load()) - if (c.getCount() == 0) - error("(no information available)"); - } finally { - h.stop(); } - - long vld = System.currentTimeMillis() - vls; - if (logNormal()) - info("Loaded " + Variables.numVariables() + " variables in " + ((vld / 100) / 10.) + " seconds"); + + @Override + protected void beforeErrors() { + logEx(); + logEx("===!!!=== Skript variable load error ===!!!==="); + logEx("Unable to load (all) variables:"); + } + + @Override + protected void afterErrors() { + logEx(); + logEx("Skript will work properly, but old variables might not be available at all and new ones may or may not be saved until Skript is able to create a backup of the old file and/or is able to connect to the database (which requires a restart of Skript)!"); + logEx(); + } }); - + + try (CountingLogHandler c = new CountingLogHandler(SkriptLogger.SEVERE).start()) { + if (!Variables.load()) + if (c.getCount() == 0) + error("(no information available)"); + } finally { + h.stop(); + } + + long vld = System.currentTimeMillis() - vls; + if (logNormal()) + info("Loaded " + Variables.numVariables() + " variables in " + ((vld / 100) / 10.) + " seconds"); + // Skript initialization done debug("Early init done"); From 2cbbf6c829a791c1286c9eec63f838718b0b2967 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Wed, 9 Aug 2023 23:00:12 +0300 Subject: [PATCH 389/619] [2.7] Address Variable Loading Changes (#5897) --- src/main/java/ch/njol/skript/Skript.java | 85 ++++++++++++------------ 1 file changed, 42 insertions(+), 43 deletions(-) diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index dab73c2b5a6..45d076b7846 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -603,52 +603,51 @@ public void run() { Documentation.generate(); // TODO move to test classes? - - Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.this, () -> { - if (logNormal()) - info("Loading variables..."); - long vls = System.currentTimeMillis(); - - LogHandler h = SkriptLogger.startLogHandler(new ErrorDescLogHandler() { - @Override - public LogResult log(final LogEntry entry) { - super.log(entry); - if (entry.level.intValue() >= Level.SEVERE.intValue()) { - logEx(entry.message); // no [Skript] prefix - return LogResult.DO_NOT_LOG; - } else { - return LogResult.LOG; - } - } - - @Override - protected void beforeErrors() { - logEx(); - logEx("===!!!=== Skript variable load error ===!!!==="); - logEx("Unable to load (all) variables:"); - } - - @Override - protected void afterErrors() { - logEx(); - logEx("Skript will work properly, but old variables might not be available at all and new ones may or may not be saved until Skript is able to create a backup of the old file and/or is able to connect to the database (which requires a restart of Skript)!"); - logEx(); + + // Variable loading + if (logNormal()) + info("Loading variables..."); + long vls = System.currentTimeMillis(); + + LogHandler h = SkriptLogger.startLogHandler(new ErrorDescLogHandler() { + @Override + public LogResult log(final LogEntry entry) { + super.log(entry); + if (entry.level.intValue() >= Level.SEVERE.intValue()) { + logEx(entry.message); // no [Skript] prefix + return LogResult.DO_NOT_LOG; + } else { + return LogResult.LOG; } - }); - - try (CountingLogHandler c = new CountingLogHandler(SkriptLogger.SEVERE).start()) { - if (!Variables.load()) - if (c.getCount() == 0) - error("(no information available)"); - } finally { - h.stop(); } - - long vld = System.currentTimeMillis() - vls; - if (logNormal()) - info("Loaded " + Variables.numVariables() + " variables in " + ((vld / 100) / 10.) + " seconds"); + + @Override + protected void beforeErrors() { + logEx(); + logEx("===!!!=== Skript variable load error ===!!!==="); + logEx("Unable to load (all) variables:"); + } + + @Override + protected void afterErrors() { + logEx(); + logEx("Skript will work properly, but old variables might not be available at all and new ones may or may not be saved until Skript is able to create a backup of the old file and/or is able to connect to the database (which requires a restart of Skript)!"); + logEx(); + } }); - + + try (CountingLogHandler c = new CountingLogHandler(SkriptLogger.SEVERE).start()) { + if (!Variables.load()) + if (c.getCount() == 0) + error("(no information available)"); + } finally { + h.stop(); + } + + long vld = System.currentTimeMillis() - vls; + if (logNormal()) + info("Loaded " + Variables.numVariables() + " variables in " + ((vld / 100) / 10.) + " seconds"); + // Skript initialization done debug("Early init done"); From 0590c6ab5fb866c4f4b052bf1c72ce59b2fa5b00 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Wed, 9 Aug 2023 23:02:32 -0600 Subject: [PATCH 390/619] Fix and add chat format to books (#5863) --- .../skript/expressions/ExprBookPages.java | 134 +++++++++++++----- .../syntaxes/expressions/ExprBookPages.sk | 5 + 2 files changed, 104 insertions(+), 35 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprBookPages.java b/src/main/java/ch/njol/skript/expressions/ExprBookPages.java index 917e3909de9..50b8801296a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprBookPages.java +++ b/src/main/java/ch/njol/skript/expressions/ExprBookPages.java @@ -18,6 +18,18 @@ */ package ch.njol.skript.expressions; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.BookMeta; +import org.eclipse.jdt.annotation.Nullable; + +import ch.njol.skript.ServerPlatform; import ch.njol.skript.Skript; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.classes.Changer.ChangeMode; @@ -29,38 +41,44 @@ import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.skript.util.chat.BungeeConverter; +import ch.njol.skript.util.chat.ChatMessages; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -import org.bukkit.event.Event; -import org.bukkit.inventory.meta.BookMeta; -import org.eclipse.jdt.annotation.Nullable; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; @Name("Book Pages") -@Description("The pages of a book.") +@Description({ + "The pages of a book (Supports Skript's chat format)", + "Note: In order to modify the pages of a new written book, you must have the title and author", + "of the book set. Skript will do this for you, but if you want your own, please set those values." +}) @Examples({ "on book sign:", - "\tmessage \"Book Pages: %pages of event-item%\"", - "\tmessage \"Book Page 1: %page 1 of event-item%\"", + "\tmessage \"Book Pages: %pages of event-item%\"", + "\tmessage \"Book Page 1: %page 1 of event-item%\"", + "", "set page 1 of player's held item to \"Book writing\"" }) @Since("2.2-dev31, 2.7 (changers)") public class ExprBookPages extends SimpleExpression<String> { + private static BungeeComponentSerializer serializer; + static { Skript.registerExpression(ExprBookPages.class, String.class, ExpressionType.PROPERTY, - "[all [[of] the]|the] [book] (pages|content) of %itemtypes%", - "%itemtypes%'[s] [book] (pages|content)", - "[book] page %number% of %itemtypes%", - "%itemtypes%'[s] [book] page %number%" + "[all [[of] the]|the] [book] (pages|content) of %itemtypes/itemstacks%", + "%itemtypes/itemstacks%'[s] [book] (pages|content)", + "[book] page %number% of %itemtypes/itemstacks%", + "%itemtypes/itemstacks%'[s] [book] page %number%" ); + if (Skript.isRunningMinecraft(1, 16) && Skript.getServerPlatform() == ServerPlatform.BUKKIT_PAPER) + serializer = BungeeComponentSerializer.get(); } - private Expression<ItemType> items; + private Expression<?> items; + @Nullable private Expression<Number> page; @@ -68,12 +86,12 @@ public class ExprBookPages extends SimpleExpression<String> { @SuppressWarnings("unchecked") public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { if (matchedPattern == 0 || matchedPattern == 1) { - items = (Expression<ItemType>) exprs[0]; + items = exprs[0]; } else if (matchedPattern == 2) { page = (Expression<Number>) exprs[0]; - items = (Expression<ItemType>) exprs[1]; + items = exprs[1]; } else { - items = (Expression<ItemType>) exprs[0]; + items = exprs[0]; page = (Expression<Number>) exprs[1]; } return true; @@ -83,10 +101,23 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Nullable protected String[] get(Event event) { List<String> pages = new ArrayList<>(); - for (ItemType itemType : items.getArray(event)) { - if (!(itemType.getItemMeta() instanceof BookMeta)) + for (Object object : items.getArray(event)) { + BookMeta bookMeta = null; + if (object instanceof ItemType) { + ItemType itemType = (ItemType) object; + if (!(itemType.getItemMeta() instanceof BookMeta)) + continue; + bookMeta = (BookMeta) itemType.getItemMeta(); + } else if (object instanceof ItemStack) { + ItemStack itemstack = (ItemStack) object; + if (!(itemstack.getItemMeta() instanceof BookMeta)) + continue; + bookMeta = (BookMeta) itemstack.getItemMeta(); + } else { + assert false; + } + if (bookMeta == null) continue; - BookMeta bookMeta = (BookMeta) itemType.getItemMeta(); if (isAllPages()) { pages.addAll(bookMeta.getPages()); } else { @@ -119,24 +150,34 @@ public Class<?>[] acceptChange(ChangeMode mode) { } @Override - @SuppressWarnings("ConstantConditions") public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { if (delta == null && (mode == ChangeMode.SET || mode == ChangeMode.ADD)) return; - ItemType[] itemTypes = items.getArray(event); int page = !isAllPages() ? this.page.getOptionalSingle(event).orElse(-1).intValue() : -1; String[] newPages = delta == null ? null : new String[delta.length]; - if (newPages != null) { for (int i = 0; i < delta.length; i++) newPages[i] = delta[i] + ""; } - for (ItemType itemType : itemTypes) { - if (!(itemType.getItemMeta() instanceof BookMeta)) + for (Object object : items.getArray(event)) { + BookMeta bookMeta = null; + if (object instanceof ItemType) { + ItemType itemType = (ItemType) object; + if (!(itemType.getItemMeta() instanceof BookMeta)) + continue; + bookMeta = (BookMeta) itemType.getItemMeta(); + } else if (object instanceof ItemStack) { + ItemStack itemstack = (ItemStack) object; + if (!(itemstack.getItemMeta() instanceof BookMeta)) + continue; + bookMeta = (BookMeta) itemstack.getItemMeta(); + } else { + assert false; + } + if (bookMeta == null) continue; - BookMeta bookMeta = (BookMeta) itemType.getItemMeta(); List<String> pages = null; if (isAllPages()) { switch (mode) { @@ -147,6 +188,10 @@ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { case SET: pages = Arrays.asList(newPages); break; + case ADD: + pages = new ArrayList<>(bookMeta.getPages()); + pages.addAll(Arrays.asList(newPages)); + break; default: assert false; } @@ -154,7 +199,6 @@ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { pages = new ArrayList<>(bookMeta.getPages()); } int pageCount = bookMeta.getPageCount(); - switch (mode) { case DELETE: case RESET: @@ -175,13 +219,33 @@ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { pages.set(page - 1, newPages[0]); } break; - case ADD: - pages.addAll(Arrays.asList(newPages)); + default: break; } - - bookMeta.setPages(pages); - itemType.setItemMeta(bookMeta); + if (serializer != null) { + if (!bookMeta.hasTitle() && bookMeta.hasDisplayName()) + bookMeta.title(bookMeta.displayName()); + List<Component> components = pages.stream() + .map(ChatMessages::parseToArray) + .map(BungeeConverter::convert) + .map(serializer::deserialize) + .collect(Collectors.toList()); + bookMeta.pages(components); + } else { + bookMeta.setPages(pages); + } + // If the title and author of the bookMeta are not set, Minecraft will not update the BookMeta, as it deems the book "not signed". + if (!bookMeta.hasTitle()) { + String title = bookMeta.hasDisplayName() ? bookMeta.getDisplayName() : "Written Book"; + bookMeta.setTitle(title); + } + if (!bookMeta.hasAuthor()) + bookMeta.setAuthor("Server"); + if (object instanceof ItemType) { + ((ItemType) object).setItemMeta(bookMeta); + } else if (object instanceof ItemStack) { + ((ItemStack) object).setItemMeta(bookMeta); + } } } @@ -201,7 +265,7 @@ public Class<? extends String> getReturnType() { @Override public String toString(@Nullable Event event, boolean debug) { - return (page != null ? "page " + page.toString(event, debug) : "the book pages") + " of " + items.toString(event, debug); + return (page != null ? "page " + page.toString(event, debug) : "book pages") + " of " + items.toString(event, debug); } } diff --git a/src/test/skript/tests/syntaxes/expressions/ExprBookPages.sk b/src/test/skript/tests/syntaxes/expressions/ExprBookPages.sk index d724f4db615..a2f7b7e3a7c 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprBookPages.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprBookPages.sk @@ -1,3 +1,8 @@ test "book pages": set {_i} to book with text assert {_i}'s book pages is not set with "non-existent book pages failed" + set {_i} to a written book + assert {_i}'s book pages is not set with "non-existent book pages failed 2" + set pages of {_i} to "&aPages testing" and "test 2" + assert size of {_i}'s book pages is 2 with "failed to match exact pages size" + assert page 2 of {_i} is "test 2" with "failed to compare page 2" From 370893eb776004c5b184bb24fd1ae22d725807a8 Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Mon, 14 Aug 2023 10:14:49 +0300 Subject: [PATCH 391/619] Add support for keybind components (#5894) --- .../java/ch/njol/skript/util/chat/BungeeConverter.java | 10 +++++----- .../java/ch/njol/skript/util/chat/ChatMessages.java | 6 ++++++ .../ch/njol/skript/util/chat/MessageComponent.java | 7 ++++++- .../java/ch/njol/skript/util/chat/SkriptChatCode.java | 7 +++++++ src/main/resources/lang/default.lang | 1 + 5 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/main/java/ch/njol/skript/util/chat/BungeeConverter.java b/src/main/java/ch/njol/skript/util/chat/BungeeConverter.java index 82522220d3a..3ceb4bf29ed 100644 --- a/src/main/java/ch/njol/skript/util/chat/BungeeConverter.java +++ b/src/main/java/ch/njol/skript/util/chat/BungeeConverter.java @@ -25,6 +25,7 @@ import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.ClickEvent; import net.md_5.bungee.api.chat.HoverEvent; +import net.md_5.bungee.api.chat.KeybindComponent; import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.chat.TranslatableComponent; @@ -42,11 +43,10 @@ public static BaseComponent convert(MessageComponent origin) { if (origin.translation != null) { String[] strings = origin.translation.split(":"); String key = strings[0]; - if (strings.length > 1) { - base = new TranslatableComponent(key, Arrays.copyOfRange(strings, 1, strings.length, Object[].class)); - } else { - base = new TranslatableComponent(key); - } + base = new TranslatableComponent(key, Arrays.copyOfRange(strings, 1, strings.length, Object[].class)); + base.addExtra(new TextComponent(origin.text)); + } else if (origin.keybind != null) { + base = new KeybindComponent(origin.keybind); base.addExtra(new TextComponent(origin.text)); } else { base = new TextComponent(origin.text); diff --git a/src/main/java/ch/njol/skript/util/chat/ChatMessages.java b/src/main/java/ch/njol/skript/util/chat/ChatMessages.java index dbe03940c76..c64d28a0a56 100644 --- a/src/main/java/ch/njol/skript/util/chat/ChatMessages.java +++ b/src/main/java/ch/njol/skript/util/chat/ChatMessages.java @@ -538,6 +538,8 @@ public static void copyStyles(MessageComponent from, MessageComponent to) { to.insertion = from.insertion; if (to.hoverEvent == null) to.hoverEvent = from.hoverEvent; + if (to.font == null) + to.font = from.font; } public static void shareStyles(MessageComponent[] components) { @@ -591,6 +593,10 @@ public static String stripStyles(String text) { List<MessageComponent> components = parse(result); StringBuilder builder = new StringBuilder(); for (MessageComponent component : components) { // This also strips bracket tags ex. <red> <ttp:..> etc. + if (component.translation != null) + builder.append(component.translation); + if (component.keybind != null) + builder.append(component.keybind); builder.append(component.text); } String plain = builder.toString(); diff --git a/src/main/java/ch/njol/skript/util/chat/MessageComponent.java b/src/main/java/ch/njol/skript/util/chat/MessageComponent.java index 468e5fafdf2..9eb41247c2c 100644 --- a/src/main/java/ch/njol/skript/util/chat/MessageComponent.java +++ b/src/main/java/ch/njol/skript/util/chat/MessageComponent.java @@ -92,7 +92,10 @@ public class MessageComponent { @Nullable public String translation; - + + @Nullable + public String keybind; + public static class ClickEvent { public ClickEvent(ClickEvent.Action action, String value) { this.action = action; @@ -176,6 +179,8 @@ public MessageComponent copy() { messageComponent.clickEvent = this.clickEvent; messageComponent.font = this.font; messageComponent.hoverEvent = this.hoverEvent; + messageComponent.translation = translation; + messageComponent.keybind = keybind; return messageComponent; } diff --git a/src/main/java/ch/njol/skript/util/chat/SkriptChatCode.java b/src/main/java/ch/njol/skript/util/chat/SkriptChatCode.java index 1caa202d424..e21705abb7b 100644 --- a/src/main/java/ch/njol/skript/util/chat/SkriptChatCode.java +++ b/src/main/java/ch/njol/skript/util/chat/SkriptChatCode.java @@ -163,6 +163,13 @@ public void updateComponent(MessageComponent component, String param) { public void updateComponent(MessageComponent component, String param) { component.translation = param; } + }, + + keybind(true) { + @Override + public void updateComponent(MessageComponent component, String param) { + component.keybind = param; + } }; private boolean hasParam; diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index cc84e272f48..c9f41ee930b 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -412,6 +412,7 @@ chat styles: font: font, f insertion: insertion, insert, ins translate: translate, tr, lang + keybind: keybind, key # -- Directions -- directions: From 2968954b3d8a2feeae94d39f2bcaf4b447305c97 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Mon, 14 Aug 2023 01:20:55 -0600 Subject: [PATCH 392/619] [2.7.0 target] Enhancement/books 5863 (#5899) --- .../skript/expressions/ExprBookPages.java | 134 +++++++++++++----- .../syntaxes/expressions/ExprBookPages.sk | 5 + 2 files changed, 104 insertions(+), 35 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprBookPages.java b/src/main/java/ch/njol/skript/expressions/ExprBookPages.java index 917e3909de9..50b8801296a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprBookPages.java +++ b/src/main/java/ch/njol/skript/expressions/ExprBookPages.java @@ -18,6 +18,18 @@ */ package ch.njol.skript.expressions; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.BookMeta; +import org.eclipse.jdt.annotation.Nullable; + +import ch.njol.skript.ServerPlatform; import ch.njol.skript.Skript; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.classes.Changer.ChangeMode; @@ -29,38 +41,44 @@ import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.skript.util.chat.BungeeConverter; +import ch.njol.skript.util.chat.ChatMessages; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -import org.bukkit.event.Event; -import org.bukkit.inventory.meta.BookMeta; -import org.eclipse.jdt.annotation.Nullable; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; @Name("Book Pages") -@Description("The pages of a book.") +@Description({ + "The pages of a book (Supports Skript's chat format)", + "Note: In order to modify the pages of a new written book, you must have the title and author", + "of the book set. Skript will do this for you, but if you want your own, please set those values." +}) @Examples({ "on book sign:", - "\tmessage \"Book Pages: %pages of event-item%\"", - "\tmessage \"Book Page 1: %page 1 of event-item%\"", + "\tmessage \"Book Pages: %pages of event-item%\"", + "\tmessage \"Book Page 1: %page 1 of event-item%\"", + "", "set page 1 of player's held item to \"Book writing\"" }) @Since("2.2-dev31, 2.7 (changers)") public class ExprBookPages extends SimpleExpression<String> { + private static BungeeComponentSerializer serializer; + static { Skript.registerExpression(ExprBookPages.class, String.class, ExpressionType.PROPERTY, - "[all [[of] the]|the] [book] (pages|content) of %itemtypes%", - "%itemtypes%'[s] [book] (pages|content)", - "[book] page %number% of %itemtypes%", - "%itemtypes%'[s] [book] page %number%" + "[all [[of] the]|the] [book] (pages|content) of %itemtypes/itemstacks%", + "%itemtypes/itemstacks%'[s] [book] (pages|content)", + "[book] page %number% of %itemtypes/itemstacks%", + "%itemtypes/itemstacks%'[s] [book] page %number%" ); + if (Skript.isRunningMinecraft(1, 16) && Skript.getServerPlatform() == ServerPlatform.BUKKIT_PAPER) + serializer = BungeeComponentSerializer.get(); } - private Expression<ItemType> items; + private Expression<?> items; + @Nullable private Expression<Number> page; @@ -68,12 +86,12 @@ public class ExprBookPages extends SimpleExpression<String> { @SuppressWarnings("unchecked") public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { if (matchedPattern == 0 || matchedPattern == 1) { - items = (Expression<ItemType>) exprs[0]; + items = exprs[0]; } else if (matchedPattern == 2) { page = (Expression<Number>) exprs[0]; - items = (Expression<ItemType>) exprs[1]; + items = exprs[1]; } else { - items = (Expression<ItemType>) exprs[0]; + items = exprs[0]; page = (Expression<Number>) exprs[1]; } return true; @@ -83,10 +101,23 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Nullable protected String[] get(Event event) { List<String> pages = new ArrayList<>(); - for (ItemType itemType : items.getArray(event)) { - if (!(itemType.getItemMeta() instanceof BookMeta)) + for (Object object : items.getArray(event)) { + BookMeta bookMeta = null; + if (object instanceof ItemType) { + ItemType itemType = (ItemType) object; + if (!(itemType.getItemMeta() instanceof BookMeta)) + continue; + bookMeta = (BookMeta) itemType.getItemMeta(); + } else if (object instanceof ItemStack) { + ItemStack itemstack = (ItemStack) object; + if (!(itemstack.getItemMeta() instanceof BookMeta)) + continue; + bookMeta = (BookMeta) itemstack.getItemMeta(); + } else { + assert false; + } + if (bookMeta == null) continue; - BookMeta bookMeta = (BookMeta) itemType.getItemMeta(); if (isAllPages()) { pages.addAll(bookMeta.getPages()); } else { @@ -119,24 +150,34 @@ public Class<?>[] acceptChange(ChangeMode mode) { } @Override - @SuppressWarnings("ConstantConditions") public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { if (delta == null && (mode == ChangeMode.SET || mode == ChangeMode.ADD)) return; - ItemType[] itemTypes = items.getArray(event); int page = !isAllPages() ? this.page.getOptionalSingle(event).orElse(-1).intValue() : -1; String[] newPages = delta == null ? null : new String[delta.length]; - if (newPages != null) { for (int i = 0; i < delta.length; i++) newPages[i] = delta[i] + ""; } - for (ItemType itemType : itemTypes) { - if (!(itemType.getItemMeta() instanceof BookMeta)) + for (Object object : items.getArray(event)) { + BookMeta bookMeta = null; + if (object instanceof ItemType) { + ItemType itemType = (ItemType) object; + if (!(itemType.getItemMeta() instanceof BookMeta)) + continue; + bookMeta = (BookMeta) itemType.getItemMeta(); + } else if (object instanceof ItemStack) { + ItemStack itemstack = (ItemStack) object; + if (!(itemstack.getItemMeta() instanceof BookMeta)) + continue; + bookMeta = (BookMeta) itemstack.getItemMeta(); + } else { + assert false; + } + if (bookMeta == null) continue; - BookMeta bookMeta = (BookMeta) itemType.getItemMeta(); List<String> pages = null; if (isAllPages()) { switch (mode) { @@ -147,6 +188,10 @@ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { case SET: pages = Arrays.asList(newPages); break; + case ADD: + pages = new ArrayList<>(bookMeta.getPages()); + pages.addAll(Arrays.asList(newPages)); + break; default: assert false; } @@ -154,7 +199,6 @@ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { pages = new ArrayList<>(bookMeta.getPages()); } int pageCount = bookMeta.getPageCount(); - switch (mode) { case DELETE: case RESET: @@ -175,13 +219,33 @@ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { pages.set(page - 1, newPages[0]); } break; - case ADD: - pages.addAll(Arrays.asList(newPages)); + default: break; } - - bookMeta.setPages(pages); - itemType.setItemMeta(bookMeta); + if (serializer != null) { + if (!bookMeta.hasTitle() && bookMeta.hasDisplayName()) + bookMeta.title(bookMeta.displayName()); + List<Component> components = pages.stream() + .map(ChatMessages::parseToArray) + .map(BungeeConverter::convert) + .map(serializer::deserialize) + .collect(Collectors.toList()); + bookMeta.pages(components); + } else { + bookMeta.setPages(pages); + } + // If the title and author of the bookMeta are not set, Minecraft will not update the BookMeta, as it deems the book "not signed". + if (!bookMeta.hasTitle()) { + String title = bookMeta.hasDisplayName() ? bookMeta.getDisplayName() : "Written Book"; + bookMeta.setTitle(title); + } + if (!bookMeta.hasAuthor()) + bookMeta.setAuthor("Server"); + if (object instanceof ItemType) { + ((ItemType) object).setItemMeta(bookMeta); + } else if (object instanceof ItemStack) { + ((ItemStack) object).setItemMeta(bookMeta); + } } } @@ -201,7 +265,7 @@ public Class<? extends String> getReturnType() { @Override public String toString(@Nullable Event event, boolean debug) { - return (page != null ? "page " + page.toString(event, debug) : "the book pages") + " of " + items.toString(event, debug); + return (page != null ? "page " + page.toString(event, debug) : "book pages") + " of " + items.toString(event, debug); } } diff --git a/src/test/skript/tests/syntaxes/expressions/ExprBookPages.sk b/src/test/skript/tests/syntaxes/expressions/ExprBookPages.sk index d724f4db615..a2f7b7e3a7c 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprBookPages.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprBookPages.sk @@ -1,3 +1,8 @@ test "book pages": set {_i} to book with text assert {_i}'s book pages is not set with "non-existent book pages failed" + set {_i} to a written book + assert {_i}'s book pages is not set with "non-existent book pages failed 2" + set pages of {_i} to "&aPages testing" and "test 2" + assert size of {_i}'s book pages is 2 with "failed to match exact pages size" + assert page 2 of {_i} is "test 2" with "failed to compare page 2" From cdfbb91b9a88876e1d486a34277c7f3dce9073ce Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Wed, 16 Aug 2023 09:22:22 -0600 Subject: [PATCH 393/619] Explain luck perms group (#5817) * Explain luck perms group * Update ExprGroup.java --- .../njol/skript/hooks/permission/expressions/ExprGroup.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/hooks/permission/expressions/ExprGroup.java b/src/main/java/ch/njol/skript/hooks/permission/expressions/ExprGroup.java index 71aebfc1db2..bd043c1c255 100644 --- a/src/main/java/ch/njol/skript/hooks/permission/expressions/ExprGroup.java +++ b/src/main/java/ch/njol/skript/hooks/permission/expressions/ExprGroup.java @@ -41,7 +41,10 @@ import java.util.List; @Name("Group") -@Description("The primary group or all groups of a player. This expression requires Vault and a compatible permissions plugin to be installed.") +@Description({ + "The primary group or all groups of a player. This expression requires Vault and a compatible permissions plugin to be installed.", + "If you have LuckPerms, ensure you have vault integration enabled in the luck perms configurations." +}) @Examples({"on join:", "\tbroadcast \"%group of player%\" # this is the player's primary group", "\tbroadcast \"%groups of player%\" # this is all of the player's groups"}) From 7f38b86f97b0fe050ec8fa9e1979a4da722523d2 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Sat, 19 Aug 2023 07:32:44 -0700 Subject: [PATCH 394/619] Disable Timings for 1.19.4+ (#5917) * Disable Timings for 1.19.4+ * Update SkriptConfig.java --- src/main/java/ch/njol/skript/SkriptConfig.java | 18 +++++++++++++----- src/main/resources/config.sk | 3 ++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/main/java/ch/njol/skript/SkriptConfig.java b/src/main/java/ch/njol/skript/SkriptConfig.java index bdf7144cb36..552debddd5c 100644 --- a/src/main/java/ch/njol/skript/SkriptConfig.java +++ b/src/main/java/ch/njol/skript/SkriptConfig.java @@ -41,6 +41,7 @@ import ch.njol.skript.util.chat.ChatMessages; import ch.njol.skript.util.chat.LinkParseMode; import ch.njol.skript.variables.Variables; +import co.aikar.timings.Timings; import org.bukkit.event.EventPriority; import org.eclipse.jdt.annotation.Nullable; @@ -196,15 +197,22 @@ public static String formatDate(final long timestamp) { public static final Option<Boolean> enableTimings = new Option<>("enable timings", false) .setter(t -> { - if (Skript.classExists("co.aikar.timings.Timings")) { // Check for Paper server - if (t) - Skript.info("Timings support enabled!"); - SkriptTimings.setEnabled(t); // Config option will be used - } else { // Not running Paper + if (!Skript.classExists("co.aikar.timings.Timings")) { // Check for Timings if (t) // Warn the server admin that timings won't work Skript.warning("Timings cannot be enabled! You are running Bukkit/Spigot, but Paper is required."); SkriptTimings.setEnabled(false); // Just to be sure, deactivate timings support completely + return; } + if (Timings.class.isAnnotationPresent(Deprecated.class)) { // check for deprecated Timings + if (t) // Warn the server admin that timings won't work + Skript.warning("Timings cannot be enabled! Paper no longer supports Timings as of 1.19.4."); + SkriptTimings.setEnabled(false); // Just to be sure, deactivate timings support completely + return; + } + // If we get here, we can safely enable timings + if (t) + Skript.info("Timings support enabled!"); + SkriptTimings.setEnabled(t); // Config option will be used }); public static final Option<String> parseLinks = new Option<>("parse links in chat messages", "disabled") diff --git a/src/main/resources/config.sk b/src/main/resources/config.sk index 6212e21d47a..a95cfe461b1 100644 --- a/src/main/resources/config.sk +++ b/src/main/resources/config.sk @@ -159,7 +159,8 @@ soft api exceptions: false enable timings: false # When enabled, triggers in scripts will be present in timings reports. -# Note that this requires Paper (https://paper.readthedocs.io/en/paper-1.11/) to work; on Bukkit/Spigot this option has no effect. +# Note that this requires Paper (https://papermc.io/downloads/paper) to work; on Bukkit/Spigot this option has no effect. +# Warning: Paper no longer supports Timings as of 1.19.4. This option has no effect on versions 1.19.4 and above. # When false, timings are not enabled for scripts even if you're running Paper. parse links in chat messages: disabled From 639bcf7b06d293199664dc65d40328dcad6fa9d7 Mon Sep 17 00:00:00 2001 From: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> Date: Sat, 19 Aug 2023 14:27:56 -0500 Subject: [PATCH 395/619] Fix issue where mixing different types of conditionals sometimes isn't allowed (#5872) --- .../njol/skript/sections/SecConditional.java | 61 +++++++++++++------ .../tests/syntaxes/sections/SecConditional.sk | 35 +++++++++++ 2 files changed, 76 insertions(+), 20 deletions(-) diff --git a/src/main/java/ch/njol/skript/sections/SecConditional.java b/src/main/java/ch/njol/skript/sections/SecConditional.java index cadc1e333ce..470f3425405 100644 --- a/src/main/java/ch/njol/skript/sections/SecConditional.java +++ b/src/main/java/ch/njol/skript/sections/SecConditional.java @@ -118,21 +118,34 @@ public boolean init(Expression<?>[] exprs, multiline = parseResult.regexes.size() == 0 && type != ConditionalType.ELSE; // ensure this conditional is chained correctly (e.g. an else must have an if) - SecConditional lastIf; if (type != ConditionalType.IF) { - lastIf = getClosestIf(triggerItems); - if (lastIf == null) { - if (type == ConditionalType.ELSE_IF) { - Skript.error("'else if' has to be placed just after another 'if' or 'else if' section"); - } else if (type == ConditionalType.ELSE) { - Skript.error("'else' has to be placed just after another 'if' or 'else if' section"); - } else if (type == ConditionalType.THEN) { + if (type == ConditionalType.THEN) { + /* + * if this is a 'then' section, the preceding conditional has to be a multiline conditional section + * otherwise, you could put a 'then' section after a non-multiline 'if'. for example: + * if 1 is 1: + * set {_example} to true + * then: # this shouldn't be possible + * set {_uh oh} to true + */ + SecConditional precedingConditional = getPrecedingConditional(triggerItems, null); + if (precedingConditional == null || !precedingConditional.multiline) { Skript.error("'then' has to placed just after a multiline 'if' or 'else if' section"); + return false; + } + } else { + // find the latest 'if' section so that we can ensure this section is placed properly (e.g. ensure a 'if' occurs before an 'else') + SecConditional precedingIf = getPrecedingConditional(triggerItems, ConditionalType.IF); + if (precedingIf == null) { + if (type == ConditionalType.ELSE_IF) { + Skript.error("'else if' has to be placed just after another 'if' or 'else if' section"); + } else if (type == ConditionalType.ELSE) { + Skript.error("'else' has to be placed just after another 'if' or 'else if' section"); + } else if (type == ConditionalType.THEN) { + Skript.error("'then' has to placed just after a multiline 'if' or 'else if' section"); + } + return false; } - return false; - } else if (!lastIf.multiline && type == ConditionalType.THEN) { - Skript.error("'then' has to placed just after a multiline 'if' or 'else if' section"); - return false; } } else { // if this is a multiline if, we need to check if there is a "then" section after this @@ -150,7 +163,6 @@ public boolean init(Expression<?>[] exprs, return false; } } - lastIf = null; } // if this an "if" or "else if", let's try to parse the conditions right away @@ -231,9 +243,11 @@ public boolean init(Expression<?>[] exprs, return true; if (type == ConditionalType.ELSE) { + SecConditional precedingIf = getPrecedingConditional(triggerItems, ConditionalType.IF); + assert precedingIf != null; // at this point, we've validated the section so this can't be null // In an else section, ... if (hasDelayAfter.isTrue() - && lastIf.hasDelayAfter.isTrue() + && precedingIf.hasDelayAfter.isTrue() && getElseIfs(triggerItems).stream().map(SecConditional::getHasDelayAfter).allMatch(Kleenean::isTrue)) { // ... if the if section, all else-if sections and the else section have definite delays, // mark delayed as TRUE. @@ -314,21 +328,28 @@ private Kleenean getHasDelayAfter() { return hasDelayAfter; } + /** + * Gets the closest conditional section in the list of trigger items + * @param triggerItems the list of items to search for the closest conditional section in + * @param type the type of conditional section to find. if null is provided, any type is allowed. + * @return the closest conditional section + */ @Nullable - private static SecConditional getClosestIf(List<TriggerItem> triggerItems) { + private static SecConditional getPrecedingConditional(List<TriggerItem> triggerItems, @Nullable ConditionalType type) { // loop through the triggerItems in reverse order so that we find the most recent items first for (int i = triggerItems.size() - 1; i >= 0; i--) { TriggerItem triggerItem = triggerItems.get(i); if (triggerItem instanceof SecConditional) { - SecConditional secConditional = (SecConditional) triggerItem; + SecConditional conditionalSection = (SecConditional) triggerItem; - if (secConditional.type == ConditionalType.IF) - // if the condition is an if, we found our most recent preceding "if" - return secConditional; - else if (secConditional.type == ConditionalType.ELSE) + if (conditionalSection.type == ConditionalType.ELSE) { // if the conditional is an else, return null because it belongs to a different condition and ends // this one return null; + } else if (type == null || conditionalSection.type == type) { + // if the conditional matches the type argument, we found our most recent preceding conditional section + return conditionalSection; + } } else { return null; } diff --git a/src/test/skript/tests/syntaxes/sections/SecConditional.sk b/src/test/skript/tests/syntaxes/sections/SecConditional.sk index 98fb633745d..f9991e9f621 100644 --- a/src/test/skript/tests/syntaxes/sections/SecConditional.sk +++ b/src/test/skript/tests/syntaxes/sections/SecConditional.sk @@ -143,3 +143,38 @@ test "SecConditional - else if all false": then: set {_a} to false assert {_a} is not set with "'else if all' ran even though all conditions were false" + +test "SecConditional - starting with a non-multiline conditional": + if 1 is 1: + set {_a} to true + else if: + 1 is 8 + 2 is 7 + then: + set {_b} to true + assert {_a} is set with "non-multiline 'if' didn't run when before a multiline 'else if'" + +test "SecConditional - non-multiline conditional in the middle": + if: + 1 is 2 + 3 is 4 + then: + set {_b} to true + else if 1 is 1: + set {_a} to true + else if: + 5 is 6 + 7 is 8 + then: + set {_c} to true + assert {_a} is set with "non-multiline 'if' didn't run when used in the middle of a multiline 'else if'" + +test "SecConditional - non-multiline conditional at the end": + if: + 1 is 2 + 3 is 4 + then: + set {_b} to true + else if 1 is 1: + set {_a} to true + assert {_a} is set with "non-multiline 'if' didn't run used at the end of a multiline 'if'" From d6cabe1a89b261a7bd018f58d88ceaaa5d564b6e Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Sun, 20 Aug 2023 00:42:37 -0700 Subject: [PATCH 396/619] [2.7.0 target] Disable Timings for 1.19.4+ 5917 (#5926) Disable Timings for 1.19.4+ (#5917) * Disable Timings for 1.19.4+ * Update SkriptConfig.java (cherry picked from commit 7f38b86f97b0fe050ec8fa9e1979a4da722523d2) --- src/main/java/ch/njol/skript/SkriptConfig.java | 18 +++++++++++++----- src/main/resources/config.sk | 3 ++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/main/java/ch/njol/skript/SkriptConfig.java b/src/main/java/ch/njol/skript/SkriptConfig.java index bdf7144cb36..552debddd5c 100644 --- a/src/main/java/ch/njol/skript/SkriptConfig.java +++ b/src/main/java/ch/njol/skript/SkriptConfig.java @@ -41,6 +41,7 @@ import ch.njol.skript.util.chat.ChatMessages; import ch.njol.skript.util.chat.LinkParseMode; import ch.njol.skript.variables.Variables; +import co.aikar.timings.Timings; import org.bukkit.event.EventPriority; import org.eclipse.jdt.annotation.Nullable; @@ -196,15 +197,22 @@ public static String formatDate(final long timestamp) { public static final Option<Boolean> enableTimings = new Option<>("enable timings", false) .setter(t -> { - if (Skript.classExists("co.aikar.timings.Timings")) { // Check for Paper server - if (t) - Skript.info("Timings support enabled!"); - SkriptTimings.setEnabled(t); // Config option will be used - } else { // Not running Paper + if (!Skript.classExists("co.aikar.timings.Timings")) { // Check for Timings if (t) // Warn the server admin that timings won't work Skript.warning("Timings cannot be enabled! You are running Bukkit/Spigot, but Paper is required."); SkriptTimings.setEnabled(false); // Just to be sure, deactivate timings support completely + return; } + if (Timings.class.isAnnotationPresent(Deprecated.class)) { // check for deprecated Timings + if (t) // Warn the server admin that timings won't work + Skript.warning("Timings cannot be enabled! Paper no longer supports Timings as of 1.19.4."); + SkriptTimings.setEnabled(false); // Just to be sure, deactivate timings support completely + return; + } + // If we get here, we can safely enable timings + if (t) + Skript.info("Timings support enabled!"); + SkriptTimings.setEnabled(t); // Config option will be used }); public static final Option<String> parseLinks = new Option<>("parse links in chat messages", "disabled") diff --git a/src/main/resources/config.sk b/src/main/resources/config.sk index 6212e21d47a..a95cfe461b1 100644 --- a/src/main/resources/config.sk +++ b/src/main/resources/config.sk @@ -159,7 +159,8 @@ soft api exceptions: false enable timings: false # When enabled, triggers in scripts will be present in timings reports. -# Note that this requires Paper (https://paper.readthedocs.io/en/paper-1.11/) to work; on Bukkit/Spigot this option has no effect. +# Note that this requires Paper (https://papermc.io/downloads/paper) to work; on Bukkit/Spigot this option has no effect. +# Warning: Paper no longer supports Timings as of 1.19.4. This option has no effect on versions 1.19.4 and above. # When false, timings are not enabled for scripts even if you're running Paper. parse links in chat messages: disabled From aa9f7bf0693f53108e9a3fd417b1f7d013871e28 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Mon, 21 Aug 2023 10:15:53 +0300 Subject: [PATCH 397/619] =?UTF-8?q?=F0=9F=9B=A0=20Fix=20Duplicated=20Space?= =?UTF-8?q?s=20in=20StructCommand=20(#5416)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ch/njol/skript/structures/StructCommand.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/ch/njol/skript/structures/StructCommand.java b/src/main/java/ch/njol/skript/structures/StructCommand.java index c389c62d7bd..0b86f6762f8 100644 --- a/src/main/java/ch/njol/skript/structures/StructCommand.java +++ b/src/main/java/ch/njol/skript/structures/StructCommand.java @@ -82,10 +82,9 @@ public class StructCommand extends Structure { public static final Priority PRIORITY = new Priority(500); - private static final Pattern - COMMAND_PATTERN = Pattern.compile("(?i)^command /?(\\S+)\\s*(\\s+(.+))?$"), - ARGUMENT_PATTERN = Pattern.compile("<\\s*(?:([^>]+?)\\s*:\\s*)?(.+?)\\s*(?:=\\s*(" + SkriptParser.wildcard + "))?\\s*>"), - DESCRIPTION_PATTERN = Pattern.compile("(?<!\\\\)%-?(.+?)%"); + private static final Pattern COMMAND_PATTERN = Pattern.compile("(?i)^command\\s+/?(\\S+)\\s*(\\s+(.+))?$"); + private static final Pattern ARGUMENT_PATTERN = Pattern.compile("<\\s*(?:([^>]+?)\\s*:\\s*)?(.+?)\\s*(?:=\\s*(" + SkriptParser.wildcard + "))?\\s*>"); + private static final Pattern DESCRIPTION_PATTERN = Pattern.compile("(?<!\\\\)%-?(.+?)%"); private static final AtomicBoolean SYNC_COMMANDS = new AtomicBoolean(); @@ -184,7 +183,10 @@ public boolean load() { Matcher matcher = COMMAND_PATTERN.matcher(fullCommand); boolean matches = matcher.matches(); - assert matches; + if (!matches) { + Skript.error("Invalid command structure pattern"); + return false; + } String command = matcher.group(1).toLowerCase(); ScriptCommand existingCommand = Commands.getScriptCommand(command); From 7b0bfce68c4fb512814005ab76cb55e6a401cbcd Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Mon, 21 Aug 2023 10:31:54 +0300 Subject: [PATCH 398/619] =?UTF-8?q?=F0=9F=9A=80=20Add=20`player(nameOrUUID?= =?UTF-8?q?:=20string,=20getExactPlayer:=20boolean)`=20function=20(#5845)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../skript/classes/data/DefaultFunctions.java | 41 +++++++++++++++++++ .../skript/registrations/DefaultClasses.java | 6 ++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java b/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java index eee9206b0a0..ed95a1a4bc5 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java @@ -34,13 +34,16 @@ import ch.njol.util.coll.CollectionUtils; import org.bukkit.Bukkit; import org.bukkit.Location; +import org.bukkit.OfflinePlayer; import org.bukkit.World; +import org.bukkit.entity.Player; import org.bukkit.util.Vector; import org.eclipse.jdt.annotation.Nullable; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.Calendar; +import java.util.UUID; public class DefaultFunctions { @@ -500,6 +503,44 @@ public ColorRGB[] executeSimple(Object[][] params) { }).description("Returns a RGB color from the given red, green and blue parameters.") .examples("dye player's leggings rgb(120, 30, 45)") .since("2.5"); + + Functions.registerFunction(new SimpleJavaFunction<Player>("player", new Parameter[] { + new Parameter<>("nameOrUUID", DefaultClasses.STRING, true, null), + new Parameter<>("getExactPlayer", DefaultClasses.BOOLEAN, true, new SimpleLiteral<Boolean>(false, true)) // getExactPlayer -- grammar ¯\_ (ツ)_/¯ + }, DefaultClasses.PLAYER, true) { + @Override + public Player[] executeSimple(Object[][] params) { + String name = (String) params[0][0]; + boolean isExact = (boolean) params[1][0]; + UUID uuid = null; + if (name.length() > 16 || name.contains("-")) { // shortcut + try { + uuid = UUID.fromString(name); + } catch (IllegalArgumentException ignored) {} + } + return CollectionUtils.array(uuid != null ? Bukkit.getPlayer(uuid) : (isExact ? Bukkit.getPlayerExact(name) : Bukkit.getPlayer(name))); + } + }).description("Returns an online player from their name or UUID, if player is offline function will return nothing.", "Setting 'getExactPlayer' parameter to true will return the player whose name is exactly equal to the provided name instead of returning a player that their name starts with the provided name.") + .examples("set {_p} to player(\"Notch\") # will return an online player whose name is or starts with 'Notch'", "set {_p} to player(\"Notch\", true) # will return the only online player whose name is 'Notch'", "set {_p} to player(\"069a79f4-44e9-4726-a5be-fca90e38aaf5\") # <none> if player is offline") + .since("INSERT VERSION"); + + Functions.registerFunction(new SimpleJavaFunction<OfflinePlayer>("offlineplayer", new Parameter[] { + new Parameter<>("nameOrUUID", DefaultClasses.STRING, true, null) + }, DefaultClasses.OFFLINE_PLAYER, true) { + @Override + public OfflinePlayer[] executeSimple(Object[][] params) { + String name = (String) params[0][0]; + UUID uuid = null; + if (name.length() > 16 || name.contains("-")) { // shortcut + try { + uuid = UUID.fromString(name); + } catch (IllegalArgumentException ignored) {} + } + return CollectionUtils.array(uuid != null ? Bukkit.getOfflinePlayer(uuid) : Bukkit.getOfflinePlayer(name)); + } + }).description("Returns a offline player from their name or UUID. This function will still return the player if they're online.") + .examples("set {_p} to offlineplayer(\"Notch\")", "set {_p} to offlineplayer(\"069a79f4-44e9-4726-a5be-fca90e38aaf5\")") + .since("INSERT VERSION"); } } diff --git a/src/main/java/ch/njol/skript/registrations/DefaultClasses.java b/src/main/java/ch/njol/skript/registrations/DefaultClasses.java index 1d0f10448af..737089d7b19 100644 --- a/src/main/java/ch/njol/skript/registrations/DefaultClasses.java +++ b/src/main/java/ch/njol/skript/registrations/DefaultClasses.java @@ -19,7 +19,9 @@ package ch.njol.skript.registrations; import org.bukkit.Location; +import org.bukkit.OfflinePlayer; import org.bukkit.World; +import org.bukkit.entity.Player; import org.bukkit.util.Vector; import org.eclipse.jdt.annotation.NonNull; @@ -45,7 +47,9 @@ public class DefaultClasses { public static ClassInfo<Color> COLOR = getClassInfo(Color.class); public static ClassInfo<Date> DATE = getClassInfo(Date.class); public static ClassInfo<Timespan> TIMESPAN = getClassInfo(Timespan.class); - + public static ClassInfo<OfflinePlayer> OFFLINE_PLAYER = getClassInfo(OfflinePlayer.class); + public static ClassInfo<Player> PLAYER = getClassInfo(Player.class); + @NonNull private static <T> ClassInfo<T> getClassInfo(Class<T> tClass) { //noinspection ConstantConditions From 2d33094787505c0c19a35f94dcd50aea0ec7deb9 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Mon, 21 Aug 2023 14:18:25 -0600 Subject: [PATCH 399/619] Add the ability to execute as a bungeecord command (#5811) --- .../ch/njol/skript/effects/EffCommand.java | 76 ++++++++++++------- 1 file changed, 48 insertions(+), 28 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffCommand.java b/src/main/java/ch/njol/skript/effects/EffCommand.java index 928bf1e2089..3a26fd82dd7 100644 --- a/src/main/java/ch/njol/skript/effects/EffCommand.java +++ b/src/main/java/ch/njol/skript/effects/EffCommand.java @@ -20,6 +20,7 @@ import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @@ -33,52 +34,71 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.VariableString; import ch.njol.skript.util.StringMode; +import ch.njol.skript.util.Utils; import ch.njol.util.Kleenean; -/** - * @author Peter Güttinger - */ @Name("Command") -@Description("Executes a command. This can be useful to use other plugins in triggers.") -@Examples({"make player execute command \"/suicide\"", - "execute console command \"/say Hello everyone!\""}) -@Since("1.0") +@Description({ + "Executes a command. This can be useful to use other plugins in triggers.", + "If the command is a bungeecord side command, " + + "you can use the [bungeecord] option to execute command on the proxy." +}) +@Examples({ + "make player execute command \"/home\"", + "execute console command \"/say Hello everyone!\"", + "execute player bungeecord command \"/alert &6Testing Announcement!\"" +}) +@Since("1.0, INSERT VERSION (bungeecord command)") public class EffCommand extends Effect { + + public static final String MESSAGE_CHANNEL = "Message"; + static { Skript.registerEffect(EffCommand.class, - "[execute] [the] command %strings% [by %-commandsenders%]", - "[execute] [the] %commandsenders% command %strings%", - "(let|make) %commandsenders% execute [[the] command] %strings%"); + "[execute] [the] [bungee:bungee[cord]] command %strings% [by %-commandsenders%]", + "[execute] [the] %commandsenders% [bungee:bungee[cord]] command %strings%", + "(let|make) %commandsenders% execute [[the] [bungee:bungee[cord]] command] %strings%"); } - + @Nullable private Expression<CommandSender> senders; - @SuppressWarnings("null") private Expression<String> commands; - - @SuppressWarnings({"unchecked", "null"}) + private boolean bungeecord; + @Override - public boolean init(final Expression<?>[] vars, final int matchedPattern, final Kleenean isDelayed, final ParseResult parser) { + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { if (matchedPattern == 0) { - commands = (Expression<String>) vars[0]; - senders = (Expression<CommandSender>) vars[1]; + commands = (Expression<String>) exprs[0]; + senders = (Expression<CommandSender>) exprs[1]; } else { - senders = (Expression<CommandSender>) vars[0]; - commands = (Expression<String>) vars[1]; + senders = (Expression<CommandSender>) exprs[0]; + commands = (Expression<String>) exprs[1]; + } + bungeecord = parseResult.hasTag("bungee"); + if (bungeecord && senders == null) { + Skript.error("The commandsenders expression cannot be omitted when using the bungeecord option"); + return false; } commands = VariableString.setStringMode(commands, StringMode.COMMAND); return true; } - + @Override - public void execute(final Event e) { - for (String command : commands.getArray(e)) { + public void execute(Event event) { + for (String command : commands.getArray(event)) { assert command != null; if (command.startsWith("/")) command = "" + command.substring(1); if (senders != null) { - for (final CommandSender sender : senders.getArray(e)) { - assert sender != null; + for (CommandSender sender : senders.getArray(event)) { + if (bungeecord) { + if (!(sender instanceof Player)) + continue; + Player player = (Player) sender; + Utils.sendPluginMessage(player, EffConnect.BUNGEE_CHANNEL, MESSAGE_CHANNEL, player.getName(), "/" + command); + continue; + } Skript.dispatchCommand(sender, command); } } else { @@ -86,10 +106,10 @@ public void execute(final Event e) { } } } - + @Override - public String toString(final @Nullable Event e, final boolean debug) { - return "make " + (senders != null ? senders.toString(e, debug) : "the console") + " execute the command " + commands.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "make " + (senders != null ? senders.toString(event, debug) : "the console") + " execute " + (bungeecord ? "bungeecord " : "") + "command " + commands.toString(event, debug); } - + } From 129d6d28a734f188146b0fdfb6e238c12009016f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Aug 2023 09:28:51 +0300 Subject: [PATCH 400/619] Bump org.gradle.toolchains.foojay-resolver-convention from 0.6.0 to 0.7.0 (#5933) --- settings.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle b/settings.gradle index db69555628c..e5432e8214f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,6 +1,6 @@ plugins { - id 'org.gradle.toolchains.foojay-resolver-convention' version '0.6.0' + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0' } rootProject.name = 'Skript' From f32ac2c67f5287fa486146a33ffda7926a377b7c Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Tue, 22 Aug 2023 10:47:54 -0600 Subject: [PATCH 401/619] [2.7.0 target] Fix issue where mixing different types of conditionals (#5932) Fix issue where mixing different types of conditionals sometimes isn't allowed (#5872) (cherry picked from commit 639bcf7b06d293199664dc65d40328dcad6fa9d7) Co-authored-by: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> --- .../njol/skript/sections/SecConditional.java | 61 +++++++++++++------ .../tests/syntaxes/sections/SecConditional.sk | 35 +++++++++++ 2 files changed, 76 insertions(+), 20 deletions(-) diff --git a/src/main/java/ch/njol/skript/sections/SecConditional.java b/src/main/java/ch/njol/skript/sections/SecConditional.java index cadc1e333ce..470f3425405 100644 --- a/src/main/java/ch/njol/skript/sections/SecConditional.java +++ b/src/main/java/ch/njol/skript/sections/SecConditional.java @@ -118,21 +118,34 @@ public boolean init(Expression<?>[] exprs, multiline = parseResult.regexes.size() == 0 && type != ConditionalType.ELSE; // ensure this conditional is chained correctly (e.g. an else must have an if) - SecConditional lastIf; if (type != ConditionalType.IF) { - lastIf = getClosestIf(triggerItems); - if (lastIf == null) { - if (type == ConditionalType.ELSE_IF) { - Skript.error("'else if' has to be placed just after another 'if' or 'else if' section"); - } else if (type == ConditionalType.ELSE) { - Skript.error("'else' has to be placed just after another 'if' or 'else if' section"); - } else if (type == ConditionalType.THEN) { + if (type == ConditionalType.THEN) { + /* + * if this is a 'then' section, the preceding conditional has to be a multiline conditional section + * otherwise, you could put a 'then' section after a non-multiline 'if'. for example: + * if 1 is 1: + * set {_example} to true + * then: # this shouldn't be possible + * set {_uh oh} to true + */ + SecConditional precedingConditional = getPrecedingConditional(triggerItems, null); + if (precedingConditional == null || !precedingConditional.multiline) { Skript.error("'then' has to placed just after a multiline 'if' or 'else if' section"); + return false; + } + } else { + // find the latest 'if' section so that we can ensure this section is placed properly (e.g. ensure a 'if' occurs before an 'else') + SecConditional precedingIf = getPrecedingConditional(triggerItems, ConditionalType.IF); + if (precedingIf == null) { + if (type == ConditionalType.ELSE_IF) { + Skript.error("'else if' has to be placed just after another 'if' or 'else if' section"); + } else if (type == ConditionalType.ELSE) { + Skript.error("'else' has to be placed just after another 'if' or 'else if' section"); + } else if (type == ConditionalType.THEN) { + Skript.error("'then' has to placed just after a multiline 'if' or 'else if' section"); + } + return false; } - return false; - } else if (!lastIf.multiline && type == ConditionalType.THEN) { - Skript.error("'then' has to placed just after a multiline 'if' or 'else if' section"); - return false; } } else { // if this is a multiline if, we need to check if there is a "then" section after this @@ -150,7 +163,6 @@ public boolean init(Expression<?>[] exprs, return false; } } - lastIf = null; } // if this an "if" or "else if", let's try to parse the conditions right away @@ -231,9 +243,11 @@ public boolean init(Expression<?>[] exprs, return true; if (type == ConditionalType.ELSE) { + SecConditional precedingIf = getPrecedingConditional(triggerItems, ConditionalType.IF); + assert precedingIf != null; // at this point, we've validated the section so this can't be null // In an else section, ... if (hasDelayAfter.isTrue() - && lastIf.hasDelayAfter.isTrue() + && precedingIf.hasDelayAfter.isTrue() && getElseIfs(triggerItems).stream().map(SecConditional::getHasDelayAfter).allMatch(Kleenean::isTrue)) { // ... if the if section, all else-if sections and the else section have definite delays, // mark delayed as TRUE. @@ -314,21 +328,28 @@ private Kleenean getHasDelayAfter() { return hasDelayAfter; } + /** + * Gets the closest conditional section in the list of trigger items + * @param triggerItems the list of items to search for the closest conditional section in + * @param type the type of conditional section to find. if null is provided, any type is allowed. + * @return the closest conditional section + */ @Nullable - private static SecConditional getClosestIf(List<TriggerItem> triggerItems) { + private static SecConditional getPrecedingConditional(List<TriggerItem> triggerItems, @Nullable ConditionalType type) { // loop through the triggerItems in reverse order so that we find the most recent items first for (int i = triggerItems.size() - 1; i >= 0; i--) { TriggerItem triggerItem = triggerItems.get(i); if (triggerItem instanceof SecConditional) { - SecConditional secConditional = (SecConditional) triggerItem; + SecConditional conditionalSection = (SecConditional) triggerItem; - if (secConditional.type == ConditionalType.IF) - // if the condition is an if, we found our most recent preceding "if" - return secConditional; - else if (secConditional.type == ConditionalType.ELSE) + if (conditionalSection.type == ConditionalType.ELSE) { // if the conditional is an else, return null because it belongs to a different condition and ends // this one return null; + } else if (type == null || conditionalSection.type == type) { + // if the conditional matches the type argument, we found our most recent preceding conditional section + return conditionalSection; + } } else { return null; } diff --git a/src/test/skript/tests/syntaxes/sections/SecConditional.sk b/src/test/skript/tests/syntaxes/sections/SecConditional.sk index 98fb633745d..f9991e9f621 100644 --- a/src/test/skript/tests/syntaxes/sections/SecConditional.sk +++ b/src/test/skript/tests/syntaxes/sections/SecConditional.sk @@ -143,3 +143,38 @@ test "SecConditional - else if all false": then: set {_a} to false assert {_a} is not set with "'else if all' ran even though all conditions were false" + +test "SecConditional - starting with a non-multiline conditional": + if 1 is 1: + set {_a} to true + else if: + 1 is 8 + 2 is 7 + then: + set {_b} to true + assert {_a} is set with "non-multiline 'if' didn't run when before a multiline 'else if'" + +test "SecConditional - non-multiline conditional in the middle": + if: + 1 is 2 + 3 is 4 + then: + set {_b} to true + else if 1 is 1: + set {_a} to true + else if: + 5 is 6 + 7 is 8 + then: + set {_c} to true + assert {_a} is set with "non-multiline 'if' didn't run when used in the middle of a multiline 'else if'" + +test "SecConditional - non-multiline conditional at the end": + if: + 1 is 2 + 3 is 4 + then: + set {_b} to true + else if 1 is 1: + set {_a} to true + assert {_a} is set with "non-multiline 'if' didn't run used at the end of a multiline 'if'" From ac9c633772e2117f0dc35f1af5ea62a1740a0605 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Wed, 23 Aug 2023 12:22:59 +0300 Subject: [PATCH 402/619] =?UTF-8?q?=E2=9A=92=20Fix=20enchanting=20multiple?= =?UTF-8?q?=20items=20(#5927)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ch/njol/skript/effects/EffEnchant.java | 54 +++++++++---------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffEnchant.java b/src/main/java/ch/njol/skript/effects/EffEnchant.java index 7bd834a1caf..c83012f3423 100644 --- a/src/main/java/ch/njol/skript/effects/EffEnchant.java +++ b/src/main/java/ch/njol/skript/effects/EffEnchant.java @@ -53,55 +53,51 @@ public class EffEnchant extends Effect { } @SuppressWarnings("null") - private Expression<ItemType> item; + private Expression<ItemType> items; @Nullable - private Expression<EnchantmentType> enchs; + private Expression<EnchantmentType> enchantments; - @SuppressWarnings({"unchecked", "null"}) @Override - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { - item = (Expression<ItemType>) exprs[0]; - if (!ChangerUtils.acceptsChange(item, ChangeMode.SET, ItemStack.class)) { - Skript.error(item + " cannot be changed, thus it cannot be (dis)enchanted"); + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + items = (Expression<ItemType>) exprs[0]; + if (!ChangerUtils.acceptsChange(items, ChangeMode.SET, ItemStack.class)) { + Skript.error(items + " cannot be changed, thus it cannot be (dis)enchanted"); return false; } if (matchedPattern == 0) - enchs = (Expression<EnchantmentType>) exprs[1]; + enchantments = (Expression<EnchantmentType>) exprs[1]; return true; } @Override - protected void execute(final Event e) { - final ItemType i = item.getSingle(e); - if (i == null) + protected void execute(Event event) { + ItemType[] items = this.items.getArray(event); + if (items.length == 0) // short circuit return; - if (enchs != null) { - final EnchantmentType[] types = enchs.getArray(e); + + if (enchantments != null) { + EnchantmentType[] types = enchantments.getArray(event); if (types.length == 0) return; - - for (final EnchantmentType type : types) { - Enchantment ench = type.getType(); - assert ench != null; - i.addEnchantments(new EnchantmentType(ench, type.getLevel())); + for (ItemType item : items) { + for (EnchantmentType type : types) { + Enchantment enchantment = type.getType(); + assert enchantment != null; + item.addEnchantments(new EnchantmentType(enchantment, type.getLevel())); + } } - item.change(e, new ItemType[] {i}, ChangeMode.SET); } else { - final EnchantmentType[] types = i.getEnchantmentTypes(); - if (types == null) - return; - - for (final EnchantmentType ench : types) { - assert ench != null; - i.removeEnchantments(ench); + for (ItemType item : items) { + item.clearEnchantments(); } - item.change(e, new ItemType[] {i}, ChangeMode.SET); } + this.items.change(event, items.clone(), ChangeMode.SET); } @Override - public String toString(final @Nullable Event e, final boolean debug) { - return enchs == null ? "disenchant " + item.toString(e, debug) : "enchant " + item.toString(e, debug) + " with " + enchs; + public String toString(@Nullable Event event, boolean debug) { + return enchantments == null ? "disenchant " + items.toString(event, debug) : "enchant " + items.toString(event, debug) + " with " + enchantments; } } From e6a94ed7a503f4ddbbad6a2786e5cf72f6c63913 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Fri, 25 Aug 2023 14:13:11 +0300 Subject: [PATCH 403/619] =?UTF-8?q?=F0=9F=9A=80=20Add=20`inventory=20close?= =?UTF-8?q?=20reasons`=20(#5680)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../skript/classes/data/BukkitClasses.java | 12 +++- .../classes/data/BukkitEventValues.java | 7 ++ .../expressions/ExprInventoryCloseReason.java | 72 +++++++++++++++++++ src/main/resources/lang/default.lang | 13 ++++ 4 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprInventoryCloseReason.java 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 a89c6aa78b4..6e17db006da 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -61,6 +61,7 @@ import org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason; import org.bukkit.event.inventory.ClickType; import org.bukkit.event.inventory.InventoryAction; +import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.event.inventory.InventoryType; import org.bukkit.event.player.PlayerQuitEvent.QuitReason; import org.bukkit.event.player.PlayerResourcePackStatusEvent.Status; @@ -1507,8 +1508,17 @@ public String toVariableNameString(EnchantmentOffer eo) { Classes.registerClass(new EnumClassInfo<>(QuitReason.class, "quitreason", "quit reasons") .user("(quit|disconnect) ?(reason|cause)s?") .name("Quit Reason") - .description("Represents a quit reason from a player quit server event.") + .description("Represents a quit reason from a <a href='/events.html#quit'>player quit server event</a>.") .requiredPlugins("Paper 1.16.5+") .since("INSERT VERSION")); + + if (Skript.classExists("org.bukkit.event.inventory.InventoryCloseEvent$Reason")) + Classes.registerClass(new EnumClassInfo<>(InventoryCloseEvent.Reason.class, "inventoryclosereason", "inventory close reasons") + .user("inventory ?close ?reasons?") + .name("Inventory Close Reasons") + .description("The inventory close reason in an <a href='/events.html#inventory_close'>inventory close event</a>.") + .requiredPlugins("Paper") + .since("INSERT VERSION")); } + } 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 ac6159e14b5..f3319fa617f 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -1309,6 +1309,13 @@ public Inventory get(final InventoryCloseEvent e) { return e.getInventory(); } }, 0); + if (Skript.classExists("org.bukkit.event.inventory.InventoryCloseEvent$Reason")) + EventValues.registerEventValue(InventoryCloseEvent.class, InventoryCloseEvent.Reason.class, new Getter<InventoryCloseEvent.Reason, InventoryCloseEvent>() { + @Override + public InventoryCloseEvent.Reason get(InventoryCloseEvent event) { + return event.getReason(); + } + }, EventValues.TIME_NOW); //InventoryPickupItemEvent EventValues.registerEventValue(InventoryPickupItemEvent.class, Inventory.class, new Getter<Inventory, InventoryPickupItemEvent>() { @Nullable diff --git a/src/main/java/ch/njol/skript/expressions/ExprInventoryCloseReason.java b/src/main/java/ch/njol/skript/expressions/ExprInventoryCloseReason.java new file mode 100644 index 00000000000..61443bc9293 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprInventoryCloseReason.java @@ -0,0 +1,72 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.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.expressions.base.EventValueExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Inventory Close Reason") +@Description("The <a href='/classes.html#inventoryclosereason'>inventory close reason</a> of an <a href='/events.html#inventory_close'>inventory close event</a>.") +@Examples({ + "on inventory close:", + "\tinventory close reason is teleport", + "\tsend \"Your inventory closed due to teleporting!\" to player" +}) +@Events("Inventory Close") +@RequiredPlugins("Paper") +@Since("INSERT VERSION") +public class ExprInventoryCloseReason extends EventValueExpression<InventoryCloseEvent.Reason> { + + static { + if (Skript.classExists("org.bukkit.event.inventory.InventoryCloseEvent$Reason")) + Skript.registerExpression(ExprInventoryCloseReason.class, InventoryCloseEvent.Reason.class, ExpressionType.SIMPLE, "[the] inventory clos(e|ing) (reason|cause)"); + } + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(InventoryCloseEvent.class)) { + Skript.error("The 'inventory close reason' expression can only be used in an inventory close event"); + return false; + } + return true; + } + + public ExprInventoryCloseReason() { + super(InventoryCloseEvent.Reason.class); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "inventory close reason"; + } + +} diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index c9f41ee930b..4d0bfe3dff3 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -1317,6 +1317,18 @@ teleport causes: dismount: dismount, dismounted exit_bed: exit bed, exiting bed, bed exit +# -- Inventory Close Reasons -- +inventory close reasons: + unknown: unknown + teleport: teleport + cant_use: can't use, can not use, cannot use + unloaded: unloaded + open_new: open new, new opened + player: player + disconnect: disconnected, disconnect + death: death + plugin: plugin + # -- Game Modes -- game modes: survival: survival @@ -2010,6 +2022,7 @@ types: gene: panda gene¦s @a gamerulevalue: gamerule value¦s @a quitreason: quit reason¦s @a + inventoryclosereason: inventory close reason¦s @an # Skript weathertype: weather type¦s @a From f4b7b06954f7250f20391e70dbda1691e14b152e Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Sat, 26 Aug 2023 18:58:03 +0300 Subject: [PATCH 404/619] =?UTF-8?q?=E2=9A=92=20[2.7]=20Fix=20enchanting=20?= =?UTF-8?q?multiple=20items=20(#5934)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ch/njol/skript/effects/EffEnchant.java | 54 +++++++++---------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffEnchant.java b/src/main/java/ch/njol/skript/effects/EffEnchant.java index 7bd834a1caf..c83012f3423 100644 --- a/src/main/java/ch/njol/skript/effects/EffEnchant.java +++ b/src/main/java/ch/njol/skript/effects/EffEnchant.java @@ -53,55 +53,51 @@ public class EffEnchant extends Effect { } @SuppressWarnings("null") - private Expression<ItemType> item; + private Expression<ItemType> items; @Nullable - private Expression<EnchantmentType> enchs; + private Expression<EnchantmentType> enchantments; - @SuppressWarnings({"unchecked", "null"}) @Override - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { - item = (Expression<ItemType>) exprs[0]; - if (!ChangerUtils.acceptsChange(item, ChangeMode.SET, ItemStack.class)) { - Skript.error(item + " cannot be changed, thus it cannot be (dis)enchanted"); + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + items = (Expression<ItemType>) exprs[0]; + if (!ChangerUtils.acceptsChange(items, ChangeMode.SET, ItemStack.class)) { + Skript.error(items + " cannot be changed, thus it cannot be (dis)enchanted"); return false; } if (matchedPattern == 0) - enchs = (Expression<EnchantmentType>) exprs[1]; + enchantments = (Expression<EnchantmentType>) exprs[1]; return true; } @Override - protected void execute(final Event e) { - final ItemType i = item.getSingle(e); - if (i == null) + protected void execute(Event event) { + ItemType[] items = this.items.getArray(event); + if (items.length == 0) // short circuit return; - if (enchs != null) { - final EnchantmentType[] types = enchs.getArray(e); + + if (enchantments != null) { + EnchantmentType[] types = enchantments.getArray(event); if (types.length == 0) return; - - for (final EnchantmentType type : types) { - Enchantment ench = type.getType(); - assert ench != null; - i.addEnchantments(new EnchantmentType(ench, type.getLevel())); + for (ItemType item : items) { + for (EnchantmentType type : types) { + Enchantment enchantment = type.getType(); + assert enchantment != null; + item.addEnchantments(new EnchantmentType(enchantment, type.getLevel())); + } } - item.change(e, new ItemType[] {i}, ChangeMode.SET); } else { - final EnchantmentType[] types = i.getEnchantmentTypes(); - if (types == null) - return; - - for (final EnchantmentType ench : types) { - assert ench != null; - i.removeEnchantments(ench); + for (ItemType item : items) { + item.clearEnchantments(); } - item.change(e, new ItemType[] {i}, ChangeMode.SET); } + this.items.change(event, items.clone(), ChangeMode.SET); } @Override - public String toString(final @Nullable Event e, final boolean debug) { - return enchs == null ? "disenchant " + item.toString(e, debug) : "enchant " + item.toString(e, debug) + " with " + enchs; + public String toString(@Nullable Event event, boolean debug) { + return enchantments == null ? "disenchant " + items.toString(event, debug) : "enchant " + items.toString(event, debug) + " with " + enchantments; } } From 58e6707b2838a6579dff747d0c074a8ab3549220 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Sat, 26 Aug 2023 22:15:12 -0600 Subject: [PATCH 405/619] Add defending for simple property expressions (#5874) * Add defending for simple property expressions * Remove redundent javadoc --------- Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> --- .../base/SimplePropertyExpression.java | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/base/SimplePropertyExpression.java b/src/main/java/ch/njol/skript/expressions/base/SimplePropertyExpression.java index 2e7b581b5d2..04918958a92 100644 --- a/src/main/java/ch/njol/skript/expressions/base/SimplePropertyExpression.java +++ b/src/main/java/ch/njol/skript/expressions/base/SimplePropertyExpression.java @@ -24,38 +24,49 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.util.LiteralUtils; import ch.njol.util.Kleenean; /** * A base class for property expressions that requires only few overridden methods * - * @author Peter Güttinger * @see PropertyExpression * @see PropertyExpression#register(Class, Class, String, String) */ @SuppressWarnings("deprecation") // for backwards compatibility public abstract class SimplePropertyExpression<F, T> extends PropertyExpression<F, T> implements Converter<F, T> { - - @SuppressWarnings({"unchecked", "null"}) + @Override - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (LiteralUtils.hasUnparsedLiteral(exprs[0])) { + setExpr(LiteralUtils.defendExpression(exprs[0])); + return LiteralUtils.canInitSafely(getExpr()); + } setExpr((Expression<? extends F>) exprs[0]); return true; } - - protected abstract String getPropertyName(); - + @Override @Nullable public abstract T convert(F f); - + @Override - protected T[] get(final Event e, final F[] source) { + protected T[] get(Event event, F[] source) { return super.get(source, this); } - + + /** + * Used to collect the property type used in the register method. + * This forms the toString of this SimplePropertyExpression. + * + * @return The name of the type used when registering this SimplePropertyExpression. + */ + protected abstract String getPropertyName(); + @Override - public String toString(final @Nullable Event e, final boolean debug) { - return "the " + getPropertyName() + " of " + getExpr().toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return getPropertyName() + " of " + getExpr().toString(event, debug); } + } From 34a36cce9ad4869220071b29fcef36e1303f314c Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Tue, 29 Aug 2023 00:50:25 -0700 Subject: [PATCH 406/619] Adds ExprPercent (#5949) --- .../njol/skript/expressions/ExprPercent.java | 90 +++++++++++++++++++ .../tests/syntaxes/expressions/ExprPercent.sk | 17 ++++ 2 files changed, 107 insertions(+) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprPercent.java create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprPercent.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprPercent.java b/src/main/java/ch/njol/skript/expressions/ExprPercent.java new file mode 100644 index 00000000000..be5778fa6df --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprPercent.java @@ -0,0 +1,90 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.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 org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Percent of") +@Description("Returns a percentage of one or more numbers.") +@Examples({ + "set damage to 10% of victim's health", + "set damage to 125 percent of damage", + "set {_result} to {_percent} percent of 999", + "set {_result::*} to 10% of {_numbers::*}", + "set experience to 50% of player's total experience" +}) +@Since("INSERT VERSION") +public class ExprPercent extends SimpleExpression<Number> { + + static { + Skript.registerExpression(ExprPercent.class, Number.class, ExpressionType.COMBINED, "%number%(\\%| percent) of %numbers%"); + } + + private Expression<Number> percent; + private Expression<Number> numbers; + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + percent = (Expression<Number>) exprs[0]; + numbers = (Expression<Number>) exprs[1]; + return true; + } + + @Override + protected @Nullable Number[] get(Event event) { + Number percent = this.percent.getSingle(event); + Number[] numbers = this.numbers.getArray(event); + if (percent == null || numbers.length == 0) + return null; + + Number[] results = new Number[numbers.length]; + for (int i = 0; i < numbers.length; i++) { + results[i] = numbers[i].doubleValue() * percent.doubleValue() / 100; + } + + return results; + } + + @Override + public boolean isSingle() { + return numbers.isSingle(); + } + + @Override + public Class<? extends Number> getReturnType() { + return Number.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return percent.toString(event, debug) + " percent of " + numbers.toString(event, debug); + } + +} diff --git a/src/test/skript/tests/syntaxes/expressions/ExprPercent.sk b/src/test/skript/tests/syntaxes/expressions/ExprPercent.sk new file mode 100644 index 00000000000..a2d3dfc377c --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprPercent.sk @@ -0,0 +1,17 @@ +test "percent of": + assert 5% of 100 is 5 with "5%% of 100 is 5" + assert 0 percent of 100 is 0 with "0%% of 100 is 0" + assert -5% of 100 is -5 with "-5%% of 100 is -5" + assert infinity value% of 100 is infinity value with "infinity%% of 100 is infinity" + assert 20 percent of 0 is 0 with "20%% of 0 is 0" + assert 110% of 100 is 110 with "110%% of 100 is 110" + + set {_a::*} to 50% of (100, 50, 0, and infinity value) + assert {_a::1} is 50 with "50%% of 100 is 50 (list)" + assert {_a::2} is 25 with "50%% of 50 is 25 (list)" + assert {_a::3} is 0 with "50%% of 0 is 0 (list)" + assert {_a::4} is infinity value with "50%% of infinity is infinity (list)" + + assert {_none}% of 10 is not set with "none%% of 10 is none" + assert {_none}% of {_none} is not set with "none%% of none is none" + assert 10% of {_none} is not set with "10%% of none is none" From 2147b8b1486a261be90336935f8fb582163a45e1 Mon Sep 17 00:00:00 2001 From: nylhus <98179506+nylhus@users.noreply.github.com> Date: Tue, 29 Aug 2023 05:02:17 -0300 Subject: [PATCH 407/619] Added "apply bone meal" effect. (#5898) --- .../njol/skript/effects/EffApplyBoneMeal.java | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 src/main/java/ch/njol/skript/effects/EffApplyBoneMeal.java diff --git a/src/main/java/ch/njol/skript/effects/EffApplyBoneMeal.java b/src/main/java/ch/njol/skript/effects/EffApplyBoneMeal.java new file mode 100644 index 00000000000..484654a14f6 --- /dev/null +++ b/src/main/java/ch/njol/skript/effects/EffApplyBoneMeal.java @@ -0,0 +1,77 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.effects; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.Bukkit; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Apply Bone Meal") +@Description("Applies bone meal to a crop, sapling, or composter") +@Examples("apply 3 bone meal to event-block") +@RequiredPlugins("MC 1.16.2+") +@Since("INSERT VERSION") +public class EffApplyBoneMeal extends Effect { + + static { + if (Skript.isRunningMinecraft(1, 16, 2)) + Skript.registerEffect(EffApplyBoneMeal.class, "apply [%-number%] bone[ ]meal[s] [to %blocks%]"); + } + + @Nullable + private Expression<Number> amount; + private Expression<Block> blocks; + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + amount = (Expression<Number>) exprs[0]; + blocks = (Expression<Block>) exprs[1]; + return true; + } + + @Override + protected void execute(Event event) { + int times = 1; + if (amount != null) + times = amount.getOptionalSingle(event).orElse(0).intValue(); + for (Block block : blocks.getArray(event)) { + for (int i = 0; i < times; i++) { + block.applyBoneMeal(BlockFace.UP); + } + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "apply " + amount != null ? amount.toString(event, debug) + " " : "" + "bone meal to " + blocks.toString(event, debug); + } + +} From f8a3131b4f5b9396850799892d21d6bc43ef2c8f Mon Sep 17 00:00:00 2001 From: DelayedGaming <72163224+DelayedGaming@users.noreply.github.com> Date: Tue, 29 Aug 2023 16:19:52 +0800 Subject: [PATCH 408/619] New Expression: Free / Max / Total Memory (#5636) --- .../njol/skript/expressions/ExprMemory.java | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprMemory.java diff --git a/src/main/java/ch/njol/skript/expressions/ExprMemory.java b/src/main/java/ch/njol/skript/expressions/ExprMemory.java new file mode 100644 index 00000000000..c9dd2ba1151 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprMemory.java @@ -0,0 +1,104 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import java.util.Locale; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.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 org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Free / Max / Total Memory") +@Description("The free, max or total memory of the server in Megabytes.") +@Examples({ + "while player is online:", + "\tsend action bar \"Memory left: %free memory%/%max memory%MB\" to player", + "\twait 5 ticks" +}) +@Since("INSERT VERSION") +public class ExprMemory extends SimpleExpression<Double> { + + private static final double BYTES_IN_MEGABYTES = 1E-6; + private static final Runtime RUNTIME = Runtime.getRuntime(); + + static { + Skript.registerExpression(ExprMemory.class, Double.class, ExpressionType.SIMPLE, "[the] [server] (:free|max:max[imum]|total) (memory|ram)"); + } + + private enum Type { + FREE, MAXIMUM, TOTAL + } + + private Type type; + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (parseResult.hasTag("free")) { + type = Type.FREE; + } else if (parseResult.hasTag("max")) { + type = Type.MAXIMUM; + } else { + type = Type.TOTAL; + } + return true; + } + + @Override + protected Double[] get(Event event) { + double memory = 0; + switch (type) { + case FREE: + memory = RUNTIME.freeMemory(); + break; + case MAXIMUM: + memory = RUNTIME.maxMemory(); + break; + case TOTAL: + memory = RUNTIME.totalMemory(); + break; + } + return CollectionUtils.array(memory * BYTES_IN_MEGABYTES); + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class<? extends Double> getReturnType() { + return Double.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return type.name().toLowerCase(Locale.ENGLISH) + " memory"; + } + +} From 91a6e9f1796fee70c70042602161b1f2a6858bad Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Tue, 29 Aug 2023 11:28:54 +0300 Subject: [PATCH 409/619] =?UTF-8?q?=F0=9F=9A=80=20Add=20inventory=20raw=20?= =?UTF-8?q?slot=20expression=20(#4593)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../classes/data/BukkitEventValues.java | 2 +- .../njol/skript/expressions/ExprClicked.java | 3 +- .../skript/expressions/ExprSlotIndex.java | 51 ++++++++++++++----- .../njol/skript/util/slot/InventorySlot.java | 44 ++++++++++------ .../njol/skript/util/slot/SlotWithIndex.java | 8 +++ 5 files changed, 77 insertions(+), 31 deletions(-) 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 f3319fa617f..d0645163c47 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -1074,7 +1074,7 @@ public Slot get(final InventoryClickEvent e) { if (invi instanceof PlayerInventory && slotIndex >= 36) { return new ch.njol.skript.util.slot.EquipmentSlot(((PlayerInventory) invi).getHolder(), slotIndex); } else { - return new InventorySlot(invi, slotIndex); + return new InventorySlot(invi, slotIndex, e.getRawSlot()); } } }, 0); diff --git a/src/main/java/ch/njol/skript/expressions/ExprClicked.java b/src/main/java/ch/njol/skript/expressions/ExprClicked.java index b336da8b8bf..f283759c3e0 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprClicked.java +++ b/src/main/java/ch/njol/skript/expressions/ExprClicked.java @@ -216,8 +216,9 @@ protected Object[] get(Event e) { // Slots are specific to inventories, so refering to wrong one is impossible // (as opposed to using the numbers directly) Inventory invi = ((InventoryClickEvent) e).getClickedInventory(); + InventoryClickEvent event = ((InventoryClickEvent) e); if (invi != null) // Inventory is technically not guaranteed to exist... - return CollectionUtils.array(new InventorySlot(invi, ((InventoryClickEvent) e).getSlot())); + return CollectionUtils.array(new InventorySlot(invi, event.getSlot(), event.getRawSlot())); break; case ENCHANT_BUTTON: if (e instanceof EnchantItemEvent) diff --git a/src/main/java/ch/njol/skript/expressions/ExprSlotIndex.java b/src/main/java/ch/njol/skript/expressions/ExprSlotIndex.java index 3af33419225..ff37907b9c9 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprSlotIndex.java +++ b/src/main/java/ch/njol/skript/expressions/ExprSlotIndex.java @@ -18,6 +18,9 @@ */ package ch.njol.skript.expressions; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.doc.Description; @@ -29,26 +32,46 @@ import ch.njol.skript.util.slot.SlotWithIndex; @Name("Slot Index") -@Description("Index of an an inventory slot. Other types of slots may or may " - + "not have indices. Note that comparing slots with numbers is also " - + "possible; if index of slot is same as the number, comparison" - + "succeeds. This expression is mainly for the cases where you must " - + "for some reason save the slot numbers.") -@Examples({"if index of event-slot is 10:", - "\tsend \"You bought a pie!\""}) -@Since("2.2-dev35") +@Description({ + "Index of an an inventory slot. Other types of slots may or may " + + "not have indices. Note that comparing slots with numbers is also " + + "possible; if index of slot is same as the number, comparison" + + "succeeds. This expression is mainly for the cases where you must " + + "for some reason save the slot numbers.", + "", + "Raw index of slot is unique for the view, see <a href=\"https://wiki.vg/Inventory\">Minecraft Wiki</a>", +}) +@Examples({ + "if index of event-slot is 10:", + "\tsend \"You bought a pie!\"", + "", + "if display name of player's top inventory is \"Custom Menu\": # 3 rows inventory", + "\tif raw index of event-slot > 27: # outside custom inventory", + "\t\tcancel event", +}) +@Since("2.2-dev35, INSERT VERSION (raw index)") public class ExprSlotIndex extends SimplePropertyExpression<Slot, Long> { static { - register(ExprSlotIndex.class, Long.class, "index", "slots"); + register(ExprSlotIndex.class, Long.class, "[raw:(raw|unique)] index", "slots"); } - + + private boolean isRaw; + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + isRaw = parseResult.hasTag("raw"); + return super.init(exprs, matchedPattern, isDelayed, parseResult); + } + @Override @Nullable - public Long convert(Slot f) { - if (f instanceof SlotWithIndex) - return (long) ((SlotWithIndex) f).getIndex(); - + public Long convert(Slot slot) { + if (slot instanceof SlotWithIndex) { + SlotWithIndex slotWithIndex = (SlotWithIndex) slot; + return (long) (isRaw ? slotWithIndex.getRawIndex() : slotWithIndex.getIndex()); + } + return 0L; // Slot does not have index. At all } diff --git a/src/main/java/ch/njol/skript/util/slot/InventorySlot.java b/src/main/java/ch/njol/skript/util/slot/InventorySlot.java index 1d5a7c10d42..74501f3a12d 100644 --- a/src/main/java/ch/njol/skript/util/slot/InventorySlot.java +++ b/src/main/java/ch/njol/skript/util/slot/InventorySlot.java @@ -37,48 +37,62 @@ * Represents a slot in some inventory. */ public class InventorySlot extends SlotWithIndex { - + private final Inventory invi; private final int index; - - public InventorySlot(final Inventory invi, final int index) { + private final int rawIndex; + + public InventorySlot(Inventory invi, int index, int rawIndex) { assert invi != null; assert index >= 0; this.invi = invi; this.index = index; + this.rawIndex = rawIndex; } - + + public InventorySlot(Inventory invi, int index) { + assert invi != null; + assert index >= 0; + this.invi = invi; + this.index = rawIndex = index; + } + public Inventory getInventory() { return invi; } - + @Override public int getIndex() { return index; } - + + @Override + public int getRawIndex() { + return rawIndex; + } + @Override @Nullable public ItemStack getItem() { - if (index == -999) //Non-existent slot, e.g. Outside GUI + if (index == -999) //Non-existent slot, e.g. Outside GUI return null; ItemStack item = invi.getItem(index); return item == null ? new ItemStack(Material.AIR, 1) : item.clone(); } - + @Override public void setItem(final @Nullable ItemStack item) { invi.setItem(index, item != null && item.getType() != Material.AIR ? item : null); if (invi instanceof PlayerInventory) PlayerUtils.updateInventory((Player) invi.getHolder()); } - + @Override public int getAmount() { ItemStack item = invi.getItem(index); return item != null ? item.getAmount() : 0; } - + @Override public void setAmount(int amount) { ItemStack item = invi.getItem(index); @@ -87,21 +101,21 @@ public void setAmount(int amount) { if (invi instanceof PlayerInventory) PlayerUtils.updateInventory((Player) invi.getHolder()); } - + @Override public String toString(@Nullable Event e, boolean debug) { InventoryHolder holder = invi != null ? invi.getHolder() : null; - + if (holder instanceof BlockState) holder = new BlockInventoryHolder((BlockState) holder); - + if (holder != null) { if (invi instanceof CraftingInventory) // 4x4 crafting grid is contained in player too! return "crafting slot " + index + " of " + Classes.toString(holder); - + return "inventory slot " + index + " of " + Classes.toString(holder); } return "inventory slot " + index + " of " + Classes.toString(invi); } - + } diff --git a/src/main/java/ch/njol/skript/util/slot/SlotWithIndex.java b/src/main/java/ch/njol/skript/util/slot/SlotWithIndex.java index 202c3a1f9a4..17efbfe400a 100644 --- a/src/main/java/ch/njol/skript/util/slot/SlotWithIndex.java +++ b/src/main/java/ch/njol/skript/util/slot/SlotWithIndex.java @@ -28,6 +28,14 @@ public abstract class SlotWithIndex extends Slot { * @return Index of the slot. */ public abstract int getIndex(); + + /** + * Gets the raw index of this slot. + * @return Raw index of the slot. + */ + public int getRawIndex() { + return getIndex(); + } @Override public boolean isSameSlot(Slot o) { From 8ea6714d997087b7d395c05464738c702df34257 Mon Sep 17 00:00:00 2001 From: DelayedGaming <72163224+DelayedGaming@users.noreply.github.com> Date: Tue, 29 Aug 2023 17:50:57 +0800 Subject: [PATCH 410/619] EvtPlayerChunkEnter - Fix Docs (#5629) --- src/main/java/ch/njol/skript/events/EvtPlayerChunkEnter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/events/EvtPlayerChunkEnter.java b/src/main/java/ch/njol/skript/events/EvtPlayerChunkEnter.java index e79030c1553..bd4f59eb7c3 100644 --- a/src/main/java/ch/njol/skript/events/EvtPlayerChunkEnter.java +++ b/src/main/java/ch/njol/skript/events/EvtPlayerChunkEnter.java @@ -31,7 +31,7 @@ public class EvtPlayerChunkEnter extends SkriptEvent { static { - Skript.registerEvent("Player Chunk Enters", EvtPlayerChunkEnter.class, PlayerMoveEvent.class, "[player] (enter[s] [a] chunk|chunk enter[ing])") + Skript.registerEvent("Player Chunk Enter", EvtPlayerChunkEnter.class, PlayerMoveEvent.class, "[player] (enter[s] [a] chunk|chunk enter[ing])") .description("Called when a player enters a chunk. Note that this event is based on 'player move' event, and may be called frequent internally.") .examples( "on player enters a chunk:", From 27106e2c98528d740e2f2e372f0cb47b379a20e8 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Tue, 29 Aug 2023 04:18:58 -0600 Subject: [PATCH 411/619] Change ExprTarget to use raytracing (#5947) --- .../njol/skript/expressions/ExprTarget.java | 30 ++++++------------- .../syntaxes/expressions/ExprYawPitch.sk | 5 ++++ 2 files changed, 14 insertions(+), 21 deletions(-) create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprYawPitch.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprTarget.java b/src/main/java/ch/njol/skript/expressions/ExprTarget.java index 8324df70070..1d8097aceb5 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTarget.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTarget.java @@ -19,6 +19,7 @@ package ch.njol.skript.expressions; import ch.njol.skript.Skript; +import ch.njol.skript.SkriptConfig; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; @@ -40,6 +41,7 @@ import org.bukkit.entity.Player; import org.bukkit.event.Event; import org.bukkit.event.entity.EntityTargetEvent; +import org.bukkit.util.RayTraceResult; import org.bukkit.util.Vector; import org.eclipse.jdt.annotation.Nullable; @@ -155,33 +157,19 @@ public String toString(@Nullable Event event, boolean debug) { * @param type The exact EntityData to find. Can be null for any entity. * @return The entity's target */ - // TODO Switch this over to RayTraceResults 1.13+ when 1.12 support is dropped. @Nullable @SuppressWarnings("unchecked") public static <T extends Entity> T getTarget(LivingEntity entity, @Nullable EntityData<T> type) { if (entity instanceof Mob) return ((Mob) entity).getTarget() == null || type != null && !type.isInstance(((Mob) entity).getTarget()) ? null : (T) ((Mob) entity).getTarget(); - Vector direction = entity.getLocation().getDirection().normalize(); - Vector eye = entity.getEyeLocation().toVector(); - double cos45 = Math.cos(Math.PI / 4); - double targetDistanceSquared = 0; - double radiusSquared = 1; - T target = null; - - for (T other : type == null ? (List<T>) entity.getWorld().getEntities() : entity.getWorld().getEntitiesByClass(type.getType())) { - if (other == null || other == entity || type != null && !type.isInstance(other)) - continue; - - if (target == null || targetDistanceSquared > other.getLocation().distanceSquared(entity.getLocation())) { - Vector t = other.getLocation().add(0, 1, 0).toVector().subtract(eye); - if (direction.clone().crossProduct(t).lengthSquared() < radiusSquared && t.normalize().dot(direction) >= cos45) { - target = other; - targetDistanceSquared = target.getLocation().distanceSquared(entity.getLocation()); - } - } - } - return target; + RayTraceResult result = entity.rayTraceEntities(SkriptConfig.maxTargetBlockDistance.value()); + if (result == null) + return null; + Entity hitEntity = result.getHitEntity(); + if (type != null && !type.isInstance(hitEntity)) + return null; + return (T) result.getHitEntity(); } } diff --git a/src/test/skript/tests/syntaxes/expressions/ExprYawPitch.sk b/src/test/skript/tests/syntaxes/expressions/ExprYawPitch.sk new file mode 100644 index 00000000000..225787c522d --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprYawPitch.sk @@ -0,0 +1,5 @@ +test "yaw and pitch": + set {_test} to spawn location of world "world" + assert yaw of {_test} is 0 with "The yaw of the spawn location was not 0" + set yaw of {_test} to 90 + assert yaw of {_test} is 90 with "The yaw of the spawn location was not set to 90" From f2e9a416c64b2b35996900b554e839c626186594 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Tue, 29 Aug 2023 17:19:39 +0300 Subject: [PATCH 412/619] =?UTF-8?q?=F0=9F=9B=A0=20[2.7]=20Fix=20Duplicated?= =?UTF-8?q?=20Spaces=20in=20StructCommand=20(#5416)=20(#5929)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ch/njol/skript/structures/StructCommand.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/ch/njol/skript/structures/StructCommand.java b/src/main/java/ch/njol/skript/structures/StructCommand.java index c389c62d7bd..0b86f6762f8 100644 --- a/src/main/java/ch/njol/skript/structures/StructCommand.java +++ b/src/main/java/ch/njol/skript/structures/StructCommand.java @@ -82,10 +82,9 @@ public class StructCommand extends Structure { public static final Priority PRIORITY = new Priority(500); - private static final Pattern - COMMAND_PATTERN = Pattern.compile("(?i)^command /?(\\S+)\\s*(\\s+(.+))?$"), - ARGUMENT_PATTERN = Pattern.compile("<\\s*(?:([^>]+?)\\s*:\\s*)?(.+?)\\s*(?:=\\s*(" + SkriptParser.wildcard + "))?\\s*>"), - DESCRIPTION_PATTERN = Pattern.compile("(?<!\\\\)%-?(.+?)%"); + private static final Pattern COMMAND_PATTERN = Pattern.compile("(?i)^command\\s+/?(\\S+)\\s*(\\s+(.+))?$"); + private static final Pattern ARGUMENT_PATTERN = Pattern.compile("<\\s*(?:([^>]+?)\\s*:\\s*)?(.+?)\\s*(?:=\\s*(" + SkriptParser.wildcard + "))?\\s*>"); + private static final Pattern DESCRIPTION_PATTERN = Pattern.compile("(?<!\\\\)%-?(.+?)%"); private static final AtomicBoolean SYNC_COMMANDS = new AtomicBoolean(); @@ -184,7 +183,10 @@ public boolean load() { Matcher matcher = COMMAND_PATTERN.matcher(fullCommand); boolean matches = matcher.matches(); - assert matches; + if (!matches) { + Skript.error("Invalid command structure pattern"); + return false; + } String command = matcher.group(1).toLowerCase(); ScriptCommand existingCommand = Commands.getScriptCommand(command); From 276a1911bf27a658d6e72e0dbd2d0df5670deb42 Mon Sep 17 00:00:00 2001 From: Shane Bee <shanebolenback@me.com> Date: Thu, 31 Aug 2023 00:08:30 -0700 Subject: [PATCH 413/619] SimpleEntityData - Display should be super entity (#5886) --- src/main/java/ch/njol/skript/entity/SimpleEntityData.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/entity/SimpleEntityData.java b/src/main/java/ch/njol/skript/entity/SimpleEntityData.java index 963244c9844..693f7b5773e 100644 --- a/src/main/java/ch/njol/skript/entity/SimpleEntityData.java +++ b/src/main/java/ch/njol/skript/entity/SimpleEntityData.java @@ -307,7 +307,7 @@ private static void addSuperEntity(String codeName, Class<? extends Entity> enti addSimpleEntity("item display", ItemDisplay.class); addSimpleEntity("block display", BlockDisplay.class); addSimpleEntity("interaction", Interaction.class); - addSimpleEntity("display", Display.class); + addSuperEntity("display", Display.class); } // Register zombie after Husk and Drowned to make sure both work From 2a9cc6f65510183856077c0d56e41e4f9beabcb5 Mon Sep 17 00:00:00 2001 From: DelayedGaming <simmz0403@gmail.com> Date: Sat, 2 Sep 2023 00:38:46 +0800 Subject: [PATCH 414/619] Finish --- src/main/java/ch/njol/skript/events/EvtMove.java | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/main/java/ch/njol/skript/events/EvtMove.java b/src/main/java/ch/njol/skript/events/EvtMove.java index 6e22096c85b..d20d2450008 100644 --- a/src/main/java/ch/njol/skript/events/EvtMove.java +++ b/src/main/java/ch/njol/skript/events/EvtMove.java @@ -79,15 +79,11 @@ public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResu public boolean check(Event event) { if (isPlayer && event instanceof PlayerMoveEvent) { PlayerMoveEvent playerEvent = (PlayerMoveEvent) event; - if (isRotate) - return rotateCheck(playerEvent.getFrom(), playerEvent.getTo()); - return moveCheck(playerEvent.getFrom(), playerEvent.getTo()); + return moveCheck(playerEvent.getFrom(), playerEvent.getTo()) ^ isRotate; } else if (HAS_ENTITY_MOVE && event instanceof EntityMoveEvent) { EntityMoveEvent entityEvent = (EntityMoveEvent) event; if (type.isInstance(entityEvent.getEntity())) { - if (isRotate) - return rotateCheck(entityEvent.getFrom(), entityEvent.getTo()); - return moveCheck(entityEvent.getFrom(), entityEvent.getTo()); + return moveCheck(entityEvent.getFrom(), entityEvent.getTo()) ^ isRotate; } } return false; @@ -115,8 +111,4 @@ private static boolean moveCheck(Location from, Location to) { return from.getX() != to.getX() || from.getY() != to.getY() || from.getZ() != to.getZ() || from.getWorld() != to.getWorld(); } - private static boolean rotateCheck(Location from, Location to) { - return !moveCheck(from, to) && from.getYaw() != to.getYaw() || from.getPitch() != to.getPitch(); - } - } From dd838d1b37c052719d755ed0b5febe0f8d8d6768 Mon Sep 17 00:00:00 2001 From: DelayedGaming <simmz0403@gmail.com> Date: Sat, 2 Sep 2023 01:14:07 +0800 Subject: [PATCH 415/619] Clean up --- .../java/ch/njol/skript/events/EvtMove.java | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/main/java/ch/njol/skript/events/EvtMove.java b/src/main/java/ch/njol/skript/events/EvtMove.java index d20d2450008..9b70d02fae6 100644 --- a/src/main/java/ch/njol/skript/events/EvtMove.java +++ b/src/main/java/ch/njol/skript/events/EvtMove.java @@ -23,14 +23,13 @@ import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptEvent; import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.log.ErrorQuality; import ch.njol.util.coll.CollectionUtils; import io.papermc.paper.event.entity.EntityMoveEvent; import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.event.Event; import org.bukkit.event.player.PlayerMoveEvent; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; public class EvtMove extends SkriptEvent { @@ -43,19 +42,23 @@ public class EvtMove extends SkriptEvent { else events = CollectionUtils.array(PlayerMoveEvent.class); - Skript.registerEvent("Move", EvtMove.class, events, "%entitydata% (move|walk|step|:rotate)") - .description("Called when a player or entity moves.", - "NOTE: Move event will only be called when the entity/player moves position, not orientation (ie: looking around).", - "NOTE: These events can be performance heavy as they are called quite often.", - "If you use these events, and later remove them, a server restart is recommended to clear registered events from Skript.") - .examples("on player move:", - "\tif player does not have permission \"player.can.move\":", - "\t\tcancel event", - "on skeleton move:", - "\tif event-entity is not in world \"world\":", - "\t\tkill event-entity") - .requiredPlugins("Paper 1.16.5+ (entity move)") - .since("2.6"); + Skript.registerEvent("Move / Rotate", EvtMove.class, events, "%entitydata% (move|walk|step|rotate:(look around|rotate))") + .description( + "Called when a player or entity moves or rotates their head.", + "NOTE: Move event will only be called when the entity/player moves position, not orientation (ie: looking around). Use the keyword 'rotate' instead.", + "NOTE: These events can be performance heavy as they are called quite often.", + "If you use these events, and later remove them, a server restart is recommended to clear registered events from Skript.") + .examples( + "on player move:", + "\tif player does not have permission \"player.can.move\":", + "\t\tcancel event", + "on skeleton move:", + "\tif event-entity is not in world \"world\":", + "\t\tkill event-entity", + "on player rotate:", + "send action bar \"You are currently looking around!\" to player") + .requiredPlugins("Paper 1.16.5+ (entity move)") + .since("2.6, INSERT VERSION (rotate)"); } private EntityData<?> type; @@ -63,13 +66,13 @@ public class EvtMove extends SkriptEvent { private boolean isRotate; @Override + @SuppressWarnings("unchecked") public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult) { type = ((Literal<EntityData<?>>) args[0]).getSingle(); isPlayer = Player.class.isAssignableFrom(type.getType()); isRotate = parseResult.hasTag("rotate"); - if (!HAS_ENTITY_MOVE && !isPlayer) { - Skript.error("Entity move event requires Paper 1.16.5+", ErrorQuality.SEMANTIC_ERROR); + Skript.error("Entity move event requires Paper 1.16.5+"); return false; } return true; @@ -103,7 +106,7 @@ public boolean check(Event event) { } @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { return type + " move"; } From 6ed6889dfbcefe0a966a410ba0de0413d6aa08de Mon Sep 17 00:00:00 2001 From: DelayedGaming <simmz0403@gmail.com> Date: Sat, 2 Sep 2023 01:19:53 +0800 Subject: [PATCH 416/619] Clean up --- src/main/java/ch/njol/skript/events/EvtMove.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/ch/njol/skript/events/EvtMove.java b/src/main/java/ch/njol/skript/events/EvtMove.java index 9b70d02fae6..2a96778fb99 100644 --- a/src/main/java/ch/njol/skript/events/EvtMove.java +++ b/src/main/java/ch/njol/skript/events/EvtMove.java @@ -41,7 +41,6 @@ public class EvtMove extends SkriptEvent { events = CollectionUtils.array(PlayerMoveEvent.class, EntityMoveEvent.class); else events = CollectionUtils.array(PlayerMoveEvent.class); - Skript.registerEvent("Move / Rotate", EvtMove.class, events, "%entitydata% (move|walk|step|rotate:(look around|rotate))") .description( "Called when a player or entity moves or rotates their head.", @@ -100,9 +99,8 @@ public boolean check(Event event) { return new Class[] {PlayerMoveEvent.class}; } else if (HAS_ENTITY_MOVE) { return new Class[] {EntityMoveEvent.class}; - } else { - return null; } + return null; } @Override From 7d2b6f02dcf22f47cc9b478eca3787de06e23d52 Mon Sep 17 00:00:00 2001 From: DelayedGaming <simmz0403@gmail.com> Date: Sat, 2 Sep 2023 01:25:14 +0800 Subject: [PATCH 417/619] Improve pattern --- src/main/java/ch/njol/skript/events/EvtMove.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/events/EvtMove.java b/src/main/java/ch/njol/skript/events/EvtMove.java index 2a96778fb99..b3b9dc9bf41 100644 --- a/src/main/java/ch/njol/skript/events/EvtMove.java +++ b/src/main/java/ch/njol/skript/events/EvtMove.java @@ -41,7 +41,7 @@ public class EvtMove extends SkriptEvent { events = CollectionUtils.array(PlayerMoveEvent.class, EntityMoveEvent.class); else events = CollectionUtils.array(PlayerMoveEvent.class); - Skript.registerEvent("Move / Rotate", EvtMove.class, events, "%entitydata% (move|walk|step|rotate:(look around|rotate))") + Skript.registerEvent("Move / Rotate", EvtMove.class, events, "%entitydata% (move|walk|step|rotate:(look[ing] around|rotate))") .description( "Called when a player or entity moves or rotates their head.", "NOTE: Move event will only be called when the entity/player moves position, not orientation (ie: looking around). Use the keyword 'rotate' instead.", From c3da232dc51df40f16a03d94cd74d22b7f0b6d13 Mon Sep 17 00:00:00 2001 From: DelayedGaming <simmz0403@gmail.com> Date: Sun, 3 Sep 2023 00:35:24 +0800 Subject: [PATCH 418/619] Revamped this class for the new features --- .../java/ch/njol/skript/events/EvtMove.java | 59 ++++++++++++++++--- 1 file changed, 50 insertions(+), 9 deletions(-) diff --git a/src/main/java/ch/njol/skript/events/EvtMove.java b/src/main/java/ch/njol/skript/events/EvtMove.java index b3b9dc9bf41..a9ef10eb39b 100644 --- a/src/main/java/ch/njol/skript/events/EvtMove.java +++ b/src/main/java/ch/njol/skript/events/EvtMove.java @@ -41,7 +41,9 @@ public class EvtMove extends SkriptEvent { events = CollectionUtils.array(PlayerMoveEvent.class, EntityMoveEvent.class); else events = CollectionUtils.array(PlayerMoveEvent.class); - Skript.registerEvent("Move / Rotate", EvtMove.class, events, "%entitydata% (move|walk|step|rotate:(look[ing] around|rotate))") + Skript.registerEvent("Move / Rotate", EvtMove.class, events, + "%entitydata% (move|walk|step|rotate:(look[ing] around|rotate))", + "%entitydata% (move|walk|step) or (look[ing] around|rotate)") .description( "Called when a player or entity moves or rotates their head.", "NOTE: Move event will only be called when the entity/player moves position, not orientation (ie: looking around). Use the keyword 'rotate' instead.", @@ -63,30 +65,65 @@ public class EvtMove extends SkriptEvent { private EntityData<?> type; private boolean isPlayer; private boolean isRotate; + private Move moveType; + + private enum Move { + + POSITION("move"), + ORIENTATION("rotate"), + POSITION_OR_ORIENTATION("move or rotate"); + + private final String toString; + Move(String toString) { + this.toString = toString; + } + + public String getString() { + return toString; + } + + } @Override @SuppressWarnings("unchecked") public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult) { type = ((Literal<EntityData<?>>) args[0]).getSingle(); - isPlayer = Player.class.isAssignableFrom(type.getType()); - isRotate = parseResult.hasTag("rotate"); if (!HAS_ENTITY_MOVE && !isPlayer) { Skript.error("Entity move event requires Paper 1.16.5+"); return false; } + isPlayer = Player.class.isAssignableFrom(type.getType()); + if (matchedPattern == 1) { + moveType = Move.POSITION_OR_ORIENTATION; + } else if (parseResult.hasTag("rotate")) { + moveType = Move.ORIENTATION; + } else { + moveType = Move.POSITION; + } return true; } @Override public boolean check(Event event) { + Location from, to; if (isPlayer && event instanceof PlayerMoveEvent) { PlayerMoveEvent playerEvent = (PlayerMoveEvent) event; - return moveCheck(playerEvent.getFrom(), playerEvent.getTo()) ^ isRotate; + from = playerEvent.getFrom(); + to = playerEvent.getTo(); } else if (HAS_ENTITY_MOVE && event instanceof EntityMoveEvent) { EntityMoveEvent entityEvent = (EntityMoveEvent) event; - if (type.isInstance(entityEvent.getEntity())) { - return moveCheck(entityEvent.getFrom(), entityEvent.getTo()) ^ isRotate; - } + from = entityEvent.getFrom(); + to = entityEvent.getTo(); + } else { + return false; + } + switch (moveType) { + case POSITION: + return hasChangedPosition(from, to); + case ORIENTATION: + return hasChangedOrientation(from, to); + case POSITION_OR_ORIENTATION: + return true; } return false; } @@ -105,11 +142,15 @@ public boolean check(Event event) { @Override public String toString(@Nullable Event event, boolean debug) { - return type + " move"; + return type + " " + moveType.getString(); } - private static boolean moveCheck(Location from, Location to) { + private static boolean hasChangedPosition(Location from, Location to) { return from.getX() != to.getX() || from.getY() != to.getY() || from.getZ() != to.getZ() || from.getWorld() != to.getWorld(); } + private static boolean hasChangedOrientation(Location from, Location to) { + return from.getYaw() != to.getYaw() || from.getPitch() != to.getPitch(); + } + } From 6e830c61aa702144a2929bb633862368577ac59a Mon Sep 17 00:00:00 2001 From: DelayedGaming <simmz0403@gmail.com> Date: Mon, 4 Sep 2023 02:24:31 +0800 Subject: [PATCH 419/619] Requested changes + minor cleanup --- .../java/ch/njol/skript/events/EvtMove.java | 51 ++++++++++--------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/src/main/java/ch/njol/skript/events/EvtMove.java b/src/main/java/ch/njol/skript/events/EvtMove.java index a9ef10eb39b..6c76b6eaedb 100644 --- a/src/main/java/ch/njol/skript/events/EvtMove.java +++ b/src/main/java/ch/njol/skript/events/EvtMove.java @@ -43,12 +43,12 @@ public class EvtMove extends SkriptEvent { events = CollectionUtils.array(PlayerMoveEvent.class); Skript.registerEvent("Move / Rotate", EvtMove.class, events, "%entitydata% (move|walk|step|rotate:(look[ing] around|rotate))", - "%entitydata% (move|walk|step) or (look[ing] around|rotate)") + "%entitydata% (move|walk|step) or (look[ing] around|rotate)", + "%entitydata% (look[ing] around|rotate) or (move|walk|step)") .description( "Called when a player or entity moves or rotates their head.", - "NOTE: Move event will only be called when the entity/player moves position, not orientation (ie: looking around). Use the keyword 'rotate' instead.", - "NOTE: These events can be performance heavy as they are called quite often.", - "If you use these events, and later remove them, a server restart is recommended to clear registered events from Skript.") + "NOTE: Move event will only be called when the entity/player moves position, keyword 'rotate' is for orientation (ie: looking around), and the combined syntax listens for both.", + "NOTE: These events can be performance heavy as they are called quite often.") .examples( "on player move:", "\tif player does not have permission \"player.can.move\":", @@ -62,24 +62,25 @@ public class EvtMove extends SkriptEvent { .since("2.6, INSERT VERSION (rotate)"); } - private EntityData<?> type; + private EntityData<?> entityData; private boolean isPlayer; - private boolean isRotate; private Move moveType; private enum Move { - POSITION("move"), - ORIENTATION("rotate"), - POSITION_OR_ORIENTATION("move or rotate"); + MOVE("move"), + MOVE_OR_ROTATE("move or rotate"), + ROTATE("rotate"); - private final String toString; - Move(String toString) { - this.toString = toString; + private final String name; + + Move(String name) { + this.name = name; } - public String getString() { - return toString; + @Override + public String toString() { + return name; } } @@ -87,18 +88,18 @@ public String getString() { @Override @SuppressWarnings("unchecked") public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult) { - type = ((Literal<EntityData<?>>) args[0]).getSingle(); + entityData = ((Literal<EntityData<?>>) args[0]).getSingle(); + isPlayer = Player.class.isAssignableFrom(entityData.getType()); if (!HAS_ENTITY_MOVE && !isPlayer) { Skript.error("Entity move event requires Paper 1.16.5+"); return false; } - isPlayer = Player.class.isAssignableFrom(type.getType()); - if (matchedPattern == 1) { - moveType = Move.POSITION_OR_ORIENTATION; + if (matchedPattern > 0) { + moveType = Move.MOVE_OR_ROTATE; } else if (parseResult.hasTag("rotate")) { - moveType = Move.ORIENTATION; + moveType = Move.ROTATE; } else { - moveType = Move.POSITION; + moveType = Move.MOVE; } return true; } @@ -112,17 +113,19 @@ public boolean check(Event event) { to = playerEvent.getTo(); } else if (HAS_ENTITY_MOVE && event instanceof EntityMoveEvent) { EntityMoveEvent entityEvent = (EntityMoveEvent) event; + if (!(entityData.isInstance(entityEvent.getEntity()))) + return false; from = entityEvent.getFrom(); to = entityEvent.getTo(); } else { return false; } switch (moveType) { - case POSITION: + case MOVE: return hasChangedPosition(from, to); - case ORIENTATION: + case ROTATE: return hasChangedOrientation(from, to); - case POSITION_OR_ORIENTATION: + case MOVE_OR_ROTATE: return true; } return false; @@ -142,7 +145,7 @@ public boolean check(Event event) { @Override public String toString(@Nullable Event event, boolean debug) { - return type + " " + moveType.getString(); + return entityData + " " + moveType; } private static boolean hasChangedPosition(Location from, Location to) { From 72fdcda7b97231c1626125e9c82fafe2df3208c9 Mon Sep 17 00:00:00 2001 From: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> Date: Mon, 4 Sep 2023 12:57:01 -0500 Subject: [PATCH 420/619] Documentation actions improvements (#5735) --------- Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> --- .github/workflows/cleanup-docs.yml | 39 +++++++++++ .../workflows/docs/generate-docs/action.yml | 64 ++++++++++++++++++- .github/workflows/docs/push-docs/action.yml | 14 ++-- .github/workflows/docs/setup-docs/action.yml | 1 - .github/workflows/nightly-docs.yml | 15 ++++- .github/workflows/release-docs.yml | 4 +- build.gradle | 31 ++++++--- 7 files changed, 145 insertions(+), 23 deletions(-) create mode 100644 .github/workflows/cleanup-docs.yml diff --git a/.github/workflows/cleanup-docs.yml b/.github/workflows/cleanup-docs.yml new file mode 100644 index 00000000000..750bab83213 --- /dev/null +++ b/.github/workflows/cleanup-docs.yml @@ -0,0 +1,39 @@ +name: Cleanup nightly documentation +on: delete +jobs: + cleanup-nightly-docs: + if: github.event.ref_type == 'branch' + runs-on: ubuntu-latest + steps: + - name: Configure workflow + id: configuration + env: + DELETED_BRANCH: ${{ github.event.ref }} + run: | + BRANCH_NAME="${DELETED_BRANCH#refs/*/}" + echo "BRANCH_NAME=${BRANCH_NAME}" >> $GITHUB_OUTPUT + echo "DOCS_OUTPUT_DIR=${GITHUB_WORKSPACE}/skript-docs/docs/nightly/${BRANCH_NAME}" >> $GITHUB_OUTPUT + echo "DOCS_REPO_DIR=${GITHUB_WORKSPACE}/skript-docs" >> $GITHUB_OUTPUT + - name: Checkout Skript + uses: actions/checkout@v3 + with: + ref: ${{ github.event.repository.default_branch }} + 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: Cleanup nightly documentation + env: + DOCS_OUTPUT_DIR: ${{ steps.configuration.outputs.DOCS_OUTPUT_DIR }} + run: | + rm -rf ${DOCS_OUTPUT_DIR} || true + - name: Push nightly documentation cleanup + uses: ./skript/.github/workflows/docs/push-docs + with: + docs_repo_dir: ${{ steps.configuration.outputs.DOCS_REPO_DIR }} + git_name: Nightly Docs Bot + git_email: nightlydocs@skriptlang.org + git_commit_message: "Delete ${{ steps.configuration.outputs.BRANCH_NAME }} branch nightly docs" diff --git a/.github/workflows/docs/generate-docs/action.yml b/.github/workflows/docs/generate-docs/action.yml index f84023a60df..84cc2153f86 100644 --- a/.github/workflows/docs/generate-docs/action.yml +++ b/.github/workflows/docs/generate-docs/action.yml @@ -18,24 +18,82 @@ inputs: required: false default: false type: boolean + cleanup_pattern: + description: "A pattern designating which files to delete when cleaning the documentation output directory" + required: false + default: "*" + type: string + +outputs: + DOCS_CHANGED: + description: "Whether or not the documentation has changed since the last push" + value: ${{ steps.generate.outputs.DOCS_CHANGED }} runs: using: 'composite' steps: - name: generate-docs + id: generate shell: bash env: DOCS_OUTPUT_DIR: ${{ inputs.docs_output_dir }} DOCS_REPO_DIR: ${{ inputs.docs_repo_dir }} SKRIPT_REPO_DIR: ${{ inputs.skript_repo_dir }} IS_RELEASE: ${{ inputs.is_release }} + CLEANUP_PATTERN: ${{ inputs.cleanup_pattern }} run: | - export SKRIPT_DOCS_TEMPLATE_DIR=${DOCS_REPO_DIR}/doc-templates - export SKRIPT_DOCS_OUTPUT_DIR=${DOCS_OUTPUT_DIR}/ + replace_in_directory() { + find $1 -type f -exec sed -i -e "s/$2/$3/g" {} \; + } + + # this should be replaced with a more reliable jq command, + # but it can't be right now because docs.json is actually not valid json. + get_skript_version_of_directory() { + grep skriptVersion "$1/docs.json" | cut -d\" -f 4 + } + + if [ -d "${DOCS_REPO_DIR}/docs/templates" ] + then + export SKRIPT_DOCS_TEMPLATE_DIR=${DOCS_REPO_DIR}/docs/templates + else + export SKRIPT_DOCS_TEMPLATE_DIR=${DOCS_REPO_DIR}/doc-templates + fi + + export SKRIPT_DOCS_OUTPUT_DIR=/tmp/generated-docs + cd $SKRIPT_REPO_DIR if [[ "${IS_RELEASE}" == "true" ]]; then ./gradlew genReleaseDocs releaseJavadoc else ./gradlew genNightlyDocs javadoc fi - cp -a "./build/docs/javadoc/." "${DOCS_OUTPUT_DIR}/javadocs" + + if [ -d "${DOCS_OUTPUT_DIR}" ]; then + mkdir -p "${SKRIPT_DOCS_OUTPUT_DIR}/javadocs" && cp -a "./build/docs/javadoc/." "$_" + + mkdir -p "/tmp/normalized-output-docs" && cp -a "${DOCS_OUTPUT_DIR}/." "$_" + mkdir -p "/tmp/normalized-generated-docs" && cp -a "${SKRIPT_DOCS_OUTPUT_DIR}/." "$_" + + output_skript_version=$(get_skript_version_of_directory "/tmp/normalized-output-docs") + generated_skript_version=$(get_skript_version_of_directory "/tmp/normalized-generated-docs") + + replace_in_directory "/tmp/normalized-output-docs" "${output_skript_version}" "Skript" + replace_in_directory "/tmp/normalized-generated-docs" "${generated_skript_version}" "Skript" + + diff -qbr /tmp/normalized-output-docs /tmp/normalized-generated-docs || diff_exit_code=$? + # If diff exits with exit code 1, that means there were some differences + if [[ ${diff_exit_code} -eq 1 ]]; then + echo "DOCS_CHANGED=true" >> $GITHUB_OUTPUT + echo "Documentation has changed since last push" + else + echo "Documentation hasn't changed since last push" + fi + else + echo "DOCS_CHANGED=true" >> $GITHUB_OUTPUT + echo "No existing documentation found" + fi + + rm -rf ${DOCS_OUTPUT_DIR}/${CLEANUP_PATTERN} || true + mkdir -p "${DOCS_OUTPUT_DIR}/" && cp -a "${SKRIPT_DOCS_OUTPUT_DIR}/." "$_" + + diff --git a/.github/workflows/docs/push-docs/action.yml b/.github/workflows/docs/push-docs/action.yml index bcc296a85c3..477a4c46aa4 100644 --- a/.github/workflows/docs/push-docs/action.yml +++ b/.github/workflows/docs/push-docs/action.yml @@ -1,10 +1,6 @@ -name: Generate documentation +name: Push documentation inputs: - docs_output_dir: - description: "The directory to generate the documentation into" - required: true - type: string docs_repo_dir: description: "The skript-docs repository directory" required: true @@ -38,4 +34,10 @@ runs: git config user.email "${GIT_EMAIL}" git add -A git commit -m "${GIT_COMMIT_MESSAGE}" || (echo "Nothing to push!" && exit 0) - git push origin main + # Attempt rebasing and pushing 5 times in case another job pushes before us + for i in 1 2 3 4 5 + do + git pull --rebase -X theirs origin main + git push origin main && break + sleep 5 + done diff --git a/.github/workflows/docs/setup-docs/action.yml b/.github/workflows/docs/setup-docs/action.yml index 1feedcd8f10..cd6c6a05216 100644 --- a/.github/workflows/docs/setup-docs/action.yml +++ b/.github/workflows/docs/setup-docs/action.yml @@ -38,7 +38,6 @@ runs: CLEANUP_PATTERN: ${{ inputs.cleanup_pattern }} run: | eval `ssh-agent` - rm -rf ${DOCS_OUTPUT_DIR}/${CLEANUP_PATTERN} || true echo "$DOCS_DEPLOY_KEY" | tr -d '\r' | ssh-add - > /dev/null mkdir ~/.ssh ssh-keyscan www.github.com >> ~/.ssh/known_hosts diff --git a/.github/workflows/nightly-docs.yml b/.github/workflows/nightly-docs.yml index 2a6841c3771..782b76b9ed5 100644 --- a/.github/workflows/nightly-docs.yml +++ b/.github/workflows/nightly-docs.yml @@ -9,12 +9,20 @@ on: jobs: nightly-docs: - if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" + if: "!contains(toJSON(github.event.commits.*.message), '[ci skip]')" runs-on: ubuntu-latest steps: - name: Configure workflow id: configuration + env: + DOCS_DEPLOY_KEY: ${{ secrets.DOCS_DEPLOY_KEY }} run: | + if [ -n "$DOCS_DEPLOY_KEY" ] + then + echo "DOCS_DEPLOY_KEY_PRESENT=true" >> $GITHUB_OUTPUT + else + echo "Secret 'DOCS_DEPLOY_KEY' not present. Exiting job." + fi BRANCH_NAME="${GITHUB_REF#refs/*/}" echo "BRANCH_NAME=${BRANCH_NAME}" >> $GITHUB_OUTPUT echo "DOCS_OUTPUT_DIR=${GITHUB_WORKSPACE}/skript-docs/docs/nightly/${BRANCH_NAME}" >> $GITHUB_OUTPUT @@ -26,20 +34,23 @@ jobs: submodules: recursive path: skript - name: Setup documentation environment + if: steps.configuration.outputs.DOCS_DEPLOY_KEY_PRESENT == 'true' 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 + id: generate + if: steps.configuration.outputs.DOCS_DEPLOY_KEY_PRESENT == 'true' 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 }} - name: Push nightly documentation + if: steps.generate.outputs.DOCS_CHANGED == 'true' uses: ./skript/.github/workflows/docs/push-docs with: - docs_output_dir: ${{ steps.configuration.outputs.DOCS_OUTPUT_DIR }} docs_repo_dir: ${{ steps.configuration.outputs.DOCS_REPO_DIR }} git_name: Nightly Docs Bot git_email: nightlydocs@skriptlang.org diff --git a/.github/workflows/release-docs.yml b/.github/workflows/release-docs.yml index 9ec5a5ad257..a8ccb4000bb 100644 --- a/.github/workflows/release-docs.yml +++ b/.github/workflows/release-docs.yml @@ -26,7 +26,6 @@ jobs: with: docs_deploy_key: ${{ secrets.DOCS_DEPLOY_KEY }} docs_output_dir: ${{ steps.configuration.outputs.DOCS_OUTPUT_DIR }} - cleanup_pattern: "!(nightly|archives)" - name: Generate documentation uses: ./skript/.github/workflows/docs/generate-docs with: @@ -34,6 +33,7 @@ jobs: docs_repo_dir: ${{ steps.configuration.outputs.DOCS_REPO_DIR }} skript_repo_dir: ${{ steps.configuration.outputs.SKRIPT_REPO_DIR }} is_release: true + cleanup_pattern: "!(nightly|archives|templates)" - name: Push release documentation uses: ./skript/.github/workflows/docs/push-docs with: @@ -68,14 +68,12 @@ jobs: - 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 - name: Push archive documentation uses: ./skript/.github/workflows/docs/push-docs with: - docs_output_dir: ${{ steps.configuration.outputs.DOCS_OUTPUT_DIR }} docs_repo_dir: ${{ steps.configuration.outputs.DOCS_REPO_DIR }} git_name: Archive Docs Bot git_email: archivedocs@skriptlang.org diff --git a/build.gradle b/build.gradle index 5920cb95ac8..9a525ff1e1d 100644 --- a/build.gradle +++ b/build.gradle @@ -149,14 +149,6 @@ license { exclude('**/*.json') // JSON files do not have headers } -javadoc { - 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 releaseJavadoc(type: Javadoc) { title = project.property('version') source = sourceSets.main.allJava @@ -394,3 +386,26 @@ task nightlyRelease(type: ShadowJar) { ) } } + +javadoc { + dependsOn nightlyResources + + source = sourceSets.main.allJava + + exclude("ch/njol/skript/conditions/**") + exclude("ch/njol/skript/expressions/**") + exclude("ch/njol/skript/effects/**") + exclude("ch/njol/skript/events/**") + exclude("ch/njol/skript/sections/**") + exclude("ch/njol/skript/structures/**") + exclude("ch/njol/skript/lang/function/EffFunctionCall.java") + exclude("ch/njol/skript/lang/function/ExprFunctionCall.java") + exclude("ch/njol/skript/hooks/**") + exclude("ch/njol/skript/test/**") + + classpath = configurations.compileClasspath + sourceSets.main.output + options.encoding = 'UTF-8' + // currently our javadoc has a lot of errors, so we need to suppress the linter + options.addStringOption('Xdoclint:none', '-quiet') +} + From 3a1a59c66c3f719f4a5700990f20c20fb348d6cb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Sep 2023 15:21:39 -0600 Subject: [PATCH 421/619] Bump actions/checkout from 3 to 4 (#5969) Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cleanup-docs.yml | 2 +- .github/workflows/java-17-builds.yml | 2 +- .github/workflows/java-8-builds.yml | 2 +- .github/workflows/junit-17-builds.yml | 2 +- .github/workflows/junit-8-builds.yml | 2 +- .github/workflows/nightly-docs.yml | 2 +- .github/workflows/release-docs.yml | 4 ++-- .github/workflows/repo.yml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/cleanup-docs.yml b/.github/workflows/cleanup-docs.yml index 750bab83213..68d12e84dc1 100644 --- a/.github/workflows/cleanup-docs.yml +++ b/.github/workflows/cleanup-docs.yml @@ -15,7 +15,7 @@ jobs: echo "DOCS_OUTPUT_DIR=${GITHUB_WORKSPACE}/skript-docs/docs/nightly/${BRANCH_NAME}" >> $GITHUB_OUTPUT echo "DOCS_REPO_DIR=${GITHUB_WORKSPACE}/skript-docs" >> $GITHUB_OUTPUT - name: Checkout Skript - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ github.event.repository.default_branch }} submodules: recursive diff --git a/.github/workflows/java-17-builds.yml b/.github/workflows/java-17-builds.yml index 67007d76d90..fa432f7f17d 100644 --- a/.github/workflows/java-17-builds.yml +++ b/.github/workflows/java-17-builds.yml @@ -12,7 +12,7 @@ jobs: if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive - name: Set up JDK 17 diff --git a/.github/workflows/java-8-builds.yml b/.github/workflows/java-8-builds.yml index adcd71fb09b..4a36e74a44b 100644 --- a/.github/workflows/java-8-builds.yml +++ b/.github/workflows/java-8-builds.yml @@ -12,7 +12,7 @@ jobs: if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive - name: Set up JDK 17 diff --git a/.github/workflows/junit-17-builds.yml b/.github/workflows/junit-17-builds.yml index eca1ebb9706..69c5b73f27a 100644 --- a/.github/workflows/junit-17-builds.yml +++ b/.github/workflows/junit-17-builds.yml @@ -12,7 +12,7 @@ jobs: if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive - name: Set up JDK 17 diff --git a/.github/workflows/junit-8-builds.yml b/.github/workflows/junit-8-builds.yml index 93971c79624..3aa98f554a7 100644 --- a/.github/workflows/junit-8-builds.yml +++ b/.github/workflows/junit-8-builds.yml @@ -12,7 +12,7 @@ jobs: if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive - name: Set up JDK 17 diff --git a/.github/workflows/nightly-docs.yml b/.github/workflows/nightly-docs.yml index 782b76b9ed5..0cddeb807e5 100644 --- a/.github/workflows/nightly-docs.yml +++ b/.github/workflows/nightly-docs.yml @@ -29,7 +29,7 @@ jobs: 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@v3 + uses: actions/checkout@v4 with: submodules: recursive path: skript diff --git a/.github/workflows/release-docs.yml b/.github/workflows/release-docs.yml index a8ccb4000bb..06cb5569076 100644 --- a/.github/workflows/release-docs.yml +++ b/.github/workflows/release-docs.yml @@ -17,7 +17,7 @@ jobs: 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@v3 + uses: actions/checkout@v4 with: submodules: recursive path: skript @@ -56,7 +56,7 @@ jobs: 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@v3 + uses: actions/checkout@v4 with: submodules: recursive path: skript diff --git a/.github/workflows/repo.yml b/.github/workflows/repo.yml index d7639b98a94..3b6640cc765 100644 --- a/.github/workflows/repo.yml +++ b/.github/workflows/repo.yml @@ -8,7 +8,7 @@ jobs: publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive - name: Set up JDK 17 From 5ae1ba12e57b2a2b2c54cce7909c066ac564504b Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Thu, 7 Sep 2023 11:46:00 -0600 Subject: [PATCH 422/619] Add infinite and icon to potion effects (#5777) --- .../skript/conditions/CondIsInfinite.java | 52 ++++++ .../ch/njol/skript/effects/EffPotion.java | 169 ++++++++---------- .../njol/skript/util/PotionEffectUtils.java | 30 ++-- 3 files changed, 141 insertions(+), 110 deletions(-) create mode 100644 src/main/java/ch/njol/skript/conditions/CondIsInfinite.java diff --git a/src/main/java/ch/njol/skript/conditions/CondIsInfinite.java b/src/main/java/ch/njol/skript/conditions/CondIsInfinite.java new file mode 100644 index 00000000000..98ed56c0570 --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondIsInfinite.java @@ -0,0 +1,52 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.conditions; + +import org.bukkit.potion.PotionEffect; + +import ch.njol.skript.Skript; +import ch.njol.skript.conditions.base.PropertyCondition; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; + +// This class can be expanded apon for other types if needed. +@Name("Is Infinite") +@Description("Checks whether potion effects are infinite.") +@Examples("all of the active potion effects of the player are infinite") +@Since("INSERT VERSION") +public class CondIsInfinite extends PropertyCondition<PotionEffect> { + + static { + if (Skript.methodExists(PotionEffect.class, "isInfinite")) + register(CondIsInfinite.class, "infinite", "potioneffects"); + } + + @Override + public boolean check(PotionEffect potion) { + return potion.isInfinite(); + } + + @Override + protected String getPropertyName() { + return "infinite"; + } + +} diff --git a/src/main/java/ch/njol/skript/effects/EffPotion.java b/src/main/java/ch/njol/skript/effects/EffPotion.java index 09c7af5912a..64b07bb0be4 100644 --- a/src/main/java/ch/njol/skript/effects/EffPotion.java +++ b/src/main/java/ch/njol/skript/effects/EffPotion.java @@ -36,143 +36,128 @@ import ch.njol.skript.util.Timespan; import ch.njol.util.Kleenean; -/** - * @author Peter Güttinger - */ @Name("Potion Effects") @Description("Apply or remove potion effects to/from entities.") -@Examples({"apply swiftness 2 to the player", +@Examples({ + "apply ambient swiftness 2 to the player", "remove haste from the victim", + "", "on join:", - "\tapply potion of strength of tier {strength.%player%} to the player for 999 days", - "apply potion effects of player's tool to player"}) -@Since("2.0, 2.2-dev27 (ambient and particle-less potion effects), 2.5 (replacing existing effect), 2.5.2 (potion effects)") + "\tapply infinite potion of strength of tier {strength::%uuid of player%} to the player", + "\tapply potion of strength of tier {strength::%uuid of player%} to the player for 999 days # Before 1.19.4", + "", + "apply potion effects of player's tool to player", + "apply haste potion of tier 3 without any particles to the player whilst hiding the potion icon # Hide potions" +}) +@Since( + "2.0, 2.2-dev27 (ambient and particle-less potion effects), " + + "2.5 (replacing existing effect), 2.5.2 (potion effects), " + + "INSERT VERSION (icon and infinite)" +) public class EffPotion extends Effect { + static { Skript.registerEffect(EffPotion.class, - "apply %potioneffects% to %livingentities%", - "apply [potion of] %potioneffecttypes% [potion] [[[of] tier] %-number%] to %livingentities% [for %-timespan%] [(1¦replacing [the] existing effect)]", - "apply ambient [potion of] %potioneffecttypes% [potion] [[[of] tier] %-number%] to %livingentities% [for %-timespan%] [(1¦replacing [the] existing effect)]", - "apply [potion of] %potioneffecttypes% [potion] [[[of] tier] %-number%] without [any] particles to %livingentities% [for %-timespan%] [(1¦replacing [the] existing effect)]" - //, "apply %itemtypes% to %livingentities%" - /*,"remove %potioneffecttypes% from %livingentities%"*/); + "apply %potioneffects% to %livingentities%", + "apply infinite [:ambient] [potion of] %potioneffecttypes% [potion] [[[of] tier] %-number%] [noparticles:without [any] particles] [icon:(whilst hiding [the]|without (the|a)) [potion] icon] to %livingentities% [replacing:replacing [the] existing effect]", + "apply [:ambient] [potion of] %potioneffecttypes% [potion] [[[of] tier] %-number%] [noparticles:without [any] particles] [icon:(whilst hiding [the]|without (the|a)) [potion] icon] to %livingentities% [for %-timespan%] [replacing:replacing [the] existing effect]" + ); } + private final static boolean COMPATIBLE = Skript.isRunningMinecraft(1, 19, 4); + private final static int DEFAULT_DURATION = 15 * 20; // 15 seconds, same as EffPoison - private boolean replaceExisting; - @SuppressWarnings("null") private Expression<PotionEffectType> potions; - @Nullable - private Expression<Number> tier; - @SuppressWarnings("null") private Expression<LivingEntity> entities; + private Expression<PotionEffect> effects; + @Nullable private Expression<Timespan> duration; - @SuppressWarnings("null") - private Expression<PotionEffect> potionEffects; - private boolean apply; + + @Nullable + private Expression<Number> tier; + + private boolean replaceExisting; // Replace the existing potion if present. + private boolean potionEffect; // PotionEffects rather than PotionEffectTypes. + private boolean noParticles; + private boolean infinite; // 1.19.4+ has an infinite option. private boolean ambient; // Ambient means less particles - private boolean particles; // Particles or no particles? - private boolean potionEffect; // PotionEffects rather than PotionEffectTypes + private boolean icon; - @SuppressWarnings({"unchecked", "null"}) @Override - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { - apply = matchedPattern > 0; + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { potionEffect = matchedPattern == 0; - replaceExisting = parseResult.mark == 1; + replaceExisting = parseResult.hasTag("replacing"); + noParticles = parseResult.hasTag("noparticles"); + ambient = parseResult.hasTag("ambient"); + icon = !parseResult.hasTag("icon"); + infinite = matchedPattern == 1; if (potionEffect) { - potionEffects = (Expression<PotionEffect>) exprs[0]; + effects = (Expression<PotionEffect>) exprs[0]; entities = (Expression<LivingEntity>) exprs[1]; - } else if (apply) { + } else { potions = (Expression<PotionEffectType>) exprs[0]; tier = (Expression<Number>) exprs[1]; entities = (Expression<LivingEntity>) exprs[2]; - duration = (Expression<Timespan>) exprs[3]; - } else { - potions = (Expression<PotionEffectType>) exprs[0]; - entities = (Expression<LivingEntity>) exprs[1]; - } - - // Ambience and particles - switch (matchedPattern) { - case 1: - ambient = false; - particles = true; - break; - case 2: - ambient = true; - particles = true; - break; - case 3: - ambient = false; - particles = false; - break; + if (infinite) + duration = (Expression<Timespan>) exprs[3]; } - return true; } @Override - protected void execute(final Event e) { + protected void execute(Event event) { if (potionEffect) { - for (LivingEntity livingEntity : entities.getArray(e)) { - PotionEffect[] potionEffects = this.potionEffects.getArray(e); - PotionEffectUtils.addEffects(livingEntity, potionEffects); - } + for (LivingEntity livingEntity : entities.getArray(event)) + PotionEffectUtils.addEffects(livingEntity, effects.getArray(event)); } else { - final PotionEffectType[] ts = potions.getArray(e); - if (ts.length == 0) - return; - if (!apply) { - for (LivingEntity en : entities.getArray(e)) { - for (final PotionEffectType t : ts) - en.removePotionEffect(t); - } + PotionEffectType[] potionEffectTypes = potions.getArray(event); + if (potionEffectTypes.length == 0) return; - } - int a = 0; - if (tier != null) { - final Number amp = tier.getSingle(e); - if (amp == null) - return; - a = amp.intValue() - 1; - } - int d = DEFAULT_DURATION; - if (duration != null) { - final Timespan dur = duration.getSingle(e); - if (dur == null) + int tier = 0; + if (this.tier != null) + tier = this.tier.getOptionalSingle(event).orElse(1).intValue() - 1; + + int duration = infinite ? (COMPATIBLE ? -1 : Integer.MAX_VALUE) : DEFAULT_DURATION; + if (this.duration != null && !infinite) { + Timespan timespan = this.duration.getSingle(event); + if (timespan == null) return; - d = (int) (dur.getTicks_i() >= Integer.MAX_VALUE ? Integer.MAX_VALUE : dur.getTicks_i()); + duration = (int) Math.max(timespan.getTicks_i(), Integer.MAX_VALUE); } - for (final LivingEntity en : entities.getArray(e)) { - for (final PotionEffectType t : ts) { - int duration = d; - if (!replaceExisting) { - if (en.hasPotionEffect(t)) { - for (final PotionEffect eff : en.getActivePotionEffects()) { - if (eff.getType() == t) { - duration += eff.getDuration(); + for (LivingEntity entity : entities.getArray(event)) { + for (PotionEffectType potionEffectType : potionEffectTypes) { + int finalDuration = duration; + if (!replaceExisting && !infinite) { + if (entity.hasPotionEffect(potionEffectType)) { + for (PotionEffect effect : entity.getActivePotionEffects()) { + if (effect.getType() == potionEffectType) { + finalDuration += effect.getDuration(); break; } } } } - en.addPotionEffect(new PotionEffect(t, duration, a, ambient, particles), true); + entity.addPotionEffect(new PotionEffect(potionEffectType, finalDuration, tier, ambient, !noParticles, icon)); } } } } @Override - public String toString(final @Nullable Event e, final boolean debug) { - if (potionEffect) - return "apply " + potionEffects.toString(e, debug) + " to " + entities.toString(e, debug); - else if (apply) - return "apply " + potions.toString(e, debug) + (tier != null ? " of tier " + tier.toString(e, debug) : "") + " to " + entities.toString(e, debug) + (duration != null ? " for " + duration.toString(e, debug) : ""); - else - return "remove " + potions.toString(e, debug) + " from " + entities.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + if (potionEffect) { + // Uses PotionEffectUtils#toString + return "apply " + effects.toString(event, debug) + " to " + entities.toString(event, debug); + } else { + return "apply " + (infinite ? " infinite " : "") + + potions.toString(event, debug) + + (tier != null ? " of tier " + tier.toString(event, debug) : "") + + " to " + entities.toString(event, debug) + + (duration != null ? " for " + duration.toString(event, debug) : ""); + } } } diff --git a/src/main/java/ch/njol/skript/util/PotionEffectUtils.java b/src/main/java/ch/njol/skript/util/PotionEffectUtils.java index 2e6c9037863..c77185d8f7f 100644 --- a/src/main/java/ch/njol/skript/util/PotionEffectUtils.java +++ b/src/main/java/ch/njol/skript/util/PotionEffectUtils.java @@ -19,7 +19,6 @@ package ch.njol.skript.util; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -41,20 +40,17 @@ import ch.njol.skript.localization.Language; import ch.njol.skript.localization.LanguageChangeListener; -/** - * @author Peter Güttinger - */ @SuppressWarnings("deprecation") public abstract class PotionEffectUtils { - + private static final boolean HAS_SUSPICIOUS_META = Skript.classExists("org.bukkit.inventory.meta.SuspiciousStewMeta"); - + private PotionEffectUtils() {} - + final static Map<String, PotionEffectType> types = new HashMap<>(); - + final static String[] names = new String[getMaxPotionId() + 1]; - + // MCPC+ workaround private static int getMaxPotionId() { int i = 0; @@ -99,32 +95,30 @@ public static PotionEffectType parseByEffectType(PotionEffectType t) { return null; } - @SuppressWarnings("null") - public static String toString(final PotionEffectType t) { + public static String toString(PotionEffectType t) { return names[t.getId()]; } // REMIND flags? - @SuppressWarnings("null") - public static String toString(final PotionEffectType t, final int flags) { + public static String toString(PotionEffectType t, int flags) { return names[t.getId()]; } - + public static String toString(PotionEffect potionEffect) { StringBuilder builder = new StringBuilder(); if (potionEffect.isAmbient()) builder.append("ambient "); builder.append("potion effect of "); builder.append(toString(potionEffect.getType())); - builder.append(" of tier ").append(potionEffect.getAmplifier() + 1); - if (!potionEffect.hasParticles()) builder.append(" without particles"); - builder.append(" for ").append(Timespan.fromTicks_i(potionEffect.getDuration())); + builder.append(" for ").append(potionEffect.getDuration() == -1 ? "infinity" : Timespan.fromTicks_i(Math.abs(potionEffect.getDuration()))); + if (!potionEffect.hasIcon()) + builder.append(" without icon"); return builder.toString(); } - + public static String[] getNames() { return names; } From 76db9440455d370c07edc9b8f85da69e6fe1e51c Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Thu, 7 Sep 2023 13:51:52 -0400 Subject: [PATCH 423/619] Structure API Finalization (#5669) --- .../java/ch/njol/skript/ScriptLoader.java | 148 ++++++++++-------- .../njol/skript/structures/StructCommand.java | 11 +- .../njol/skript/structures/StructOptions.java | 19 +-- .../skript/lang/entry/EntryContainer.java | 12 +- .../lang/entry/util/ExpressionEntryData.java | 51 +++--- .../lang/entry/util/LiteralEntryData.java | 5 +- .../lang/entry/util/TriggerEntryData.java | 36 +---- .../entry/util/VariableStringEntryData.java | 36 +---- .../skriptlang/skript/lang/script/Script.java | 34 +++- .../skript/lang/structure/Structure.java | 5 +- 10 files changed, 168 insertions(+), 189 deletions(-) diff --git a/src/main/java/ch/njol/skript/ScriptLoader.java b/src/main/java/ch/njol/skript/ScriptLoader.java index 14cd0b3e628..3fa8c855c32 100644 --- a/src/main/java/ch/njol/skript/ScriptLoader.java +++ b/src/main/java/ch/njol/skript/ScriptLoader.java @@ -516,67 +516,89 @@ private static CompletableFuture<ScriptInfo> loadScripts(List<Config> configs, O try { openCloseable.open(); - scripts.stream() - .flatMap(pair -> { // Flatten each entry down to a stream of Script-Structure pairs - return pair.getSecond().stream() - .map(structure -> new NonNullPair<>(pair, structure)); - }) - .sorted(Comparator.comparing(pair -> pair.getSecond().getPriority())) - .forEach(pair -> { - Script script = pair.getFirst().getFirst(); - Structure structure = pair.getSecond(); - - parser.setActive(script); - parser.setCurrentStructure(structure); - parser.setNode(structure.getEntryContainer().getSource()); - - try { - if (!structure.preLoad()) - pair.getFirst().getSecond().remove(structure); - } catch (Exception e) { - //noinspection ThrowableNotThrown - Skript.exception(e, "An error occurred while trying to load a Structure."); + // build sorted list + // this nest of pairs is terrible, but we need to keep the reference to the modifiable structures list + List<NonNullPair<NonNullPair<Script, List<Structure>>, Structure>> pairs = scripts.stream() + .flatMap(pair -> { // Flatten each entry down to a stream of Script-Structure pairs + return pair.getSecond().stream() + .map(structure -> new NonNullPair<>(pair, structure)); + }) + .sorted(Comparator.comparing(pair -> pair.getSecond().getPriority())) + .collect(Collectors.toCollection(ArrayList::new)); + + // pre-loading + pairs.removeIf(pair -> { + Structure structure = pair.getSecond(); + + parser.setActive(pair.getFirst().getFirst()); + parser.setCurrentStructure(structure); + parser.setNode(structure.getEntryContainer().getSource()); + + try { + if (!structure.preLoad()) { pair.getFirst().getSecond().remove(structure); + return true; } - }); - + } catch (Exception e) { + //noinspection ThrowableNotThrown + Skript.exception(e, "An error occurred while trying to preLoad a Structure."); + pair.getFirst().getSecond().remove(structure); + return true; + } + return false; + }); parser.setInactive(); - // TODO in the future, Structure#load should be split across multiple threads if parallel loading is enabled. + // TODO in the future, Structure#load/Structure#postLoad should be split across multiple threads if parallel loading is enabled. // However, this is not possible right now as reworks in multiple areas will be needed. // For example, the "Commands" class still uses a static list for currentArguments that is cleared between loads. // Until these reworks happen, limiting main loading to asynchronous (not parallel) is the only choice we have. - for (NonNullPair<Script, List<Structure>> pair : scripts) { - parser.setActive(pair.getFirst()); - pair.getSecond().removeIf(structure -> { - parser.setCurrentStructure(structure); - parser.setNode(structure.getEntryContainer().getSource()); - try { - return !structure.load(); - } catch (Exception e) { - //noinspection ThrowableNotThrown - Skript.exception(e, "An error occurred while trying to load a Structure."); + + // loading + pairs.removeIf(pair -> { + Structure structure = pair.getSecond(); + + parser.setActive(pair.getFirst().getFirst()); + parser.setCurrentStructure(structure); + parser.setNode(structure.getEntryContainer().getSource()); + + try { + if (!structure.load()) { + pair.getFirst().getSecond().remove(structure); return true; } - }); - } - + } catch (Exception e) { + //noinspection ThrowableNotThrown + Skript.exception(e, "An error occurred while trying to load a Structure."); + pair.getFirst().getSecond().remove(structure); + return true; + } + return false; + }); parser.setInactive(); - for (NonNullPair<Script, List<Structure>> pair : scripts) { - parser.setActive(pair.getFirst()); - pair.getSecond().removeIf(structure -> { - parser.setCurrentStructure(structure); - parser.setNode(structure.getEntryContainer().getSource()); - try { - return !structure.postLoad(); - } catch (Exception e) { - //noinspection ThrowableNotThrown - Skript.exception(e, "An error occurred while trying to load a Structure."); + // post-loading + pairs.removeIf(pair -> { + Structure structure = pair.getSecond(); + + parser.setActive(pair.getFirst().getFirst()); + parser.setCurrentStructure(structure); + parser.setNode(structure.getEntryContainer().getSource()); + + try { + if (!structure.postLoad()) { + pair.getFirst().getSecond().remove(structure); return true; } - }); - } + } catch (Exception e) { + //noinspection ThrowableNotThrown + Skript.exception(e, "An error occurred while trying to postLoad a Structure."); + pair.getFirst().getSecond().remove(structure); + return true; + } + return false; + }); + parser.setInactive(); return scriptInfo; } catch (Exception e) { @@ -593,7 +615,7 @@ private static CompletableFuture<ScriptInfo> loadScripts(List<Config> configs, O /** * Creates a script and loads the provided config into it. * @param config The config to load into a script. - * @return The script that was loaded. + * @return A pair containing the script that was loaded and a modifiable version of the structures list. */ // Whenever you call this method, make sure to also call PreScriptLoadEvent private static NonNullPair<Script, List<Structure>> loadScript(Config config) { @@ -1009,20 +1031,6 @@ public static FileFilter getDisabledScriptsFilter() { * by new methods in this class. */ - /** - * Reloads a single script. - * @param scriptFile The file representing the script to reload. - * @return Future of statistics of the newly loaded script. - * @deprecated Use {@link #reloadScript(Script, OpenCloseable)}. - */ - @Deprecated - public static CompletableFuture<ScriptInfo> reloadScript(File scriptFile, OpenCloseable openCloseable) { - Script script = getScript(scriptFile); - if (script == null) - return CompletableFuture.completedFuture(new ScriptInfo()); - return reloadScript(script, openCloseable); - } - /** * Unloads the provided script. * @param scriptFile The file representing the script to unload. @@ -1049,6 +1057,18 @@ private static ScriptInfo unloadScripts(File folder) { return unloadScripts(getScripts(folder)); } + /** + * Reloads a single script. + * @param scriptFile The file representing the script to reload. + * @return Future of statistics of the newly loaded script. + * @deprecated Use {@link #reloadScript(Script, OpenCloseable)}. + */ + @Deprecated + public static CompletableFuture<ScriptInfo> reloadScript(File scriptFile, OpenCloseable openCloseable) { + unloadScript(scriptFile); + return loadScripts(scriptFile, openCloseable); + } + /** * Reloads all scripts in the given folder and its subfolders. * @param folder A folder. @@ -1058,7 +1078,7 @@ private static ScriptInfo unloadScripts(File folder) { @Deprecated public static CompletableFuture<ScriptInfo> reloadScripts(File folder, OpenCloseable openCloseable) { unloadScripts(folder); - return loadScripts(loadStructures(folder), openCloseable); + return loadScripts(folder, openCloseable); } /** diff --git a/src/main/java/ch/njol/skript/structures/StructCommand.java b/src/main/java/ch/njol/skript/structures/StructCommand.java index 0b86f6762f8..540e653bb49 100644 --- a/src/main/java/ch/njol/skript/structures/StructCommand.java +++ b/src/main/java/ch/njol/skript/structures/StructCommand.java @@ -96,7 +96,7 @@ public class StructCommand extends Structure { .addEntry("description", "", true) .addEntry("prefix", null, true) .addEntry("permission", "", true) - .addEntryData(new VariableStringEntryData("permission message", null, true, ScriptCommandEvent.class)) + .addEntryData(new VariableStringEntryData("permission message", null, true)) .addEntryData(new KeyValueEntryData<List<String>>("aliases", new ArrayList<>(), true) { private final Pattern pattern = Pattern.compile("\\s*,\\s*/?"); @@ -131,9 +131,9 @@ protected Integer getValue(String value) { } }) .addEntryData(new LiteralEntryData<>("cooldown", null, true, Timespan.class)) - .addEntryData(new VariableStringEntryData("cooldown message", null, true, ScriptCommandEvent.class)) + .addEntryData(new VariableStringEntryData("cooldown message", null, true)) .addEntry("cooldown bypass", null, true) - .addEntryData(new VariableStringEntryData("cooldown storage", null, true, StringMode.VARIABLE_NAME, ScriptCommandEvent.class)) + .addEntryData(new VariableStringEntryData("cooldown storage", null, true, StringMode.VARIABLE_NAME)) .addSection("trigger", false) .unexpectedEntryMessage(key -> "Unexpected entry '" + key + "'. Check that it's spelled correctly, and ensure that you have put all code into a trigger." @@ -152,7 +152,6 @@ public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResu } @Override - @SuppressWarnings("unchecked") public boolean load() { getParser().setCurrentEvent("command", ScriptCommandEvent.class); @@ -272,8 +271,8 @@ public boolean load() { if (permissionMessage != null && permission.isEmpty()) Skript.warning("command /" + command + " has a permission message set, but not a permission"); - List<String> aliases = (List<String>) entryContainer.get("aliases", true); - int executableBy = (Integer) entryContainer.get("executable by", true); + List<String> aliases = entryContainer.get("aliases", List.class,true); + int executableBy = entryContainer.get("executable by", Integer.class, true); Timespan cooldown = entryContainer.getOptional("cooldown", Timespan.class, false); VariableString cooldownMessage = entryContainer.getOptional("cooldown message", VariableString.class, false); diff --git a/src/main/java/ch/njol/skript/structures/StructOptions.java b/src/main/java/ch/njol/skript/structures/StructOptions.java index 574592e5c96..3f414fb59bd 100644 --- a/src/main/java/ch/njol/skript/structures/StructOptions.java +++ b/src/main/java/ch/njol/skript/structures/StructOptions.java @@ -32,6 +32,7 @@ import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.entry.EntryContainer; +import org.skriptlang.skript.lang.script.Script; import org.skriptlang.skript.lang.script.ScriptData; import org.skriptlang.skript.lang.structure.Structure; @@ -75,20 +76,16 @@ public class StructOptions extends Structure { public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult, EntryContainer entryContainer) { SectionNode node = entryContainer.getSource(); node.convertToEntries(-1); - - OptionsData optionsData = new OptionsData(); - loadOptions(node, "", optionsData.options); - getParser().getCurrentScript().addData(optionsData); - + loadOptions(node, "", getParser().getCurrentScript().getData(OptionsData.class, OptionsData::new).options); return true; } private void loadOptions(SectionNode sectionNode, String prefix, Map<String, String> options) { - for (Node n : sectionNode) { - if (n instanceof EntryNode) { - options.put(prefix + n.getKey(), ((EntryNode) n).getValue()); - } else if (n instanceof SectionNode) { - loadOptions((SectionNode) n, prefix + n.getKey() + ".", options); + for (Node node : sectionNode) { + if (node instanceof EntryNode) { + options.put(prefix + node.getKey(), ((EntryNode) node).getValue()); + } else if (node instanceof SectionNode) { + loadOptions((SectionNode) node, prefix + node.getKey() + ".", options); } else { Skript.error("Invalid line in options"); } @@ -111,7 +108,7 @@ public Priority getPriority() { } @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { return "options"; } diff --git a/src/main/java/org/skriptlang/skript/lang/entry/EntryContainer.java b/src/main/java/org/skriptlang/skript/lang/entry/EntryContainer.java index bc277094db6..c613b7fbffc 100644 --- a/src/main/java/org/skriptlang/skript/lang/entry/EntryContainer.java +++ b/src/main/java/org/skriptlang/skript/lang/entry/EntryContainer.java @@ -89,11 +89,11 @@ public List<Node> getUnhandledNodes() { * @return The entry's value. * @throws RuntimeException If the entry's value is null, or if it is not of the expected type. */ - public <T> T get(String key, Class<T> expectedType, boolean useDefaultValue) { - T parsed = getOptional(key, expectedType, useDefaultValue); - if (parsed == null) + public <E, R extends E> R get(String key, Class<E> expectedType, boolean useDefaultValue) { + R value = getOptional(key, expectedType, useDefaultValue); + if (value == null) throw new RuntimeException("Null value for asserted non-null value"); - return parsed; + return value; } /** @@ -124,13 +124,13 @@ public Object get(String key, boolean useDefaultValue) { */ @Nullable @SuppressWarnings("unchecked") - public <T> T getOptional(String key, Class<T> expectedType, boolean useDefaultValue) { + public <E, R extends E> R getOptional(String key, Class<E> expectedType, boolean useDefaultValue) { Object parsed = getOptional(key, useDefaultValue); if (parsed == null) return null; if (!expectedType.isInstance(parsed)) throw new RuntimeException("Expected entry with key '" + key + "' to be '" + expectedType + "', but got '" + parsed.getClass() + "'"); - return (T) parsed; + return (R) parsed; } /** diff --git a/src/main/java/org/skriptlang/skript/lang/entry/util/ExpressionEntryData.java b/src/main/java/org/skriptlang/skript/lang/entry/util/ExpressionEntryData.java index 7dbd560731b..e707d3337d3 100644 --- a/src/main/java/org/skriptlang/skript/lang/entry/util/ExpressionEntryData.java +++ b/src/main/java/org/skriptlang/skript/lang/entry/util/ExpressionEntryData.java @@ -21,74 +21,63 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ParseContext; import ch.njol.skript.lang.SkriptParser; -import ch.njol.skript.lang.parser.ParserInstance; -import org.skriptlang.skript.lang.entry.KeyValueEntryData; -import ch.njol.util.Kleenean; -import org.bukkit.event.Event; +import ch.njol.skript.localization.Message; +import ch.njol.skript.log.ErrorQuality; +import ch.njol.skript.log.ParseLogHandler; import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.entry.KeyValueEntryData; /** * A type of {@link KeyValueEntryData} designed to parse its value as an {@link Expression}. * This data <b>CAN</b> return null if expression parsing fails. + * Note that it <b>will</b> print an error. */ public class ExpressionEntryData<T> extends KeyValueEntryData<Expression<? extends T>> { + private static final Message M_IS = new Message("is"); + private final Class<T> returnType; private final int flags; - - private final Class<? extends Event>[] events; /** * @param returnType The expected return type of the matched expression. - * @param events Events to be present during parsing and Trigger execution. - * This allows the usage of event-restricted syntax and event-values. - * @see ParserInstance#setCurrentEvents(Class[]) */ - @SafeVarargs public ExpressionEntryData( String key, @Nullable Expression<T> defaultValue, boolean optional, - Class<T> returnType, Class<? extends Event>... events + Class<T> returnType ) { - this(key, defaultValue, optional, returnType, SkriptParser.ALL_FLAGS, events); + this(key, defaultValue, optional, returnType, SkriptParser.ALL_FLAGS); } /** * @param returnType The expected return type of the matched expression. * @param flags Parsing flags. See {@link SkriptParser#SkriptParser(String, int, ParseContext)} * javadoc for more details. - * @param events Events to be present during parsing and Trigger execution. - * This allows the usage of event-restricted syntax and event-values. - * @see ParserInstance#setCurrentEvents(Class[]) */ - @SafeVarargs public ExpressionEntryData( String key, @Nullable Expression<T> defaultValue, boolean optional, - Class<T> returnType, int flags, Class<? extends Event>... events + Class<T> returnType, int flags ) { super(key, defaultValue, optional); this.returnType = returnType; this.flags = flags; - this.events = events; } @Override @Nullable @SuppressWarnings("unchecked") protected Expression<? extends T> getValue(String value) { - ParserInstance parser = ParserInstance.get(); - - Class<? extends Event>[] oldEvents = parser.getCurrentEvents(); - Kleenean oldHasDelayBefore = parser.getHasDelayBefore(); - - parser.setCurrentEvents(events); - parser.setHasDelayBefore(Kleenean.FALSE); - - Expression<? extends T> expression = new SkriptParser(value, flags, ParseContext.DEFAULT).parseExpression(returnType); - - parser.setCurrentEvents(oldEvents); - parser.setHasDelayBefore(oldHasDelayBefore); - + Expression<? extends T> expression; + try (ParseLogHandler log = new ParseLogHandler()) { + expression = new SkriptParser(value, flags, ParseContext.DEFAULT) + .parseExpression(returnType); + if (expression == null) // print an error if it couldn't parse + log.printError( + "'" + value + "' " + M_IS + " " + SkriptParser.notOfType(returnType), + ErrorQuality.NOT_AN_EXPRESSION + ); + } return expression; } diff --git a/src/main/java/org/skriptlang/skript/lang/entry/util/LiteralEntryData.java b/src/main/java/org/skriptlang/skript/lang/entry/util/LiteralEntryData.java index 9b48a19e341..43e9932dc81 100644 --- a/src/main/java/org/skriptlang/skript/lang/entry/util/LiteralEntryData.java +++ b/src/main/java/org/skriptlang/skript/lang/entry/util/LiteralEntryData.java @@ -38,7 +38,10 @@ public class LiteralEntryData<T> extends KeyValueEntryData<T> { /** * @param type The type to parse the value into. */ - public LiteralEntryData(String key, @Nullable T defaultValue, boolean optional, Class<T> type) { + public LiteralEntryData( + String key, @Nullable T defaultValue, boolean optional, + Class<T> type + ) { super(key, defaultValue, optional); this.type = type; } diff --git a/src/main/java/org/skriptlang/skript/lang/entry/util/TriggerEntryData.java b/src/main/java/org/skriptlang/skript/lang/entry/util/TriggerEntryData.java index b0a856e3756..a445dc438a4 100644 --- a/src/main/java/org/skriptlang/skript/lang/entry/util/TriggerEntryData.java +++ b/src/main/java/org/skriptlang/skript/lang/entry/util/TriggerEntryData.java @@ -32,49 +32,25 @@ /** * An entry data class designed to take a {@link SectionNode} and parse it into a Trigger. - * Events specified during construction *should* be used when the Trigger is executed. * This data will <b>NEVER</b> return null. * @see SectionEntryData */ public class TriggerEntryData extends EntryData<Trigger> { - private final Class<? extends Event>[] events; - - /** - * @param events Events to be present during parsing and Trigger execution. - * This allows the usage of event-restricted syntax and event-values. - * @see ParserInstance#setCurrentEvents(Class[]) - */ - @SafeVarargs - public TriggerEntryData( - String key, @Nullable Trigger defaultValue, boolean optional, - Class<? extends Event>... events - ) { + public TriggerEntryData(String key, @Nullable Trigger defaultValue, boolean optional) { super(key, defaultValue, optional); - this.events = events; } @Nullable @Override public Trigger getValue(Node node) { assert node instanceof SectionNode; - - ParserInstance parser = ParserInstance.get(); - - Class<? extends Event>[] oldEvents = parser.getCurrentEvents(); - Kleenean oldHasDelayBefore = parser.getHasDelayBefore(); - - parser.setCurrentEvents(events); - parser.setHasDelayBefore(Kleenean.FALSE); - - Trigger trigger = new Trigger( - parser.getCurrentScript(), "entry with key: " + getKey(), new SimpleEvent(), ScriptLoader.loadItems((SectionNode) node) + return new Trigger( + ParserInstance.get().getCurrentScript(), + "entry with key: " + getKey(), + new SimpleEvent(), + ScriptLoader.loadItems((SectionNode) node) ); - - parser.setCurrentEvents(oldEvents); - parser.setHasDelayBefore(oldHasDelayBefore); - - return trigger; } @Override diff --git a/src/main/java/org/skriptlang/skript/lang/entry/util/VariableStringEntryData.java b/src/main/java/org/skriptlang/skript/lang/entry/util/VariableStringEntryData.java index 57264fd0393..6a61e3642fa 100644 --- a/src/main/java/org/skriptlang/skript/lang/entry/util/VariableStringEntryData.java +++ b/src/main/java/org/skriptlang/skript/lang/entry/util/VariableStringEntryData.java @@ -36,58 +36,34 @@ public class VariableStringEntryData extends KeyValueEntryData<VariableString> { private final StringMode stringMode; - private final Class<? extends Event>[] events; - /** - * @param events Events to be present during parsing and Trigger execution. - * This allows the usage of event-restricted syntax and event-values. - * @see ParserInstance#setCurrentEvents(Class[]) + * Uses {@link StringMode#MESSAGE} as the default string mode. + * @see #VariableStringEntryData(String, VariableString, boolean, StringMode) */ - @SafeVarargs public VariableStringEntryData( - String key, @Nullable VariableString defaultValue, boolean optional, - Class<? extends Event>... events + String key, @Nullable VariableString defaultValue, boolean optional ) { - this(key, defaultValue, optional, StringMode.MESSAGE, events); + this(key, defaultValue, optional, StringMode.MESSAGE); } /** * @param stringMode Sets <i>how</i> to parse the string (e.g. as a variable, message, etc.). - * @param events Events to be present during parsing and Trigger execution. - * This allows the usage of event-restricted syntax and event-values. - * @see ParserInstance#setCurrentEvents(Class[]) */ - @SafeVarargs public VariableStringEntryData( String key, @Nullable VariableString defaultValue, boolean optional, - StringMode stringMode, Class<? extends Event>... events + StringMode stringMode ) { super(key, defaultValue, optional); this.stringMode = stringMode; - this.events = events; } @Override @Nullable protected VariableString getValue(String value) { - ParserInstance parser = ParserInstance.get(); - - Class<? extends Event>[] oldEvents = parser.getCurrentEvents(); - Kleenean oldHasDelayBefore = parser.getHasDelayBefore(); - - parser.setCurrentEvents(events); - parser.setHasDelayBefore(Kleenean.FALSE); - // Double up quotations outside of expressions if (stringMode != StringMode.VARIABLE_NAME) value = VariableString.quote(value); - - VariableString variableString = VariableString.newInstance(value, stringMode); - - parser.setCurrentEvents(oldEvents); - parser.setHasDelayBefore(oldHasDelayBefore); - - return variableString; + return VariableString.newInstance(value, stringMode); } } diff --git a/src/main/java/org/skriptlang/skript/lang/script/Script.java b/src/main/java/org/skriptlang/skript/lang/script/Script.java index 888f53d71c5..ed3b1920474 100644 --- a/src/main/java/org/skriptlang/skript/lang/script/Script.java +++ b/src/main/java/org/skriptlang/skript/lang/script/Script.java @@ -103,33 +103,41 @@ public boolean suppressesWarning(ScriptWarning warning) { private final Map<Class<? extends ScriptData>, ScriptData> scriptData = new ConcurrentHashMap<>(5); /** - * Clears the data stored for this script. - */ - public void clearData() { - scriptData.clear(); - } - - /** + * <b>This API is experimental and subject to change.</b> * Adds new ScriptData to this Script's data map. * @param data The data to add. */ + @ApiStatus.Experimental public void addData(ScriptData data) { scriptData.put(data.getClass(), data); } /** + * <b>This API is experimental and subject to change.</b> * Removes the ScriptData matching the specified data type. * @param dataType The type of the data to remove. */ + @ApiStatus.Experimental public void removeData(Class<? extends ScriptData> dataType) { scriptData.remove(dataType); } /** + * <b>This API is experimental and subject to change.</b> + * Clears the data stored for this script. + */ + @ApiStatus.Experimental + public void clearData() { + scriptData.clear(); + } + + /** + * <b>This API is experimental and subject to change.</b> * A method to obtain ScriptData matching the specified data type. * @param dataType The class representing the ScriptData to obtain. * @return ScriptData found matching the provided class, or null if no data is present. */ + @ApiStatus.Experimental @Nullable @SuppressWarnings("unchecked") public <Type extends ScriptData> Type getData(Class<Type> dataType) { @@ -137,12 +145,14 @@ public <Type extends ScriptData> Type getData(Class<Type> dataType) { } /** + * <b>This API is experimental and subject to change.</b> * A method that always obtains ScriptData matching the specified data type. * By using the mapping supplier, it will also add ScriptData of the provided type if it is not already present. * @param dataType The class representing the ScriptData to obtain. * @param mapper A supplier to create ScriptData of the provided type if such ScriptData is not already present. * @return Existing ScriptData found matching the provided class, or new data provided by the mapping function. */ + @ApiStatus.Experimental @SuppressWarnings("unchecked") public <Value extends ScriptData> Value getData(Class<? extends Value> dataType, Supplier<Value> mapper) { return (Value) scriptData.computeIfAbsent(dataType, clazz -> mapper.get()); @@ -153,42 +163,52 @@ public <Value extends ScriptData> Value getData(Class<? extends Value> dataType, private final Set<ScriptEvent> eventHandlers = new HashSet<>(5); /** + * <b>This API is experimental and subject to change.</b> * Adds the provided event to this Script. * @param event The event to add. */ + @ApiStatus.Experimental public void registerEvent(ScriptEvent event) { eventHandlers.add(event); } /** + * <b>This API is experimental and subject to change.</b> * Adds the provided event to this Script. * @param eventType The type of event being added. This is useful for registering the event through lambdas. * @param event The event to add. */ + @ApiStatus.Experimental public <T extends ScriptEvent> void registerEvent(Class<T> eventType, T event) { eventHandlers.add(event); } /** + * <b>This API is experimental and subject to change.</b> * Removes the provided event from this Script. * @param event The event to remove. */ + @ApiStatus.Experimental public void unregisterEvent(ScriptEvent event) { eventHandlers.remove(event); } /** + * <b>This API is experimental and subject to change.</b> * @return An unmodifiable set of all events. */ + @ApiStatus.Experimental @Unmodifiable public Set<ScriptEvent> getEvents() { return Collections.unmodifiableSet(eventHandlers); } /** + * <b>This API is experimental and subject to change.</b> * @param type The type of events to get. * @return An unmodifiable set of all events of the specified type. */ + @ApiStatus.Experimental @Unmodifiable @SuppressWarnings("unchecked") public <T extends ScriptEvent> Set<T> getEvents(Class<T> type) { diff --git a/src/main/java/org/skriptlang/skript/lang/structure/Structure.java b/src/main/java/org/skriptlang/skript/lang/structure/Structure.java index 1a787c366a8..17de96e446c 100644 --- a/src/main/java/org/skriptlang/skript/lang/structure/Structure.java +++ b/src/main/java/org/skriptlang/skript/lang/structure/Structure.java @@ -130,15 +130,14 @@ public boolean preLoad() { /** * The second phase of Structure loading. - * During this phase, Structures are loaded script by script. - * The order they are loaded in for each script is based on the Structure's priority. + * During this phase, all Structures across all loading scripts are loaded with respect to their priorities. * @return Whether loading was successful. An error should be printed prior to returning false to specify the cause. */ public abstract boolean load(); /** * The third and final phase of Structure loading. - * The loading order and method is the same as {@link #load()}. + * During this phase, all Structures across all loading scripts are loaded with respect to their priorities. * This method is primarily designed for Structures that wish to execute actions after * most other Structures have finished loading. * @return Whether postLoading was successful. An error should be printed prior to returning false to specify the cause. From a0691039972d1daf6689911298dda7a754f73dce Mon Sep 17 00:00:00 2001 From: Fusezion <fusezionstream@gmail.com> Date: Fri, 8 Sep 2023 16:20:33 -0400 Subject: [PATCH 424/619] Add decrease to EffChange (#5976) --- src/main/java/ch/njol/skript/effects/EffChange.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/effects/EffChange.java b/src/main/java/ch/njol/skript/effects/EffChange.java index 80ddd172646..6b16e36071f 100644 --- a/src/main/java/ch/njol/skript/effects/EffChange.java +++ b/src/main/java/ch/njol/skript/effects/EffChange.java @@ -87,7 +87,7 @@ public class EffChange extends Effect { {"remove (all|every) %objects% from %~objects%", ChangeMode.REMOVE_ALL}, {"(remove|subtract) %objects% from %~objects%", ChangeMode.REMOVE}, - {"reduce %~objects% by %objects%", ChangeMode.REMOVE}, + {"(reduce|decrease) %~objects% by %objects%", ChangeMode.REMOVE}, {"(delete|clear) %~objects%", ChangeMode.DELETE}, From 9c82fed023ff02626cc22233a1d1c78bb65a3d6c Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Fri, 8 Sep 2023 18:30:57 -0600 Subject: [PATCH 425/619] Fixes an exception from being thrown on async events (#5699) --- .../ch/njol/skript/SkriptEventHandler.java | 68 ++++++++++++------- .../java/ch/njol/skript/lang/SkriptEvent.java | 7 ++ 2 files changed, 51 insertions(+), 24 deletions(-) diff --git a/src/main/java/ch/njol/skript/SkriptEventHandler.java b/src/main/java/ch/njol/skript/SkriptEventHandler.java index 144eca18721..28eaff9b23c 100644 --- a/src/main/java/ch/njol/skript/SkriptEventHandler.java +++ b/src/main/java/ch/njol/skript/SkriptEventHandler.java @@ -18,11 +18,17 @@ */ package ch.njol.skript; -import ch.njol.skript.lang.SkriptEvent; -import ch.njol.skript.lang.Trigger; -import ch.njol.skript.timings.SkriptTimings; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.Multimap; +import java.lang.ref.WeakReference; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.stream.Collectors; + import org.bukkit.Bukkit; import org.bukkit.event.Cancellable; import org.bukkit.event.Event; @@ -36,16 +42,13 @@ import org.bukkit.plugin.RegisteredListener; import org.eclipse.jdt.annotation.Nullable; -import java.lang.ref.WeakReference; -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.stream.Collectors; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; + +import ch.njol.skript.lang.SkriptEvent; +import ch.njol.skript.lang.Trigger; +import ch.njol.skript.timings.SkriptTimings; +import ch.njol.skript.util.Task; public final class SkriptEventHandler { @@ -110,12 +113,14 @@ private static List<Trigger> getTriggers(Class<? extends Event> event) { */ private static void check(Event event, EventPriority priority) { List<Trigger> triggers = getTriggers(event.getClass()); + if (triggers.isEmpty()) + return; if (Skript.logVeryHigh()) { boolean hasTrigger = false; for (Trigger trigger : triggers) { SkriptEvent triggerEvent = trigger.getEvent(); - if (triggerEvent.getEventPriority() == priority && triggerEvent.check(event)) { + if (triggerEvent.getEventPriority() == priority && Boolean.TRUE.equals(Task.callSync(() -> triggerEvent.check(event)))) { hasTrigger = true; break; } @@ -137,16 +142,31 @@ private static void check(Event event, EventPriority priority) { for (Trigger trigger : triggers) { SkriptEvent triggerEvent = trigger.getEvent(); - if (triggerEvent.getEventPriority() != priority || !triggerEvent.check(event)) + if (triggerEvent.getEventPriority() != priority) continue; - logTriggerStart(trigger); - Object timing = SkriptTimings.start(trigger.getDebugLabel()); - - trigger.execute(event); - - SkriptTimings.stop(timing); - logTriggerEnd(trigger); + // these methods need to be run on whatever thread the trigger is + Runnable execute = () -> { + logTriggerStart(trigger); + Object timing = SkriptTimings.start(trigger.getDebugLabel()); + trigger.execute(event); + SkriptTimings.stop(timing); + logTriggerEnd(trigger); + }; + + if (trigger.getEvent().canExecuteAsynchronously()) { + // check should be performed on the main thread + if (Boolean.FALSE.equals(Task.callSync(() -> triggerEvent.check(event)))) + continue; + execute.run(); + } else { // Ensure main thread + Task.callSync(() -> { + if (!triggerEvent.check(event)) + return null; + execute.run(); + return null; // we don't care about a return value + }); + } } logEventEnd(); diff --git a/src/main/java/ch/njol/skript/lang/SkriptEvent.java b/src/main/java/ch/njol/skript/lang/SkriptEvent.java index e150e8699d8..1d89b2c6aa9 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptEvent.java +++ b/src/main/java/ch/njol/skript/lang/SkriptEvent.java @@ -216,6 +216,13 @@ public boolean isEventPrioritySupported() { return true; } + /** + * Override this method to allow Skript to not force synchronization. + */ + public boolean canExecuteAsynchronously() { + return false; + } + /** * Fixes patterns in event by modifying every {@link ch.njol.skript.patterns.TypePatternElement} * to be nullable. From 49d562065561acd41db418a4c16db120430e9034 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Sat, 9 Sep 2023 10:37:51 +0300 Subject: [PATCH 426/619] =?UTF-8?q?=F0=9F=8D=92=20[2.7]=20Add=20infinite?= =?UTF-8?q?=20and=20icon=20to=20potion=20effects=20(#5982)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add infinite and icon to potion effects (#5777) Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> --- .../skript/conditions/CondIsInfinite.java | 52 ++++++ .../ch/njol/skript/effects/EffPotion.java | 169 ++++++++---------- .../njol/skript/util/PotionEffectUtils.java | 30 ++-- 3 files changed, 141 insertions(+), 110 deletions(-) create mode 100644 src/main/java/ch/njol/skript/conditions/CondIsInfinite.java diff --git a/src/main/java/ch/njol/skript/conditions/CondIsInfinite.java b/src/main/java/ch/njol/skript/conditions/CondIsInfinite.java new file mode 100644 index 00000000000..98ed56c0570 --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondIsInfinite.java @@ -0,0 +1,52 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.conditions; + +import org.bukkit.potion.PotionEffect; + +import ch.njol.skript.Skript; +import ch.njol.skript.conditions.base.PropertyCondition; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; + +// This class can be expanded apon for other types if needed. +@Name("Is Infinite") +@Description("Checks whether potion effects are infinite.") +@Examples("all of the active potion effects of the player are infinite") +@Since("INSERT VERSION") +public class CondIsInfinite extends PropertyCondition<PotionEffect> { + + static { + if (Skript.methodExists(PotionEffect.class, "isInfinite")) + register(CondIsInfinite.class, "infinite", "potioneffects"); + } + + @Override + public boolean check(PotionEffect potion) { + return potion.isInfinite(); + } + + @Override + protected String getPropertyName() { + return "infinite"; + } + +} diff --git a/src/main/java/ch/njol/skript/effects/EffPotion.java b/src/main/java/ch/njol/skript/effects/EffPotion.java index 09c7af5912a..64b07bb0be4 100644 --- a/src/main/java/ch/njol/skript/effects/EffPotion.java +++ b/src/main/java/ch/njol/skript/effects/EffPotion.java @@ -36,143 +36,128 @@ import ch.njol.skript.util.Timespan; import ch.njol.util.Kleenean; -/** - * @author Peter Güttinger - */ @Name("Potion Effects") @Description("Apply or remove potion effects to/from entities.") -@Examples({"apply swiftness 2 to the player", +@Examples({ + "apply ambient swiftness 2 to the player", "remove haste from the victim", + "", "on join:", - "\tapply potion of strength of tier {strength.%player%} to the player for 999 days", - "apply potion effects of player's tool to player"}) -@Since("2.0, 2.2-dev27 (ambient and particle-less potion effects), 2.5 (replacing existing effect), 2.5.2 (potion effects)") + "\tapply infinite potion of strength of tier {strength::%uuid of player%} to the player", + "\tapply potion of strength of tier {strength::%uuid of player%} to the player for 999 days # Before 1.19.4", + "", + "apply potion effects of player's tool to player", + "apply haste potion of tier 3 without any particles to the player whilst hiding the potion icon # Hide potions" +}) +@Since( + "2.0, 2.2-dev27 (ambient and particle-less potion effects), " + + "2.5 (replacing existing effect), 2.5.2 (potion effects), " + + "INSERT VERSION (icon and infinite)" +) public class EffPotion extends Effect { + static { Skript.registerEffect(EffPotion.class, - "apply %potioneffects% to %livingentities%", - "apply [potion of] %potioneffecttypes% [potion] [[[of] tier] %-number%] to %livingentities% [for %-timespan%] [(1¦replacing [the] existing effect)]", - "apply ambient [potion of] %potioneffecttypes% [potion] [[[of] tier] %-number%] to %livingentities% [for %-timespan%] [(1¦replacing [the] existing effect)]", - "apply [potion of] %potioneffecttypes% [potion] [[[of] tier] %-number%] without [any] particles to %livingentities% [for %-timespan%] [(1¦replacing [the] existing effect)]" - //, "apply %itemtypes% to %livingentities%" - /*,"remove %potioneffecttypes% from %livingentities%"*/); + "apply %potioneffects% to %livingentities%", + "apply infinite [:ambient] [potion of] %potioneffecttypes% [potion] [[[of] tier] %-number%] [noparticles:without [any] particles] [icon:(whilst hiding [the]|without (the|a)) [potion] icon] to %livingentities% [replacing:replacing [the] existing effect]", + "apply [:ambient] [potion of] %potioneffecttypes% [potion] [[[of] tier] %-number%] [noparticles:without [any] particles] [icon:(whilst hiding [the]|without (the|a)) [potion] icon] to %livingentities% [for %-timespan%] [replacing:replacing [the] existing effect]" + ); } + private final static boolean COMPATIBLE = Skript.isRunningMinecraft(1, 19, 4); + private final static int DEFAULT_DURATION = 15 * 20; // 15 seconds, same as EffPoison - private boolean replaceExisting; - @SuppressWarnings("null") private Expression<PotionEffectType> potions; - @Nullable - private Expression<Number> tier; - @SuppressWarnings("null") private Expression<LivingEntity> entities; + private Expression<PotionEffect> effects; + @Nullable private Expression<Timespan> duration; - @SuppressWarnings("null") - private Expression<PotionEffect> potionEffects; - private boolean apply; + + @Nullable + private Expression<Number> tier; + + private boolean replaceExisting; // Replace the existing potion if present. + private boolean potionEffect; // PotionEffects rather than PotionEffectTypes. + private boolean noParticles; + private boolean infinite; // 1.19.4+ has an infinite option. private boolean ambient; // Ambient means less particles - private boolean particles; // Particles or no particles? - private boolean potionEffect; // PotionEffects rather than PotionEffectTypes + private boolean icon; - @SuppressWarnings({"unchecked", "null"}) @Override - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { - apply = matchedPattern > 0; + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { potionEffect = matchedPattern == 0; - replaceExisting = parseResult.mark == 1; + replaceExisting = parseResult.hasTag("replacing"); + noParticles = parseResult.hasTag("noparticles"); + ambient = parseResult.hasTag("ambient"); + icon = !parseResult.hasTag("icon"); + infinite = matchedPattern == 1; if (potionEffect) { - potionEffects = (Expression<PotionEffect>) exprs[0]; + effects = (Expression<PotionEffect>) exprs[0]; entities = (Expression<LivingEntity>) exprs[1]; - } else if (apply) { + } else { potions = (Expression<PotionEffectType>) exprs[0]; tier = (Expression<Number>) exprs[1]; entities = (Expression<LivingEntity>) exprs[2]; - duration = (Expression<Timespan>) exprs[3]; - } else { - potions = (Expression<PotionEffectType>) exprs[0]; - entities = (Expression<LivingEntity>) exprs[1]; - } - - // Ambience and particles - switch (matchedPattern) { - case 1: - ambient = false; - particles = true; - break; - case 2: - ambient = true; - particles = true; - break; - case 3: - ambient = false; - particles = false; - break; + if (infinite) + duration = (Expression<Timespan>) exprs[3]; } - return true; } @Override - protected void execute(final Event e) { + protected void execute(Event event) { if (potionEffect) { - for (LivingEntity livingEntity : entities.getArray(e)) { - PotionEffect[] potionEffects = this.potionEffects.getArray(e); - PotionEffectUtils.addEffects(livingEntity, potionEffects); - } + for (LivingEntity livingEntity : entities.getArray(event)) + PotionEffectUtils.addEffects(livingEntity, effects.getArray(event)); } else { - final PotionEffectType[] ts = potions.getArray(e); - if (ts.length == 0) - return; - if (!apply) { - for (LivingEntity en : entities.getArray(e)) { - for (final PotionEffectType t : ts) - en.removePotionEffect(t); - } + PotionEffectType[] potionEffectTypes = potions.getArray(event); + if (potionEffectTypes.length == 0) return; - } - int a = 0; - if (tier != null) { - final Number amp = tier.getSingle(e); - if (amp == null) - return; - a = amp.intValue() - 1; - } - int d = DEFAULT_DURATION; - if (duration != null) { - final Timespan dur = duration.getSingle(e); - if (dur == null) + int tier = 0; + if (this.tier != null) + tier = this.tier.getOptionalSingle(event).orElse(1).intValue() - 1; + + int duration = infinite ? (COMPATIBLE ? -1 : Integer.MAX_VALUE) : DEFAULT_DURATION; + if (this.duration != null && !infinite) { + Timespan timespan = this.duration.getSingle(event); + if (timespan == null) return; - d = (int) (dur.getTicks_i() >= Integer.MAX_VALUE ? Integer.MAX_VALUE : dur.getTicks_i()); + duration = (int) Math.max(timespan.getTicks_i(), Integer.MAX_VALUE); } - for (final LivingEntity en : entities.getArray(e)) { - for (final PotionEffectType t : ts) { - int duration = d; - if (!replaceExisting) { - if (en.hasPotionEffect(t)) { - for (final PotionEffect eff : en.getActivePotionEffects()) { - if (eff.getType() == t) { - duration += eff.getDuration(); + for (LivingEntity entity : entities.getArray(event)) { + for (PotionEffectType potionEffectType : potionEffectTypes) { + int finalDuration = duration; + if (!replaceExisting && !infinite) { + if (entity.hasPotionEffect(potionEffectType)) { + for (PotionEffect effect : entity.getActivePotionEffects()) { + if (effect.getType() == potionEffectType) { + finalDuration += effect.getDuration(); break; } } } } - en.addPotionEffect(new PotionEffect(t, duration, a, ambient, particles), true); + entity.addPotionEffect(new PotionEffect(potionEffectType, finalDuration, tier, ambient, !noParticles, icon)); } } } } @Override - public String toString(final @Nullable Event e, final boolean debug) { - if (potionEffect) - return "apply " + potionEffects.toString(e, debug) + " to " + entities.toString(e, debug); - else if (apply) - return "apply " + potions.toString(e, debug) + (tier != null ? " of tier " + tier.toString(e, debug) : "") + " to " + entities.toString(e, debug) + (duration != null ? " for " + duration.toString(e, debug) : ""); - else - return "remove " + potions.toString(e, debug) + " from " + entities.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + if (potionEffect) { + // Uses PotionEffectUtils#toString + return "apply " + effects.toString(event, debug) + " to " + entities.toString(event, debug); + } else { + return "apply " + (infinite ? " infinite " : "") + + potions.toString(event, debug) + + (tier != null ? " of tier " + tier.toString(event, debug) : "") + + " to " + entities.toString(event, debug) + + (duration != null ? " for " + duration.toString(event, debug) : ""); + } } } diff --git a/src/main/java/ch/njol/skript/util/PotionEffectUtils.java b/src/main/java/ch/njol/skript/util/PotionEffectUtils.java index 2e6c9037863..c77185d8f7f 100644 --- a/src/main/java/ch/njol/skript/util/PotionEffectUtils.java +++ b/src/main/java/ch/njol/skript/util/PotionEffectUtils.java @@ -19,7 +19,6 @@ package ch.njol.skript.util; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -41,20 +40,17 @@ import ch.njol.skript.localization.Language; import ch.njol.skript.localization.LanguageChangeListener; -/** - * @author Peter Güttinger - */ @SuppressWarnings("deprecation") public abstract class PotionEffectUtils { - + private static final boolean HAS_SUSPICIOUS_META = Skript.classExists("org.bukkit.inventory.meta.SuspiciousStewMeta"); - + private PotionEffectUtils() {} - + final static Map<String, PotionEffectType> types = new HashMap<>(); - + final static String[] names = new String[getMaxPotionId() + 1]; - + // MCPC+ workaround private static int getMaxPotionId() { int i = 0; @@ -99,32 +95,30 @@ public static PotionEffectType parseByEffectType(PotionEffectType t) { return null; } - @SuppressWarnings("null") - public static String toString(final PotionEffectType t) { + public static String toString(PotionEffectType t) { return names[t.getId()]; } // REMIND flags? - @SuppressWarnings("null") - public static String toString(final PotionEffectType t, final int flags) { + public static String toString(PotionEffectType t, int flags) { return names[t.getId()]; } - + public static String toString(PotionEffect potionEffect) { StringBuilder builder = new StringBuilder(); if (potionEffect.isAmbient()) builder.append("ambient "); builder.append("potion effect of "); builder.append(toString(potionEffect.getType())); - builder.append(" of tier ").append(potionEffect.getAmplifier() + 1); - if (!potionEffect.hasParticles()) builder.append(" without particles"); - builder.append(" for ").append(Timespan.fromTicks_i(potionEffect.getDuration())); + builder.append(" for ").append(potionEffect.getDuration() == -1 ? "infinity" : Timespan.fromTicks_i(Math.abs(potionEffect.getDuration()))); + if (!potionEffect.hasIcon()) + builder.append(" without icon"); return builder.toString(); } - + public static String[] getNames() { return names; } From 7148bf2a992a6693787990ffe0c2b140d4e8214d Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Sat, 9 Sep 2023 10:37:58 +0300 Subject: [PATCH 427/619] =?UTF-8?q?=F0=9F=8D=92=20[2.7]=20Structure=20API?= =?UTF-8?q?=20Finalization=20(#5669)=20(#5981)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Structure API Finalization (#5669) Co-authored-by: Patrick Miller <apickledwalrus@gmail.com> --- .../java/ch/njol/skript/ScriptLoader.java | 148 ++++++++++-------- .../njol/skript/structures/StructCommand.java | 11 +- .../njol/skript/structures/StructOptions.java | 19 +-- .../skript/lang/entry/EntryContainer.java | 12 +- .../lang/entry/util/ExpressionEntryData.java | 51 +++--- .../lang/entry/util/LiteralEntryData.java | 5 +- .../lang/entry/util/TriggerEntryData.java | 36 +---- .../entry/util/VariableStringEntryData.java | 36 +---- .../skriptlang/skript/lang/script/Script.java | 34 +++- .../skript/lang/structure/Structure.java | 5 +- 10 files changed, 168 insertions(+), 189 deletions(-) diff --git a/src/main/java/ch/njol/skript/ScriptLoader.java b/src/main/java/ch/njol/skript/ScriptLoader.java index 14cd0b3e628..3fa8c855c32 100644 --- a/src/main/java/ch/njol/skript/ScriptLoader.java +++ b/src/main/java/ch/njol/skript/ScriptLoader.java @@ -516,67 +516,89 @@ private static CompletableFuture<ScriptInfo> loadScripts(List<Config> configs, O try { openCloseable.open(); - scripts.stream() - .flatMap(pair -> { // Flatten each entry down to a stream of Script-Structure pairs - return pair.getSecond().stream() - .map(structure -> new NonNullPair<>(pair, structure)); - }) - .sorted(Comparator.comparing(pair -> pair.getSecond().getPriority())) - .forEach(pair -> { - Script script = pair.getFirst().getFirst(); - Structure structure = pair.getSecond(); - - parser.setActive(script); - parser.setCurrentStructure(structure); - parser.setNode(structure.getEntryContainer().getSource()); - - try { - if (!structure.preLoad()) - pair.getFirst().getSecond().remove(structure); - } catch (Exception e) { - //noinspection ThrowableNotThrown - Skript.exception(e, "An error occurred while trying to load a Structure."); + // build sorted list + // this nest of pairs is terrible, but we need to keep the reference to the modifiable structures list + List<NonNullPair<NonNullPair<Script, List<Structure>>, Structure>> pairs = scripts.stream() + .flatMap(pair -> { // Flatten each entry down to a stream of Script-Structure pairs + return pair.getSecond().stream() + .map(structure -> new NonNullPair<>(pair, structure)); + }) + .sorted(Comparator.comparing(pair -> pair.getSecond().getPriority())) + .collect(Collectors.toCollection(ArrayList::new)); + + // pre-loading + pairs.removeIf(pair -> { + Structure structure = pair.getSecond(); + + parser.setActive(pair.getFirst().getFirst()); + parser.setCurrentStructure(structure); + parser.setNode(structure.getEntryContainer().getSource()); + + try { + if (!structure.preLoad()) { pair.getFirst().getSecond().remove(structure); + return true; } - }); - + } catch (Exception e) { + //noinspection ThrowableNotThrown + Skript.exception(e, "An error occurred while trying to preLoad a Structure."); + pair.getFirst().getSecond().remove(structure); + return true; + } + return false; + }); parser.setInactive(); - // TODO in the future, Structure#load should be split across multiple threads if parallel loading is enabled. + // TODO in the future, Structure#load/Structure#postLoad should be split across multiple threads if parallel loading is enabled. // However, this is not possible right now as reworks in multiple areas will be needed. // For example, the "Commands" class still uses a static list for currentArguments that is cleared between loads. // Until these reworks happen, limiting main loading to asynchronous (not parallel) is the only choice we have. - for (NonNullPair<Script, List<Structure>> pair : scripts) { - parser.setActive(pair.getFirst()); - pair.getSecond().removeIf(structure -> { - parser.setCurrentStructure(structure); - parser.setNode(structure.getEntryContainer().getSource()); - try { - return !structure.load(); - } catch (Exception e) { - //noinspection ThrowableNotThrown - Skript.exception(e, "An error occurred while trying to load a Structure."); + + // loading + pairs.removeIf(pair -> { + Structure structure = pair.getSecond(); + + parser.setActive(pair.getFirst().getFirst()); + parser.setCurrentStructure(structure); + parser.setNode(structure.getEntryContainer().getSource()); + + try { + if (!structure.load()) { + pair.getFirst().getSecond().remove(structure); return true; } - }); - } - + } catch (Exception e) { + //noinspection ThrowableNotThrown + Skript.exception(e, "An error occurred while trying to load a Structure."); + pair.getFirst().getSecond().remove(structure); + return true; + } + return false; + }); parser.setInactive(); - for (NonNullPair<Script, List<Structure>> pair : scripts) { - parser.setActive(pair.getFirst()); - pair.getSecond().removeIf(structure -> { - parser.setCurrentStructure(structure); - parser.setNode(structure.getEntryContainer().getSource()); - try { - return !structure.postLoad(); - } catch (Exception e) { - //noinspection ThrowableNotThrown - Skript.exception(e, "An error occurred while trying to load a Structure."); + // post-loading + pairs.removeIf(pair -> { + Structure structure = pair.getSecond(); + + parser.setActive(pair.getFirst().getFirst()); + parser.setCurrentStructure(structure); + parser.setNode(structure.getEntryContainer().getSource()); + + try { + if (!structure.postLoad()) { + pair.getFirst().getSecond().remove(structure); return true; } - }); - } + } catch (Exception e) { + //noinspection ThrowableNotThrown + Skript.exception(e, "An error occurred while trying to postLoad a Structure."); + pair.getFirst().getSecond().remove(structure); + return true; + } + return false; + }); + parser.setInactive(); return scriptInfo; } catch (Exception e) { @@ -593,7 +615,7 @@ private static CompletableFuture<ScriptInfo> loadScripts(List<Config> configs, O /** * Creates a script and loads the provided config into it. * @param config The config to load into a script. - * @return The script that was loaded. + * @return A pair containing the script that was loaded and a modifiable version of the structures list. */ // Whenever you call this method, make sure to also call PreScriptLoadEvent private static NonNullPair<Script, List<Structure>> loadScript(Config config) { @@ -1009,20 +1031,6 @@ public static FileFilter getDisabledScriptsFilter() { * by new methods in this class. */ - /** - * Reloads a single script. - * @param scriptFile The file representing the script to reload. - * @return Future of statistics of the newly loaded script. - * @deprecated Use {@link #reloadScript(Script, OpenCloseable)}. - */ - @Deprecated - public static CompletableFuture<ScriptInfo> reloadScript(File scriptFile, OpenCloseable openCloseable) { - Script script = getScript(scriptFile); - if (script == null) - return CompletableFuture.completedFuture(new ScriptInfo()); - return reloadScript(script, openCloseable); - } - /** * Unloads the provided script. * @param scriptFile The file representing the script to unload. @@ -1049,6 +1057,18 @@ private static ScriptInfo unloadScripts(File folder) { return unloadScripts(getScripts(folder)); } + /** + * Reloads a single script. + * @param scriptFile The file representing the script to reload. + * @return Future of statistics of the newly loaded script. + * @deprecated Use {@link #reloadScript(Script, OpenCloseable)}. + */ + @Deprecated + public static CompletableFuture<ScriptInfo> reloadScript(File scriptFile, OpenCloseable openCloseable) { + unloadScript(scriptFile); + return loadScripts(scriptFile, openCloseable); + } + /** * Reloads all scripts in the given folder and its subfolders. * @param folder A folder. @@ -1058,7 +1078,7 @@ private static ScriptInfo unloadScripts(File folder) { @Deprecated public static CompletableFuture<ScriptInfo> reloadScripts(File folder, OpenCloseable openCloseable) { unloadScripts(folder); - return loadScripts(loadStructures(folder), openCloseable); + return loadScripts(folder, openCloseable); } /** diff --git a/src/main/java/ch/njol/skript/structures/StructCommand.java b/src/main/java/ch/njol/skript/structures/StructCommand.java index 0b86f6762f8..540e653bb49 100644 --- a/src/main/java/ch/njol/skript/structures/StructCommand.java +++ b/src/main/java/ch/njol/skript/structures/StructCommand.java @@ -96,7 +96,7 @@ public class StructCommand extends Structure { .addEntry("description", "", true) .addEntry("prefix", null, true) .addEntry("permission", "", true) - .addEntryData(new VariableStringEntryData("permission message", null, true, ScriptCommandEvent.class)) + .addEntryData(new VariableStringEntryData("permission message", null, true)) .addEntryData(new KeyValueEntryData<List<String>>("aliases", new ArrayList<>(), true) { private final Pattern pattern = Pattern.compile("\\s*,\\s*/?"); @@ -131,9 +131,9 @@ protected Integer getValue(String value) { } }) .addEntryData(new LiteralEntryData<>("cooldown", null, true, Timespan.class)) - .addEntryData(new VariableStringEntryData("cooldown message", null, true, ScriptCommandEvent.class)) + .addEntryData(new VariableStringEntryData("cooldown message", null, true)) .addEntry("cooldown bypass", null, true) - .addEntryData(new VariableStringEntryData("cooldown storage", null, true, StringMode.VARIABLE_NAME, ScriptCommandEvent.class)) + .addEntryData(new VariableStringEntryData("cooldown storage", null, true, StringMode.VARIABLE_NAME)) .addSection("trigger", false) .unexpectedEntryMessage(key -> "Unexpected entry '" + key + "'. Check that it's spelled correctly, and ensure that you have put all code into a trigger." @@ -152,7 +152,6 @@ public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResu } @Override - @SuppressWarnings("unchecked") public boolean load() { getParser().setCurrentEvent("command", ScriptCommandEvent.class); @@ -272,8 +271,8 @@ public boolean load() { if (permissionMessage != null && permission.isEmpty()) Skript.warning("command /" + command + " has a permission message set, but not a permission"); - List<String> aliases = (List<String>) entryContainer.get("aliases", true); - int executableBy = (Integer) entryContainer.get("executable by", true); + List<String> aliases = entryContainer.get("aliases", List.class,true); + int executableBy = entryContainer.get("executable by", Integer.class, true); Timespan cooldown = entryContainer.getOptional("cooldown", Timespan.class, false); VariableString cooldownMessage = entryContainer.getOptional("cooldown message", VariableString.class, false); diff --git a/src/main/java/ch/njol/skript/structures/StructOptions.java b/src/main/java/ch/njol/skript/structures/StructOptions.java index 574592e5c96..3f414fb59bd 100644 --- a/src/main/java/ch/njol/skript/structures/StructOptions.java +++ b/src/main/java/ch/njol/skript/structures/StructOptions.java @@ -32,6 +32,7 @@ import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.entry.EntryContainer; +import org.skriptlang.skript.lang.script.Script; import org.skriptlang.skript.lang.script.ScriptData; import org.skriptlang.skript.lang.structure.Structure; @@ -75,20 +76,16 @@ public class StructOptions extends Structure { public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult, EntryContainer entryContainer) { SectionNode node = entryContainer.getSource(); node.convertToEntries(-1); - - OptionsData optionsData = new OptionsData(); - loadOptions(node, "", optionsData.options); - getParser().getCurrentScript().addData(optionsData); - + loadOptions(node, "", getParser().getCurrentScript().getData(OptionsData.class, OptionsData::new).options); return true; } private void loadOptions(SectionNode sectionNode, String prefix, Map<String, String> options) { - for (Node n : sectionNode) { - if (n instanceof EntryNode) { - options.put(prefix + n.getKey(), ((EntryNode) n).getValue()); - } else if (n instanceof SectionNode) { - loadOptions((SectionNode) n, prefix + n.getKey() + ".", options); + for (Node node : sectionNode) { + if (node instanceof EntryNode) { + options.put(prefix + node.getKey(), ((EntryNode) node).getValue()); + } else if (node instanceof SectionNode) { + loadOptions((SectionNode) node, prefix + node.getKey() + ".", options); } else { Skript.error("Invalid line in options"); } @@ -111,7 +108,7 @@ public Priority getPriority() { } @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { return "options"; } diff --git a/src/main/java/org/skriptlang/skript/lang/entry/EntryContainer.java b/src/main/java/org/skriptlang/skript/lang/entry/EntryContainer.java index bc277094db6..c613b7fbffc 100644 --- a/src/main/java/org/skriptlang/skript/lang/entry/EntryContainer.java +++ b/src/main/java/org/skriptlang/skript/lang/entry/EntryContainer.java @@ -89,11 +89,11 @@ public List<Node> getUnhandledNodes() { * @return The entry's value. * @throws RuntimeException If the entry's value is null, or if it is not of the expected type. */ - public <T> T get(String key, Class<T> expectedType, boolean useDefaultValue) { - T parsed = getOptional(key, expectedType, useDefaultValue); - if (parsed == null) + public <E, R extends E> R get(String key, Class<E> expectedType, boolean useDefaultValue) { + R value = getOptional(key, expectedType, useDefaultValue); + if (value == null) throw new RuntimeException("Null value for asserted non-null value"); - return parsed; + return value; } /** @@ -124,13 +124,13 @@ public Object get(String key, boolean useDefaultValue) { */ @Nullable @SuppressWarnings("unchecked") - public <T> T getOptional(String key, Class<T> expectedType, boolean useDefaultValue) { + public <E, R extends E> R getOptional(String key, Class<E> expectedType, boolean useDefaultValue) { Object parsed = getOptional(key, useDefaultValue); if (parsed == null) return null; if (!expectedType.isInstance(parsed)) throw new RuntimeException("Expected entry with key '" + key + "' to be '" + expectedType + "', but got '" + parsed.getClass() + "'"); - return (T) parsed; + return (R) parsed; } /** diff --git a/src/main/java/org/skriptlang/skript/lang/entry/util/ExpressionEntryData.java b/src/main/java/org/skriptlang/skript/lang/entry/util/ExpressionEntryData.java index 7dbd560731b..e707d3337d3 100644 --- a/src/main/java/org/skriptlang/skript/lang/entry/util/ExpressionEntryData.java +++ b/src/main/java/org/skriptlang/skript/lang/entry/util/ExpressionEntryData.java @@ -21,74 +21,63 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ParseContext; import ch.njol.skript.lang.SkriptParser; -import ch.njol.skript.lang.parser.ParserInstance; -import org.skriptlang.skript.lang.entry.KeyValueEntryData; -import ch.njol.util.Kleenean; -import org.bukkit.event.Event; +import ch.njol.skript.localization.Message; +import ch.njol.skript.log.ErrorQuality; +import ch.njol.skript.log.ParseLogHandler; import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.entry.KeyValueEntryData; /** * A type of {@link KeyValueEntryData} designed to parse its value as an {@link Expression}. * This data <b>CAN</b> return null if expression parsing fails. + * Note that it <b>will</b> print an error. */ public class ExpressionEntryData<T> extends KeyValueEntryData<Expression<? extends T>> { + private static final Message M_IS = new Message("is"); + private final Class<T> returnType; private final int flags; - - private final Class<? extends Event>[] events; /** * @param returnType The expected return type of the matched expression. - * @param events Events to be present during parsing and Trigger execution. - * This allows the usage of event-restricted syntax and event-values. - * @see ParserInstance#setCurrentEvents(Class[]) */ - @SafeVarargs public ExpressionEntryData( String key, @Nullable Expression<T> defaultValue, boolean optional, - Class<T> returnType, Class<? extends Event>... events + Class<T> returnType ) { - this(key, defaultValue, optional, returnType, SkriptParser.ALL_FLAGS, events); + this(key, defaultValue, optional, returnType, SkriptParser.ALL_FLAGS); } /** * @param returnType The expected return type of the matched expression. * @param flags Parsing flags. See {@link SkriptParser#SkriptParser(String, int, ParseContext)} * javadoc for more details. - * @param events Events to be present during parsing and Trigger execution. - * This allows the usage of event-restricted syntax and event-values. - * @see ParserInstance#setCurrentEvents(Class[]) */ - @SafeVarargs public ExpressionEntryData( String key, @Nullable Expression<T> defaultValue, boolean optional, - Class<T> returnType, int flags, Class<? extends Event>... events + Class<T> returnType, int flags ) { super(key, defaultValue, optional); this.returnType = returnType; this.flags = flags; - this.events = events; } @Override @Nullable @SuppressWarnings("unchecked") protected Expression<? extends T> getValue(String value) { - ParserInstance parser = ParserInstance.get(); - - Class<? extends Event>[] oldEvents = parser.getCurrentEvents(); - Kleenean oldHasDelayBefore = parser.getHasDelayBefore(); - - parser.setCurrentEvents(events); - parser.setHasDelayBefore(Kleenean.FALSE); - - Expression<? extends T> expression = new SkriptParser(value, flags, ParseContext.DEFAULT).parseExpression(returnType); - - parser.setCurrentEvents(oldEvents); - parser.setHasDelayBefore(oldHasDelayBefore); - + Expression<? extends T> expression; + try (ParseLogHandler log = new ParseLogHandler()) { + expression = new SkriptParser(value, flags, ParseContext.DEFAULT) + .parseExpression(returnType); + if (expression == null) // print an error if it couldn't parse + log.printError( + "'" + value + "' " + M_IS + " " + SkriptParser.notOfType(returnType), + ErrorQuality.NOT_AN_EXPRESSION + ); + } return expression; } diff --git a/src/main/java/org/skriptlang/skript/lang/entry/util/LiteralEntryData.java b/src/main/java/org/skriptlang/skript/lang/entry/util/LiteralEntryData.java index 9b48a19e341..43e9932dc81 100644 --- a/src/main/java/org/skriptlang/skript/lang/entry/util/LiteralEntryData.java +++ b/src/main/java/org/skriptlang/skript/lang/entry/util/LiteralEntryData.java @@ -38,7 +38,10 @@ public class LiteralEntryData<T> extends KeyValueEntryData<T> { /** * @param type The type to parse the value into. */ - public LiteralEntryData(String key, @Nullable T defaultValue, boolean optional, Class<T> type) { + public LiteralEntryData( + String key, @Nullable T defaultValue, boolean optional, + Class<T> type + ) { super(key, defaultValue, optional); this.type = type; } diff --git a/src/main/java/org/skriptlang/skript/lang/entry/util/TriggerEntryData.java b/src/main/java/org/skriptlang/skript/lang/entry/util/TriggerEntryData.java index b0a856e3756..a445dc438a4 100644 --- a/src/main/java/org/skriptlang/skript/lang/entry/util/TriggerEntryData.java +++ b/src/main/java/org/skriptlang/skript/lang/entry/util/TriggerEntryData.java @@ -32,49 +32,25 @@ /** * An entry data class designed to take a {@link SectionNode} and parse it into a Trigger. - * Events specified during construction *should* be used when the Trigger is executed. * This data will <b>NEVER</b> return null. * @see SectionEntryData */ public class TriggerEntryData extends EntryData<Trigger> { - private final Class<? extends Event>[] events; - - /** - * @param events Events to be present during parsing and Trigger execution. - * This allows the usage of event-restricted syntax and event-values. - * @see ParserInstance#setCurrentEvents(Class[]) - */ - @SafeVarargs - public TriggerEntryData( - String key, @Nullable Trigger defaultValue, boolean optional, - Class<? extends Event>... events - ) { + public TriggerEntryData(String key, @Nullable Trigger defaultValue, boolean optional) { super(key, defaultValue, optional); - this.events = events; } @Nullable @Override public Trigger getValue(Node node) { assert node instanceof SectionNode; - - ParserInstance parser = ParserInstance.get(); - - Class<? extends Event>[] oldEvents = parser.getCurrentEvents(); - Kleenean oldHasDelayBefore = parser.getHasDelayBefore(); - - parser.setCurrentEvents(events); - parser.setHasDelayBefore(Kleenean.FALSE); - - Trigger trigger = new Trigger( - parser.getCurrentScript(), "entry with key: " + getKey(), new SimpleEvent(), ScriptLoader.loadItems((SectionNode) node) + return new Trigger( + ParserInstance.get().getCurrentScript(), + "entry with key: " + getKey(), + new SimpleEvent(), + ScriptLoader.loadItems((SectionNode) node) ); - - parser.setCurrentEvents(oldEvents); - parser.setHasDelayBefore(oldHasDelayBefore); - - return trigger; } @Override diff --git a/src/main/java/org/skriptlang/skript/lang/entry/util/VariableStringEntryData.java b/src/main/java/org/skriptlang/skript/lang/entry/util/VariableStringEntryData.java index 57264fd0393..6a61e3642fa 100644 --- a/src/main/java/org/skriptlang/skript/lang/entry/util/VariableStringEntryData.java +++ b/src/main/java/org/skriptlang/skript/lang/entry/util/VariableStringEntryData.java @@ -36,58 +36,34 @@ public class VariableStringEntryData extends KeyValueEntryData<VariableString> { private final StringMode stringMode; - private final Class<? extends Event>[] events; - /** - * @param events Events to be present during parsing and Trigger execution. - * This allows the usage of event-restricted syntax and event-values. - * @see ParserInstance#setCurrentEvents(Class[]) + * Uses {@link StringMode#MESSAGE} as the default string mode. + * @see #VariableStringEntryData(String, VariableString, boolean, StringMode) */ - @SafeVarargs public VariableStringEntryData( - String key, @Nullable VariableString defaultValue, boolean optional, - Class<? extends Event>... events + String key, @Nullable VariableString defaultValue, boolean optional ) { - this(key, defaultValue, optional, StringMode.MESSAGE, events); + this(key, defaultValue, optional, StringMode.MESSAGE); } /** * @param stringMode Sets <i>how</i> to parse the string (e.g. as a variable, message, etc.). - * @param events Events to be present during parsing and Trigger execution. - * This allows the usage of event-restricted syntax and event-values. - * @see ParserInstance#setCurrentEvents(Class[]) */ - @SafeVarargs public VariableStringEntryData( String key, @Nullable VariableString defaultValue, boolean optional, - StringMode stringMode, Class<? extends Event>... events + StringMode stringMode ) { super(key, defaultValue, optional); this.stringMode = stringMode; - this.events = events; } @Override @Nullable protected VariableString getValue(String value) { - ParserInstance parser = ParserInstance.get(); - - Class<? extends Event>[] oldEvents = parser.getCurrentEvents(); - Kleenean oldHasDelayBefore = parser.getHasDelayBefore(); - - parser.setCurrentEvents(events); - parser.setHasDelayBefore(Kleenean.FALSE); - // Double up quotations outside of expressions if (stringMode != StringMode.VARIABLE_NAME) value = VariableString.quote(value); - - VariableString variableString = VariableString.newInstance(value, stringMode); - - parser.setCurrentEvents(oldEvents); - parser.setHasDelayBefore(oldHasDelayBefore); - - return variableString; + return VariableString.newInstance(value, stringMode); } } diff --git a/src/main/java/org/skriptlang/skript/lang/script/Script.java b/src/main/java/org/skriptlang/skript/lang/script/Script.java index 888f53d71c5..ed3b1920474 100644 --- a/src/main/java/org/skriptlang/skript/lang/script/Script.java +++ b/src/main/java/org/skriptlang/skript/lang/script/Script.java @@ -103,33 +103,41 @@ public boolean suppressesWarning(ScriptWarning warning) { private final Map<Class<? extends ScriptData>, ScriptData> scriptData = new ConcurrentHashMap<>(5); /** - * Clears the data stored for this script. - */ - public void clearData() { - scriptData.clear(); - } - - /** + * <b>This API is experimental and subject to change.</b> * Adds new ScriptData to this Script's data map. * @param data The data to add. */ + @ApiStatus.Experimental public void addData(ScriptData data) { scriptData.put(data.getClass(), data); } /** + * <b>This API is experimental and subject to change.</b> * Removes the ScriptData matching the specified data type. * @param dataType The type of the data to remove. */ + @ApiStatus.Experimental public void removeData(Class<? extends ScriptData> dataType) { scriptData.remove(dataType); } /** + * <b>This API is experimental and subject to change.</b> + * Clears the data stored for this script. + */ + @ApiStatus.Experimental + public void clearData() { + scriptData.clear(); + } + + /** + * <b>This API is experimental and subject to change.</b> * A method to obtain ScriptData matching the specified data type. * @param dataType The class representing the ScriptData to obtain. * @return ScriptData found matching the provided class, or null if no data is present. */ + @ApiStatus.Experimental @Nullable @SuppressWarnings("unchecked") public <Type extends ScriptData> Type getData(Class<Type> dataType) { @@ -137,12 +145,14 @@ public <Type extends ScriptData> Type getData(Class<Type> dataType) { } /** + * <b>This API is experimental and subject to change.</b> * A method that always obtains ScriptData matching the specified data type. * By using the mapping supplier, it will also add ScriptData of the provided type if it is not already present. * @param dataType The class representing the ScriptData to obtain. * @param mapper A supplier to create ScriptData of the provided type if such ScriptData is not already present. * @return Existing ScriptData found matching the provided class, or new data provided by the mapping function. */ + @ApiStatus.Experimental @SuppressWarnings("unchecked") public <Value extends ScriptData> Value getData(Class<? extends Value> dataType, Supplier<Value> mapper) { return (Value) scriptData.computeIfAbsent(dataType, clazz -> mapper.get()); @@ -153,42 +163,52 @@ public <Value extends ScriptData> Value getData(Class<? extends Value> dataType, private final Set<ScriptEvent> eventHandlers = new HashSet<>(5); /** + * <b>This API is experimental and subject to change.</b> * Adds the provided event to this Script. * @param event The event to add. */ + @ApiStatus.Experimental public void registerEvent(ScriptEvent event) { eventHandlers.add(event); } /** + * <b>This API is experimental and subject to change.</b> * Adds the provided event to this Script. * @param eventType The type of event being added. This is useful for registering the event through lambdas. * @param event The event to add. */ + @ApiStatus.Experimental public <T extends ScriptEvent> void registerEvent(Class<T> eventType, T event) { eventHandlers.add(event); } /** + * <b>This API is experimental and subject to change.</b> * Removes the provided event from this Script. * @param event The event to remove. */ + @ApiStatus.Experimental public void unregisterEvent(ScriptEvent event) { eventHandlers.remove(event); } /** + * <b>This API is experimental and subject to change.</b> * @return An unmodifiable set of all events. */ + @ApiStatus.Experimental @Unmodifiable public Set<ScriptEvent> getEvents() { return Collections.unmodifiableSet(eventHandlers); } /** + * <b>This API is experimental and subject to change.</b> * @param type The type of events to get. * @return An unmodifiable set of all events of the specified type. */ + @ApiStatus.Experimental @Unmodifiable @SuppressWarnings("unchecked") public <T extends ScriptEvent> Set<T> getEvents(Class<T> type) { diff --git a/src/main/java/org/skriptlang/skript/lang/structure/Structure.java b/src/main/java/org/skriptlang/skript/lang/structure/Structure.java index 1a787c366a8..17de96e446c 100644 --- a/src/main/java/org/skriptlang/skript/lang/structure/Structure.java +++ b/src/main/java/org/skriptlang/skript/lang/structure/Structure.java @@ -130,15 +130,14 @@ public boolean preLoad() { /** * The second phase of Structure loading. - * During this phase, Structures are loaded script by script. - * The order they are loaded in for each script is based on the Structure's priority. + * During this phase, all Structures across all loading scripts are loaded with respect to their priorities. * @return Whether loading was successful. An error should be printed prior to returning false to specify the cause. */ public abstract boolean load(); /** * The third and final phase of Structure loading. - * The loading order and method is the same as {@link #load()}. + * During this phase, all Structures across all loading scripts are loaded with respect to their priorities. * This method is primarily designed for Structures that wish to execute actions after * most other Structures have finished loading. * @return Whether postLoading was successful. An error should be printed prior to returning false to specify the cause. From 3f0885313dad667828eb79b7184f6feadc41261c Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Sat, 9 Sep 2023 10:43:04 +0300 Subject: [PATCH 428/619] =?UTF-8?q?=F0=9F=8D=92=20[2.7]=20Fixes=20an=20exc?= =?UTF-8?q?eption=20from=20being=20thrown=20on=20async=20events=20(#5980)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes an exception from being thrown on async events (#5699) Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Co-authored-by: Moderocky <admin@moderocky.com> --- .../ch/njol/skript/SkriptEventHandler.java | 68 ++++++++++++------- .../java/ch/njol/skript/lang/SkriptEvent.java | 7 ++ 2 files changed, 51 insertions(+), 24 deletions(-) diff --git a/src/main/java/ch/njol/skript/SkriptEventHandler.java b/src/main/java/ch/njol/skript/SkriptEventHandler.java index 144eca18721..28eaff9b23c 100644 --- a/src/main/java/ch/njol/skript/SkriptEventHandler.java +++ b/src/main/java/ch/njol/skript/SkriptEventHandler.java @@ -18,11 +18,17 @@ */ package ch.njol.skript; -import ch.njol.skript.lang.SkriptEvent; -import ch.njol.skript.lang.Trigger; -import ch.njol.skript.timings.SkriptTimings; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.Multimap; +import java.lang.ref.WeakReference; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.stream.Collectors; + import org.bukkit.Bukkit; import org.bukkit.event.Cancellable; import org.bukkit.event.Event; @@ -36,16 +42,13 @@ import org.bukkit.plugin.RegisteredListener; import org.eclipse.jdt.annotation.Nullable; -import java.lang.ref.WeakReference; -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.stream.Collectors; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; + +import ch.njol.skript.lang.SkriptEvent; +import ch.njol.skript.lang.Trigger; +import ch.njol.skript.timings.SkriptTimings; +import ch.njol.skript.util.Task; public final class SkriptEventHandler { @@ -110,12 +113,14 @@ private static List<Trigger> getTriggers(Class<? extends Event> event) { */ private static void check(Event event, EventPriority priority) { List<Trigger> triggers = getTriggers(event.getClass()); + if (triggers.isEmpty()) + return; if (Skript.logVeryHigh()) { boolean hasTrigger = false; for (Trigger trigger : triggers) { SkriptEvent triggerEvent = trigger.getEvent(); - if (triggerEvent.getEventPriority() == priority && triggerEvent.check(event)) { + if (triggerEvent.getEventPriority() == priority && Boolean.TRUE.equals(Task.callSync(() -> triggerEvent.check(event)))) { hasTrigger = true; break; } @@ -137,16 +142,31 @@ private static void check(Event event, EventPriority priority) { for (Trigger trigger : triggers) { SkriptEvent triggerEvent = trigger.getEvent(); - if (triggerEvent.getEventPriority() != priority || !triggerEvent.check(event)) + if (triggerEvent.getEventPriority() != priority) continue; - logTriggerStart(trigger); - Object timing = SkriptTimings.start(trigger.getDebugLabel()); - - trigger.execute(event); - - SkriptTimings.stop(timing); - logTriggerEnd(trigger); + // these methods need to be run on whatever thread the trigger is + Runnable execute = () -> { + logTriggerStart(trigger); + Object timing = SkriptTimings.start(trigger.getDebugLabel()); + trigger.execute(event); + SkriptTimings.stop(timing); + logTriggerEnd(trigger); + }; + + if (trigger.getEvent().canExecuteAsynchronously()) { + // check should be performed on the main thread + if (Boolean.FALSE.equals(Task.callSync(() -> triggerEvent.check(event)))) + continue; + execute.run(); + } else { // Ensure main thread + Task.callSync(() -> { + if (!triggerEvent.check(event)) + return null; + execute.run(); + return null; // we don't care about a return value + }); + } } logEventEnd(); diff --git a/src/main/java/ch/njol/skript/lang/SkriptEvent.java b/src/main/java/ch/njol/skript/lang/SkriptEvent.java index e150e8699d8..1d89b2c6aa9 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptEvent.java +++ b/src/main/java/ch/njol/skript/lang/SkriptEvent.java @@ -216,6 +216,13 @@ public boolean isEventPrioritySupported() { return true; } + /** + * Override this method to allow Skript to not force synchronization. + */ + public boolean canExecuteAsynchronously() { + return false; + } + /** * Fixes patterns in event by modifying every {@link ch.njol.skript.patterns.TypePatternElement} * to be nullable. From 0ea98d4b69d1939914891427b29c84eb19771b29 Mon Sep 17 00:00:00 2001 From: Shane Bee <shanebolenback@me.com> Date: Mon, 11 Sep 2023 01:19:47 -0700 Subject: [PATCH 429/619] EffPotion - fix potion duration issue (#5992) --- src/main/java/ch/njol/skript/effects/EffPotion.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffPotion.java b/src/main/java/ch/njol/skript/effects/EffPotion.java index 64b07bb0be4..46abcb3ecf4 100644 --- a/src/main/java/ch/njol/skript/effects/EffPotion.java +++ b/src/main/java/ch/njol/skript/effects/EffPotion.java @@ -101,7 +101,7 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye potions = (Expression<PotionEffectType>) exprs[0]; tier = (Expression<Number>) exprs[1]; entities = (Expression<LivingEntity>) exprs[2]; - if (infinite) + if (!infinite) duration = (Expression<Timespan>) exprs[3]; } return true; @@ -125,7 +125,7 @@ protected void execute(Event event) { Timespan timespan = this.duration.getSingle(event); if (timespan == null) return; - duration = (int) Math.max(timespan.getTicks_i(), Integer.MAX_VALUE); + duration = (int) Math.min(timespan.getTicks_i(), Integer.MAX_VALUE); } for (LivingEntity entity : entities.getArray(event)) { for (PotionEffectType potionEffectType : potionEffectTypes) { From b722cc3c09086b207e808641bbd32c8913427baa Mon Sep 17 00:00:00 2001 From: MihirKohli <55236890+MihirKohli@users.noreply.github.com> Date: Wed, 13 Sep 2023 18:36:46 +0530 Subject: [PATCH 430/619] Rename Slot classinfo name to Slot (#6001) --- src/main/java/ch/njol/skript/classes/data/SkriptClasses.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 184aa1d219c..55727c673a6 100644 --- a/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java @@ -479,7 +479,7 @@ public String toVariableNameString(final Direction o) { Classes.registerClass(new ClassInfo<>(Slot.class, "slot") .user("(inventory )?slots?") - .name("Inventory Slot") + .name("Slot") .description("Represents a single slot of an <a href='#inventory'>inventory</a>. " + "Notable slots are the <a href='./expressions.html#ExprArmorSlot'>armour slots</a> and <a href='./expressions/#ExprFurnaceSlot'>furnace slots</a>. ", "The most important property that distinguishes a slot from an <a href='#itemstack'>item</a> is its ability to be changed, e.g. it can be set, deleted, enchanted, etc. " + From 869a5de1e8955bcb0b2020b00e0d01c1d2bd9bcf Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Fri, 15 Sep 2023 01:19:22 +0300 Subject: [PATCH 431/619] =?UTF-8?q?=F0=9F=9B=A0=20Fix=20keep=20inv/exp=20a?= =?UTF-8?q?nd=20other=20bugs=20related=20to=20death=20event=20(#5658)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../njol/skript/effects/EffKeepInventory.java | 25 +++++++++++-------- .../ch/njol/skript/effects/EffRespawn.java | 4 +-- .../ch/njol/skript/expressions/ExprLevel.java | 5 ++-- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffKeepInventory.java b/src/main/java/ch/njol/skript/effects/EffKeepInventory.java index 4c86bac1bcf..34cf29e85c6 100644 --- a/src/main/java/ch/njol/skript/effects/EffKeepInventory.java +++ b/src/main/java/ch/njol/skript/effects/EffKeepInventory.java @@ -19,6 +19,7 @@ package ch.njol.skript.effects; import org.bukkit.event.Event; +import org.bukkit.event.entity.EntityDeathEvent; import org.bukkit.event.entity.PlayerDeathEvent; import org.eclipse.jdt.annotation.Nullable; @@ -35,17 +36,19 @@ @Name("Keep Inventory / Experience") @Description("Keeps the inventory or/and experiences of the dead player in a death event.") -@Examples({"on death of a player:", +@Examples({ + "on death of a player:", "\tif the victim is an op:", - "\t\tkeep the inventory and experiences"}) + "\t\tkeep the inventory and experiences" +}) @Since("2.4") @Events("death") public class EffKeepInventory extends Effect { static { Skript.registerEffect(EffKeepInventory.class, - "keep [the] (inventory|items) [(1¦and [e]xp[erience][s] [point[s]])]", - "keep [the] [e]xp[erience][s] [point[s]] [(1¦and (inventory|items))]"); + "keep [the] (inventory|items) [(1:and [e]xp[erience][s] [point[s]])]", + "keep [the] [e]xp[erience][s] [point[s]] [(1:and (inventory|items))]"); } private boolean keepItems, keepExp; @@ -54,7 +57,7 @@ public class EffKeepInventory extends Effect { public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { keepItems = matchedPattern == 0 || parseResult.mark == 1; keepExp = matchedPattern == 1 || parseResult.mark == 1; - if (!getParser().isCurrentEvent(PlayerDeathEvent.class)) { + if (!getParser().isCurrentEvent(EntityDeathEvent.class)) { Skript.error("The keep inventory/experience effect can't be used outside of a death event"); return false; } @@ -66,18 +69,18 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye } @Override - protected void execute(Event e) { - if (e instanceof PlayerDeathEvent) { - PlayerDeathEvent event = (PlayerDeathEvent) e; + protected void execute(Event event) { + if (event instanceof PlayerDeathEvent) { + PlayerDeathEvent deathEvent = (PlayerDeathEvent) event; if (keepItems) - event.setKeepInventory(true); + deathEvent.setKeepInventory(true); if (keepExp) - event.setKeepLevel(true); + deathEvent.setKeepLevel(true); } } @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { if (keepItems && !keepExp) return "keep the inventory"; else diff --git a/src/main/java/ch/njol/skript/effects/EffRespawn.java b/src/main/java/ch/njol/skript/effects/EffRespawn.java index eb21064e4a8..de67c2b07ad 100644 --- a/src/main/java/ch/njol/skript/effects/EffRespawn.java +++ b/src/main/java/ch/njol/skript/effects/EffRespawn.java @@ -20,7 +20,7 @@ import org.bukkit.entity.Player; import org.bukkit.event.Event; -import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.entity.EntityDeathEvent; import org.bukkit.event.player.PlayerRespawnEvent; import org.bukkit.scheduler.BukkitRunnable; import org.eclipse.jdt.annotation.Nullable; @@ -62,7 +62,7 @@ public boolean init(final Expression<?>[] exprs, final int matchedPattern, final players = (Expression<Player>) exprs[0]; // Force a delay before respawning the player if we're in the death event and there isn't already a delay // Unexpected behavior may occur if we don't do this - forceDelay = getParser().isCurrentEvent(PlayerDeathEvent.class) && isDelayed.isFalse(); + forceDelay = getParser().isCurrentEvent(EntityDeathEvent.class) && isDelayed.isFalse(); return true; } diff --git a/src/main/java/ch/njol/skript/expressions/ExprLevel.java b/src/main/java/ch/njol/skript/expressions/ExprLevel.java index d55550616e2..74795570377 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprLevel.java +++ b/src/main/java/ch/njol/skript/expressions/ExprLevel.java @@ -20,6 +20,7 @@ import org.bukkit.entity.Player; import org.bukkit.event.Event; +import org.bukkit.event.entity.EntityDeathEvent; import org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.event.player.PlayerLevelChangeEvent; import org.bukkit.event.player.PlayerRespawnEvent; @@ -80,11 +81,11 @@ public Class<?>[] acceptChange(final ChangeMode mode) { Skript.error("Cannot change a player's level in a respawn event. Add a delay of 1 tick or change the 'new level' in a death event."); return null; } - if (getParser().isCurrentEvent(PlayerDeathEvent.class) && getTime() == 0 && getExpr().isDefault() && !getParser().getHasDelayBefore().isTrue()) { + if (getParser().isCurrentEvent(EntityDeathEvent.class) && getTime() == 0 && getExpr().isDefault() && !getParser().getHasDelayBefore().isTrue()) { Skript.warning("Changing the player's level in a death event will change the player's level before he dies. " + "Use either 'past level of player' or 'new level of player' to clearly state whether to change the level before or after he dies."); } - if (getTime() == -1 && !getParser().isCurrentEvent(PlayerDeathEvent.class)) + if (getTime() == -1 && !getParser().isCurrentEvent(EntityDeathEvent.class)) return null; return new Class[] {Number.class}; } From bd134d05aaed31f7460bfc94ff9ad0acc94642cf Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Thu, 14 Sep 2023 22:28:29 -0600 Subject: [PATCH 432/619] Fix event-item not being present in book sign (#5877) --- .../java/ch/njol/skript/classes/data/BukkitEventValues.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 d0645163c47..aa90764daa4 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -1380,7 +1380,7 @@ public ItemStack get(PlayerEditBookEvent event) { book.setItemMeta(event.getNewBookMeta()); return book; } - }, EventValues.TIME_FUTURE); + }, EventValues.TIME_NOW); EventValues.registerEventValue(PlayerEditBookEvent.class, String[].class, new Getter<String[], PlayerEditBookEvent>() { @Override public String[] get(PlayerEditBookEvent event) { @@ -1392,7 +1392,7 @@ public String[] get(PlayerEditBookEvent event) { public String[] get(PlayerEditBookEvent event) { return event.getNewBookMeta().getPages().toArray(new String[0]); } - }, EventValues.TIME_FUTURE); + }, EventValues.TIME_NOW); //ItemDespawnEvent EventValues.registerEventValue(ItemDespawnEvent.class, Item.class, new Getter<Item, ItemDespawnEvent>() { @Override From 7a2c11301abd7f3579f4d3386250eb5e6ee9e006 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Fri, 15 Sep 2023 15:40:39 +0300 Subject: [PATCH 433/619] =?UTF-8?q?=E2=9A=92=20[2.7]=20EffPotion=20-=20fix?= =?UTF-8?q?=20potion=20duration=20issue=20(#5997)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/ch/njol/skript/effects/EffPotion.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffPotion.java b/src/main/java/ch/njol/skript/effects/EffPotion.java index 64b07bb0be4..46abcb3ecf4 100644 --- a/src/main/java/ch/njol/skript/effects/EffPotion.java +++ b/src/main/java/ch/njol/skript/effects/EffPotion.java @@ -101,7 +101,7 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye potions = (Expression<PotionEffectType>) exprs[0]; tier = (Expression<Number>) exprs[1]; entities = (Expression<LivingEntity>) exprs[2]; - if (infinite) + if (!infinite) duration = (Expression<Timespan>) exprs[3]; } return true; @@ -125,7 +125,7 @@ protected void execute(Event event) { Timespan timespan = this.duration.getSingle(event); if (timespan == null) return; - duration = (int) Math.max(timespan.getTicks_i(), Integer.MAX_VALUE); + duration = (int) Math.min(timespan.getTicks_i(), Integer.MAX_VALUE); } for (LivingEntity entity : entities.getArray(event)) { for (PotionEffectType potionEffectType : potionEffectTypes) { From 264b36eaf3a4a5c838c170d13c920cb06e8c8e8f Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Fri, 15 Sep 2023 15:51:44 +0300 Subject: [PATCH 434/619] =?UTF-8?q?=F0=9F=9B=A0=20[2.7]=20Fix=20keep=20inv?= =?UTF-8?q?/exp=20and=20other=20bugs=20related=20to=20death=20event=20(#56?= =?UTF-8?q?58)=20(#6003)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../njol/skript/effects/EffKeepInventory.java | 25 +++++++++++-------- .../ch/njol/skript/effects/EffRespawn.java | 4 +-- .../ch/njol/skript/expressions/ExprLevel.java | 5 ++-- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffKeepInventory.java b/src/main/java/ch/njol/skript/effects/EffKeepInventory.java index 4c86bac1bcf..34cf29e85c6 100644 --- a/src/main/java/ch/njol/skript/effects/EffKeepInventory.java +++ b/src/main/java/ch/njol/skript/effects/EffKeepInventory.java @@ -19,6 +19,7 @@ package ch.njol.skript.effects; import org.bukkit.event.Event; +import org.bukkit.event.entity.EntityDeathEvent; import org.bukkit.event.entity.PlayerDeathEvent; import org.eclipse.jdt.annotation.Nullable; @@ -35,17 +36,19 @@ @Name("Keep Inventory / Experience") @Description("Keeps the inventory or/and experiences of the dead player in a death event.") -@Examples({"on death of a player:", +@Examples({ + "on death of a player:", "\tif the victim is an op:", - "\t\tkeep the inventory and experiences"}) + "\t\tkeep the inventory and experiences" +}) @Since("2.4") @Events("death") public class EffKeepInventory extends Effect { static { Skript.registerEffect(EffKeepInventory.class, - "keep [the] (inventory|items) [(1¦and [e]xp[erience][s] [point[s]])]", - "keep [the] [e]xp[erience][s] [point[s]] [(1¦and (inventory|items))]"); + "keep [the] (inventory|items) [(1:and [e]xp[erience][s] [point[s]])]", + "keep [the] [e]xp[erience][s] [point[s]] [(1:and (inventory|items))]"); } private boolean keepItems, keepExp; @@ -54,7 +57,7 @@ public class EffKeepInventory extends Effect { public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { keepItems = matchedPattern == 0 || parseResult.mark == 1; keepExp = matchedPattern == 1 || parseResult.mark == 1; - if (!getParser().isCurrentEvent(PlayerDeathEvent.class)) { + if (!getParser().isCurrentEvent(EntityDeathEvent.class)) { Skript.error("The keep inventory/experience effect can't be used outside of a death event"); return false; } @@ -66,18 +69,18 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye } @Override - protected void execute(Event e) { - if (e instanceof PlayerDeathEvent) { - PlayerDeathEvent event = (PlayerDeathEvent) e; + protected void execute(Event event) { + if (event instanceof PlayerDeathEvent) { + PlayerDeathEvent deathEvent = (PlayerDeathEvent) event; if (keepItems) - event.setKeepInventory(true); + deathEvent.setKeepInventory(true); if (keepExp) - event.setKeepLevel(true); + deathEvent.setKeepLevel(true); } } @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { if (keepItems && !keepExp) return "keep the inventory"; else diff --git a/src/main/java/ch/njol/skript/effects/EffRespawn.java b/src/main/java/ch/njol/skript/effects/EffRespawn.java index eb21064e4a8..de67c2b07ad 100644 --- a/src/main/java/ch/njol/skript/effects/EffRespawn.java +++ b/src/main/java/ch/njol/skript/effects/EffRespawn.java @@ -20,7 +20,7 @@ import org.bukkit.entity.Player; import org.bukkit.event.Event; -import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.entity.EntityDeathEvent; import org.bukkit.event.player.PlayerRespawnEvent; import org.bukkit.scheduler.BukkitRunnable; import org.eclipse.jdt.annotation.Nullable; @@ -62,7 +62,7 @@ public boolean init(final Expression<?>[] exprs, final int matchedPattern, final players = (Expression<Player>) exprs[0]; // Force a delay before respawning the player if we're in the death event and there isn't already a delay // Unexpected behavior may occur if we don't do this - forceDelay = getParser().isCurrentEvent(PlayerDeathEvent.class) && isDelayed.isFalse(); + forceDelay = getParser().isCurrentEvent(EntityDeathEvent.class) && isDelayed.isFalse(); return true; } diff --git a/src/main/java/ch/njol/skript/expressions/ExprLevel.java b/src/main/java/ch/njol/skript/expressions/ExprLevel.java index d55550616e2..74795570377 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprLevel.java +++ b/src/main/java/ch/njol/skript/expressions/ExprLevel.java @@ -20,6 +20,7 @@ import org.bukkit.entity.Player; import org.bukkit.event.Event; +import org.bukkit.event.entity.EntityDeathEvent; import org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.event.player.PlayerLevelChangeEvent; import org.bukkit.event.player.PlayerRespawnEvent; @@ -80,11 +81,11 @@ public Class<?>[] acceptChange(final ChangeMode mode) { Skript.error("Cannot change a player's level in a respawn event. Add a delay of 1 tick or change the 'new level' in a death event."); return null; } - if (getParser().isCurrentEvent(PlayerDeathEvent.class) && getTime() == 0 && getExpr().isDefault() && !getParser().getHasDelayBefore().isTrue()) { + if (getParser().isCurrentEvent(EntityDeathEvent.class) && getTime() == 0 && getExpr().isDefault() && !getParser().getHasDelayBefore().isTrue()) { Skript.warning("Changing the player's level in a death event will change the player's level before he dies. " + "Use either 'past level of player' or 'new level of player' to clearly state whether to change the level before or after he dies."); } - if (getTime() == -1 && !getParser().isCurrentEvent(PlayerDeathEvent.class)) + if (getTime() == -1 && !getParser().isCurrentEvent(EntityDeathEvent.class)) return null; return new Class[] {Number.class}; } From 7c81d22c8930abec5a0edbd0d1800d96bed517a6 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Fri, 15 Sep 2023 09:00:18 -0600 Subject: [PATCH 435/619] Fix book sign event item (#6004) --- .../java/ch/njol/skript/classes/data/BukkitEventValues.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 b628c56a887..c47d5ab2885 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -1367,7 +1367,7 @@ public ItemStack get(PlayerEditBookEvent event) { book.setItemMeta(event.getNewBookMeta()); return book; } - }, EventValues.TIME_FUTURE); + }, EventValues.TIME_NOW); EventValues.registerEventValue(PlayerEditBookEvent.class, String[].class, new Getter<String[], PlayerEditBookEvent>() { @Override public String[] get(PlayerEditBookEvent event) { @@ -1379,7 +1379,7 @@ public String[] get(PlayerEditBookEvent event) { public String[] get(PlayerEditBookEvent event) { return event.getNewBookMeta().getPages().toArray(new String[0]); } - }, EventValues.TIME_FUTURE); + }, EventValues.TIME_NOW); //ItemDespawnEvent EventValues.registerEventValue(ItemDespawnEvent.class, Item.class, new Getter<Item, ItemDespawnEvent>() { @Override From cd731f114242f248a08724c4737f0c648ac1ef35 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Sun, 17 Sep 2023 09:58:17 +0300 Subject: [PATCH 436/619] =?UTF-8?q?=E2=9A=92=EF=B8=8F=20Add=20`NoDoc`=20an?= =?UTF-8?q?notation=20to=20test=20elements=20(#6006)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/ch/njol/skript/test/runner/CondMethodExists.java | 2 ++ .../java/ch/njol/skript/test/runner/CondMinecraftVersion.java | 2 ++ src/main/java/ch/njol/skript/test/runner/EvtTestCase.java | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/main/java/ch/njol/skript/test/runner/CondMethodExists.java b/src/main/java/ch/njol/skript/test/runner/CondMethodExists.java index 0735ea997ab..3103d6379ba 100644 --- a/src/main/java/ch/njol/skript/test/runner/CondMethodExists.java +++ b/src/main/java/ch/njol/skript/test/runner/CondMethodExists.java @@ -19,6 +19,7 @@ package ch.njol.skript.test.runner; import ch.njol.skript.conditions.base.PropertyCondition; +import ch.njol.skript.doc.NoDoc; import org.apache.commons.lang.StringUtils; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @@ -42,6 +43,7 @@ @Description("Checks if a method exists") @Examples("if method \"org.bukkit.Bukkit#getPluginCommand(java.lang.String)") @Since("2.7") +@NoDoc public class CondMethodExists extends PropertyCondition<String> { private final static Pattern SIGNATURE_PATTERN = Pattern.compile("(?<class>.+)#(?<name>.+)\\((?<params>.*)\\)"); diff --git a/src/main/java/ch/njol/skript/test/runner/CondMinecraftVersion.java b/src/main/java/ch/njol/skript/test/runner/CondMinecraftVersion.java index bc5f1436349..24a47479a49 100644 --- a/src/main/java/ch/njol/skript/test/runner/CondMinecraftVersion.java +++ b/src/main/java/ch/njol/skript/test/runner/CondMinecraftVersion.java @@ -18,6 +18,7 @@ */ package ch.njol.skript.test.runner; +import ch.njol.skript.doc.NoDoc; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @@ -36,6 +37,7 @@ @Description("Checks if current Minecraft version is given version or newer.") @Examples("running minecraft \"1.14\"") @Since("2.5") +@NoDoc public class CondMinecraftVersion extends Condition { static { diff --git a/src/main/java/ch/njol/skript/test/runner/EvtTestCase.java b/src/main/java/ch/njol/skript/test/runner/EvtTestCase.java index cec92f1dadc..0fc0a79a8c2 100644 --- a/src/main/java/ch/njol/skript/test/runner/EvtTestCase.java +++ b/src/main/java/ch/njol/skript/test/runner/EvtTestCase.java @@ -18,6 +18,7 @@ */ package ch.njol.skript.test.runner; +import ch.njol.skript.doc.NoDoc; import org.bukkit.Location; import org.bukkit.block.Block; import org.bukkit.event.Event; @@ -32,6 +33,7 @@ import ch.njol.skript.registrations.EventValues; import ch.njol.skript.util.Getter; +@NoDoc public class EvtTestCase extends SkriptEvent { static { From 27e20771355b377fb4a4f1d613aa14886fe2083f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 17 Sep 2023 08:37:43 +0100 Subject: [PATCH 437/619] Bump org.easymock:easymock from 5.1.0 to 5.2.0 (#5970) Bumps [org.easymock:easymock](https://github.com/easymock/easymock) from 5.1.0 to 5.2.0. - [Release notes](https://github.com/easymock/easymock/releases) - [Changelog](https://github.com/easymock/easymock/blob/master/ReleaseNotes.md) - [Commits](https://github.com/easymock/easymock/compare/easymock-5.1.0...easymock-5.2.0) --- updated-dependencies: - dependency-name: org.easymock:easymock dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Co-authored-by: Moderocky <admin@moderocky.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 9a525ff1e1d..15505bcbd3e 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.1.0' + testShadow group: 'org.easymock', name: 'easymock', version: '5.2.0' } task checkAliases { From 07cc9d67ba729a98d43433107e86db4ddbf05816 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Sun, 17 Sep 2023 03:23:56 -0600 Subject: [PATCH 438/619] Add GitHub's gradle wrapper checker for security (#6002) Add GitHub's new gradle wrapper checker for security --- .github/workflows/java-17-builds.yml | 2 ++ .github/workflows/java-8-builds.yml | 2 ++ .github/workflows/junit-17-builds.yml | 2 ++ .github/workflows/junit-8-builds.yml | 2 ++ .github/workflows/repo.yml | 2 ++ 5 files changed, 10 insertions(+) diff --git a/.github/workflows/java-17-builds.yml b/.github/workflows/java-17-builds.yml index fa432f7f17d..36ddf386257 100644 --- a/.github/workflows/java-17-builds.yml +++ b/.github/workflows/java-17-builds.yml @@ -15,6 +15,8 @@ jobs: - uses: actions/checkout@v4 with: submodules: recursive + - name: validate gradle wrapper + uses: gradle/wrapper-validation-action@v1 - name: Set up JDK 17 uses: actions/setup-java@v3 with: diff --git a/.github/workflows/java-8-builds.yml b/.github/workflows/java-8-builds.yml index 4a36e74a44b..50b42dc0491 100644 --- a/.github/workflows/java-8-builds.yml +++ b/.github/workflows/java-8-builds.yml @@ -15,6 +15,8 @@ jobs: - uses: actions/checkout@v4 with: submodules: recursive + - name: validate gradle wrapper + uses: gradle/wrapper-validation-action@v1 - name: Set up JDK 17 uses: actions/setup-java@v3 with: diff --git a/.github/workflows/junit-17-builds.yml b/.github/workflows/junit-17-builds.yml index 69c5b73f27a..64cd93ec47f 100644 --- a/.github/workflows/junit-17-builds.yml +++ b/.github/workflows/junit-17-builds.yml @@ -15,6 +15,8 @@ jobs: - uses: actions/checkout@v4 with: submodules: recursive + - name: validate gradle wrapper + uses: gradle/wrapper-validation-action@v1 - name: Set up JDK 17 uses: actions/setup-java@v3 with: diff --git a/.github/workflows/junit-8-builds.yml b/.github/workflows/junit-8-builds.yml index 3aa98f554a7..cafa1c0f501 100644 --- a/.github/workflows/junit-8-builds.yml +++ b/.github/workflows/junit-8-builds.yml @@ -15,6 +15,8 @@ jobs: - uses: actions/checkout@v4 with: submodules: recursive + - name: validate gradle wrapper + uses: gradle/wrapper-validation-action@v1 - name: Set up JDK 17 uses: actions/setup-java@v3 with: diff --git a/.github/workflows/repo.yml b/.github/workflows/repo.yml index 3b6640cc765..2499cf67f74 100644 --- a/.github/workflows/repo.yml +++ b/.github/workflows/repo.yml @@ -11,6 +11,8 @@ jobs: - uses: actions/checkout@v4 with: submodules: recursive + - name: validate gradle wrapper + uses: gradle/wrapper-validation-action@v1 - name: Set up JDK 17 uses: actions/setup-java@v3 with: From d4c0a68010fb2558a92eb3cc02edde518f0e4601 Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Sun, 17 Sep 2023 14:38:05 +0300 Subject: [PATCH 439/619] Fix ExprRandomNumber using a method from Java 17 (#6022) --- src/main/java/ch/njol/skript/expressions/ExprRandomNumber.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprRandomNumber.java b/src/main/java/ch/njol/skript/expressions/ExprRandomNumber.java index d774cbd9848..ba3853ad311 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprRandomNumber.java +++ b/src/main/java/ch/njol/skript/expressions/ExprRandomNumber.java @@ -88,7 +88,7 @@ protected Number[] get(Event event) { return new Long[] {sup}; return new Long[0]; } - return new Long[] {random.nextLong(inf, sup + 1)}; + return new Long[] {inf + Math2.mod(random.nextLong(), sup - inf + 1)}; } return new Double[] {min + random.nextDouble() * (max - min)}; From e04806843413bb52a5f06a0755cf69b400cf5fc1 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Sun, 17 Sep 2023 04:46:06 -0700 Subject: [PATCH 440/619] Fix changing remaining time of command cooldown (#6021) Update ScriptCommand.java Co-authored-by: Moderocky <admin@moderocky.com> --- .../ch/njol/skript/command/ScriptCommand.java | 86 +++++++++++-------- 1 file changed, 49 insertions(+), 37 deletions(-) diff --git a/src/main/java/ch/njol/skript/command/ScriptCommand.java b/src/main/java/ch/njol/skript/command/ScriptCommand.java index 93e8cad25b0..be7942e3ca7 100644 --- a/src/main/java/ch/njol/skript/command/ScriptCommand.java +++ b/src/main/java/ch/njol/skript/command/ScriptCommand.java @@ -18,44 +18,11 @@ */ package ch.njol.skript.command; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.UUID; - import ch.njol.skript.ScriptLoader; -import ch.njol.skript.config.SectionNode; -import org.skriptlang.skript.lang.script.Script; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.OfflinePlayer; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.command.PluginCommand; -import org.bukkit.command.SimpleCommandMap; -import org.bukkit.command.TabExecutor; -import org.bukkit.entity.Player; -import org.bukkit.event.Event; -import org.bukkit.help.GenericCommandHelpTopic; -import org.bukkit.help.HelpMap; -import org.bukkit.help.HelpTopic; -import org.bukkit.help.HelpTopicComparator; -import org.bukkit.help.IndexHelpTopic; -import org.bukkit.plugin.Plugin; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.SkriptConfig; import ch.njol.skript.command.Commands.CommandAliasHelpTopic; +import ch.njol.skript.config.SectionNode; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser; import ch.njol.skript.lang.Trigger; @@ -77,6 +44,39 @@ import ch.njol.skript.variables.Variables; import ch.njol.util.StringUtils; import ch.njol.util.Validate; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.PluginCommand; +import org.bukkit.command.SimpleCommandMap; +import org.bukkit.command.TabExecutor; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.help.GenericCommandHelpTopic; +import org.bukkit.help.HelpMap; +import org.bukkit.help.HelpTopic; +import org.bukkit.help.HelpTopicComparator; +import org.bukkit.help.IndexHelpTopic; +import org.bukkit.plugin.Plugin; +import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.script.Script; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; /** * This class is used for user-defined commands. @@ -274,9 +274,21 @@ public boolean execute(final CommandSender sender, final String commandLabel, fi } Runnable runnable = () -> { + // save previous last usage date to check if the execution has set the last usage date + Date previousLastUsage = null; + if (sender instanceof Player) + previousLastUsage = getLastUsage(((Player) sender).getUniqueId(), event); + + // execute the command - may modify the last usage date execute2(event, sender, commandLabel, rest); - if (sender instanceof Player && !event.isCooldownCancelled()) - setLastUsage(((Player) sender).getUniqueId(), event, new Date()); + + if (sender instanceof Player && !event.isCooldownCancelled()) { + Date lastUsage = getLastUsage(((Player) sender).getUniqueId(), event); + // check if the execution has set the last usage date + // if not, set it to the current date. if it has, we leave it alone so as not to affect the remaining/elapsed time (#5862) + if (Objects.equals(lastUsage, previousLastUsage)) + setLastUsage(((Player) sender).getUniqueId(), event, new Date()); + } }; if (Bukkit.isPrimaryThread()) { runnable.run(); @@ -510,7 +522,7 @@ public void setRemainingMilliseconds(UUID uuid, Event event, long milliseconds) assert cooldown != null; long cooldownMs = cooldown.getMilliSeconds(); if (milliseconds > cooldownMs) - throw new IllegalArgumentException("Remaining time may not be longer than the cooldown"); + milliseconds = cooldownMs; setElapsedMilliSeconds(uuid, event, cooldownMs - milliseconds); } From 5b591053c9b0fe274e948f2a3d51c0ccac4694bd Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Sun, 17 Sep 2023 04:46:31 -0700 Subject: [PATCH 441/619] Fix NPE with invalid attributes and clean up ExprEntityAttribute (#5978) * Avoid NPE and clean up class * Update ExprEntityAttribute.java * Update src/main/java/ch/njol/skript/expressions/ExprEntityAttribute.java Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> * Update src/main/java/ch/njol/skript/expressions/ExprEntityAttribute.java --------- Co-authored-by: Moderocky <admin@moderocky.com> Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> --- .../expressions/ExprEntityAttribute.java | 78 ++++++++++--------- 1 file changed, 41 insertions(+), 37 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprEntityAttribute.java b/src/main/java/ch/njol/skript/expressions/ExprEntityAttribute.java index c7c81de0d5c..ddb4b3b27ad 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprEntityAttribute.java +++ b/src/main/java/ch/njol/skript/expressions/ExprEntityAttribute.java @@ -18,16 +18,6 @@ */ package ch.njol.skript.expressions; -import org.bukkit.attribute.Attribute; - -import java.util.stream.Stream; - -import org.bukkit.attribute.Attributable; -import org.bukkit.attribute.AttributeInstance; -import org.bukkit.entity.Entity; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; @@ -40,21 +30,34 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; +import org.bukkit.attribute.Attributable; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; +import org.bukkit.entity.Entity; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.Objects; +import java.util.stream.Stream; @Name("Entity Attribute") -@Description({"The numerical value of an entity's particular attribute.", - "Note that the movement speed attribute cannot be reliably used for players. For that purpose, use the speed expression instead.", - "Resetting an entity's attribute is only available in Minecraft 1.11 and above."}) -@Examples({"on damage of player:", - " send \"You are wounded!\"", - " set victim's attack speed attribute to 2"}) +@Description({ + "The numerical value of an entity's particular attribute.", + "Note that the movement speed attribute cannot be reliably used for players. For that purpose, use the speed expression instead.", + "Resetting an entity's attribute is only available in Minecraft 1.11 and above." +}) +@Examples({ + "on damage of player:", + "\tsend \"You are wounded!\" to victim", + "\tset victim's attack speed attribute to 2" +}) @Since("2.5, 2.6.1 (final attribute value)") public class ExprEntityAttribute extends PropertyExpression<Entity, Number> { static { Skript.registerExpression(ExprEntityAttribute.class, Number.class, ExpressionType.COMBINED, - "[the] %attributetype% [(1¦(total|final|modified))] attribute [value] of %entities%", - "%entities%'[s] %attributetype% [(1¦(total|final|modified))] attribute [value]"); + "[the] %attributetype% [(1:(total|final|modified))] attribute [value] of %entities%", + "%entities%'[s] %attributetype% [(1:(total|final|modified))] attribute [value]"); } @Nullable @@ -72,10 +75,11 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") - protected Number[] get(Event e, Entity[] entities) { - Attribute a = attributes.getSingle(e); + protected Number[] get(Event event, Entity[] entities) { + Attribute attribute = attributes.getSingle(event); return Stream.of(entities) - .map(ent -> getAttribute(ent, a)) + .map(ent -> getAttribute(ent, attribute)) + .filter(Objects::nonNull) .map(att -> withModifiers ? att.getValue() : att.getBaseValue()) .toArray(Number[]::new); } @@ -90,27 +94,27 @@ public Class<?>[] acceptChange(ChangeMode mode) { @Override @SuppressWarnings("null") - public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { - Attribute a = attributes.getSingle(e); - double d = delta == null ? 0 : ((Number) delta[0]).doubleValue(); - for (Entity entity : getExpr().getArray(e)) { - AttributeInstance ai = getAttribute(entity, a); - if(ai != null) { + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + Attribute attribute = attributes.getSingle(event); + double deltaValue = delta == null ? 0 : ((Number) delta[0]).doubleValue(); + for (Entity entity : getExpr().getArray(event)) { + AttributeInstance instance = getAttribute(entity, attribute); + if (instance != null) { switch(mode) { case ADD: - ai.setBaseValue(ai.getBaseValue() + d); + instance.setBaseValue(instance.getBaseValue() + deltaValue); break; case SET: - ai.setBaseValue(d); + instance.setBaseValue(deltaValue); break; case DELETE: - ai.setBaseValue(0); + instance.setBaseValue(0); break; case RESET: - ai.setBaseValue(ai.getDefaultValue()); + instance.setBaseValue(instance.getDefaultValue()); break; case REMOVE: - ai.setBaseValue(ai.getBaseValue() - d); + instance.setBaseValue(instance.getBaseValue() - deltaValue); break; case REMOVE_ALL: assert false; @@ -126,14 +130,14 @@ public Class<? extends Number> getReturnType() { @Override @SuppressWarnings("null") - public String toString(@Nullable Event e, boolean debug) { - return "entity " + getExpr().toString(e, debug) + "'s " + (attributes == null ? "" : attributes.toString(e, debug)) + "attribute"; + public String toString(@Nullable Event event, boolean debug) { + return "entity " + getExpr().toString(event, debug) + "'s " + (attributes == null ? "" : attributes.toString(event, debug)) + "attribute"; } @Nullable - private static AttributeInstance getAttribute(Entity e, @Nullable Attribute a) { - if (a != null && e instanceof Attributable) { - return ((Attributable) e).getAttribute(a); + private static AttributeInstance getAttribute(Entity entity, @Nullable Attribute attribute) { + if (attribute != null && entity instanceof Attributable) { + return ((Attributable) entity).getAttribute(attribute); } return null; } From 4b78e985bf67527f8a4be41cd75ab81766eba2ff Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Sun, 17 Sep 2023 08:00:47 -0600 Subject: [PATCH 442/619] Bump version to 2.7.1 (#5993) Co-authored-by: Moderocky <admin@moderocky.com> --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 62d7b588c56..a8e8c966957 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ groupid=ch.njol name=skript -version=2.7.0 +version=2.7.1 jarName=Skript.jar testEnv=java17/paper-1.20.1 testEnvJavaVersion=17 From 66b08b8d4029b4cbc4f2b49b7620b201225f2f9c Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Sun, 17 Sep 2023 08:08:27 -0600 Subject: [PATCH 443/619] Fix cast throwing if existing variable for command storage exists (#5942) * Fix cast throwing if existing variable for command storage exists * Update src/main/java/ch/njol/skript/command/ScriptCommand.java Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> --------- Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Co-authored-by: Moderocky <admin@moderocky.com> --- src/main/java/ch/njol/skript/command/ScriptCommand.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/command/ScriptCommand.java b/src/main/java/ch/njol/skript/command/ScriptCommand.java index 93e8cad25b0..fdfb1364f17 100644 --- a/src/main/java/ch/njol/skript/command/ScriptCommand.java +++ b/src/main/java/ch/njol/skript/command/ScriptCommand.java @@ -474,7 +474,13 @@ public Date getLastUsage(UUID uuid, Event event) { } else { String name = getStorageVariableName(event); assert name != null; - return (Date) Variables.getVariable(name, null, false); + Object variable = Variables.getVariable(name, null, false); + if (!(variable instanceof Date)) { + Skript.warning("Variable {" + name + "} was not a date! You may be using this variable elsewhere. " + + "This warning is letting you know that this variable is now overridden for the command storage."); + return null; + } + return (Date) variable; } } From 3dfc2527de6927578deb90ef654315c000fe3997 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Sun, 17 Sep 2023 15:21:52 -0600 Subject: [PATCH 444/619] Allow and with for the title effect (#6010) * Allow and with for the title effect * apply changes --------- Co-authored-by: Moderocky <admin@moderocky.com> --- src/main/java/ch/njol/skript/effects/EffSendTitle.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffSendTitle.java b/src/main/java/ch/njol/skript/effects/EffSendTitle.java index d6746ad36db..29363bf1277 100644 --- a/src/main/java/ch/njol/skript/effects/EffSendTitle.java +++ b/src/main/java/ch/njol/skript/effects/EffSendTitle.java @@ -58,8 +58,8 @@ public class EffSendTitle extends Effect { static { if (TIME_SUPPORTED) Skript.registerEffect(EffSendTitle.class, - "send title %string% [with subtitle %-string%] [to %players%] [for %-timespan%] [with fade[(-| )]in %-timespan%] [(and|with) fade[(-| )]out %-timespan%]", - "send subtitle %string% [to %players%] [for %-timespan%] [with fade[(-| )]in %-timespan%] [(and|with) fade[(-| )]out %-timespan%]"); + "send title %string% [with subtitle %-string%] [to %players%] [for %-timespan%] [with fade[(-| )]in %-timespan%] [[and] [with] fade[(-| )]out %-timespan%]", + "send subtitle %string% [to %players%] [for %-timespan%] [with fade[(-| )]in %-timespan%] [[and] [with] fade[(-| )]out %-timespan%]"); else Skript.registerEffect(EffSendTitle.class, "send title %string% [with subtitle %-string%] [to %players%]", From da67d640ff2f4c1ea78c9dcbae071597908fc78f Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Mon, 18 Sep 2023 01:43:47 -0700 Subject: [PATCH 445/619] fix 3 stray INSERT VERSIONs from 2.7.0 (#6027) correct incorrect values --- src/main/java/ch/njol/skript/conditions/CondIsInfinite.java | 2 +- src/main/java/ch/njol/skript/effects/EffPotion.java | 2 +- src/main/java/ch/njol/skript/events/SimpleEvents.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/njol/skript/conditions/CondIsInfinite.java b/src/main/java/ch/njol/skript/conditions/CondIsInfinite.java index 98ed56c0570..725480bac1b 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsInfinite.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsInfinite.java @@ -31,7 +31,7 @@ @Name("Is Infinite") @Description("Checks whether potion effects are infinite.") @Examples("all of the active potion effects of the player are infinite") -@Since("INSERT VERSION") +@Since("2.7") public class CondIsInfinite extends PropertyCondition<PotionEffect> { static { diff --git a/src/main/java/ch/njol/skript/effects/EffPotion.java b/src/main/java/ch/njol/skript/effects/EffPotion.java index 46abcb3ecf4..4ebc19e8d9f 100644 --- a/src/main/java/ch/njol/skript/effects/EffPotion.java +++ b/src/main/java/ch/njol/skript/effects/EffPotion.java @@ -52,7 +52,7 @@ @Since( "2.0, 2.2-dev27 (ambient and particle-less potion effects), " + "2.5 (replacing existing effect), 2.5.2 (potion effects), " + - "INSERT VERSION (icon and infinite)" + "2.7 (icon and infinite)" ) public class EffPotion extends Effect { diff --git a/src/main/java/ch/njol/skript/events/SimpleEvents.java b/src/main/java/ch/njol/skript/events/SimpleEvents.java index bcb301489e1..376acae9d13 100644 --- a/src/main/java/ch/njol/skript/events/SimpleEvents.java +++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java @@ -718,7 +718,7 @@ public class SimpleEvents { "\t\tsend \"You can't drag your items here!\" to player", "\t\tcancel event" ) - .since("INSERT VERSION"); + .since("2.7"); } From 4b5579c6d6b5bb8c51584870c3848092d8573a27 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Mon, 18 Sep 2023 04:24:03 -0600 Subject: [PATCH 446/619] Add get all armour of living entities (#5456) * Add get all armour * Apply changes * Update src/main/java/ch/njol/skript/expressions/ExprArmorSlot.java Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> --------- Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Co-authored-by: Moderocky <admin@moderocky.com> --- .../skript/expressions/ExprArmorSlot.java | 88 ++++++++++++------- .../syntaxes/expressions/ExprArmorSlot.sk | 14 +++ 2 files changed, 68 insertions(+), 34 deletions(-) create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprArmorSlot.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprArmorSlot.java b/src/main/java/ch/njol/skript/expressions/ExprArmorSlot.java index 49f8f9c674c..09f3677eb14 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprArmorSlot.java +++ b/src/main/java/ch/njol/skript/expressions/ExprArmorSlot.java @@ -18,17 +18,21 @@ */ package ch.njol.skript.expressions; +import java.util.Arrays; import java.util.Locale; +import java.util.stream.Stream; import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Event; import org.bukkit.inventory.EntityEquipment; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Keywords; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; -import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.expressions.base.PropertyExpression; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.util.slot.EquipmentSlot; @@ -36,51 +40,67 @@ import ch.njol.skript.util.slot.Slot; import ch.njol.util.Kleenean; -/** - * @author Peter Güttinger - */ @Name("Armour Slot") -@Description("A part of a player's armour, i.e. the boots, leggings, chestplate or helmet.") -@Examples({"set chestplate of the player to a diamond chestplate", - "helmet of player is neither a helmet nor air # player is wearing a block, e.g. from another plugin"}) -@Since("1.0") -public class ExprArmorSlot extends SimplePropertyExpression<LivingEntity, Slot> { +@Description("Equipment of living entities, i.e. the boots, leggings, chestplate or helmet.") +@Examples({ + "set chestplate of the player to a diamond chestplate", + "helmet of player is neither a helmet nor air # player is wearing a block, e.g. from another plugin" +}) +@Keywords("armor") +@Since("1.0, INSERT VERSION (Armour)") +public class ExprArmorSlot extends PropertyExpression<LivingEntity, Slot> { + static { - register(ExprArmorSlot.class, Slot.class, "(0¦boot[s]|0¦shoe[s]|1¦leg[ging][s]|2¦chestplate[s]|3¦helm[et][s]) [(0¦item|4¦slot)]", "livingentities"); + register(ExprArmorSlot.class, Slot.class, "((:boots|:shoes|leggings:leg[ging]s|chestplate:chestplate[s]|helmet:helmet[s]) [(item|:slot)]|armour:armo[u]r[s])", "livingentities"); } - - @SuppressWarnings("null") + + @Nullable private EquipSlot slot; private boolean explicitSlot; - - private final static EquipSlot[] slots = {EquipSlot.BOOTS, EquipSlot.LEGGINGS, EquipSlot.CHESTPLATE, EquipSlot.HELMET}; - - @SuppressWarnings("null") + @Override - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { - super.init(exprs, matchedPattern, isDelayed, parseResult); - slot = slots[parseResult.mark & 3]; // 3 least significant bits determine armor type - explicitSlot = (parseResult.mark >>> 2) == 1; // User explicitly asked for SLOT, not item + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + slot = parseResult.hasTag("armour") ? null : EquipSlot.valueOf(parseResult.tags.get(0).toUpperCase(Locale.ENGLISH)); + explicitSlot = parseResult.hasTag("slot"); // User explicitly asked for SLOT, not item + setExpr((Expression<? extends LivingEntity>) exprs[0]); return true; } - - @Override - @Nullable - public Slot convert(final LivingEntity e) { - final EntityEquipment eq = e.getEquipment(); - if (eq == null) - return null; - return new EquipmentSlot(eq, slot, explicitSlot); - } - + @Override - protected String getPropertyName() { - return "" + slot.name().toLowerCase(Locale.ENGLISH); + protected Slot[] get(Event event, LivingEntity[] source) { + if (slot == null) { // All Armour + return Arrays.stream(source) + .map(LivingEntity::getEquipment) + .flatMap(equipment -> { + if (equipment == null) + return null; + return Stream.of( + new EquipmentSlot(equipment, EquipSlot.HELMET, explicitSlot), + new EquipmentSlot(equipment, EquipSlot.CHESTPLATE, explicitSlot), + new EquipmentSlot(equipment, EquipSlot.LEGGINGS, explicitSlot), + new EquipmentSlot(equipment, EquipSlot.BOOTS, explicitSlot) + ); + }) + .toArray(Slot[]::new); + } + + return get(source, entity -> { + EntityEquipment equipment = entity.getEquipment(); + if (equipment == null) + return null; + return new EquipmentSlot(equipment, slot, explicitSlot); + }); } - + @Override public Class<Slot> getReturnType() { return Slot.class; } - + + @Override + public String toString(@Nullable Event event, boolean debug) { + return slot == null ? "armour" : slot.name().toLowerCase(Locale.ENGLISH) + " of " + getExpr().toString(event, debug); + } + } diff --git a/src/test/skript/tests/syntaxes/expressions/ExprArmorSlot.sk b/src/test/skript/tests/syntaxes/expressions/ExprArmorSlot.sk new file mode 100644 index 00000000000..bb41dbd844a --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprArmorSlot.sk @@ -0,0 +1,14 @@ +test "armour slot": + spawn zombie at spawn of world "world": + set boots of event-entity to gold boots + assert boots of event-entity are gold boots with "Gold boots were not applied" + set leggings of event-entity to iron leggings + assert leggings of event-entity are iron leggings with "Iron leggings were not applied" + set chestplate of event-entity to diamond chestplate + assert chestplate of event-entity is diamond chestplate with "Diamond chestplate was not applied" + set helmet of event-entity to dirt block + assert helmet of event-entity is dirt block with "Dirt helmet was not applied" + assert armour of event-entity contains dirt block, diamond chestplate, iron leggings and gold boots with "Armour contents were not correct" + clear armour of event-entity + assert armour of event-entity does not contain dirt block, diamond chestplate, iron leggings and gold boots with "Failed to clear EquipmentSlots" + delete event-entity From 38bfa0aaf48e28252bb9855192852d2659742873 Mon Sep 17 00:00:00 2001 From: DelayedGaming <72163224+DelayedGaming@users.noreply.github.com> Date: Mon, 18 Sep 2023 18:32:39 +0800 Subject: [PATCH 447/619] Enhance Shearing Related Elements (#5571) * Enhance * Enhance (finish) * Enhance (finish x2) * Docs * Eclipse -> Jetbrains annotation * Requested change * Requested change * Requested change Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> * Camel case static final field * Requested change and minor correction on comment * Remove un-needed null warning suppression * Requested change + fix docs --------- Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Co-authored-by: Moderocky <admin@moderocky.com> --- .../njol/skript/conditions/CondIsSheared.java | 73 +++++++++++++++++ .../java/ch/njol/skript/effects/EffShear.java | 78 ++++++++++++------- 2 files changed, 125 insertions(+), 26 deletions(-) create mode 100644 src/main/java/ch/njol/skript/conditions/CondIsSheared.java diff --git a/src/main/java/ch/njol/skript/conditions/CondIsSheared.java b/src/main/java/ch/njol/skript/conditions/CondIsSheared.java new file mode 100644 index 00000000000..f667f292899 --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondIsSheared.java @@ -0,0 +1,73 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.conditions; + +import ch.njol.skript.Skript; +import ch.njol.skript.conditions.base.PropertyCondition; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import io.papermc.paper.entity.Shearable; +import org.bukkit.entity.Cow; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Sheep; +import org.bukkit.entity.Snowman; +import org.bukkit.event.entity.CreatureSpawnEvent; + +@Name("Entity Is Sheared") +@Description("Checks whether entities are sheared. This condition only works on cows, sheep and snowmen for versions below 1.19.4.") +@Examples({ + "if targeted entity of player is sheared:", + "\tsend \"This entity has nothing left to shear!\" to player" +}) +@Since("INSERT VERSION") +@RequiredPlugins("MC 1.13+ (cows, sheep & snowmen), Paper 1.19.4+ (all shearable entities)") +public class CondIsSheared extends PropertyCondition<LivingEntity> { + + private static final boolean INTERFACE_METHOD = Skript.classExists("io.papermc.paper.entity.Shearable"); + + static { + register(CondIsSheared.class, "(sheared|shorn)", "livingentities"); + } + + @Override + public boolean check(LivingEntity entity) { + if (entity instanceof Cow) { + return entity.getEntitySpawnReason() == CreatureSpawnEvent.SpawnReason.SHEARED; + } else if (INTERFACE_METHOD) { + if (!(entity instanceof Shearable)) { + return false; + } + return !((Shearable) entity).readyToBeSheared(); + } else if (entity instanceof Sheep) { + return ((Sheep) entity).isSheared(); + } else if (entity instanceof Snowman) { + return ((Snowman) entity).isDerp(); + } + return false; + } + + @Override + protected String getPropertyName() { + return "sheared"; + } + +} diff --git a/src/main/java/ch/njol/skript/effects/EffShear.java b/src/main/java/ch/njol/skript/effects/EffShear.java index 467aebeb70a..5bb6bf3d991 100644 --- a/src/main/java/ch/njol/skript/effects/EffShear.java +++ b/src/main/java/ch/njol/skript/effects/EffShear.java @@ -18,60 +18,86 @@ */ package ch.njol.skript.effects; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Sheep; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Effect; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; -/** - * @author Peter Güttinger - */ +import io.papermc.paper.entity.Shearable; + +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Sheep; +import org.bukkit.entity.Snowman; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; + @Name("Shear") -@Description("Shears or 'un-shears' a sheep. Please note that no wool is dropped, this only sets the 'sheared' state of the sheep.") -@Examples({"on rightclick on a sheep holding a sword:", - " shear the clicked sheep"}) -@Since("2.0") +@Description({ + "Shears or un-shears a shearable entity with drops by shearing and a 'sheared' sound. Using with 'force' will force this effect despite the entity's 'shear state'.", + "\nPlease note that..:", + "\n- If your server is not running with Paper 1.19.4 or higher, this effect will only change its 'shear state', and the 'force' effect is unavailable", + "\n- Force-shearing or un-shearing on a sheared mushroom cow is not possible" +}) +@Examples({ + "on rightclick on a sheep holding a sword:", + "\tshear the clicked sheep", + "\tchance of 10%", + "\tforce shear the clicked sheep" +}) +@Since("2.0 (cows, sheep & snowmen), INSERT VERSION (all shearable entities)") +@RequiredPlugins("Paper 1.19.4+ (all shearable entities)") public class EffShear extends Effect { + + private static final boolean INTERFACE_METHOD = Skript.classExists("io.papermc.paper.entity.Shearable"); + static { Skript.registerEffect(EffShear.class, - "shear %livingentities%", + (INTERFACE_METHOD ? "[:force] " : "") + "shear %livingentities%", "un[-]shear %livingentities%"); } - - @SuppressWarnings("null") - private Expression<LivingEntity> sheep; + + private Expression<LivingEntity> entity; + private boolean force; private boolean shear; - - @SuppressWarnings({"unchecked", "null"}) + @Override - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { - sheep = (Expression<LivingEntity>) exprs[0]; + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + entity = (Expression<LivingEntity>) exprs[0]; + force = parseResult.hasTag("force"); shear = matchedPattern == 0; return true; } @Override - protected void execute(final Event e) { - for (final LivingEntity en : sheep.getArray(e)) { - if (en instanceof Sheep) { - ((Sheep) en).setSheared(shear); + protected void execute(Event event) { + for (LivingEntity entity : entity.getArray(event)) { + if (shear && INTERFACE_METHOD) { + if (!(entity instanceof Shearable)) + continue; + Shearable shearable = ((Shearable) entity); + if (!force && !shearable.readyToBeSheared()) + continue; + shearable.shear(); + continue; + } + if (entity instanceof Sheep) { + ((Sheep) entity).setSheared(shear); + } else if (entity instanceof Snowman) { + ((Snowman) entity).setDerp(shear); } } } @Override - public String toString(final @Nullable Event e, final boolean debug) { - return (shear ? "" : "un") + "shear " + sheep.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return (shear ? "" : "un") + "shear " + entity.toString(event, debug); } } From d55e5a6dd8870a4e13d8c5007153785190e1de94 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Mon, 18 Sep 2023 04:39:29 -0600 Subject: [PATCH 448/619] Move documentation into its own folder (#5580) * Move documentation into it's own folder * Update src/main/java/ch/njol/skript/doc/Documentation.java Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> * Update src/main/java/ch/njol/skript/doc/Documentation.java Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> --------- Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Co-authored-by: Moderocky <admin@moderocky.com> --- src/main/java/ch/njol/skript/doc/Documentation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/doc/Documentation.java b/src/main/java/ch/njol/skript/doc/Documentation.java index 5da07aefb09..c7b9ebebbb1 100644 --- a/src/main/java/ch/njol/skript/doc/Documentation.java +++ b/src/main/java/ch/njol/skript/doc/Documentation.java @@ -60,7 +60,7 @@ public class Documentation { private static final Pattern CP_EMPTY_PARSE_MARKS_PATTERN = Pattern.compile("\\(\\)"); private static final Pattern CP_PARSE_TAGS_PATTERN = Pattern.compile("(?<=[(|\\[ ])[-a-zA-Z0-9!$#%^&*_+~=\"'<>?,.]*?:"); private static final Pattern CP_EXTRA_OPTIONAL_PATTERN = Pattern.compile("\\[\\(((\\w+? ?)+)\\)]"); - private static final File DOCS_TEMPLATE_DIRECTORY = new File(Skript.getInstance().getDataFolder(), "doc-templates"); + private static final File DOCS_TEMPLATE_DIRECTORY = new File(Skript.getInstance().getDataFolder(), "docs/templates"); private static final File DOCS_OUTPUT_DIRECTORY = new File(Skript.getInstance().getDataFolder(), "docs"); /** From 41349d1ccfd77812ff0bff4d3804b83b1c3313cd Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Mon, 18 Sep 2023 21:05:29 +0300 Subject: [PATCH 449/619] Allow 'continue' the continue outer loops (#6024) * Allow continuing outer loops * Requested Changes * Add tests --------- Co-authored-by: Moderocky <admin@moderocky.com> --- .../ch/njol/skript/effects/EffContinue.java | 40 +++++++++++++++---- .../tests/syntaxes/effects/EffContinue.sk | 8 ++++ 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffContinue.java b/src/main/java/ch/njol/skript/effects/EffContinue.java index 026ca015d86..4b4c4b3682c 100644 --- a/src/main/java/ch/njol/skript/effects/EffContinue.java +++ b/src/main/java/ch/njol/skript/effects/EffContinue.java @@ -25,17 +25,20 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Effect; import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.LoopSection; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.TriggerItem; import ch.njol.util.Kleenean; +import ch.njol.util.StringUtils; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; import java.util.List; @Name("Continue") -@Description("Immediately moves the (while) loop on to the next iteration.") +@Description("Moves the loop to the next iteration. You may also continue an outer loop from an inner one." + + " The loops are labelled from 1 until the current loop, starting with the outermost one.") @Examples({ "# Broadcast online moderators", "loop all players:", @@ -52,26 +55,45 @@ "\t\tcontinue # only print when counter is 1, 2, 3, 5 or 10", "\tbroadcast \"Game starting in %{_counter}% second(s)\"", }) -@Since("2.2-dev37, 2.7 (while loops)") +@Since("2.2-dev37, 2.7 (while loops), INSERT VERSION (outer loops)") public class EffContinue extends Effect { static { - Skript.registerEffect(EffContinue.class, "continue [loop]"); + Skript.registerEffect(EffContinue.class, + "continue [this loop|[the] [current] loop]", + "continue [the] %*integer%(st|nd|rd|th) loop" + ); } @SuppressWarnings("NotNullFieldNotInitialized") private LoopSection loop; + @SuppressWarnings("NotNullFieldNotInitialized") + private List<LoopSection> innerLoops; @Override + @SuppressWarnings("unchecked") public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { List<LoopSection> currentLoops = getParser().getCurrentSections(LoopSection.class); - - if (currentLoops.isEmpty()) { - Skript.error("The 'continue' effect may only be used in while and regular loops"); + + int size = currentLoops.size(); + if (size == 0) { + Skript.error("The 'continue' effect may only be used in loops"); + return false; + } + + int level = matchedPattern == 0 ? size : ((Literal<Integer>) exprs[0]).getSingle(); + if (level < 1) { + Skript.error("Can't continue the " + StringUtils.fancyOrderNumber(level) + " loop"); + return false; + } + if (level > size) { + Skript.error("Can't continue the " + StringUtils.fancyOrderNumber(level) + " loop as there " + + (size == 1 ? "is only 1 loop" : "are only " + size + " loops") + " present"); return false; } - - loop = currentLoops.get(currentLoops.size() - 1); + + loop = currentLoops.get(level - 1); + innerLoops = currentLoops.subList(level, size); return true; } @@ -83,6 +105,8 @@ protected void execute(Event event) { @Override @Nullable protected TriggerItem walk(Event event) { + for (LoopSection loop : innerLoops) + loop.exit(event); return loop; } diff --git a/src/test/skript/tests/syntaxes/effects/EffContinue.sk b/src/test/skript/tests/syntaxes/effects/EffContinue.sk index 2a4685a8877..83804bc908f 100644 --- a/src/test/skript/tests/syntaxes/effects/EffContinue.sk +++ b/src/test/skript/tests/syntaxes/effects/EffContinue.sk @@ -9,3 +9,11 @@ test "continue effect": if {_i} is equal to 5: continue assert {_i} is not 5 with "continue in while failed" + loop integers from 1 to 10: + continue this loop if loop-value is 5 + assert loop-value is not 5 with "leveled continue failed ##1" + loop integers from 11 to 20: + continue 2nd loop if loop-value-2 is 15 + assert loop-value-2 is not 15 with "leveled continue failed ##2" + continue 1st loop if loop-value-1 is 10 + assert loop-value is not 10 with "leveled continue failed ##3" From a27101f8026d029685bb7fd3d2b736af03ae143d Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Tue, 19 Sep 2023 17:53:49 -0400 Subject: [PATCH 450/619] Fix Documentation Actions on dev/patch (#6042) --- .github/workflows/cleanup-docs.yml | 39 +++++++++++ .../workflows/docs/generate-docs/action.yml | 64 ++++++++++++++++++- .github/workflows/docs/push-docs/action.yml | 14 ++-- .github/workflows/docs/setup-docs/action.yml | 1 - .github/workflows/nightly-docs.yml | 15 ++++- .github/workflows/release-docs.yml | 4 +- build.gradle | 31 ++++++--- .../ch/njol/skript/doc/Documentation.java | 2 +- 8 files changed, 146 insertions(+), 24 deletions(-) create mode 100644 .github/workflows/cleanup-docs.yml diff --git a/.github/workflows/cleanup-docs.yml b/.github/workflows/cleanup-docs.yml new file mode 100644 index 00000000000..750bab83213 --- /dev/null +++ b/.github/workflows/cleanup-docs.yml @@ -0,0 +1,39 @@ +name: Cleanup nightly documentation +on: delete +jobs: + cleanup-nightly-docs: + if: github.event.ref_type == 'branch' + runs-on: ubuntu-latest + steps: + - name: Configure workflow + id: configuration + env: + DELETED_BRANCH: ${{ github.event.ref }} + run: | + BRANCH_NAME="${DELETED_BRANCH#refs/*/}" + echo "BRANCH_NAME=${BRANCH_NAME}" >> $GITHUB_OUTPUT + echo "DOCS_OUTPUT_DIR=${GITHUB_WORKSPACE}/skript-docs/docs/nightly/${BRANCH_NAME}" >> $GITHUB_OUTPUT + echo "DOCS_REPO_DIR=${GITHUB_WORKSPACE}/skript-docs" >> $GITHUB_OUTPUT + - name: Checkout Skript + uses: actions/checkout@v3 + with: + ref: ${{ github.event.repository.default_branch }} + 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: Cleanup nightly documentation + env: + DOCS_OUTPUT_DIR: ${{ steps.configuration.outputs.DOCS_OUTPUT_DIR }} + run: | + rm -rf ${DOCS_OUTPUT_DIR} || true + - name: Push nightly documentation cleanup + uses: ./skript/.github/workflows/docs/push-docs + with: + docs_repo_dir: ${{ steps.configuration.outputs.DOCS_REPO_DIR }} + git_name: Nightly Docs Bot + git_email: nightlydocs@skriptlang.org + git_commit_message: "Delete ${{ steps.configuration.outputs.BRANCH_NAME }} branch nightly docs" diff --git a/.github/workflows/docs/generate-docs/action.yml b/.github/workflows/docs/generate-docs/action.yml index f84023a60df..84cc2153f86 100644 --- a/.github/workflows/docs/generate-docs/action.yml +++ b/.github/workflows/docs/generate-docs/action.yml @@ -18,24 +18,82 @@ inputs: required: false default: false type: boolean + cleanup_pattern: + description: "A pattern designating which files to delete when cleaning the documentation output directory" + required: false + default: "*" + type: string + +outputs: + DOCS_CHANGED: + description: "Whether or not the documentation has changed since the last push" + value: ${{ steps.generate.outputs.DOCS_CHANGED }} runs: using: 'composite' steps: - name: generate-docs + id: generate shell: bash env: DOCS_OUTPUT_DIR: ${{ inputs.docs_output_dir }} DOCS_REPO_DIR: ${{ inputs.docs_repo_dir }} SKRIPT_REPO_DIR: ${{ inputs.skript_repo_dir }} IS_RELEASE: ${{ inputs.is_release }} + CLEANUP_PATTERN: ${{ inputs.cleanup_pattern }} run: | - export SKRIPT_DOCS_TEMPLATE_DIR=${DOCS_REPO_DIR}/doc-templates - export SKRIPT_DOCS_OUTPUT_DIR=${DOCS_OUTPUT_DIR}/ + replace_in_directory() { + find $1 -type f -exec sed -i -e "s/$2/$3/g" {} \; + } + + # this should be replaced with a more reliable jq command, + # but it can't be right now because docs.json is actually not valid json. + get_skript_version_of_directory() { + grep skriptVersion "$1/docs.json" | cut -d\" -f 4 + } + + if [ -d "${DOCS_REPO_DIR}/docs/templates" ] + then + export SKRIPT_DOCS_TEMPLATE_DIR=${DOCS_REPO_DIR}/docs/templates + else + export SKRIPT_DOCS_TEMPLATE_DIR=${DOCS_REPO_DIR}/doc-templates + fi + + export SKRIPT_DOCS_OUTPUT_DIR=/tmp/generated-docs + cd $SKRIPT_REPO_DIR if [[ "${IS_RELEASE}" == "true" ]]; then ./gradlew genReleaseDocs releaseJavadoc else ./gradlew genNightlyDocs javadoc fi - cp -a "./build/docs/javadoc/." "${DOCS_OUTPUT_DIR}/javadocs" + + if [ -d "${DOCS_OUTPUT_DIR}" ]; then + mkdir -p "${SKRIPT_DOCS_OUTPUT_DIR}/javadocs" && cp -a "./build/docs/javadoc/." "$_" + + mkdir -p "/tmp/normalized-output-docs" && cp -a "${DOCS_OUTPUT_DIR}/." "$_" + mkdir -p "/tmp/normalized-generated-docs" && cp -a "${SKRIPT_DOCS_OUTPUT_DIR}/." "$_" + + output_skript_version=$(get_skript_version_of_directory "/tmp/normalized-output-docs") + generated_skript_version=$(get_skript_version_of_directory "/tmp/normalized-generated-docs") + + replace_in_directory "/tmp/normalized-output-docs" "${output_skript_version}" "Skript" + replace_in_directory "/tmp/normalized-generated-docs" "${generated_skript_version}" "Skript" + + diff -qbr /tmp/normalized-output-docs /tmp/normalized-generated-docs || diff_exit_code=$? + # If diff exits with exit code 1, that means there were some differences + if [[ ${diff_exit_code} -eq 1 ]]; then + echo "DOCS_CHANGED=true" >> $GITHUB_OUTPUT + echo "Documentation has changed since last push" + else + echo "Documentation hasn't changed since last push" + fi + else + echo "DOCS_CHANGED=true" >> $GITHUB_OUTPUT + echo "No existing documentation found" + fi + + rm -rf ${DOCS_OUTPUT_DIR}/${CLEANUP_PATTERN} || true + mkdir -p "${DOCS_OUTPUT_DIR}/" && cp -a "${SKRIPT_DOCS_OUTPUT_DIR}/." "$_" + + diff --git a/.github/workflows/docs/push-docs/action.yml b/.github/workflows/docs/push-docs/action.yml index bcc296a85c3..477a4c46aa4 100644 --- a/.github/workflows/docs/push-docs/action.yml +++ b/.github/workflows/docs/push-docs/action.yml @@ -1,10 +1,6 @@ -name: Generate documentation +name: Push documentation inputs: - docs_output_dir: - description: "The directory to generate the documentation into" - required: true - type: string docs_repo_dir: description: "The skript-docs repository directory" required: true @@ -38,4 +34,10 @@ runs: git config user.email "${GIT_EMAIL}" git add -A git commit -m "${GIT_COMMIT_MESSAGE}" || (echo "Nothing to push!" && exit 0) - git push origin main + # Attempt rebasing and pushing 5 times in case another job pushes before us + for i in 1 2 3 4 5 + do + git pull --rebase -X theirs origin main + git push origin main && break + sleep 5 + done diff --git a/.github/workflows/docs/setup-docs/action.yml b/.github/workflows/docs/setup-docs/action.yml index 1feedcd8f10..cd6c6a05216 100644 --- a/.github/workflows/docs/setup-docs/action.yml +++ b/.github/workflows/docs/setup-docs/action.yml @@ -38,7 +38,6 @@ runs: CLEANUP_PATTERN: ${{ inputs.cleanup_pattern }} run: | eval `ssh-agent` - rm -rf ${DOCS_OUTPUT_DIR}/${CLEANUP_PATTERN} || true echo "$DOCS_DEPLOY_KEY" | tr -d '\r' | ssh-add - > /dev/null mkdir ~/.ssh ssh-keyscan www.github.com >> ~/.ssh/known_hosts diff --git a/.github/workflows/nightly-docs.yml b/.github/workflows/nightly-docs.yml index 2a6841c3771..782b76b9ed5 100644 --- a/.github/workflows/nightly-docs.yml +++ b/.github/workflows/nightly-docs.yml @@ -9,12 +9,20 @@ on: jobs: nightly-docs: - if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" + if: "!contains(toJSON(github.event.commits.*.message), '[ci skip]')" runs-on: ubuntu-latest steps: - name: Configure workflow id: configuration + env: + DOCS_DEPLOY_KEY: ${{ secrets.DOCS_DEPLOY_KEY }} run: | + if [ -n "$DOCS_DEPLOY_KEY" ] + then + echo "DOCS_DEPLOY_KEY_PRESENT=true" >> $GITHUB_OUTPUT + else + echo "Secret 'DOCS_DEPLOY_KEY' not present. Exiting job." + fi BRANCH_NAME="${GITHUB_REF#refs/*/}" echo "BRANCH_NAME=${BRANCH_NAME}" >> $GITHUB_OUTPUT echo "DOCS_OUTPUT_DIR=${GITHUB_WORKSPACE}/skript-docs/docs/nightly/${BRANCH_NAME}" >> $GITHUB_OUTPUT @@ -26,20 +34,23 @@ jobs: submodules: recursive path: skript - name: Setup documentation environment + if: steps.configuration.outputs.DOCS_DEPLOY_KEY_PRESENT == 'true' 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 + id: generate + if: steps.configuration.outputs.DOCS_DEPLOY_KEY_PRESENT == 'true' 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 }} - name: Push nightly documentation + if: steps.generate.outputs.DOCS_CHANGED == 'true' uses: ./skript/.github/workflows/docs/push-docs with: - docs_output_dir: ${{ steps.configuration.outputs.DOCS_OUTPUT_DIR }} docs_repo_dir: ${{ steps.configuration.outputs.DOCS_REPO_DIR }} git_name: Nightly Docs Bot git_email: nightlydocs@skriptlang.org diff --git a/.github/workflows/release-docs.yml b/.github/workflows/release-docs.yml index 9ec5a5ad257..a8ccb4000bb 100644 --- a/.github/workflows/release-docs.yml +++ b/.github/workflows/release-docs.yml @@ -26,7 +26,6 @@ jobs: with: docs_deploy_key: ${{ secrets.DOCS_DEPLOY_KEY }} docs_output_dir: ${{ steps.configuration.outputs.DOCS_OUTPUT_DIR }} - cleanup_pattern: "!(nightly|archives)" - name: Generate documentation uses: ./skript/.github/workflows/docs/generate-docs with: @@ -34,6 +33,7 @@ jobs: docs_repo_dir: ${{ steps.configuration.outputs.DOCS_REPO_DIR }} skript_repo_dir: ${{ steps.configuration.outputs.SKRIPT_REPO_DIR }} is_release: true + cleanup_pattern: "!(nightly|archives|templates)" - name: Push release documentation uses: ./skript/.github/workflows/docs/push-docs with: @@ -68,14 +68,12 @@ jobs: - 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 - name: Push archive documentation uses: ./skript/.github/workflows/docs/push-docs with: - docs_output_dir: ${{ steps.configuration.outputs.DOCS_OUTPUT_DIR }} docs_repo_dir: ${{ steps.configuration.outputs.DOCS_REPO_DIR }} git_name: Archive Docs Bot git_email: archivedocs@skriptlang.org diff --git a/build.gradle b/build.gradle index 5920cb95ac8..9a525ff1e1d 100644 --- a/build.gradle +++ b/build.gradle @@ -149,14 +149,6 @@ license { exclude('**/*.json') // JSON files do not have headers } -javadoc { - 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 releaseJavadoc(type: Javadoc) { title = project.property('version') source = sourceSets.main.allJava @@ -394,3 +386,26 @@ task nightlyRelease(type: ShadowJar) { ) } } + +javadoc { + dependsOn nightlyResources + + source = sourceSets.main.allJava + + exclude("ch/njol/skript/conditions/**") + exclude("ch/njol/skript/expressions/**") + exclude("ch/njol/skript/effects/**") + exclude("ch/njol/skript/events/**") + exclude("ch/njol/skript/sections/**") + exclude("ch/njol/skript/structures/**") + exclude("ch/njol/skript/lang/function/EffFunctionCall.java") + exclude("ch/njol/skript/lang/function/ExprFunctionCall.java") + exclude("ch/njol/skript/hooks/**") + exclude("ch/njol/skript/test/**") + + classpath = configurations.compileClasspath + sourceSets.main.output + options.encoding = 'UTF-8' + // currently our javadoc has a lot of errors, so we need to suppress the linter + options.addStringOption('Xdoclint:none', '-quiet') +} + diff --git a/src/main/java/ch/njol/skript/doc/Documentation.java b/src/main/java/ch/njol/skript/doc/Documentation.java index 5da07aefb09..c7b9ebebbb1 100644 --- a/src/main/java/ch/njol/skript/doc/Documentation.java +++ b/src/main/java/ch/njol/skript/doc/Documentation.java @@ -60,7 +60,7 @@ public class Documentation { private static final Pattern CP_EMPTY_PARSE_MARKS_PATTERN = Pattern.compile("\\(\\)"); private static final Pattern CP_PARSE_TAGS_PATTERN = Pattern.compile("(?<=[(|\\[ ])[-a-zA-Z0-9!$#%^&*_+~=\"'<>?,.]*?:"); private static final Pattern CP_EXTRA_OPTIONAL_PATTERN = Pattern.compile("\\[\\(((\\w+? ?)+)\\)]"); - private static final File DOCS_TEMPLATE_DIRECTORY = new File(Skript.getInstance().getDataFolder(), "doc-templates"); + private static final File DOCS_TEMPLATE_DIRECTORY = new File(Skript.getInstance().getDataFolder(), "docs/templates"); private static final File DOCS_OUTPUT_DIRECTORY = new File(Skript.getInstance().getDataFolder(), "docs"); /** From 9e429f2597a7c39de6c9b7faa0c26f7c66fab6c9 Mon Sep 17 00:00:00 2001 From: Moderocky <admin@moderocky.com> Date: Wed, 20 Sep 2023 09:34:31 +0100 Subject: [PATCH 451/619] Tidy up parts of config class. (#6025) --- .../java/ch/njol/skript/config/Config.java | 61 ++++++++----------- 1 file changed, 26 insertions(+), 35 deletions(-) diff --git a/src/main/java/ch/njol/skript/config/Config.java b/src/main/java/ch/njol/skript/config/Config.java index 6ec44491749..bcabb8859f3 100644 --- a/src/main/java/ch/njol/skript/config/Config.java +++ b/src/main/java/ch/njol/skript/config/Config.java @@ -18,9 +18,11 @@ */ package ch.njol.skript.config; +import ch.njol.skript.Skript; +import ch.njol.skript.config.validate.SectionValidator; + import java.io.ByteArrayInputStream; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; @@ -28,15 +30,13 @@ import java.lang.reflect.Modifier; import java.nio.channels.Channels; import java.nio.channels.FileChannel; +import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.Skript; -import ch.njol.skript.config.validate.SectionValidator; - /** * Represents a config file. * @@ -44,7 +44,7 @@ */ public class Config implements Comparable<Config> { - boolean simple = false; + boolean simple; /** * One level of the indentation, e.g. a tab or 4 spaces. @@ -58,8 +58,6 @@ public class Config implements Comparable<Config> { final String defaultSeparator; String separator; - String line = ""; - int level = 0; private final SectionNode main; @@ -91,11 +89,8 @@ public Config(final InputStream source, final String fileName, @Nullable final F if (Skript.logVeryHigh()) Skript.info("loading '" + fileName + "'"); - final ConfigReader r = new ConfigReader(source); - try { - main = SectionNode.load(this, r); - } finally { - r.close(); + try (ConfigReader reader = new ConfigReader(source)) { + main = SectionNode.load(this, reader); } } finally { source.close(); @@ -106,9 +101,8 @@ public Config(final InputStream source, final String fileName, final boolean sim this(source, fileName, null, simple, allowEmptySections, defaultSeparator); } - @SuppressWarnings("resource") public Config(final File file, final boolean simple, final boolean allowEmptySections, final String defaultSeparator) throws IOException { - this(new FileInputStream(file), "" + file.getName(), simple, allowEmptySections, defaultSeparator); + this(Files.newInputStream(file.toPath()), file.getName(), simple, allowEmptySections, defaultSeparator); this.file = file.toPath(); } @@ -120,7 +114,7 @@ public Config(final Path file, final boolean simple, final boolean allowEmptySec /** * For testing - * + * * @param s * @param fileName * @param simple @@ -133,7 +127,7 @@ public Config(final String s, final String fileName, final boolean simple, final } void setIndentation(final String indent) { - assert indent != null && indent.length() > 0 : indent; + assert indent != null && !indent.isEmpty() : indent; indentation = indent; indentationName = (indent.charAt(0) == ' ' ? "space" : "tab"); } @@ -156,7 +150,7 @@ public String getFileName() { /** * Saves the config to a file. - * + * * @param f The file to save to * @throws IOException If the file could not be written to. */ @@ -175,7 +169,7 @@ public void save(final File f) throws IOException { * Sets this config's values to those in the given config. * <p> * Used by Skript to import old settings into the updated config. The return value is used to not modify the config if no new options were added. - * + * * @param other * @return Whether the configs' keys differ, i.e. false == configs only differ in values, not keys. */ @@ -203,7 +197,7 @@ public File getFile() { if (file != null) { try { return file.toFile(); - } catch(Exception e) { + } catch (Exception e) { return null; // ZipPath, for example, throws undocumented exception } } @@ -235,7 +229,7 @@ public String getSaveSeparator() { /** * Splits the given path at the dot character and passes the result to {@link #get(String...)}. - * + * * @param path * @return <tt>get(path.split("\\."))</tt> */ @@ -247,7 +241,7 @@ public String getByPath(final String path) { /** * Gets an entry node's value at the designated path - * + * * @param path * @return The entry node's value at the location defined by path or null if it either doesn't exist or is not an entry. */ @@ -284,22 +278,19 @@ public boolean validate(final SectionValidator validator) { return validator.validate(getMainNode()); } - private void load(final Class<?> c, final @Nullable Object o, final String path) { - for (final Field f : c.getDeclaredFields()) { - f.setAccessible(true); - if (o != null || Modifier.isStatic(f.getModifiers())) { + private void load(final Class<?> cls, final @Nullable Object object, final String path) { + for (final Field field : cls.getDeclaredFields()) { + field.setAccessible(true); + if (object != null || Modifier.isStatic(field.getModifiers())) { try { - if (OptionSection.class.isAssignableFrom(f.getType())) { - final Object p = f.get(o); - @NonNull - final Class<?> pc = p.getClass(); - load(pc, p, path + ((OptionSection) p).key + "."); - } else if (Option.class.isAssignableFrom(f.getType())) { - ((Option<?>) f.get(o)).set(this, path); + if (OptionSection.class.isAssignableFrom(field.getType())) { + final OptionSection section = (OptionSection) field.get(object); + @NonNull final Class<?> pc = section.getClass(); + load(pc, section, path + section.key + "."); + } else if (Option.class.isAssignableFrom(field.getType())) { + ((Option<?>) field.get(object)).set(this, path); } - } catch (final IllegalArgumentException e) { - assert false; - } catch (final IllegalAccessException e) { + } catch (final IllegalArgumentException | IllegalAccessException e) { assert false; } } From 438851a4648dba346911b54312f117800845d1e6 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Wed, 20 Sep 2023 04:55:48 -0400 Subject: [PATCH 452/619] Add Release Model Document (#6041) Add release model document Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Co-authored-by: Moderocky <admin@moderocky.com> --- CLOCKWORK_RELEASE_MODEL.md | 294 +++++++++++++++++++++++++++++++++++++ README.md | 2 + 2 files changed, 296 insertions(+) create mode 100644 CLOCKWORK_RELEASE_MODEL.md diff --git a/CLOCKWORK_RELEASE_MODEL.md b/CLOCKWORK_RELEASE_MODEL.md new file mode 100644 index 00000000000..e5e086f62eb --- /dev/null +++ b/CLOCKWORK_RELEASE_MODEL.md @@ -0,0 +1,294 @@ +# Clockwork Release Model + +## Table of Contents +1. [Introduction](#introduction) + - [Preamble](#preamble) + - [Motivations](#motivations) + - [Goals](#goals) + - [Non-Goals](#non-goals) +2. [Release Types](#release-types) + - [Feature Releases](#feature-releases) + - [Patch Releases](#patch-releases) + - [Pre-Releases](#pre-releases) + - [Emergency Patch Releases](#emergency-patch-releases) +3. [Timetable](#timetable) + - [Major Version Schedule](#major-version-schedule) + - [Pre-Release Schedule](#pre-release-schedule) + - [Patch Schedule](#patch-schedule) +4. [Content Curation](#content-curation) + - [Labels](#labels) + - [Branches](#branches) +5. [Conclusion](#conclusion) + - [Paradigm Versions](#addendum-1-paradigm-versions) + - [Failure Standards](#addendum-2-failure-standards) + +## Introduction + +### Preamble + +This document defines the structure of future Skript releases, the kinds of material included in releases and the outline of the dates on which releases will be published. + +A 'release' is the publication of a verified and signed build artefact on the GitHub releases tab, made available for all users to download and install. + +This document does *not* cover the distribution or publication of artifacts built in other ways (e.g. privately, from a nightly action) or those not published from our GitHub (e.g. test builds shared in our public testing group). + +Plans for a new release model began in March 2023 and several models were discussed, with this being the final version agreed upon by the organisation's administrative group and approved by the core contributors. + +### Motivations + +The release cycle for the `2.7.0` version was significant in that it took an unusually long time and included an unusually-large number of additions and changes. + +While it was not the first version to have taken a long time to finalise and produce, it was distinct in that a lot of time had passed since the previous public build of Skript having been marked stable. + +Members of the organisation and the wider community identified several problems that resulted from this, some of which are (not exhaustively) detailed below: +- 291 days had passed since the previous release + - Users were unable to benefit from bug fixes or new features produced during that time + - Although beta versions were released, these were marked unstable and were not fully tested +- When the release arrived it contained a very large number of changes and additions + - Some users were unaware of changes that could not be extensively documented in the changelog or were buried in a large list + - Users who obtained a build elsewhere (e.g. direct download, automatic installer) may have been unaware of the scale of the changes +- Several additions were made at short notice and without sufficient testing + - Some of these introduced problems that required fixes in a following `2.7.1` patch +- Several features could not be completed in time and had to be dropped to a future `2.8.0` version + - One result of this was that any corrections or improvements made as part of these were not present in `2.7.0` + - Aspects of some of these larger-scale changes had to be re-created or cherry-picked for the `2.7.0` version +- The release lacked a clear timetable or vision for what additions to include + - The initial timetable was not adhered to; plans were made for pre-releases to begin at the end of November 2022, which was delayed to early December in order to accommodate a large feature PR (which was eventually dropped to `2.8.0`) + - Delays persisted, and the final release took place 7 months later in September 2023 + - There was no clear cut-off point for new features; feature pull requests were being included even up to 3 days before release + +Of these, the principle complaint is that the `2.7.0` version took a significant amount of time to finish and this had an adverse effect on the community and the wider ecosystem. + +### Goals + +Our release model has been designed to achieve the following goals: +1. To reduce the delay between finishing new features and releasing them to the public. +2. To significantly reduce the time between an issue being fixed and that fix being made public in a stable build. +3. To reduce the risk of untested changes going into a release. +4. To make the release timetable clear and accessible. +5. To prevent a version being indefinitely delayed to accommodate additional changes. + +### Non-Goals + +This release model is not intended to change any of the following: +- The content or feature-theme of a particular version. +- The process for reviewing or approving changes. + +## Release Types + +This section details the different categories of version for release. + +The versioning will follow the form `A.B.C`, where `B` is a [feature version](#Feature-Releases) containing changes and additions, `C` is a [patch version](#Patch-Releases) containing only issue fixes, and `A` is reserved for major paradigmatic changes. + +### Feature Releases +A 'feature' version (labelled `0.X.0`) may contain: +- Additions to the language (syntax, grammar, structures) +- Bug fixes +- Developer API additions and changes +- Breaking changes<sup>1</sup> + +All content added to a feature version must pass through the typical review process. +Content must also have been included in a prior [pre-release](#Pre-Releases) for public testing. + +> <sup>1</sup> Breaking changes are to be avoided where possible but may be necessary, such as in a case where a significant improvement could be made to an existing feature but only by changing its structure somehow. +> Such changes should be clearly labelled and well documented, preferably giving users ample notice. + +### Patch Releases +A 'patch' version (labelled `0.0.X`) may contain: +- Bug fixes +- Non-impactful<sup>2</sup> improvements to existing features +- Changes to meta content (e.g. documentation) + +There may be **very rare** occasions when a breaking change is necessary in a patch release. These may occur if and only if: either a breaking change is required in order to fix an issue, and the issue is significant enough to need fixing in a patch rather than waiting for a major release, or an issue occurred with an inclusion in the version immediately-prior to this, which must be changed or reverted in some way. + +All content added to a patch version must pass through the typical review process. + +> <sup>2</sup> A non-impactful change is one in which there is no apparent difference to the user in how a feature is employed or what it does but that may have a material difference in areas such as performance, efficiency or machine resource usage. + +### Pre-Releases + +A 'pre-release' version (labelled `0.X.0-preY`) will contain all of the content expected to be in the feature release immediately following this. + +Pre-release versions are a final opportunity for testing and getting public feedback on changes before a major release, allowing time to identify and fix any issues before the proper release, rather than needing an immediate patch. + +The content of a pre-release should be identical to the content of the upcoming release -- barring any bug fixes -- and new content should never be included after a pre-release. + +### Emergency Patch Releases + +An 'emergency patch' version will be released if a critical security vulnerability is reported that the organisation feels prevents an immediate risk to the user base, such that it cannot wait for the subsequent patch. + +An emergency patch will be labelled as another patch version (`0.0.X`). It should be noted that an emergency patch will *not* disrupt the typical timetable detailed below. + +These kinds of releases may be published immediately and do not have to go through the typical reviewing and testing process. \ +They must never include content, additions or unnecessary changes. + +The only content permitted in an emergency patch is the material needed to fix the security risk. + +The exact nature of the security vulnerability (such as the means to reproduce it) should not be included in the notes surrounding the release. + +## Timetable + +The 'clockwork' release model follows a strict monthly cycle, with versions being released on exact dates. + +A table of (expected) dates is displayed below. + +| Date | Release Type | Example Version<br>Name | +|----------|-----------------|-------------------------| +| 1st Jan | Pre-release | 0.1.0-pre1 | +| 15th Jan | Feature release | 0.1.0 | +| 1st Feb | Patch | 0.1.1 | +| 1st Mar | Patch | 0.1.2 | +| 1st Apr | Patch | 0.1.3 | +| 1st May | Patch | 0.1.4 | +| 1st Jun | Patch | 0.1.5 | +| 1st Jul | Pre-release | 0.2.0-pre1 | +| 15th Jul | Feature release | 0.2.0 | +| 1st Aug | Patch | 0.2.1 | +| 1st Sep | Patch | 0.2.2 | +| 1st Oct | Patch | 0.2.3 | +| 1st Nov | Patch | 0.2.4 | +| 1st Dec | Patch | 0.2.5 | + +An estimated 14 releases are expected per year, with 10 patches, 2 pre-releases and 2 feature-releases that immediately follow them. + +Please note that the actual number may differ from this in cases such as: +- A version requiring multiple pre-releases to correct mistakes (`0.3.0-pre1`, `0.3.0-pre2`) +- An emergency patch having to be released +- No bug fixes being prepared in a month, meaning no patch is needed + +There is no fixed timetable for the circulation of unpublished builds to the public testing group or the addon developers group. + +### Major Version Schedule + +A [feature version](#feature-releases) will be released on the **15th of January** and the **15th of July**. + +This will include all finished content from the previous 6 months that was tested in the pre-release. + +Any features, additions or changes that were *not* ready or approved at the time of the pre-release may **not** be included in the feature release [according to goal 3](#goals). \ +The feature release must **not** be delayed to accomodate content that was not ready by the deadline [according to goal 5](#goals). + +If there is no content ready at the scheduled date of a feature release, the release will be skipped and a notice published explaining this. + +### Pre-Release Schedule + +A [pre-release](#pre-releases) will be released on the **1st of January** and the **1st of July**, leaving two weeks before the following release for public testing to occur. + +This pre-release may include all finished content from the previous 6 months. + +Any features, additions or changes that have *not* passed the review/approval process by the day of the pre-release may **not** be included in the pre-release [according to goal 3](#goals). \ +The pre-release must **not** be delayed to accomodate content that was not ready by the deadline [according to goal 5](#goals). + +If there is no content ready at the scheduled date of a pre-release, the entire feature-release will be skipped and a notice published explaining this. + +If issues are found requiring a new build be produced (e.g. the build fails to load, a core feature is non-functional, a fix was made but needs additional testing) then another version of the pre-release may be published. +There is no limit on the number of pre-releases that can be published if required. + +### Patch Schedule + +A [patch](#patch-releases) will be released on the **1st** of every month (except January and July) containing any fixes prepared during the previous month(s). + +On the 1st of January and July the patch will be replaced by the pre-release. + +A patch should include all bug fixes from the previous month that have passed the review/approval process. + +Ideally, a patch build should be circulated in the public testing group prior to its release, but this is not a strict requirement. + +If there are no applicable bug fixes ready by the scheduled date of the patch then the month will be skipped and the patch will not take place. A public notice is not required to explain this. + +## Content Curation + +To help curate content on our GitHub repository we have designed a new branch model and accompanying labels for categorising contributions. + +### Labels + +We shall provide issue and pull request labels to help categorise changes to prevent contributions missing a release (or slipping into the incorrect kind of release). + +1. `patch-ready` \ + to denote a pull request that has: + - passed the review/approval process + - is of the sufficient kind to be included in a monthly patch version +2. `feature-ready` \ + to denote a pull request that has: + - passed the review/approval process + - should wait for a biannual feature release + - is not suitable to be included in a patch + +### Branches + +We shall maintain three core branches: `dev/patch`, `dev/feature` and `master`, which function vertically<sup>3</sup>. + +We may also create legacy branches where necessary. \ +As an example, if a previous release, say `2.6.4` requires an emergency security update, a branch can be made from its release tag and the patch may directly target that branch (and be released). + +We may also maintain other assorted branches for individual features, for the purpose of group work or for experimentation. These are not detailed below. + +> <sup>3</sup> Changes are always made to the 'top' (working) branch and then this is merged downwards into the more stable branch below when required. +> +> Branches are never merged upwards. + +#### Patch + +Pull requests that only address issues or are otherwise suitable for a patch release should target the **`dev/patch` branch**. These may be merged whenever appropriate (i.e. all review and testing requirements have passed). + +At the time of the patch release, the **patch branch** will be merged downwards into the **master branch**, and a release will be created from the **master branch**. + +When a feature release occurs and all branches are merged into the master branch, the patch branch will be rebased off the current master commit, effectively bringing it up to speed with the new changes. \ +As an example, when feature version 0.5.0 releases, the patch branch will be at 0.4.5 and missing the new features, so must be rebased off the current release and catch up before changes for version 0.5.1 may be merged. + +#### Feature + +Pull requests that add features, make breaking changes or are otherwise unsuitable for a patch version should target the **`dev/feature` branch**. \ +These should be merged whenever appropriate (i.e. all review and testing requirements have passed), so that testing builds can be created and circulated in the public testing group. + +The **patch branch** may be merged downwards into the **feature branch** whenever appropriate (e.g. after changes have been made to it that may affect the state of contributions targeting the feature branch). + +The feature branch should __**never**__ be merged upwards into the patch branch<sup>4</sup>. + +The feature branch is only merged downwards into the master branch directly before a full feature release (i.e. after the pre-release and testing is complete.) + +Pre-releases are made directly from the feature branch<sup>5</sup>. At the end of the pre-release testing period the feature branch can be merged downwards into the master branch in order for the full release to be made. + +> <sup>4</sup> Merges only ever occur downwards. For the patch branch to see changes from the feature branch it must be rebased onto master branch after a feature release occurs. +> +> <sup>5</sup> Merging the branch down for the pre-release would introduce potentially-buggy, untested changes to the master branch. + +#### Master + +The **`master` branch** should reflect the most recent feature release. +Pull requests should **never** directly target the master branch. Changes are made to one of the other branches (as applicable) and then that branch is merged downwards into the **master branch** only when it is time for a release. + +This means that any user building from the master branch is guaranteed to receive a safe, stable build of the quality that we would release. + +The master branch should never be merged upwards into the feature or patch branches. If these branches need to see changes from the master branch they must be rebased onto the latest master branch commit. + +## Conclusion + +It is our aim that this release model will address all of our goals and satisfy our motivations. + +Setting a strict and regular schedule ought to prevent too much time passing without a release, while also helping to prevent a single release from becoming bloated and overbearing. + +By including testing requirements and mandating public pre-releases we hope to solve the persistent issue of untested changes slipping into supposedly-stable versions. + +Finally, by scheduling regular patches we aim to reduce the time between a bug being 'fixed' by a contributor and the userbase being able to benefit from that fix. Keeping these patches as small, controlled releases allows us to mark them as 'stable' therefore letting the version reach a wider audience. + +### Addendum 1: Paradigm Versions + +Paradigmatic `X.0.0` versions were deliberately excluded from this proposal. \ +The reasoning behind this choice was that over 10 years have passed since the inception of major version`2.0.0` in 2012, the previous paradigmatic change. + +As of writing this document there are proposals and roadmaps for a version `3.0.0` but no timetable or predicted date on the horizon. + +This kind of version, were it to be released, would likely take the place of a typical feature release in the model calendar, i.e. occurring on the 15th of January or July. However, due to its potentially-significant nature it may require exceptional changes to the pre-release cycle. + +As details of such a version are neither known nor easy to predict, it has been left to the discretion of the future team to be decided when required. + +### Addendum 2: Failure Standards + +No proposal is complete without failure standards; this model can be deemed to have failed if, in two years' time: +1. The delay between finishing new features and releasing them to the public has not been reduced. +2. The delay between an issue being fixed and that fix being made public in a stable build has not been reduced. +3. Untested features are being released in 'stable' builds. +4. The release timetable is unclear or inaccessible. +5. Versions are being indefinitely delayed to accommodate additional changes. + +Additionally, if this model is considered to have put an undue burden on the core development team, to the extent that it has hampered progress in a significant and measurable way, then it can be considered to have failed. diff --git a/README.md b/README.md index e23e1a288d2..36c82e5c00f 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,8 @@ versions will be supported as soon as possible. ## Download You can find the downloads for each version with their release notes in the [releases page](https://github.com/SkriptLang/Skript/releases). +Two major feature updates are expected each year in January and July, with monthly patches occurring in between. For full details, please review our [release model](CLOCKWORK_RELEASE_MODEL.md). + ## Documentation Documentation is available [here](https://docs.skriptlang.org/) for the latest version of Skript. From 06df2811c5e1ee6e6b9d353fc0a4142e883d0e39 Mon Sep 17 00:00:00 2001 From: Moderocky <admin@moderocky.com> Date: Wed, 20 Sep 2023 10:08:03 +0100 Subject: [PATCH 453/619] (Cherry Pick) Fix cast throwing if existing variable for command storage exists (#5942) (#6026) Fix cast throwing if existing variable for command storage exists (#5942) * Fix cast throwing if existing variable for command storage exists * Update src/main/java/ch/njol/skript/command/ScriptCommand.java --------- Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> --- src/main/java/ch/njol/skript/command/ScriptCommand.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/command/ScriptCommand.java b/src/main/java/ch/njol/skript/command/ScriptCommand.java index be7942e3ca7..2ce33ac7152 100644 --- a/src/main/java/ch/njol/skript/command/ScriptCommand.java +++ b/src/main/java/ch/njol/skript/command/ScriptCommand.java @@ -486,7 +486,13 @@ public Date getLastUsage(UUID uuid, Event event) { } else { String name = getStorageVariableName(event); assert name != null; - return (Date) Variables.getVariable(name, null, false); + Object variable = Variables.getVariable(name, null, false); + if (!(variable instanceof Date)) { + Skript.warning("Variable {" + name + "} was not a date! You may be using this variable elsewhere. " + + "This warning is letting you know that this variable is now overridden for the command storage."); + return null; + } + return (Date) variable; } } From 5711da917a84f81124aa4a52711da385df638a76 Mon Sep 17 00:00:00 2001 From: Moderocky <admin@moderocky.com> Date: Wed, 20 Sep 2023 10:40:16 +0100 Subject: [PATCH 454/619] (Cherry Pick) Fix NPE with invalid attributes and clean up ExprEntityAttribute (#5978) (#6023) Fix NPE with invalid attributes and clean up ExprEntityAttribute (#5978) * Avoid NPE and clean up class * Update ExprEntityAttribute.java * Update src/main/java/ch/njol/skript/expressions/ExprEntityAttribute.java * Update src/main/java/ch/njol/skript/expressions/ExprEntityAttribute.java --------- Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> --- .../expressions/ExprEntityAttribute.java | 78 ++++++++++--------- 1 file changed, 41 insertions(+), 37 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprEntityAttribute.java b/src/main/java/ch/njol/skript/expressions/ExprEntityAttribute.java index c7c81de0d5c..ddb4b3b27ad 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprEntityAttribute.java +++ b/src/main/java/ch/njol/skript/expressions/ExprEntityAttribute.java @@ -18,16 +18,6 @@ */ package ch.njol.skript.expressions; -import org.bukkit.attribute.Attribute; - -import java.util.stream.Stream; - -import org.bukkit.attribute.Attributable; -import org.bukkit.attribute.AttributeInstance; -import org.bukkit.entity.Entity; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; @@ -40,21 +30,34 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; +import org.bukkit.attribute.Attributable; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; +import org.bukkit.entity.Entity; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.Objects; +import java.util.stream.Stream; @Name("Entity Attribute") -@Description({"The numerical value of an entity's particular attribute.", - "Note that the movement speed attribute cannot be reliably used for players. For that purpose, use the speed expression instead.", - "Resetting an entity's attribute is only available in Minecraft 1.11 and above."}) -@Examples({"on damage of player:", - " send \"You are wounded!\"", - " set victim's attack speed attribute to 2"}) +@Description({ + "The numerical value of an entity's particular attribute.", + "Note that the movement speed attribute cannot be reliably used for players. For that purpose, use the speed expression instead.", + "Resetting an entity's attribute is only available in Minecraft 1.11 and above." +}) +@Examples({ + "on damage of player:", + "\tsend \"You are wounded!\" to victim", + "\tset victim's attack speed attribute to 2" +}) @Since("2.5, 2.6.1 (final attribute value)") public class ExprEntityAttribute extends PropertyExpression<Entity, Number> { static { Skript.registerExpression(ExprEntityAttribute.class, Number.class, ExpressionType.COMBINED, - "[the] %attributetype% [(1¦(total|final|modified))] attribute [value] of %entities%", - "%entities%'[s] %attributetype% [(1¦(total|final|modified))] attribute [value]"); + "[the] %attributetype% [(1:(total|final|modified))] attribute [value] of %entities%", + "%entities%'[s] %attributetype% [(1:(total|final|modified))] attribute [value]"); } @Nullable @@ -72,10 +75,11 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") - protected Number[] get(Event e, Entity[] entities) { - Attribute a = attributes.getSingle(e); + protected Number[] get(Event event, Entity[] entities) { + Attribute attribute = attributes.getSingle(event); return Stream.of(entities) - .map(ent -> getAttribute(ent, a)) + .map(ent -> getAttribute(ent, attribute)) + .filter(Objects::nonNull) .map(att -> withModifiers ? att.getValue() : att.getBaseValue()) .toArray(Number[]::new); } @@ -90,27 +94,27 @@ public Class<?>[] acceptChange(ChangeMode mode) { @Override @SuppressWarnings("null") - public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { - Attribute a = attributes.getSingle(e); - double d = delta == null ? 0 : ((Number) delta[0]).doubleValue(); - for (Entity entity : getExpr().getArray(e)) { - AttributeInstance ai = getAttribute(entity, a); - if(ai != null) { + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + Attribute attribute = attributes.getSingle(event); + double deltaValue = delta == null ? 0 : ((Number) delta[0]).doubleValue(); + for (Entity entity : getExpr().getArray(event)) { + AttributeInstance instance = getAttribute(entity, attribute); + if (instance != null) { switch(mode) { case ADD: - ai.setBaseValue(ai.getBaseValue() + d); + instance.setBaseValue(instance.getBaseValue() + deltaValue); break; case SET: - ai.setBaseValue(d); + instance.setBaseValue(deltaValue); break; case DELETE: - ai.setBaseValue(0); + instance.setBaseValue(0); break; case RESET: - ai.setBaseValue(ai.getDefaultValue()); + instance.setBaseValue(instance.getDefaultValue()); break; case REMOVE: - ai.setBaseValue(ai.getBaseValue() - d); + instance.setBaseValue(instance.getBaseValue() - deltaValue); break; case REMOVE_ALL: assert false; @@ -126,14 +130,14 @@ public Class<? extends Number> getReturnType() { @Override @SuppressWarnings("null") - public String toString(@Nullable Event e, boolean debug) { - return "entity " + getExpr().toString(e, debug) + "'s " + (attributes == null ? "" : attributes.toString(e, debug)) + "attribute"; + public String toString(@Nullable Event event, boolean debug) { + return "entity " + getExpr().toString(event, debug) + "'s " + (attributes == null ? "" : attributes.toString(event, debug)) + "attribute"; } @Nullable - private static AttributeInstance getAttribute(Entity e, @Nullable Attribute a) { - if (a != null && e instanceof Attributable) { - return ((Attributable) e).getAttribute(a); + private static AttributeInstance getAttribute(Entity entity, @Nullable Attribute attribute) { + if (attribute != null && entity instanceof Attributable) { + return ((Attributable) entity).getAttribute(attribute); } return null; } From fa4351429d1555ce7292307643adaf11a1bad255 Mon Sep 17 00:00:00 2001 From: Kiip <25848425+kiip1@users.noreply.github.com> Date: Wed, 20 Sep 2023 22:38:29 +0200 Subject: [PATCH 455/619] Yggdrasil Cleanup (#5408) --- .../java/ch/njol/yggdrasil/ClassResolver.java | 11 +- .../DefaultYggdrasilInputStream.java | 126 +++---- .../DefaultYggdrasilOutputStream.java | 154 ++++---- .../java/ch/njol/yggdrasil/FieldHandler.java | 22 +- src/main/java/ch/njol/yggdrasil/Fields.java | 296 ++++++++------- .../java/ch/njol/yggdrasil/JRESerializer.java | 104 +++--- .../java/ch/njol/yggdrasil/PseudoEnum.java | 88 +++-- .../njol/yggdrasil/SimpleClassResolver.java | 23 +- src/main/java/ch/njol/yggdrasil/Tag.java | 71 ++-- .../java/ch/njol/yggdrasil/Yggdrasil.java | 309 +++++++--------- .../ch/njol/yggdrasil/YggdrasilException.java | 14 +- .../java/ch/njol/yggdrasil/YggdrasilID.java | 4 +- .../njol/yggdrasil/YggdrasilInputStream.java | 201 ++++------- .../njol/yggdrasil/YggdrasilOutputStream.java | 205 +++++------ .../njol/yggdrasil/YggdrasilSerializable.java | 64 ++-- .../njol/yggdrasil/YggdrasilSerializer.java | 49 ++- .../njol/yggdrasil/util/JREFieldHandler.java | 99 +++-- .../njol/yggdrasil/xml/YggXMLInputStream.java | 322 ----------------- .../yggdrasil/xml/YggXMLOutputStream.java | 339 ------------------ .../ch/njol/yggdrasil/xml/package-info.java | 27 -- 20 files changed, 816 insertions(+), 1712 deletions(-) delete mode 100644 src/main/java/ch/njol/yggdrasil/xml/YggXMLInputStream.java delete mode 100644 src/main/java/ch/njol/yggdrasil/xml/YggXMLOutputStream.java delete mode 100644 src/main/java/ch/njol/yggdrasil/xml/package-info.java diff --git a/src/main/java/ch/njol/yggdrasil/ClassResolver.java b/src/main/java/ch/njol/yggdrasil/ClassResolver.java index bf0c22e29ed..e8cea60120e 100644 --- a/src/main/java/ch/njol/yggdrasil/ClassResolver.java +++ b/src/main/java/ch/njol/yggdrasil/ClassResolver.java @@ -29,17 +29,16 @@ public interface ClassResolver { * @return The Class object that represents data with the given ID, or null if the ID does not belong to the implementor */ @Nullable - public Class<?> getClass(String id); + Class<?> getClass(String id); /** - * Gets an ID for a Class. The ID is used to identify the type of a saved object. + * Gets an ID for a Class. The ID is used to identify the type of saved object. * <p> - * // TODO make sure that it's unique - * - * @param c The class to get the ID of + * @param clazz The class to get the ID of * @return The ID of the given class, or null if this is not a class of the implementor */ + // TODO make sure that it's unique @Nullable - public String getID(Class<?> c); + String getID(Class<?> clazz); } diff --git a/src/main/java/ch/njol/yggdrasil/DefaultYggdrasilInputStream.java b/src/main/java/ch/njol/yggdrasil/DefaultYggdrasilInputStream.java index 2bf434bf364..87142f293a1 100644 --- a/src/main/java/ch/njol/yggdrasil/DefaultYggdrasilInputStream.java +++ b/src/main/java/ch/njol/yggdrasil/DefaultYggdrasilInputStream.java @@ -18,102 +18,90 @@ */ package ch.njol.yggdrasil; -import static ch.njol.yggdrasil.Tag.T_ARRAY; -import static ch.njol.yggdrasil.Tag.T_REFERENCE; +import ch.njol.util.coll.CollectionUtils; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.StreamCorruptedException; -import java.lang.reflect.Array; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; -import org.eclipse.jdt.annotation.NonNull; +import static ch.njol.yggdrasil.Tag.T_ARRAY; +import static ch.njol.yggdrasil.Tag.T_REFERENCE; //Naming conventions: // x(): read info & data (e.g. content type, contents) [i.e. no tag] // _x(): read data only (e.g. contents) - public final class DefaultYggdrasilInputStream extends YggdrasilInputStream { - @SuppressWarnings("null") - private final static Charset UTF_8 = Charset.forName("UTF-8"); - + private final InputStream in; private final short version; - final InputStream in; - - public DefaultYggdrasilInputStream(final Yggdrasil y, final InputStream in) throws IOException { - super(y); + public DefaultYggdrasilInputStream(Yggdrasil yggdrasil, InputStream in) throws IOException { + super(yggdrasil); this.in = in; - final int m = readInt(); - if (m != Yggdrasil.MAGIC_NUMBER) + if (readInt() != Yggdrasil.MAGIC_NUMBER) throw new StreamCorruptedException("Not an Yggdrasil stream"); version = readShort(); if (version <= 0 || version > Yggdrasil.LATEST_VERSION) throw new StreamCorruptedException("Input was saved using a later version of Yggdrasil"); } - // private - /** * @throws EOFException If the end of the stream is reached */ private int read() throws IOException { - final int b = in.read(); + int b = in.read(); if (b < 0) throw new EOFException(); return b; } - private void readFully(final byte[] buf) throws IOException { - readFully(buf, 0, buf.length); + private void readFully(byte[] buf) throws IOException { + readFully(buf, buf.length); } - private void readFully(final byte[] buf, int off, final int len) throws IOException { - int l = len; - while (l > 0) { - final int n = in.read(buf, off, l); + private void readFully(byte[] buf, int startLength) throws IOException { + int offset = 0; + int length = startLength; + while (length > 0) { + int n = in.read(buf, offset, length); if (n < 0) - throw new EOFException("Expected " + len + " bytes, but could only read " + (len - l)); - off += n; - l -= n; + throw new EOFException("Expected " + startLength + " bytes, but could only read " + (startLength - length)); + offset += n; + length -= n; } } private final List<String> readShortStrings = new ArrayList<>(); private String readShortString() throws IOException { - final int length = read(); + int length = read(); if (length == (T_REFERENCE.tag & 0xFF)) { - final int i = version <= 1 ? readInt() : readUnsignedInt(); + int i = version <= 1 ? readInt() : readUnsignedInt(); if (i < 0 || i > readShortStrings.size()) throw new StreamCorruptedException("Invalid short string reference " + i); return "" + readShortStrings.get(i); } - final byte[] d = new byte[length]; + byte[] d = new byte[length]; readFully(d); - final String s = new String(d, UTF_8); + String s = new String(d, StandardCharsets.UTF_8); if (length > 4) readShortStrings.add(s); return s; } - // Tag - @Override protected Tag readTag() throws IOException { - final int t = read(); - final Tag tag = Tag.byID(t); + int t = read(); + Tag tag = Tag.byID(t); if (tag == null) throw new StreamCorruptedException("Invalid tag 0x" + Integer.toHexString(t)); return tag; } - // Primitives - private byte readByte() throws IOException { return (byte) read(); } @@ -123,7 +111,7 @@ private short readShort() throws IOException { } private short readUnsignedShort() throws IOException { - final int b = read(); + int b = read(); if ((b & 0x80) != 0) return (short) (b & ~0x80); return (short) (b << 8 | read()); @@ -137,21 +125,17 @@ private int readInt() throws IOException { } private int readUnsignedInt() throws IOException { - final int b = read(); + int b = read(); if ((b & 0x80) != 0) return (b & ~0x80) << 8 | read(); return b << 24 | read() << 16 | read() << 8 | read(); } private long readLong() throws IOException { - return (long) read() << 56 - | (long) read() << 48 - | (long) read() << 40 - | (long) read() << 32 - | (long) read() << 24 - | read() << 16 - | read() << 8 - | read(); + return (long) in.read() << 56 | (long) in.read() << 48 + | (long) in.read() << 40 | (long) in.read() << 32 + | (long) in.read() << 24 | (long) in.read() << 16 + | (long) in.read() << 8 | read(); } private float readFloat() throws IOException { @@ -167,7 +151,7 @@ private char readChar() throws IOException { } private boolean readBoolean() throws IOException { - final int r = read(); + int r = read(); if (r == 0) return false; else if (r == 1) @@ -176,7 +160,7 @@ else if (r == 1) } @Override - protected Object readPrimitive(final Tag type) throws IOException { + protected Object readPrimitive(Tag type) throws IOException { switch (type) { case T_BYTE: return readByte(); @@ -194,29 +178,24 @@ protected Object readPrimitive(final Tag type) throws IOException { return readChar(); case T_BOOLEAN: return readBoolean(); - //$CASES-OMITTED$ default: throw new YggdrasilException("Internal error; " + type); } } @Override - protected Object readPrimitive_(final Tag type) throws IOException { + protected Object readPrimitive_(Tag type) throws IOException { return readPrimitive(type); } - // String - @Override protected String readString() throws IOException { - final int length = readUnsignedInt(); - final byte[] d = new byte[length]; + int length = readUnsignedInt(); + byte[] d = new byte[length]; readFully(d); - return new String(d, UTF_8); + return new String(d, StandardCharsets.UTF_8); } - // Array - @Override protected Class<?> readArrayComponentType() throws IOException { return readClass(); @@ -227,8 +206,6 @@ protected int readArrayLength() throws IOException { return readUnsignedInt(); } - // Enum - @Override protected Class<?> readEnumType() throws IOException { return yggdrasil.getClass(readShortString()); @@ -239,21 +216,18 @@ protected String readEnumID() throws IOException { return readShortString(); } - // Class - @SuppressWarnings("null") @Override protected Class<?> readClass() throws IOException { Tag type; - int dim = 0; + int dimensions = 0; while ((type = readTag()) == T_ARRAY) - dim++; - @NonNull - Class<?> c; + dimensions++; + Class<?> clazz; switch (type) { case T_OBJECT: case T_ENUM: - c = yggdrasil.getClass(readShortString()); + clazz = yggdrasil.getClass(readShortString()); break; case T_BOOLEAN: case T_BOOLEAN_OBJ: @@ -273,8 +247,8 @@ protected Class<?> readClass() throws IOException { case T_SHORT_OBJ: case T_CLASS: case T_STRING: - c = type.c; - assert c != null; + assert type.type != null; + clazz = type.type; break; case T_NULL: case T_REFERENCE: @@ -283,20 +257,16 @@ protected Class<?> readClass() throws IOException { default: throw new YggdrasilException("Internal error; " + type); } - while (dim-- > 0) - c = Array.newInstance(c, 0).getClass(); - return c; + while (dimensions-- > 0) + clazz = CollectionUtils.arrayType(clazz); + return clazz; } - // Reference - @Override protected int readReference() throws IOException { return readUnsignedInt(); } - // generic Object - @Override protected Class<?> readObjectType() throws IOException { return yggdrasil.getClass(readShortString()); @@ -312,14 +282,12 @@ protected String readFieldID() throws IOException { return readShortString(); } - // stream - @Override public void close() throws IOException { try { read(); throw new StreamCorruptedException("Stream still has data, at least " + (1 + in.available()) + " bytes remain"); - } catch (final EOFException e) {} finally { + } catch (EOFException ignored) {} finally { in.close(); } } diff --git a/src/main/java/ch/njol/yggdrasil/DefaultYggdrasilOutputStream.java b/src/main/java/ch/njol/yggdrasil/DefaultYggdrasilOutputStream.java index 139a50b6e00..f77b8c2405c 100644 --- a/src/main/java/ch/njol/yggdrasil/DefaultYggdrasilOutputStream.java +++ b/src/main/java/ch/njol/yggdrasil/DefaultYggdrasilOutputStream.java @@ -18,81 +18,75 @@ */ package ch.njol.yggdrasil; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + import static ch.njol.yggdrasil.Tag.T_ARRAY; import static ch.njol.yggdrasil.Tag.T_REFERENCE; import static ch.njol.yggdrasil.Tag.getPrimitiveFromWrapper; import static ch.njol.yggdrasil.Tag.getType; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.charset.Charset; -import java.util.HashMap; - public final class DefaultYggdrasilOutputStream extends YggdrasilOutputStream { - private final static Charset UTF_8 = Charset.forName("UTF-8"); - private final OutputStream out; - private final short version; - public DefaultYggdrasilOutputStream(final Yggdrasil y, final OutputStream out) throws IOException { - super(y); + public DefaultYggdrasilOutputStream(Yggdrasil yggdrasil, OutputStream out) throws IOException { + super(yggdrasil); this.out = out; - version = y.version; + version = yggdrasil.version; writeInt(Yggdrasil.MAGIC_NUMBER); writeShort(version); } - // private - - private void write(final int b) throws IOException { + private void write(int b) throws IOException { out.write(b); } @Override - protected void writeTag(final Tag t) throws IOException { - out.write(t.tag); + protected void writeTag(Tag tag) throws IOException { + out.write(tag.tag); } - private final HashMap<String, Integer> writtenShortStrings = new HashMap<>(); + private final Map<String, Integer> writtenShortStrings = new HashMap<>(); int nextShortStringID = 0; /** * Writes a class ID or Field name */ - private void writeShortString(final String s) throws IOException { - if (writtenShortStrings.containsKey(s)) { + private void writeShortString(String string) throws IOException { + if (writtenShortStrings.containsKey(string)) { writeTag(T_REFERENCE); if (version <= 1) - writeInt(writtenShortStrings.get(s)); + writeInt(writtenShortStrings.get(string)); else - writeUnsignedInt(writtenShortStrings.get(s)); + writeUnsignedInt(writtenShortStrings.get(string)); } else { if (nextShortStringID < 0) throw new YggdrasilException("Too many field names/class IDs (max: " + Integer.MAX_VALUE + ")"); - final byte[] d = s.getBytes(UTF_8); + byte[] d = string.getBytes(StandardCharsets.UTF_8); if (d.length >= (T_REFERENCE.tag & 0xFF)) - throw new YggdrasilException("Field name or Class ID too long: " + s); + throw new YggdrasilException("Field name or Class ID too long: " + string); write(d.length); out.write(d); if (d.length > 4) - writtenShortStrings.put(s, nextShortStringID++); + writtenShortStrings.put(string, nextShortStringID++); } } - // Primitives - - private void writeByte(final byte b) throws IOException { + private void writeByte(byte b) throws IOException { write(b & 0xFF); } - private void writeShort(final short s) throws IOException { + private void writeShort(short s) throws IOException { write((s >>> 8) & 0xFF); write(s & 0xFF); } - private void writeUnsignedShort(final short s) throws IOException { + private void writeUnsignedShort(short s) throws IOException { assert s >= 0; if (s <= 0x7f) writeByte((byte) (0x80 | s)); @@ -100,14 +94,14 @@ private void writeUnsignedShort(final short s) throws IOException { writeShort(s); } - private void writeInt(final int i) throws IOException { + private void writeInt(int i) throws IOException { write((i >>> 24) & 0xFF); write((i >>> 16) & 0xFF); write((i >>> 8) & 0xFF); write(i & 0xFF); } - private void writeUnsignedInt(final int i) throws IOException { + private void writeUnsignedInt(int i) throws IOException { assert i >= 0; if (i <= 0x7FFF) writeShort((short) (0x8000 | i)); @@ -115,7 +109,7 @@ private void writeUnsignedInt(final int i) throws IOException { writeInt(i); } - private void writeLong(final long l) throws IOException { + private void writeLong(long l) throws IOException { write((int) ((l >>> 56) & 0xFF)); write((int) ((l >>> 48) & 0xFF)); write((int) ((l >>> 40) & 0xFF)); @@ -126,102 +120,96 @@ private void writeLong(final long l) throws IOException { write((int) (l & 0xFF)); } - private void writeFloat(final float f) throws IOException { + private void writeFloat(float f) throws IOException { writeInt(Float.floatToIntBits(f)); } - private void writeDouble(final double d) throws IOException { + private void writeDouble(double d) throws IOException { writeLong(Double.doubleToLongBits(d)); } - private void writeChar(final char c) throws IOException { + private void writeChar(char c) throws IOException { writeShort((short) c); } - private void writeBoolean(final boolean b) throws IOException { + private void writeBoolean(boolean b) throws IOException { write(b ? 1 : 0); } @Override - protected void writePrimitive_(final Object o) throws IOException { - switch (getPrimitiveFromWrapper(o.getClass())) { + protected void writePrimitive_(Object object) throws IOException { + switch (getPrimitiveFromWrapper(object.getClass())) { case T_BYTE: - writeByte((Byte) o); + writeByte((Byte) object); break; case T_SHORT: - writeShort((Short) o); + writeShort((Short) object); break; case T_INT: - writeInt((Integer) o); + writeInt((Integer) object); break; case T_LONG: - writeLong((Long) o); + writeLong((Long) object); break; case T_FLOAT: - writeFloat((Float) o); + writeFloat((Float) object); break; case T_DOUBLE: - writeDouble((Double) o); + writeDouble((Double) object); break; case T_CHAR: - writeChar((Character) o); + writeChar((Character) object); break; case T_BOOLEAN: - writeBoolean((Boolean) o); + writeBoolean((Boolean) object); break; //$CASES-OMITTED$ default: - throw new YggdrasilException("Invalid call to writePrimitive with argument " + o); + throw new YggdrasilException("Invalid call to writePrimitive with argument " + object); } } @Override - protected void writePrimitiveValue(final Object o) throws IOException { - writePrimitive_(o); + protected void writePrimitiveValue(Object object) throws IOException { + writePrimitive_(object); } - // String - @Override - protected void writeStringValue(final String s) throws IOException { - final byte[] d = s.getBytes(UTF_8); + protected void writeStringValue(String string) throws IOException { + byte[] d = string.getBytes(StandardCharsets.UTF_8); writeUnsignedInt(d.length); out.write(d); } - // Array - @Override - protected void writeArrayComponentType(final Class<?> componentType) throws IOException { + protected void writeArrayComponentType(Class<?> componentType) throws IOException { writeClass_(componentType); } @Override - protected void writeArrayLength(final int length) throws IOException { + protected void writeArrayLength(int length) throws IOException { writeUnsignedInt(length); } @Override - protected void writeArrayEnd() throws IOException {} - - // Class + protected void writeArrayEnd() {} @Override - protected void writeClassType(final Class<?> c) throws IOException { - writeClass_(c); + protected void writeClassType(Class<?> type) throws IOException { + writeClass_(type); } - private void writeClass_(Class<?> c) throws IOException { - while (c.isArray()) { + private void writeClass_(Class<?> type) throws IOException { + while (type.isArray()) { writeTag(T_ARRAY); - c = c.getComponentType(); + type = type.getComponentType(); } - final Tag t = getType(c); - switch (t) { + Tag tag = getType(type); + switch (tag) { case T_OBJECT: case T_ENUM: - writeTag(t); - writeShortString(yggdrasil.getID(c)); + writeTag(tag); + writeShortString(yggdrasil.getID(type)); break; case T_BOOLEAN: case T_BOOLEAN_OBJ: @@ -241,57 +229,49 @@ private void writeClass_(Class<?> c) throws IOException { case T_SHORT_OBJ: case T_CLASS: case T_STRING: - writeTag(t); + writeTag(tag); break; case T_NULL: case T_REFERENCE: case T_ARRAY: default: - throw new YggdrasilException("" + c.getCanonicalName()); + throw new YggdrasilException("" + type.getCanonicalName()); } } - // Enum - @Override - protected void writeEnumType(final String type) throws IOException { + protected void writeEnumType(String type) throws IOException { writeShortString(type); } @Override - protected void writeEnumID(final String id) throws IOException { + protected void writeEnumID(String id) throws IOException { writeShortString(id); } - // generic Object - @Override - protected void writeObjectType(final String type) throws IOException { + protected void writeObjectType(String type) throws IOException { writeShortString(type); } @Override - protected void writeNumFields(final short numFields) throws IOException { + protected void writeNumFields(short numFields) throws IOException { writeUnsignedShort(numFields); } @Override - protected void writeFieldID(final String id) throws IOException { + protected void writeFieldID(String id) throws IOException { writeShortString(id); } @Override - protected void writeObjectEnd() throws IOException {} - - // Reference + protected void writeObjectEnd() {} @Override - protected void writeReferenceID(final int ref) throws IOException { - writeUnsignedInt(ref); + protected void writeReferenceID(int reference) throws IOException { + writeUnsignedInt(reference); } - // stream - @Override public void flush() throws IOException { out.flush(); diff --git a/src/main/java/ch/njol/yggdrasil/FieldHandler.java b/src/main/java/ch/njol/yggdrasil/FieldHandler.java index c912924ff9f..ea894c68088 100644 --- a/src/main/java/ch/njol/yggdrasil/FieldHandler.java +++ b/src/main/java/ch/njol/yggdrasil/FieldHandler.java @@ -18,39 +18,39 @@ */ package ch.njol.yggdrasil; +import ch.njol.yggdrasil.Fields.FieldContext; + import java.io.StreamCorruptedException; import java.lang.reflect.Field; -import ch.njol.yggdrasil.Fields.FieldContext; - public interface FieldHandler { /** * Called when a loaded field doesn't exist. * - * @param o The object whose filed is missing + * @param object The object whose filed is missing * @param field The field read from stream * @return Whether this Handler handled the request */ - public boolean excessiveField(Object o, FieldContext field) throws StreamCorruptedException; + boolean excessiveField(Object object, FieldContext field) throws StreamCorruptedException; /** * Called if a field was not found in the stream. * - * @param o The object whose filed is missing + * @param object The object whose filed is missing * @param field The field that didn't occur in the stream * @return Whether this Handler handled the request */ - public boolean missingField(Object o, Field field) throws StreamCorruptedException; + boolean missingField(Object object, Field field) throws StreamCorruptedException; /** - * Called when a loaded value is not compatible with the type of a field. + * Called when a loaded value is not compatible with the type of field. * - * @param o The object the field belongs to - * @param f The field to set - * @param field The field read from stream + * @param object The object the field belongs to + * @param field The field to set + * @param context The field read from stream * @return Whether this Handler handled the request */ - public boolean incompatibleField(Object o, Field f, FieldContext field) throws StreamCorruptedException; + boolean incompatibleField(Object object, Field field, FieldContext context) throws StreamCorruptedException; } diff --git a/src/main/java/ch/njol/yggdrasil/Fields.java b/src/main/java/ch/njol/yggdrasil/Fields.java index 11d8b18fadc..36198ead5e0 100644 --- a/src/main/java/ch/njol/yggdrasil/Fields.java +++ b/src/main/java/ch/njol/yggdrasil/Fields.java @@ -18,6 +18,11 @@ */ package ch.njol.yggdrasil; +import ch.njol.yggdrasil.Fields.FieldContext; +import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilRobustSerializable; +import org.eclipse.jdt.annotation.Nullable; + +import javax.annotation.concurrent.NotThreadSafe; import java.io.NotSerializableException; import java.io.StreamCorruptedException; import java.lang.reflect.Field; @@ -31,40 +36,31 @@ import java.util.Map; import java.util.Set; -import javax.annotation.concurrent.NotThreadSafe; - -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.yggdrasil.Fields.FieldContext; // required - wtf -import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilRobustSerializable; - @NotThreadSafe public final class Fields implements Iterable<FieldContext> { /** - * Holds a field's name and value, and throws {@link StreamCorruptedException}s if primitives or objects are used incorrectly. - * - * @author Peter Güttinger + * Holds a field's name and value, and throws {@link StreamCorruptedException}s + * if primitives or objects are used incorrectly. */ @NotThreadSafe - public final static class FieldContext { + public static final class FieldContext { final String id; /** not null if this {@link #isPrimitiveValue is a primitive} */ @Nullable private Object value; - private boolean isPrimitiveValue; - FieldContext(final String id) { + FieldContext(String id) { this.id = id; } - FieldContext(final Field f, final Object o) throws IllegalArgumentException, IllegalAccessException { - id = Yggdrasil.getID(f); - value = f.get(o); - isPrimitiveValue = f.getType().isPrimitive(); + FieldContext(Field field, Object object) throws IllegalArgumentException, IllegalAccessException { + id = Yggdrasil.getID(field); + value = field.get(object); + isPrimitiveValue = field.getType().isPrimitive(); } public String getID() { @@ -77,12 +73,12 @@ public boolean isPrimitive() { @Nullable public Class<?> getType() { - final Object value = this.value; + Object value = this.value; if (value == null) return null; - final Class<?> c = value.getClass(); - assert c != null; - return isPrimitiveValue ? Tag.getPrimitiveFromWrapper(c).c : c; + Class<?> type = value.getClass(); + assert type != null; + return isPrimitiveValue ? Tag.getPrimitiveFromWrapper(type).type : type; } @Nullable @@ -94,10 +90,10 @@ public Object getObject() throws StreamCorruptedException { @SuppressWarnings("unchecked") @Nullable - public <T> T getObject(final Class<T> expectedType) throws StreamCorruptedException { + public <T> T getObject(Class<T> expectedType) throws StreamCorruptedException { if (isPrimitiveValue) throw new StreamCorruptedException("field " + id + " is a primitive, but expected " + expectedType); - final Object value = this.value; + Object value = this.value; if (value != null && !expectedType.isInstance(value)) throw new StreamCorruptedException("Field " + id + " of " + value.getClass() + ", but expected " + expectedType); return (T) value; @@ -111,42 +107,42 @@ public Object getPrimitive() throws StreamCorruptedException { } @SuppressWarnings("unchecked") - public <T> T getPrimitive(final Class<T> expectedType) throws StreamCorruptedException { + public <T> T getPrimitive(Class<T> expectedType) throws StreamCorruptedException { if (!isPrimitiveValue) throw new StreamCorruptedException("field " + id + " is not a primitive, but expected " + expectedType); assert expectedType.isPrimitive() || Tag.isWrapper(expectedType); - final Object value = this.value; + Object value = this.value; assert value != null; if (!(expectedType.isPrimitive() ? Tag.getWrapperClass(expectedType).isInstance(value) : expectedType.isInstance(value))) throw new StreamCorruptedException("Field " + id + " of " + value.getClass() + ", but expected " + expectedType); return (T) value; } - public void setObject(final @Nullable Object value) { + public void setObject(@Nullable Object value) { this.value = value; isPrimitiveValue = false; } - public void setPrimitive(final Object value) { - assert value != null && Tag.isWrapper(value.getClass()); + public void setPrimitive(Object value) { + assert Tag.isWrapper(value.getClass()); this.value = value; isPrimitiveValue = true; } - public void setField(final Object o, final Field f, final Yggdrasil y) throws StreamCorruptedException { - if (Modifier.isStatic(f.getModifiers())) - throw new StreamCorruptedException("The field " + id + " of " + f.getDeclaringClass() + " is static"); - if (Modifier.isTransient(f.getModifiers())) - throw new StreamCorruptedException("The field " + id + " of " + f.getDeclaringClass() + " is transient"); - if (f.getType().isPrimitive() != isPrimitiveValue) - throw new StreamCorruptedException("The field " + id + " of " + f.getDeclaringClass() + " is " + (f.getType().isPrimitive() ? "" : "not ") + "primitive"); + public void setField(Object object, Field field, Yggdrasil yggdrasil) throws StreamCorruptedException { + if (Modifier.isStatic(field.getModifiers())) + throw new StreamCorruptedException("The field " + id + " of " + field.getDeclaringClass() + " is static"); + if (Modifier.isTransient(field.getModifiers())) + throw new StreamCorruptedException("The field " + id + " of " + field.getDeclaringClass() + " is transient"); + if (field.getType().isPrimitive() != isPrimitiveValue) + throw new StreamCorruptedException("The field " + id + " of " + field.getDeclaringClass() + " is " + (field.getType().isPrimitive() ? "" : "not ") + "primitive"); try { - f.setAccessible(true); - f.set(o, value); - } catch (final IllegalArgumentException e) { - if (!(o instanceof YggdrasilRobustSerializable) || !((YggdrasilRobustSerializable) o).incompatibleField(f, this)) - y.incompatibleField(o, f, this); - } catch (final IllegalAccessException e) { + field.setAccessible(true); + field.set(object, value); + } catch (IllegalArgumentException e) { + if (!(object instanceof YggdrasilRobustSerializable) || !((YggdrasilRobustSerializable) object).incompatibleField(field, this)) + yggdrasil.incompatibleField(object, field, this); + } catch (IllegalAccessException e) { assert false; } } @@ -157,14 +153,14 @@ public int hashCode() { } @Override - public boolean equals(final @Nullable Object obj) { - if (this == obj) + public boolean equals(@Nullable Object object) { + if (this == object) return true; - if (obj == null) + if (object == null) return false; - if (!(obj instanceof FieldContext)) + if (!(object instanceof FieldContext)) return false; - final FieldContext other = (FieldContext) obj; + FieldContext other = (FieldContext) object; return id.equals(other.id); } @@ -182,130 +178,132 @@ public Fields() { yggdrasil = null; } - public Fields(final Yggdrasil yggdrasil) { + public Fields(Yggdrasil yggdrasil) { this.yggdrasil = yggdrasil; } /** - * Creates a fields object and initialises it with all non-transient and non-static fields of the given class and its superclasses. + * Creates a fields object and initialises it with all non-transient and + * non-static fields of the given class and its superclasses. * - * @param c Some class - * @throws NotSerializableException If a field occurs more than once (i.e. if a class has a field with the same name as a field in one of its superclasses) + * @param type Some class + * @throws NotSerializableException If a field occurs more than once (i.e. if a class has a + * field with the same name as a field in one of its superclasses) */ - public Fields(final Class<?> c, final Yggdrasil yggdrasil) throws NotSerializableException { + public Fields(Class<?> type, Yggdrasil yggdrasil) throws NotSerializableException { this.yggdrasil = yggdrasil; - for (final Field f : getFields(c)) { - assert f != null; - final String id = Yggdrasil.getID(f); + for (Field field : getFields(type)) { + assert field != null; + String id = Yggdrasil.getID(field); fields.put(id, new FieldContext(id)); } } /** - * Creates a fields object and initialises it with all non-transient and non-static fields of the given object. + * Creates a fields object and initialises it with all non-transient + * and non-static fields of the given object. * - * @param o Some object - * @throws NotSerializableException If a field occurs more than once (i.e. if a class has a field with the same name as a field in one of its superclasses) + * @param object Some object + * @throws NotSerializableException If a field occurs more than once (i.e. if a class + * has a field with the same name as a field in one of its superclasses) */ - public Fields(final Object o) throws NotSerializableException { - this(o, null); + public Fields(Object object) throws NotSerializableException { + this(object, null); } /** - * Creates a fields object and initialises it with all non-transient and non-static fields of the given object. + * Creates a fields object and initialises it with all non-transient + * and non-static fields of the given object. * - * @param o Some object - * @throws NotSerializableException If a field occurs more than once (i.e. if a class has a field with the same name as a field in one of its superclasses) + * @param object Some object + * @throws NotSerializableException If a field occurs more than once (i.e. if a class + * has a field with the same name as a field in one of its superclasses) */ - public Fields(final Object o, @Nullable final Yggdrasil yggdrasil) throws NotSerializableException { + public Fields(Object object, @Nullable Yggdrasil yggdrasil) throws NotSerializableException { this.yggdrasil = yggdrasil; - final Class<?> c = o.getClass(); - assert c != null; - for (final Field f : getFields(c)) { - assert f != null; + Class<?> type = object.getClass(); + assert type != null; + for (Field field : getFields(type)) { + assert field != null; try { - fields.put(Yggdrasil.getID(f), new FieldContext(f, o)); - } catch (final IllegalArgumentException e) { - assert false; - } catch (final IllegalAccessException e) { + fields.put(Yggdrasil.getID(field), new FieldContext(field, object)); + } catch (IllegalArgumentException | IllegalAccessException e) { assert false; } } } - private final static Map<Class<?>, Collection<Field>> cache = new HashMap<>(); + private static final Map<Class<?>, Collection<Field>> cache = new HashMap<>(); /** - * Gets all serialisable fields of the provided class, including superclasses. + * Gets all serializable fields of the provided class, including superclasses. * - * @param c The class to get the fields of + * @param type The class to get the fields of * @return All non-static and non-transient fields of the given class and its superclasses - * @throws NotSerializableException If a field occurs more than once (i.e. if a class has a field with the same name as a field in one of its superclasses) + * @throws NotSerializableException If a field occurs more than once (i.e. if a class has a + * field with the same name as a field in one of its superclasses) */ - public static Collection<Field> getFields(final Class<?> c) throws NotSerializableException { - Collection<Field> fields = cache.get(c); + public static Collection<Field> getFields(Class<?> type) throws NotSerializableException { + Collection<Field> fields = cache.get(type); if (fields != null) return fields; fields = new ArrayList<>(); - final Set<String> ids = new HashSet<>(); - for (Class<?> sc = c; sc != null; sc = sc.getSuperclass()) { - final Field[] fs = sc.getDeclaredFields(); - for (final Field f : fs) { - final int m = f.getModifiers(); - if (Modifier.isStatic(m) || Modifier.isTransient(m)) + Set<String> ids = new HashSet<>(); + for (Class<?> superClass = type; superClass != null; superClass = superClass.getSuperclass()) { + Field[] declaredFields = superClass.getDeclaredFields(); + for (Field field : declaredFields) { + int modifiers = field.getModifiers(); + if (field.isSynthetic() || Modifier.isStatic(modifiers) || Modifier.isTransient(modifiers)) continue; - final String id = Yggdrasil.getID(f); + String id = Yggdrasil.getID(field); if (ids.contains(id)) - throw new NotSerializableException(c + "/" + sc + ": duplicate field id '" + id + "'"); - f.setAccessible(true); - fields.add(f); + throw new NotSerializableException(type + "/" + superClass + ": duplicate field id '" + id + "'"); + field.setAccessible(true); + fields.add(field); ids.add(id); } } fields = Collections.unmodifiableCollection(fields); - assert fields != null; - cache.put(c, fields); + cache.put(type, fields); return fields; } /** * Sets all fields of the given Object to the values stored in this Fields object. * - * @param o The object whose fields should be set - * @throws StreamCorruptedException - * @throws NotSerializableException + * @param object The object whose fields should be set * @throws YggdrasilException If this was called on a Fields object not created by Yggdrasil itself */ - public void setFields(final Object o) throws StreamCorruptedException, NotSerializableException { - final Yggdrasil y = yggdrasil; - if (y == null) + public void setFields(Object object) throws StreamCorruptedException, NotSerializableException { + Yggdrasil yggdrasil = this.yggdrasil; + if (yggdrasil == null) throw new YggdrasilException(""); - final Set<FieldContext> excessive = new HashSet<>(fields.values()); - final Class<?> oc = o.getClass(); - assert oc != null; - for (final Field f : getFields(oc)) { - assert f != null; - final String id = Yggdrasil.getID(f); - final FieldContext c = fields.get(id); - if (c == null) { - if (!(o instanceof YggdrasilRobustSerializable) || !((YggdrasilRobustSerializable) o).missingField(f)) - y.missingField(o, f); + Set<FieldContext> excessive = new HashSet<>(fields.values()); + Class<?> type = object.getClass(); + assert type != null; + for (Field field : getFields(type)) { + assert field != null; + String id = Yggdrasil.getID(field); + FieldContext context = fields.get(id); + if (context == null) { + if (!(object instanceof YggdrasilRobustSerializable) || !((YggdrasilRobustSerializable) object).missingField(field)) + yggdrasil.missingField(object, field); } else { - c.setField(o, f, y); + context.setField(object, field, yggdrasil); } - excessive.remove(c); + excessive.remove(context); } - for (final FieldContext f : excessive) { - assert f != null; - if (!(o instanceof YggdrasilRobustSerializable) || !((YggdrasilRobustSerializable) o).excessiveField(f)) - y.excessiveField(o, f); + for (FieldContext context : excessive) { + assert context != null; + if (!(object instanceof YggdrasilRobustSerializable) || !((YggdrasilRobustSerializable) object).excessiveField(context)) + yggdrasil.excessiveField(object, context); } } @Deprecated - public void setFields(final Object o, final Yggdrasil y) throws StreamCorruptedException, NotSerializableException { - assert yggdrasil == y; - setFields(o); + public void setFields(Object object, Yggdrasil yggdrasil) throws StreamCorruptedException, NotSerializableException { + assert this.yggdrasil == yggdrasil; + setFields(object); } /** @@ -315,75 +313,75 @@ public int size() { return fields.size(); } - public void putObject(final String fieldID, final @Nullable Object value) { - FieldContext c = fields.get(fieldID); - if (c == null) - fields.put(fieldID, c = new FieldContext(fieldID)); - c.setObject(value); + public void putObject(String fieldID, @Nullable Object value) { + FieldContext context = fields.get(fieldID); + if (context == null) + fields.put(fieldID, context = new FieldContext(fieldID)); + context.setObject(value); } - public void putPrimitive(final String fieldID, final Object value) { - FieldContext c = fields.get(fieldID); - if (c == null) - fields.put(fieldID, c = new FieldContext(fieldID)); - c.setPrimitive(value); + public void putPrimitive(String fieldID, Object value) { + FieldContext context = fields.get(fieldID); + if (context == null) + fields.put(fieldID, context = new FieldContext(fieldID)); + context.setPrimitive(value); } /** * @param fieldID A field's id * @return Whether the field is defined */ - public boolean contains(final String fieldID) { + public boolean contains(String fieldID) { return fields.containsKey(fieldID); } public boolean hasField(String fieldID) { - return this.fields.containsKey(fieldID); + return fields.containsKey(fieldID); } @Nullable - public Object getObject(final String field) throws StreamCorruptedException { - final FieldContext c = fields.get(field); - if (c == null) + public Object getObject(String field) throws StreamCorruptedException { + FieldContext context = fields.get(field); + if (context == null) throw new StreamCorruptedException("Nonexistent field " + field); - return c.getObject(); + return context.getObject(); } @Nullable - public <T> T getObject(final String fieldID, final Class<T> expectedType) throws StreamCorruptedException { + public <T> T getObject(String fieldID, Class<T> expectedType) throws StreamCorruptedException { assert !expectedType.isPrimitive(); - final FieldContext c = fields.get(fieldID); - if (c == null) + FieldContext context = fields.get(fieldID); + if (context == null) throw new StreamCorruptedException("Nonexistent field " + fieldID); - return c.getObject(expectedType); + return context.getObject(expectedType); } - public Object getPrimitive(final String fieldID) throws StreamCorruptedException { - final FieldContext c = fields.get(fieldID); - if (c == null) + public Object getPrimitive(String fieldID) throws StreamCorruptedException { + FieldContext context = fields.get(fieldID); + if (context == null) throw new StreamCorruptedException("Nonexistent field " + fieldID); - return c.getPrimitive(); + return context.getPrimitive(); } - public <T> T getPrimitive(final String fieldID, final Class<T> expectedType) throws StreamCorruptedException { + public <T> T getPrimitive(String fieldID, Class<T> expectedType) throws StreamCorruptedException { assert expectedType.isPrimitive() || Tag.getPrimitiveFromWrapper(expectedType).isPrimitive(); - final FieldContext c = fields.get(fieldID); - if (c == null) + FieldContext context = fields.get(fieldID); + if (context == null) throw new StreamCorruptedException("Nonexistent field " + fieldID); - return c.getPrimitive(expectedType); + return context.getPrimitive(expectedType); } @Nullable - public <T> T getAndRemoveObject(final String field, final Class<T> expectedType) throws StreamCorruptedException { - final T t = getObject(field, expectedType); + public <T> T getAndRemoveObject(String field, Class<T> expectedType) throws StreamCorruptedException { + T object = getObject(field, expectedType); removeField(field); - return t; + return object; } - public <T> T getAndRemovePrimitive(final String field, final Class<T> expectedType) throws StreamCorruptedException { - final T t = getPrimitive(field, expectedType); + public <T> T getAndRemovePrimitive(String field, Class<T> expectedType) throws StreamCorruptedException { + T object = getPrimitive(field, expectedType); removeField(field); - return t; + return object; } /** @@ -392,7 +390,7 @@ public <T> T getAndRemovePrimitive(final String field, final Class<T> expectedTy * @param fieldID The id of the field to remove * @return Whether a field with the given name was actually defined */ - public boolean removeField(final String fieldID) { + public boolean removeField(String fieldID) { return fields.remove(fieldID) != null; } diff --git a/src/main/java/ch/njol/yggdrasil/JRESerializer.java b/src/main/java/ch/njol/yggdrasil/JRESerializer.java index f08c83831e1..fdd2bfafaaa 100644 --- a/src/main/java/ch/njol/yggdrasil/JRESerializer.java +++ b/src/main/java/ch/njol/yggdrasil/JRESerializer.java @@ -18,6 +18,9 @@ */ package ch.njol.yggdrasil; +import com.google.common.collect.ImmutableList; +import org.eclipse.jdt.annotation.Nullable; + import java.io.NotSerializableException; import java.io.StreamCorruptedException; import java.util.ArrayList; @@ -26,75 +29,68 @@ import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; +import java.util.List; import java.util.Map; -import java.util.Set; import java.util.UUID; -import org.eclipse.jdt.annotation.Nullable; - public class JRESerializer extends YggdrasilSerializer<Object> { - private final static Class<?>[] supportedClasses = { - ArrayList.class, LinkedList.class, - HashSet.class, - HashMap.class, - UUID.class - }; - - private final static Set<Class<?>> set = new HashSet<>(Arrays.asList(supportedClasses)); + private static final List<Class<?>> SUPPORTED_CLASSES = ImmutableList.of( + ArrayList.class, LinkedList.class, HashSet.class, HashMap.class, UUID.class); @Override @Nullable - public Class<?> getClass(final String id) { - for (final Class<?> c : supportedClasses) - if (c.getSimpleName().equals(id)) - return c; + public Class<?> getClass(String id) { + for (Class<?> type : SUPPORTED_CLASSES) + if (type.getSimpleName().equals(id)) + return type; return null; } @Override @Nullable - public String getID(final Class<?> c) { - if (set.contains(c)) - return c.getSimpleName(); + public String getID(Class<?> type) { + if (SUPPORTED_CLASSES.contains(type)) + return type.getSimpleName(); return null; } @Override - public Fields serialize(final Object o) { - if (!set.contains(o.getClass())) + public Fields serialize(Object object) { + if (!SUPPORTED_CLASSES.contains(object.getClass())) throw new IllegalArgumentException(); - final Fields f = new Fields(); - if (o instanceof Collection) { - final Collection<?> c = ((Collection<?>) o); - f.putObject("values", c.toArray()); - } else if (o instanceof Map) { - final Map<?, ?> m = ((Map<?, ?>) o); - f.putObject("keys", m.keySet().toArray()); - f.putObject("values", m.values().toArray()); - } else if (o instanceof UUID) { - f.putPrimitive("mostSigBits", Long.valueOf(((UUID)o).getMostSignificantBits())); - f.putPrimitive("leastSigBits", Long.valueOf(((UUID)o).getLeastSignificantBits())); + Fields fields = new Fields(); + if (object instanceof Collection) { + Collection<?> collection = ((Collection<?>) object); + fields.putObject("values", collection.toArray()); + } else if (object instanceof Map) { + Map<?, ?> map = ((Map<?, ?>) object); + fields.putObject("keys", map.keySet().toArray()); + fields.putObject("values", map.values().toArray()); + } else if (object instanceof UUID) { + fields.putPrimitive("mostSigBits", ((UUID) object).getMostSignificantBits()); + fields.putPrimitive("leastSigBits", ((UUID) object).getLeastSignificantBits()); } - assert f.size() > 0 : o; - return f; + assert fields.size() > 0 : object; + return fields; } @Override - public boolean canBeInstantiated(Class<? extends Object> c){ - return c != UUID.class; + public boolean canBeInstantiated(Class<?> type) { + return type != UUID.class; } @Override @Nullable - public <T> T newInstance(final Class<T> c) { + public <T> T newInstance(Class<T> type) { try { - return c.newInstance(); - } catch (final InstantiationException e) { // all collections handled here have public nullary constructors + //noinspection deprecation + return type.newInstance(); + } catch (InstantiationException e) { // all collections handled here have public nullary constructors e.printStackTrace(); assert false; return null; - } catch (final IllegalAccessException e) { + } catch (IllegalAccessException e) { e.printStackTrace(); assert false; return null; @@ -103,25 +99,25 @@ public <T> T newInstance(final Class<T> c) { @SuppressWarnings({"unchecked", "rawtypes"}) @Override - public void deserialize(final Object o, final Fields fields) throws StreamCorruptedException { + public void deserialize(Object object, Fields fields) throws StreamCorruptedException { try { - if (o instanceof Collection) { - final Collection<?> c = ((Collection<?>) o); - final Object[] values = fields.getObject("values", Object[].class); + if (object instanceof Collection) { + Collection<?> collection = ((Collection<?>) object); + Object[] values = fields.getObject("values", Object[].class); if (values == null) throw new StreamCorruptedException(); - c.addAll((Collection) Arrays.asList(values)); + collection.addAll((Collection) Arrays.asList(values)); return; - } else if (o instanceof Map) { - final Map<?, ?> m = ((Map<?, ?>) o); - final Object[] keys = fields.getObject("keys", Object[].class), values = fields.getObject("values", Object[].class); + } else if (object instanceof Map) { + Map<?, ?> map = ((Map<?, ?>) object); + Object[] keys = fields.getObject("keys", Object[].class), values = fields.getObject("values", Object[].class); if (keys == null || values == null || keys.length != values.length) throw new StreamCorruptedException(); for (int i = 0; i < keys.length; i++) - ((Map) m).put(keys[i], values[i]); + ((Map) map).put(keys[i], values[i]); return; } - } catch (final Exception e) { + } catch (Exception e) { throw new StreamCorruptedException(e.getClass().getSimpleName() + ": " + e.getMessage()); } throw new StreamCorruptedException(); @@ -129,10 +125,12 @@ public void deserialize(final Object o, final Fields fields) throws StreamCorrup @SuppressWarnings({"unchecked", "null"}) @Override - public <E> E deserialize(Class<E> c, Fields fields) throws StreamCorruptedException, NotSerializableException { - if (c == UUID.class) { - return (E) new UUID(fields.getPrimitive("mostSigBits", Long.TYPE).longValue(), fields.getPrimitive("leastSigBits", Long.TYPE).longValue()); - } + public <E> E deserialize(Class<E> type, Fields fields) throws StreamCorruptedException, NotSerializableException { + if (type == UUID.class) + return (E) new UUID( + fields.getPrimitive("mostSigBits", Long.TYPE), + fields.getPrimitive("leastSigBits", Long.TYPE)); + throw new StreamCorruptedException(); } diff --git a/src/main/java/ch/njol/yggdrasil/PseudoEnum.java b/src/main/java/ch/njol/yggdrasil/PseudoEnum.java index 259f58adc73..a3d2b5ed6a7 100644 --- a/src/main/java/ch/njol/yggdrasil/PseudoEnum.java +++ b/src/main/java/ch/njol/yggdrasil/PseudoEnum.java @@ -18,6 +18,9 @@ */ package ch.njol.yggdrasil; +import org.eclipse.jdt.annotation.Nullable; + +import javax.annotation.concurrent.ThreadSafe; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -26,27 +29,23 @@ import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; -import javax.annotation.concurrent.ThreadSafe; - -import org.eclipse.jdt.annotation.Nullable; - /** - * A class that acts as a "pseudo-enum", i.e. a class which only has immutable, (public,) final static instances, which can be identified by their unique name. The instances don't - * even have to be defined in their class, as they are registered in the constructor. + * A class that acts as a "pseudo-enum", i.e. a class which only has immutable, (public,) static final instances, + * which can be identified by their unique name. The instances don't even have to be defined in their class, + * as they are registered in the constructor. * <p> - * Please note that you cannot define a constant's id used for saving by annotating it with {@link YggdrasilID @YggdrasilID}, as the field(s) of the constant may not be known, and + * Please note that you cannot define a constant's id used for saving by annotating it with + * {@link YggdrasilID @YggdrasilID}, as the field(s) of the constant may not be known, and * furthermore a constant can be assigned to any number of fields. * <p> - * This class defines methods similar to those in {@link Enum} with minor differences, e.g. {@link #values()} returns a {@link List} instead of an array. - * - * @author Peter Güttinger + * This class defines methods similar to those in {@link Enum} with minor differences, e.g. {@link #values()} returns + * a {@link List} instead of an array. */ @ThreadSafe public abstract class PseudoEnum<T extends PseudoEnum<T>> { private final String name; private final int ordinal; - private final Info<T> info; /** @@ -54,7 +53,7 @@ public abstract class PseudoEnum<T extends PseudoEnum<T>> { * @throws IllegalArgumentException If the given name is already in use. */ @SuppressWarnings("unchecked") - protected PseudoEnum(final String name) throws IllegalArgumentException { + protected PseudoEnum(String name) throws IllegalArgumentException { this.name = name; info = (Info<T>) getInfo(getClass()); info.writeLock.lock(); @@ -113,8 +112,8 @@ public final int hashCode() { * Checks for reference equality (==). */ @Override - public final boolean equals(@Nullable final Object obj) { - return obj == this; + public final boolean equals(@Nullable Object object) { + return object == this; } /** @@ -129,8 +128,9 @@ protected final Object clone() throws CloneNotSupportedException { } /** - * Returns this constant's pseudo-enum class, i.e. the first non-anonymous superclass of this constant. This class is the same for all constants inheriting from a common class - * independently from whether they define an anonymous subclass. + * Returns this constant's pseudo-enum class, i.e. the first non-anonymous superclass of this constant. + * This class is the same for all constants inheriting from a common class + * independently of whether they define an anonymous subclass. * * @return This constant's pseudo-enum class. * @see Enum#getDeclaringClass() @@ -146,11 +146,11 @@ public final Class<T> getDeclaringClass() { * @return The pseudo-enum class of the given class. * @see Enum#getDeclaringClass() */ - public static <T extends PseudoEnum<T>> Class<? super T> getDeclaringClass(final Class<T> type) { - Class<? super T> c = type; - while (c.isAnonymousClass()) - c = c.getSuperclass(); - return c; + public static <T extends PseudoEnum<T>> Class<? super T> getDeclaringClass(Class<T> type) { + Class<? super T> superClass = type; + while (superClass.isAnonymousClass()) + superClass = superClass.getSuperclass(); + return superClass; } /** @@ -164,26 +164,26 @@ public static <T extends PseudoEnum<T>> Class<? super T> getDeclaringClass(final * @see Enum#valueOf(Class, String) */ public final List<T> values() { - return values(getDeclaringClass(), info); + return values(getDeclaringClass()); } /** - * Returns all constants of the given class registered so far, ordered by their {@link #ordinal() id} (i.e. <tt>c.values()[c.ordinal()] == c</tt> is true for any constant - * c). + * Returns all constants of the given class registered so far, ordered by their {@link #ordinal() id} (i.e. <tt>type.values()[type.ordinal()] == type</tt> is true for any constant + * type). * <p> * The returned list is a copy of the internal list at the time this method was called. * * @return All constants registered so far. - * @throws IllegalArgumentException If <tt>{@link #getDeclaringClass(Class) getDeclaringClass}(c) != c</tt> (i.e. if the given class is anonymous). + * @throws IllegalArgumentException If <tt>{@link #getDeclaringClass(Class) getDeclaringClass}(type) != type</tt> (i.e. if the given class is anonymous). * @see Enum#valueOf(Class, String) */ - public static <T extends PseudoEnum<T>> List<T> values(final Class<T> c) throws IllegalArgumentException { - if (c != getDeclaringClass(c)) - throw new IllegalArgumentException(c + " != " + getDeclaringClass(c)); - return values(c, getInfo(c)); + public static <T extends PseudoEnum<T>> List<T> values(Class<T> type) throws IllegalArgumentException { + if (type != getDeclaringClass(type)) + throw new IllegalArgumentException(type + " != " + getDeclaringClass(type)); + return values(getInfo(type)); } - private static <T extends PseudoEnum<T>> List<T> values(final Class<T> c, final Info<T> info) { + private static <T extends PseudoEnum<T>> List<T> values(Info<T> info) { info.readLock.lock(); try { return new ArrayList<>(info.values); @@ -200,8 +200,7 @@ private static <T extends PseudoEnum<T>> List<T> values(final Class<T> c, final * @throws IndexOutOfBoundsException if ID is < 0 or >= {@link #numConstants()} * @see #valueOf(String) */ - @SuppressWarnings("null") - public final T getConstant(final int id) throws IndexOutOfBoundsException { + public final T getConstant(int id) throws IndexOutOfBoundsException { info.readLock.lock(); try { return info.values.get(id); @@ -228,7 +227,7 @@ public final int numConstants() { * @see Enum#valueOf(Class, String) */ @Nullable - public final T valueOf(final String name) { + public final T valueOf(String name) { info.readLock.lock(); try { return info.map.get(name); @@ -238,14 +237,14 @@ public final T valueOf(final String name) { } /** - * @param c The class of the constant to find + * @param type The class of the constant to find * @param name The name of the constant to find * @return The constant with the given name, or null if no constant with that exact name was found in the given class. * @see Enum#valueOf(Class, String) */ @Nullable - public static <T extends PseudoEnum<T>> T valueOf(final Class<T> c, final String name) { - final Info<T> info = getInfo(c); + public static <T extends PseudoEnum<T>> T valueOf(Class<T> type, String name) { + Info<T> info = getInfo(type); info.readLock.lock(); try { return info.map.get(name); @@ -254,27 +253,24 @@ public static <T extends PseudoEnum<T>> T valueOf(final Class<T> c, final String } } - @SuppressWarnings("null") - private final static class Info<T extends PseudoEnum<T>> { + private static final class Info<T extends PseudoEnum<T>> { + final List<T> values = new ArrayList<>(); final Map<String, T> map = new HashMap<>(); final ReadWriteLock lock = new ReentrantReadWriteLock(true); final Lock readLock = lock.readLock(), writeLock = lock.writeLock(); - public Info() {} } - /** - * Must be synchronised - */ - private final static Map<Class<? extends PseudoEnum<?>>, Info<?>> infos = new HashMap<>(); + private static final Map<Class<? extends PseudoEnum<?>>, Info<?>> infos = new HashMap<>(); - private static <T extends PseudoEnum<T>> Info<T> getInfo(final Class<T> c) { + @SuppressWarnings("unchecked") + private static <T extends PseudoEnum<T>> Info<T> getInfo(Class<T> type) { synchronized (infos) { - Info<T> info = (Info<T>) infos.get(getDeclaringClass(c)); + Info<T> info = (Info<T>) infos.get(getDeclaringClass(type)); if (info == null) - infos.put(c, info = new Info<>()); + infos.put(type, info = new Info<>()); return info; } } diff --git a/src/main/java/ch/njol/yggdrasil/SimpleClassResolver.java b/src/main/java/ch/njol/yggdrasil/SimpleClassResolver.java index 9d66d9dc858..05bf2ade436 100644 --- a/src/main/java/ch/njol/yggdrasil/SimpleClassResolver.java +++ b/src/main/java/ch/njol/yggdrasil/SimpleClassResolver.java @@ -18,34 +18,33 @@ */ package ch.njol.yggdrasil; -import javax.annotation.concurrent.NotThreadSafe; - +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; import org.eclipse.jdt.annotation.Nullable; -import ch.njol.util.coll.BidiHashMap; -import ch.njol.util.coll.BidiMap; +import javax.annotation.concurrent.NotThreadSafe; @NotThreadSafe public class SimpleClassResolver implements ClassResolver { - private final BidiMap<Class<?>, String> classes = new BidiHashMap<>(); + private final BiMap<Class<?>, String> classes = HashBiMap.create(); - public void registerClass(final Class<?> c, final String id) { - final String oldId = classes.put(c, id); + public void registerClass(Class<?> type, String id) { + String oldId = classes.put(type, id); if (oldId != null && !oldId.equals(id)) - throw new YggdrasilException("Changed id of " + c + " from " + oldId + " to " + id); + throw new YggdrasilException("Changed id of " + type + " from " + oldId + " to " + id); } @Override @Nullable - public Class<?> getClass(final String id) { - return classes.getKey(id); + public Class<?> getClass(String id) { + return classes.inverse().get(id); } @Override @Nullable - public String getID(final Class<?> c) { - return classes.getValue(c); + public String getID(Class<?> type) { + return classes.get(type); } } diff --git a/src/main/java/ch/njol/yggdrasil/Tag.java b/src/main/java/ch/njol/yggdrasil/Tag.java index 6a25a82d030..068f3377be1 100644 --- a/src/main/java/ch/njol/yggdrasil/Tag.java +++ b/src/main/java/ch/njol/yggdrasil/Tag.java @@ -18,11 +18,11 @@ */ package ch.njol.yggdrasil; +import org.eclipse.jdt.annotation.Nullable; + import java.util.HashMap; import java.util.Map; -import org.eclipse.jdt.annotation.Nullable; - public enum Tag { /** the null reference */ T_NULL(0x0, null, "null"), @@ -53,20 +53,22 @@ public enum Tag { T_REFERENCE(0xFF, null, "reference"); /** primitive tags are between these value */ - public final static int MIN_PRIMITIVE = T_BYTE.tag, MAX_PRIMITIVE = T_BOOLEAN.tag; + public static final int MIN_PRIMITIVE = T_BYTE.tag; + public static final int MAX_PRIMITIVE = T_BOOLEAN.tag; /** primitive tags are between these value */ - public final static int MIN_WRAPPER = T_BYTE_OBJ.tag, MAX_WRAPPER = T_BOOLEAN_OBJ.tag; + public static final int MIN_WRAPPER = T_BYTE_OBJ.tag; + public static final int MAX_WRAPPER = T_BOOLEAN_OBJ.tag; public final byte tag; @Nullable - public final Class<?> c; + public final Class<?> type; public final String name; - private Tag(final int tag, final @Nullable Class<?> c, final String name) { + Tag(int tag, @Nullable Class<?> type, String name) { assert 0 <= tag && tag <= 0xFF : tag; this.tag = (byte) tag; - this.c = c; + this.type = type; this.name = name; } @@ -92,7 +94,6 @@ public boolean isWrapper() { return MIN_WRAPPER <= tag && tag <= MAX_WRAPPER; } - @SuppressWarnings("null") public Tag getWrapper() { if (!isPrimitive()) { assert false; @@ -101,44 +102,44 @@ public Tag getWrapper() { return byID[tag - MIN_PRIMITIVE + MIN_WRAPPER]; } - private final static Map<Class<?>, Tag> types = new HashMap<>(); - private final static Tag[] byID = new Tag[256]; - private final static Map<String, Tag> byName = new HashMap<>(); + private static final Map<Class<?>, Tag> types = new HashMap<>(); + private static final Tag[] byID = new Tag[256]; + private static final Map<String, Tag> byName = new HashMap<>(); static { - for (final Tag t : Tag.values()) { - types.put(t.c, t); - byID[t.tag & 0xFF] = t; - byName.put(t.name, t); + for (Tag tag : Tag.values()) { + types.put(tag.type, tag); + byID[tag.tag & 0xFF] = tag; + byName.put(tag.name, tag); } } - public static Tag getType(final @Nullable Class<?> c) { - if (c == null) + public static Tag getType(@Nullable Class<?> type) { + if (type == null) return T_NULL; - final Tag t = types.get(c); + Tag t = types.get(type); if (t != null) return t; - return c.isArray() ? T_ARRAY - : Enum.class.isAssignableFrom(c) || PseudoEnum.class.isAssignableFrom(c) ? T_ENUM // isEnum() doesn't work for subclasses + return type.isArray() ? T_ARRAY + : Enum.class.isAssignableFrom(type) || PseudoEnum.class.isAssignableFrom(type) ? T_ENUM // isEnum() doesn't work for subclasses : T_OBJECT; } @Nullable - public static Tag byID(final byte tag) { + public static Tag byID(byte tag) { return byID[tag & 0xFF]; } @Nullable - public static Tag byID(final int tag) { + public static Tag byID(int tag) { return byID[tag]; } @Nullable - public static Tag byName(final String name) { + public static Tag byName(String name) { return byName.get(name); } - private final static HashMap<Class<?>, Tag> wrapperTypes = new HashMap<>(); + private static final HashMap<Class<?>, Tag> wrapperTypes = new HashMap<>(); static { wrapperTypes.put(Byte.class, T_BYTE); wrapperTypes.put(Short.class, T_SHORT); @@ -150,28 +151,28 @@ public static Tag byName(final String name) { wrapperTypes.put(Boolean.class, T_BOOLEAN); } - public static boolean isWrapper(final Class<?> c) { - return wrapperTypes.containsKey(c); + public static boolean isWrapper(Class<?> type) { + return wrapperTypes.containsKey(type); } - public static Tag getPrimitiveFromWrapper(final Class<?> wrapper) { - final Tag t = wrapperTypes.get(wrapper); - if (t == null) { + public static Tag getPrimitiveFromWrapper(Class<?> wrapper) { + Tag tag = wrapperTypes.get(wrapper); + if (tag == null) { assert false : wrapper; return T_NULL; } - return t; + return tag; } - public static Class<?> getWrapperClass(final Class<?> primitive) { + public static Class<?> getWrapperClass(Class<?> primitive) { assert primitive.isPrimitive(); - final Tag t = types.get(primitive); - if (t == null) { + Tag tag = types.get(primitive); + if (tag == null) { assert false : primitive; return Object.class; } - final Class<?> wrapper = t.getWrapper().c; - assert wrapper != null : t; + Class<?> wrapper = tag.getWrapper().type; + assert wrapper != null : tag; return wrapper; } diff --git a/src/main/java/ch/njol/yggdrasil/Yggdrasil.java b/src/main/java/ch/njol/yggdrasil/Yggdrasil.java index e89482f606e..d97a42900af 100644 --- a/src/main/java/ch/njol/yggdrasil/Yggdrasil.java +++ b/src/main/java/ch/njol/yggdrasil/Yggdrasil.java @@ -18,6 +18,13 @@ */ package ch.njol.yggdrasil; +import ch.njol.yggdrasil.Fields.FieldContext; +import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilExtendedSerializable; +import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilRobustEnum; +import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilRobustSerializable; +import org.eclipse.jdt.annotation.Nullable; + +import javax.annotation.concurrent.NotThreadSafe; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -32,60 +39,47 @@ import java.util.ArrayList; import java.util.List; -import javax.annotation.concurrent.NotThreadSafe; - -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.yggdrasil.Fields.FieldContext; -import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilExtendedSerializable; -import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilRobustEnum; -import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilRobustSerializable; -import ch.njol.yggdrasil.xml.YggXMLInputStream; -import ch.njol.yggdrasil.xml.YggXMLOutputStream; - /** * Yggdrasil is a simple data format to store object graphs. * <p> - * Yggdrasil uses String IDs to identify classes, thus all classes to be (de)serialised have to be registered to Yggdrasil before doing anything (they can also be registered while - * Yggdrasil is working, but you must make sure that all classes are registered in time when deserialising). A {@link ClassResolver} or {@link YggdrasilSerializer} can also be used - * to find classes and IDs dynamically. + * Yggdrasil uses String IDs to identify classes, thus all classes to be (de)serialized have to be registered to + * Yggdrasil before doing anything (they can also be registered while Yggdrasil is working, but you must make sure + * that all classes are registered in time when deserializing). A {@link ClassResolver} or + * {@link YggdrasilSerializer} can also be used to find classes and IDs dynamically. * <p> * <b>Default behaviour</b> * <p> - * A Java object can be serialised and deserialised if it is a primitive, a primitive wrapper, a String, an enum or {@link PseudoEnum} (both require an ID), or its class meets all - * of the following requirements: + * A Java object can be serialized and deserialized if it is a primitive, a primitive wrapper, a String, an enum or + * {@link PseudoEnum} (both require an ID), or its class meets all the following requirements: * <ul> * <li>It implements {@link YggdrasilSerializable} * <li>It has an ID assigned to it (using the methods described above) - * <li>It provides a nullary constructor (any access modifier) (in particular anonymous and non-static inner classes can't be serialised) - * <li>All its non-transient and non-static fields are serialisable according to these requirements + * <li>It provides a nullary constructor (any access modifier) (in particular anonymous and non-static inner classes can't be serialized) + * <li>All its non-transient and non-static fields are serializable according to these requirements * </ul> * <p> * Yggdrasil will generate errors if an object loaded either has too many fields and/or is missing some in the stream. * <p> * <b>Customisation</b> * <p> - * Any object that does not meet the above requirements for serialisation can still be (de)serialised using an {@link YggdrasilSerializer} (useful for objects of an external API), - * or by implementing {@link YggdrasilExtendedSerializable}. + * Any object that does not meet the above requirements for serialisation can still be (de)serialized using an + * {@link YggdrasilSerializer} (useful for objects of an external API), or by implementing {@link YggdrasilExtendedSerializable}. * <p> - * The behaviour in case of an invalid or outdated stream can be defined likewise, or one can implement {@link YggdrasilRobustSerializable} or {@link YggdrasilRobustEnum} - * respectively. - * - * @author Peter Güttinger + * The behaviour in case of an invalid or outdated stream can be defined likewise, or one can + * implement {@link YggdrasilRobustSerializable} or {@link YggdrasilRobustEnum} respectively. */ -@SuppressWarnings("deprecation") @NotThreadSafe public final class Yggdrasil { /** - * Magic Number: "Ygg\0" + * Magic Number: "Ygg\0" (('Y' << 24) + ('g' << 16) + ('g' << 8) + '\0') * <p> * hex: 0x59676700 */ - public final static int MAGIC_NUMBER = ('Y' << 24) + ('g' << 16) + ('g' << 8) + '\0'; + public static final int MAGIC_NUMBER = 0x59676700; /** latest protocol version */ - public final static short LATEST_VERSION = 1; // version 2 is only one minor change currently + public static final short LATEST_VERSION = 1; // version 2 is only one minor change currently public final short version; @@ -98,7 +92,7 @@ public Yggdrasil() { this(LATEST_VERSION); } - public Yggdrasil(final short version) { + public Yggdrasil(short version) { if (version <= 0 || version > LATEST_VERSION) throw new YggdrasilException("Unsupported version number"); this.version = version; @@ -106,77 +100,68 @@ public Yggdrasil(final short version) { classResolvers.add(simpleClassResolver); } - public YggdrasilOutputStream newOutputStream(final OutputStream out) throws IOException { + public YggdrasilOutputStream newOutputStream(OutputStream out) throws IOException { return new DefaultYggdrasilOutputStream(this, out); } - public YggdrasilInputStream newInputStream(final InputStream in) throws IOException { + public YggdrasilInputStream newInputStream(InputStream in) throws IOException { return new DefaultYggdrasilInputStream(this, in); } - @Deprecated - public YggXMLOutputStream newXMLOutputStream(final OutputStream out) throws IOException { - return new YggXMLOutputStream(this, out); - } - - @Deprecated - public YggdrasilInputStream newXMLInputStream(final InputStream in) throws IOException { - return new YggXMLInputStream(this, in); + public void registerClassResolver(ClassResolver resolver) { + if (!classResolvers.contains(resolver)) + classResolvers.add(resolver); } - public void registerClassResolver(final ClassResolver r) { - if (!classResolvers.contains(r)) - classResolvers.add(r); - } - - public void registerSingleClass(final Class<?> c, final String id) { - simpleClassResolver.registerClass(c, id); + public void registerSingleClass(Class<?> type, String id) { + simpleClassResolver.registerClass(type, id); } /** * Registers a class and uses its {@link YggdrasilID} as id. */ - public void registerSingleClass(final Class<?> c) { - final YggdrasilID id = c.getAnnotation(YggdrasilID.class); + public void registerSingleClass(Class<?> type) { + YggdrasilID id = type.getAnnotation(YggdrasilID.class); if (id == null) - throw new IllegalArgumentException(c.toString()); - simpleClassResolver.registerClass(c, id.value()); + throw new IllegalArgumentException(type.toString()); + simpleClassResolver.registerClass(type, id.value()); } - public void registerFieldHandler(final FieldHandler h) { - if (!fieldHandlers.contains(h)) - fieldHandlers.add(h); + public void registerFieldHandler(FieldHandler handler) { + if (!fieldHandlers.contains(handler)) + fieldHandlers.add(handler); } - public final boolean isSerializable(final Class<?> c) { + public boolean isSerializable(Class<?> type) { try { - return c.isPrimitive() || c == Object.class || (Enum.class.isAssignableFrom(c) || PseudoEnum.class.isAssignableFrom(c)) && getIDNoError(c) != null || - ((YggdrasilSerializable.class.isAssignableFrom(c) || getSerializer(c) != null) && newInstance(c) != c);// whatever, just make true out if it (null is a valid return value) - } catch (final StreamCorruptedException e) { // thrown by newInstance if the class does not provide a correct constructor or is abstract + return type.isPrimitive() || type == Object.class || (Enum.class.isAssignableFrom(type) || + PseudoEnum.class.isAssignableFrom(type)) && getIDNoError(type) != null || + ((YggdrasilSerializable.class.isAssignableFrom(type) || getSerializer(type) != null) && newInstance(type) != type); // whatever, just make true out if it (null is a valid return value) + } catch (StreamCorruptedException e) { // thrown by newInstance if the class does not provide a correct constructor or is abstract return false; - } catch (final NotSerializableException e) { + } catch (NotSerializableException e) { return false; } } @Nullable - YggdrasilSerializer<?> getSerializer(final Class<?> c) { - for (final ClassResolver r : classResolvers) { - if (r instanceof YggdrasilSerializer && r.getID(c) != null) - return (YggdrasilSerializer<?>) r; + YggdrasilSerializer<?> getSerializer(Class<?> type) { + for (ClassResolver resolver : classResolvers) { + if (resolver instanceof YggdrasilSerializer && resolver.getID(type) != null) + return (YggdrasilSerializer<?>) resolver; } return null; } - public Class<?> getClass(final String id) throws StreamCorruptedException { + public Class<?> getClass(String id) throws StreamCorruptedException { if ("Object".equals(id)) return Object.class; - for (final ClassResolver r : classResolvers) { - final Class<?> c = r.getClass(id); - if (c != null) { // TODO error if not serialisable? - assert Tag.byName(id) == null && (Tag.getType(c) == Tag.T_OBJECT || Tag.getType(c) == Tag.T_ENUM) : "Tag IDs should not be matched: " + id + " (class resolver: " + r + ")"; - assert id.equals(r.getID(c)) : r + " returned " + c + " for id " + id + ", but returns id " + r.getID(c) + " for that class"; - return c; + for (ClassResolver resolver : classResolvers) { + Class<?> type = resolver.getClass(id); + if (type != null) { // TODO error if not serializable? + assert Tag.byName(id) == null && (Tag.getType(type) == Tag.T_OBJECT || Tag.getType(type) == Tag.T_ENUM) : "Tag IDs should not be matched: " + id + " (class resolver: " + resolver + ")"; + assert id.equals(resolver.getID(type)) : resolver + " returned " + type + " for id " + id + ", but returns id " + resolver.getID(type) + " for that class"; + return type; } } throw new StreamCorruptedException("No class found for ID " + id); @@ -184,35 +169,35 @@ public Class<?> getClass(final String id) throws StreamCorruptedException { @SuppressWarnings({"unchecked", "rawtypes"}) @Nullable - private String getIDNoError(Class<?> c) { - if (c == Object.class) + private String getIDNoError(Class<?> type) { + if (type == Object.class) return "Object"; - assert Tag.getType(c) == Tag.T_OBJECT || Tag.getType(c) == Tag.T_ENUM; - if (Enum.class.isAssignableFrom(c) && c.getSuperclass() != Enum.class) { - final Class<?> s = c.getSuperclass(); - assert s != null; // c cannot be Object.class - c = s; + assert Tag.getType(type) == Tag.T_OBJECT || Tag.getType(type) == Tag.T_ENUM; + if (Enum.class.isAssignableFrom(type) && type.getSuperclass() != Enum.class) { + Class<?> s = type.getSuperclass(); + assert s != null; // type cannot be Object.class + type = s; } - if (PseudoEnum.class.isAssignableFrom(c)) - c = PseudoEnum.getDeclaringClass((Class) c); - for (final ClassResolver r : classResolvers) { - final String id = r.getID(c); + if (PseudoEnum.class.isAssignableFrom(type)) + type = PseudoEnum.getDeclaringClass((Class) type); + for (ClassResolver resolver : classResolvers) { + String id = resolver.getID(type); if (id != null) { - assert Tag.byName(id) == null : "Class IDs should not match Tag IDs: " + id + " (class resolver: " + r + ")"; - final Class<?> c2 = r.getClass(id); - assert c2 != null && (r instanceof YggdrasilSerializer ? id.equals(r.getID(c2)) : r.getClass(id) == c) : r + " returned id " + id + " for " + c + ", but returns " + c2 + " for that id"; + assert Tag.byName(id) == null : "Class IDs should not match Tag IDs: " + id + " (class resolver: " + resolver + ")"; + Class<?> c2 = resolver.getClass(id); + assert c2 != null && (resolver instanceof YggdrasilSerializer ? id.equals(resolver.getID(c2)) : resolver.getClass(id) == type) : resolver + " returned id " + id + " for " + type + ", but returns " + c2 + " for that id"; return id; } } return null; } - public String getID(final Class<?> c) throws NotSerializableException { - final String id = getIDNoError(c); + public String getID(Class<?> type) throws NotSerializableException { + String id = getIDNoError(type); if (id == null) - throw new NotSerializableException("No ID found for " + c); - if (!isSerializable(c)) - throw new NotSerializableException(c.getCanonicalName()); + throw new NotSerializableException("No ID found for " + type); + if (!isSerializable(type)) + throw new NotSerializableException(type.getCanonicalName()); return id; } @@ -220,151 +205,125 @@ public String getID(final Class<?> c) throws NotSerializableException { * Gets the ID of a field. * <p> * This method performs no checks on the given field. - * - * @param f + * * @return The field's id as given by its {@link YggdrasilID} annotation, or its name if it's not annotated. */ - public static String getID(final Field f) { - final YggdrasilID yid = f.getAnnotation(YggdrasilID.class); + public static String getID(Field field) { + YggdrasilID yid = field.getAnnotation(YggdrasilID.class); if (yid != null) { return yid.value(); } - return "" + f.getName(); + return "" + field.getName(); } - @SuppressWarnings("null") - public static String getID(final Enum<?> e) { + public static String getID(Enum<?> e) { try { return getID(e.getDeclaringClass().getDeclaredField(e.name())); - } catch (final NoSuchFieldException ex) { + } catch (NoSuchFieldException ex) { assert false : e; return "" + e.name(); } } - @SuppressWarnings({"unchecked", "null", "unused"}) - public static <T extends Enum<T>> Enum<T> getEnumConstant(final Class<T> c, final String id) throws StreamCorruptedException { - final Field[] fields = c.getDeclaredFields(); - for (final Field f : fields) { - assert f != null; - if (getID(f).equals(id)) - return Enum.valueOf(c, f.getName()); + @SuppressWarnings({"unchecked", "unused"}) + public static <T extends Enum<T>> Enum<T> getEnumConstant(Class<T> type, String id) throws StreamCorruptedException { + Field[] fields = type.getDeclaredFields(); + for (Field field : fields) { + assert field != null; + if (getID(field).equals(id)) + return Enum.valueOf(type, field.getName()); } - if (YggdrasilRobustEnum.class.isAssignableFrom(c)) { - final Object[] cs = c.getEnumConstants(); - if (cs.length == 0) - throw new StreamCorruptedException(c + " does not have any enum constants"); - final Enum<?> e = ((YggdrasilRobustEnum) cs[0]).excessiveConstant(id); - if (e == null) - throw new YggdrasilException("YggdrasilRobustEnum " + c + " returned null from excessiveConstant(" + id + ")"); - if (!c.isInstance(e)) - throw new YggdrasilException(c + " returned a foreign enum constant: " + e.getClass() + "." + e); + if (YggdrasilRobustEnum.class.isAssignableFrom(type)) { + Object[] constants = type.getEnumConstants(); + if (constants.length == 0) + throw new StreamCorruptedException(type + " does not have any enum constants"); + Enum<?> e = ((YggdrasilRobustEnum) constants[0]).excessiveConstant(id); + if (!type.isInstance(e)) + throw new YggdrasilException(type + " returned a foreign enum constant: " + e.getClass() + "." + e); return (Enum<T>) e; } // TODO use field handlers/new enum handlers - throw new StreamCorruptedException("Enum constant " + id + " does not exist in " + c); + throw new StreamCorruptedException("Enum constant " + id + " does not exist in " + type); } - public void excessiveField(final Object o, final FieldContext field) throws StreamCorruptedException { - for (final FieldHandler h : fieldHandlers) { - if (h.excessiveField(o, field)) + public void excessiveField(Object object, FieldContext field) throws StreamCorruptedException { + for (FieldHandler handler : fieldHandlers) { + if (handler.excessiveField(object, field)) return; } - throw new StreamCorruptedException("Excessive field " + field.id + " in class " + o.getClass().getCanonicalName() + " was not handled"); + throw new StreamCorruptedException("Excessive field " + field.id + " in class " + object.getClass().getCanonicalName() + " was not handled"); } - public void missingField(final Object o, final Field f) throws StreamCorruptedException { - for (final FieldHandler h : fieldHandlers) { - if (h.missingField(o, f)) + public void missingField(Object object, Field field) throws StreamCorruptedException { + for (FieldHandler handler : fieldHandlers) { + if (handler.missingField(object, field)) return; } - throw new StreamCorruptedException("Missing field " + getID(f) + " in class " + o.getClass().getCanonicalName() + " was not handled"); + throw new StreamCorruptedException("Missing field " + getID(field) + " in class " + object.getClass().getCanonicalName() + " was not handled"); } - public void incompatibleField(final Object o, final Field f, final FieldContext field) throws StreamCorruptedException { - for (final FieldHandler h : fieldHandlers) { - if (h.incompatibleField(o, f, field)) + public void incompatibleField(Object object, Field field, FieldContext context) throws StreamCorruptedException { + for (FieldHandler handler : fieldHandlers) { + if (handler.incompatibleField(object, field, context)) return; } - throw new StreamCorruptedException("Incompatible field " + getID(f) + " in class " + o.getClass().getCanonicalName() + " of incompatible " + field.getType() + " was not handled"); + throw new StreamCorruptedException("Incompatible field " + getID(field) + " in class " + object.getClass().getCanonicalName() + " of incompatible " + context.getType() + " was not handled"); } - public void saveToFile(final Object o, final File f) throws IOException { - FileOutputStream fout = null; - YggdrasilOutputStream yout = null; - try { - fout = new FileOutputStream(f); - yout = newOutputStream(fout); - yout.writeObject(o); + public void saveToFile(Object object, File file) throws IOException { + try ( + FileOutputStream fout = new FileOutputStream(file); + YggdrasilOutputStream yout = newOutputStream(fout) + ) { + yout.writeObject(object); yout.flush(); - } finally { - if (yout != null) - yout.close(); - if (fout != null) - fout.close(); } } @Nullable - public <T> T loadFromFile(final File f, final Class<T> expectedType) throws IOException { - FileInputStream fin = null; - YggdrasilInputStream yin = null; - try { - fin = new FileInputStream(f); - yin = newInputStream(fin); + public <T> T loadFromFile(File file, Class<T> expectedType) throws IOException { + try ( + FileInputStream fin = new FileInputStream(file); + YggdrasilInputStream yin = newInputStream(fin) + ) { return yin.readObject(expectedType); - } finally { - if (yin != null) - yin.close(); - if (fin != null) - fin.close(); } } @SuppressWarnings({"rawtypes", "unchecked"}) @Nullable - final Object newInstance(final Class<?> c) throws StreamCorruptedException, NotSerializableException { - final YggdrasilSerializer s = getSerializer(c); - if (s != null) { - if (!s.canBeInstantiated(c)) { // only used by isSerializable - return null if OK, throw an YggdrasilException if not + Object newInstance(Class<?> type) throws StreamCorruptedException, NotSerializableException { + YggdrasilSerializer serializer = getSerializer(type); + if (serializer != null) { + if (!serializer.canBeInstantiated(type)) { // only used by isSerializable - return null if OK, throw an YggdrasilException if not try { - s.deserialize(c, new Fields(this)); - } catch (final StreamCorruptedException e) {} + serializer.deserialize(type, new Fields(this)); + } catch (StreamCorruptedException ignored) {} return null; } - final Object o = s.newInstance(c); + Object o = serializer.newInstance(type); if (o == null) - throw new YggdrasilException("YggdrasilSerializer " + s + " returned null from newInstance(" + c + ")"); + throw new YggdrasilException("YggdrasilSerializer " + serializer + " returned null from newInstance(" + type + ")"); return o; } // try whether a nullary constructor exists try { - final Constructor<?> constr = c.getDeclaredConstructor(); + Constructor<?> constr = type.getDeclaredConstructor(); constr.setAccessible(true); return constr.newInstance(); - } catch (final NoSuchMethodException e) { - throw new StreamCorruptedException("Cannot create an instance of " + c + " because it has no nullary constructor"); - } catch (final SecurityException e) { - throw new StreamCorruptedException("Cannot create an instance of " + c + " because the security manager didn't allow it"); - } catch (final InstantiationException e) { - throw new StreamCorruptedException("Cannot create an instance of " + c + " because it is abstract"); - } catch (final IllegalAccessException e) { - e.printStackTrace(); - assert false; - return null; - } catch (final IllegalArgumentException e) { + } catch (NoSuchMethodException e) { + throw new StreamCorruptedException("Cannot create an instance of " + type + " because it has no nullary constructor"); + } catch (SecurityException e) { + throw new StreamCorruptedException("Cannot create an instance of " + type + " because the security manager didn't allow it"); + } catch (InstantiationException e) { + throw new StreamCorruptedException("Cannot create an instance of " + type + " because it is abstract"); + } catch (IllegalAccessException | IllegalArgumentException e) { e.printStackTrace(); assert false; return null; - } catch (final InvocationTargetException e) { + } catch (InvocationTargetException e) { throw new RuntimeException(e); } } - // TODO command line, e.g. convert to XML - public static void main(final String[] args) { - System.err.println("Command line not supported yet"); - System.exit(1); - } - } diff --git a/src/main/java/ch/njol/yggdrasil/YggdrasilException.java b/src/main/java/ch/njol/yggdrasil/YggdrasilException.java index ee085237395..8ffb86dc39a 100644 --- a/src/main/java/ch/njol/yggdrasil/YggdrasilException.java +++ b/src/main/java/ch/njol/yggdrasil/YggdrasilException.java @@ -19,24 +19,24 @@ package ch.njol.yggdrasil; /** - * Thrown if the object(s) that should be saved/loaded with Yggdrasil do not comply with its requirements, or if Yggdrasil is used incorrectly. + * Thrown if the object(s) that should be saved/loaded with Yggdrasil do + * not comply with its requirements, or if Yggdrasil is used incorrectly. * <p> * A detail message will always be supplied, so fixing these errors should be trivial. - * - * @author Peter Güttinger */ -public class YggdrasilException extends RuntimeException { +public final class YggdrasilException extends RuntimeException { + private static final long serialVersionUID = -6130660396780458226L; - public YggdrasilException(final String message) { + public YggdrasilException(String message) { super(message); } - public YggdrasilException(final String message, final Throwable cause) { + public YggdrasilException(String message, Throwable cause) { super(message, cause); } - public YggdrasilException(final Throwable cause) { + public YggdrasilException(Throwable cause) { super(cause.getClass().getSimpleName() + (cause.getMessage() == null ? "" : ": " + cause.getMessage()), cause); } diff --git a/src/main/java/ch/njol/yggdrasil/YggdrasilID.java b/src/main/java/ch/njol/yggdrasil/YggdrasilID.java index ab03ff3f737..47b529a174a 100644 --- a/src/main/java/ch/njol/yggdrasil/YggdrasilID.java +++ b/src/main/java/ch/njol/yggdrasil/YggdrasilID.java @@ -26,12 +26,12 @@ /** * Can be used to set a class's or field's id used by Yggdrasil. - * - * @author Peter Güttinger */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.FIELD}) @Documented public @interface YggdrasilID { + String value(); + } diff --git a/src/main/java/ch/njol/yggdrasil/YggdrasilInputStream.java b/src/main/java/ch/njol/yggdrasil/YggdrasilInputStream.java index a9836a8d11b..3fd09daf89b 100644 --- a/src/main/java/ch/njol/yggdrasil/YggdrasilInputStream.java +++ b/src/main/java/ch/njol/yggdrasil/YggdrasilInputStream.java @@ -18,9 +18,8 @@ */ package ch.njol.yggdrasil; -import static ch.njol.yggdrasil.Tag.T_NULL; -import static ch.njol.yggdrasil.Tag.T_REFERENCE; -import static ch.njol.yggdrasil.Tag.getType; +import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilExtendedSerializable; +import org.eclipse.jdt.annotation.Nullable; import java.io.Closeable; import java.io.IOException; @@ -29,70 +28,58 @@ import java.util.ArrayList; import java.util.List; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilExtendedSerializable; +import static ch.njol.yggdrasil.Tag.T_NULL; +import static ch.njol.yggdrasil.Tag.T_REFERENCE; +import static ch.njol.yggdrasil.Tag.getType; public abstract class YggdrasilInputStream implements Closeable { protected final Yggdrasil yggdrasil; - protected YggdrasilInputStream(final Yggdrasil yggdrasil) { + protected YggdrasilInputStream(Yggdrasil yggdrasil) { this.yggdrasil = yggdrasil; } - // Tag - protected abstract Tag readTag() throws IOException; - // Primitives - protected abstract Object readPrimitive(Tag type) throws IOException; protected abstract Object readPrimitive_(Tag type) throws IOException; - // String - protected abstract String readString() throws IOException; - // Array - protected abstract Class<?> readArrayComponentType() throws IOException; protected abstract int readArrayLength() throws IOException; - private final void readArrayContents(final Object array) throws IOException { + private void readArrayContents(Object array) throws IOException { if (array.getClass().getComponentType().isPrimitive()) { - final int length = Array.getLength(array); - final Tag type = getType(array.getClass().getComponentType()); - for (int i = 0; i < length; i++) { + int length = Array.getLength(array); + Tag type = getType(array.getClass().getComponentType()); + for (int i = 0; i < length; i++) Array.set(array, i, readPrimitive_(type)); - } } else { - for (int i = 0; i < ((Object[]) array).length; i++) { + for (int i = 0; i < ((Object[]) array).length; i++) ((Object[]) array)[i] = readObject(); - } } } - // Enum - protected abstract Class<?> readEnumType() throws IOException; protected abstract String readEnumID() throws IOException; @SuppressWarnings({"unchecked", "rawtypes"}) - private final Object readEnum() throws IOException { - final Class<?> c = readEnumType(); - final String id = readEnumID(); + private Object readEnum() throws IOException { + Class<?> c = readEnumType(); + String id = readEnumID(); if (Enum.class.isAssignableFrom(c)) { return Yggdrasil.getEnumConstant((Class) c, id); } else if (PseudoEnum.class.isAssignableFrom(c)) { - final Object o = PseudoEnum.valueOf((Class) c, id); + Object o = PseudoEnum.valueOf((Class) c, id); if (o != null) return o; // if (YggdrasilRobustPseudoEnum.class.isAssignableFrom(c)) { -// // TODO create this and a handler (for Enums as well) +// TODO create this and a handler (for Enums as well) // } throw new StreamCorruptedException("Enum constant " + id + " does not exist in " + c); } else { @@ -100,115 +87,106 @@ private final Object readEnum() throws IOException { } } - // Class - protected abstract Class<?> readClass() throws IOException; - // Reference - protected abstract int readReference() throws IOException; - // generic Object - protected abstract Class<?> readObjectType() throws IOException; protected abstract short readNumFields() throws IOException; protected abstract String readFieldID() throws IOException; - private final Fields readFields() throws IOException { - final Fields fields = new Fields(yggdrasil); - final short numFields = readNumFields(); + private Fields readFields() throws IOException { + Fields fields = new Fields(yggdrasil); + short numFields = readNumFields(); for (int i = 0; i < numFields; i++) { - final String id = readFieldID(); - final Tag t = readTag(); - if (t.isPrimitive()) - fields.putPrimitive(id, readPrimitive(t)); + String id = readFieldID(); + Tag tag = readTag(); + if (tag.isPrimitive()) + fields.putPrimitive(id, readPrimitive(tag)); else - fields.putObject(id, readObject(t)); + fields.putObject(id, readObject(tag)); } return fields; } - // any Objects - private final List<Object> readObjects = new ArrayList<>(); @Nullable public final Object readObject() throws IOException { - final Tag t = readTag(); - return readObject(t); + Tag tag = readTag(); + return readObject(tag); } @SuppressWarnings("unchecked") @Nullable - public final <T> T readObject(final Class<T> expectedType) throws IOException { - final Tag t = readTag(); - final Object o = readObject(t); - if (o != null && !expectedType.isInstance(o)) - throw new StreamCorruptedException("Object " + o + " is of " + o.getClass() + " but expected " + expectedType); - return (T) o; + public final <T> T readObject(Class<T> expectedType) throws IOException { + Tag tag = readTag(); + Object object = readObject(tag); + if (object != null && !expectedType.isInstance(object)) + throw new StreamCorruptedException("Object " + object + " is of " + object.getClass() + " but expected " + expectedType); + return (T) object; } - @SuppressWarnings({"rawtypes", "unchecked", "null", "unused"}) + @SuppressWarnings({"rawtypes", "unchecked"}) @Nullable - private final Object readObject(final Tag t) throws IOException { - if (t == T_NULL) + private Object readObject(Tag tag) throws IOException { + if (tag == T_NULL) return null; - if (t == T_REFERENCE) { - final int ref = readReference(); + if (tag == T_REFERENCE) { + int ref = readReference(); if (ref < 0 || ref >= readObjects.size()) throw new StreamCorruptedException("Invalid reference " + ref + ", " + readObjects.size() + " object(s) read so far"); - final Object o = readObjects.get(ref); - if (o == null) + Object object = readObjects.get(ref); + if (object == null) throw new StreamCorruptedException("Reference to uninstantiable object: " + ref); - return o; + return object; } - final Object o; - switch (t) { + Object object; + switch (tag) { case T_ARRAY: { - final Class<?> c = readArrayComponentType(); - o = Array.newInstance(c, readArrayLength()); - assert o != null; - readObjects.add(o); - readArrayContents(o); - return o; + Class<?> type = readArrayComponentType(); + object = Array.newInstance(type, readArrayLength()); + readObjects.add(object); + readArrayContents(object); + return object; } case T_CLASS: - o = readClass(); + object = readClass(); break; case T_ENUM: - o = readEnum(); + object = readEnum(); break; case T_STRING: - o = readString(); + object = readString(); break; case T_OBJECT: { - final Class<?> c = readObjectType(); - final YggdrasilSerializer s = yggdrasil.getSerializer(c); + Class<?> c = readObjectType(); + YggdrasilSerializer s = yggdrasil.getSerializer(c); if (s != null && !s.canBeInstantiated(c)) { - final int ref = readObjects.size(); + int ref = readObjects.size(); readObjects.add(null); - final Fields fields = readFields(); - o = s.deserialize(c, fields); - if (o == null) + Fields fields = readFields(); + object = s.deserialize(c, fields); + if (object == null) throw new YggdrasilException("YggdrasilSerializer " + s + " returned null from deserialize(" + c + "," + fields + ")"); - readObjects.set(ref, o); + readObjects.set(ref, object); } else { - o = yggdrasil.newInstance(c); - if (o == null) + object = yggdrasil.newInstance(c); + if (object == null) throw new StreamCorruptedException(); - readObjects.add(o); - final Fields fields = readFields(); + readObjects.add(object); + Fields fields = readFields(); if (s != null) { - s.deserialize(o, fields); - } else if (o instanceof YggdrasilExtendedSerializable) { - ((YggdrasilExtendedSerializable) o).deserialize(fields); + s.deserialize(object, fields); + } else if (object instanceof YggdrasilExtendedSerializable) { + ((YggdrasilExtendedSerializable) object).deserialize(fields); } else { - fields.setFields(o); + fields.setFields(object); } } - return o; + return object; } case T_BOOLEAN_OBJ: case T_BYTE_OBJ: @@ -218,9 +196,9 @@ private final Object readObject(final Tag t) throws IOException { case T_INT_OBJ: case T_LONG_OBJ: case T_SHORT_OBJ: - final Tag p = t.getPrimitive(); - assert p != null; - o = readPrimitive(p); + Tag primitive = tag.getPrimitive(); + assert primitive != null; + object = readPrimitive(primitive); break; case T_BYTE: case T_BOOLEAN: @@ -231,49 +209,12 @@ private final Object readObject(final Tag t) throws IOException { case T_LONG: case T_SHORT: throw new StreamCorruptedException(); - case T_REFERENCE: - case T_NULL: default: assert false; throw new StreamCorruptedException(); } - readObjects.add(o); - return o; + readObjects.add(object); + return object; } -// private final static class Validation implements Comparable<Validation> { -// private final ObjectInputValidation v; -// private final int prio; -// -// public Validation(final ObjectInputValidation v, final int prio) { -// this.v = v; -// this.prio = prio; -// } -// -// private void validate() throws InvalidObjectException { -// v.validateObject(); -// } -// -// @Override -// public int compareTo(final Validation o) { -// return o.prio - prio; -// } -// } -// -// private final SortedSet<Validation> validations = new TreeSet<>(); -// -// public void registerValidation(final ObjectInputValidation v, final int prio) throws NotActiveException, InvalidObjectException { -// if (depth == 0) -// throw new NotActiveException("stream inactive"); -// if (v == null) -// throw new InvalidObjectException("null callback"); -// validations.add(new Validation(v, prio)); -// } -// -// private void validate() throws InvalidObjectException { -// for (final Validation v : validations) -// v.validate(); -// validations.clear(); // if multiple objects are written to the stream this method will be called multiple times -// } - } diff --git a/src/main/java/ch/njol/yggdrasil/YggdrasilOutputStream.java b/src/main/java/ch/njol/yggdrasil/YggdrasilOutputStream.java index f296d2aac3b..7f0124f9573 100644 --- a/src/main/java/ch/njol/yggdrasil/YggdrasilOutputStream.java +++ b/src/main/java/ch/njol/yggdrasil/YggdrasilOutputStream.java @@ -18,14 +18,9 @@ */ package ch.njol.yggdrasil; -import static ch.njol.yggdrasil.Tag.T_ARRAY; -import static ch.njol.yggdrasil.Tag.T_CLASS; -import static ch.njol.yggdrasil.Tag.T_ENUM; -import static ch.njol.yggdrasil.Tag.T_NULL; -import static ch.njol.yggdrasil.Tag.T_OBJECT; -import static ch.njol.yggdrasil.Tag.T_REFERENCE; -import static ch.njol.yggdrasil.Tag.T_STRING; -import static ch.njol.yggdrasil.Tag.getType; +import ch.njol.yggdrasil.Fields.FieldContext; +import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilExtendedSerializable; +import org.eclipse.jdt.annotation.Nullable; import java.io.Closeable; import java.io.Flushable; @@ -34,130 +29,115 @@ import java.lang.reflect.Array; import java.util.IdentityHashMap; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.yggdrasil.Fields.FieldContext; -import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilExtendedSerializable; +import static ch.njol.yggdrasil.Tag.T_ARRAY; +import static ch.njol.yggdrasil.Tag.T_CLASS; +import static ch.njol.yggdrasil.Tag.T_ENUM; +import static ch.njol.yggdrasil.Tag.T_NULL; +import static ch.njol.yggdrasil.Tag.T_OBJECT; +import static ch.njol.yggdrasil.Tag.T_REFERENCE; +import static ch.njol.yggdrasil.Tag.T_STRING; +import static ch.njol.yggdrasil.Tag.getType; public abstract class YggdrasilOutputStream implements Flushable, Closeable { protected final Yggdrasil yggdrasil; - protected YggdrasilOutputStream(final Yggdrasil yggdrasil) { + protected YggdrasilOutputStream(Yggdrasil yggdrasil) { this.yggdrasil = yggdrasil; } - // Tag - - protected abstract void writeTag(Tag t) throws IOException; - - // Null + protected abstract void writeTag(Tag tag) throws IOException; - private final void writeNull() throws IOException { + private void writeNull() throws IOException { writeTag(T_NULL); } - // Primitives + protected abstract void writePrimitiveValue(Object object) throws IOException; - protected abstract void writePrimitiveValue(Object o) throws IOException; + protected abstract void writePrimitive_(Object object) throws IOException; - protected abstract void writePrimitive_(Object o) throws IOException; - - private final void writePrimitive(final Object o) throws IOException { - final Tag t = Tag.getType(o.getClass()); - assert t.isWrapper(); - final Tag p = t.getPrimitive(); - assert p != null; - writeTag(p); - writePrimitiveValue(o); + private void writePrimitive(Object object) throws IOException { + Tag tag = Tag.getType(object.getClass()); + assert tag.isWrapper(); + Tag primitive = tag.getPrimitive(); + assert primitive != null; + writeTag(primitive); + writePrimitiveValue(object); } - private final void writeWrappedPrimitive(final Object o) throws IOException { - final Tag t = Tag.getType(o.getClass()); - assert t.isWrapper(); - writeTag(t); - writePrimitiveValue(o); + private void writeWrappedPrimitive(Object object) throws IOException { + Tag tag = Tag.getType(object.getClass()); + assert tag.isWrapper(); + writeTag(tag); + writePrimitiveValue(object); } - // String - - protected abstract void writeStringValue(String s) throws IOException; + protected abstract void writeStringValue(String string) throws IOException; - private final void writeString(final String s) throws IOException { + private void writeString(String string) throws IOException { writeTag(T_STRING); - writeStringValue(s); + writeStringValue(string); } - // Array - protected abstract void writeArrayComponentType(Class<?> componentType) throws IOException; protected abstract void writeArrayLength(int length) throws IOException; protected abstract void writeArrayEnd() throws IOException; - private final void writeArray(final Object array) throws IOException { - final int length = Array.getLength(array); - final Class<?> ct = array.getClass().getComponentType(); - assert ct != null; + private void writeArray(Object array) throws IOException { + int length = Array.getLength(array); + Class<?> type = array.getClass().getComponentType(); + assert type != null; writeTag(T_ARRAY); - writeArrayComponentType(ct); + writeArrayComponentType(type); writeArrayLength(length); - if (ct.isPrimitive()) { + if (type.isPrimitive()) { for (int i = 0; i < length; i++) { - final Object p = Array.get(array, i); - assert p != null; - writePrimitive_(p); + Object object = Array.get(array, i); + assert object != null; + writePrimitive_(object); } writeArrayEnd(); } else { - for (final Object o : (Object[]) array) - writeObject(o); + for (Object object : (Object[]) array) + writeObject(object); writeArrayEnd(); } } - // Enum - protected abstract void writeEnumType(String type) throws IOException; protected abstract void writeEnumID(String id) throws IOException; - private final void writeEnum(final Enum<?> o) throws IOException { + private void writeEnum(Enum<?> object) throws IOException { writeTag(T_ENUM); - final Class<?> c = o.getDeclaringClass(); - assert c != null; - writeEnumType(yggdrasil.getID(c)); - writeEnumID(Yggdrasil.getID(o)); + Class<?> type = object.getDeclaringClass(); + writeEnumType(yggdrasil.getID(type)); + writeEnumID(Yggdrasil.getID(object)); } - private final void writeEnum(final PseudoEnum<?> o) throws IOException { + private void writeEnum(PseudoEnum<?> object) throws IOException { writeTag(T_ENUM); - writeEnumType(yggdrasil.getID(o.getDeclaringClass())); - writeEnumID(o.name()); + writeEnumType(yggdrasil.getID(object.getDeclaringClass())); + writeEnumID(object.name()); } - // Class + protected abstract void writeClassType(Class<?> type) throws IOException; - protected abstract void writeClassType(Class<?> c) throws IOException; - - private final void writeClass(final Class<?> c) throws IOException { + private void writeClass(Class<?> type) throws IOException { writeTag(T_CLASS); - writeClassType(c); + writeClassType(type); } - // Reference - protected abstract void writeReferenceID(int ref) throws IOException; - protected final void writeReference(final int ref) throws IOException { + protected final void writeReference(int ref) throws IOException { assert ref >= 0; writeTag(T_REFERENCE); writeReferenceID(ref); } - // generic Objects - protected abstract void writeObjectType(String type) throws IOException; protected abstract void writeNumFields(short numFields) throws IOException; @@ -167,88 +147,85 @@ protected final void writeReference(final int ref) throws IOException { protected abstract void writeObjectEnd() throws IOException; @SuppressWarnings({"rawtypes", "unchecked"}) - private final void writeGenericObject(final Object o, int ref) throws IOException { - final Class<?> c = o.getClass(); - assert c != null; - if (!yggdrasil.isSerializable(c)) - throw new NotSerializableException(c.getName()); - final Fields fields; - final YggdrasilSerializer s = yggdrasil.getSerializer(c); - if (s != null) { - fields = s.serialize(o); - if (!s.canBeInstantiated(c)) { + private void writeGenericObject(Object object, int ref) throws IOException { + Class<?> type = object.getClass(); + assert type != null; + if (!yggdrasil.isSerializable(type)) + throw new NotSerializableException(type.getName()); + Fields fields; + YggdrasilSerializer serializer = yggdrasil.getSerializer(type); + if (serializer != null) { + fields = serializer.serialize(object); + if (!serializer.canBeInstantiated(type)) { ref = ~ref; // ~ instead of - to also get a negative value if ref is 0 - writtenObjects.put(o, ref); + writtenObjects.put(object, ref); } - } else if (o instanceof YggdrasilExtendedSerializable) { - fields = ((YggdrasilExtendedSerializable) o).serialize(); + } else if (object instanceof YggdrasilExtendedSerializable) { + fields = ((YggdrasilExtendedSerializable) object).serialize(); } else { - fields = new Fields(o, yggdrasil); + fields = new Fields(object, yggdrasil); } if (fields.size() > Short.MAX_VALUE) - throw new YggdrasilException("Class " + c.getCanonicalName() + " has too many fields (" + fields.size() + ")"); + throw new YggdrasilException("Class " + type.getCanonicalName() + " has too many fields (" + fields.size() + ")"); writeTag(T_OBJECT); - writeObjectType(yggdrasil.getID(c)); + writeObjectType(yggdrasil.getID(type)); writeNumFields((short) fields.size()); - for (final FieldContext f : fields) { - writeFieldID(f.id); - if (f.isPrimitive()) - writePrimitive(f.getPrimitive()); + for (FieldContext field : fields) { + writeFieldID(field.id); + if (field.isPrimitive()) + writePrimitive(field.getPrimitive()); else - writeObject(f.getObject()); + writeObject(field.getObject()); } writeObjectEnd(); if (ref < 0) - writtenObjects.put(o, ~ref); + writtenObjects.put(object, ~ref); } - // any Objects - private int nextObjectID = 0; private final IdentityHashMap<Object, Integer> writtenObjects = new IdentityHashMap<>(); - public final void writeObject(final @Nullable Object o) throws IOException { - if (o == null) { + public final void writeObject(@Nullable Object object) throws IOException { + if (object == null) { writeNull(); return; } - if (writtenObjects.containsKey(o)) { - final int ref = writtenObjects.get(o); + if (writtenObjects.containsKey(object)) { + int ref = writtenObjects.get(object); if (ref < 0) - throw new YggdrasilException("Uninstantiable object " + o + " is referenced in its fields' graph"); + throw new YggdrasilException("Uninstantiable object " + object + " is referenced in its fields' graph"); writeReference(ref); return; } - final int ref = nextObjectID; + int ref = nextObjectID; nextObjectID++; - writtenObjects.put(o, ref); - final Tag type = getType(o.getClass()); + writtenObjects.put(object, ref); + Tag type = getType(object.getClass()); if (type.isWrapper()) { - writeWrappedPrimitive(o); + writeWrappedPrimitive(object); return; } switch (type) { case T_ARRAY: - writeArray(o); + writeArray(object); return; case T_STRING: - writeString((String) o); + writeString((String) object); return; case T_ENUM: - if (o instanceof Enum) - writeEnum((Enum<?>) o); + if (object instanceof Enum) + writeEnum((Enum<?>) object); else - writeEnum((PseudoEnum<?>) o); + writeEnum((PseudoEnum<?>) object); return; case T_CLASS: - writeClass((Class<?>) o); + writeClass((Class<?>) object); return; case T_OBJECT: - writeGenericObject(o, ref); + writeGenericObject(object, ref); return; - //$CASES-OMITTED$ default: throw new YggdrasilException("unhandled type " + type); } diff --git a/src/main/java/ch/njol/yggdrasil/YggdrasilSerializable.java b/src/main/java/ch/njol/yggdrasil/YggdrasilSerializable.java index 3908c434aa1..b9b4fac355b 100644 --- a/src/main/java/ch/njol/yggdrasil/YggdrasilSerializable.java +++ b/src/main/java/ch/njol/yggdrasil/YggdrasilSerializable.java @@ -27,20 +27,17 @@ import ch.njol.yggdrasil.Fields.FieldContext; /** - * Marks a class as serialisable by Yggdrasil. + * Marks a class as serializable by Yggdrasil. * <p> - * Enums don't have to implement this interface to be serialisable, but can implement {@link YggdrasilRobustEnum}. - * - * @author Peter Güttinger + * Enums don't have to implement this interface to be serializable, but can implement {@link YggdrasilRobustEnum}. */ public interface YggdrasilSerializable { /** - * A class that has had fields added, changed, or removed from it should implement this interface to handle the now invalid/missing fields that may still be read from stream. - * - * @author Peter Güttinger + * A class that has had fields added, changed, or removed from it should implement this + * interface to handle the now invalid/missing fields that may still be read from stream. */ - public static interface YggdrasilRobustSerializable extends YggdrasilSerializable { + interface YggdrasilRobustSerializable extends YggdrasilSerializable { /** * Called if a field that was read from stream is of an incompatible type to the existing field in this class. @@ -48,19 +45,20 @@ public static interface YggdrasilRobustSerializable extends YggdrasilSerializabl * @param field The Java field * @param value The field read from stream * @return Whether the field was handled. If false, - * <tt>yggdrasil.{@link Yggdrasil#incompatibleField(Object, Field, FieldContext) incompatibleField}(this, field, value)</tt> will be called. + * <tt>yggdrasil.{@link Yggdrasil#incompatibleField(Object, Field, FieldContext) incompatibleField}(this, field, value)</tt> + * will be called. */ - @SuppressWarnings("null") - public boolean incompatibleField(@NonNull Field field, @NonNull FieldContext value) throws StreamCorruptedException; + boolean incompatibleField(@NonNull Field field, @NonNull FieldContext value) throws StreamCorruptedException; /** * Called if a field was read from stream which does not exist in this class. * * @param field The field read from stream - * @return Whether the field was handled. If false, <tt>yggdrasil.{@link Yggdrasil#excessiveField(Object, FieldContext) excessiveField}(this, field)</tt> will be called. + * @return Whether the field was handled. If false, + * <tt>yggdrasil.{@link Yggdrasil#excessiveField(Object, FieldContext) excessiveField}(this, field)</tt> + * will be called. */ - @SuppressWarnings("null") - public boolean excessiveField(@NonNull FieldContext field) throws StreamCorruptedException; + boolean excessiveField(@NonNull FieldContext field) throws StreamCorruptedException; /** * Called if a field was not found in the stream. @@ -69,17 +67,14 @@ public static interface YggdrasilRobustSerializable extends YggdrasilSerializabl * @return Whether the field was handled (e.g. true if the default value is fine). If false, * <tt>yggdrasil.{@link Yggdrasil#missingField(Object, Field) missingField}(this, field)</tt> will be called. */ - @SuppressWarnings("null") - public boolean missingField(@NonNull Field field) throws StreamCorruptedException; + boolean missingField(@NonNull Field field) throws StreamCorruptedException; } /** * Provides a method to resolve missing enum constants. - * - * @author Peter Güttinger */ - public static interface YggdrasilRobustEnum { + interface YggdrasilRobustEnum { /** * Called when an enum constant is read from stream that does not exist in this enum. @@ -90,19 +85,19 @@ public static interface YggdrasilRobustEnum { * @param name The name read from stream * @return The renamed enum constant or null if the read string is invalid. If the returned Enum is not an instance of this enum type an exception will be thrown. */ - public Enum<?> excessiveConstant(String name); + Enum<?> excessiveConstant(String name); } /** - * A class that has transient fields or more generally wants to exactly define which fields to write to/read from stream should implement this interface. It provides two - * methods similar to Java's writeObject and readObject methods. + * A class that has transient fields or more generally wants to exactly + * define which fields to write to/read from stream should implement this interface. + * It provides two methods similar to Java's writeObject and readObject methods. * <p> - * If a class implements this interface implementing {@link YggdrasilRobustSerializable} as well is pointless since its methods won't get called. - * - * @author Peter Güttinger + * If a class implements this interface implementing {@link YggdrasilRobustSerializable} + * as well is pointless since its methods won't get called. */ - public static interface YggdrasilExtendedSerializable extends YggdrasilSerializable { + interface YggdrasilExtendedSerializable extends YggdrasilSerializable { /** * Serialises this object. Only fields contained in the returned Fields object will be written to stream. @@ -110,23 +105,24 @@ public static interface YggdrasilExtendedSerializable extends YggdrasilSerializa * You can use <tt>return new {@link Fields#Fields(Object) Fields}(this);</tt> to emulate the default behaviour. * * @return A Fields object containing all fields that should be written to stream - * @throws NotSerializableException If this object or one of its fields is not serialisable + * @throws NotSerializableException If this object or one of its fields is not serializable */ - public Fields serialize() throws NotSerializableException; + Fields serialize() throws NotSerializableException; /** - * Deserialises this object. No fields have been set when this method is called, use <tt>fields.{@link Fields#setFields setFields}(this, yggdrasil)</tt> to set all - * compatible non-transient and non-static fields (and call incompatible/missing field handlers if applicable – this implies that errors will be thrown if the fields + * Deserializes this object. No fields have been set when this method is called, use + * <tt>fields.{@link Fields#setFields setFields}(this, yggdrasil)</tt> to set all + * compatible non-transient and non-static fields (and call incompatible/missing field + * handlers if applicable – this implies that errors will be thrown if the fields * object is invalid). * <p> * You can use <tt>fields.{@link Fields#setFields(Object) setFields}(this);</tt> to emulate the default behaviour. * * @param fields A Fields object containing all fields read from stream - * @throws StreamCorruptedException If the Fields object is invalid, i.e. was not written by {@link #serialize()} or Yggrdasil's default serialisation. - * @throws NotSerializableException + * @throws StreamCorruptedException If the Fields object is invalid, i.e. was not written + * by {@link #serialize()} or Yggdrasil's default serialisation. */ - @SuppressWarnings("null") - public void deserialize(@NonNull Fields fields) throws StreamCorruptedException, NotSerializableException; + void deserialize(@NonNull Fields fields) throws StreamCorruptedException, NotSerializableException; } diff --git a/src/main/java/ch/njol/yggdrasil/YggdrasilSerializer.java b/src/main/java/ch/njol/yggdrasil/YggdrasilSerializer.java index 98b5ebda871..0424bb72695 100644 --- a/src/main/java/ch/njol/yggdrasil/YggdrasilSerializer.java +++ b/src/main/java/ch/njol/yggdrasil/YggdrasilSerializer.java @@ -18,15 +18,14 @@ */ package ch.njol.yggdrasil; +import org.eclipse.jdt.annotation.Nullable; + import java.io.NotSerializableException; import java.io.StreamCorruptedException; -import org.eclipse.jdt.annotation.Nullable; - /** - * Utility to be able to save and load classes with Yggdrasil that the user has no control of, e.g. classes of an external API. - * - * @author Peter Güttinger + * Utility to be able to save and load classes with Yggdrasil that + * the user has no control of, e.g. classes of an external API. */ public abstract class YggdrasilSerializer<T> implements ClassResolver { @@ -39,23 +38,25 @@ public abstract class YggdrasilSerializer<T> implements ClassResolver { * <p> * Use <tt>return new {@link Fields#Fields(Object) Fields}(this);</tt> to emulate the default behaviour. * - * @param o The object to serialise + * @param object The object to serialise * @return A Fields object representing the object's fields to serialise. Must not be null. - * @throws NotSerializableException If this object could not be serialised + * @throws NotSerializableException If this object could not be serialized */ - public abstract Fields serialize(T o) throws NotSerializableException; + public abstract Fields serialize(T object) throws NotSerializableException; /** - * Whether an instance of the given class can be dynamically created. If this method returns false, {@link #newInstance(Class)} and {@link #deserialize(Object, Fields)} will - * not be called for the given class, but {@link #deserialize(Class, Fields)} will be used instead, and having any reference to an object of the given class in its own fields' - * graph will cause Yggdrasil to throw an exception upon serialisation as no reference to the object will be available when deserialising the object. // TODO allow this + * Whether an instance of the given class can be dynamically created. If this method returns false, + * {@link #newInstance(Class)} and {@link #deserialize(Object, Fields)} will not be called for the given class, + * but {@link #deserialize(Class, Fields)} will be used instead, and having any reference to an object of the given + * class in its own fields' graph will cause Yggdrasil to throw an exception upon serialisation as no reference to + * the object will be available when deserializing the object. // TODO allow this * <p> - * Please note that you must not change the return value of this function ever - it is not saved in the stream. // TODO clarify + * Please note that you must not change the return value of this function ever - it is not saved in the stream. * - * @param c The class to check + * @param type The class to check * @return true by default */ - public boolean canBeInstantiated(final Class<? extends T> c) { + public boolean canBeInstantiated(Class<? extends T> type) { return true; } @@ -69,28 +70,26 @@ public boolean canBeInstantiated(final Class<? extends T> c) { public abstract <E extends T> E newInstance(Class<E> c); /** - * Deserialises an object. + * Deserializes an object. * <p> * Use <tt>fields.{@link Fields#setFields(Object) setFields}(o);</tt> to emulate the default behaviour. * - * @param o The object to deserialise as returned by {@link #newInstance(Class)}. + * @param object The object to deserialize as returned by {@link #newInstance(Class)}. * @param fields The fields read from stream - * @throws StreamCorruptedException If deserialisation failed because the data read from stream is incomplete or invalid. - * @throws NotSerializableException + * @throws StreamCorruptedException If deserialization failed because the data read from stream is incomplete or invalid. */ - public abstract void deserialize(T o, Fields fields) throws StreamCorruptedException, NotSerializableException; + public abstract void deserialize(T object, Fields fields) throws StreamCorruptedException, NotSerializableException; /** - * Deserialises an object. + * Deserializes an object. * - * @param c The class to get an instance of + * @param type The class to get an instance of * @param fields The fields read from stream * @return An object representing the read fields. Must not be null (throw an exception instead). - * @throws StreamCorruptedException If deserialisation failed because the data read from stream is incomplete or invalid. - * @throws NotSerializableException If the class is not serialisable + * @throws StreamCorruptedException If deserialization failed because the data read from stream is incomplete or invalid. + * @throws NotSerializableException If the class is not serializable */ - @SuppressWarnings("unused") - public <E extends T> E deserialize(final Class<E> c, final Fields fields) throws StreamCorruptedException, NotSerializableException { + public <E extends T> E deserialize(Class<E> type, Fields fields) throws StreamCorruptedException, NotSerializableException { throw new YggdrasilException(getClass() + " does not override deserialize(Class, Fields)"); } diff --git a/src/main/java/ch/njol/yggdrasil/util/JREFieldHandler.java b/src/main/java/ch/njol/yggdrasil/util/JREFieldHandler.java index a5534cc855f..6bff027da85 100644 --- a/src/main/java/ch/njol/yggdrasil/util/JREFieldHandler.java +++ b/src/main/java/ch/njol/yggdrasil/util/JREFieldHandler.java @@ -18,117 +18,98 @@ */ package ch.njol.yggdrasil.util; +import ch.njol.yggdrasil.FieldHandler; +import ch.njol.yggdrasil.Fields.FieldContext; +import ch.njol.yggdrasil.YggdrasilException; + import java.io.StreamCorruptedException; import java.lang.reflect.Array; import java.lang.reflect.Field; -import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Map; -import ch.njol.yggdrasil.FieldHandler; -import ch.njol.yggdrasil.Fields.FieldContext; -import ch.njol.yggdrasil.YggdrasilException; - /** - * Handles common JRE-related incompatible field types. This handler is not added by default and is merely a utility. - * - * @author Peter Güttinger + * Handles common JRE-related incompatible field types. + * This handler is not added by default and is merely a utility. */ +@Deprecated public class JREFieldHandler implements FieldHandler { - /** - * Not used - */ @Override - public boolean excessiveField(final Object o, final FieldContext field) { + public boolean excessiveField(Object object, FieldContext field) { return false; } - /** - * Not used - */ @Override - public boolean missingField(final Object o, final Field field) throws StreamCorruptedException { + public boolean missingField(Object object, Field field) { return false; } /** * Converts collection types and non-primitive arrays - * - * @throws StreamCorruptedException */ @SuppressWarnings({"rawtypes", "unchecked"}) @Override - public boolean incompatibleField(final Object o, final Field f, final FieldContext field) throws StreamCorruptedException { - Object value = field.getObject(); + public boolean incompatibleField(Object object, Field field, FieldContext context) throws StreamCorruptedException { + Object value = context.getObject(); if (value instanceof Object[]) - value = Arrays.asList(value); + value = Collections.singletonList(value); if (value instanceof Collection) { - final Collection v = (Collection) value; + Collection collection = (Collection) value; try { - if (Collection.class.isAssignableFrom(f.getType())) { - final Collection c = (Collection) f.get(o); + if (Collection.class.isAssignableFrom(field.getType())) { + Collection c = (Collection) field.get(object); if (c != null) { c.clear(); - c.addAll(v); + c.addAll(collection); return true; } - } else if (Object[].class.isAssignableFrom(f.getType())) { - Object[] array = (Object[]) f.get(o); + } else if (Object[].class.isAssignableFrom(field.getType())) { + Object[] array = (Object[]) field.get(object); if (array != null) { - if (array.length < v.size()) + if (array.length < collection.size()) return false; - final Class<?> ct = array.getClass().getComponentType(); - for (final Object x : v) { - if (!ct.isInstance(x)) + Class<?> ct = array.getClass().getComponentType(); + for (Object iterated : collection) { + if (!ct.isInstance(iterated)) return false; } } else { - array = (Object[]) Array.newInstance(f.getType().getComponentType(), v.size()); - f.set(o, array); + array = (Object[]) Array.newInstance(field.getType().getComponentType(), collection.size()); + field.set(object, array); } - final int l = array.length; + int length = array.length; int i = 0; - for (final Object x : v) - array[i++] = x; - while (i < l) + for (Object iterated : collection) + array[i++] = iterated; + while (i < length) array[i++] = null; } - } catch (final IllegalArgumentException e) { - throw new YggdrasilException(e); - } catch (final IllegalAccessException e) { - throw new YggdrasilException(e); - } catch (final UnsupportedOperationException e) { - throw new YggdrasilException(e); - } catch (final ClassCastException e) { - throw new YggdrasilException(e); - } catch (final NullPointerException e) { - throw new YggdrasilException(e); - } catch (final IllegalStateException e) { + } catch ( + IllegalArgumentException | NullPointerException | IllegalStateException | + ClassCastException | UnsupportedOperationException | IllegalAccessException e + ) { throw new YggdrasilException(e); } } else if (value instanceof Map) { - if (!Map.class.isAssignableFrom(f.getType())) + if (!Map.class.isAssignableFrom(field.getType())) return false; try { - final Map m = (Map) f.get(o); + Map m = (Map) field.get(object); if (m != null) { m.clear(); m.putAll((Map) value); return true; } - } catch (final IllegalArgumentException e) { - throw new YggdrasilException(e); - } catch (final IllegalAccessException e) { - throw new YggdrasilException(e); - } catch (final UnsupportedOperationException e) { - throw new YggdrasilException(e); - } catch (final ClassCastException e) { - throw new YggdrasilException(e); - } catch (final NullPointerException e) { + } catch ( + IllegalArgumentException | IllegalAccessException | UnsupportedOperationException | + ClassCastException | NullPointerException e + ) { throw new YggdrasilException(e); } } + return false; } diff --git a/src/main/java/ch/njol/yggdrasil/xml/YggXMLInputStream.java b/src/main/java/ch/njol/yggdrasil/xml/YggXMLInputStream.java deleted file mode 100644 index b787be0e461..00000000000 --- a/src/main/java/ch/njol/yggdrasil/xml/YggXMLInputStream.java +++ /dev/null @@ -1,322 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.yggdrasil.xml; - -import java.io.EOFException; -import java.io.IOException; -import java.io.InputStream; -import java.io.StreamCorruptedException; -import java.lang.reflect.Array; -import java.util.NoSuchElementException; - -import javax.xml.stream.FactoryConfigurationError; -import javax.xml.stream.XMLInputFactory; -import javax.xml.stream.XMLStreamConstants; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamReader; - -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.yggdrasil.Tag; -import ch.njol.yggdrasil.Yggdrasil; -import ch.njol.yggdrasil.YggdrasilInputStream; - -/** - * @deprecated XML has so many quirks that storing arbitrary data cannot be guaranteed. - * @author Peter Güttinger - */ -@Deprecated -public final class YggXMLInputStream extends YggdrasilInputStream { - - private final XMLStreamReader in; - private final InputStream is; - - @SuppressWarnings("unused") - private final short version; - - @SuppressWarnings("null") - public YggXMLInputStream(final Yggdrasil y, final InputStream in) throws IOException { - super(y); - is = in; - try { - this.in = XMLInputFactory.newFactory().createXMLStreamReader(in); - while (this.in.next() != XMLStreamConstants.START_ELEMENT) {} - if (!this.in.getLocalName().equals("yggdrasil")) - throw new StreamCorruptedException("Not an Yggdrasil stream"); - final String v = getAttribute("version"); - short ver = 0; - try { - ver = Short.parseShort(v); - } catch (final NumberFormatException e) {} - if (ver <= 0 || ver > Yggdrasil.LATEST_VERSION) - throw new StreamCorruptedException("Input was saved using a later version of Yggdrasil"); - version = ver; - } catch (final XMLStreamException e) { - throw new IOException(e); - } catch (final FactoryConfigurationError e) { - throw new IOException(e); - } - } - - // private - - private Class<?> getType(String s) throws StreamCorruptedException { - int dim = 0; - while (s.endsWith("[]")) { - s = "" + s.substring(0, s.length() - 2); - dim++; - } - Class<?> c; - final Tag t = Tag.byName(s); - if (t != null) - c = t.c; - else - c = yggdrasil.getClass(s); - if (c == null) - throw new StreamCorruptedException("Invalid type " + s); - if (dim == 0) - return c; - while (dim-- > 0) - c = Array.newInstance(c, 0).getClass(); - return c; - } - - private String getAttribute(final String name) throws StreamCorruptedException { - final String s = in.getAttributeValue(null, name); - if (s == null) - throw new StreamCorruptedException("Missing attribute " + name + " for <" + in.getLocalName() + ">"); - return s; - } - - // Tag - - @Nullable - private Tag nextTag = null; - - @Override - protected Tag readTag() throws IOException { - if (nextTag != null) { - final Tag t = nextTag; - nextTag = null; - return t; - } - try { - while (in.next() != XMLStreamConstants.START_ELEMENT) {} - } catch (final XMLStreamException e) { - throw new StreamCorruptedException(e.getMessage()); - } catch (final NoSuchElementException e) { - throw new EOFException(); - } - @SuppressWarnings("null") - final Tag t = Tag.byName(in.getLocalName()); - if (t == null) - throw new StreamCorruptedException("Invalid tag " + in.getLocalName()); - return t; - } - - // Primitives - - @Override - protected Object readPrimitive(final Tag type) throws IOException { - try { - final String v = in.getElementText(); - switch (type) { - case T_BYTE: - return Byte.parseByte(v); - case T_SHORT: - return Short.parseShort(v); - case T_INT: - return Integer.parseInt(v); - case T_LONG: - return Long.parseLong(v); - case T_FLOAT: - return Float.parseFloat(v); - case T_DOUBLE: - return Double.parseDouble(v); - case T_BOOLEAN: - return Boolean.parseBoolean(v); - case T_CHAR: - if (v.length() > 1) - throw new StreamCorruptedException(); - return v.charAt(0); - //$CASES-OMITTED$ - default: - throw new StreamCorruptedException(); - } - } catch (final XMLStreamException e) { - throw new StreamCorruptedException(); - } catch (final NumberFormatException e) { - throw new StreamCorruptedException(); - } - } - - @Nullable - String primitiveData = null; - int primitiveDataIndex = 0; - - @Override - protected Object readPrimitive_(final Tag type) throws IOException { - try { - if (in.getEventType() == XMLStreamConstants.START_ELEMENT) { - primitiveData = in.getElementText(); // advances stream to END_ELEMENT - primitiveDataIndex = 0; - } - final String primitiveData = this.primitiveData; - if (primitiveData == null) - throw new StreamCorruptedException(); - assert in.getEventType() == XMLStreamConstants.END_ELEMENT; - switch (type) { - case T_BYTE: - return (byte) Short.parseShort(primitiveData.substring(primitiveDataIndex, primitiveDataIndex += 2), 16); - case T_SHORT: - return (short) Integer.parseInt(primitiveData.substring(primitiveDataIndex, primitiveDataIndex += 4), 16); - case T_INT: - return (int) Long.parseLong(primitiveData.substring(primitiveDataIndex, primitiveDataIndex += 8), 16); - case T_LONG: - return Long.parseLong(primitiveData.substring(primitiveDataIndex, primitiveDataIndex += 8), 16) << 32 | Long.parseLong(primitiveData.substring(primitiveDataIndex, primitiveDataIndex += 8), 16); - case T_FLOAT: - return Float.intBitsToFloat((int) Long.parseLong(primitiveData.substring(primitiveDataIndex, primitiveDataIndex += 8), 16)); - case T_DOUBLE: - return Double.longBitsToDouble(Long.parseLong(primitiveData.substring(primitiveDataIndex, primitiveDataIndex += 8), 16) << 32 | Long.parseLong(primitiveData.substring(primitiveDataIndex, primitiveDataIndex += 8), 16)); - case T_BOOLEAN: - final char c = primitiveData.charAt(primitiveDataIndex++); - if (c == '1') - return true; - else if (c == '0') - return false; - throw new StreamCorruptedException(); - case T_CHAR: - return primitiveData.charAt(primitiveDataIndex++); - //$CASES-OMITTED$ - default: - throw new StreamCorruptedException(); - } - } catch (final XMLStreamException e) { - throw new StreamCorruptedException(); - } catch (final StringIndexOutOfBoundsException e) { - throw new StreamCorruptedException(); - } catch (final NumberFormatException e) { - throw new StreamCorruptedException(); - } - } - - // String - - @SuppressWarnings("null") - @Override - protected String readString() throws IOException { - try { - return in.getElementText(); - } catch (final XMLStreamException e) { - throw new StreamCorruptedException(); - } - } - - // Array - - @Override - protected Class<?> readArrayComponentType() throws IOException { - return getType(getAttribute("componentType")); - } - - @Override - protected int readArrayLength() throws IOException { - try { - return Integer.parseInt(getAttribute("length")); - } catch (final NumberFormatException e) { - throw new StreamCorruptedException(); - } - } - - // Enum - - @Override - protected Class<?> readEnumType() throws IOException { - return getType(getAttribute("type")); - } - - @Override - protected String readEnumID() throws IOException { - try { - return "" + in.getElementText(); - } catch (final XMLStreamException e) { - throw new StreamCorruptedException(); - } - } - - // Class - - @Override - protected Class<?> readClass() throws IOException { - try { - return getType("" + in.getElementText()); - } catch (final XMLStreamException e) { - throw new StreamCorruptedException(); - } - } - - // Reference - - @Override - protected int readReference() throws IOException { - try { - return Integer.parseInt(in.getElementText()); - } catch (final NumberFormatException e) { - throw new StreamCorruptedException(); - } catch (final XMLStreamException e) { - throw new StreamCorruptedException(); - } - } - - // generic Object - - @Override - protected Class<?> readObjectType() throws IOException { - return getType(getAttribute("type")); - } - - @Override - protected short readNumFields() throws IOException { - try { - return Short.parseShort(getAttribute("numFields")); - } catch (final NumberFormatException e) { - throw new StreamCorruptedException(); - } - } - - @Override - protected String readFieldID() throws IOException { - nextTag = readTag(); - return getAttribute("id"); - } - - // stream - - @Override - public void close() throws IOException { - try { - // TODO error if not at EOF? - in.close(); - is.close(); - } catch (final XMLStreamException e) { - throw new IOException(e); - } - } - -} diff --git a/src/main/java/ch/njol/yggdrasil/xml/YggXMLOutputStream.java b/src/main/java/ch/njol/yggdrasil/xml/YggXMLOutputStream.java deleted file mode 100644 index 5d470cc7197..00000000000 --- a/src/main/java/ch/njol/yggdrasil/xml/YggXMLOutputStream.java +++ /dev/null @@ -1,339 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.yggdrasil.xml; - -import static ch.njol.yggdrasil.Tag.T_NULL; -import static ch.njol.yggdrasil.Tag.getPrimitiveFromWrapper; -import static ch.njol.yggdrasil.Tag.getType; - -import java.io.IOException; -import java.io.NotSerializableException; -import java.io.OutputStream; -import java.util.Locale; -import java.util.regex.Pattern; - -import javax.xml.stream.FactoryConfigurationError; -import javax.xml.stream.XMLOutputFactory; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamWriter; - -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.util.StringUtils; -import ch.njol.yggdrasil.Tag; -import ch.njol.yggdrasil.Yggdrasil; -import ch.njol.yggdrasil.YggdrasilException; -import ch.njol.yggdrasil.YggdrasilOutputStream; - -/** - * @deprecated XML has so many quirks that storing arbitrary data cannot be guaranteed. - * @author Peter Güttinger - */ -@Deprecated -public final class YggXMLOutputStream extends YggdrasilOutputStream { - - private final OutputStream os; - private final XMLStreamWriter out; - - private final short version; - - @SuppressWarnings("null") - public YggXMLOutputStream(final Yggdrasil y, final OutputStream out) throws IOException, FactoryConfigurationError { - super(y); - version = y.version; - try { - os = out; - this.out = XMLOutputFactory.newFactory().createXMLStreamWriter(out, "UTF-8"); - this.out.writeStartDocument("utf-8", "1.0"); - this.out.writeStartElement("yggdrasil"); - writeAttribute("version", "" + version); - } catch (final XMLStreamException e) { - throw new IOException(e); - } - } - - // private - - @SuppressWarnings("null") - private String getTypeName(Class<?> c) throws NotSerializableException { - String a = ""; - while (c.isArray()) { - a += "[]"; - c = c.getComponentType(); - } - final String s; - final Tag type = getType(c); - switch (type) { - case T_OBJECT: - case T_ENUM: - s = yggdrasil.getID(c); - break; - case T_BOOLEAN: - case T_BOOLEAN_OBJ: - case T_BYTE: - case T_BYTE_OBJ: - case T_CHAR: - case T_CHAR_OBJ: - case T_DOUBLE: - case T_DOUBLE_OBJ: - case T_FLOAT: - case T_FLOAT_OBJ: - case T_INT: - case T_INT_OBJ: - case T_LONG: - case T_LONG_OBJ: - case T_SHORT: - case T_SHORT_OBJ: - case T_CLASS: - case T_STRING: - default: - s = type.name; - break; - case T_NULL: - case T_REFERENCE: - case T_ARRAY: - throw new YggdrasilException("" + c.getCanonicalName()); - } - return s + a; - } - - @SuppressWarnings("null") - private final static Pattern valid = Pattern.compile("[\\u0009 \\u000A \\u000D \\u0020-\\u007E \\u0085 \\u00A0-\\uD7FF \\uE000-\\uFFFD \\x{10000}–\\x{10FFFF}]*", Pattern.COMMENTS); - - private static void validateString(final String s) throws IOException { - if (!valid.matcher(s).matches()) - throw new IOException("The string '" + s + "' contains characters illegal in XML 1.0: '" + toUnicodeEscapes("" + valid.matcher(s).replaceAll("")) + "'"); - } - - private static String toUnicodeEscapes(final String s) { - final StringBuilder b = new StringBuilder(); - for (int i = 0; i < s.length(); i++) { - b.append(String.format("\\u%04x", (int) s.charAt(i))); - } - return "" + b; - } - - private void writeEndElement() throws IOException { - try { - out.writeEndElement(); - } catch (final XMLStreamException e) { - throw new IOException(e); - } - } - - private void writeAttribute(final String s, final String value) throws IOException { - validateString(s); - validateString(value); - try { - out.writeAttribute(s, value); - } catch (final XMLStreamException e) { - throw new IOException(e); - } - } - - private void writeCharacters(final String s) throws IOException { - validateString(s); - try { - out.writeCharacters(s); - } catch (final XMLStreamException e) { - throw new IOException(e); - } - } - - // Tag - - @Override - protected void writeTag(final Tag t) throws IOException { - try { - if (t == T_NULL) - out.writeEmptyElement(t.name); - else - out.writeStartElement(t.name); - writeID(); - } catch (final XMLStreamException e) { - throw new IOException(e); - } - } - - // Primitives - - @Override - protected void writePrimitiveValue(final Object o) throws IOException { - writeCharacters("" + o); - writeEndElement(); - } - - @Override - protected void writePrimitive_(final Object o) throws IOException { - final Tag type = getPrimitiveFromWrapper(o.getClass()); - final int size; - final long value; - switch (type) { - case T_BYTE: - size = 1; - value = 0xFFL & ((Byte) o); - break; - case T_SHORT: - size = 2; - value = 0xFFFFL & ((Short) o); - break; - case T_INT: - size = 4; - value = 0xFFFFFFFFL & ((Integer) o); - break; - case T_LONG: - size = 8; - value = (Long) o; - break; - case T_FLOAT: - size = 4; - value = 0xFFFFFFFFL & Float.floatToIntBits((Float) o); - break; - case T_DOUBLE: - size = 8; - value = Double.doubleToLongBits((Double) o); - break; - case T_CHAR: - size = 2; - value = 0xFFFFL & ((Character) o); - break; - case T_BOOLEAN: - size = 0; // results in 1 character - 0 or 1 - value = ((Boolean) o) ? 1 : 0; - break; - //$CASES-OMITTED$ - default: - throw new YggdrasilException("Invalid call to writePrimitive with argument " + o); - } - final String s = Long.toHexString(value).toUpperCase(Locale.ENGLISH); - writeCharacters(StringUtils.multiply('0', Math.max(0, 2 * size - s.length())) + s); - } - - // String - - @Override - protected void writeStringValue(final String s) throws IOException { - writeCharacters(s); - writeEndElement(); - } - - // Array - - @Override - protected void writeArrayComponentType(final Class<?> contentType) throws IOException { - writeAttribute("componentType", getTypeName(contentType)); - } - - @Override - protected void writeArrayLength(final int length) throws IOException { - writeAttribute("length", "" + length); - } - - @Override - protected void writeArrayEnd() throws IOException { - writeEndElement(); - } - - // Enum - - @Override - protected void writeEnumType(final String type) throws IOException { - writeAttribute("type", type); - } - - @Override - protected void writeEnumID(final String id) throws IOException { - writeCharacters(id); - writeEndElement(); - } - - // Class - - @Override - protected void writeClassType(final Class<?> c) throws IOException { - writeCharacters(getTypeName(c)); - writeEndElement(); - } - - // Reference - - @Override - protected void writeReferenceID(final int ref) throws IOException { - writeCharacters("" + ref); - writeEndElement(); - } - - // generic Object - - @Override - protected void writeObjectType(final String type) throws IOException { - writeAttribute("type", type); - } - - @Override - protected void writeNumFields(final short numFields) throws IOException { - writeAttribute("numFields", "" + numFields); - } - - // name of the next field - @Nullable - private String id = null; - - private final void writeID() throws IOException { - if (id != null) { - writeAttribute("id", id); - id = null; - } - } - - @Override - protected void writeFieldID(final String id) throws IOException { - this.id = id; - } - - @Override - protected void writeObjectEnd() throws IOException { - writeEndElement(); - } - - // stream - - @Override - public void flush() throws IOException { - try { - out.flush(); - os.flush(); - } catch (final XMLStreamException e) { - throw new IOException(e); - } - } - - @Override - public void close() throws IOException { - try { - out.writeEndElement(); // </yggdrasil> - out.writeEndDocument(); - out.close(); - os.close(); - } catch (final XMLStreamException e) { - throw new IOException(e); - } - } - -} diff --git a/src/main/java/ch/njol/yggdrasil/xml/package-info.java b/src/main/java/ch/njol/yggdrasil/xml/package-info.java deleted file mode 100644 index f80c88a2e9f..00000000000 --- a/src/main/java/ch/njol/yggdrasil/xml/package-info.java +++ /dev/null @@ -1,27 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -/** - * @author Peter Güttinger - */ -@NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) -package ch.njol.yggdrasil.xml; - -import org.eclipse.jdt.annotation.DefaultLocation; -import org.eclipse.jdt.annotation.NonNullByDefault; - From 293e8249a8e578e745cc2d5447e8725d2c1c68a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Sep 2023 15:11:42 -0600 Subject: [PATCH 456/619] Bump actions/checkout from 3 to 4 (#6029) Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... From 5bedcee93e4585ffc829fe781b93e752b929f267 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Sep 2023 16:01:09 -0600 Subject: [PATCH 457/619] Bump org.easymock:easymock from 5.1.0 to 5.2.0 (#6030) Bumps [org.easymock:easymock](https://github.com/easymock/easymock) from 5.1.0 to 5.2.0. - [Release notes](https://github.com/easymock/easymock/releases) - [Changelog](https://github.com/easymock/easymock/blob/master/ReleaseNotes.md) - [Commits](https://github.com/easymock/easymock/compare/easymock-5.1.0...easymock-5.2.0) --- updated-dependencies: - dependency-name: org.easymock:easymock dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> From b7c4cdd13f5db85638ff5c2afb6f809603e3c9a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Sep 2023 16:14:37 -0600 Subject: [PATCH 458/619] Bump org.gradle.toolchains.foojay-resolver-convention from 0.5.0 to 0.7.0 (#6031) Bump org.gradle.toolchains.foojay-resolver-convention Bumps org.gradle.toolchains.foojay-resolver-convention from 0.5.0 to 0.7.0. --- updated-dependencies: - dependency-name: org.gradle.toolchains.foojay-resolver-convention dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> From 1ffe86f5799aa0a700645441728c38504344127f Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Wed, 20 Sep 2023 16:20:30 -0600 Subject: [PATCH 459/619] Update gradle to gradle 8.3 (#5974) --- gradle.properties | 4 ++++ gradle/wrapper/gradle-wrapper.jar | Bin 61608 -> 63721 bytes gradle/wrapper/gradle-wrapper.properties | 3 ++- gradlew | 15 ++++++++++----- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/gradle.properties b/gradle.properties index 2d552275498..847cfde2fcc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,7 @@ +# Done to increase the memory available to gradle. +org.gradle.jvmargs=-Xmx1G +org.gradle.parallel=true + groupid=ch.njol name=skript version=2.8.0-dev diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index ccebba7710deaf9f98673a68957ea02138b60d0a..7f93135c49b765f8051ef9d0a6055ff8e46073d8 100644 GIT binary patch delta 38440 zcmZ6SQ*<3%6s?oSw$a$O+t{{kpWwtNwr$(CZL_f&H8y*DImUnA_rrded+oL6w`Tc1 z#Mv!G{V@^ba0IDo(ixEwh?a02n}!DKIvnc^%1aoMq1+#kAhJLP_oX|jgEPsr;V`0k zL+b-2$5Bo(3c}Buz&-hcO_SO<6900dXYt$Ter6+M<Np3Rtqu&`gRTH-fePOQT?Z`% zRdO&0d}|-~sGXi;2%SfQ=gK)+=4P)K5)Orj7!b|QkzThfLNYoJ%2|xj<FFjJ^`y+4 zsalZsbx~biRjDjxn3ND^O*9j%w5jyWGwxj5_;5s!?bfhJLu6uar+(xNDNu*Sj?Aix zY@n$7Exnk#*aYy?=2B~5hT9Hg8hX7=D>ad;kQkW8)Rc`!+|ia-jh^V%qoFb5JQ`<p zB19fJn;_O9cHv9`C9rT@=!_;KvG<r)c8;=6%v;piYZi0BU5Go{==(8B-Z!R3S;&d5 z048Ll=QBK^xSY$AVzS%Y;RW6Ro#u+glOnQdX4Jmkn-Y*x28{&wcd3uGJjASa+0xiN zWB6@sBac*6q3yWnhygmYtWtPAR%R)*>cw$=gQbRBDL9uPo|6z1d&0P$^g)iXXq+aG z!TDoXPDJupv0Ph%%-$NJB$S*LH3Pj-EMdD`^o@l9JuD9pr?_DZoCP&%ipHzPybh{p zrtwXlO?7IzUEJYjV*z1IMJAc^TVcT#aJCR5sh_J*AgM{aL<tsgj7Ap5h9){+zf@dx zMzW$(EQAPzro?-KnN(2LZCb3kmQvjTYSkNVkn+8dvT}^O;IveVJ(e$9J;<r5xL%2T z8hZ7MJGz$cFM)pUW1<4!tGO<q^rZNGPM=_0Mryb@MO;C>(JnfKKDlvti|)rcg$yR> zFZY!7AP{l1!V<|A<jxEYVee6A<hh!~A+$qk(Vv6(DNNgCcI=jc4}nfg0@!b0Yt-%J zdCJ#=+#C!KzLM|6FEZ&IF{G_gr8$C9^xiks!Q{=4PnYzWK!(OZi7qhHx%pAHQzKc} zdh;lmad*H^b42cWdCcK+sv12^CrI!uvTWi+uE0+b|DpkE7H?UzrD80et;k#=iCvQX zbv>O7dcVC$a;d#(osjC65jwf6;m;>++|PEYd&my^0!s~Q>DmRKZN~jp{~ON52%b=q z9ZhEnQes`_LM8(XXSsI$^djEl)Gsh4SqMl>FfdqHFfcG-unjH*zN8pZVbEU%3}Mu< zK5xbz0Aq7G$yY7xH_bgTEb-`wy|{Qa4&f}RLJR9TfY&zrrsyr256<6sG0KHun45y= zrnRE%=A-^}sxJ1Xro4=eoXxMd&rfQ9?JbUc)8(J|rrQ)T7c}@%CX4-;3`9$O7PxF! zbp|JeI)LCA7PULGnM!E`H;{eR0pF@A6M?&%^_*n@soNA<EGWy^jA6&Odpch4t`c&N zc4*SVcRN+_rBgQ5l(KGcn=LJNQPgyC*9jkSgTKy4SIFyvKUe9z5w%qh;8|pxWOEru z7=cGY8dmg9d*PD<(rVN6W%?{lC;(MIWcD4VPKnRxJeH8#ARO8vg5a2J={j1)Lx~l| zQ28wI-ZBB~bc}CmLpm^kFs)nh=7yD5Z{sgK#cB_ew#;cmJO=klMj-=^#8`U7sj&7# zk3VwrFuzut@yjnlQJO*qv>v(4M|!WzdvUGqGgPoF5`;C7G@DLC3l8yzd>B3}JjlDB z3x`&ah?|Q|rq9}QK_f`h>~h2v;CNKAfG5NzNk^nNpF_nI<K{~Vl+HSl?C(Qk3Nc^b z@u_}={mpdH)qC^tZ1HlOO6#89mVfuh$&dafm}c!IdWNPXD6WFTv0`a<MtTWujX#5z zborA#$}nM_N?nvWrblXi_T<!r*{D8kJi$KyELI>EBA~cF8stYk|6Sz7O2a_sKs73b zj6jQWa9#&7hJhMllw>wII;=UUko*~9!czPm0iABBcJAGu0K9(Q)gIi1R7u)WdQnO! z41v^(wR%Ho`GC84TClP}LyO3oimU>kv2lUoM13DebVvVA`Bv4!8OF}rFw`~yQI$~v zUUZ;=_6$8Vh&x&$FemM-6fEgVx~w6tTVcV6uj1=dF1xHFAMBOm6Ff8B{}$%I+Qsio z_(<}nFaZTI)54_%7xVDcVCW?6m=&}mw<aeXQ`OB&p6V8}U&be&9UJodh6%~=Z<F&H zjX`H9SiGI+J^k7Ed|Jv~`FfpG>;u#47sazo5E2)a7=>LV<i$g;og6<1B1v_MIqY0# zaH)oE;lyKeaN0sN$~jDp-h5FSMdjkTV;fvQq5(N=nd3J1)p8ZOAYynfRTk5wcB(F^ z>eOWFa9+5rtWRhneb(A=y3$Mj(yWqLF)-1}d(r@@520N*pB-DY;ufpBNLuocLuSo* zX}4Wf9OFw5daA10>Q<^_y{9UnJK;%aJcN~&>{ja_b*(eB*|E{IA&ItW*xD2oB<cLZ zFatH0kw$Wq6IDKul<{MSk}lEwsx-$T-c(=Odr3E5*YD~*ou|^>TzZu}=eDUV#ZOUN zXswi>Tw~0*4c#%%VtG-oA}haWl-!+leTbj{?i0_q&Ns+EUcB_*I1#9aq{baKPOP8> zquJO$$G)w<wiAknZm%V3?vPINcbs_hQG-G~Ss7Tw-aE!faArzNlxM?(2`?8ju(s%V z;#i@Ic*dOLNIl6EMvmX%TW{dT%HU<zEy6u+EB3&JOvgREIvxMu|Hdeiw8;H&E;Rrp z2dietHXP?(Z>OH=wq5Tiu=1cmtY?)G-3*}6ChrN)@=vIk7+q!%J!R2GIF{_RZ~^(0 z{1n&rSjNQ`6#d0RmcOtAGsuJ$MgO;k=K*5et>Z(i<w4=6BoRaQN6QlVy~3I)#+bUv z`e_x}<9oT8`qG00d1h|Wm|I2Mv%uk-&94=m$jAU1M51q%Hdxyh?d!-(Gs$VdP4O^T zwr>MwKVVyV!{>}(x^|yTjyaQap8`SK%w+|d2}-<W%hgq4vZXMyFk|d)N}ZBkTUcLT zH&iwE%zOG*f0@tGsT(3;9~H=#0S!u0n2tKH`xq^Rh>nX_*q5Ii^bId@HzNlTf-ik# zJt&R~9y-|#9MD?O!l@&h6a*#gi(@<+p-qK4GHjvGP7UUd%ua~&)mNf)BN!k%6o{;b z?|a^{1H%YT-~B{M=wqb&On0#I-jMfw$4hwZgIE_eb1zNHF@+dEquD7zjW2`+f{1@R z<D6)Dsd|>Z4l^*~{ojx~Aa%*o*H~t2O#>Op2Q=bMZI`iY`RAFt@{#ka{{8#H*j0Es zuc*a&YKU-+ys?}Vtg`aflW?HLXKnx?y5yc6tk|9p{qnan84i~)e)sY1e3of!qA^y@ z<9^mM?3e7r^uxrVTrK_4_t~|MgLSRpE{-~qpN1^5`>&`ao+{|R0BmQQu#?~lC9pz8 zgOJm}rgn%0Ma$cSqoETH5b4C<Z~rX-zl(p$$bJKZ2D^p?1CvNH#v)2Gl7LC_Iiv)Y zsOl?X2xAI1eb+D7t$?a5gf+qXMTXFgt17ZgMI|4ZnS3K;AI8`IhpS2V!0`6R%>e?H z5N_V5{HTW|P&G*0_CWMxc8m9U%Ui*+@AKsYs-MaI-brb~RDzj{`208_$Sr5bNy$YC zSU(ZJnG_KHj5i%~juy0n_#S9n%LoCwwQ>E0bsi7&olO2^9QQ7%C+$0%sPUm8fk-b; z9HD_4L6X=YfRUp$VKk4Z7^Zj2qlV}}4;yUrCJ+gU)om4KvxkIv{XTf{x=8+q7Uiw; zf}obHfKHW$08xRNcLDc;;1a`cR4@+)f?1r4ihTupC3r|HbRc$>9URy2I<XPdD<EWH zq^5d0-2HszkHWr?*4cBF8*Z?bKCPyUOiaC3p#sGJ<$3tYC3<W)kJv=S3+o}Uc<}ti zF+-Eu`#jaqBR%jgmU10TzJkGAlB$tw)m9RvcjP=Xf-Qivd4n6-r`nM|76@_oxbgR^ zDQ#U<7!g5})<i4o3VX=sNdXx&v`PY-GXIH(UFX667BU^XfLOJ3I)N0{%TihBI2%~t zTbLwE9Y<DVwI($1B1HfJrQ^b9&7xP<ll6@y3|+jGaXJ#KfHAV<?Kjl)=P^os+T7y~ zUs-hqOA5yzO9DH?5fVY|K1Fp)Gn?s3mw0lL0_9e5jxc+Bd&uT+Q?i2P0}&IwP}vu- z@k{Y^wpgotrEhSjcozzA0zy-k+Qbq@X0W7Cq{YvG_+tYzNoyof72~Z{;EhoQ_a(HN z_yWeA_=?onC8M9}q&XDD{sa8Ky0d(sC_sb(1M@&jy5^)#x;Z2S&1hW!u~xAD<{bT= zoiXc2rASdSrV9?v!hvJ<<lq!b9+$5lh9=j*yCO;q$jabh=LXmPzPu`_n<At(e}@jQ zQxTYGH2kJh)!b6${yOrvzvY8KkHPIaqoeL<Q2ym8&1Lpali^9*v(Wq2`x*DOFIYa( zAzSJ`IG(Zqjd58tNX6<{Y-2<;I}8$q52Y)^?dPBoWN5G;8@6d(G(3eTCuV>)IKt5M zLwq#f`p21+)I|BH736z*yP*U*aq2C?M`GLega)LzyHQg#f!SmTtkaP?4-b*SI?s3R z?oTtQ+(B?S#imnsbU*ri;I~N5KlrH|7r#QKV+CcCYj`o=K!4cex3A=EVvG2{F|tk( zTjABWh^aV6P{*Vy*jh=kW3Ub_U$5Fpwxu0S*5SauV?#Q0gqAalZeiUrH28;Gk|mH! zk(tL<b#w-wNc{p9q``fg8S$Ojx73tuHj7l$#@j54v0|iMlWHAC94qO$GcfI6-b$8_ zOq2Vyvj?K)1i9o>@&Ng(HjUClH<_F&{X)yJBrADX*!e#ig#whyQu3F!`xyi)3iLaD z<67yZlPlA)))IX)?QeiT5j%pQh1GjTnkD3Ss{|g_uz~sYG^EtJ7GZ+%_>1zIMy8)? z9%(Zi&1j58bvBiJ0oud3yPGvgFlAXY4fA$wlDj8~Ao_1p7nXJ|PySa9tJ!~~WiHaT zPl#eE!jkxk)P9!`n%5p9pFgeUt0fyVXP?c{p0QK@64sFNFc=hu1EIb=KmRz1bZPVQ z)NXX-MX3>JzO4@oOVJBkn*ZtltV7FRyKC%ZR8@Av;lf9=?*IM%Mlzsd<NT`L-u_1# zkK?EG0H{W*Dj%E?KD9`_U<woxn4(o=gkbUwNfSt&1DW+S@<>uiA9z_xaar}nSh2do z=VmjwNVL+dx3cndUTINzc4S{#MVr;F3%p!7Jle>+{nqtd7?|cHWh5F_aV|P^^%b4Y zSIK)`Zy$pTCs=URYRBn^wKRi{Oz(kLM{_!=4}$*+Ob;Xvm*%0X@1*&)9eSw5^!h3y ziCi1#33c}8o;5%#&W$LxvM7|loXVBjfq>X8M^eLykQ%c)G6`LQ_Jv-7o+n5Y-eBkg z{%cg&=30SLKWLKKbVXUxO*fC~C;U#oHWbLxXlGd06a1E5dVn`}#Iu`mp9s7~OP{vg zAH*8Kx#p=4-z?uWe;@oCGoN$jbQIMpTe<^*A!mnCg4?wftV?fuRd1@a?dPoyPI=a7 z&50qME6x&bsTPX0D`y?!yqWtj)f)a|&bJ;@g97N0$)rx+J!G%e&T^uoANb+bl;}yt zOBUtwYRb>2QdBhyau5X$^mwyWOqaH$DIkzS0MU{I_?ip73$RO27xayL6-TU0Bx=lU zOnb2tz^i|l!)`AN<)$F?kt|pEj31CSTLuFUS?<x5wRl~(>UPF>gp1N7A6U%nA)-;T zFzze1YG#tz8w@ZZU}ftt$f3@z1y><KvO)eKJzjh!|C$eTHk40|z5xsqvAAxo0WIIS z2Q4`(<USeXKv^84Ne!=tRVT=auNu(XS$DtXFEXdmeA3W~VgaRf)SZhSEY4GQ<SrMp zDpEv@h-zf(iH-3qATUaDXkgAH)$nFyiw?+S3e%K+fzS$Alf?-}QDeDcg(JCQNYQf? zhwtulWAo<QQPD|a3*-oeOL$^Ekq#hjhO4wF6bd!62kn#QS@1z1elDmTB5L~S6%3-V z4>oK)5phro`WuYJW10jr$<{lQh2b#LcdqCnwjM#tbFP{xd@3dNz{_H*E8az|^PIxa zd4CV2M2{Q+5QOZAc5@W>0_Dz~g3_h#Ty{PTa8KdRp1nqtM!PwFRllA7#=Y!BP)YWw z*m=tX_kQCJG5{1bl<o$%@)>=HGJDL=nia>)l&=#lT;j`B{Sx_YC`-_w06oFrM?ec# zW(F;~7JpWReEQkJC!u)4&xF0UP-f;Q^j6YB-+^^v3ESAJ7UFpB6!U;TD6bm7Pkv5@ z4qX?H?rNjnV|~OETOPJ==>9>>kqK&GA<7jG8DbZflmvnfA^nDtr;A1yzkN4$Ruo<0 zNzHzJqBoSkgdCxn0;*=#)rJv~t-i7E`bkgSF?8DpgAb3T(+dsI-74B)rx}NX_WfT_ z1Y3kgq;CzgvHuR4-{J{j{2e<va6##yDR4<<YY!tch@1-E*O=6vU1nx8_Cy9AmI(_n z{PbACvP%HfU&2pPKKR9C(kpe3%%tQezH6hlZx<&oA_JNW8m#?J$%bF03;2(C=dWDM z6BQ*4m4w(@VUEcUck+GA9=wl-s^05^?2`BV6tCwN{tTc>kn3?Qe9>iUhP}+8;1Ha> z6Kfda6}SRfzkPv5My^HgfdBa<p~39#&$`Nz{;)}db`_VDFvt3U1ly}vcy!@EGBC`4 z#3PCQRKghf&Z@vMU=*l`vpSu5mAd6#emjC=_+WS|j7}7R@&*2-JnFK^Hm1d<mFjx3 zm3z<oWAp3v{es(Ho8dx=q>em7B(b<qrOdV1Nk2!Pz5;Vhj<5+av*}VhHvABJ=*Gp+ zZ=l2$G|3s~O%-%22CG`TzU3yLW?XwPu2Vtfxxyu@+p4bPeD20ni6xsJuscjT&L-Yr z4>#2{RUdx=6K`l4Uc;k4X(@$Q%XOYr>NCM@&&R9n+}hqVTT=3H!}7bQfo40wGW9~I z5S!$BLPu-G<eH(=ONI7~?{B?zB2~{GvG{~KXy=Z|ps}MoWmQ1hVd@nrlqQWcWw#&; zF({VDfBiJ@^xI|KJFR3jpUDrk^M0Re;8YJVJqUa;){5Perc%wDsgHP!aOk3!YG>1O zbT@MRy|HteMUHSJMA*;;A28)(R>6oTXvSWXX>0^*?bc!aa|{mR9+$n|UmW7Zd7z&W z6g}23%(A<S&MHS#)`!?M=13yi3gpM7iet&nUx1t1HYC~3dup|>WI5hyXcK?dT`12Q zG#!5cNQCYfNPE>SBo&tmCx(V+qUInbJ*79pewM-8p(KdhQWtg=ON<km5mU}VL$4#d zJ|UN#+HtUqStstv!cUz7p-o9$X@hM*Ato3Nj*Phy&jUiO&du18L)zK*yv>xPC$uAF zh%8z!0ah3gBNbH5*F^1#LQELAtxiFt3etSiXo<`w0+73K7;<zh$GF$QehyQt8-Y~1 zycSNpVvH|d;e~{TpN@I8TN^kO>zdy!Aw$8LkTM5TQ1AR1iiC%r`rY{x6YQ}BTDk28 z9lH~24>AoGimx+@G6NV)II`KI)87J9Sie1f(jB6oX+ud-fBk=b-hew=T+$h*E-Yno zGSwBOy-boE*H6&6)jTU5aE;R|+IpO<Y@okA5)O&3RPBg!n5C#N-z1gPz%5hWVJ$M% zo-G=aNO^7zs*ZEor^Wa<53rxvvhpLHGBo^o5x~AAI8W66Buu(6m1^$^#3i-}+8?;< zU<+zIU#8gzcC~BH#8MSFOo8@<j#NnDp48J?e9DQGO9PQUnr$`_dYgwK7}gv)#~r+N z+Gm-x4Ct>>Y>619X6(p4)J{-=bXh0RcZ5QXX>@%?4DWq{HXnT^FjDmmCj~8GU6j+{ zfKU~bu|50iFp#(86Fb-9IN7%g9Pieu+g{UcpYYa{Wt5e_Gbv-#*3M`v%E<$YW0%sG z7UER-96%;oBi*sOT1pM&rf^e;E&&<U-8XU#7I16yEp&eA#`S5smD?8S0>meNJ>>yj zWwg9jPrnt{RYM~{6*ZAvh^7NBxf#}T&dUIl&TM3SpL6`W72@t&NalNqsgEhMl&*@- z<%<1o9Zr32c5eALj|bdm6@@>mppL$=ni;ROU!aK&i9lzWOrbPBUnx=C<a;EFB*4K0 zMyQA=2UsY(85SH`DW`Sxbs*j!dXdp#rop_Aru-&MOVfERY`D}R)K?YEBKGE7m|y%A zXk@bpxK9~46RG9ltY1)*VWPdTBLh_Y9%NwxXnMnxk?eXBzfLxU4GGj)gvjO8e485` z!yUUtZBWijo6UmR;(8@bP#WZSmxWq)HEe7W1F%rdn}|9?M7IO$yi=a&M2h0n=nP=V zFN^E;32UHB(8^A<DaBYJG%;WQva(2r<JK%u0E+aZP96VKI>DaovoNwCz`&&cd9>XB zZ>(iG#s;ORZv&PjFuzt)$;D|#cp^j>zmo*($e3h9%2;KW!u_7Baki2d71d3TsbMXQ zJW4Pt?Kvg5`Q{tA<FrIjrfR{b_Z)!B$4E3QLnm*FwuI+BceQc-an)9}`}gaS8%(yH z`VayOqdoti9e1>W;jl0gQ9~`cf$oqT%9ak(hXG`&KY|9XQcoHxYoe{}q&-g0R$Dm^ z*F;!5RSsX5H~yj~AwP-~TA)7F5Xc$Y>|n<#h1F)xMp&aAt9^ZSmA=E+Ynnu*>5{dz zbC659+Moh|r7I-r-h`o5tLhBo-#QGqV6{>oW9^BY@!h2u?#R4ok+qL&vuj#zu0b~j zc7T@9jU51U+&vjVLX(I9c0J{M+c#2VvZ*_xU}efY2OdMSbYi<3_+GJ=E1f|)P4E8i zcZoAO?iFYrsY^_f-jI0KklnABNIMams`JkQ+(6>=nf$Ff)=IW>QV*jz?5Trjm0d=b z>2A18<96c~B4Sr@4BNV?QUWdav)$P`WRSx&euoBu!<ucTCZn=VFY|}?e&+sBJ_o1V zzCBV_`$YXmC_@B(U!LRZ5z_ugb@UvEPb@Ur<K70^B~e@XK6bBBI#0WIh3y7o!alZx z?F;%Q{?#5mv;?-{AwlS4z^g{PqXyBgs}FrWapq@49<Kkq6Fv~d-c+rle};wO3v})M zlvgX(Y=kR&0HvQXkWFsTJVOF6g7Y`s;m@Jg5Mnsb;d{;|7gTh;b*K2UYENo&p4>{~ z!kqPFKT12!OIyvJkoeNOW3_V_vCyXLuWvt5zV{#u`2ok`cv)*TV~TBshWUw>tXVOW ze4Z*y-r@_;+yCC!%PG3*OWmZS7J(w6y!zsTj1zk?&{YoZ=Ff?AQl!`tGd2W^L8a30 z*NZ$9$$#*6iaM~toxUwmWaP`4O=AL5Nw<qSh>NMm?V1D;Kfk@wCcpXQ*@)Bq4)s<7 z4OdX}H1@zS>cu_WXrM^CXybeFJYf`@_^L!EEU~hwJP5NVEy9aWM9>tCNP{ZE8pe_p z`Kn2o0j6`TlV3jrKBW~$3||6zU~T!n9}}%nSXkcpX`PT`TBQ!s^oAh^`Cu8Dq*|c= zwgMq8A}pKJJm{A7H3*Bk2X(T2Z*$GCTcnV*ILS5f$^8;M`u=_KR{~88!*CY{|7*M% zolu(EUB?l5e-%EPYdS)6-YADjxZz-jQtlXS;!^To$jjVf5^?Z3I<o<qc}(A<Gt-EQ zwEw>3UX8TAq%VF~2uxp`CO3gVa46XP+4jn$55b?HO9c#NR6*c-!)0T#3Z$4aT2!;_ zl*+IoYBVR61SAG#&|jv@QU(WL*7nG0(F^2Ph#1NL1}-j+IFP>}trk1jT`Xs_GdeEw z`hI{Q(np#&yo{|VN;%Aw&@FgFtdY2lE7KS93n|3J!#6OmBG!4aP}Bs>!kzmD;&pd^ zgYr7goZ5_QLwYZ{e&Zz?&^HUFB|~kt42g$Mh*;{Cm9@{J)_J)_6*z9V*QDh9E<Nr$ z%!;L?^W?m$cG<;tKZ*|1b??uwn+&waWi#xkJ=P>5hQ!{^oXXv0ArPwLPt|s@YdYu5 z+Ny~{ckKAC<@aSK#2Ig`wU5AW#rtsq4{}t=p${=>lki>>F?fFnZKEK<io;bYSnJie zP2YbEc@xSr6rYuNWh4K0f9=o@@jF~Ej~WXo;a)aw(X*97%CdzPXZxAWTiaDxT?XTF z2xFb%(^E+eNn{8SfrW)I23F=Yw`tP=;}?djd!o$>-QL(Ttz~C~BE;yxI#aa~5W48A zN;h8P-&;-Ywi+FeNjFSRzc0kI8l2&m7Lp@BT%Y<O#?S$#EJnX#>N#D;sTz!J2XLa= za!dTPbx$!Ci64E4pRQQ=)H8|A*|@AZaAyQyf@+Oi<B<y+mV{&$a+l-|$xj1MX6N~G zQ4kCfJgNy6v}RiPqDS9t%Zzc?KwuRSY;rb*@gjWo<a8KP1X?V9B)PJLB(F$CefbWA zWR%k!uXsc-^S!1=r-diWBTUC*6lKNzCY?tGZO-Jx+^1C{86I_u-!U+*$;gZrX}@8N zf<J5@Uj35#q>e|oFRniQa8K@*aXb9BkxoY>vcaEJJMLOU1Q0;QI$ZdvCcc#bfu=)@ z5pJ&99yNh%v3#a&F-dE2h^`|nJV_>T<cF#A=&KhZ!C#=r^@#FcS;y8r-=%*0XMjQd zGr$;=n1rB{n1s<m|J!n9@46gp44ITvZ}!(JC>dAz2}?^F|Ic%+Oka*a-BjW-ciy1v z`e(WZQ1AMjMKQ-)nLMoQnneTM=_AE$eNMOHcwMeL%GlqJH{avHQtK;=zBlo?c36$u zrY{wk7%-u5GK||dmWGNWQHCCcUjEvr3QA=D!&Z*v2--Elt=y=6Y<6q5e4QJ~70uYs zTtl6$y|KpwCfaxy!60Vj9L_&&d7W-LG#wBTAs7bd&Karcuieqd!hVZ#y5O91%#=6$ z!=Jk0WS3MgIk=rRlMDBw{$uMgWfD<`JhJ@EuGgJLA@~fo&3bek04!B1VK-2#q1XU| zGqt5jfr9t???ne2(tdoKEIlu<>h>aw<zF(e?rs8FaSr~^<16);0p7^Ebg~n#7viB? zeffBA7w$_~#<J)2x71uU^X}7m2llRWz0xHQoojaltERfKsb@BABq(?+@oe?cSV(cW ztzLlY@czoqfMaNPt30i3Zj(kB5%+91mQX~H=o@ec(e|%rKrLp}hzV|w0%Pde&R<A< z^f5rzzVL(HX{r1pUy!&LkPjF7G^KJ+MJp1TmTiKqF`@(E26*IzxR9AFE#%MfDuLLK z6rE_Uk~_OUr$mEdhcj1D?wDWUSU{I`-zQOJ^Mv`9?iC8@XgOEx9U1)V3+xf9>t`WI z>eMVNl1BLu?#<4>jAa}q;aD_%-}>m4MaBnm%7=lh8J+tQHE~CND^kkgkjpcBoY9mJ zSZ;yMyG5x;iQ>v<ZP!r8qSbwVW{+=K$Ucj|kJVVwaX@W#|G+P4_|BYJYyxkMr|1@0 zopekjyzv-lVbV|e7xKzD^9gG!?uM5`Ap8%a#CWV~?{_G~ByQUMGoj2<f3nd&i2thB zV~QC8@}GL!|4{?B|J7dg|HcKSsOmc|8e#e=&|)IE__J+)n2dAb60B_FMN^^6%Z*zE zZj4+R<QwZxZXVlPKK$@JjG;1kf%s4!y>GQ^6b|IDo|DYG&pyd+`}{mPVFrV-+K?); zArG>_R<vKPH#Z&=$%*$olV<i3ms%khgKWY;)E~52OS;-0X}_+FE4&7|lrAGd5B|WO zT)L&OmMAlj8uvw|b5&<XN29*KE^$>K!!bU+o6_J`BDX9ao(JXxT=V7_319=t`6=95 zD(<)nSg;M>m?Y`=_~N>MqE9|vWxP-j8jdF{MI}>9UXH?%UWBB@Co7TH0H7tc;Eph} zLZA!MU)Egnrmfk^E)PJ?7VX-iOz~j>-I(tdI|8!TwDGF(Z2&9dw0c#;>HF@QxL+?H z(Ms>%IR>tvaBTK^?;k4aW{h;aG6fjS;50tn*4G_589DCPMHdEzj2$yJJeon9yytlx zLE%pIAHU#MMuzllQwgEwSg;rN)IdEc;)oU@pc=#n^MQGKuze7=B(kna*3KvRApt+M zou7qk;^cTko+6l?CU{axJ}ySeIu(U|EgOehQ}P?~{RL9yW4Z-n00l<_5C$kLr#1SS z?m<M5O)b!_vKDRH>qxhmts((uF03k)iP>vpD>s@+%cOWFZFnYq$eKHBo`TyKv1!L0 zf55$|#cE%>K8i2cio)3)|B3Kltz2sjcPH^l1jF?Z7$R^xY@Nb1jY(8|LqdJx!;e7+ z1EZq@1EcysEjn~kzBDYz+DLaL<tvu)Y+{Z%3xgWX3{5-y(gQ9aeONmOie*@#pL9SQ zHuBSsUW0;sf+Dm&_~!<#Y=OL17PbmaGsA$i6C8_qh6=oSzUpAJd3pw$?3A4GBDhHE z>y>XJ!v;LpX94iK>p9zHPH+l1=j(O2eh&jCm-gvtHB@$fo8blesEhC-WV_MCa3Ey6 z1-)z?-QYm?ayapPhq+CG!;q9a+Aj|<rGh^6=>19Y-J3#E5P;;IvCiaB=OKrPGlW_f zp`3VvrP^~+6;?t6opG8xV1QRB5n4IM!l|;CvcD`Y;ZZUlpFl-*h^m>Y92(7~94u;Z z+mMp9d}ubpDj^6Or2{`q-nu>C*YvXKRlU1ylv6@sH7kse$uJu?!(cyF6D`G6F*9k! z%qmYr7w1@sSHZZqhN5LxuCbsi*@}n9h)U^4F$>fzU7!PGW@cQaEUnO4dYFgW(983x zT2$^Zh*sV;=vjE1$CA3J19NrUdNr|WR;qd$m3o-j%o;!uE6I$qV(Upwr%)|TD}Y8) zOYdXCmKJ=BwDFEgV6J8=9mGne3iXtnHq(js@i#irG^rk1`XiCLYPF;)#11K=e<;n} zRhb4gYJ+uP-2C#^h(hms9P|)l6j|0Uh=rd5=$ysEEPl!)7Hh$#?mXDhFw9Nf7;vF{ zTW3C1P(>hS#I_LftfASXyPq5uk{5>o?e^pNwH`ZgzgOWy&G8JVX{wP%P4s?c)Wo)) z(q5Hx@;XT7ETog-AV{1hGzBhbb@!S=JpRIj<yP(&>EcCi@s1&A6f*Zep9v_>X}F>c z{D=+!!;x!CHF{}0rngC5si~`~)o=9ke2m1BWhw_@yreoH>S33khQo+rDR+(Rs5A(m zmTBy7sF^v-`#uhq$gWcJq#4#9RpWb=^zXw#4KiZIx}u}#4BL-D?7vL@EFXHBgdT_w zZ`ugSktPZ)uF)W{qSiI=t*0am2Z~u2P-Zl_yfHO#pxakiML=hYxQm<8XeHFoEE=u5 zbMb?`iOrD_f<+Q$S_T;1RR_1^TspnliRW&(EkhMm7O++vqk<axL*wPCJ$^$D4ZH{l zJ1F1E1lTGUPiVZklyt_*6Z|=a7caL%vqlWofG!k%%*Ayb(G5x#*tcDF%N94^zO1C? zX|j^v{ltDw&ELpeJW>+zGo7PvOA?Rn*J}ioEGUE;6d744<EtZm)?j{L2c&B77;6vU zeL4VC@&pPn<A@wg9OhX$5d)>zR9=ZOacAGHDWWJ+Cy;0k&^DS&hc>-0GDcoDo=j{; z#R5~G`;w*wqo*HyExi~2ZbWBMW~x^s7uJt*Npx9o1{bus_WyynTYJg1Bgv#Nq0|5o zT}{MDi~B;FN2#eI=!~k_5?i%fV6ay=02lpyODWL#v9Q$I^W(y`s9Kjdo!PQ;dD$#U zPL20SMeVTgwv;~xwemxldtKwy+S=INb9_kIv=R`LiO?drHWtjhc#32?p31kY_2UWg zGE27>jpWX<HZ6eYs7$J0ldH&MMdu*jh$U=`!b1wd>UJ_BXRIh~PBVvFhK*uULDgaH z3FAe?S*6U$4$)?~%`N!=#=wZ4%XH2}>c?O6aN`!1n-W~*bCZHaf62)Z@<?Ywn{gLq ztafnW*!F&7mcZ#?!iD^iAOhPk;X}yogu9vCJfq%kL?WOS6VA$wF7a9PMtTELSd>+w z&xOYRwm8mS@Ws|Cw_FCyRAP?{RC`0t(kiEB<oQb40Q|GwMrm^<<=E%ft;S4}EabAN z()E^qz+>KX%x<vNgt?R+2>6m@`_hFMhil6O1CckfgEp(<T*U~qC1}NnHzZX$5>59V zw#@grwjA~qR=QxG(F?p%d8k3Cw#RItkc=BN_SjcrOaXmA)-TC}tWBk*xSXNjswBmf zHm;qMT2R%t)J|1o3(=b>n21vKsInqxdU7l$ut}Ycl2fz+9eSiR7r>DRe09d=lzLr_ z*53ym7o|FY5?38Sik|%hXX8wwtvP=|k(FOHk1%Un_i*CHk(c-bqb$fFo&YY+%Rs(L z+Xn{NU?d%#QbW&dyj2FS*@&u(9wSkhx>cM^yu!^GSU{D+I_SI2(53~b+On9qoJ8GC zy-eLA1?m9OwyY+$+t(7W#|2Zjs>B|jbn~=YkGk#zSCzDlR7OVU*grbSY&IXN_L{*p zehPA<nExU=Z&*rvKLULnQDLm(IW%RV2aHt{=HKi*6Uh?C!?3ri5XC_AVX?7O$b4dd z!SyvAk#@*~$r7htBj~uY5=Xus*0knHhW}Xnv-{I_Y%0(gbnw9VoL7@_d$CCSJI>86 zd?U^Mre1|SK%UDU>mJ7BONwXtEDpxR{*QzkHRWKU{#`!lk9yEe?Z<qgq!(=_A2x%h z8dR$-zz)De-b+;7V%BEYdL(&=3*^v-89Iz!OW&=-Wq56n`JJ$#yb@21`I%HLxERS; zXdz27+}Ui3!JS;0p)JNM@uZkRn00^x02P!IS)D&{->RW(${))_<<W$FEhm<#EvhrZ z2M;B*|CnD+UI(=f$qqp#wmxw4addNuAAZdYcvOL8Hj)7d+<OwDxvfWdo~!YKbhmtv zw$L;StUjg`gedd20ZQJsB-58-K`~#W8eZkf{NYUdI-S}-N<M5=hv3*nbDN4~Y1vAx zE<b8)H|xrgm9J84ReBm9FJvMaH|XE3mi>xSM?M4nB0z6FOLOO%!x6Rrw&kk-q=5z= z9Q}^*V7K!2h6bVo3f%LqVIjn0cMLHgOLmXQIO0ty?`6dtcvPs;y-aEu;I!r;P~K#U zdw$RqF`3`y)IiUYEXDKjG%3lG$)ZR#y5B>Z%x)b;SJEgSVLdbM2zJ_0pPUvRV66D| zVyNYu6N6ak-ea=a?ICEQ-uhslKpleebsF=U2&w7=$up7`cFbo8Zi2Q*!{>h(oj)gn zA9Lud4J9zKEa?-&9N=9Yl(?_01U%sw7$#NxYBloUnJ)JVmZM0{;Lw*JSm$?peS^w; zO{?xqbNM#V(R8)cl3L7&iv#)W5vrQ(_8mK@EJ2@UY&;F=Y>i*8Z1!N!PX>7m(MIhP zJB3U)Ob!(i9Zgj4t8z%fd`M6TO&!0pYIE{<l1xr2Y-_3W*^^Hb)SuuYg(7c9$t$MZ zl{Hpi+SZ*{6$Q_;6fUtEv2;8|+}kO4Du<Gx$K!%8$o7jN!fYmwxKe380lJvY*_n(_ zjUXs@ra4P!MOlu;g<iVyRa&VzuSau{k&Wc9BtEo+o}c8NPiMR+I913Yz0lg}#8lkz zof{}qQidn|5shmm{HwS~R6kl<subbbo9sOOC2pEGa_|#ip5<1dlUcXucC6g%zB&u6 zyY>7^?*A<b*J2ROYDVQod3A=pgC9LG6M#$`DIF9UI<iEG8di@67N$G|U|?`FBW&NQ z9gA%(rY)d;c&#sw7*9tOH|vUww!LB6i9b?yO3ssE`#g5?IX1J~9o4V#$u%`e)yhv2 zBvQ$LqKJ-LHJT2TZzp%wR6t(u1uu-BF0WnIO`I|O<=De)E+F=Yp|1Fq)|2Ff;Q$KV z!+vscq6c*s-G6SreIkE(=&yZtsq?!wcWU4HqFQ@|o!oc%WQFNH@#r_=)FwK#6Fd#x z69RHqvv&`d%n+vi5!BZQa*yXtsjp1CV%9!kqzcSt%&V;KWlFDt7ND+GX-SD^TwAeO z7D;7bm3m-qQBS^G?2bIWe{9F;tAV0LTy_HqUNH~PB@9Hlr$ZA3$2a191`M>kS<71q zf(T?}%$Y-%<+tMsx;5K<YCmzQ)=3>J4FdKk5$gbHuUq=K4WhXZ9SHDUEC85LcT=?> z6C(XKUE;m^It1#=e(B<e4rw0%&ZwHFPcBn5<$wly^b+K;(DQtUKCa=JGl&L3dSA0S zbnfPM#x3HvV2GS$l69a3pZHSe7Mx1tV}~GZ=k1))fdXct7kUMdtdAh+EK=2w0wJPO zz-Ciu&QETl08NrkJ9juzhrc^5Uf<kGhFrL#rkX$;Q%z54F{C2W!v;r2oxgPQ;@-W$ z4hG(5=g7SR-a%vN*zIbv8#E|j-WaK$KB0dre>r7nZV}0WK{k^-e%E<%XZC!n>250I zmf-4!ao@$-@uLSbp+cXl#^Jfa+FYNf=7E-olrA?{m^SYbHwG&`96>oIg$JkV#U1UN zl~H501oi5kO|`jAwQ0X$v}8T8w-cNJ)ks|fc%LY_kQjzgSj?eSDoEHWLD?#$0XFhG zK?yD?FGf}|v9WegxZQBcVr;oyMEX{AyFeteXW;c*fvR69{Sl{KN}F@c2i)e7svdKg zQ;<(c))B75gfd*E$zM3cA_L+NupdYF#L;CMagEQgp{J}tncA}qZqBEOwOrs22yzj| zLVjYg0yZteb6y<|2B1RC=)N)2V)pW&p^M{Lp`k;<`+S|;vA7MFDmxjA@7^dX^s7gA zmI4uZEZo$Sk}tHmCSQU%azOb<MDIC(8FDD`kQ#(1bSuHS3nV)t;&UFW`7#=20}zwb z2me$+BnH`Z$5`Spjfe7tMc_}Vc?X2rT*K~&p8K8^v?QhqE{L-BBwqJUjTy5uXmU3r zR7AprKqPvKPUJ28A27d~Au}O(GVUW6+1lzFXlQ|Vd@}L6T<&ayNU7bD0Ty)4w?tLw zo#q-G)5`xoI^#7HB!{W0bz#5ytJL|O9n#Gl8P-@$`&$ql7F&w@{`B_P52mw-W4H%& zD??^wC&Eki7?3^X?X{1W6P9m#=0d@PU{Ge9sPJIc$^ElHCvA{dX}v3>Uunbgl$-}R zO#5LmN=^a}Z=nwlTJ0gCcw7J%1T&sThGGG4Z+|hljq99oH0jdr{z<Gf!0;FlY|OhJ zlYylAY=Z6F7U?0-6>!6A$^3gYrNrO!$L|H~?R3RWMo>8*#o9#;?E@X!Q}q!Y`<BO~ zG9^6(vxqcVsey*pj5UOTlma(4S-{XhWKw2D)~KsuzHIQ3h3chyqu=_+Y+?O2?k>Cj zI`HkKNXT(u<J}2avjCn`=8x_8S|f0fh9?bz$uM_rEAAeE^~sS}m0xdEaX)#gR3=~Z zt8a0+2t+1o%ct;&o{1mg00qE$SJFJQsHRCZ_tpNT$}p88{&eUwQ;Wsh(Y*I<4WZm| zTpfcR@=?D%J}XN-b8Az^p(_60n6^h|8FYns2dybmIOOr6F={rnRN>JbSq@1iOsw25 zNl0UXC~x`1Y<UD3o^Sm7kHzmlJ#rmk>6|dQVW96USAKIlW6|}B<!8o)e^|T8s0=7; zdzpkjL`L_JwEYbyRk~@XZs<)mbQ_5rr+hsmcLuudh<B2nZwZJl+DuuFnMdj2AKTAO zJyI5tYQ4Gt?IcD2j$@6m0<yP!K$_kn$hT!@3e0BTI1$N`mky{ijS_gM9ybxn{f6`I z8wBYhUJAT&?oj>y-A~QDPgI)_b=FAm_a~Cx@(WbAY_r=1gUaiP)t!M%(k|*@Hr7{y zX>2r(Dx05dRHa=PT2Ne*7ohRd7lPewkjN}E;H4YVViyh~rSoq96<=V-@bVB(2CNsR zDMv5zoS*`Ehj+ubY@D3_=_BEPk*BT?77Gx~1Lq3$rC5i1cum-4$F|l?y|(H_CikP8 z2mGwQPUCmmr3)H4g=J;+s|NlA&B=J3wo0{}8JsGEtM2VF&=juDZjkjcJj0F9r>jyO zZ+aE-p|x%#h^p6D%;#N`p})_V^lSVRls(v{q7l|N_I(*|<o-icMf6!{v7|Qb;V(!; zp#^2cSMv?8<m~UbhPDrfo+RsZzYXs<QGx)8s=tX|JY}<E!|!9a*Kpk^i@-^An9|i$ z;`B7#GdkCp?U0$0klHhK2c)|zG2Y{gQA#AJ0I|1@i)Z5@1CB;1Ri~xo>^ASYA3+x8 zHK|O^S90@^-b;EX2OG|++;jwLkKG^~0$W*5#M1AShm$Hl4;H;TWRkQV3Tk5kEsylt zM-ZR^Sf9zIWA^FdI-G;L>KGwOc2+<7liCr@=v#7u_c(OlSXNM6SvSkKnzv+U7x(yC za<@rBsT?*K^s?#8hmy&*qYM+mAkFU-l1c+$L?B#@KL2+bcsCAq3TouXgCC%qyojXE zz#2d{oAVV$&lNoT;;a?-mu!vP?;rbf+UYJ{h8&ITphP!wy#C4P?!OB98Q7<I{hITu zA*d+OYYy#zypiER`FB)5>>1x{1<|ua%a(RR+W@B<5XXe!{Ol88)ens_o=&3)(37m$ zMYD;!xnXx`$G>hM;heSMV9c>O@dQYVpkH@5ey3^GZ0w1=TuBy4zvQ%QYv@@`3A(ha zBmaD~>nlmPBk?<%cnAGYzbh&41V2g55;f_k8ETWL9vTYL_gONzu73zR0vZgAJ;~UX z5Ts=6YNZA+b~gvO7~6?jnVZ_01DH(hj9pw()O0lo)G@z8AuV7<nN=I0=p?)X<TmlV zgk=kw(^YCpX}Nxui0g+rWoEmxVa#Rw3%nlWk%0QsgvwE9Wz*iS;s70U6eZR%C{(sN zQ#<c}t~c9WFRl&$f;mC$n$5>e;YA-tf+DK0YctYk_C$S>h2hs8t!D?3dggSs=H0m& z8P4AseLL)pCkDsfa@(B7{^5r%Y@vxg)h&Wy%NM;Wc<iez8hl(W)B>d_XqBZ7H;6Nz z<{2bH#_)I?f)++5%?*v8=w+xW+V!)=^Utv?HI-ysdpKm;xAckjFk6l^XfI@HAiYqA z8_o2}TA9`%^XWjJl(ak-UdR5}4b@sVaSiexCuIn8>&W*I*cu;+iDcE&Ctr+Y{VWYY zud`6u$dtTY%SYr6Q!)dD-C?}5ko)z14_<gI3}YWjDFcm!E&4_xWn&q!f&gK(AL#q; z%>>liWbbmcHsfr4^c=HRb-02tAkogS7^Mh&L!1T@6<K54iedT={Oei#Duh1VkP!(~ zhLw@-m?e+7Q4Rn!m7!`c4rKRWAf>kTvPXGpAN{>oRuQ)wQ|@FWk1UvW>WZ;t27~4Z z@?2uc)|3RBE_I=Hj&NM#{jZCl8cWRP2pBGnn~+dHtD@j2XvE#o%HKwIpkGc&Lr4U+ zT5Zn47w-ah$-f>12;!Q#jy<!Q-y+uUeIP6_z3~@@)3~^tR)<Xq6n|dz4z3mN_@=DO z{;dkx+1q8JXt`UGT1{tTSu2`Wgm;A5>KgKDg+&UbJvi3Uzf`_89VsqMRZ@pot(q<H za_^hg%wV{6BoJV@FCJka1~~wCTvd0Yu<Gz|E1g=%iuls-3+&^~NHh3s8hcw7jW>a` z;WzP-@C!_7p4dQnl{Wb}o?CEV+P4DGCyQ&-0-rE<p1Y{*b{dOcsp$8eGB*;mSaR8_ zZ7vd=q-K-@#94P@$9_3ij4SZBC+BnSVy<l0H{gR~@zx?Zw87v9Xz4S8K_z01ycvSm z>li&GN`Hsi3Hii2PsT202~xCaKHxQ1%DPCq4XP>vp_YAJq%=Z$4NnHSDA<vEzkSvT zLGp>!$G_;EW$6c5j+b+|kc2Wg`}iK4!aNX&s7ae@`;d2L!nQeL=C8x=mc{pTP9$s^ zCCn__6mnGzbRKd6B6J+ERw2&Dh_D-W+mWVdy@;kr-l1X_mIkX;&33^Z{HAa~m7qnD zt}AA>{{uEd3MI#yz0mUy%Oo24C;wHqw|`RKo*F{YEN+1fM^RO_CtG)N@_JEcTM2vr zkN<k!$~q$h!vFXP-akGf^e-jo2(V=S*F@W5f;0ifPEO_kF>4oBfVGL6tF@zpI>1iU z(cxcJaQ(mKAV<~yU-Saiua#L(Hx6eiij1rzztvxGn0XW*BQP@^lOuci#@Snc@}L3O z;Q;qmb%Q{W__8wm4*@o<=@{6V98hw*u+L4iH?e#F@bmqIHbPPaj2rKWu!eykv7@z` zopw)Qc#Ci6!xKXqLnuQFPE@2n14*^%*6>sagjHFhSBuegbB030ckV+KbA1?Z+6_+< zINawl7T+6ZZmO}x7|XD3OPRA{Q+vr%uoDh??@14gX$sRCYsfc97?k)E`q0YB|Ahon zj3+Pf*F<nNWLqHohKcg3QV&7sr8+CMlF+lZXHhmCwsZwKIhWkSoWHsi68~lekv2(g zYzpV&Nz}3}3j7kLz7zZ2LGs<5^I6E@t{(WZRb{FeT&l7;$hP5-ZS8@J{nz;>lGHMN z=Zc0AMO?p?GNG0p%ZgZ)ZCWpv4yB|CtGp)mx-P8I<`CKr`W+B8+GYYeM56=w?^M|F zl;gUtjG9)n3ViOmu}14h-`gMl^xzoGn38YLOa49fIl&@_yk6L0wUIhs+s2G+t%Xr? zm(MBxB)X;K0Z{b1_7H=@k<m=!YX7MNvy9@V*Xb_L636gK@>|+JLpYI;YKnA5sw{1` zzjycscA{XNH%d9tof`~f+Ahr(B```mNdc08mtGukp>U5N>F5ANE(4?U$a}ivc111g z6%u&0c?-(TrnAckjUqf!BN_s~X&lmpFkw)!;I(NFV>&1wGn6`K?Lc0|b3;5~Kt)yK zHL3*fmRxVNUwfxtxEX-?{eQ%+uYR$j6CA9G$dMZs>U)*_Gh>oAPzy9g6&$Zb)2C}C znh)7h7~k58Bql0a5gcf>qMf)|f7uw7h4kQf8l>oZ7A3^%dt1Uzre^sdZ}}$4o9hi1 zpa5QG%GUAxJJ);m+4m&7=lv~j_Y3SssCOlye~j_8zZutJIDW;t9edP`i2FvGKCzwF z-Qlb~MvvZtwT!fGUll}z=7ZhY0sXq5+~19RWu-O$FD*hQI__Sje#QELsb7~_;bk%M zJA-I$>(_xnDNT%as8h&hx6NDj{-iXF<u=in`^==^T#Z+^2E7hv9{=LwR_a(AW1E3( z?!G|=zYqjH>KEr!!v_H(P{k6}gL-@cE7WMV26e5q@c5?daR%ge#h7)$bvYd6u+gk3 zH;4Y0AWIlDRLP1seHUi2{iGN*ZG=gf>C&oQ<qT-|!>-%^8!k~Ht%2W~==k=i<1pk} z**!R)Kqti3HWNMcIsI88;_hc)wKy#!4rvtLYi$vptREKmCVr*%i8T2~Ajl6_Pb?Z$ z<n=bNV?`+&QxB9gq>`>~0Jx`uQ&WY}$~o>lNjSdDggb%on3}=FKFLjfO?B)*Za6KR zPQBoAo6vP&V@l~-0s1Zp731+2dbF3MZSlPxF%GZ+a4ki^@J~ksA#!*u^AWb8XN$)A zvE)#z;E?hNv_vNQVVUqAwtVu9H~M|6t+cRGnQ3aM=Rp=c%s_h?UyF*XmsKi9e5ixW zP-Y~b5E$s*Q3#}67PG&SE7A{WI|py%2Bm+Zdqln2X=s%Tu2l1|y~>d*r&u<uC?X^- zk5gWNZdb{k?honvsRE4n<nPcuiPzc+Dfo_?%@;nmr0PQIJXao0{@Li%P~@={TNOO| zV-<{!9-ymO4p{32@}WT5Opy(ayl`SVOfeMNEbNq;ljv;Yc!wd#aAwS5@v-?w92|L3 zm!A(vGs2C}g>j>PZ*2<i@G}t)#9Cf<H~|kYOv%lSQG2pyoIn2uSLYNSNYtg>*tTt_ zW81bmwry0<v7L@>cWfseJL%XqI!^zY@1KjAd7iqdi@G~iwa?k>UE7peJrEMrj80b@ zMy0eMBor3;A^8($puWoi9WDKLsxZ=Qrq8dSCv?#VNWcP0FZUzx?*p^)XIL)L1;Uj` z$?YFkaVs_y<PSJ|jf{Vy^75u;exXh<!Kq|Ak7DGSsN&ifu*-2oSfem<CfV*LneH+k z*7I^!3Lb22F)3zTAu5ef2R8D#_+`y-l&I(V_C7s$DTZ7$$EH3$Mpnp-teaaIs>x@r z-^ncci-A}2SBT@{4&)rGQAvXWj=&{1)e?pt;q{nTJ~G?QGonJECuK)1BWy0g8Pqsw zPEO`hjQM<&0>2NVQyMKS!5o`qG^`}0N}NgdfK6QzQ^%)HVNXB>=OF9uP8B1Wo>Vrm za6q}#!Ini=2740)QD3fD){EK%)BNPLPmL?6g@1JWy2zjSHZQ9LInwIlj<%#{O;~P6 zEE0>veBhq^dy1n$ho3S<=xgu){=Z?xX$0pcpZSUaZ;t3vD4&1rT>hFX#NbP-;Ymls zw<ND3l^~-T^wXv+dnahgpwndCkMsC+yUrYky^DSP)ZA&3sC<r;{m`N#eHE@zhjstE zC*Fe2!|vi?>CyW$=f%R~dL>=x17U|j7Anq|U3O3h+}}&9;<(TY`I`O|9GrNsD~L7b zyv>LNs02~@9<!5_$?UlgN9FcCHXo(GI4&OHVJ9;PqL$u~_`I!PA3cl-*sP?wM*x>t zp+6qarTO!%bvII0!lzxvbFe{Q1&?|M^6MiZhY(T`H}-%g*0z&IA*M9va^QAZ#c|3Q z!I7njCb5F?H|d6<gbQns4q`3qapNruodeJZczev@wjXhx5l0gZ`mvTODdc+==6-i0 z(Oi8tK3M+t8+?>i@J%~s$(AjNej4H;JC+t|6^h^=q8q=SZjmLcZwOVJo_fnYOo7E9 zO}NgiokBspiPn(09X;|l4&b4r6RG7DG;iDZ*&Bx&2lZg{bZ>)W&g~77*`MKQn@d{< z=tP~aPHSqK_uWFpxpu_`P-60wvJ{!7?Nc3bA2sBgCYr+Lowf}(#LVEnuCev%{<=td z;MS~{4LA2xJ8r8%xOVaj>O{Ep5DOdp!)YM$LZFO@6vTm9^V=CtChX$hIdqt5Cv}rO zf}2?qX^QINXhqi1!3u{CWl`u<6j`$Zpv1wYF+utpivpASg^oK+?3XKBYZ|U%Z{>C= zP2|~dI~gN~lmz{cy(wO?)()CZg^kV-NBjGILhZs=_=S`u8y9vZ#7u#moK+9QdqzH~ zC$Vl8xZQ-T#Ko<mC&>jX0rw|A&$0>=M#T!76b#gr3nz=qT=oyF@L0;dK2P{_U?A&; zzd;6BjjmfYzjT{AJ5|&NHsk^6X-;oy&)Gd%8cswlJ&N~5VSYNXbk5E!j7Vg{j`V@h z8V}0qf(+pXUvYK}{7;`umx8OdyT{<5VR5K+t@#-Y5HGepKvq$O)SclCa|ta@MBdQ_ zX<8n41yNQIw;In~y$kD88kT1Q*b!@YVR8!G!FQlZl@gOT@bjyV+UlM73-=TGqfitF z3n&<3?BIqzXA6?_E^wV7XOHG-mw;UFLCU|!!hIqp{Z{U)IvM6O!z6nk%qERYoZ?oc zpcS==RbO}jAlBqEczs%ce}SVS1V|h>85AK2d-QBgkS#($$a+MW8YQBj8izJXED63k zDYo{}Emq1CiiShX1v^~&=N>9t9rA+K>%ad~2+V_%%<+ENE7%!8KuD8~J&Az-zXhGr zh4oWkS`woEIqCbe%MM%|3RDUbK_`_?E*KRp7?uK*4VFEBUg|C>M&4pxN84^`Ez0AU zNlCPBYfBo_ya_!z3jLadUgsbG)@Z%-y5*~?sVAYQ?mtPa^Ve?yy#c~k8;2XdE&q6q zw_ugt&-=q8t#JQ!XVA4T0)yt?uSDuZGi9y2@Mi83vMha1L~_*zH2id736I<7pV@P@ zM4|Go3Ls|Ew}R1lN8mXG#Vjl)eg;@iryMcF&iqxzWKQZgu}*^tadoRr)0cCvooibb zgKwW6eB2<3jwM<fIrcfB_sboba0^Hc$GUxKbP1ZCICKr2tWL2=1=tnl*=@UY_u&N= z$9}mxB+f(^>lEGVzSu3bD+nI@mItj^Z`9uV<S?hHgoo!IDOz)mo#Jy`&9|+K*|v|0 z*?J|1=VCg{z_nU>|6)&<UN|o}=HA&Ayh^u-p4Jm+idw-;e6Jv(+ts=c4A0%Q=XHF# zDe?Px(e~YfX~ipi8mOE5lfp=dca2d4U4ol5l4PNsiE1rb0&C+JI^8Kgk}z#bv^Hv- zL(Ut2{;vBoHVWE=2=BOU8W#B@#PhsMYQOtiv(hoWdq9rl_R{ksQBPFe@(Ui%J|doB z)h)hzK=Sm;X_w*gxqFX@-J`|sD^|{S<^8%_K<6~q**j8>BQXCGXxF8)?`h))pFD?) zq&L@+fE%sDTFSPpGiX7EVTk<)9Z6!cU}|nmdNFFaBe<arQk06h^zW5kRz$q+u9wqn zj1-H`*7mOk%Z24!LcKL#@SH3k;br&G_H{dipSy`8!u)s$@$77=EfNC_ZMboctYWZo z$0733%guF$>%jI>Yf)LZ<9X>;EZ-|{=Q`Kp)8)z4#n~A`Q>*=%4z|(Wv}mC#U52dg z+e)3|^(N}c#&TSRjS1cQM(63?)?Z;GDLv=|8jL5_cCo}nRMh8(T4}dpX9^pn(CQHZ z6<5EFC{B+GiHKt34bwOsd7_6F?Ky20G>b}{yiza!9>CRLT9Lb*tOfalwK|Z62;)3$ zT~cN!q+rCsl|XzrXh(WAhPn`CCwOCNX@Qa3zZB9kCA8)_SK3X>&}lM?Vo%xMorNH# zm8Ds8CjYI1pw|sPxr*_dS>#23{ZcZ{y0ygHWDcp^cLH2?F}+DE<&o)PdO^G&rSKNt zu*@9ZwgK5?+QJ-89N6nHXYd6Ee?|8+RyO!4xias37?!xJLCek$9_n<H#7w9hOaAlc z%J;It2N2K!DH@lUUyl6d`A2@omTx`_)AZH-UiS~Ux9-*?4oFu6g6^3k*YnStc)j5j zZ;Kd$=F0j4MIoO#(%hJFYF%VKA3K0@pjuN>1mHad%q!BtTz&j=q@g_agE)6`f~at? zfmFe5t=EkhH`VkiAN&^XQ?o)<jRRIO56YSJ-4t*v%Sm#=XqlX;b{pE?|LE2)XEy!; zhXnECkF1m{%lvA4h5vQ_7w3#gRVZRMy-}osXmV7)M2x7Qb2g9!suo-1r;~)l5y1RS z6nGUjgYX|~ptCu_h+H76Mh;`3?6p8bj677C4$C)Vxme9-NXOuyaJRAKc90k>g-MIh znQJCbQxi{g@t_I|FA>JHa~z)A9n!9&G)WmEcV=5O50%<0(xKK7tDxNi@hv<1u17)2 zka2WGlWRK5#wHguw!=h!rJhYNSP7?|3TT?CkqPETfWxNogdGMOagWmOP8N=|+W2LS zwA#K4#$HSFfd}YvV}h%Fw2)Ym)iV=tJz2<UK!&ttHtg)EFEUP@SDlVWukeSQvc5Aq zoFE`e1;*r9OzH*%_<AX0j#DmqpIP;+D-TCtwsJ{y);wE52xg^AU^j<}?JQf>0kB|< z64`}I7sl*QwTFvvJK{%7s~GlNMFsiFQ@;zxTVlz+CrX@XvWOfvRTG|tt(IWzRPdpu z(>R7iCg8TdE~IhO_vX1QX{Nzf(WeW>i8;Fmac*VrWAyyA&^i|ACwJ9LpgB0xF+tAu z)ibxC6*!I?y1+MZrbrWL=j7$416F$aMRtHW@8jHw#RRVO(rd;AM`TFfh6(ftio%ez zAqG0Ps16Y;#Ez`1xo4)3>my$TUU_1!69XUV%y2^M%YEL-RhqbzP$|R!AwYN^8PAUy z7~2thHx3aRAU4lTJw7wK8GSB-+8(vLF+TOr77CKPMzFzg!{2n6eLD#RL_B9fzsp#V z7(BosgTYKnA#@~PeLD$po6VOxQ8BW?cxf~Ke%IBuu=DnkEY0~1e?oq6i_JOra~8!S zZ~hrHB;I8{-*A>0F?Yuliuf2Bm7m!VQTZG1+Da|kGsNKE-qL@;3*x~(knG^?D9dl` zLUuPY0Yf`d!_axVGo{n&z|FZtuy~JLIMSXe9j6|8e?n9?-0o5Q8pkAY{4?Sw0br0h znqd4fNR|I>@jxjRqZgvFS9h5>oIy@vCS3Jux<+3bT<-kcneX<sD$!}wB<d3$3P{Xj zd6pA~_m9pM_m;PJ)PNko8=oFVA@5u^WBzSzn07=D1!zp(as<o+J9YP#h@Vh^+P)+1 zyZvWe|Mnp=+l%Ve-8HM}Xa3?B-OcZS$YJ#^5Yjs%#PS^ePEIxdJPIWQ`F5`aL1x({ z$|&Mi92aK(F*rAtL2ONC-^qZE^G9ZMCohT(pqiSII4Uc7ieaBA4APw;<+q^0u7C3x z7JKJ(klZEWN&5t3n0Hh2N$hU!-{DI?^j7}<b^H7M;yOMug3#YT>!Wh~*>xgDC|D|L z8)1;2)fNSt1Sm~{NmjT1YnuGe1QYR-bV!T6jg6EY@pFoFLY)P6G#Dg9DA5APN_ZUA zBhl(cwyeC}o)qDgvD@l~`9%xrUnb}a++2sqyp&iKoemW6FU(Mw#taTM*iCkt>$&y) zYt3DBN6+((Ux+SZH-z-<ChNzsf9CY}9t-saeE6TAYvH|1eX1ep+niVPb430Af(P;c z#)aZ}Kn;o^o=>*njwV-51$;)Lr0#Q*I7BZ;!Kd;cGEG1!6LpTf`v%|6;l5+Vy!;pK zl7>SZ9|%k(5WBu5oPXfAxD8$z@VRnu|C~^?)@V=8%7LS9_^=H5H*k?s9k!_m%9fp| zmN9<0t8F0}J%o4Rb1K4Yxu#Be@i5#lGRz7*?rHyeZDp^ry)k@o9E0>mvp?>5NOkX* z_kQLtrciI6&)x&SWZPG4>-!|L^5~JWa`@p<5(X%q$=GMQ==`9(-yUwfH{p)vhv)S# znj0f}YZsut#-zgbJzEE-iC`JS@^NtY=$`RDIzN*O2`t&7{-d5@GtHV>I2U3Ku5p-9 zhCr<pufn!$dKp`lKFf(wU5|HQ5H#P)$7Qx@1-kFUPw2sWl+7iw+qN;FrKz;d>rZw4 zf)D(gAxd>|Wg<YOXZTI7XmEr*<p-Z-?L6llWemlu)>Hm4RNdxUB(h<qujP8Oh%0d$ z0cI=VKJ6;D6>J_T%8j3RUpjlhpGDWV;5}V>b>({3joP=6Me2Uj!hEm4Ob=*iMP4GE zKcolM+C084XD5X~u#xe9bJT%sQxukEzy-pb9?KpAov(PO)K2c7-RVC3q}c~urY6ry zP4%p+O_pN@Ts`Py_~`G$JmN3$;3Q3dI-)fi?)6N5pO({D9#3JLENG&g=wqNh%uX9- ze51APz1)HQA<X}{;UTf~tE=3{w<@EPOp3BuJ*qRPJPGf3q(^}M4<*j|<i@WIn}0y8 z6U2jRYb=P371=Up)UU%79kne7e9m%`Rdk6fFh3IaGO(m;^LwyBj3^RhOvErUR&R%X zsGxooIlA<)04edFnZzv%b+t0fC~FE={iOP0i}d_XL7TXk3h13@S`nd{*1><j><jbo zcP+zA)J)hR&+S|4D^nt-8VQT3vh0D3gGIb;w{ORkh~$y<+51?%qB|ux5yomi%@lG4 z*2^w`3!1Mz!MPebAdbL<c}jWo166l8rf-wRd5Xs-^~?UwkKNvah=;aSFH81phY%tX zN48K;P87TrBW2R5QZMNWl2dL}8mhq=Te3uVa|&)CA6b#*fm@NcjBYFf0ocG)k*8(s zcF3*ouaVXyEuzH}-$R8O!D}bMTL#TM&jo>epttZJ$cPthkzriB@2Qv-vT;++#zE4b zOx@ZlAxX&5a;M<$PgM%UlIQ)@b1&+W*t1fzRSmySiyl}X^(I^>p{jCWRP%kvWD88( z@&>JbuqSQ1YFe;n?q}u~R#XF;s*p9R)zht9#X1_L|G9Xpi>Y7RoyLt{xtIG=;2X&H z90WzKf?(%6Y?6wJ%S=kII`$8YGn{#NDdpo6=G63nY}ubl>S2mJ3;SLIL}Y_RMt`|A zFy@!VlH*s1`hPM}x~3*MbDX-7GL~YkNn@)Z*QngR_4~6j7AVaEAF=~e4>GS9g6?Pe z&?$~_mFRE;l@#R{&B#XfV7B%LK9OB`gbOFn6pWPVlE`c1u?Td^oBqkGDV@qz%sboL z^%meeKki1vT8vM0y$0<&yKA?O?=UrT)pnl?SthX0cm8^m$VDsUu3QGU2D5{}Er8Sq zk<l=yX5t}zY|hpJW2ol~ven^)4Y_??RR5OpErPo^nx(8Y6+Tol_4i!~OL#bX3X6s~ zsLO5gTO5KF`D9&fkJ{pskcaNSB{3J6G+~GMLi^;*8i7xR86K%%=ZI~pBmc{m%1Y5B z<ZNxyoS4^Tl(TJSs99?Kx*@13rY?82JZ)u=>P>$3{y_w|2lov)6zJ&QgETxn?6dcR z&{U}xLE1dvq~_&^f^+bTSQ3x}k~QBbMVY6{1S?A!W&N^tRi)lxA;*JXC^J3~{Fh;a zCRnJ0iE0HTEvZhcuY%J$C~`hv7;Ri$zHbf7=&Guni;?7psBp-)v&+<TiQiGe!pl@9 zm-d*8Q2GPAOqF~hh~U)asB><$PhAv@gT{4tx$)D%6#6^*af(Tr^0VW~;a2X+wiahp zZg!H`URjqpWWUdnpTr=MW7QCT_G$J)wvpn2WVae0s%A>0_ZOv;7hUD^`XDAH%P2IV zCvw4TBO71Scp#In$B{rnF5sm3=M~Ki(!lD1+6n=+@7~<8frAf_uViiK!iVS&lsI$X zjsduWOzKc!_y&c}u{{Gdcs05*h8mKV9cV&^Mbv}@Pih5U!Lp|Tie|eS)FFcu8TS@0 zF(bCH0PY+n7qN?TS&UQvke<jKTnorM^Q@_AC+5YZe}460zHJp_&aTOC5H&%kD@cLP z=MX^i<MW1-Z}Nn53wMVMdiq-vMU5O!m%x$^vtn;sMR{W;(R`L0t~<jMMjZ{Ufyx>E z-KoqOfwsYSCv1I>7)}c7{Tg+jU`<4?PO9=ZGmYQbdml$pNtLRPI=ULXSw16C$Xg;% zY5Z@<Tax#S+0BI*7e~t`wUr0l_8w~8O3XmET6VD<lEPM8iO$v*1*D%ueBu?otg7LM zX;ZhL%tv^6oeX*yt&=^ZpZK7bw_y&n`WggcwjJFJkWU5uI<6#}#1x`oDD1YIJQLV# zxWl$ohB&aE@I9ii-j{$i6=aD>7sipHbOro}7U@G>sYM5r^*y3A`wE%rb_AF&p%Ktd zTmT4=c!OV-a)s<MKEHXS+~KPPIe?uYkpz9o0*N60>7BmqJYb;BEl6|EkfU}7R`o?c z5J|hxuguzSNVnd<xIxzy^LcY@PGOVc!Uk`_7fbKGa!xPw@rY~}T!WdaK#V^*d9YHz z?mvWpG)r`zKtG7l1*sQ3kUz*4i~`uCgYiKP*cGV#w<*O^2FaonK1gJ9zfg7h7$4AI zX)#5iJpN)ivwg$_Ai<~Je!~=Zz~og@{Y{~rYNK8;#~GWue1aRByL7_yBbiRi*#vvQ z8LfZ?9h1KDR}~d$S6Vt!Yo5$W#xLn<G}|Uf(D!$WN>K?xuw!KDS$Wal>H9z;W_-uH zZlBDZR{yjr_nFdc3+Q+57Pd^eTUXowrqLOs_b!XzNpk%pqVAC9q&xNex5OEbBZ~KU z<0C;{c$Ak2S1oRMT_MR{)rUtqy+0M(3U)TD`Xs&9PIt4$+#+{+a)@3CkBj8%o7c@Y zq-sfFj^tvHLoi^4(|nmx#reRH(g%dT@hDP~SSbvn`r}>l&LeHc6R31j@j~CG(x~%4 zZ<h1m)g(nz##8oX(OyttxO!ho692y6<t)oW6%e^OIW5O@C?8lj>VFRsT(#O!E!I<u zl|6Gj8Dxwdi*FX#bBkC0E_xSzi)&{gqInnr&#-e@Vp~R+S=&^nz$O3~sG#6%yM2?I zupv5$3;d<(@7){kAKUr-nWm-ewb-sTJR*Z5cpco*`(C<X-fVBI*UO>ONS`I3ACAG^ z5zC3HK+@&%4VZ(CF#fhd?Yc68Nng0rGO%D{^cHRJ|A6An`V7p4XLCJ`f1vhq!r*K< zu9f)xkL?4p+khgow~!jZX=k-R$4}KS@wqJPo!cfY(r!zGqw4Qb?#BUrK#mNerw$_0 z@{vu3E3Fv6Q}X#Oa_5f|$+o@-d1&J<5=7atbd$MQUv|aH3-0~k1}#$VJwd0uwyo?) zegbpjahb>-c7cEYlvo)6!vBFn`zR$mqNcNIEqA_fq`vRc1^Ee-IK0Cr7E>*(_G=td z+BDR4j7bb#7?sYqOK%m5`<u!xGI98+)sn50V5$s$R2V1A#nvgsM=w5yJ*-~(yN1>V z28aF$0xifx#{t;@19vb~F%rIS2M71DH_9K-50|V$CMaqpkTC?iuzKZb^2PhG+=;hD zm6$1T<B!)HXKu7}C~xJ)qAfdb6ZZBZrM&4tjb2<}=}eJeDrWp%>`=bhl)HI?xto3H zbPnpGbyDrxu)iv4f|)2=6eH}w(N1yhpff}?+!iROZkum|s+thJMfF)RyE`r0Hw_*{ zcS^`Tns4ehr2oGCA54bm0`C`Oaw<IHZjq<87$gWtH9QCi+1EKFRx(K?G(f@n-$0D! zsTR5x#y>;|sF5-fhv8JX8d5OObj`DQGU-@IaWay%HN_-LoS&1$Z0K_>9@|gDOxb^3 zdmp0pVX}P`--eYwl6}^4xA>BUgA>V}T(&!(Hb1sJx8EPPuK^J5z;>UDVQVN>C`%mv zK_h7~`Y??cD`jWRVW2$B2Z7Bc&ePOA;@&5hb{~NXNB&z|zA=g#o{w{4e~Ax&^s$$x z&fvXQkdB*$jFmK5iO3#Yk+ookrm@v(NEO}-OVNOOqKYxCRaa@2eQD0(RJ5eWEK5l+ zqL|jBt<Pe8Q4Fz0eVn&y*f}CjT(E+~Br6)vOnr^>=y$ZnVgQ?vI684w(n4-+8IwH6 z)$K@aR?v*^)M{-`tu$tu*N2ln=ftf3Z+c^rUk#UioBbx7hbCH-PHkBh)jc5_!ysQl zA8g%-Hjx@-^~o#v+p1sz$)aN!jX9!N7^RfU&mV5n=dg+<3n<5KqMisN@Ixr0#lk}j z(J4PW&SgbC>wrB^J>*avGKG}A5vRBDaTs!=^Sm^f%F4@ggVGJ^eTmJI>SFnclj#_@ zU@C3mr-y^d`dr4g+MJHu6rH$rjYN(5_LG<jz0;FFLmEFHq^0~fr1d(|U8Re!4Z;FN z^-U!!IFfiq6VtFSsi{}T8}4ot>y@1Ojw#G{)L2`XEP$foyvC%`OFT^<ZJz}X+2Lp~ zr0+pUZgBj;QjmXHVT$xgf<M6YZ=@9}FoOt)wCe5uEOB$Rqze9In@n+os5REy;dP)X zziPCEFfi$YXNK<z&H}6Z<`rvQw~rXab(_|=?j<$c33k2fAB22U5ETz0I6@7U_wDJ6 zaT>-3GX(bNF<T*eEKVJ7YWh6eDvaJ5=}mbi*uJ^YKH6n8@sAK)f3LAF*U>yO=&#jC zt~1&OZ-I*qG(VK<N!))*FB7FAtnhUSJPHfmiqdMnqtyFzw`APodA=}@zr+)m&!Ijb z^$_#@PlgDqw|u<=eMvr>-}P-bTIgM}QH~`v1td_jE9+V$kJ{cc?PRT}I%0nuc6}w$ z=+G9Fhj?IVIM6(mO)cf_n2A<IojhM6v!d{esrv|5lV}8w$+zDQIqOn;%C4P4N-ag8 zuQAnDN5{jQQ7)4`VDQl_?2eMq$FzmPIZoA#Xqfs@K5_;6&)qGD9+P9F0;ZiZwK*<Z zMIO+qeuhEkWG|aJpFFWeVB)y^`OYA~eQ0Ux9pIsEL{x9dxUgKYq@mSIHcA+ho8i>{ z`)_JpGtKa+kGF?mmk<`cM*!>mAc6HU9@*oyjnkvl0Ymbvs3A#>@@!b<h-B_ONX;7@ z{ge$*;(S@hK7JZfnHiuTk)+ObIb8(T+yu0U|D6$wbek-qo9Br?lXkTtDv)-a;<q7# zFaP-IRXHnGpGFY``pN>{f);D$j1mqCjB;YUmbJ@Tnu)Z>;B6mm@+p^*^%l^@ckq?x z=>}pMo!_A-oWmY87zgD7g__56+~MSJ_p99DL}WDb2uPV&4c2X@s9DF#QWI(Gy?_yY z&H9~J#B&xk{D#H@JZ7;CRztVAj1?;1-D-PvStzp33YkY@+-7o+=~QyhC*LaDFcjA8 zU0I&c^Z+b73PCY}kb+@s8S4+6J?;>A97!;8>3xE3OXrP31R&n|Jp%Uqhs?VyL3G=J zC37&!@5??^g2#KW)`U@(fIimB0z1+9&CrE?@$Hy1z*!`;vhc~#?isqMiBw702ZR65 zs+0~uH`ZZjSgWQ$5g+cT#gGew11Fijh;niNqExtT2<<d3gkP+TJXafO<%|0Mi1r9; z^?w^c4Q6EvK3{AScCv68C$RY?7Q-3I`{hngPq_0w!kj{DN<v6%5@8ZUA(>o&%_etq zRZSpS(Ddta@!uFe6MLc+`|UjZKbn1CMR<Hus5J9y9!KBY?YAz0vXA?xQDzW$ZO#M< zov@z-p*px%6K;x95^?uXF50sVV@B-YJ*QFFpSeaj6<0S;8!mq5?ZAGNl$&uhp93Gt zIT?a?vL?nJCeu@<CL<g{olMn(8D_}KJUsbThwb{nmPQ42Fh_cL0SvCD9QD$IhLDwR z1MIn2pB^KsHf+hNLJ{v;X0MEdWK|rsLGu$Hnb#nGt;^y1M$LU+qPL66HXnOUxcz$R z591QIu0fGXOJcrUbU?s3<09z6x>C`i$UdA7Nwwc;paUkGQOfRHvD|_q?p<@ldgpmp zD!aMUl8keYs&NKm4la{UMSLofkqDS36vzWntk`TJL=6%GZ$L1T@kA<9G_r>3{dy3i z+1&JPW-<(Vy3?N_nCYJ^iCo4;t3&P^vtK4LcS#|t+MP-SWB_M`ciMo<;|C#Y%s=9y zmhw1QpFEd`wG!n7BrDAX535NP#Vg_C<VH#>cJP?IF^Bp)u9LjfGydV~uEESUVOKVT z(C8@RF{(Gz@oHq_Cmd3b<RnC@ABf}g>~X3u1$UF6O?MZd^M-LOkaVC&BBp{a)uE_= z6-lX0a7UaGl>s(bOB<>zg}L}#Kh-1L*gJ!scg!`%lQn9qQ~!>Ht2;b*?<wk?tcMvA zj`x&U#9b_nSZ~dNiG<6^0Hv8&CPk=L%J3mi7H?5`3m;-_z!}x^En>kEeVcFXHtv~E zwJh>pmfyYU&!&k-Cim8HQq%&DTBi~*L-4HR$A2{@asG!$pYhpX^&J-kM3EB&gyg>~ z0wh2e+DmT<Q}EcDUnc;!WuIbPT~wZ6<^qL>g1beGz>a!VN0EkPeWpb?Erx=c^_Mxu z09~jmo|O)vHGF6>qeuDA5=hyvH>kvSuA6K9W5!=;LQ{0U&hOUuY18@Y=RcmOsN=1d zc~kKwb-oF?;qTP(KHltJ6**idejOpthtxpV!CtKQi9^aLm3KC2JQcIR%#?w{?v#N~ zO6%CZe2!i>`_FZ=InA8b-`SVE*Eg!D*BEP;cdLgbj<*ih6*=2QUoJ&Ksbg1nBp+)g z#~Y{cF22LLLu0kAO@quqfyS1VK~nELYPXqmDQzNuUMaUYpNgorZuQ^TW8_rvZPNfO zGP0L9^7Z45AT#n^C{fRt+{ClVpI5XT=Eoaz(SvTHfV^~@>d%S$Q*1v8!H=Z6lu-?$ zfL=Gl%UxTus{mxfteKVD$2;c`eusw}=&lFi9+_igp9+a*uUN;lT)kq<^yTm_KYCzQ z4z)*N=r!WPxo@!b+%McMU{Q|vFdmRO&LQfN+w$~%A?jHfBu-CMf&tG<iOjpZ5n%>0 zg~;&%&EvPO#X<ao{Q#Xuo}bTw;lztC3De;M@7~COh-B|ciC};<k(fd5^dK*B;?tl3 z-VXCA|6Yl|NubohM>&6od%)yG{5|@e*SuW%%`nktV|d=(oz`=e_dB}~Iq=SZzFYVE zjpxt7IWI6M_a*`Q_LBATeQG;t^q#kOrv%@aIUvz6Y5C*Z+d6M=^}T1;1F4P^JfNlN zuLR*WJP|En|8^^U{>J++@V8aW5PH8;TkXmPzoEb6q_#dcPJEU#m7)J&*qXh9-b{s~ z-TK;ETfqAA$Q0wTUBzZcB2ZC(+O?Bw<j#$ATHzo!B$}9uR|@N{Qh+UEha3hikNht| z#&KQScX}R^e$K`oBr%MW%E67~JAwR_Q&ZV!4C|C#)QC@|uStJxy-qh5*2OiK>5_sC zikX1^Tz8@q`|2<|^~%tQkMDQw6J#nM+kU`MN}Fb8rrAjDh=3u}7`Qq|KpqnFokT3C z*IEB#J+eR5FRgs5nG-EKsDd4s?W;h8CR=UJ+xdXwLa3MIY3V!XL1n4mG+~yxh!K^+ z2<}g4VP}gHVv=iR$<if&1OK~q^HlyJ%xGDM$Fqe1bi<d=)Plu$IeZVcbcu&@)wtXE z@=AF|m>~m3f}A6o0f>i@sFI*IiJG2tE|nNnfJ1XPe$y^D74B&r9*{?8yhCME>8@!; zRf8KKGw~nc&jEg1l{Re3Pi$*s!H?7oAa$^)5J#18Dot3FPkDyE9^&(c2xWXUezgNm zIMH*u{}9+QPn9MxPG&UhFLU!7i4L)M?mbq3?CL+W4Ua`p0WHW{hB#++P*t=<{eW|6 zz`9RJ#B58DV4H&{3PWHV5gRKL4NCzd>IHu<&uU$l^fB@IJNl1FZc?`LZ0C1_WX9LR z@8x?6507Q+mtn3RGTMqtHQ$A1H|<9zYc)B@2F#UzW2~KwyQJj>KL|sNtF%~VW-Un4 zF1M7y6jeF90#v)!`WCD58Bx>ARzPz5=nv-MA#B-EHD|X-Io!zV$Yi#QB?wLtcN-4` z<iFJWt3no6nHK`f%O~$_IRMgaM^S^KYl!xl)gGn3GEP=;wvR2#vMCjVy^>;v|A)+R zV+R~-i7N@hk1>|#_Xd-rMsjkb4Z?A4`L(OZS@F7d0QrMdSgn<;_ObubLN9vX;#im= z1*h5pstru%Tuh*JKDtOxnPCts@iPd*gEiz!aKQ@&YS1s9K#P1(vza(C;@lKKf`J9h zy<qeHQ!Nt`-S?f;F85_wsM0H%`{8mz&@q9*Ox^Y;zk`Hrg3j~zYW`M?=ZNSBmOOW7 z3Y!-rz>8nxZ;?s64i_c;J*B76jUWGdoN6)~J07Wz5u@@b>+N-F`p2J=$qeiZc?5{T z%9<w~n{dfNEQDZIz=6-wr$&w;vLe~TQfcW=2;!QkK%$We2~=bBcIYy9o#f=`l=z_F zvxF#^aWw8Z{O~^3iOnARl~*A>Z-O*WcwsXsfOig0g$9S4ZA>96e{~HKY;I*K$3RER ze~NbVJD{}i7Trd*zsv`poCF{LB>eN+u2HagPluJEAq!YICkrj!uTAY^t%7i6KPj5v zMkskcS!~c>n{!@r%9A;k#*wmo`?KKakI#X0Tt?~B2KD%CyI&pJ(JWm{q3Qw<BuBO+ zAOf*enT?)tETXaz#XvI#U)hAN6e1$&yWR9jIs~n{Mp8zaNGs{LS<BLI?P8bL7<<}I z#%QBuv6e%MHFB=Wx@rOJ%aGS!QP;X%*POY}egtl#O1ddn^mfRkomo-kGBKV+a)!!B zsMHQ@27|7Q3TjIexn;$FN-?v48Pi750B;5b*M{$H5jiZ_|H4JZ)?4L?CKkG?Q4Hvm z0<lT=?bUWn-+l_z$(G{U&Bi0V!uc=dbDb!J5FKm>e!^mIH=eXY`Nt8loRIjh<<HDo zB6h@IaXov!`{w*3xfa5Er~dra59|sRDe{KNq`|cJ;=OZy@_9{R4DSFpLZGBlfT7(@ zuUD8z-FzLwE9t==a4&?22TAA?+7n+Q#XN)H6J#Jit^B#biz4&mb#f8m$BarNZlmQl zl??sQN&bE8?|<>%i31df>d31<3fhFw9<O1;#PkE43QE;+-^1!(zmBPH^vFsKFtvVe z?X<oShDa6g#V%sRicosuhKZ7@0mhotX%Ugci|k^qfx$-@=-7wl%L(=8h2XnPH|aN3 zsteorF;A~5v(N5R=2i|0(6U;mnWM9J&kB9mEfG@WpuGj&BALh0Y0)l=w&gbS#hCmi zGCeS757dRT3DKKSIN3&4=*FV<Nu0Pp&LC3@EKk_Oe&EJ_(FzQ?h*mXc0P+Z0m}VcF z(Pr^q^U7Mx0q|xru`IG5QRXe#;q($e*VOHG=i^$EzM+iImf8x6WfgpB&UM!8^s`K@ zTsc^Hm9miUt*LR>!2VosETle}>-iYV_!ach+l-_}aEDR`7eol*;L5WiPuAXXtyUNo zUrBP_WxXZ*8w~lRv?i^}2WpzUIX$z(9mMG86githbt?)O=a1OBkIUs(eMcK47Pp^z zosF!_b=zw-aCA$cJUF{MhtMMWGf)(MU(GbYrBpQ!Lw%$LSX#+7l}vW8mn_1$(=Fah zb-LEv=}{MZQzj-!^VKsPXZP%oyt~JrUb9R^2%%n_r;puSBU1W&<^IXwzMJ}giaW9d zg`XN`+_SgLZWBkF7@N!@qe7W8!`sr?S64KS7Rb?epw4_*SZI)zuaYP~jS#BMpNs7* znV&pI3;n;M_nau$<l5%OsW3+~3_%~|7~r2tUNRJ?8v#5&#}bxuzL}QkXF;Wi2#*+6 z!HqkEceG5-rrFXX0QMy<bhxE(2)3pTJNYR<m`4=El#P8wzo4_bqZExv2;*5s(UODC zXC}j^eonW)3WiCX27SGG-#{&%m|jtijg{?is6S671pcok_ciNpy`0fA>Kd@CD1_3= zJK}7|tHTAgOSrn-%SKlC5HI8N!av5bw(=P4KR!;^G+CGf0aeBLkt#A8RQF3avGsC} zvo$I)J~7*GO%^qfgT6Jv^Rt|`UwK9O8ccJiX}pOY)taf<mMEWA*fxbJeeKgV53%(E z4!kwRxqsCS!o56I(%HF@W<SLWJ3Z$1m_pKTRK?b7rp$D`vP)3D9P;%Lu(DGn)B)+q zlUp9RDZWqNfm9_;m$fzg=&NSM9rR!Ebid8Yc=6Cx8zc`k?{SV-CpS`b>ak*6FBsS? zFoxvSTr?Wago8vUG}czjksi4xEzeC{P7516tv@{%Xcu^>UC^I}^jk6GcGU@kR3}TX zx4JUYJCJ#Ab)RWcZzC56uJZjMs;0b|^BF=H!8`ZefVA>jaQZ#2uYIp3Q>xe{P6J!5 zMrq1Xm$Y;EUfK;h+T2=8$H)l9bbF{FkF(}c8y=1lo&UyErlYGz^-Hced%LA`ZB0Ho zp-JS_l7<OC-j-=;U8GBW>fd2ln*y3?G*uWKsr}@w4=gBr>=MgAT*33_iN1qW?ysKi zVMWeefaMX3_bZu3Jap%jeFF+hx7(z~AMH4QsqTYxF|(#omp?}b{!!v)_R#9z5?B}! zy+vs!Gjub(k&}kdcx;P4?OC5dxwBCp$35aw_}XjULW;8O=I^E$1(xTOz1!PoM8BUX zqRlMRs0n&$^<$n6Vadfg%oJ^H@eHGYDRr(e17K*K(661r1GRBT@C_UKoT9vP*uNQ# zp(tb(HEgDo#JNhDpQ7aOU7_I=Zv+aBgvtJ9YUp}0lr4+hhV^_7<!=O6UDO7A;z)Ts z-TshOc+Kcijr(18w77U{2+h4;0AD}>y(px(k#g#4-uyUc1-6QI;V4PCk=7?8vB)J; z30#)$2t$PG*r~CkdEBwKI^&kxM*or>(h+d@dmFvS$t)~`LmWaNqi{7Y>*+q{g!3f` zH_WA7Zn(F%j;k8wZ!h04(ig(&^BjpvAdXt0j>nueuZIud#>K0Fmn=k8|1hS<D@!|S zQ&6MjV0uL#VOz@U!CQQS5{pDc8%Ed20L%^!tZ}Uw-g<m|uJGypI8MQOZ?{cM$B5#T zL`QYNddSKKSeiyG(MKUr+OAx<_@EsB5QK+lS(QRBO^&BuDSmE1pgJ{VGHGot36Pz} zDOUmA!q|*Hz{e<8yh`MuJ|@aNh(y21{jH_%$g&JI$`+(I{yW?FP->W#>v!q?1aMb( zfj=c9ymw?ZX$O<#KTFv%jxF9I^sBCV(WPWcnx)z%zlJp%-t;yf6B#Wg%nT2=u`#>z z+V5No#BhymL|;wQl8)k>^T8_MXbqm5)0zlVjSuzops4f2!K4G)5N|X3I^NPReeVtZ z;rHTGS-G;q;w)2&9LHl%{FT=A3@``og>8Hl;s!G_`0T^nJ{I{D+-U5Q7MoDjJ*zJ~ zgOvP<;ypf2T-enZ6+Wh?`GRsttjXs`G=qCE;)<zRn@{To=cTRSN+V3Ig6I!QZjPn! zN+uv)tUG{SmF84Zk#=(^e2s!wBvrmG<{fnxbw~OwbD~H3#Y*0)f}k)G6rj#c24iyb z@vBA6+%PzDeaXf=98_l!4^>j0E5PtoBgL50<yR_-RENh}2|4r`UemP=!LX%2fe}EU z`ree$BTk;rl87}={=luB1;)lNq9mGu%bwg5lNOyjfJc4AJUCf5tHn6-!gew-I5s7C zjG_SBMnJ!LmCBou=M%_33|xU1^C2fKG|a1A@PMSIX(22$HI5r+8c@N<klv-feIH~m zswT)`Yj`DDg61FYcq5{_Y0o7Ezb84p2%Ko2_b(CDtp;Kg@d|h@$=O@|Gu}h*!>j~b z_$r)0@x;`>$x{m%T=e)qG!E{#gV`6eSz%G2T{ej_6_R|f3h7#m1m+y>)R0q^nNt^D zX2pnWifA~Lg_iXrQn8iUhOMZF8*BnQtdK3POIR+8&QHl+N%X7P*AZ<3xDOT{5WMS6 z2nFN}#o%>itlun3Bs3Z2*5}a(`PAj?b|EZR=3JdzuxSj$450<M+Pb28K&4hrvohK< z`LAaoa>yoM;ufA6fp1<ZM@dp;COJ`VuW3GlbyO(l?B|TQ8IMr>Yzu+Rv(cIaH{`4G z2;5}3E2`F2m`trGuM7;$DZJv*VAH5{Hg!0K-+bXR4{_DPD!>Hka$(`)l_?R_mzYXx zh)Rb+TW18s<-j_+(q13LK;xJ0_=%hb8t#*pzayEXN?C>C0AHcNYmJ|5iXwGw(_dP1 z;$UsdWY}}m&%(3I!h53%bE|6&#|dz-8)y0l=J0UCps}{7%oCgHt@y_k<Een^wm7u< zYlDs;s8h*~5J8ajjbesPxT@)*%kf~vY6D5;idAlW;TwA&++{rRApyB<6&9AH#?J^s z+j%sh*?NlzpeLF+yzz`~Ub3N~w))v`J|T^femG_uKQuA>=ioejnqO#-qv_Z^3Dx?Q zx(XJzv}KDKokJ`pO<B~`@}7!uYkaK*cYnvC(cyl0Du@G%s%e!gQg+hG;7KQVcWb}3 z5STo_@cN0znh3E1X(6`AEyIU!`|EUysz<4)Nf{gjpin^;-z1r1vo(}dJh*Ih68=Dr z-_{HIE!ZV%JP^K&LyKR&S)MT!8J+GHya_Fq7pl9^ZnUy>^JuZDX|Dp24=GO2Rr@kI z?5Xpn3*Vy$I?<2WUU<z~yix{^4cY6~G&FXHlyZ`a5-f?lYmfRt&FP)(0k0xAZ+%aJ z;?h16*fu<QnBYn=`znC#z<cb<ijg9l?%aw~*ArVvF|Ctu6t@#;w<R0OF}Z0?edzOJ zB*PiDAlR9{<`a13HhhV><+%-a7e%^+5Fh?Acy-5fk_*^N&2PlI;ffE)Q@6yWbP+z- zu6q%3=1xgXzr+r(B`%2dm2NsMA*|kxDYWDOTvzH=Qf^|#3#c{@cm-)56nj%sam|p) zr>IJOJbIncewZeWjJB5BJg@OCc?+z*5!3wKuX<h%=OW+0n5>__$BPxWVCkWP9^h#; zRNXcBR&)eP(nsr0FbRn2F@mfeb!FZ8Pydb%w|Ya$Q|maoZkr#5mNHC;l|>3_moZ`o z^7Bn%ee`VB;wpdS!*F4+z!vBQP6`l;2;ducgTT>*!ljYA*9PN5+z29G^FBXGMgUav zhYl2z1{4wSAg3YHNY`T9OHSr0mpV5uVCC0OAl}gB)5Suw#09wqf#Z2Q5e{o`Yrsf( z+XvzUHjU2Sjm<0l2Vzu?c>4VIDe(^APp-Nm)UX^Rurk|wvYk&hqAmJL1JjA&@-9`j z5=dBw{yDV@VkIK_GsZFJXuu3UfyY2<`nNom<7}44MQkvnaiQLDF2p7hYzJ*9UrxNL zZ)Z{ACCnwSXeYAq0mEXwr}2q^X*dD$!k<jc0>NWc-eX*OUe8GiTo@wR;ltkmokTZC zf#HufX43T@g!p%)y<<#XLXbAx9Tz4P(Z%+SJ(k0YO%F5KbH;Kt@Ko1R`=N4h+v9mY zy0nEkI#Z$6ZZ9;REi$1(_po_+RIK=Ke|BHdIMLe5tNg;U!%_VRX_kiKd6CCj#r2HS zYY*rgZ9U<(TADlQ*4H)#%f@g4CmBL=+Pi~MwKz?vdJ3m~2xKNBF~M15IFlNjL^g1y zMwBpvxF;JNUj3z|lHwY%wO&0LJ<Bh{^1|#fNR8TRP#Stw_l3H97-D;Es}&kUp0&rz z&p9Mbd1aP4I%yZaQ>iij9~Pdj!)(i=Fpnh9K6-RR#ql*MboHzH7ai%q(M$I5Rms@f zD@rI*g39Rvap^t#nsW)h%2V=qQ)E>i(VQ(s?oZ_KECf@9AlgzfETJE|h@>|CpHxk) zfxn6?w8%Xu$lOwMlWnY;V+C<_L@be?pB7ZtPh`~e1Z33G6X=PTQl`C&xLHp;1ctrA zma@YYtfy@cOd8@wUY@@+54bYFlE~)NgzN8z|HAdc4}zRoK(RbFcM_LKcNVe0anW3a zqxWGmS>cyvl-ow^fu_%Jh)04q_0wfTtsVN#uv}_1FTh-ectF>jS<CqL-Los)pN3-V zvhLqEA<jvRnbt)mF+1=`|6*u7pJjj{Nnj!@oG9M!W=vH|7Gkpnx|sE$wRQGcifJWP z+z>_OwpzbR5YwxH-C&X)q+J32peQdg%zHE45lD<3(UW!<BbLzV!wNS+rJ{*dDXoUQ z+TEXSj5CE<vpu1f3pM=VS3B))$TLMscYd$mn3Y>D!k0XiF==|MgX@1#-%)5cr(2k& zckTic<*D%X$>=bEFBG;hGq!$oyQ&WNspjf~4wI(!Sw#vlf5|`=YI}I!O0sMRbl>J? z%M$+BKJIW&#VhVVGTz5ie!|rBI8kB;ZfO5tzv)RC@pF|@yZO^3*Z{APc*|8BZz#^h za(cS1cNqO!?+%xUS{piMzkewDjAbh4`A4a=T_{Vw6-fa=W%JO{dSIGmCCl;wwO`f^ zdV)rNP3iaHdDX63>sqf&ES}}*TH&57t<v{elfSCmM6y|_(<aB42QC{x33$U_HJN`; zWloZ$bXrD5vMeeR$?bRrEg4w1XgJ2Sj3ZC7203uQBr<rQe@{g2Xy+#-g4?Cte$UX* zu9K<glGKm_su%f!2prW4rtiJ}o|6>*{`X*HI-Y!%Am;V~bLl9>*{*4mc|PPyTzXDf zkul#PBHNA+N$dPKA^*7#wL>Nj&*9~n1VzGR&%*1UE4drI=&{(P;1kQ{7r~BnVxIc# zlrv9Io;Y!p+bMx+XNQ!LfFCJtD~!-FU6f1-uZLe9Xj6RlZpFa{KF)H&L$(gR3>CF1 zTiN_m4%BGlSLP^cDT(XCN0;|m=7rC_z&UPCn-aO^m>dcn97M$JMk1?2p>DQD^-jnf zBz`o_eLWrW<le>M-bYiwDF~~wE6SlVL8)Ad|9xsVd6$`2RW(D-JMr%RTurt&cjAJ= zQy?9n7n&)eZvz`q*w1Nh9Iu77kpc8rfZpjP#|2v6O_*MyuXuC;Nyu^p(sqLDm~F}v zly#-uSoO?dxEdd00D}KsDJ7&Lh7052>}A^D)M-8|6N!={92U3H{yCw3Ts=^J==soc zx^8dic5iIV8;8tcOYo<e0!TjxNnK_cneV_?uLn-2IZcc$Y5BNv{C5zUFJ4_>v~Ch! zakqrS0VY8x&(wn%a@~Q3haHDeYPO=AyN64$qxyzTG~ICUcIH6YD#J+xvfL>zba5ld zZ#)Cj^Ie8c&AVW;l5CEqL7ocTU~e_`ZrVx%<z@%CE+>xj(!L4XB?S>W71J4azf5LE z$-WVy)<w%~Um<v#jf#(rzX5m~+Aixp9cn)ZgTTMDR-a0sHGleIAid)peqjHC?9GKh z)T$WmK>r#qD2((Ux9}HS;&hD;q-vfT;;&!|gruRGXp@=p8P%I#;E}>&bULr&j3S~9 zjfw0umaNL$NaGEy&c}R<j_oXSZ*@~xD^|$j+|0Tvj-{k-x_$aoun*|=v`7eypnADG z>f!R_dFpz*^8FX^bm<9_-T%R&Xj(()oZ_X8EH?beBE#iHbFYkAaN9TyBv(dQ4xaSk zeK{L{l4`#<55nwHF>mh~iw#|}_}u<v5saP*z4i7By6nXnNFVS*GDO<P>-m~8hiZ6u zy)u9^wBr377U3A>CHTWJl=X*W_zj`vClm6vsnoXRDI_X$*bO)qfHNRR+C?xBul;Y7 z%mDSKFUBBYBDvBU*Ji5)P}fH%G3QWV{bv4$9aF7uH~JnunN6)q4qolPYQeR=Vkva^ z<>EpDH1X&8odS<WTC=K65P?;RGiVO3J{zOXue8I)8kmy~ej!h5x?ijHXLPtO4b==k zqgZRSv<Kab&MHQi8bz6WD#HTwX|<CpMT#6Yuu%BF2L8E=^fjzH0GFG;63szBV+<(j zHrXmmC)ZNDOc=&r8V8(sR?Pn>dtWiW*0`r85oFHAs*X#fb(oJWRhkqtO)S4cBr20C zggCjUW7u+OR5fUND0LW;nJ6&+37hQLC%?wuJ@EY-e9Ca~K$c&Ul?Dp=<F2t{a(vRc zT-rguVQ|*HbU7#T9SE_=b(5%p%ZTpwG!+uhWsDMSioI*Ht4$Us=$Nm-Qqjfp)Wldw zksB}8Rh5_P%daw{gIrgxS+Cfb-YzB{+iq!dTCXjIgh|=&Cp0MIgp*y`r?%oHrTMbN zU^^TfRBB;thN;du(y8I675+rJ445*OBk}mb;C!MHJh7XQ4n&q}SCqV52x72w%SpDY zT(7Skbjg~N>`{Xa9B?UQ9odb|enN1}MCCnYAYi+#)6jHDhxSxekF1Iga0k%sqI-!C z@bHuEX~0^)%MWPqQ|`)4%4Uq9yZ!a-NWXFIq7sqJ?dUV0vDRb6#uOW1);>;u#$-)S zy0OiT-RD9J04C<IZ!!bfh<on-jC!N?rr+>>lI+E!eGXl@WMJ-0DCA<^TEYVThLrD! zS!i2*+mH{uDD(&1shV;+Q>wBt*TmH<aa_3T`k>Idi@|}PM`v9%IyMr2Ze2-=)(=h8 z7ivy;QytWoDqAq!qb+0nS7<!Z&f2JV8M_#bqx*Y?f!)IEbiU@!ZbZ!ip<CukoH|$4 z0vCnsXw^*#4K^EFF7}T!u{l1oO>x2W<Jxvx>;5Vy!x+8&8)*Ka12WFWR$1hW(*WzT z8y{1~D*w7~W%AxmG&M9v58yfebwh2*I<Fp7)Vc;Bodk@X(2+hrqvx{PaN?SFjFL_m z&cA`80}9>>I^WbmrSyo;sEFsMc^*;Z^0ddyeywxIzvodMawb%}Dq+FwoPM0HwoZh$ zX|%;o6XWk{VmDPu{2}uUT3upKMq%)o3vQ_HbYV%eZ`Z%oB<yW(Y;!niZR6nG@R*$l z?88otZRha#1IBkd=dkv2`RPNGW>1*z2wumY0$AF&+g;rrYd$=WrTXtN>~jmGDK4N- z#S8Ms{6hJEZJl>Ko7>~ZiM?YJp*F3pLT%F8t*sQLRP0%^H6k{#W7J-$QlqWfO6^VU z-r9SVlqi>$)-UzF_kMrw@0WkhdCqyC^(4>h^_+8ZKA!`L>^d&hZ%O;llejw<lGaIO zRDQ%AC1q14Bu%(R`UD+e5<hWu)BBGz=`BX?Q}vyOJ>GnY6-?+GOB8GtJQ8r?`;xsP zaThPK+lEkMRmF|>WeT?QJra+I_-i*k<aC#G9lkms7xoT2eusTk_JRp2bY9V#c)%og z|4Xvt%!Ave=$t4eWh%(g6t=yoz24m}^DE+Eb7Gn+h^S$#WrBH|V#dq!U5bTrm=v{< zl>}5Vtm}?*l;FI|#DLdq&wM|YnqNYVwQe%!*o(U<q0-focR-__aY9)~0j4RhLrK`5 z1dq_+CDKAwvnElX?1y(ebMb!cujo@n?*@3*XCW!zQ;|y%Y^7ocKq5s<JF>+cbP&ZZ z7{()^A9ccM4-Rk(iVncD5|FP4Baaqt!xI4<S@#TU5h9e;r({#(a*4;em0ZnPbDd+_ z>x7u;fwRP|wWA0n-Cx;9it1~GiU*VM?V_n@!9%jRky8$S@-UWmu&B{Fb@8^*bAdT} zUa?2x1NN1O5K0~DAF$ML;30R9)0)ui?chA&ruAp^DM?eFU&axpw3@E`K?D%Wn;LEG z8_!~;zmmmChU?aNWVm?nn2RZ4?$B#JCNdRFWj;~fMhh@}pn2c;F@^7JPaxkG-o)!M zQce-hpwN^BKC-T`L9_k@!YBtNRK9Da+0bk-6oIo_rk7O4l3rpddq|f#@8{X3ZhJ~F z6@<TF?}XFRdL^ukS#*bgQ)seT9Tngv6#PyO#mbD=XT`)z+qd0YguKq>er&9+d+$Yk zQrGR16TCL<j{M?fy-A#_H=ExG7k*HG!X#c0M`SNCZB(`H4{M(|qYjO{L$piEghKeA z7r33VK@K8v6>GKUFr0AN12UBAOI8$ok_2`AfF7QZ=4Ldh0_-~xPp&6+L}ZW;h8Kzu z#TG9UkODS}4BLxBZ*Ei4I|?IG#kFqS4B@xlj%RlWS|K^OE&G*Ub<oQz=v`pb!o^py zhRpLHKi35C?zXE_u8IqFSrS^Q6|rx^%wpQPMZVc(^a~HYfrg~tMGU)0x=1p>%p3~S zsQTb_3E|l;X8hsxt8f1_FuJ#3;$KfbNx7PUB^nuL^*NO_8L2Gei_c<I+E3WzkkFKN zPfx6k9Vj=%q#@1+ZhaFPAN=UnmAI7=T;rDe<^Bk)ot&~++1aF|vguh{(OBZco^6is z4Aw5G`3J-@H9RepXtj!XzBy!6xyO_)jZYxlLcZ)-<4n>0g=yFD{P24NijrVr9<`zB zGDZs_Q{UtHnbA=ni{RZewW0Gz#?JNX53y8pLd`iJo|s|xNa2<k>VmX>s}Zgl-Rd+r z=ppk)b-wFM9&KIw*IRMgXMIDT4*VxA-Miu^bR7kwUbQ00dUJb8lvkN)xPRRS@UYFp zBfMO1twne%a>FQ&ZYT=QQnlch^~#T}o%5&-el^H48+i3O`bUkb_Xj@E*IRjLfPZ09 zN9ZziOnz8CF(l5kulMb1*O4ux&YAp+7gax}M~?Z3<ccV42q(gJ8qFDP%*Fh+OFS7g zp_zaMy9qRg(ec|%(}TL&8bCv#2EwWlUZnz%W6(_D{#GEv{8UVAaI@oW$aW{^FZ|V0 znphQjlWr6G*03!ULXW6T{$Qt_2f0N{5)5F;W19^LlIsYL3gJCrH>+M#XGdFV=&;*) zuw!S&ExGAmca7v4lWbI&I^(%r>vwEX>Fkwo1@)<4clBjsqbg$WE?Z@eZfPWdH*!P; zsy=2^?8)|CDm)%qDi&F^hle+Qe|@TC2fzy3%NY2%*HTwyYD#$clR~<zBGU>OjSCs` z*de3bQ@+4hk^xfHC*n5%DFMj_Q}L0ZV+Uo}=fFFgs>WRX1QIdB;R@;_C}6T3o}?(D z)0%2NxvH^*CL4`pLUtA~*%%Lgs833_qPiKunbgfm%V9?g`3_9}if0EUrQ1-wk4x?0 zG~lqKg7g5Beeezm`H47Xl)X7~pvl+4tTYoy>zg!&&=EgU(x0kj*qZ^Ocm@H<4bWuf zTfz7-o#F3qkDLL4KOzm*SoOXg{>v&T(_NeZLxy{sjb*~a6TJLm6nv5yP8P)Az<8lo zGvFGf#mSbM%JL<HSW}uyb&%@CJ!rB>)3AFOnRD)*ZYpAA)Xy&iZ^qoByza(v_0+Rz zf}~f4d~8UDin>Rwub(mRpS2~;{#YD-leIt@^Wj`_>7@0f?LxloWbp*^SWod37z%-w z$C$=6P9~XZ3wBoAd2bFSgC}YNJ(-QHcvkB|-s^Vn@_~EVc+*xA=$MvcVq~<;Vt8P@ zPi5eX!v!Vpu^hkNNBS=btyBfD4Ye*L`5&C>ZZ7n_^Z$-@=UD10IQiif-*+N$_EX_3 z^xCy`Y{;*m{v?`jr?kJoCu@XB*I=KMuzUE*x`(&A(u6<Ss|C#`k-$IC8-)ag1<Q>L zN1u9|ce9e^9BA<?3)|2YtSS~{9FQvUtQh3NtUqd<3UjP|-r^17us&Er0-PJ7dBzc* zeUGH(KgrA88dIEA9{aQe0kSgzw-+n+OlRq|pA?JOg3O=TB0~>#6|<z%(Xwb>$ycGx z3ZaRAYGzV!6?|UeSb*=?isHQN{pypdA2ftWN&dmJ9GBvG;mA8HGiDOAy~@ye?5E~` zLbV4<hze}}L0lqwAEO`W9}pN=>ry*rfp0iCX7tL&+1Xqr0pry>Md$WYR_^IT34yxX zI>~d4_2|+DY&Ng<<7ZDSbAV%0Rc!Qb^igzrWp0yEsl~x=Ef_dXtA2nbu6##p))-RF zhIV)jy4m1D9PGPR7+RO`eK`}lCs)`jS)0%vl=m!G*y4{WzUjr`sprmj(k1F4V^w`= z-O09&@BL!-8cH&4HT-Uc{v=Aw(74N2|5`&!_Ds9MS!-mT{}Bw4zF6b}qT?K^cQzIE zWsfPpE&x#SHY|a+%UI?kH=<s3H@G-Wj*%opg!UxdvywGW8A}iNuxt6#JoWi2$@H5M zmDB=8tdsm6v{*h`>(a7yR~_UjIgvLMA`(1nhFcS7dZ?deJW}6*5HGj51AQtWRM>>Q z&>}FXvqXzyoaS#XafzYDnQgdN26o4MQFrefDw3a7v6RA{%}7>^;>qIF)1#>+q3^he zrmVHOh=PX+F1VlWJCR3zW|}M4OZiA47&K~QaQNCW*CTg}Ye-j%W6y*IR?^otq!w4P zrp@nFR5BgF8JW|W)@S3fsX0k1u>+rkeHLGJiol)W7}lqWp%DE1F)HTt2CSl})Te82 ze8Pz?2bl_I7{950w@$~$OSPz0@_i`mq6OXW?@vYHD4g7oS&<9dwPa4i1_dP;2Qn&t zYa1<K?ms2wc<J(JbO~{HVybk?GK<X7%awc-LDDzye0gwVC%vOlcL?oHJWXI6Rb}kz zZ5I4^zTGZ}e#zEtsWjgw3nIW1Ru->xG!#HhY*z+%b)ocPUFz^x3)1VwZ7s%sJH>a3 zZvm-9r{0I1C-c#)^0O-2);*>B87F&vtxp(BZ-2ifD@7|(v`N!myI0RG-U4<W-?M(( z{03B`coq~gI&Zx$;ks-sxn??CR!A*X#dcQApr?4->l6~w6(cirDiWe$%J(9L$@K2@ zLVFq9ZSYi)!=b~%?i?SE4p|=0-m#QBzRT_2lRxL=IlK0pGI&s~^nT(+LD|Dz3KRBj zF4l%MVAk5Gl<EFP6+8pWd~VXabjyS8IUUxY^`c!_?Odn#0~Q^Bv3#9YZ^+mIAQHiC zAecc^pwC^{YD!~HM4vk|Jg}^ET$m@jlLmrM_Cg>WQ>ppA*8Oj1C}{gX&>ybS<w+em zodI%D7;iNf0~;-h#@TOS!@KHJC?wl=3+EU9v*w08*I%3kXiK!p$F&5`@hvaRylR!Z zzH?(Mq=LnHqSi&pzhp$wUb^{9{|$E$tpxdUUQuHsRc!+=j48&Ykl+3z1utP`o1-^8 zH*kx=vXWvlvcUuPxQ+76$i!2_8=n?}(Gp4C$j%mXG0J4<-*nD2+d<aw&O(Y`xcVB- zw!V>-ZtQx?oZh`NB4jT_yWL!V_v6+nsB6!U-3@eD?P*&o)^P+$6j#}vXfP4osNytb zYSa2I+YyWi8{htpwvA0SgF1ZL75#-<nAh*tT$G;FJ{<~{85uHv{dNBskE>-up~T_k zmbXiON%6GxkKU%z&iuOjvKkad<#*rE!L=nOrgnyrX|~UI7L)Lq!LrHv>AWBOSwtI- zBETIcN8X<JKY>=E<~}wgRr&WSxGPJJbw4#{{OYVeHZ)&aHYagv9jbFL^lfl~x2Dc0 zR{O@xv{w5H0fJg56xwAfArpBzkBNRpuy+HszLuH@Xgd*4<%fv}k*jqTsdzE9840qE z7E4iJ8;MRVf6Rv6V<~;@>d_fP^e{?Mww)}7={27e!G)sP6LMG={(wJ-A_{ps<w5_0 zv;rGgQ4o7iMj|-~VG=PO_pH(nzT=E@D$nN=nwnIh0b7)NGP@nnZUhM;$pKcH?MU5X z$*CuF2>owb#t;-l1MBHMB+L}e!KuqMeWxO;hw-MFtpT7;VYpBf=`dWlSSnKIWNKdP zFk4%AuO$vp=Splk*G-($I8SG3GkJfuRvkHGHwl%ll0{~)R@OLGNms!|kkdB<W+bML z%?(<56U}dGxS24ye9Yza4-X)8c-tHMiOQ$49kPB-F5GP{Tu<kwsO8rE#I=*ywNz?0 z%)m7=*2RmYe#Oc%daW_vQ;w{heQOLMAoxIbh6^ee4N8R<`O<9_J)(SL4~BPSd)YER z5HJWYeo2OYK5BU<C58^p@d=oI&0ANUaw6_zIwcd)-ppF5OmUsTEVCzSVdl--gLD6h ztY`zZjE{NnyvMQLzf6)($2G?)SKYa>-^COo?{)dq<^*P6Z?@Qm?@DO4Dpc1Lo2r9e zdCyj(Yw@2NskfQvKP4<vdF51D@-~0Z9-}%`{6hCnY<#N&x`*W@+||FQD0{746(}*_ zHKzSGGe9YIQRCvM`U!VUl=`0o9tCH0k&ybnjfN$xg$iBsyALCutvk;sWRjYsGuNeG zdX~^v5Xu^N{w(a7X>)t$Vd7ooLHsGNpX+m<xUNLL3ZHCOj0@&rQv_+d%IpMP;nd)m zZa$_@3X|TyN5CL%4Hw08iP7^g=tET95vl=<37?k5kq7_b;8gh*_5}1ZAZ2=mWrv@{ ziG+)BPE*86vw0V|#e?1JkrJvA_wW<<PmvnRjLzP5@KQ9{7oOc0!_#dE9rAA9kX?QP z2#F;KLJD|B6U{#$RS0Hn7xNAE@D1HBVJx7QDZ;C(JI)ENgSTlInD5Fj9tvukCyFV? z)c}uksrn+DY%SCm!#(-pHla|e<PM149$jZupIf)`p4|7SToEc+3sCMhc2Bw{POptx z?454p5EP0<k;dGzG*mZi*c$y%K%V43Z4vAq7(2NY6>JBej?^jU6!mX@Qmo{l)A|@0 znrU(9f6lD`&T}D)kgz+J>oJOI6-M}3nnDicxB8F})SPmT`m}0KxQSNLMyalO5;+|4 z7Ez+=*X)V@<0sFx&Ip*3M6C$&pe)vf*nV4dzrvjd>Bi|)E8?_j|Nfv6^QfYAeee}4 z<-ap=AsVrA>#`E@4Sw)%m)4i~cz7kadE>1s(73{15HTuegX=Hz){YHEz^Kwc8KO#* zD5^~$_`jx@|L)3w>i>d&TV(=K1G_-*6(7PY%{Je;EYP>f1^#V7w}2a<3+@$K`3gj> zaR~`*iGr`V(OrQwwNYMMh5*)wC@xDu6lUuNfFFj7a-;O^C{d@&K$Ph=2%znV^4_)w za5$spwqXFQ3ktfU0N`>(h3*J}uLyx(soMNE0H*yvvf!wcWft(iHH7~ziALCa;)<~` z{r^(JD6ZvesEZvCfWRBY^??y3{zV-??t2+k@dBZ;a2;;=0s>e)yG%NVlcEm3uz)WW zVR2%`f8g$s>fbHlLNr1(@E;KF&;PqT_)`55_eF=`V5uwc2>c)T&F*#Z6|K1|U~Bka zAT^3*?*>5n`9I}2QB0SejNStQ__HpP8~3yUB88Vxnj8>HV;hLNjR66$#g|Dwm}}rG zhe@wAG*@y7;m($#hA=GPe-1z4e(WMwK&$e9K(?<u;46nwu7Et1e}Pn}pszOo4|{Q! zT&MwDlxzU?wu>9J-@$?s+XsTLY(iYA0yBsM1uuuWI|!uwI}eSxGjUlr^$`2zjsEXS z%)fhU{OJ<>&)N(R_&-y`zh;PkSBDGH2*SC)Kt?8g9U|PQe?OnM@N(&KzUUjjul@(= CEjxVx delta 36364 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%0Dd@gG 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 Pg;r%T9-eUK&({9{Yi$yT diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index fc10b601f7c..ac72c34e8ac 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 79a61d421cc..0adc8e1a532 100755 --- a/gradlew +++ b/gradlew @@ -83,10 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +131,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -197,6 +198,10 @@ if "$cygwin" || "$msys" ; then done fi + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in From afc9148d264e4ef7b925ac02a12b98d3ca1d1fbf Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Thu, 21 Sep 2023 23:26:51 -0400 Subject: [PATCH 460/619] Fix multiple aliases sections not working (#6050) --- .../java/ch/njol/skript/structures/StructAliases.java | 8 +++++++- .../skript/tests/syntaxes/structures/StructAliases.sk | 9 +++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 src/test/skript/tests/syntaxes/structures/StructAliases.sk diff --git a/src/main/java/ch/njol/skript/structures/StructAliases.java b/src/main/java/ch/njol/skript/structures/StructAliases.java index 8d09fcf802d..cf473c63eb3 100644 --- a/src/main/java/ch/njol/skript/structures/StructAliases.java +++ b/src/main/java/ch/njol/skript/structures/StructAliases.java @@ -20,6 +20,7 @@ import ch.njol.skript.Skript; import ch.njol.skript.aliases.Aliases; +import ch.njol.skript.aliases.ScriptAliases; import ch.njol.skript.config.SectionNode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; @@ -30,6 +31,7 @@ import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.entry.EntryContainer; +import org.skriptlang.skript.lang.script.Script; import org.skriptlang.skript.lang.structure.Structure; @Name("Aliases") @@ -54,7 +56,11 @@ public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResu node.convertToEntries(0, "="); // Initialize and load script aliases - Aliases.createScriptAliases(getParser().getCurrentScript()).parser.load(node); + Script script = getParser().getCurrentScript(); + ScriptAliases scriptAliases = Aliases.getScriptAliases(script); + if (scriptAliases == null) + scriptAliases = Aliases.createScriptAliases(script); + scriptAliases.parser.load(node); return true; } diff --git a/src/test/skript/tests/syntaxes/structures/StructAliases.sk b/src/test/skript/tests/syntaxes/structures/StructAliases.sk new file mode 100644 index 00000000000..88b1024793b --- /dev/null +++ b/src/test/skript/tests/syntaxes/structures/StructAliases.sk @@ -0,0 +1,9 @@ +aliases: + cool_dirt = dirt + +aliases: + cool_stone = stone + +test "script aliases": + assert cool_dirt is dirt with "custom dirt alias failed" + assert cool_stone is stone with "custom stone alias failed" From 27c6275390c8ac0d187891acd5816acfacb3c236 Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Fri, 22 Sep 2023 14:25:48 +0300 Subject: [PATCH 461/619] Remove 'MarkedForRemoval' Annotation (#6054) Remove 'MarkedForRemoval' annotation --- .../ch/njol/skript/util/MarkedForRemoval.java | 43 ------------------- src/main/java/ch/njol/util/Math2.java | 24 +++++------ 2 files changed, 12 insertions(+), 55 deletions(-) delete mode 100644 src/main/java/ch/njol/skript/util/MarkedForRemoval.java diff --git a/src/main/java/ch/njol/skript/util/MarkedForRemoval.java b/src/main/java/ch/njol/skript/util/MarkedForRemoval.java deleted file mode 100644 index 87739c1a333..00000000000 --- a/src/main/java/ch/njol/skript/util/MarkedForRemoval.java +++ /dev/null @@ -1,43 +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 <http://www.gnu.org/licenses/>. - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.util; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Marks the annotated element as being subject to removal in the future. - * <p> - * The annotated element should also be annotated with {@link Deprecated}. - * <p> - * It is recommended to provide when the annotated element will be removed, - * using the {@code version} element. - */ -@Retention(RetentionPolicy.SOURCE) -public @interface MarkedForRemoval { - - /** - * When the annotated element is expected to be removed. - * <p> - * For example, this could be {@code after "2.6.4"}, - * {@code "starting from 2.7"} or simply {@code "2.7"}. - */ - String version() default ""; - -} diff --git a/src/main/java/ch/njol/util/Math2.java b/src/main/java/ch/njol/util/Math2.java index b8b5179ff22..c16018d341e 100644 --- a/src/main/java/ch/njol/util/Math2.java +++ b/src/main/java/ch/njol/util/Math2.java @@ -19,7 +19,7 @@ package ch.njol.util; import ch.njol.skript.Skript; -import ch.njol.skript.util.MarkedForRemoval; +import org.jetbrains.annotations.ApiStatus.ScheduledForRemoval; import org.jetbrains.annotations.ApiStatus; import java.util.Arrays; @@ -157,32 +157,32 @@ public static float safe(float value) { } @Deprecated - @MarkedForRemoval + @ScheduledForRemoval public static int floorI(double value) { return (int) Math.floor(value + Skript.EPSILON); } @Deprecated - @MarkedForRemoval + @ScheduledForRemoval public static int ceilI(double value) { return (int) Math.ceil(value - Skript.EPSILON); } // Change signature to return int instead of removing. @Deprecated - @MarkedForRemoval + @ScheduledForRemoval public static long floor(float value) { return (long) Math.floor(value + Skript.EPSILON); } @Deprecated - @MarkedForRemoval + @ScheduledForRemoval public static int min(int a, int b, int c) { return Math.min(a, Math.min(b, c)); } @Deprecated - @MarkedForRemoval + @ScheduledForRemoval public static int min(int... numbers) { if (numbers.length == 0) return 0; @@ -193,13 +193,13 @@ public static int min(int... numbers) { } @Deprecated - @MarkedForRemoval + @ScheduledForRemoval public static int max(int a, int b, int c) { return Math.max(a, Math.max(b, c)); } @Deprecated - @MarkedForRemoval + @ScheduledForRemoval public static int max(int... numbers) { if (numbers.length == 0) return 0; @@ -210,13 +210,13 @@ public static int max(int... numbers) { } @Deprecated - @MarkedForRemoval + @ScheduledForRemoval public static double min(double a, double b, double c) { return Math.min(a, Math.min(b, c)); } @Deprecated - @MarkedForRemoval + @ScheduledForRemoval public static double min(double... numbers) { if (numbers.length == 0) return Double.NaN; @@ -227,13 +227,13 @@ public static double min(double... numbers) { } @Deprecated - @MarkedForRemoval + @ScheduledForRemoval public static double max(double a, double b, double c) { return Math.max(a, Math.max(b, c)); } @Deprecated - @MarkedForRemoval + @ScheduledForRemoval public static double max(double... numbers) { if (numbers.length == 0) return Double.NaN; From bfa4421188896d1b71061f022b13de159a4b08e7 Mon Sep 17 00:00:00 2001 From: Moderocky <admin@moderocky.com> Date: Fri, 22 Sep 2023 18:28:14 +0100 Subject: [PATCH 462/619] Fix error when unloading a script with multiple variables sections (#6047) * Returns the old 2.6.4 duplicate variables section behaviour. * Add an error but i don't know what it's for * Add lots of brackets to keep walrus happy :}}} * Add load tracker to prevent multiple loading. * Prevent variable data wipe, fix another bug * Support IDEs from the dark ages that don't know what a star is and think it orbits the earth or something * add a test --------- Co-authored-by: APickledWalrus <apickledwalrus@gmail.com> --- .../skript/structures/StructVariables.java | 39 ++++++++++++++----- .../syntaxes/structures/StructVariables.sk | 10 +++++ 2 files changed, 40 insertions(+), 9 deletions(-) create mode 100644 src/test/skript/tests/syntaxes/structures/StructVariables.sk diff --git a/src/main/java/ch/njol/skript/structures/StructVariables.java b/src/main/java/ch/njol/skript/structures/StructVariables.java index b6810c4812e..f3e32e19dcf 100644 --- a/src/main/java/ch/njol/skript/structures/StructVariables.java +++ b/src/main/java/ch/njol/skript/structures/StructVariables.java @@ -87,14 +87,14 @@ public static class DefaultVariables implements ScriptData { private final Deque<Map<String, Class<?>[]>> hints = new ArrayDeque<>(); private final List<NonNullPair<String, Object>> variables; + private boolean loaded; public DefaultVariables(Collection<NonNullPair<String, Object>> variables) { this.variables = ImmutableList.copyOf(variables); } - @SuppressWarnings("unchecked") public void add(String variable, Class<?>... hints) { - if (hints == null || hints.length <= 0) + if (hints == null || hints.length == 0) return; if (CollectionUtils.containsAll(hints, Object.class)) // Ignore useless type hint. return; @@ -115,7 +115,7 @@ public void exitScope() { /** * Returns the type hints of a variable. * Can be null if no type hint was saved. - * + * * @param variable The variable string of a variable. * @return type hints of a variable if found otherwise null. */ @@ -140,14 +140,24 @@ public boolean hasDefaultVariables() { public List<NonNullPair<String, Object>> getVariables() { return variables; } + + private boolean isLoaded() { + return loaded; + } } @Override public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult, EntryContainer entryContainer) { SectionNode node = entryContainer.getSource(); node.convertToEntries(0, "="); - - List<NonNullPair<String, Object>> variables = new ArrayList<>(); + List<NonNullPair<String, Object>> variables; + Script script = getParser().getCurrentScript(); + DefaultVariables existing = script.getData(DefaultVariables.class); // if the user has TWO variables: sections + if (existing != null && existing.hasDefaultVariables()) { + variables = new ArrayList<>(existing.variables); + } else { + variables = new ArrayList<>(); + } for (Node n : node) { if (!(n instanceof EntryNode)) { Skript.error("Invalid line in variables structure"); @@ -227,20 +237,26 @@ public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResu } variables.add(new NonNullPair<>(name, o)); } - getParser().getCurrentScript().addData(new DefaultVariables(variables)); + script.addData(new DefaultVariables(variables)); // we replace the previous entry return true; } @Override public boolean load() { DefaultVariables data = getParser().getCurrentScript().getData(DefaultVariables.class); + if (data == null) { // this shouldn't happen + Skript.error("Default variables data missing"); + return false; + } else if (data.isLoaded()) { + return true; + } for (NonNullPair<String, Object> pair : data.getVariables()) { String name = pair.getKey(); if (Variables.getVariable(name, null, false) != null) continue; - Variables.setVariable(name, pair.getValue(), null, false); } + data.loaded = true; return true; } @@ -248,8 +264,13 @@ public boolean load() { public void postUnload() { Script script = getParser().getCurrentScript(); DefaultVariables data = script.getData(DefaultVariables.class); - for (NonNullPair<String, Object> pair : data.getVariables()) - Variables.setVariable(pair.getKey(), null, null, false); + if (data == null) // band-aid fix for this section's behaviour being handled by a previous section + return; // see https://github.com/SkriptLang/Skript/issues/6013 + for (NonNullPair<String, Object> pair : data.getVariables()) { + String name = pair.getKey(); + if (name.contains("<") && name.contains(">")) // probably a template made by us + Variables.setVariable(pair.getKey(), null, null, false); + } script.removeData(DefaultVariables.class); } diff --git a/src/test/skript/tests/syntaxes/structures/StructVariables.sk b/src/test/skript/tests/syntaxes/structures/StructVariables.sk new file mode 100644 index 00000000000..5de1d5c7be3 --- /dev/null +++ b/src/test/skript/tests/syntaxes/structures/StructVariables.sk @@ -0,0 +1,10 @@ +variables: + {variables_test1} = true + +variables: + {variables_test1} = false # we hope it doesn't overwrite! + {variables_test2} = true + +test "default variables": + assert {variables_test1} is true with "{variables_test1} was not true" + assert {variables_test2} is true with "{variables_test2} was not true" From e618afa662b03d29363d8be2e18c623c8eb28c72 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Sep 2023 15:11:42 -0600 Subject: [PATCH 463/619] Bump actions/checkout from 3 to 4 (#6029) Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... From 347ab462bb24b69ff141ebbcb06a4c8e655a6de7 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Sat, 23 Sep 2023 19:19:40 +0300 Subject: [PATCH 464/619] =?UTF-8?q?=E2=9A=92=20Disable=20Javadocs=20genera?= =?UTF-8?q?tion=20for=20nightly=20docs=20&=20improvements=20(#6059)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Let's see if I am good at GH actions 🤞 * ops! * Use proper docs template reference when possible * Disable nightly javadocs generation with an option Each javadoc is ~50mb, which was causing the big size of the docs! while each docs generation is ~2mb only * Fix building * Revert pull changes They are not what fixed the issue, probably the old PRs aren't syncing for some reason * Update build.gradle --------- Co-authored-by: Moderocky <admin@moderocky.com> --- .github/workflows/docs/generate-docs/action.yml | 16 +++++++++++++--- .github/workflows/nightly-docs.yml | 6 +++++- src/main/java/ch/njol/skript/SkriptCommand.java | 2 +- .../njol/skript/SkriptCommandTabCompleter.java | 3 ++- src/main/resources/lang/english.lang | 2 +- src/main/resources/lang/french.lang | 2 +- src/main/resources/lang/german.lang | 2 +- src/main/resources/lang/simplifiedchinese.lang | 2 +- 8 files changed, 25 insertions(+), 10 deletions(-) diff --git a/.github/workflows/docs/generate-docs/action.yml b/.github/workflows/docs/generate-docs/action.yml index 84cc2153f86..e033996868e 100644 --- a/.github/workflows/docs/generate-docs/action.yml +++ b/.github/workflows/docs/generate-docs/action.yml @@ -23,6 +23,11 @@ inputs: required: false default: "*" type: string + generate_javadocs: + description: "Designates whether to generate javadocs for this nightly documentation" + required: false + default: false + type: boolean outputs: DOCS_CHANGED: @@ -41,6 +46,7 @@ runs: SKRIPT_REPO_DIR: ${{ inputs.skript_repo_dir }} IS_RELEASE: ${{ inputs.is_release }} CLEANUP_PATTERN: ${{ inputs.cleanup_pattern }} + GENERATE_JAVADOCS: ${{ inputs.generate_javadocs }} run: | replace_in_directory() { find $1 -type f -exec sed -i -e "s/$2/$3/g" {} \; @@ -55,7 +61,7 @@ runs: if [ -d "${DOCS_REPO_DIR}/docs/templates" ] then export SKRIPT_DOCS_TEMPLATE_DIR=${DOCS_REPO_DIR}/docs/templates - else + else # compatibility for older versions export SKRIPT_DOCS_TEMPLATE_DIR=${DOCS_REPO_DIR}/doc-templates fi @@ -64,12 +70,16 @@ runs: cd $SKRIPT_REPO_DIR if [[ "${IS_RELEASE}" == "true" ]]; then ./gradlew genReleaseDocs releaseJavadoc - else + elif [[ "${GENERATE_JAVADOCS}" == "true" ]]; then ./gradlew genNightlyDocs javadoc + else + ./gradlew genNightlyDocs fi if [ -d "${DOCS_OUTPUT_DIR}" ]; then - mkdir -p "${SKRIPT_DOCS_OUTPUT_DIR}/javadocs" && cp -a "./build/docs/javadoc/." "$_" + if [[ "${GENERATE_JAVADOCS}" == "true" ]]; then + mkdir -p "${SKRIPT_DOCS_OUTPUT_DIR}/javadocs" && cp -a "./build/docs/javadoc/." "$_" + fi mkdir -p "/tmp/normalized-output-docs" && cp -a "${DOCS_OUTPUT_DIR}/." "$_" mkdir -p "/tmp/normalized-generated-docs" && cp -a "${SKRIPT_DOCS_OUTPUT_DIR}/." "$_" diff --git a/.github/workflows/nightly-docs.yml b/.github/workflows/nightly-docs.yml index 782b76b9ed5..8bd3d70aaa1 100644 --- a/.github/workflows/nightly-docs.yml +++ b/.github/workflows/nightly-docs.yml @@ -3,7 +3,11 @@ name: Nightly documentation on: push: branches: - - '**' + - 'dev/feature' + - 'dev/patch' + - 'enhancement/**' + - 'feature/**' + - 'fix/**' tags-ignore: - '**' diff --git a/src/main/java/ch/njol/skript/SkriptCommand.java b/src/main/java/ch/njol/skript/SkriptCommand.java index 7f01e3ff331..62261d0e426 100644 --- a/src/main/java/ch/njol/skript/SkriptCommand.java +++ b/src/main/java/ch/njol/skript/SkriptCommand.java @@ -398,7 +398,7 @@ else if (args[0].equalsIgnoreCase("info")) { else if (args[0].equalsIgnoreCase("gen-docs")) { File templateDir = Documentation.getDocsTemplateDirectory(); if (!templateDir.exists()) { - Skript.error(sender, "Cannot generate docs! Documentation templates not found at 'plugins/Skript/doc-templates/'"); + Skript.error(sender, "Cannot generate docs! Documentation templates not found at '" + Documentation.getDocsTemplateDirectory().getPath() + "'"); TestMode.docsFailed = true; return true; } diff --git a/src/main/java/ch/njol/skript/SkriptCommandTabCompleter.java b/src/main/java/ch/njol/skript/SkriptCommandTabCompleter.java index 19588312957..4cd9c445173 100644 --- a/src/main/java/ch/njol/skript/SkriptCommandTabCompleter.java +++ b/src/main/java/ch/njol/skript/SkriptCommandTabCompleter.java @@ -18,6 +18,7 @@ */ package ch.njol.skript; +import ch.njol.skript.doc.Documentation; import ch.njol.skript.test.runner.TestMode; import ch.njol.util.StringUtils; import org.bukkit.command.Command; @@ -116,7 +117,7 @@ public List<String> onTabComplete(CommandSender sender, Command command, String options.add("disable"); options.add("update"); options.add("info"); - if (new File(Skript.getInstance().getDataFolder() + "/doc-templates").exists()) + if (Documentation.getDocsTemplateDirectory().exists()) options.add("gen-docs"); if (TestMode.DEV_MODE) options.add("test"); diff --git a/src/main/resources/lang/english.lang b/src/main/resources/lang/english.lang index 17225590e3b..40db6ddb7a0 100644 --- a/src/main/resources/lang/english.lang +++ b/src/main/resources/lang/english.lang @@ -45,7 +45,7 @@ skript command: changes: Lists all changes since the current version download: Download the newest version info: Prints a message with links to Skript's aliases and documentation - gen-docs: Generates documentation using doc-templates in plugin folder + gen-docs: Generates documentation using docs/templates in plugin folder test: Used for running internal Skript tests invalid script: Can't find the script <grey>'<gold>%s<grey>'<red> in the scripts folder! diff --git a/src/main/resources/lang/french.lang b/src/main/resources/lang/french.lang index 9a548ba8587..6aa7e26342d 100644 --- a/src/main/resources/lang/french.lang +++ b/src/main/resources/lang/french.lang @@ -45,7 +45,7 @@ skript command: changes: Liste toutes les modifications apportées depuis la version actuelle download: Télécharge la dernière version info: Affiche un message contenant les liens vers les alias et la documentation de Skript - gen-docs: Génère la documentation en utilisant doc-templates dans le dossier du plugin + gen-docs: Génère la documentation en utilisant docs/templates dans le dossier du plugin test: Utilisé pour exécuter les tests Skript invalid script: Impossible de trouver le script <grey>'<gold>%s<grey>'<red> dans le dossier des scripts ! diff --git a/src/main/resources/lang/german.lang b/src/main/resources/lang/german.lang index cb9637e9dd7..0a8255f44e8 100644 --- a/src/main/resources/lang/german.lang +++ b/src/main/resources/lang/german.lang @@ -45,7 +45,7 @@ skript command: changes: Listet alle Änderungen seit der aktuellen Version auf (auf englisch) download: Lädt die neueste Version herunter info: Druckt eine Nachricht mit Links zu den Aliases und der Dokumentation von Skript. - gen-docs: Generiert Dokumentation mithilfe von doc-templates im Plugin-Ordner + gen-docs: Generiert Dokumentation mithilfe von docs/templates im Plugin-Ordner test: Wird zum Ausführen von Skript-Tests verwendet invalid script: Das Skript <grey>'<gold>%s<grey>'<red> konnte nicht gefunden werden. diff --git a/src/main/resources/lang/simplifiedchinese.lang b/src/main/resources/lang/simplifiedchinese.lang index e1229106eba..c64b007caba 100644 --- a/src/main/resources/lang/simplifiedchinese.lang +++ b/src/main/resources/lang/simplifiedchinese.lang @@ -45,7 +45,7 @@ skript command: changes: 列出自当前版本以来的所有变化 download: 下载最新的版本 info: 打印一个带有Skript的别名和文档链接的信息 - gen-docs: 使用插件文件夹中的doc-templates生成文档 + gen-docs: 使用插件文件夹中的docs/templates生成文档 test: 用于运行内部的Skript测试 invalid script: 无法在scripts文件夹中找到脚本<grey>“<gold>%s<grey>”<red>! From 31e24ad7cca1a25729b1b87d95e463768f54a9fe Mon Sep 17 00:00:00 2001 From: Moderocky <admin@moderocky.com> Date: Sat, 23 Sep 2023 20:20:50 +0100 Subject: [PATCH 465/619] Merge patches in to feature branch. (#6061) --- CLOCKWORK_RELEASE_MODEL.md | 294 ++++++++++++++++++ README.md | 2 + .../ch/njol/skript/command/ScriptCommand.java | 86 ++--- .../skript/conditions/CondIsInfinite.java | 2 +- .../java/ch/njol/skript/config/Config.java | 61 ++-- .../ch/njol/skript/effects/EffPotion.java | 2 +- .../ch/njol/skript/events/SimpleEvents.java | 2 +- .../skript/expressions/ExprRandomNumber.java | 2 +- .../njol/skript/structures/StructAliases.java | 8 +- .../skript/structures/StructVariables.java | 39 ++- .../syntaxes/structures/StructAliases.sk | 9 + .../syntaxes/structures/StructVariables.sk | 10 + 12 files changed, 431 insertions(+), 86 deletions(-) create mode 100644 CLOCKWORK_RELEASE_MODEL.md create mode 100644 src/test/skript/tests/syntaxes/structures/StructAliases.sk create mode 100644 src/test/skript/tests/syntaxes/structures/StructVariables.sk diff --git a/CLOCKWORK_RELEASE_MODEL.md b/CLOCKWORK_RELEASE_MODEL.md new file mode 100644 index 00000000000..e5e086f62eb --- /dev/null +++ b/CLOCKWORK_RELEASE_MODEL.md @@ -0,0 +1,294 @@ +# Clockwork Release Model + +## Table of Contents +1. [Introduction](#introduction) + - [Preamble](#preamble) + - [Motivations](#motivations) + - [Goals](#goals) + - [Non-Goals](#non-goals) +2. [Release Types](#release-types) + - [Feature Releases](#feature-releases) + - [Patch Releases](#patch-releases) + - [Pre-Releases](#pre-releases) + - [Emergency Patch Releases](#emergency-patch-releases) +3. [Timetable](#timetable) + - [Major Version Schedule](#major-version-schedule) + - [Pre-Release Schedule](#pre-release-schedule) + - [Patch Schedule](#patch-schedule) +4. [Content Curation](#content-curation) + - [Labels](#labels) + - [Branches](#branches) +5. [Conclusion](#conclusion) + - [Paradigm Versions](#addendum-1-paradigm-versions) + - [Failure Standards](#addendum-2-failure-standards) + +## Introduction + +### Preamble + +This document defines the structure of future Skript releases, the kinds of material included in releases and the outline of the dates on which releases will be published. + +A 'release' is the publication of a verified and signed build artefact on the GitHub releases tab, made available for all users to download and install. + +This document does *not* cover the distribution or publication of artifacts built in other ways (e.g. privately, from a nightly action) or those not published from our GitHub (e.g. test builds shared in our public testing group). + +Plans for a new release model began in March 2023 and several models were discussed, with this being the final version agreed upon by the organisation's administrative group and approved by the core contributors. + +### Motivations + +The release cycle for the `2.7.0` version was significant in that it took an unusually long time and included an unusually-large number of additions and changes. + +While it was not the first version to have taken a long time to finalise and produce, it was distinct in that a lot of time had passed since the previous public build of Skript having been marked stable. + +Members of the organisation and the wider community identified several problems that resulted from this, some of which are (not exhaustively) detailed below: +- 291 days had passed since the previous release + - Users were unable to benefit from bug fixes or new features produced during that time + - Although beta versions were released, these were marked unstable and were not fully tested +- When the release arrived it contained a very large number of changes and additions + - Some users were unaware of changes that could not be extensively documented in the changelog or were buried in a large list + - Users who obtained a build elsewhere (e.g. direct download, automatic installer) may have been unaware of the scale of the changes +- Several additions were made at short notice and without sufficient testing + - Some of these introduced problems that required fixes in a following `2.7.1` patch +- Several features could not be completed in time and had to be dropped to a future `2.8.0` version + - One result of this was that any corrections or improvements made as part of these were not present in `2.7.0` + - Aspects of some of these larger-scale changes had to be re-created or cherry-picked for the `2.7.0` version +- The release lacked a clear timetable or vision for what additions to include + - The initial timetable was not adhered to; plans were made for pre-releases to begin at the end of November 2022, which was delayed to early December in order to accommodate a large feature PR (which was eventually dropped to `2.8.0`) + - Delays persisted, and the final release took place 7 months later in September 2023 + - There was no clear cut-off point for new features; feature pull requests were being included even up to 3 days before release + +Of these, the principle complaint is that the `2.7.0` version took a significant amount of time to finish and this had an adverse effect on the community and the wider ecosystem. + +### Goals + +Our release model has been designed to achieve the following goals: +1. To reduce the delay between finishing new features and releasing them to the public. +2. To significantly reduce the time between an issue being fixed and that fix being made public in a stable build. +3. To reduce the risk of untested changes going into a release. +4. To make the release timetable clear and accessible. +5. To prevent a version being indefinitely delayed to accommodate additional changes. + +### Non-Goals + +This release model is not intended to change any of the following: +- The content or feature-theme of a particular version. +- The process for reviewing or approving changes. + +## Release Types + +This section details the different categories of version for release. + +The versioning will follow the form `A.B.C`, where `B` is a [feature version](#Feature-Releases) containing changes and additions, `C` is a [patch version](#Patch-Releases) containing only issue fixes, and `A` is reserved for major paradigmatic changes. + +### Feature Releases +A 'feature' version (labelled `0.X.0`) may contain: +- Additions to the language (syntax, grammar, structures) +- Bug fixes +- Developer API additions and changes +- Breaking changes<sup>1</sup> + +All content added to a feature version must pass through the typical review process. +Content must also have been included in a prior [pre-release](#Pre-Releases) for public testing. + +> <sup>1</sup> Breaking changes are to be avoided where possible but may be necessary, such as in a case where a significant improvement could be made to an existing feature but only by changing its structure somehow. +> Such changes should be clearly labelled and well documented, preferably giving users ample notice. + +### Patch Releases +A 'patch' version (labelled `0.0.X`) may contain: +- Bug fixes +- Non-impactful<sup>2</sup> improvements to existing features +- Changes to meta content (e.g. documentation) + +There may be **very rare** occasions when a breaking change is necessary in a patch release. These may occur if and only if: either a breaking change is required in order to fix an issue, and the issue is significant enough to need fixing in a patch rather than waiting for a major release, or an issue occurred with an inclusion in the version immediately-prior to this, which must be changed or reverted in some way. + +All content added to a patch version must pass through the typical review process. + +> <sup>2</sup> A non-impactful change is one in which there is no apparent difference to the user in how a feature is employed or what it does but that may have a material difference in areas such as performance, efficiency or machine resource usage. + +### Pre-Releases + +A 'pre-release' version (labelled `0.X.0-preY`) will contain all of the content expected to be in the feature release immediately following this. + +Pre-release versions are a final opportunity for testing and getting public feedback on changes before a major release, allowing time to identify and fix any issues before the proper release, rather than needing an immediate patch. + +The content of a pre-release should be identical to the content of the upcoming release -- barring any bug fixes -- and new content should never be included after a pre-release. + +### Emergency Patch Releases + +An 'emergency patch' version will be released if a critical security vulnerability is reported that the organisation feels prevents an immediate risk to the user base, such that it cannot wait for the subsequent patch. + +An emergency patch will be labelled as another patch version (`0.0.X`). It should be noted that an emergency patch will *not* disrupt the typical timetable detailed below. + +These kinds of releases may be published immediately and do not have to go through the typical reviewing and testing process. \ +They must never include content, additions or unnecessary changes. + +The only content permitted in an emergency patch is the material needed to fix the security risk. + +The exact nature of the security vulnerability (such as the means to reproduce it) should not be included in the notes surrounding the release. + +## Timetable + +The 'clockwork' release model follows a strict monthly cycle, with versions being released on exact dates. + +A table of (expected) dates is displayed below. + +| Date | Release Type | Example Version<br>Name | +|----------|-----------------|-------------------------| +| 1st Jan | Pre-release | 0.1.0-pre1 | +| 15th Jan | Feature release | 0.1.0 | +| 1st Feb | Patch | 0.1.1 | +| 1st Mar | Patch | 0.1.2 | +| 1st Apr | Patch | 0.1.3 | +| 1st May | Patch | 0.1.4 | +| 1st Jun | Patch | 0.1.5 | +| 1st Jul | Pre-release | 0.2.0-pre1 | +| 15th Jul | Feature release | 0.2.0 | +| 1st Aug | Patch | 0.2.1 | +| 1st Sep | Patch | 0.2.2 | +| 1st Oct | Patch | 0.2.3 | +| 1st Nov | Patch | 0.2.4 | +| 1st Dec | Patch | 0.2.5 | + +An estimated 14 releases are expected per year, with 10 patches, 2 pre-releases and 2 feature-releases that immediately follow them. + +Please note that the actual number may differ from this in cases such as: +- A version requiring multiple pre-releases to correct mistakes (`0.3.0-pre1`, `0.3.0-pre2`) +- An emergency patch having to be released +- No bug fixes being prepared in a month, meaning no patch is needed + +There is no fixed timetable for the circulation of unpublished builds to the public testing group or the addon developers group. + +### Major Version Schedule + +A [feature version](#feature-releases) will be released on the **15th of January** and the **15th of July**. + +This will include all finished content from the previous 6 months that was tested in the pre-release. + +Any features, additions or changes that were *not* ready or approved at the time of the pre-release may **not** be included in the feature release [according to goal 3](#goals). \ +The feature release must **not** be delayed to accomodate content that was not ready by the deadline [according to goal 5](#goals). + +If there is no content ready at the scheduled date of a feature release, the release will be skipped and a notice published explaining this. + +### Pre-Release Schedule + +A [pre-release](#pre-releases) will be released on the **1st of January** and the **1st of July**, leaving two weeks before the following release for public testing to occur. + +This pre-release may include all finished content from the previous 6 months. + +Any features, additions or changes that have *not* passed the review/approval process by the day of the pre-release may **not** be included in the pre-release [according to goal 3](#goals). \ +The pre-release must **not** be delayed to accomodate content that was not ready by the deadline [according to goal 5](#goals). + +If there is no content ready at the scheduled date of a pre-release, the entire feature-release will be skipped and a notice published explaining this. + +If issues are found requiring a new build be produced (e.g. the build fails to load, a core feature is non-functional, a fix was made but needs additional testing) then another version of the pre-release may be published. +There is no limit on the number of pre-releases that can be published if required. + +### Patch Schedule + +A [patch](#patch-releases) will be released on the **1st** of every month (except January and July) containing any fixes prepared during the previous month(s). + +On the 1st of January and July the patch will be replaced by the pre-release. + +A patch should include all bug fixes from the previous month that have passed the review/approval process. + +Ideally, a patch build should be circulated in the public testing group prior to its release, but this is not a strict requirement. + +If there are no applicable bug fixes ready by the scheduled date of the patch then the month will be skipped and the patch will not take place. A public notice is not required to explain this. + +## Content Curation + +To help curate content on our GitHub repository we have designed a new branch model and accompanying labels for categorising contributions. + +### Labels + +We shall provide issue and pull request labels to help categorise changes to prevent contributions missing a release (or slipping into the incorrect kind of release). + +1. `patch-ready` \ + to denote a pull request that has: + - passed the review/approval process + - is of the sufficient kind to be included in a monthly patch version +2. `feature-ready` \ + to denote a pull request that has: + - passed the review/approval process + - should wait for a biannual feature release + - is not suitable to be included in a patch + +### Branches + +We shall maintain three core branches: `dev/patch`, `dev/feature` and `master`, which function vertically<sup>3</sup>. + +We may also create legacy branches where necessary. \ +As an example, if a previous release, say `2.6.4` requires an emergency security update, a branch can be made from its release tag and the patch may directly target that branch (and be released). + +We may also maintain other assorted branches for individual features, for the purpose of group work or for experimentation. These are not detailed below. + +> <sup>3</sup> Changes are always made to the 'top' (working) branch and then this is merged downwards into the more stable branch below when required. +> +> Branches are never merged upwards. + +#### Patch + +Pull requests that only address issues or are otherwise suitable for a patch release should target the **`dev/patch` branch**. These may be merged whenever appropriate (i.e. all review and testing requirements have passed). + +At the time of the patch release, the **patch branch** will be merged downwards into the **master branch**, and a release will be created from the **master branch**. + +When a feature release occurs and all branches are merged into the master branch, the patch branch will be rebased off the current master commit, effectively bringing it up to speed with the new changes. \ +As an example, when feature version 0.5.0 releases, the patch branch will be at 0.4.5 and missing the new features, so must be rebased off the current release and catch up before changes for version 0.5.1 may be merged. + +#### Feature + +Pull requests that add features, make breaking changes or are otherwise unsuitable for a patch version should target the **`dev/feature` branch**. \ +These should be merged whenever appropriate (i.e. all review and testing requirements have passed), so that testing builds can be created and circulated in the public testing group. + +The **patch branch** may be merged downwards into the **feature branch** whenever appropriate (e.g. after changes have been made to it that may affect the state of contributions targeting the feature branch). + +The feature branch should __**never**__ be merged upwards into the patch branch<sup>4</sup>. + +The feature branch is only merged downwards into the master branch directly before a full feature release (i.e. after the pre-release and testing is complete.) + +Pre-releases are made directly from the feature branch<sup>5</sup>. At the end of the pre-release testing period the feature branch can be merged downwards into the master branch in order for the full release to be made. + +> <sup>4</sup> Merges only ever occur downwards. For the patch branch to see changes from the feature branch it must be rebased onto master branch after a feature release occurs. +> +> <sup>5</sup> Merging the branch down for the pre-release would introduce potentially-buggy, untested changes to the master branch. + +#### Master + +The **`master` branch** should reflect the most recent feature release. +Pull requests should **never** directly target the master branch. Changes are made to one of the other branches (as applicable) and then that branch is merged downwards into the **master branch** only when it is time for a release. + +This means that any user building from the master branch is guaranteed to receive a safe, stable build of the quality that we would release. + +The master branch should never be merged upwards into the feature or patch branches. If these branches need to see changes from the master branch they must be rebased onto the latest master branch commit. + +## Conclusion + +It is our aim that this release model will address all of our goals and satisfy our motivations. + +Setting a strict and regular schedule ought to prevent too much time passing without a release, while also helping to prevent a single release from becoming bloated and overbearing. + +By including testing requirements and mandating public pre-releases we hope to solve the persistent issue of untested changes slipping into supposedly-stable versions. + +Finally, by scheduling regular patches we aim to reduce the time between a bug being 'fixed' by a contributor and the userbase being able to benefit from that fix. Keeping these patches as small, controlled releases allows us to mark them as 'stable' therefore letting the version reach a wider audience. + +### Addendum 1: Paradigm Versions + +Paradigmatic `X.0.0` versions were deliberately excluded from this proposal. \ +The reasoning behind this choice was that over 10 years have passed since the inception of major version`2.0.0` in 2012, the previous paradigmatic change. + +As of writing this document there are proposals and roadmaps for a version `3.0.0` but no timetable or predicted date on the horizon. + +This kind of version, were it to be released, would likely take the place of a typical feature release in the model calendar, i.e. occurring on the 15th of January or July. However, due to its potentially-significant nature it may require exceptional changes to the pre-release cycle. + +As details of such a version are neither known nor easy to predict, it has been left to the discretion of the future team to be decided when required. + +### Addendum 2: Failure Standards + +No proposal is complete without failure standards; this model can be deemed to have failed if, in two years' time: +1. The delay between finishing new features and releasing them to the public has not been reduced. +2. The delay between an issue being fixed and that fix being made public in a stable build has not been reduced. +3. Untested features are being released in 'stable' builds. +4. The release timetable is unclear or inaccessible. +5. Versions are being indefinitely delayed to accommodate additional changes. + +Additionally, if this model is considered to have put an undue burden on the core development team, to the extent that it has hampered progress in a significant and measurable way, then it can be considered to have failed. diff --git a/README.md b/README.md index e23e1a288d2..36c82e5c00f 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,8 @@ versions will be supported as soon as possible. ## Download You can find the downloads for each version with their release notes in the [releases page](https://github.com/SkriptLang/Skript/releases). +Two major feature updates are expected each year in January and July, with monthly patches occurring in between. For full details, please review our [release model](CLOCKWORK_RELEASE_MODEL.md). + ## Documentation Documentation is available [here](https://docs.skriptlang.org/) for the latest version of Skript. diff --git a/src/main/java/ch/njol/skript/command/ScriptCommand.java b/src/main/java/ch/njol/skript/command/ScriptCommand.java index fdfb1364f17..2ce33ac7152 100644 --- a/src/main/java/ch/njol/skript/command/ScriptCommand.java +++ b/src/main/java/ch/njol/skript/command/ScriptCommand.java @@ -18,44 +18,11 @@ */ package ch.njol.skript.command; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.UUID; - import ch.njol.skript.ScriptLoader; -import ch.njol.skript.config.SectionNode; -import org.skriptlang.skript.lang.script.Script; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.OfflinePlayer; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.command.PluginCommand; -import org.bukkit.command.SimpleCommandMap; -import org.bukkit.command.TabExecutor; -import org.bukkit.entity.Player; -import org.bukkit.event.Event; -import org.bukkit.help.GenericCommandHelpTopic; -import org.bukkit.help.HelpMap; -import org.bukkit.help.HelpTopic; -import org.bukkit.help.HelpTopicComparator; -import org.bukkit.help.IndexHelpTopic; -import org.bukkit.plugin.Plugin; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.SkriptConfig; import ch.njol.skript.command.Commands.CommandAliasHelpTopic; +import ch.njol.skript.config.SectionNode; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser; import ch.njol.skript.lang.Trigger; @@ -77,6 +44,39 @@ import ch.njol.skript.variables.Variables; import ch.njol.util.StringUtils; import ch.njol.util.Validate; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.PluginCommand; +import org.bukkit.command.SimpleCommandMap; +import org.bukkit.command.TabExecutor; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.help.GenericCommandHelpTopic; +import org.bukkit.help.HelpMap; +import org.bukkit.help.HelpTopic; +import org.bukkit.help.HelpTopicComparator; +import org.bukkit.help.IndexHelpTopic; +import org.bukkit.plugin.Plugin; +import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.script.Script; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; /** * This class is used for user-defined commands. @@ -274,9 +274,21 @@ public boolean execute(final CommandSender sender, final String commandLabel, fi } Runnable runnable = () -> { + // save previous last usage date to check if the execution has set the last usage date + Date previousLastUsage = null; + if (sender instanceof Player) + previousLastUsage = getLastUsage(((Player) sender).getUniqueId(), event); + + // execute the command - may modify the last usage date execute2(event, sender, commandLabel, rest); - if (sender instanceof Player && !event.isCooldownCancelled()) - setLastUsage(((Player) sender).getUniqueId(), event, new Date()); + + if (sender instanceof Player && !event.isCooldownCancelled()) { + Date lastUsage = getLastUsage(((Player) sender).getUniqueId(), event); + // check if the execution has set the last usage date + // if not, set it to the current date. if it has, we leave it alone so as not to affect the remaining/elapsed time (#5862) + if (Objects.equals(lastUsage, previousLastUsage)) + setLastUsage(((Player) sender).getUniqueId(), event, new Date()); + } }; if (Bukkit.isPrimaryThread()) { runnable.run(); @@ -516,7 +528,7 @@ public void setRemainingMilliseconds(UUID uuid, Event event, long milliseconds) assert cooldown != null; long cooldownMs = cooldown.getMilliSeconds(); if (milliseconds > cooldownMs) - throw new IllegalArgumentException("Remaining time may not be longer than the cooldown"); + milliseconds = cooldownMs; setElapsedMilliSeconds(uuid, event, cooldownMs - milliseconds); } diff --git a/src/main/java/ch/njol/skript/conditions/CondIsInfinite.java b/src/main/java/ch/njol/skript/conditions/CondIsInfinite.java index 98ed56c0570..725480bac1b 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsInfinite.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsInfinite.java @@ -31,7 +31,7 @@ @Name("Is Infinite") @Description("Checks whether potion effects are infinite.") @Examples("all of the active potion effects of the player are infinite") -@Since("INSERT VERSION") +@Since("2.7") public class CondIsInfinite extends PropertyCondition<PotionEffect> { static { diff --git a/src/main/java/ch/njol/skript/config/Config.java b/src/main/java/ch/njol/skript/config/Config.java index 6ec44491749..bcabb8859f3 100644 --- a/src/main/java/ch/njol/skript/config/Config.java +++ b/src/main/java/ch/njol/skript/config/Config.java @@ -18,9 +18,11 @@ */ package ch.njol.skript.config; +import ch.njol.skript.Skript; +import ch.njol.skript.config.validate.SectionValidator; + import java.io.ByteArrayInputStream; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; @@ -28,15 +30,13 @@ import java.lang.reflect.Modifier; import java.nio.channels.Channels; import java.nio.channels.FileChannel; +import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.Skript; -import ch.njol.skript.config.validate.SectionValidator; - /** * Represents a config file. * @@ -44,7 +44,7 @@ */ public class Config implements Comparable<Config> { - boolean simple = false; + boolean simple; /** * One level of the indentation, e.g. a tab or 4 spaces. @@ -58,8 +58,6 @@ public class Config implements Comparable<Config> { final String defaultSeparator; String separator; - String line = ""; - int level = 0; private final SectionNode main; @@ -91,11 +89,8 @@ public Config(final InputStream source, final String fileName, @Nullable final F if (Skript.logVeryHigh()) Skript.info("loading '" + fileName + "'"); - final ConfigReader r = new ConfigReader(source); - try { - main = SectionNode.load(this, r); - } finally { - r.close(); + try (ConfigReader reader = new ConfigReader(source)) { + main = SectionNode.load(this, reader); } } finally { source.close(); @@ -106,9 +101,8 @@ public Config(final InputStream source, final String fileName, final boolean sim this(source, fileName, null, simple, allowEmptySections, defaultSeparator); } - @SuppressWarnings("resource") public Config(final File file, final boolean simple, final boolean allowEmptySections, final String defaultSeparator) throws IOException { - this(new FileInputStream(file), "" + file.getName(), simple, allowEmptySections, defaultSeparator); + this(Files.newInputStream(file.toPath()), file.getName(), simple, allowEmptySections, defaultSeparator); this.file = file.toPath(); } @@ -120,7 +114,7 @@ public Config(final Path file, final boolean simple, final boolean allowEmptySec /** * For testing - * + * * @param s * @param fileName * @param simple @@ -133,7 +127,7 @@ public Config(final String s, final String fileName, final boolean simple, final } void setIndentation(final String indent) { - assert indent != null && indent.length() > 0 : indent; + assert indent != null && !indent.isEmpty() : indent; indentation = indent; indentationName = (indent.charAt(0) == ' ' ? "space" : "tab"); } @@ -156,7 +150,7 @@ public String getFileName() { /** * Saves the config to a file. - * + * * @param f The file to save to * @throws IOException If the file could not be written to. */ @@ -175,7 +169,7 @@ public void save(final File f) throws IOException { * Sets this config's values to those in the given config. * <p> * Used by Skript to import old settings into the updated config. The return value is used to not modify the config if no new options were added. - * + * * @param other * @return Whether the configs' keys differ, i.e. false == configs only differ in values, not keys. */ @@ -203,7 +197,7 @@ public File getFile() { if (file != null) { try { return file.toFile(); - } catch(Exception e) { + } catch (Exception e) { return null; // ZipPath, for example, throws undocumented exception } } @@ -235,7 +229,7 @@ public String getSaveSeparator() { /** * Splits the given path at the dot character and passes the result to {@link #get(String...)}. - * + * * @param path * @return <tt>get(path.split("\\."))</tt> */ @@ -247,7 +241,7 @@ public String getByPath(final String path) { /** * Gets an entry node's value at the designated path - * + * * @param path * @return The entry node's value at the location defined by path or null if it either doesn't exist or is not an entry. */ @@ -284,22 +278,19 @@ public boolean validate(final SectionValidator validator) { return validator.validate(getMainNode()); } - private void load(final Class<?> c, final @Nullable Object o, final String path) { - for (final Field f : c.getDeclaredFields()) { - f.setAccessible(true); - if (o != null || Modifier.isStatic(f.getModifiers())) { + private void load(final Class<?> cls, final @Nullable Object object, final String path) { + for (final Field field : cls.getDeclaredFields()) { + field.setAccessible(true); + if (object != null || Modifier.isStatic(field.getModifiers())) { try { - if (OptionSection.class.isAssignableFrom(f.getType())) { - final Object p = f.get(o); - @NonNull - final Class<?> pc = p.getClass(); - load(pc, p, path + ((OptionSection) p).key + "."); - } else if (Option.class.isAssignableFrom(f.getType())) { - ((Option<?>) f.get(o)).set(this, path); + if (OptionSection.class.isAssignableFrom(field.getType())) { + final OptionSection section = (OptionSection) field.get(object); + @NonNull final Class<?> pc = section.getClass(); + load(pc, section, path + section.key + "."); + } else if (Option.class.isAssignableFrom(field.getType())) { + ((Option<?>) field.get(object)).set(this, path); } - } catch (final IllegalArgumentException e) { - assert false; - } catch (final IllegalAccessException e) { + } catch (final IllegalArgumentException | IllegalAccessException e) { assert false; } } diff --git a/src/main/java/ch/njol/skript/effects/EffPotion.java b/src/main/java/ch/njol/skript/effects/EffPotion.java index 46abcb3ecf4..4ebc19e8d9f 100644 --- a/src/main/java/ch/njol/skript/effects/EffPotion.java +++ b/src/main/java/ch/njol/skript/effects/EffPotion.java @@ -52,7 +52,7 @@ @Since( "2.0, 2.2-dev27 (ambient and particle-less potion effects), " + "2.5 (replacing existing effect), 2.5.2 (potion effects), " + - "INSERT VERSION (icon and infinite)" + "2.7 (icon and infinite)" ) public class EffPotion extends Effect { diff --git a/src/main/java/ch/njol/skript/events/SimpleEvents.java b/src/main/java/ch/njol/skript/events/SimpleEvents.java index d16254ded31..a9b880a9f1e 100644 --- a/src/main/java/ch/njol/skript/events/SimpleEvents.java +++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java @@ -729,7 +729,7 @@ public class SimpleEvents { "\t\tsend \"You can't drag your items here!\" to player", "\t\tcancel event" ) - .since("INSERT VERSION"); + .since("2.7"); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprRandomNumber.java b/src/main/java/ch/njol/skript/expressions/ExprRandomNumber.java index d774cbd9848..ba3853ad311 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprRandomNumber.java +++ b/src/main/java/ch/njol/skript/expressions/ExprRandomNumber.java @@ -88,7 +88,7 @@ protected Number[] get(Event event) { return new Long[] {sup}; return new Long[0]; } - return new Long[] {random.nextLong(inf, sup + 1)}; + return new Long[] {inf + Math2.mod(random.nextLong(), sup - inf + 1)}; } return new Double[] {min + random.nextDouble() * (max - min)}; diff --git a/src/main/java/ch/njol/skript/structures/StructAliases.java b/src/main/java/ch/njol/skript/structures/StructAliases.java index 8d09fcf802d..cf473c63eb3 100644 --- a/src/main/java/ch/njol/skript/structures/StructAliases.java +++ b/src/main/java/ch/njol/skript/structures/StructAliases.java @@ -20,6 +20,7 @@ import ch.njol.skript.Skript; import ch.njol.skript.aliases.Aliases; +import ch.njol.skript.aliases.ScriptAliases; import ch.njol.skript.config.SectionNode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; @@ -30,6 +31,7 @@ import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.entry.EntryContainer; +import org.skriptlang.skript.lang.script.Script; import org.skriptlang.skript.lang.structure.Structure; @Name("Aliases") @@ -54,7 +56,11 @@ public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResu node.convertToEntries(0, "="); // Initialize and load script aliases - Aliases.createScriptAliases(getParser().getCurrentScript()).parser.load(node); + Script script = getParser().getCurrentScript(); + ScriptAliases scriptAliases = Aliases.getScriptAliases(script); + if (scriptAliases == null) + scriptAliases = Aliases.createScriptAliases(script); + scriptAliases.parser.load(node); return true; } diff --git a/src/main/java/ch/njol/skript/structures/StructVariables.java b/src/main/java/ch/njol/skript/structures/StructVariables.java index b6810c4812e..f3e32e19dcf 100644 --- a/src/main/java/ch/njol/skript/structures/StructVariables.java +++ b/src/main/java/ch/njol/skript/structures/StructVariables.java @@ -87,14 +87,14 @@ public static class DefaultVariables implements ScriptData { private final Deque<Map<String, Class<?>[]>> hints = new ArrayDeque<>(); private final List<NonNullPair<String, Object>> variables; + private boolean loaded; public DefaultVariables(Collection<NonNullPair<String, Object>> variables) { this.variables = ImmutableList.copyOf(variables); } - @SuppressWarnings("unchecked") public void add(String variable, Class<?>... hints) { - if (hints == null || hints.length <= 0) + if (hints == null || hints.length == 0) return; if (CollectionUtils.containsAll(hints, Object.class)) // Ignore useless type hint. return; @@ -115,7 +115,7 @@ public void exitScope() { /** * Returns the type hints of a variable. * Can be null if no type hint was saved. - * + * * @param variable The variable string of a variable. * @return type hints of a variable if found otherwise null. */ @@ -140,14 +140,24 @@ public boolean hasDefaultVariables() { public List<NonNullPair<String, Object>> getVariables() { return variables; } + + private boolean isLoaded() { + return loaded; + } } @Override public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult, EntryContainer entryContainer) { SectionNode node = entryContainer.getSource(); node.convertToEntries(0, "="); - - List<NonNullPair<String, Object>> variables = new ArrayList<>(); + List<NonNullPair<String, Object>> variables; + Script script = getParser().getCurrentScript(); + DefaultVariables existing = script.getData(DefaultVariables.class); // if the user has TWO variables: sections + if (existing != null && existing.hasDefaultVariables()) { + variables = new ArrayList<>(existing.variables); + } else { + variables = new ArrayList<>(); + } for (Node n : node) { if (!(n instanceof EntryNode)) { Skript.error("Invalid line in variables structure"); @@ -227,20 +237,26 @@ public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResu } variables.add(new NonNullPair<>(name, o)); } - getParser().getCurrentScript().addData(new DefaultVariables(variables)); + script.addData(new DefaultVariables(variables)); // we replace the previous entry return true; } @Override public boolean load() { DefaultVariables data = getParser().getCurrentScript().getData(DefaultVariables.class); + if (data == null) { // this shouldn't happen + Skript.error("Default variables data missing"); + return false; + } else if (data.isLoaded()) { + return true; + } for (NonNullPair<String, Object> pair : data.getVariables()) { String name = pair.getKey(); if (Variables.getVariable(name, null, false) != null) continue; - Variables.setVariable(name, pair.getValue(), null, false); } + data.loaded = true; return true; } @@ -248,8 +264,13 @@ public boolean load() { public void postUnload() { Script script = getParser().getCurrentScript(); DefaultVariables data = script.getData(DefaultVariables.class); - for (NonNullPair<String, Object> pair : data.getVariables()) - Variables.setVariable(pair.getKey(), null, null, false); + if (data == null) // band-aid fix for this section's behaviour being handled by a previous section + return; // see https://github.com/SkriptLang/Skript/issues/6013 + for (NonNullPair<String, Object> pair : data.getVariables()) { + String name = pair.getKey(); + if (name.contains("<") && name.contains(">")) // probably a template made by us + Variables.setVariable(pair.getKey(), null, null, false); + } script.removeData(DefaultVariables.class); } diff --git a/src/test/skript/tests/syntaxes/structures/StructAliases.sk b/src/test/skript/tests/syntaxes/structures/StructAliases.sk new file mode 100644 index 00000000000..88b1024793b --- /dev/null +++ b/src/test/skript/tests/syntaxes/structures/StructAliases.sk @@ -0,0 +1,9 @@ +aliases: + cool_dirt = dirt + +aliases: + cool_stone = stone + +test "script aliases": + assert cool_dirt is dirt with "custom dirt alias failed" + assert cool_stone is stone with "custom stone alias failed" diff --git a/src/test/skript/tests/syntaxes/structures/StructVariables.sk b/src/test/skript/tests/syntaxes/structures/StructVariables.sk new file mode 100644 index 00000000000..5de1d5c7be3 --- /dev/null +++ b/src/test/skript/tests/syntaxes/structures/StructVariables.sk @@ -0,0 +1,10 @@ +variables: + {variables_test1} = true + +variables: + {variables_test1} = false # we hope it doesn't overwrite! + {variables_test2} = true + +test "default variables": + assert {variables_test1} is true with "{variables_test1} was not true" + assert {variables_test2} is true with "{variables_test2} was not true" From 3b4c9aaf4ff76ed7c3f6f732d1d2b05191db0725 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Sun, 24 Sep 2023 04:54:58 -0600 Subject: [PATCH 466/619] Change the target branch of dependabot (#6063) Update dependabot.yml --- .github/dependabot.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 191b0e340d9..5b930003730 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,12 +1,14 @@ version: 2 updates: - package-ecosystem: "github-actions" + target-branch: "dev/patch" directory: "/" schedule: interval: "weekly" labels: - "dependencies" - package-ecosystem: "gradle" + target-branch: "dev/patch" directory: "/" schedule: interval: "weekly" From f348832af6ca0f1e6df5679bdac031b3febc4cbc Mon Sep 17 00:00:00 2001 From: Moderocky <admin@moderocky.com> Date: Sun, 24 Sep 2023 12:37:22 +0100 Subject: [PATCH 467/619] Merge branch 'dev/patch' into dev/feature (#6065) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix ExprRandomNumber using a method from Java 17 (#6022) * Fix changing remaining time of command cooldown (#6021) Update ScriptCommand.java Co-authored-by: Moderocky <admin@moderocky.com> * Bump version to 2.7.1 (#5993) Co-authored-by: Moderocky <admin@moderocky.com> * fix 3 stray INSERT VERSIONs from 2.7.0 (#6027) correct incorrect values * Fix Documentation Actions on dev/patch (#6042) * Tidy up parts of config class. (#6025) * Add Release Model Document (#6041) Add release model document Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Co-authored-by: Moderocky <admin@moderocky.com> * (Cherry Pick) Fix cast throwing if existing variable for command storage exists (#5942) (#6026) Fix cast throwing if existing variable for command storage exists (#5942) * Fix cast throwing if existing variable for command storage exists * Update src/main/java/ch/njol/skript/command/ScriptCommand.java --------- Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> * (Cherry Pick) Fix NPE with invalid attributes and clean up ExprEntityAttribute (#5978) (#6023) Fix NPE with invalid attributes and clean up ExprEntityAttribute (#5978) * Avoid NPE and clean up class * Update ExprEntityAttribute.java * Update src/main/java/ch/njol/skript/expressions/ExprEntityAttribute.java * Update src/main/java/ch/njol/skript/expressions/ExprEntityAttribute.java --------- Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> * Fix multiple aliases sections not working (#6050) * Fix error when unloading a script with multiple variables sections (#6047) * Returns the old 2.6.4 duplicate variables section behaviour. * Add an error but i don't know what it's for * Add lots of brackets to keep walrus happy :}}} * Add load tracker to prevent multiple loading. * Prevent variable data wipe, fix another bug * Support IDEs from the dark ages that don't know what a star is and think it orbits the earth or something * add a test --------- Co-authored-by: APickledWalrus <apickledwalrus@gmail.com> * Bump actions/checkout from 3 to 4 (#6029) Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... * ⚒ Disable Javadocs generation for nightly docs & improvements (#6059) * Let's see if I am good at GH actions 🤞 * ops! * Use proper docs template reference when possible * Disable nightly javadocs generation with an option Each javadoc is ~50mb, which was causing the big size of the docs! while each docs generation is ~2mb only * Fix building * Revert pull changes They are not what fixed the issue, probably the old PRs aren't syncing for some reason * Update build.gradle --------- Co-authored-by: Moderocky <admin@moderocky.com> * Change the target branch of dependabot (#6063) Update dependabot.yml * Update cleanup-docs.yml --------- Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> --- .github/dependabot.yml | 2 ++ .github/workflows/docs/generate-docs/action.yml | 16 +++++++++++++--- .github/workflows/nightly-docs.yml | 6 +++++- src/main/java/ch/njol/skript/SkriptCommand.java | 2 +- .../njol/skript/SkriptCommandTabCompleter.java | 3 ++- src/main/resources/lang/english.lang | 2 +- src/main/resources/lang/french.lang | 2 +- src/main/resources/lang/german.lang | 2 +- src/main/resources/lang/simplifiedchinese.lang | 2 +- 9 files changed, 27 insertions(+), 10 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 191b0e340d9..5b930003730 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,12 +1,14 @@ version: 2 updates: - package-ecosystem: "github-actions" + target-branch: "dev/patch" directory: "/" schedule: interval: "weekly" labels: - "dependencies" - package-ecosystem: "gradle" + target-branch: "dev/patch" directory: "/" schedule: interval: "weekly" diff --git a/.github/workflows/docs/generate-docs/action.yml b/.github/workflows/docs/generate-docs/action.yml index 84cc2153f86..e033996868e 100644 --- a/.github/workflows/docs/generate-docs/action.yml +++ b/.github/workflows/docs/generate-docs/action.yml @@ -23,6 +23,11 @@ inputs: required: false default: "*" type: string + generate_javadocs: + description: "Designates whether to generate javadocs for this nightly documentation" + required: false + default: false + type: boolean outputs: DOCS_CHANGED: @@ -41,6 +46,7 @@ runs: SKRIPT_REPO_DIR: ${{ inputs.skript_repo_dir }} IS_RELEASE: ${{ inputs.is_release }} CLEANUP_PATTERN: ${{ inputs.cleanup_pattern }} + GENERATE_JAVADOCS: ${{ inputs.generate_javadocs }} run: | replace_in_directory() { find $1 -type f -exec sed -i -e "s/$2/$3/g" {} \; @@ -55,7 +61,7 @@ runs: if [ -d "${DOCS_REPO_DIR}/docs/templates" ] then export SKRIPT_DOCS_TEMPLATE_DIR=${DOCS_REPO_DIR}/docs/templates - else + else # compatibility for older versions export SKRIPT_DOCS_TEMPLATE_DIR=${DOCS_REPO_DIR}/doc-templates fi @@ -64,12 +70,16 @@ runs: cd $SKRIPT_REPO_DIR if [[ "${IS_RELEASE}" == "true" ]]; then ./gradlew genReleaseDocs releaseJavadoc - else + elif [[ "${GENERATE_JAVADOCS}" == "true" ]]; then ./gradlew genNightlyDocs javadoc + else + ./gradlew genNightlyDocs fi if [ -d "${DOCS_OUTPUT_DIR}" ]; then - mkdir -p "${SKRIPT_DOCS_OUTPUT_DIR}/javadocs" && cp -a "./build/docs/javadoc/." "$_" + if [[ "${GENERATE_JAVADOCS}" == "true" ]]; then + mkdir -p "${SKRIPT_DOCS_OUTPUT_DIR}/javadocs" && cp -a "./build/docs/javadoc/." "$_" + fi mkdir -p "/tmp/normalized-output-docs" && cp -a "${DOCS_OUTPUT_DIR}/." "$_" mkdir -p "/tmp/normalized-generated-docs" && cp -a "${SKRIPT_DOCS_OUTPUT_DIR}/." "$_" diff --git a/.github/workflows/nightly-docs.yml b/.github/workflows/nightly-docs.yml index 0cddeb807e5..0d5cc824ad0 100644 --- a/.github/workflows/nightly-docs.yml +++ b/.github/workflows/nightly-docs.yml @@ -3,7 +3,11 @@ name: Nightly documentation on: push: branches: - - '**' + - 'dev/feature' + - 'dev/patch' + - 'enhancement/**' + - 'feature/**' + - 'fix/**' tags-ignore: - '**' diff --git a/src/main/java/ch/njol/skript/SkriptCommand.java b/src/main/java/ch/njol/skript/SkriptCommand.java index 7f01e3ff331..62261d0e426 100644 --- a/src/main/java/ch/njol/skript/SkriptCommand.java +++ b/src/main/java/ch/njol/skript/SkriptCommand.java @@ -398,7 +398,7 @@ else if (args[0].equalsIgnoreCase("info")) { else if (args[0].equalsIgnoreCase("gen-docs")) { File templateDir = Documentation.getDocsTemplateDirectory(); if (!templateDir.exists()) { - Skript.error(sender, "Cannot generate docs! Documentation templates not found at 'plugins/Skript/doc-templates/'"); + Skript.error(sender, "Cannot generate docs! Documentation templates not found at '" + Documentation.getDocsTemplateDirectory().getPath() + "'"); TestMode.docsFailed = true; return true; } diff --git a/src/main/java/ch/njol/skript/SkriptCommandTabCompleter.java b/src/main/java/ch/njol/skript/SkriptCommandTabCompleter.java index 19588312957..4cd9c445173 100644 --- a/src/main/java/ch/njol/skript/SkriptCommandTabCompleter.java +++ b/src/main/java/ch/njol/skript/SkriptCommandTabCompleter.java @@ -18,6 +18,7 @@ */ package ch.njol.skript; +import ch.njol.skript.doc.Documentation; import ch.njol.skript.test.runner.TestMode; import ch.njol.util.StringUtils; import org.bukkit.command.Command; @@ -116,7 +117,7 @@ public List<String> onTabComplete(CommandSender sender, Command command, String options.add("disable"); options.add("update"); options.add("info"); - if (new File(Skript.getInstance().getDataFolder() + "/doc-templates").exists()) + if (Documentation.getDocsTemplateDirectory().exists()) options.add("gen-docs"); if (TestMode.DEV_MODE) options.add("test"); diff --git a/src/main/resources/lang/english.lang b/src/main/resources/lang/english.lang index 17225590e3b..40db6ddb7a0 100644 --- a/src/main/resources/lang/english.lang +++ b/src/main/resources/lang/english.lang @@ -45,7 +45,7 @@ skript command: changes: Lists all changes since the current version download: Download the newest version info: Prints a message with links to Skript's aliases and documentation - gen-docs: Generates documentation using doc-templates in plugin folder + gen-docs: Generates documentation using docs/templates in plugin folder test: Used for running internal Skript tests invalid script: Can't find the script <grey>'<gold>%s<grey>'<red> in the scripts folder! diff --git a/src/main/resources/lang/french.lang b/src/main/resources/lang/french.lang index 9a548ba8587..6aa7e26342d 100644 --- a/src/main/resources/lang/french.lang +++ b/src/main/resources/lang/french.lang @@ -45,7 +45,7 @@ skript command: changes: Liste toutes les modifications apportées depuis la version actuelle download: Télécharge la dernière version info: Affiche un message contenant les liens vers les alias et la documentation de Skript - gen-docs: Génère la documentation en utilisant doc-templates dans le dossier du plugin + gen-docs: Génère la documentation en utilisant docs/templates dans le dossier du plugin test: Utilisé pour exécuter les tests Skript invalid script: Impossible de trouver le script <grey>'<gold>%s<grey>'<red> dans le dossier des scripts ! diff --git a/src/main/resources/lang/german.lang b/src/main/resources/lang/german.lang index cb9637e9dd7..0a8255f44e8 100644 --- a/src/main/resources/lang/german.lang +++ b/src/main/resources/lang/german.lang @@ -45,7 +45,7 @@ skript command: changes: Listet alle Änderungen seit der aktuellen Version auf (auf englisch) download: Lädt die neueste Version herunter info: Druckt eine Nachricht mit Links zu den Aliases und der Dokumentation von Skript. - gen-docs: Generiert Dokumentation mithilfe von doc-templates im Plugin-Ordner + gen-docs: Generiert Dokumentation mithilfe von docs/templates im Plugin-Ordner test: Wird zum Ausführen von Skript-Tests verwendet invalid script: Das Skript <grey>'<gold>%s<grey>'<red> konnte nicht gefunden werden. diff --git a/src/main/resources/lang/simplifiedchinese.lang b/src/main/resources/lang/simplifiedchinese.lang index e1229106eba..c64b007caba 100644 --- a/src/main/resources/lang/simplifiedchinese.lang +++ b/src/main/resources/lang/simplifiedchinese.lang @@ -45,7 +45,7 @@ skript command: changes: 列出自当前版本以来的所有变化 download: 下载最新的版本 info: 打印一个带有Skript的别名和文档链接的信息 - gen-docs: 使用插件文件夹中的doc-templates生成文档 + gen-docs: 使用插件文件夹中的docs/templates生成文档 test: 用于运行内部的Skript测试 invalid script: 无法在scripts文件夹中找到脚本<grey>“<gold>%s<grey>”<red>! From 3922933adb90fd386457df69fa44b2e4fd31f227 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Sun, 24 Sep 2023 18:38:56 +0300 Subject: [PATCH 468/619] =?UTF-8?q?=E2=9A=92=20Fix=20stop=20all=20sounds?= =?UTF-8?q?=20NPE=20(#6067)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/ch/njol/skript/effects/EffStopSound.java | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffStopSound.java b/src/main/java/ch/njol/skript/effects/EffStopSound.java index fe0af9ef26c..dc39449a06a 100644 --- a/src/main/java/ch/njol/skript/effects/EffStopSound.java +++ b/src/main/java/ch/njol/skript/effects/EffStopSound.java @@ -80,14 +80,9 @@ public class EffStopSound extends Effect { @SuppressWarnings("unchecked") public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { allSounds = parseResult.hasTag("all"); - if (allSounds) { - category = (Expression<SoundCategory>) exprs[0]; - players = (Expression<Player>) exprs[1]; - } else { - sounds = (Expression<String>) exprs[0]; - category = (Expression<SoundCategory>) exprs[1]; - players = (Expression<Player>) exprs[2]; - } + sounds = (Expression<String>) exprs[0]; + category = (Expression<SoundCategory>) exprs[1]; + players = (Expression<Player>) exprs[2]; return true; } From 762100f6ebb64841305276d903e23aafc42b1786 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 24 Sep 2023 23:18:21 -0600 Subject: [PATCH 469/619] Bump actions/checkout from 3 to 4 (#6069) Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> --- .github/workflows/java-17-builds.yml | 2 +- .github/workflows/java-8-builds.yml | 2 +- .github/workflows/junit-17-builds.yml | 2 +- .github/workflows/junit-8-builds.yml | 2 +- .github/workflows/nightly-docs.yml | 2 +- .github/workflows/release-docs.yml | 4 ++-- .github/workflows/repo.yml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/java-17-builds.yml b/.github/workflows/java-17-builds.yml index 67007d76d90..fa432f7f17d 100644 --- a/.github/workflows/java-17-builds.yml +++ b/.github/workflows/java-17-builds.yml @@ -12,7 +12,7 @@ jobs: if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive - name: Set up JDK 17 diff --git a/.github/workflows/java-8-builds.yml b/.github/workflows/java-8-builds.yml index adcd71fb09b..4a36e74a44b 100644 --- a/.github/workflows/java-8-builds.yml +++ b/.github/workflows/java-8-builds.yml @@ -12,7 +12,7 @@ jobs: if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive - name: Set up JDK 17 diff --git a/.github/workflows/junit-17-builds.yml b/.github/workflows/junit-17-builds.yml index eca1ebb9706..69c5b73f27a 100644 --- a/.github/workflows/junit-17-builds.yml +++ b/.github/workflows/junit-17-builds.yml @@ -12,7 +12,7 @@ jobs: if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive - name: Set up JDK 17 diff --git a/.github/workflows/junit-8-builds.yml b/.github/workflows/junit-8-builds.yml index 93971c79624..3aa98f554a7 100644 --- a/.github/workflows/junit-8-builds.yml +++ b/.github/workflows/junit-8-builds.yml @@ -12,7 +12,7 @@ jobs: if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive - name: Set up JDK 17 diff --git a/.github/workflows/nightly-docs.yml b/.github/workflows/nightly-docs.yml index 8bd3d70aaa1..0d5cc824ad0 100644 --- a/.github/workflows/nightly-docs.yml +++ b/.github/workflows/nightly-docs.yml @@ -33,7 +33,7 @@ jobs: 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@v3 + uses: actions/checkout@v4 with: submodules: recursive path: skript diff --git a/.github/workflows/release-docs.yml b/.github/workflows/release-docs.yml index a8ccb4000bb..06cb5569076 100644 --- a/.github/workflows/release-docs.yml +++ b/.github/workflows/release-docs.yml @@ -17,7 +17,7 @@ jobs: 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@v3 + uses: actions/checkout@v4 with: submodules: recursive path: skript @@ -56,7 +56,7 @@ jobs: 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@v3 + uses: actions/checkout@v4 with: submodules: recursive path: skript diff --git a/.github/workflows/repo.yml b/.github/workflows/repo.yml index d7639b98a94..3b6640cc765 100644 --- a/.github/workflows/repo.yml +++ b/.github/workflows/repo.yml @@ -8,7 +8,7 @@ jobs: publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive - name: Set up JDK 17 From 9934f23081829dc3cfaeb3c36ed2c25f56a13a59 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Sep 2023 00:06:51 -0600 Subject: [PATCH 470/619] Bump org.gradle.toolchains.foojay-resolver-convention from 0.5.0 to 0.7.0 (#6070) Bump org.gradle.toolchains.foojay-resolver-convention Bumps org.gradle.toolchains.foojay-resolver-convention from 0.5.0 to 0.7.0. --- updated-dependencies: - dependency-name: org.gradle.toolchains.foojay-resolver-convention dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> --- settings.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle b/settings.gradle index 0a8d7b2d301..e5432e8214f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,6 +1,6 @@ plugins { - id 'org.gradle.toolchains.foojay-resolver-convention' version '0.5.0' + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0' } rootProject.name = 'Skript' From 611813d01f308f711e113d0525b589c0e4297257 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Mon, 25 Sep 2023 00:45:23 -0600 Subject: [PATCH 471/619] Remove deprecated getTicks (#5307) --- .../java/ch/njol/skript/SkriptConfig.java | 2 +- .../java/ch/njol/skript/effects/Delay.java | 2 +- .../ch/njol/skript/effects/EffIgnite.java | 70 +++++++++--------- .../ch/njol/skript/effects/EffPoison.java | 2 +- .../ch/njol/skript/effects/EffPotion.java | 2 +- .../ch/njol/skript/effects/EffSendTitle.java | 6 +- .../skript/effects/IndeterminateDelay.java | 2 +- .../ch/njol/skript/events/EvtPeriodical.java | 2 +- .../skript/expressions/ExprBurnCookTime.java | 8 +-- .../skript/expressions/ExprFireTicks.java | 4 +- .../skript/expressions/ExprFreezeTicks.java | 4 +- .../expressions/ExprMaxFreezeTicks.java | 2 +- .../skript/expressions/ExprPickupDelay.java | 4 +- .../skript/expressions/ExprPotionEffect.java | 2 +- .../skript/expressions/ExprRemainingAir.java | 8 +-- .../ch/njol/skript/expressions/ExprTime.java | 2 +- .../skript/expressions/ExprTimePlayed.java | 8 +-- .../njol/skript/util/PotionEffectUtils.java | 2 +- .../java/ch/njol/skript/util/Timespan.java | 72 ++++++++++--------- .../skript/variables/VariablesStorage.java | 4 +- 20 files changed, 106 insertions(+), 102 deletions(-) diff --git a/src/main/java/ch/njol/skript/SkriptConfig.java b/src/main/java/ch/njol/skript/SkriptConfig.java index 552debddd5c..2af2518149a 100644 --- a/src/main/java/ch/njol/skript/SkriptConfig.java +++ b/src/main/java/ch/njol/skript/SkriptConfig.java @@ -89,7 +89,7 @@ public class SkriptConfig { .setter(t -> { SkriptUpdater updater = Skript.getInstance().getUpdater(); if (updater != null) - updater.setCheckFrequency(t.getTicks_i()); + updater.setCheckFrequency(t.getTicks()); }); static final Option<Integer> updaterDownloadTries = new Option<>("updater download tries", 7) .optional(true); diff --git a/src/main/java/ch/njol/skript/effects/Delay.java b/src/main/java/ch/njol/skript/effects/Delay.java index 95025faa534..e82f4255b78 100644 --- a/src/main/java/ch/njol/skript/effects/Delay.java +++ b/src/main/java/ch/njol/skript/effects/Delay.java @@ -108,7 +108,7 @@ protected TriggerItem walk(Event event) { Variables.removeLocals(event); // Clean up local vars, we may be exiting now SkriptTimings.stop(timing); // Stop timing if it was even started - }, Math.max(duration.getTicks_i(), 1)); // Minimum delay is one tick, less than it is useless! + }, Math.max(duration.getTicks(), 1)); // Minimum delay is one tick, less than it is useless! } return null; } diff --git a/src/main/java/ch/njol/skript/effects/EffIgnite.java b/src/main/java/ch/njol/skript/effects/EffIgnite.java index 83624f5d499..e8ed1fff339 100644 --- a/src/main/java/ch/njol/skript/effects/EffIgnite.java +++ b/src/main/java/ch/njol/skript/effects/EffIgnite.java @@ -36,72 +36,72 @@ import ch.njol.skript.util.Timespan; import ch.njol.util.Kleenean; -/** - * @author Peter Güttinger - */ @Name("Ignite/Extinguish") -@Description({"Lights entities on fire or extinguishes them."}) -@Examples({"ignite the player", - "extinguish the player"}) +@Description("Lights entities on fire or extinguishes them.") +@Examples({ + "ignite the player", + "extinguish the player" +}) @Since("1.4") public class EffIgnite extends Effect { + static { Skript.registerEffect(EffIgnite.class, "(ignite|set fire to) %entities% [for %-timespan%]", "(set|light) %entities% on fire [for %-timespan%]", "extinguish %entities%"); } - - private final static int DEFAULT_DURATION = 8 * 20; // default is 8 seconds for lava and fire, I didn't test other sources - - @SuppressWarnings("null") + + private static final int DEFAULT_DURATION = 8 * 20; // default is 8 seconds for lava and fire. + + @Nullable + private Expression<Timespan> duration; + private Expression<Entity> entities; private boolean ignite; - @Nullable - private Expression<Timespan> duration = null; - - @SuppressWarnings({"unchecked", "null"}) + @Override - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { entities = (Expression<Entity>) exprs[0]; ignite = exprs.length > 1; if (ignite) duration = (Expression<Timespan>) exprs[1]; return true; } - + @Override - protected void execute(final Event e) { - final int d; - if (duration != null) { - final Timespan t = duration.getSingle(e); - if (t == null) - return; - d = (int) (t.getTicks_i() >= Integer.MAX_VALUE ? Integer.MAX_VALUE : t.getTicks_i()); + protected void execute(Event event) { + int duration; + if (this.duration == null) { + duration = ignite ? DEFAULT_DURATION : 0; } else { - d = ignite ? DEFAULT_DURATION : 0; + Timespan timespan = this.duration.getSingle(event); + if (timespan == null) + return; + duration = (int) timespan.getTicks(); } - for (final Entity en : entities.getArray(e)) { - if (e instanceof EntityDamageEvent && ((EntityDamageEvent) e).getEntity() == en && !Delay.isDelayed(e)) { + for (Entity entity : entities.getArray(event)) { + if (event instanceof EntityDamageEvent && ((EntityDamageEvent) event).getEntity() == entity && !Delay.isDelayed(event)) { Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.getInstance(), new Runnable() { @Override public void run() { - en.setFireTicks(d); + entity.setFireTicks(duration); } }); } else { - if (e instanceof EntityCombustEvent && ((EntityCombustEvent) e).getEntity() == en && !Delay.isDelayed(e)) - ((EntityCombustEvent) e).setCancelled(true);// can't change the duration, thus simply cancel the event (and create a new one) - en.setFireTicks(d); + if (event instanceof EntityCombustEvent && ((EntityCombustEvent) event).getEntity() == entity && !Delay.isDelayed(event)) + ((EntityCombustEvent) event).setCancelled(true);// can't change the duration, thus simply cancel the event (and create a new one) + entity.setFireTicks(duration); } } } - + @Override - public String toString(final @Nullable Event e, final boolean debug) { + public String toString(@Nullable Event event, boolean debug) { if (ignite) - return "set " + entities.toString(e, debug) + " on fire for " + (duration != null ? duration.toString(e, debug) : Timespan.fromTicks(DEFAULT_DURATION).toString()); + return "set " + entities.toString(event, debug) + " on fire for " + (duration != null ? duration.toString(event, debug) : Timespan.fromTicks(DEFAULT_DURATION).toString()); else - return "extinguish " + entities.toString(e, debug); + return "extinguish " + entities.toString(event, debug); } - + } diff --git a/src/main/java/ch/njol/skript/effects/EffPoison.java b/src/main/java/ch/njol/skript/effects/EffPoison.java index b71be158bfc..5fd2b9f6f27 100644 --- a/src/main/java/ch/njol/skript/effects/EffPoison.java +++ b/src/main/java/ch/njol/skript/effects/EffPoison.java @@ -81,7 +81,7 @@ protected void execute(final Event e) { if (!cure) { Timespan dur; int d = (int) (duration != null && (dur = duration.getSingle(e)) != null ? - (dur.getTicks_i() >= Integer.MAX_VALUE ? Integer.MAX_VALUE : dur.getTicks_i()) : DEFAULT_DURATION); + (dur.getTicks() >= Integer.MAX_VALUE ? Integer.MAX_VALUE : dur.getTicks()) : DEFAULT_DURATION); if (le.hasPotionEffect(PotionEffectType.POISON)) { for (final PotionEffect pe : le.getActivePotionEffects()) { if (pe.getType() != PotionEffectType.POISON) diff --git a/src/main/java/ch/njol/skript/effects/EffPotion.java b/src/main/java/ch/njol/skript/effects/EffPotion.java index 4ebc19e8d9f..e418c8c0523 100644 --- a/src/main/java/ch/njol/skript/effects/EffPotion.java +++ b/src/main/java/ch/njol/skript/effects/EffPotion.java @@ -125,7 +125,7 @@ protected void execute(Event event) { Timespan timespan = this.duration.getSingle(event); if (timespan == null) return; - duration = (int) Math.min(timespan.getTicks_i(), Integer.MAX_VALUE); + duration = (int) Math.min(timespan.getTicks(), Integer.MAX_VALUE); } for (LivingEntity entity : entities.getArray(event)) { for (PotionEffectType potionEffectType : potionEffectTypes) { diff --git a/src/main/java/ch/njol/skript/effects/EffSendTitle.java b/src/main/java/ch/njol/skript/effects/EffSendTitle.java index 29363bf1277..f17de3a142b 100644 --- a/src/main/java/ch/njol/skript/effects/EffSendTitle.java +++ b/src/main/java/ch/njol/skript/effects/EffSendTitle.java @@ -101,17 +101,17 @@ protected void execute(final Event e) { if (this.fadeIn != null) { Timespan t = this.fadeIn.getSingle(e); - fadeIn = t != null ? (int) t.getTicks_i() : -1; + fadeIn = t != null ? (int) t.getTicks() : -1; } if (this.stay != null) { Timespan t = this.stay.getSingle(e); - stay = t != null ? (int) t.getTicks_i() : -1; + stay = t != null ? (int) t.getTicks() : -1; } if (this.fadeOut != null) { Timespan t = this.fadeOut.getSingle(e); - fadeOut = t != null ? (int) t.getTicks_i() : -1; + fadeOut = t != null ? (int) t.getTicks() : -1; } for (Player p : recipients.getArray(e)) diff --git a/src/main/java/ch/njol/skript/effects/IndeterminateDelay.java b/src/main/java/ch/njol/skript/effects/IndeterminateDelay.java index 37dee92b1eb..c3a1cd1712c 100644 --- a/src/main/java/ch/njol/skript/effects/IndeterminateDelay.java +++ b/src/main/java/ch/njol/skript/effects/IndeterminateDelay.java @@ -57,7 +57,7 @@ protected TriggerItem walk(Event event) { Variables.setLocalVariables(event, localVars); TriggerItem.walk(next, event); - }, duration.getTicks_i()); + }, duration.getTicks()); } return null; diff --git a/src/main/java/ch/njol/skript/events/EvtPeriodical.java b/src/main/java/ch/njol/skript/events/EvtPeriodical.java index 79a7de9b051..df946200581 100644 --- a/src/main/java/ch/njol/skript/events/EvtPeriodical.java +++ b/src/main/java/ch/njol/skript/events/EvtPeriodical.java @@ -72,7 +72,7 @@ public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResu @Override public boolean postLoad() { - long ticks = period.getTicks_i(); + long ticks = period.getTicks(); if (worlds == null) { taskIDs = new int[]{ diff --git a/src/main/java/ch/njol/skript/expressions/ExprBurnCookTime.java b/src/main/java/ch/njol/skript/expressions/ExprBurnCookTime.java index 27b4ab2a3e4..4f1468c995a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprBurnCookTime.java +++ b/src/main/java/ch/njol/skript/expressions/ExprBurnCookTime.java @@ -86,13 +86,13 @@ protected Timespan[] get(Event e, Block[] source) { if (!(e instanceof FurnaceBurnEvent)) return null; - return CollectionUtils.array(Timespan.fromTicks_i(((FurnaceBurnEvent) e).getBurnTime())); + return CollectionUtils.array(Timespan.fromTicks(((FurnaceBurnEvent) e).getBurnTime())); } else { Timespan[] result = Arrays.stream(source) .filter(block -> anyFurnace.isOfType(block)) .map(furnace -> { Furnace state = (Furnace) furnace.getState(); - return Timespan.fromTicks_i(cookTime ? state.getCookTime() : state.getBurnTime()); + return Timespan.fromTicks(cookTime ? state.getCookTime() : state.getBurnTime()); }) .toArray(Timespan[]::new); assert result != null; @@ -154,7 +154,7 @@ public void change(Event e, @Nullable Object[] delta, Changer.ChangeMode mode) { return; FurnaceBurnEvent event = (FurnaceBurnEvent) e; - event.setBurnTime(value.apply(Timespan.fromTicks_i(event.getBurnTime())).getTicks()); + event.setBurnTime((int) value.apply(Timespan.fromTicks(event.getBurnTime())).getTicks()); return; } @@ -162,7 +162,7 @@ public void change(Event e, @Nullable Object[] delta, Changer.ChangeMode mode) { if (!anyFurnace.isOfType(block)) continue; Furnace furnace = (Furnace) block.getState(); - long time = value.apply(Timespan.fromTicks_i(cookTime ? furnace.getCookTime() : furnace.getBurnTime())).getTicks_i(); + long time = value.apply(Timespan.fromTicks(cookTime ? furnace.getCookTime() : furnace.getBurnTime())).getTicks(); if (cookTime) furnace.setCookTime((short) time); diff --git a/src/main/java/ch/njol/skript/expressions/ExprFireTicks.java b/src/main/java/ch/njol/skript/expressions/ExprFireTicks.java index a6c3d8ea57c..76b663b146c 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprFireTicks.java +++ b/src/main/java/ch/njol/skript/expressions/ExprFireTicks.java @@ -43,7 +43,7 @@ public class ExprFireTicks extends SimplePropertyExpression<Entity, Timespan> { @Override @Nullable public Timespan convert(Entity entity) { - return Timespan.fromTicks_i(Math.max(entity.getFireTicks(), 0)); + return Timespan.fromTicks(Math.max(entity.getFireTicks(), 0)); } @Override @@ -55,7 +55,7 @@ public Class<?>[] acceptChange(ChangeMode mode) { @Override public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { Entity[] entities = getExpr().getArray(event); - int change = delta == null ? 0 : (int) ((Timespan) delta[0]).getTicks_i(); + int change = delta == null ? 0 : (int) ((Timespan) delta[0]).getTicks(); switch (mode) { case REMOVE: change = -change; diff --git a/src/main/java/ch/njol/skript/expressions/ExprFreezeTicks.java b/src/main/java/ch/njol/skript/expressions/ExprFreezeTicks.java index 4ae246eb7c7..49b75f31ce1 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprFreezeTicks.java +++ b/src/main/java/ch/njol/skript/expressions/ExprFreezeTicks.java @@ -48,7 +48,7 @@ public class ExprFreezeTicks extends SimplePropertyExpression<Entity, Timespan> @Override @Nullable public Timespan convert(Entity entity) { - return Timespan.fromTicks_i(entity.getFreezeTicks()); + return Timespan.fromTicks(entity.getFreezeTicks()); } @Override @@ -59,7 +59,7 @@ public Class<?>[] acceptChange(ChangeMode mode) { @Override public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { - int time = delta == null ? 0 : (int) ((Timespan) delta[0]).getTicks_i(); + int time = delta == null ? 0 : (int) ((Timespan) delta[0]).getTicks(); int newTime; switch (mode) { case ADD: diff --git a/src/main/java/ch/njol/skript/expressions/ExprMaxFreezeTicks.java b/src/main/java/ch/njol/skript/expressions/ExprMaxFreezeTicks.java index 1e69a508e4a..5788bb628c0 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprMaxFreezeTicks.java +++ b/src/main/java/ch/njol/skript/expressions/ExprMaxFreezeTicks.java @@ -48,7 +48,7 @@ public class ExprMaxFreezeTicks extends SimplePropertyExpression<Entity, Timespa @Override @Nullable public Timespan convert(Entity entity) { - return Timespan.fromTicks_i(entity.getMaxFreezeTicks()); + return Timespan.fromTicks(entity.getMaxFreezeTicks()); } @Override diff --git a/src/main/java/ch/njol/skript/expressions/ExprPickupDelay.java b/src/main/java/ch/njol/skript/expressions/ExprPickupDelay.java index 674406006be..5ec516e119b 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprPickupDelay.java +++ b/src/main/java/ch/njol/skript/expressions/ExprPickupDelay.java @@ -49,7 +49,7 @@ public class ExprPickupDelay extends SimplePropertyExpression<Entity, Timespan> public Timespan convert(Entity entity) { if (!(entity instanceof Item)) return null; - return Timespan.fromTicks_i(((Item) entity).getPickupDelay()); + return Timespan.fromTicks(((Item) entity).getPickupDelay()); } @@ -70,7 +70,7 @@ public Class<?>[] acceptChange(ChangeMode mode) { @Override public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { Entity[] entities = getExpr().getArray(event); - int change = delta == null ? 0 : (int) ((Timespan) delta[0]).getTicks_i(); + int change = delta == null ? 0 : (int) ((Timespan) delta[0]).getTicks(); switch (mode) { case REMOVE: change = -change; diff --git a/src/main/java/ch/njol/skript/expressions/ExprPotionEffect.java b/src/main/java/ch/njol/skript/expressions/ExprPotionEffect.java index 1561162364b..98b9ad7ffb6 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprPotionEffect.java +++ b/src/main/java/ch/njol/skript/expressions/ExprPotionEffect.java @@ -85,7 +85,7 @@ protected PotionEffect[] get(final Event e) { if (this.timespan != null) { Timespan timespan = this.timespan.getSingle(e); if (timespan != null) - ticks = (int) timespan.getTicks_i(); + ticks = (int) timespan.getTicks(); } return new PotionEffect[]{new PotionEffect(potionEffectType, ticks, tier, ambient, particles)}; } diff --git a/src/main/java/ch/njol/skript/expressions/ExprRemainingAir.java b/src/main/java/ch/njol/skript/expressions/ExprRemainingAir.java index 45d24dedd75..57b07d4d641 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprRemainingAir.java +++ b/src/main/java/ch/njol/skript/expressions/ExprRemainingAir.java @@ -58,7 +58,7 @@ protected String getPropertyName() { @Override public Timespan convert(final LivingEntity entity) { - return Timespan.fromTicks_i(entity.getRemainingAir()); + return Timespan.fromTicks(entity.getRemainingAir()); } @Nullable @@ -72,7 +72,7 @@ public Class<?>[] acceptChange(Changer.ChangeMode mode) { public void change(Event event, @Nullable Object[] delta, Changer.ChangeMode mode) { switch (mode) { case ADD: - long ticks = ((Timespan)delta[0]).getTicks_i(); + long ticks = ((Timespan)delta[0]).getTicks(); for (LivingEntity entity : getExpr().getArray(event)) { int newTicks = entity.getRemainingAir() + (int) ticks; @@ -83,12 +83,12 @@ public void change(Event event, @Nullable Object[] delta, Changer.ChangeMode mod } break; case REMOVE: - ticks = ((Timespan)delta[0]).getTicks_i(); + ticks = ((Timespan)delta[0]).getTicks(); for (LivingEntity entity : getExpr().getArray(event)) entity.setRemainingAir(entity.getRemainingAir() - (int) ticks); break; case SET: - ticks = ((Timespan)delta[0]).getTicks_i(); + ticks = ((Timespan)delta[0]).getTicks(); // Sanitize remaining air to avoid client hangs/crashes if (ticks > 20000) // 1000 seconds ticks = 20000; diff --git a/src/main/java/ch/njol/skript/expressions/ExprTime.java b/src/main/java/ch/njol/skript/expressions/ExprTime.java index a7413fae0e6..d52e24dec45 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTime.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTime.java @@ -106,7 +106,7 @@ public void change(final Event e, final @Nullable Object[] delta, final ChangeMo assert delta != null; final Timespan ts = (Timespan) delta[0]; for (final World w : worlds) { - w.setTime(w.getTime() + mod * ts.getTicks_i()); + w.setTime(w.getTime() + mod * ts.getTicks()); } break; case DELETE: diff --git a/src/main/java/ch/njol/skript/expressions/ExprTimePlayed.java b/src/main/java/ch/njol/skript/expressions/ExprTimePlayed.java index 708b24ede15..62e00fa067d 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTimePlayed.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTimePlayed.java @@ -75,7 +75,7 @@ public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { if (delta == null) return; - long ticks = ((Timespan) delta[0]).getTicks_i(); + long ticks = ((Timespan) delta[0]).getTicks(); for (OfflinePlayer offlinePlayer : getExpr().getArray(event)) { if (!IS_OFFLINE_SUPPORTED && !offlinePlayer.isOnline()) continue; @@ -84,7 +84,7 @@ public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { if (playerTimespan == null) continue; - long playerTicks = playerTimespan.getTicks_i(); + long playerTicks = playerTimespan.getTicks(); switch (mode) { case ADD: ticks = playerTicks + ticks; @@ -115,9 +115,9 @@ protected String getPropertyName() { @Nullable private Timespan getTimePlayed(OfflinePlayer offlinePlayer) { if (IS_OFFLINE_SUPPORTED) { - return Timespan.fromTicks_i(offlinePlayer.getStatistic(Statistic.PLAY_ONE_MINUTE)); + return Timespan.fromTicks(offlinePlayer.getStatistic(Statistic.PLAY_ONE_MINUTE)); } else if (offlinePlayer.isOnline()) { - return Timespan.fromTicks_i(offlinePlayer.getPlayer().getStatistic(Statistic.PLAY_ONE_MINUTE)); + return Timespan.fromTicks(offlinePlayer.getPlayer().getStatistic(Statistic.PLAY_ONE_MINUTE)); } return null; } diff --git a/src/main/java/ch/njol/skript/util/PotionEffectUtils.java b/src/main/java/ch/njol/skript/util/PotionEffectUtils.java index c77185d8f7f..b0dd8fd7683 100644 --- a/src/main/java/ch/njol/skript/util/PotionEffectUtils.java +++ b/src/main/java/ch/njol/skript/util/PotionEffectUtils.java @@ -113,7 +113,7 @@ public static String toString(PotionEffect potionEffect) { builder.append(" of tier ").append(potionEffect.getAmplifier() + 1); if (!potionEffect.hasParticles()) builder.append(" without particles"); - builder.append(" for ").append(potionEffect.getDuration() == -1 ? "infinity" : Timespan.fromTicks_i(Math.abs(potionEffect.getDuration()))); + builder.append(" for ").append(potionEffect.getDuration() == -1 ? "infinity" : Timespan.fromTicks(Math.abs(potionEffect.getDuration()))); if (!potionEffect.hasIcon()) builder.append(" without icon"); return builder.toString(); diff --git a/src/main/java/ch/njol/skript/util/Timespan.java b/src/main/java/ch/njol/skript/util/Timespan.java index 5d2a7a0e5a9..3723fdb192f 100644 --- a/src/main/java/ch/njol/skript/util/Timespan.java +++ b/src/main/java/ch/njol/skript/util/Timespan.java @@ -22,6 +22,7 @@ import java.util.Locale; import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.ApiStatus.ScheduledForRemoval; import ch.njol.skript.Skript; import ch.njol.skript.localization.GeneralWords; @@ -32,23 +33,19 @@ import ch.njol.util.coll.CollectionUtils; import ch.njol.yggdrasil.YggdrasilSerializable; -/** - * @author Peter Güttinger - * @edited by Mirreducki. Increased maximum timespan. - */ public class Timespan implements YggdrasilSerializable, Comparable<Timespan> { // REMIND unit - private final static Noun m_tick = new Noun("time.tick"); - private final static Noun m_second = new Noun("time.second"); - private final static Noun m_minute = new Noun("time.minute"); - private final static Noun m_hour = new Noun("time.hour"); - private final static Noun m_day = new Noun("time.day"); - private final static Noun m_week = new Noun("time.week"); - private final static Noun m_month = new Noun("time.month"); - private final static Noun m_year = new Noun("time.year"); - final static Noun[] names = {m_tick, m_second, m_minute, m_hour, m_day, m_week, m_month, m_year}; - final static long[] times = {50L, 1000L, 1000L * 60L, 1000L * 60L * 60L, 1000L * 60L * 60L * 24L, 1000L * 60L * 60L * 24L * 7L, 1000L * 60L * 60L * 24L * 30L, 1000L * 60L * 60L * 24L * 365L}; - final static HashMap<String, Long> parseValues = new HashMap<>(); + private static final Noun m_tick = new Noun("time.tick"); + private static final Noun m_second = new Noun("time.second"); + private static final Noun m_minute = new Noun("time.minute"); + private static final Noun m_hour = new Noun("time.hour"); + private static final Noun m_day = new Noun("time.day"); + private static final Noun m_week = new Noun("time.week"); + private static final Noun m_month = new Noun("time.month"); + private static final Noun m_year = new Noun("time.year"); + static final Noun[] names = {m_tick, m_second, m_minute, m_hour, m_day, m_week, m_month, m_year}; + static final long[] times = {50L, 1000L, 1000L * 60L, 1000L * 60L * 60L, 1000L * 60L * 60L * 24L, 1000L * 60L * 60L * 24L * 7L, 1000L * 60L * 60L * 24L * 30L, 1000L * 60L * 60L * 24L * 365L}; + static final HashMap<String, Long> parseValues = new HashMap<>(); static { Language.addListener(new LanguageChangeListener() { @Override @@ -145,38 +142,46 @@ public Timespan(final long millis) { throw new IllegalArgumentException("millis must be >= 0"); this.millis = millis; } - + /** - * @deprecated Use fromTicks_i(long ticks) instead. Since this method limits timespan to 50 * Integer.MAX_VALUE. - * @addon I only keep this to allow for older addons to still work. / Mirre + * Builds a Timespan from the given long parameter. + * + * @param ticks The amount of Minecraft ticks to convert to a timespan. + * @return Timespan based on the provided long. */ - @Deprecated - public static Timespan fromTicks(final int ticks) { + public static Timespan fromTicks(long ticks) { return new Timespan(ticks * 50L); } - - public static Timespan fromTicks_i(final long ticks) { + + /** + * @deprecated Use {@link Timespan#fromTicks(long)} instead. Old API naming changes. + */ + @Deprecated + @ScheduledForRemoval + public static Timespan fromTicks_i(long ticks) { return new Timespan(ticks * 50L); } - + public long getMilliSeconds() { return millis; } - - public long getTicks_i() { + + /** + * @return the amount of Minecraft ticks this timespan represents. + */ + public long getTicks() { return Math.round((millis / 50.0)); } - + /** - * @deprecated Use getTicks_i() instead. Since this method limits timespan to Integer.MAX_VALUE. - * @addon I only keep this to allow for older addons to still work. / Mirre - * @Well if need the ticks because of a method that takes a int input it doesn't really matter. + * @deprecated Use getTicks() instead. Old API naming changes. */ @Deprecated - public int getTicks() { - return Math.round((millis >= Float.MAX_VALUE ? Float.MAX_VALUE : millis) / 50f); + @ScheduledForRemoval + public long getTicks_i() { + return Math.round((millis / 50.0)); } - + @Override public String toString() { return toString(millis); @@ -187,7 +192,7 @@ public String toString(final int flags) { } @SuppressWarnings("unchecked") - final static NonNullPair<Noun, Long>[] simpleValues = new NonNullPair[] { + static final NonNullPair<Noun, Long>[] simpleValues = new NonNullPair[] { new NonNullPair<>(m_day, 1000L * 60 * 60 * 24), new NonNullPair<>(m_hour, 1000L * 60 * 60), new NonNullPair<>(m_minute, 1000L * 60), @@ -198,7 +203,6 @@ public static String toString(final long millis) { return toString(millis, 0); } - @SuppressWarnings("null") public static String toString(final long millis, final int flags) { for (int i = 0; i < simpleValues.length - 1; i++) { if (millis >= simpleValues[i].getSecond()) { diff --git a/src/main/java/ch/njol/skript/variables/VariablesStorage.java b/src/main/java/ch/njol/skript/variables/VariablesStorage.java index 04fca57a0eb..4a21812f2e0 100644 --- a/src/main/java/ch/njol/skript/variables/VariablesStorage.java +++ b/src/main/java/ch/njol/skript/variables/VariablesStorage.java @@ -309,10 +309,10 @@ public final boolean load(SectionNode sectionNode) { */ public void startBackupTask(Timespan backupInterval) { // File is null or backup interval is invalid - if (file == null || backupInterval.getTicks_i() == 0) + if (file == null || backupInterval.getTicks() == 0) return; - backupTask = new Task(Skript.getInstance(), backupInterval.getTicks_i(), backupInterval.getTicks_i(), true) { + backupTask = new Task(Skript.getInstance(), backupInterval.getTicks(), backupInterval.getTicks(), true) { @Override public void run() { synchronized (connectionLock) { From 98c75ed27696e1b7e5aa57210a2e7d6b0bd61bb7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Sep 2023 00:46:31 -0600 Subject: [PATCH 472/619] Bump org.easymock:easymock from 5.1.0 to 5.2.0 (#6071) Bumps [org.easymock:easymock](https://github.com/easymock/easymock) from 5.1.0 to 5.2.0. - [Release notes](https://github.com/easymock/easymock/releases) - [Changelog](https://github.com/easymock/easymock/blob/master/ReleaseNotes.md) - [Commits](https://github.com/easymock/easymock/compare/easymock-5.1.0...easymock-5.2.0) --- updated-dependencies: - dependency-name: org.easymock:easymock dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 9a525ff1e1d..15505bcbd3e 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.1.0' + testShadow group: 'org.easymock', name: 'easymock', version: '5.2.0' } task checkAliases { From 8603b2b46875d686dd2ca8e35eb390a472965224 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Sep 2023 10:31:17 +0100 Subject: [PATCH 473/619] Bump io.papermc.paper:paper-api from 1.20.1-R0.1-SNAPSHOT to 1.20.2-R0.1-SNAPSHOT (#6072) * Bump io.papermc.paper:paper-api Bumps io.papermc.paper:paper-api from 1.20.1-R0.1-SNAPSHOT to 1.20.2-R0.1-SNAPSHOT. --- updated-dependencies: - dependency-name: io.papermc.paper:paper-api dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * Apply 1.20.2 to the test runner * Deprecate org.bukkit.util.Consumer usage in EntityData * Adapt against the Java Consumer instead of Bukkit's * Resolve existing method deprecation * Adapt against the Java Consumer instead of Bukkit's * Update developer note * Result in reflection for Bukkit Consumer * Resolve ThrownPotion Consumer * Result in reflection for Bukkit Consumer * Pretty else if * Add common reflective spawn method. * Use common spawn method in potion class. * Remove old suppression! * Whoops I forgot about the consumer * Don't need reflection import anymore :) * Thrown potion class --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Co-authored-by: TheLimeGlass <seantgrover@gmail.com> Co-authored-by: Moderocky <admin@moderocky.com> --- build.gradle | 5 +- gradle.properties | 2 +- .../ch/njol/skript/entity/EntityData.java | 157 +++++++++++++----- .../njol/skript/entity/FallingBlockData.java | 8 +- .../njol/skript/entity/ThrownPotionData.java | 20 +-- .../java/ch/njol/skript/entity/XpOrbData.java | 6 +- .../ch/njol/skript/sections/EffSecSpawn.java | 29 ++-- src/main/resources/lang/default.lang | 1 + .../{paper-1.20.1.json => paper-1.20.2.json} | 4 +- 9 files changed, 149 insertions(+), 83 deletions(-) rename src/test/skript/environments/java17/{paper-1.20.1.json => paper-1.20.2.json} (85%) diff --git a/build.gradle b/build.gradle index 15505bcbd3e..7ce7614c3b0 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ dependencies { shadow group: 'org.bstats', name: 'bstats-bukkit', version: '3.0.2' shadow group: 'net.kyori', name: 'adventure-text-serializer-bungeecord', version: '4.3.0' - implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.20.1-R0.1-SNAPSHOT' + implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.20.2-R0.1-SNAPSHOT' implementation group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.700' implementation group: 'com.google.code.findbugs', name: 'findbugs', version: '3.0.1' implementation group: 'com.sk89q.worldguard', name: 'worldguard-legacy', version: '7.0.0-SNAPSHOT' @@ -246,7 +246,7 @@ void createTestTask(String name, String desc, String environments, int javaVersi } } -def latestEnv = 'java17/paper-1.20.1.json' +def latestEnv = 'java17/paper-1.20.2.json' def latestJava = 17 def oldestJava = 8 @@ -389,7 +389,6 @@ task nightlyRelease(type: ShadowJar) { javadoc { dependsOn nightlyResources - source = sourceSets.main.allJava exclude("ch/njol/skript/conditions/**") diff --git a/gradle.properties b/gradle.properties index a8e8c966957..1d5c2fb3e73 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,5 +2,5 @@ groupid=ch.njol name=skript version=2.7.1 jarName=Skript.jar -testEnv=java17/paper-1.20.1 +testEnv=java17/paper-1.20.2 testEnvJavaVersion=17 diff --git a/src/main/java/ch/njol/skript/entity/EntityData.java b/src/main/java/ch/njol/skript/entity/EntityData.java index d49668a8af3..ae02901b769 100644 --- a/src/main/java/ch/njol/skript/entity/EntityData.java +++ b/src/main/java/ch/njol/skript/entity/EntityData.java @@ -18,6 +18,28 @@ */ package ch.njol.skript.entity; +import java.io.NotSerializableException; +import java.io.StreamCorruptedException; +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.function.Consumer; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.RegionAccessor; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.eclipse.jdt.annotation.Nullable; + import ch.njol.skript.Skript; import ch.njol.skript.SkriptAPIException; import ch.njol.skript.bukkitutil.EntityUtils; @@ -44,37 +66,40 @@ import ch.njol.util.coll.iterator.SingleItemIterator; import ch.njol.yggdrasil.Fields; import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilExtendedSerializable; -import org.bukkit.Bukkit; -import org.bukkit.Chunk; -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; -import org.bukkit.util.Consumer; -import org.eclipse.jdt.annotation.Nullable; -import java.io.NotSerializableException; -import java.io.StreamCorruptedException; -import java.lang.reflect.Array; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -/** - * @author Peter Güttinger - */ @SuppressWarnings("rawtypes") public abstract class EntityData<E extends Entity> implements SyntaxElement, YggdrasilExtendedSerializable {// TODO extended horse support, zombie villagers // REMIND unit + /* + * In 1.20.2 Spigot deprecated org.bukkit.util.Consumer. + * From the class header: "API methods which use this consumer will be remapped to Java's consumer at runtime, resulting in an error." + * But in 1.13-1.16 the only way to use a consumer was World#spawn(Location, Class, org.bukkit.util.Consumer). + */ + @Nullable + protected static Method WORLD_1_13_CONSUMER_METHOD; + protected static final boolean WORLD_1_13_CONSUMER = Skript.methodExists(World.class, "spawn", Location.class, Class.class, org.bukkit.util.Consumer.class); + + @Nullable + protected static Method WORLD_1_17_CONSUMER_METHOD; + protected static boolean WORLD_1_17_CONSUMER; + + static { + try { + if (WORLD_1_13_CONSUMER) { + WORLD_1_13_CONSUMER_METHOD = World.class.getDeclaredMethod("spawn", Location.class, Class.class, org.bukkit.util.Consumer.class); + } else if (Skript.classExists("org.bukkit.RegionAccessor")) { + if (WORLD_1_17_CONSUMER = Skript.methodExists(RegionAccessor.class, "spawn", Location.class, Class.class, org.bukkit.util.Consumer.class)) + WORLD_1_17_CONSUMER_METHOD = RegionAccessor.class.getDeclaredMethod("spawn", Location.class, Class.class, org.bukkit.util.Consumer.class); + } + } catch (NoSuchMethodException | SecurityException ignored) { /* We already checked if the method exists */ } + } + public final static String LANGUAGE_NODE = "entities"; - + public final static Message m_age_pattern = new Message(LANGUAGE_NODE + ".age pattern"); public final static Adjective m_baby = new Adjective(LANGUAGE_NODE + ".age adjectives.baby"), m_adult = new Adjective(LANGUAGE_NODE + ".age adjectives.adult"); - + // must be here to be initialised before 'new SimpleLiteral' is called in the register block below private final static List<EntityDataInfo<EntityData<?>>> infos = new ArrayList<>(); @@ -312,7 +337,7 @@ public final boolean init(final Expression<?>[] exprs, final int matchedPattern, /** * Returns the super type of this entity data, e.g. 'wolf' for 'angry wolf'. - * + * * @return The supertype of this entity data. Must not be null. */ public abstract EntityData getSuperType(); @@ -401,7 +426,7 @@ public static EntityDataInfo<?> getInfo(final String codeName) { /** * Prints errors. - * + * * @param s String with optional indefinite article at the beginning * @return The parsed entity data */ @@ -416,11 +441,10 @@ public static EntityData<?> parse(String s) { /** * Prints errors. - * + * * @param s * @return The parsed entity data */ - @SuppressWarnings("null") @Nullable public static EntityData<?> parseWithoutIndefiniteArticle(String s) { if (!REGEX_PATTERN.matcher(s).matches()) @@ -428,11 +452,6 @@ public static EntityData<?> parseWithoutIndefiniteArticle(String s) { Iterator<EntityDataInfo<EntityData<?>>> it = infos.iterator(); return SkriptParser.parseStatic(s, it, null); } - - @Nullable - public final E spawn(Location loc) { - return spawn(loc, null); - } private E apply(E entity) { if (baby.isTrue()) { @@ -444,24 +463,54 @@ private E apply(E entity) { return entity; } + /** + * Spawn this entity data at a location. + * + * @param location The {@link Location} to spawn the entity at. + * @return The Entity object that is spawned. + */ + @Nullable + public final E spawn(Location location) { + return spawn(location, (Consumer<E>) null); + } + + /** + * Spawn this entity data at a location. + * The consumer allows for modiciation to the entity before it actually gets spawned. + * <p> + * Bukkit's own {@link org.bukkit.util.Consumer} is deprecated. + * Use {@link #spawn(Location, Consumer)} + * + * @param location The {@link Location} to spawn the entity at. + * @param consumer A {@link Consumer} to apply the entity changes to. + * @return The Entity object that is spawned. + */ + @Nullable + @Deprecated + @SuppressWarnings("deprecation") + public E spawn(Location location, org.bukkit.util.@Nullable Consumer<E> consumer) { + return spawn(location, (Consumer<E>) e -> consumer.accept(e)); + } + + /** + * Spawn this entity data at a location. + * The consumer allows for modiciation to the entity before it actually gets spawned. + * + * @param location The {@link Location} to spawn the entity at. + * @param consumer A {@link Consumer} to apply the entity changes to. + * @return The Entity object that is spawned. + */ @Nullable - @SuppressWarnings("unchecked") public E spawn(Location location, @Nullable Consumer<E> consumer) { assert location != null; - try { - if (consumer != null) { - return location.getWorld().spawn(location, (Class<E>) getType(), e -> consumer.accept(apply(e))); - } else { - return apply(location.getWorld().spawn(location, getType())); - } - } catch (IllegalArgumentException e) { - if (Skript.testing()) - Skript.error("Can't spawn " + getType().getName()); - return null; + if (consumer != null) { + return EntityData.spawn(location, getType(), e -> consumer.accept(this.apply(e))); + } else { + return apply(location.getWorld().spawn(location, getType())); } } - - @SuppressWarnings({"null", "unchecked"}) + + @SuppressWarnings("unchecked") public E[] getAll(final World... worlds) { assert worlds != null && worlds.length > 0 : Arrays.toString(worlds); final List<E> list = new ArrayList<>(); @@ -598,4 +647,22 @@ protected boolean deserialize(final String s) { return false; } + @SuppressWarnings({"unchecked", "deprecation"}) + protected static <E extends Entity> @Nullable E spawn(Location location, Class<E> type, Consumer<E> consumer) { + try { + if (WORLD_1_17_CONSUMER) { + return (@Nullable E) WORLD_1_17_CONSUMER_METHOD.invoke(location.getWorld(), location, type, + (org.bukkit.util.Consumer<E>) consumer::accept); + } else if (WORLD_1_13_CONSUMER) { + return (@Nullable E) WORLD_1_13_CONSUMER_METHOD.invoke(location.getWorld(), location, type, + (org.bukkit.util.Consumer<E>) consumer::accept); + } + } catch (InvocationTargetException | IllegalAccessException e) { + if (Skript.testing()) + Skript.exception(e, "Can't spawn " + type.getName()); + return null; + } + return location.getWorld().spawn(location, type, consumer); + } + } diff --git a/src/main/java/ch/njol/skript/entity/FallingBlockData.java b/src/main/java/ch/njol/skript/entity/FallingBlockData.java index b3dcfbbcadf..2f937148fe9 100644 --- a/src/main/java/ch/njol/skript/entity/FallingBlockData.java +++ b/src/main/java/ch/njol/skript/entity/FallingBlockData.java @@ -19,12 +19,13 @@ package ch.njol.skript.entity; import java.util.Arrays; + import java.util.Iterator; +import java.util.function.Consumer; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.entity.FallingBlock; -import org.bukkit.util.Consumer; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; @@ -42,9 +43,6 @@ import org.skriptlang.skript.lang.converter.Converters; import ch.njol.util.coll.CollectionUtils; -/** - * @author Peter Güttinger - */ public class FallingBlockData extends EntityData<FallingBlock> { static { EntityData.register(FallingBlockData.class, "falling block", FallingBlock.class, "falling block"); @@ -117,11 +115,11 @@ public FallingBlock spawn(Location loc, @Nullable Consumer<FallingBlock> consume ItemType t = types == null ? new ItemType(Material.STONE) : CollectionUtils.getRandom(types); assert t != null; Material material = t.getMaterial(); - if (!material.isBlock()) { assert false : t; return null; } + FallingBlock fallingBlock = loc.getWorld().spawnFallingBlock(loc, material.createBlockData()); if (consumer != null) consumer.accept(fallingBlock); diff --git a/src/main/java/ch/njol/skript/entity/ThrownPotionData.java b/src/main/java/ch/njol/skript/entity/ThrownPotionData.java index 2f60014a310..407460d56c5 100644 --- a/src/main/java/ch/njol/skript/entity/ThrownPotionData.java +++ b/src/main/java/ch/njol/skript/entity/ThrownPotionData.java @@ -19,13 +19,13 @@ package ch.njol.skript.entity; import java.util.Arrays; +import java.util.function.Consumer; import org.bukkit.Location; import org.bukkit.entity.LingeringPotion; import org.bukkit.entity.ThrownPotion; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.util.Consumer; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; @@ -40,9 +40,6 @@ import org.skriptlang.skript.lang.converter.Converters; import ch.njol.util.coll.CollectionUtils; -/** - * @author Peter Güttinger - */ public class ThrownPotionData extends EntityData<ThrownPotion> { static { EntityData.register(ThrownPotionData.class, "thrown potion", ThrownPotion.class, "thrown potion"); @@ -103,9 +100,9 @@ protected boolean match(ThrownPotion entity) { return true; } - @SuppressWarnings({"unchecked", "rawtypes"}) @Override - public @Nullable ThrownPotion spawn(Location loc, @Nullable Consumer<ThrownPotion> consumer) { + @SuppressWarnings({"unchecked", "rawtypes"}) + public @Nullable ThrownPotion spawn(Location location, @Nullable Consumer<ThrownPotion> consumer) { ItemType t = CollectionUtils.getRandom(types); assert t != null; ItemStack i = t.getRandom(); @@ -114,11 +111,14 @@ protected boolean match(ThrownPotion entity) { Class<ThrownPotion> thrownPotionClass = (Class) (LINGER_POTION.isOfType(i) ? LINGERING_POTION_ENTITY_CLASS : ThrownPotion.class); ThrownPotion potion; - if (consumer != null) - potion = loc.getWorld().spawn(loc, thrownPotionClass, consumer); - else - potion = loc.getWorld().spawn(loc, thrownPotionClass); + if (consumer != null) { + potion = EntityData.spawn(location, thrownPotionClass, consumer); + } else { + potion = location.getWorld().spawn(location, thrownPotionClass); + } + if (potion == null) + return null; potion.setItem(i); return potion; } diff --git a/src/main/java/ch/njol/skript/entity/XpOrbData.java b/src/main/java/ch/njol/skript/entity/XpOrbData.java index 8582d513c33..69aa7d8c9f2 100644 --- a/src/main/java/ch/njol/skript/entity/XpOrbData.java +++ b/src/main/java/ch/njol/skript/entity/XpOrbData.java @@ -18,18 +18,16 @@ */ package ch.njol.skript.entity; +import java.util.function.Consumer; + import org.bukkit.Location; import org.bukkit.entity.ExperienceOrb; -import org.bukkit.util.Consumer; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.localization.ArgsMessage; -/** - * @author Peter Güttinger - */ public class XpOrbData extends EntityData<ExperienceOrb> { static { EntityData.register(XpOrbData.class, "xporb", ExperienceOrb.class, "xp-orb"); diff --git a/src/main/java/ch/njol/skript/sections/EffSecSpawn.java b/src/main/java/ch/njol/skript/sections/EffSecSpawn.java index 878fe70d1db..04e1bd2ad62 100644 --- a/src/main/java/ch/njol/skript/sections/EffSecSpawn.java +++ b/src/main/java/ch/njol/skript/sections/EffSecSpawn.java @@ -18,6 +18,17 @@ */ package ch.njol.skript.sections; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; + +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + import ch.njol.skript.Skript; import ch.njol.skript.config.SectionNode; import ch.njol.skript.doc.Description; @@ -35,16 +46,6 @@ import ch.njol.skript.util.Getter; import ch.njol.skript.variables.Variables; import ch.njol.util.Kleenean; -import org.bukkit.Location; -import org.bukkit.entity.Entity; -import org.bukkit.event.Event; -import org.bukkit.event.HandlerList; -import org.bukkit.util.Consumer; -import org.eclipse.jdt.annotation.Nullable; -import org.jetbrains.annotations.NotNull; - -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; @Name("Spawn") @Description({ @@ -93,16 +94,18 @@ public Entity get(SpawnEvent spawnEvent) { }, EventValues.TIME_NOW); } - @Nullable - public static Entity lastSpawned = null; - @SuppressWarnings("NotNullFieldNotInitialized") private Expression<Location> locations; + @SuppressWarnings("NotNullFieldNotInitialized") private Expression<EntityType> types; + @Nullable private Expression<Number> amount; + @Nullable + public static Entity lastSpawned; + @Nullable private Trigger trigger; diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 00236cd6d6b..89130449b8c 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -1910,6 +1910,7 @@ attribute types: generic_follow_range: generic follow range, follow range generic_knockback_resistance: generic knockback resistance, knockback resistance generic_luck: generic luck, luck + generic_max_absorption: generic max absorption, max absorption generic_max_health: generic max health, max health generic_movement_speed: generic movement speed, movement speed horse_jump_strength: horse jump strength diff --git a/src/test/skript/environments/java17/paper-1.20.1.json b/src/test/skript/environments/java17/paper-1.20.2.json similarity index 85% rename from src/test/skript/environments/java17/paper-1.20.1.json rename to src/test/skript/environments/java17/paper-1.20.2.json index 3a117b97397..0512ae142b0 100644 --- a/src/test/skript/environments/java17/paper-1.20.1.json +++ b/src/test/skript/environments/java17/paper-1.20.2.json @@ -1,11 +1,11 @@ { - "name": "paper-1.20.1", + "name": "paper-1.20.2", "resources": [ {"source": "server.properties.generic", "target": "server.properties"} ], "paperDownloads": [ { - "version": "1.20.1", + "version": "1.20.2", "target": "paperclip.jar" } ], From 16d47e66b4c15da6e5c68ada9aab8e6851b916ed Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Mon, 25 Sep 2023 05:16:17 -0600 Subject: [PATCH 474/619] Allow for all possible entity change block states (#5796) --- .../classes/data/BukkitEventValues.java | 30 ++++++++++-- .../skript/events/EvtEntityBlockChange.java | 46 +++++++++++++------ 2 files changed, 59 insertions(+), 17 deletions(-) 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 aa90764daa4..78c28305a49 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -56,6 +56,7 @@ import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.block.BlockState; +import org.bukkit.block.data.BlockData; import org.bukkit.command.CommandSender; import org.bukkit.entity.AbstractVillager; import org.bukkit.entity.Egg; @@ -607,14 +608,37 @@ public Entity get(final EntityTameEvent e) { return e.getEntity(); } }, 0); + // EntityChangeBlockEvent EventValues.registerEventValue(EntityChangeBlockEvent.class, Block.class, new Getter<Block, EntityChangeBlockEvent>() { @Override @Nullable - public Block get(final EntityChangeBlockEvent e) { - return e.getBlock(); + public Block get(EntityChangeBlockEvent event) { + return event.getBlock(); } - }, 0); + }, EventValues.TIME_PAST); + EventValues.registerEventValue(EntityChangeBlockEvent.class, Block.class, new Getter<Block, EntityChangeBlockEvent>() { + @Override + @Nullable + public Block get(EntityChangeBlockEvent event) { + return event.getBlock(); + } + }, EventValues.TIME_NOW); + EventValues.registerEventValue(EntityChangeBlockEvent.class, BlockData.class, new Getter<BlockData, EntityChangeBlockEvent>() { + @Override + @Nullable + public BlockData get(EntityChangeBlockEvent event) { + return event.getBlockData(); + } + }, EventValues.TIME_NOW); + EventValues.registerEventValue(EntityChangeBlockEvent.class, BlockData.class, new Getter<BlockData, EntityChangeBlockEvent>() { + @Override + @Nullable + public BlockData get(EntityChangeBlockEvent event) { + return event.getBlockData(); + } + }, EventValues.TIME_FUTURE); + // AreaEffectCloudApplyEvent EventValues.registerEventValue(AreaEffectCloudApplyEvent.class, LivingEntity[].class, new Getter<LivingEntity[], AreaEffectCloudApplyEvent>() { @Override diff --git a/src/main/java/ch/njol/skript/events/EvtEntityBlockChange.java b/src/main/java/ch/njol/skript/events/EvtEntityBlockChange.java index 517471c0dcb..358a0a5086a 100644 --- a/src/main/java/ch/njol/skript/events/EvtEntityBlockChange.java +++ b/src/main/java/ch/njol/skript/events/EvtEntityBlockChange.java @@ -20,6 +20,7 @@ import ch.njol.skript.Skript; import ch.njol.skript.bukkitutil.ItemUtils; +import ch.njol.skript.entity.EntityData; import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptEvent; import ch.njol.skript.lang.SkriptParser.ParseResult; @@ -31,17 +32,18 @@ import org.bukkit.entity.Silverfish; import org.bukkit.event.Event; import org.bukkit.event.entity.EntityChangeBlockEvent; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; import java.util.Locale; public class EvtEntityBlockChange extends SkriptEvent { - + static { Skript.registerEvent("Enderman/Sheep/Silverfish/Falling Block", EvtEntityBlockChange.class, EntityChangeBlockEvent.class, ChangeEvent.patterns) .description( "Called when an enderman places or picks up a block, a sheep eats grass, " + - "a silverfish boops into/out of a block or a falling block lands and turns into a block respectively." + "a silverfish boops into/out of a block or a falling block lands and turns into a block respectively.", + "event-block represents the old block and event-blockdata represents the new replacement that'll be applied to the block." ) .examples( "on sheep eat:", @@ -52,9 +54,9 @@ public class EvtEntityBlockChange extends SkriptEvent { "\tevent-entity is a falling dirt", "\tcancel event" ) - .since("<i>unknown</i>, 2.5.2 (falling block)"); + .since("<i>unknown</i>, 2.5.2 (falling block), INSERT VERSION (any entity support)"); } - + private enum ChangeEvent { ENDERMAN_PLACE("enderman place", event -> event.getEntity() instanceof Enderman && !ItemUtils.isAir(event.getTo())), @@ -66,12 +68,20 @@ private enum ChangeEvent { SILVERFISH_EXIT("silverfish exit", event -> event.getEntity() instanceof Silverfish && ItemUtils.isAir(event.getTo())), FALLING_BLOCK_FALLING("falling block fall[ing]", event -> event.getEntity() instanceof FallingBlock && ItemUtils.isAir(event.getTo())), - FALLING_BLOCK_LANDING("falling block land[ing]", event -> event.getEntity() instanceof FallingBlock && !ItemUtils.isAir(event.getTo())); + FALLING_BLOCK_LANDING("falling block land[ing]", event -> event.getEntity() instanceof FallingBlock && !ItemUtils.isAir(event.getTo())), - private final String pattern; + // Covers all possible entity block changes. + GENERIC("(entity|%*-entitydatas%) chang(e|ing) block[s]"); + + @Nullable private final Checker<EntityChangeBlockEvent> checker; + private final String pattern; + + ChangeEvent(String pattern) { + this(pattern, null); + } - ChangeEvent(String pattern, Checker<EntityChangeBlockEvent> checker) { + ChangeEvent(String pattern, @Nullable Checker<EntityChangeBlockEvent> checker) { this.pattern = pattern; this.checker = checker; } @@ -84,26 +94,34 @@ private enum ChangeEvent { patterns[i] = values()[i].pattern; } } - - @SuppressWarnings("NotNullFieldNotInitialized") + + @Nullable + private Literal<EntityData<?>> datas; private ChangeEvent event; @Override - public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parser) { + @SuppressWarnings("unchecked") + public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult) { event = ChangeEvent.values()[matchedPattern]; + if (event == ChangeEvent.GENERIC) + datas = (Literal<EntityData<?>>) args[0]; return true; } - + @Override public boolean check(Event event) { if (!(event instanceof EntityChangeBlockEvent)) return false; + if (datas != null && !datas.check(event, data -> data.isInstance(((EntityChangeBlockEvent) event).getEntity()))) + return false; + if (this.event.checker == null) + return true; return this.event.checker.check((EntityChangeBlockEvent) event); } - + @Override public String toString(@Nullable Event event, boolean debug) { return this.event.name().toLowerCase(Locale.ENGLISH).replace('_', ' '); } - + } From 23c8c2284dc887ee131de744cd1de6517abafc91 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Mon, 25 Sep 2023 05:21:54 -0600 Subject: [PATCH 475/619] Add chat format support for inventories (#5841) --- .../expressions/ExprChestInventory.java | 74 ++++++---- .../ch/njol/skript/expressions/ExprName.java | 138 +++++++++--------- 2 files changed, 121 insertions(+), 91 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprChestInventory.java b/src/main/java/ch/njol/skript/expressions/ExprChestInventory.java index fdcd07b63be..671a5b86f7d 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprChestInventory.java +++ b/src/main/java/ch/njol/skript/expressions/ExprChestInventory.java @@ -18,34 +18,56 @@ */ package ch.njol.skript.expressions; +import org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.eclipse.jdt.annotation.Nullable; + import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; -import ch.njol.skript.lang.SkriptParser; +import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.skript.util.chat.BungeeConverter; +import ch.njol.skript.util.chat.ChatMessages; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -import org.bukkit.Bukkit; -import org.bukkit.event.Event; -import org.bukkit.event.inventory.InventoryType; -import org.bukkit.inventory.Inventory; -import org.eclipse.jdt.annotation.Nullable; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; +import net.md_5.bungee.api.chat.BaseComponent; @Name("Custom Chest Inventory") @Description("Returns a chest inventory with the given amount of rows and the name. Use the <a href=effects.html#EffOpenInventory>open inventory</a> effect to open it.") -@Examples({"open chest inventory with 1 row named \"test\" to player", - "set {_inventory} to chest inventory with 1 row"}) -@Since("2.2-dev34") +@Examples({ + "open chest inventory with 1 row named \"test\" to player", + "", + "set {_inventory} to a chest inventory with 1 row", + "set slot 4 of {_inventory} to a diamond named \"example\"", + "open {_inventory} to player", + "", + "open chest inventory named \"<##00ff00>hex coloured title!\" with 6 rows to player", +}) +@RequiredPlugins("Paper 1.16+ (chat format)") +@Since("2.2-dev34, INSERT VERSION (chat format)") public class ExprChestInventory extends SimpleExpression<Inventory> { + @Nullable + private static BungeeComponentSerializer serializer; + static { + if (Skript.classExists("net.kyori.adventure.text.Component") && + Skript.methodExists(Bukkit.class, "createInventory", InventoryHolder.class, int.class, Component.class)) + serializer = BungeeComponentSerializer.get(); Skript.registerExpression(ExprChestInventory.class, Inventory.class, ExpressionType.COMBINED, - "[a] [new] chest inventory (named|with name) %string% [with %-number% row[s]]", - "[a] [new] chest inventory with %number% row[s] [(named|with name) %-string%]"); + "[a] [new] chest inventory (named|with name) %string% [with %-number% row[s]]", + "[a] [new] chest inventory with %number% row[s] [(named|with name) %-string%]"); } private static final String DEFAULT_CHEST_TITLE = InventoryType.CHEST.getDefaultTitle(); @@ -53,29 +75,26 @@ public class ExprChestInventory extends SimpleExpression<Inventory> { @Nullable private Expression<Number> rows; + @Nullable private Expression<String> name; - @SuppressWarnings("unchecked") @Override - public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { name = (Expression<String>) exprs[matchedPattern]; rows = (Expression<Number>) exprs[matchedPattern ^ 1]; return true; } @Override - protected Inventory[] get(Event e) { - String name = this.name != null ? this.name.getSingle(e) : DEFAULT_CHEST_TITLE; - Number rows = this.rows != null ? this.rows.getSingle(e) : DEFAULT_CHEST_ROWS; - - rows = rows == null ? DEFAULT_CHEST_ROWS : rows; - name = name == null ? DEFAULT_CHEST_TITLE : name; + protected Inventory[] get(Event event) { + String name = this.name != null ? this.name.getOptionalSingle(event).orElse(DEFAULT_CHEST_TITLE) : DEFAULT_CHEST_TITLE; + Number rows = this.rows != null ? this.rows.getOptionalSingle(event).orElse(DEFAULT_CHEST_ROWS) : DEFAULT_CHEST_ROWS; int size = rows.intValue() * 9; - if (size % 9 != 0) { + if (size % 9 != 0) size = 27; - } // Sanitize inventory size if (size < 0) @@ -83,6 +102,10 @@ protected Inventory[] get(Event e) { if (size > 54) // Too big values cause visual weirdness, or exceptions on newer server versions size = 54; + if (serializer != null) { + BaseComponent[] components = BungeeConverter.convert(ChatMessages.parseToArray(name)); + return CollectionUtils.array(Bukkit.createInventory(null, size, serializer.deserialize(components))); + } return CollectionUtils.array(Bukkit.createInventory(null, size, name)); } @@ -97,11 +120,10 @@ public Class<? extends Inventory> getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return "chest inventory named " - + (name != null ? name.toString(e, debug) : "\"" + DEFAULT_CHEST_TITLE + "\"") + - " with " - + (rows != null ? rows.toString(e, debug) : "" + DEFAULT_CHEST_ROWS + " rows"); + public String toString(@Nullable Event event, boolean debug) { + return "chest inventory named " + + (name != null ? name.toString(event, debug) : "\"" + DEFAULT_CHEST_TITLE + "\"") + + " with " + (rows != null ? rows.toString(event, debug) : "" + DEFAULT_CHEST_ROWS + " rows"); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprName.java b/src/main/java/ch/njol/skript/expressions/ExprName.java index d4b219b3fda..b6f873e0b89 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprName.java +++ b/src/main/java/ch/njol/skript/expressions/ExprName.java @@ -37,6 +37,7 @@ import org.bukkit.event.Event; import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.World; @@ -53,10 +54,16 @@ import ch.njol.skript.expressions.base.SimplePropertyExpression; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.util.chat.BungeeConverter; +import ch.njol.skript.util.chat.ChatMessages; import ch.njol.skript.util.slot.Slot; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.BaseComponent; @Name("Name / Display Name / Tab List Name") @Description({ @@ -112,21 +119,18 @@ public class ExprName extends SimplePropertyExpression<Object, String> { @Nullable - static final MethodHandle TITLE_METHOD; + private static BungeeComponentSerializer serializer; static final boolean HAS_GAMERULES; static { + // Check for Adventure API + if (Skript.classExists("net.kyori.adventure.text.Component") && + Skript.methodExists(Bukkit.class, "createInventory", InventoryHolder.class, int.class, Component.class)) + serializer = BungeeComponentSerializer.get(); HAS_GAMERULES = Skript.classExists("org.bukkit.GameRule"); register(ExprName.class, String.class, "(1¦name[s]|2¦(display|nick|chat|custom)[ ]name[s])", "offlineplayers/entities/blocks/itemtypes/inventories/slots/worlds" + (HAS_GAMERULES ? "/gamerules" : "")); register(ExprName.class, String.class, "(3¦(player|tab)[ ]list name[s])", "players"); - - // Get the old method for getting the name of an inventory. - MethodHandle _METHOD = null; - try { - _METHOD = MethodHandles.lookup().findVirtual(Inventory.class, "getTitle", MethodType.methodType(String.class)); - } catch (IllegalAccessException | NoSuchMethodException ignored) {} - TITLE_METHOD = _METHOD; } /* @@ -137,7 +141,6 @@ public class ExprName extends SimplePropertyExpression<Object, String> { private int mark; private static final ItemType AIR = Aliases.javaItemType("air"); - @SuppressWarnings("null") @Override public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { mark = parseResult.mark; @@ -151,53 +154,45 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable - public String convert(Object o) { - if (o instanceof OfflinePlayer && ((OfflinePlayer) o).isOnline()) - o = ((OfflinePlayer) o).getPlayer(); + public String convert(Object object) { + if (object instanceof OfflinePlayer && ((OfflinePlayer) object).isOnline()) + object = ((OfflinePlayer) object).getPlayer(); - if (o instanceof Player) { + if (object instanceof Player) { switch (mark) { case 1: - return ((Player) o).getName(); + return ((Player) object).getName(); case 2: - return ((Player) o).getDisplayName(); + return ((Player) object).getDisplayName(); case 3: - return ((Player) o).getPlayerListName(); + return ((Player) object).getPlayerListName(); } - } else if (o instanceof OfflinePlayer) { - return mark == 1 ? ((OfflinePlayer) o).getName() : null; - } else if (o instanceof Entity) { - return ((Entity) o).getCustomName(); - } else if (o instanceof Block) { - BlockState state = ((Block) o).getState(); + } else if (object instanceof OfflinePlayer) { + return mark == 1 ? ((OfflinePlayer) object).getName() : null; + } else if (object instanceof Entity) { + return ((Entity) object).getCustomName(); + } else if (object instanceof Block) { + BlockState state = ((Block) object).getState(); if (state instanceof Nameable) return ((Nameable) state).getCustomName(); - } else if (o instanceof ItemType) { - ItemMeta m = ((ItemType) o).getItemMeta(); + } else if (object instanceof ItemType) { + ItemMeta m = ((ItemType) object).getItemMeta(); return m.hasDisplayName() ? m.getDisplayName() : null; - } else if (o instanceof Inventory) { - if (TITLE_METHOD != null) { - try { - return (String) TITLE_METHOD.invoke(o); - } catch (Throwable ex) { - Skript.exception(ex); - return null; - } - } else { - if (!((Inventory) o).getViewers().isEmpty()) - return ((Inventory) o).getViewers().get(0).getOpenInventory().getTitle(); + } else if (object instanceof Inventory) { + Inventory inventory = (Inventory) object; + if (inventory.getViewers().isEmpty()) return null; - } - } else if (o instanceof Slot) { - ItemStack is = ((Slot) o).getItem(); + return inventory.getViewers().get(0).getOpenInventory().getTitle(); + } else if (object instanceof Slot) { + ItemStack is = ((Slot) object).getItem(); if (is != null && is.hasItemMeta()) { ItemMeta m = is.getItemMeta(); return m.hasDisplayName() ? m.getDisplayName() : null; } - } else if (o instanceof World) { - return ((World) o).getName(); - } else if (HAS_GAMERULES && o instanceof GameRule) { - return ((GameRule) o).getName(); + } else if (object instanceof World) { + return ((World) object).getName(); + } else if (HAS_GAMERULES && object instanceof GameRule) { + return ((GameRule) object).getName(); } return null; } @@ -220,37 +215,37 @@ public Class<?>[] acceptChange(ChangeMode mode) { } @Override - public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { String name = delta != null ? (String) delta[0] : null; - for (Object o : getExpr().getArray(e)) { - if (o instanceof Player) { + for (Object object : getExpr().getArray(event)) { + if (object instanceof Player) { switch (mark) { case 2: - ((Player) o).setDisplayName(name != null ? name + ChatColor.RESET : ((Player) o).getName()); + ((Player) object).setDisplayName(name != null ? name + ChatColor.RESET : ((Player) object).getName()); break; case 3: // Null check not necessary. This method will use the player's name if 'name' is null. - ((Player) o).setPlayerListName(name); + ((Player) object).setPlayerListName(name); break; } - } else if (o instanceof Entity) { - ((Entity) o).setCustomName(name); + } else if (object instanceof Entity) { + ((Entity) object).setCustomName(name); if (mark == 2 || mode == ChangeMode.RESET) // Using "display name" - ((Entity) o).setCustomNameVisible(name != null); - if (o instanceof LivingEntity) - ((LivingEntity) o).setRemoveWhenFarAway(name == null); - } else if (o instanceof Block) { - BlockState state = ((Block) o).getState(); + ((Entity) object).setCustomNameVisible(name != null); + if (object instanceof LivingEntity) + ((LivingEntity) object).setRemoveWhenFarAway(name == null); + } else if (object instanceof Block) { + BlockState state = ((Block) object).getState(); if (state instanceof Nameable) { ((Nameable) state).setCustomName(name); state.update(); } - } else if (o instanceof ItemType) { - ItemType i = (ItemType) o; + } else if (object instanceof ItemType) { + ItemType i = (ItemType) object; ItemMeta m = i.getItemMeta(); m.setDisplayName(name); i.setItemMeta(m); - } else if (o instanceof Inventory) { - Inventory inv = (Inventory) o; + } else if (object instanceof Inventory) { + Inventory inv = (Inventory) object; if (inv.getViewers().isEmpty()) return; @@ -260,19 +255,32 @@ public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { InventoryType type = inv.getType(); if (!type.isCreatable()) return; - if (name == null) - name = type.getDefaultTitle(); Inventory copy; - if (type == InventoryType.CHEST) { - copy = Bukkit.createInventory(inv.getHolder(), inv.getSize(), name); + if (serializer == null) { + if (name == null) + name = type.getDefaultTitle(); + if (type == InventoryType.CHEST) { + copy = Bukkit.createInventory(inv.getHolder(), inv.getSize(), name); + } else { + copy = Bukkit.createInventory(inv.getHolder(), type, name); + } } else { - copy = Bukkit.createInventory(inv.getHolder(), type, name); + Component component = type.defaultTitle(); + if (name != null) { + BaseComponent[] components = BungeeConverter.convert(ChatMessages.parseToArray(name)); + component = serializer.deserialize(components); + } + if (type == InventoryType.CHEST) { + copy = Bukkit.createInventory(inv.getHolder(), inv.getSize(), component); + } else { + copy = Bukkit.createInventory(inv.getHolder(), type, component); + } } copy.setContents(inv.getContents()); viewers.forEach(viewer -> viewer.openInventory(copy)); - } else if (o instanceof Slot) { - Slot s = (Slot) o; + } else if (object instanceof Slot) { + Slot s = (Slot) object; ItemStack is = s.getItem(); if (is != null && !AIR.isOfType(is)) { ItemMeta m = is.hasItemMeta() ? is.getItemMeta() : Bukkit.getItemFactory().getItemMeta(is.getType()); From 9814e79971603c3f3778640c37da860c7208b3e4 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Mon, 25 Sep 2023 05:26:50 -0600 Subject: [PATCH 476/619] Change NORMAL ExpressionType to EVENT (#5941) --- .../skript/expressions/ExprCommandSender.java | 34 ++--- .../skript/expressions/ExprDamageCause.java | 18 +-- .../ch/njol/skript/expressions/ExprEgg.java | 11 +- .../skript/expressions/ExprEvtInitiator.java | 15 +- .../expressions/ExprInventoryAction.java | 15 +- .../ch/njol/skript/expressions/ExprItem.java | 73 +++++---- .../skript/expressions/ExprQuitReason.java | 15 +- .../skript/expressions/ExprSpawnReason.java | 19 +-- .../skript/expressions/ExprTeleportCause.java | 14 +- .../base/EventValueExpression.java | 140 ++++++++++-------- .../hooks/regions/expressions/ExprRegion.java | 23 +-- .../ch/njol/skript/lang/ExpressionType.java | 26 ++-- 12 files changed, 182 insertions(+), 221 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprCommandSender.java b/src/main/java/ch/njol/skript/expressions/ExprCommandSender.java index 1060bc32f2c..076a2b8f5cd 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprCommandSender.java +++ b/src/main/java/ch/njol/skript/expressions/ExprCommandSender.java @@ -19,41 +19,35 @@ package ch.njol.skript.expressions; import org.bukkit.command.CommandSender; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Events; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.EventValueExpression; -import ch.njol.skript.lang.ExpressionType; -/** - * @author Peter Güttinger - */ @Name("Command Sender") -@Description({"The player or the console who sent a command. Mostly useful in <a href='commands'>commands</a> and <a href='events.html#command'>command events</a>.", - "If the command sender is a command block, its location can be retrieved by using %block's location%"}) -@Examples({"make the command sender execute \"/say hi!\"", - "on command:", - " log \"%executor% used command /%command% %arguments%\" to \"commands.log\""}) +@Description({ + "The player or the console who sent a command. Mostly useful in <a href='commands'>commands</a> and <a href='events.html#command'>command events</a>.", + "If the command sender is a command block, its location can be retrieved by using %block's location%" +}) +@Examples({ + "make the command sender execute \"/say hi!\"", + "", + "on command:", + "\tlog \"%executor% used command /%command% %arguments%\" to \"commands.log\"" +}) @Since("2.0") @Events("command") public class ExprCommandSender extends EventValueExpression<CommandSender> { + static { - Skript.registerExpression(ExprCommandSender.class, CommandSender.class, ExpressionType.SIMPLE, "[the] [command['s]] (sender|executor)"); + register(ExprCommandSender.class, CommandSender.class, "[command['s]] (sender|executor)"); } - + public ExprCommandSender() { super(CommandSender.class); } - - @Override - public String toString(final @Nullable Event e, final boolean debug) { - return "the command sender"; - } - + } diff --git a/src/main/java/ch/njol/skript/expressions/ExprDamageCause.java b/src/main/java/ch/njol/skript/expressions/ExprDamageCause.java index c9e8756efc3..9cd8bac4ea3 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprDamageCause.java +++ b/src/main/java/ch/njol/skript/expressions/ExprDamageCause.java @@ -18,40 +18,32 @@ */ package ch.njol.skript.expressions; -import org.bukkit.event.Event; import org.bukkit.event.entity.EntityDamageEvent.DamageCause; -import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.EventValueExpression; -import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.registrations.EventValues; @Name("Damage Cause") @Description("The <a href='./classes.html#damagecause'>damage cause</a> of a damage event. Please click on the link for more information.") @Examples("damage cause is lava, fire or burning") @Since("2.0") public class ExprDamageCause extends EventValueExpression<DamageCause> { - + static { - Skript.registerExpression(ExprDamageCause.class, DamageCause.class, ExpressionType.SIMPLE, "[the] damage (cause|type)"); + register(ExprDamageCause.class, DamageCause.class, "damage (cause|type)"); } - + public ExprDamageCause() { super(DamageCause.class); } @Override public boolean setTime(int time) { - return time != 1; // allow past and present + return time != EventValues.TIME_FUTURE; // allow past and present } - @Override - public String toString(final @Nullable Event e, final boolean debug) { - return "the damage cause"; - } - } diff --git a/src/main/java/ch/njol/skript/expressions/ExprEgg.java b/src/main/java/ch/njol/skript/expressions/ExprEgg.java index 3ca80a02e33..ae30091c46e 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprEgg.java +++ b/src/main/java/ch/njol/skript/expressions/ExprEgg.java @@ -18,17 +18,16 @@ */ package ch.njol.skript.expressions; -import ch.njol.skript.Skript; +import org.bukkit.entity.Egg; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Events; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.EventValueExpression; -import ch.njol.skript.lang.ExpressionType; -import org.bukkit.entity.Egg; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; @Name("The Egg") @Description("The egg thrown in a Player Egg Throw event.") @@ -38,7 +37,7 @@ public class ExprEgg extends EventValueExpression<Egg> { static { - Skript.registerExpression(ExprEgg.class, Egg.class, ExpressionType.SIMPLE, "[the] [thrown] egg"); + register(ExprEgg.class, Egg.class, "[thrown] egg"); } public ExprEgg() { diff --git a/src/main/java/ch/njol/skript/expressions/ExprEvtInitiator.java b/src/main/java/ch/njol/skript/expressions/ExprEvtInitiator.java index 678c53037d2..0ab07a427ba 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprEvtInitiator.java +++ b/src/main/java/ch/njol/skript/expressions/ExprEvtInitiator.java @@ -18,6 +18,9 @@ */ package ch.njol.skript.expressions; +import org.bukkit.event.inventory.InventoryMoveItemEvent; +import org.bukkit.inventory.Inventory; + import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Events; @@ -26,26 +29,22 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.EventValueExpression; import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; -import org.bukkit.event.inventory.InventoryMoveItemEvent; -import org.bukkit.inventory.Inventory; - @Name("Initiator Inventory") @Description("Returns the initiator inventory in an on <a href=\"./events.html?search=#inventory_item_move\">inventory item move</a> event.") @Examples({ - "on inventory item move:", - "\tif holder of event-initiator-inventory is a chest:", - "broadcast \"Item transport requested at %location at holder of event-initiator-inventory%...\"" + "on inventory item move:", + "\tholder of event-initiator-inventory is a chest", + "\tbroadcast \"Item transport requested at %location at holder of event-initiator-inventory%...\"" }) @Events("Inventory Item Move") @Since("INSERT VERSION") public class ExprEvtInitiator extends EventValueExpression<Inventory> { static { - Skript.registerExpression(ExprEvtInitiator.class, Inventory.class, ExpressionType.SIMPLE, "[the] [event-]initiator[( |-)inventory]"); + register(ExprEvtInitiator.class, Inventory.class, "[event-]initiator[( |-)inventory]"); } public ExprEvtInitiator() { diff --git a/src/main/java/ch/njol/skript/expressions/ExprInventoryAction.java b/src/main/java/ch/njol/skript/expressions/ExprInventoryAction.java index 0c8d13fc6f3..6b46f610f70 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprInventoryAction.java +++ b/src/main/java/ch/njol/skript/expressions/ExprInventoryAction.java @@ -18,17 +18,13 @@ */ package ch.njol.skript.expressions; -import org.bukkit.event.Event; import org.bukkit.event.inventory.InventoryAction; -import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.EventValueExpression; -import ch.njol.skript.lang.ExpressionType; @Name("Inventory Action") @Description("The <a href='./classes.html#inventoryaction'>inventory action</a> of an inventory event. Please click on the link for more information.") @@ -37,16 +33,11 @@ public class ExprInventoryAction extends EventValueExpression<InventoryAction> { static { - Skript.registerExpression(ExprInventoryAction.class, InventoryAction.class, ExpressionType.SIMPLE, "[the] inventory action"); + register(ExprInventoryAction.class, InventoryAction.class, "inventory action"); } - + public ExprInventoryAction() { super(InventoryAction.class); } - - @Override - public String toString(final @Nullable Event e, final boolean debug) { - return "the inventory action"; - } - + } diff --git a/src/main/java/ch/njol/skript/expressions/ExprItem.java b/src/main/java/ch/njol/skript/expressions/ExprItem.java index 3b0f724de70..c9a032ba516 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprItem.java +++ b/src/main/java/ch/njol/skript/expressions/ExprItem.java @@ -23,7 +23,6 @@ import org.bukkit.inventory.ItemStack; import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.Skript; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; @@ -31,35 +30,35 @@ import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.EventValueExpression; -import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.util.slot.Slot; -/** - * @author Peter Güttinger - */ @Name("Item") @Description("The item involved in an event, e.g. in a drop, dispense, pickup or craft event.") -@Examples({"on dispense:", - " item is a clock", - " set the time to 6:00"}) +@Examples({ + "on dispense:", + "\titem is a clock", + "\tset the time to 6:00" +}) @Since("<i>unknown</i> (before 2.1)") public class ExprItem extends EventValueExpression<ItemStack> { + static { - Skript.registerExpression(ExprItem.class, ItemStack.class, ExpressionType.SIMPLE, "[the] item"); + register(ExprItem.class, ItemStack.class, "item"); } - + public ExprItem() { super(ItemStack.class); } - + @Nullable private EventValueExpression<Item> item; + @Nullable private EventValueExpression<Slot> slot; - + @Override @Nullable - public Class<?>[] acceptChange(final ChangeMode mode) { + public Class<?>[] acceptChange(ChangeMode mode) { if (mode == ChangeMode.RESET) return null; item = new EventValueExpression<>(Item.class); @@ -72,49 +71,49 @@ public Class<?>[] acceptChange(final ChangeMode mode) { slot = null; return null; } - + @Override - public void change(final Event e, final @Nullable Object[] delta, final ChangeMode mode) { + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { assert mode != ChangeMode.RESET; - - final ItemType t = delta == null ? null : (ItemType) delta[0]; - final Item i = item != null ? item.getSingle(e) : null; - final Slot s = slot != null ? slot.getSingle(e) : null; - if (i == null && s == null) + ItemType itemType = delta == null ? null : (ItemType) delta[0]; + Item item = this.item != null ? this.item.getSingle(event) : null; + Slot slot = this.slot != null ? this.slot.getSingle(event) : null; + if (item == null && slot == null) return; - ItemStack is = i != null ? i.getItemStack() : s != null ? s.getItem() : null; + ItemStack itemstack = item != null ? item.getItemStack() : slot != null ? slot.getItem() : null; switch (mode) { case SET: - assert t != null; - is = t.getRandom(); + assert itemType != null; + itemstack = itemType.getRandom(); break; case ADD: case REMOVE: case REMOVE_ALL: - assert t != null; - if (t.isOfType(is)) { + assert itemType != null; + if (itemType.isOfType(itemstack)) { if (mode == ChangeMode.ADD) - is = t.addTo(is); + itemstack = itemType.addTo(itemstack); else if (mode == ChangeMode.REMOVE) - is = t.removeFrom(is); + itemstack = itemType.removeFrom(itemstack); else - is = t.removeAll(is); + itemstack = itemType.removeAll(itemstack); } break; case DELETE: - is = null; - if (i != null) - i.remove(); + itemstack = null; + if (item != null) + item.remove(); break; case RESET: assert false; } - if (i != null && is != null) - i.setItemStack(is); - else if (s != null) - s.setItem(is); - else + if (item != null && itemstack != null) { + item.setItemStack(itemstack); + } else if (slot != null) { + slot.setItem(itemstack); + } else { assert false; + } } - + } diff --git a/src/main/java/ch/njol/skript/expressions/ExprQuitReason.java b/src/main/java/ch/njol/skript/expressions/ExprQuitReason.java index 6d5fd5cf751..f8b02e4e253 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprQuitReason.java +++ b/src/main/java/ch/njol/skript/expressions/ExprQuitReason.java @@ -18,9 +18,7 @@ */ package ch.njol.skript.expressions; -import org.bukkit.event.Event; import org.bukkit.event.player.PlayerQuitEvent.QuitReason; -import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; @@ -29,7 +27,6 @@ import ch.njol.skript.doc.RequiredPlugins; import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.EventValueExpression; -import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.registrations.EventValues; @Name("Quit Reason") @@ -46,24 +43,16 @@ public class ExprQuitReason extends EventValueExpression<QuitReason> { static { if (Skript.classExists("org.bukkit.event.player.PlayerQuitEvent$QuitReason")) - Skript.registerExpression(ExprQuitReason.class, QuitReason.class, ExpressionType.SIMPLE, "[the] (quit|disconnect) (cause|reason)"); + register(ExprQuitReason.class, QuitReason.class, "(quit|disconnect) (cause|reason)"); } public ExprQuitReason() { super(QuitReason.class); } - // Allow for 'the quit reason was ...' as that's proper grammar support for this event value. @Override public boolean setTime(int time) { - if (time == EventValues.TIME_FUTURE) - return super.setTime(time); - return true; - } - - @Override - public String toString(@Nullable Event event, boolean debug) { - return "quit reason"; + return time != EventValues.TIME_FUTURE; // allow past and present } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprSpawnReason.java b/src/main/java/ch/njol/skript/expressions/ExprSpawnReason.java index 6b680059287..b4b2d8378e4 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprSpawnReason.java +++ b/src/main/java/ch/njol/skript/expressions/ExprSpawnReason.java @@ -18,35 +18,30 @@ */ package ch.njol.skript.expressions; -import org.bukkit.event.Event; import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; -import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.EventValueExpression; -import ch.njol.skript.lang.ExpressionType; -// TODO add a comparator for item types once aliases rework is done. @Name("Spawn Reason") @Description("The <a href='classes.html#spawnreason'>spawn reason</a> in a <a href='events.html#spawn'>spawn</a> event.") -@Examples({"on spawn:", - "\tspawn reason is reinforcements or breeding"}) +@Examples({ + "on spawn:", + "\tspawn reason is reinforcements or breeding", + "\tcancel event" +}) @Since("2.3") public class ExprSpawnReason extends EventValueExpression<SpawnReason> { static { - Skript.registerExpression(ExprSpawnReason.class, SpawnReason.class, ExpressionType.SIMPLE, "[the] spawn[ing] reason"); + register(ExprSpawnReason.class, SpawnReason.class, "spawn[ing] reason"); } + public ExprSpawnReason() { super(SpawnReason.class); } - @Override - public String toString(final @Nullable Event e, final boolean debug) { - return "the spawning reason"; - } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprTeleportCause.java b/src/main/java/ch/njol/skript/expressions/ExprTeleportCause.java index 3fd20798e94..537ad9d0707 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTeleportCause.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTeleportCause.java @@ -18,38 +18,30 @@ */ package ch.njol.skript.expressions; -import org.bukkit.event.Event; import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; -import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.EventValueExpression; -import ch.njol.skript.lang.ExpressionType; @Name("Teleport Cause") @Description("The <a href='classes.html#teleportcause'>teleport cause</a> within a player <a href='events.html#teleport'>teleport</a> event.") @Examples({ "on teleport:", - "\tteleport cause is nether portal, end portal or end gateway" + "\tteleport cause is nether portal, end portal or end gateway", + "\tcancel event" }) @Since("2.2-dev35") public class ExprTeleportCause extends EventValueExpression<TeleportCause> { static { - Skript.registerExpression(ExprTeleportCause.class, TeleportCause.class, ExpressionType.SIMPLE, "[the] teleport (cause|reason|type)"); + register(ExprTeleportCause.class, TeleportCause.class, "teleport (cause|reason|type)"); } public ExprTeleportCause() { super(TeleportCause.class); } - @Override - public String toString(@Nullable Event event, boolean debug) { - return "teleport cause"; - } - } diff --git a/src/main/java/ch/njol/skript/expressions/base/EventValueExpression.java b/src/main/java/ch/njol/skript/expressions/base/EventValueExpression.java index 9781eade3dd..e5bb37b2c9a 100644 --- a/src/main/java/ch/njol/skript/expressions/base/EventValueExpression.java +++ b/src/main/java/ch/njol/skript/expressions/base/EventValueExpression.java @@ -26,6 +26,7 @@ import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; +import ch.njol.skript.Skript; import ch.njol.skript.SkriptAPIException; import ch.njol.skript.classes.Changer; import ch.njol.skript.classes.Changer.ChangeMode; @@ -33,6 +34,7 @@ import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.lang.DefaultExpression; import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.skript.log.ParseLogHandler; @@ -62,15 +64,29 @@ * @see DefaultExpression */ public class EventValueExpression<T> extends SimpleExpression<T> implements DefaultExpression<T> { - - private final Class<? extends T> c; + + /** + * Registers an expression as {@link ExpressionType#EVENT} with the provided pattern. + * This also adds '[the]' to the start of the pattern. + * + * @param expression The class that represents this EventValueExpression. + * @param type The return type of the expression. + * @param pattern The pattern for this syntax. + */ + public static <T> void register(Class<? extends EventValueExpression<T>> expression, Class<T> type, String pattern) { + Skript.registerExpression(expression, type, ExpressionType.EVENT, "[the] " + pattern); + } + + private final Map<Class<? extends Event>, Getter<? extends T, ?>> getters = new HashMap<>(); + private final Class<?> componentType; + private final Class<? extends T> c; + @Nullable private Changer<? super T> changer; - private final Map<Class<? extends Event>, Getter<? extends T, ?>> getters = new HashMap<>(); private final boolean single; private final boolean exact; - + public EventValueExpression(Class<? extends T> c) { this(c, null); } @@ -97,55 +113,17 @@ public EventValueExpression(Class<? extends T> c, @Nullable Changer<? super T> c single = !c.isArray(); componentType = single ? c : c.getComponentType(); } - - @Override - @Nullable - @SuppressWarnings("unchecked") - protected T[] get(Event event) { - T value = getValue(event); - if (value == null) - return (T[]) Array.newInstance(componentType, 0); - if (single) { - T[] one = (T[]) Array.newInstance(c, 1); - one[0] = value; - return one; - } - T[] dataArray = (T[]) value; - T[] array = (T[]) Array.newInstance(componentType, dataArray.length); - System.arraycopy(dataArray, 0, array, 0, array.length); - return array; - } - @Nullable - @SuppressWarnings("unchecked") - private <E extends Event> T getValue(E event) { - if (getters.containsKey(event.getClass())) { - final Getter<? extends T, ? super E> g = (Getter<? extends T, ? super E>) getters.get(event.getClass()); - return g == null ? null : g.get(event); - } - - for (final Entry<Class<? extends Event>, Getter<? extends T, ?>> p : getters.entrySet()) { - if (p.getKey().isAssignableFrom(event.getClass())) { - getters.put(event.getClass(), p.getValue()); - return p.getValue() == null ? null : ((Getter<? extends T, ? super E>) p.getValue()).get(event); - } - } - - getters.put(event.getClass(), null); - - return null; - } - @Override public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) { if (exprs.length != 0) throw new SkriptAPIException(this.getClass().getName() + " has expressions in its pattern but does not override init(...)"); return init(); } - + @Override public boolean init() { - final ParseLogHandler log = SkriptLogger.startParseLogHandler(); + ParseLogHandler log = SkriptLogger.startParseLogHandler(); try { boolean hasValue = false; Class<? extends Event>[] events = getParser().getCurrentEvents(); @@ -179,23 +157,43 @@ public boolean init() { log.stop(); } } - + @Override + @Nullable @SuppressWarnings("unchecked") - public Class<? extends T> getReturnType() { - return (Class<? extends T>) componentType; - } - - @Override - public boolean isSingle() { - return single; + protected T[] get(Event event) { + T value = getValue(event); + if (value == null) + return (T[]) Array.newInstance(componentType, 0); + if (single) { + T[] one = (T[]) Array.newInstance(c, 1); + one[0] = value; + return one; + } + T[] dataArray = (T[]) value; + T[] array = (T[]) Array.newInstance(componentType, dataArray.length); + System.arraycopy(dataArray, 0, array, 0, array.length); + return array; } - - @Override - public String toString(@Nullable Event event, boolean debug) { - if (!debug || event == null) - return "event-" + Classes.getSuperClassInfo(componentType).getName().toString(!single); - return Classes.getDebugMessage(getValue(event)); + + @Nullable + @SuppressWarnings("unchecked") + private <E extends Event> T getValue(E event) { + if (getters.containsKey(event.getClass())) { + final Getter<? extends T, ? super E> g = (Getter<? extends T, ? super E>) getters.get(event.getClass()); + return g == null ? null : g.get(event); + } + + for (final Entry<Class<? extends Event>, Getter<? extends T, ?>> p : getters.entrySet()) { + if (p.getKey().isAssignableFrom(event.getClass())) { + getters.put(event.getClass(), p.getValue()); + return p.getValue() == null ? null : ((Getter<? extends T, ? super E>) p.getValue()).get(event); + } + } + + getters.put(event.getClass(), null); + + return null; } @Override @@ -206,14 +204,14 @@ public Class<?>[] acceptChange(ChangeMode mode) { changer = (Changer<? super T>) Classes.getSuperClassInfo(componentType).getChanger(); return changer == null ? null : changer.acceptChange(mode); } - + @Override public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { if (changer == null) throw new SkriptAPIException("The changer cannot be null"); ChangerUtils.change(changer, getArray(event), delta, mode); } - + @Override public boolean setTime(int time) { Class<? extends Event>[] events = getParser().getCurrentEvents(); @@ -240,7 +238,7 @@ public boolean setTime(int time) { } return false; } - + /** * @return true */ @@ -248,5 +246,23 @@ public boolean setTime(int time) { public boolean isDefault() { return true; } - + + @Override + public boolean isSingle() { + return single; + } + + @Override + @SuppressWarnings("unchecked") + public Class<? extends T> getReturnType() { + return (Class<? extends T>) componentType; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + if (!debug || event == null) + return "event-" + Classes.getSuperClassInfo(componentType).getName().toString(!single); + return Classes.getDebugMessage(getValue(event)); + } + } diff --git a/src/main/java/ch/njol/skript/hooks/regions/expressions/ExprRegion.java b/src/main/java/ch/njol/skript/hooks/regions/expressions/ExprRegion.java index 0cd7f735bd9..a0b1426e3ce 100644 --- a/src/main/java/ch/njol/skript/hooks/regions/expressions/ExprRegion.java +++ b/src/main/java/ch/njol/skript/hooks/regions/expressions/ExprRegion.java @@ -18,7 +18,6 @@ */ package ch.njol.skript.hooks.regions.expressions; -import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; @@ -26,13 +25,7 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.EventValueExpression; import ch.njol.skript.hooks.regions.classes.Region; -import ch.njol.skript.lang.ExpressionType; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; -/** - * @author Peter Güttinger - */ @Name("Region") @Description({ "The <a href='./classes.html#region'>region</a> involved in an event.", @@ -40,23 +33,19 @@ }) @Examples({ "on region enter:", - "\tregion is {forbidden region}", - "\tcancel the event" + "\tregion is {forbidden region}", + "\tcancel the event" }) @Since("2.1") @RequiredPlugins("Supported regions plugin") public class ExprRegion extends EventValueExpression<Region> { + static { - Skript.registerExpression(ExprRegion.class, Region.class, ExpressionType.SIMPLE, "[the] [event-]region"); + register(ExprRegion.class, Region.class, "[event-]region"); } - + public ExprRegion() { super(Region.class); } - - @Override - public String toString(final @Nullable Event e, final boolean debug) { - return "the region"; - } - + } diff --git a/src/main/java/ch/njol/skript/lang/ExpressionType.java b/src/main/java/ch/njol/skript/lang/ExpressionType.java index e639c27b3f5..7f16051ee57 100644 --- a/src/main/java/ch/njol/skript/lang/ExpressionType.java +++ b/src/main/java/ch/njol/skript/lang/ExpressionType.java @@ -18,37 +18,43 @@ */ package ch.njol.skript.lang; +import ch.njol.skript.expressions.base.EventValueExpression; +import ch.njol.skript.expressions.base.PropertyExpression; + /** * Used to define in which order to parse expressions. - * - * @author Peter Güttinger */ public enum ExpressionType { + /** * Expressions that only match simple text, e.g. "[the] player" */ SIMPLE, - + /** - * I don't know what this was used for. It will be removed or renamed in the future. + * Expressions that are related to the Event that are typically simple. + * + * @see EventValueExpression */ - @Deprecated - NORMAL, - + EVENT, + /** * Expressions that contain other expressions, e.g. "[the] distance between %location% and %location%" * * @see #PROPERTY */ COMBINED, - + /** * Property expressions, e.g. "[the] data value[s] of %items%"/"%items%'[s] data value[s]" + * + * @see PropertyExpression */ PROPERTY, - + /** - * Expressions whose pattern matches (almost) everything, e.g. "[the] [event-]<.+>" + * Expressions whose pattern matches (almost) everything. Typically when using regex. Example: "[the] [loop-]<.+>" */ PATTERN_MATCHES_EVERYTHING; + } From 7152d75ed7c22bef11797ab22d38b1f6c7ac46e9 Mon Sep 17 00:00:00 2001 From: DelayedGaming <72163224+DelayedGaming@users.noreply.github.com> Date: Mon, 25 Sep 2023 19:42:03 +0800 Subject: [PATCH 477/619] Adds PlayerStonecutterRecipeSelectEvent (#5460) --- .../classes/data/BukkitEventValues.java | 47 +++++++----- .../java/ch/njol/skript/events/EvtItem.java | 75 +++++++++++-------- 2 files changed, 71 insertions(+), 51 deletions(-) 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 78c28305a49..ace15fc3a5f 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -44,6 +44,7 @@ import com.destroystokyo.paper.event.player.PlayerArmorChangeEvent; import io.papermc.paper.event.entity.EntityMoveEvent; import io.papermc.paper.event.player.PlayerInventorySlotChangeEvent; +import io.papermc.paper.event.player.PlayerStonecutterRecipeSelectEvent; import io.papermc.paper.event.player.PlayerTradeEvent; import org.bukkit.Bukkit; import org.bukkit.Chunk; @@ -171,13 +172,14 @@ */ @SuppressWarnings("deprecation") public final class BukkitEventValues { - - public BukkitEventValues() {} + + public BukkitEventValues() { + } private static final ItemStack AIR_IS = new ItemStack(Material.AIR); static { - + // === WorldEvents === EventValues.registerEventValue(WorldEvent.class, World.class, new Getter<World, WorldEvent>() { @Override @@ -239,7 +241,7 @@ public Chunk get(final ChunkEvent e) { return e.getChunk(); } }, 0); - + // === BlockEvents === EventValues.registerEventValue(BlockEvent.class, Block.class, new Getter<Block, BlockEvent>() { @Override @@ -533,7 +535,7 @@ public DamageCause get(final EntityDeathEvent e) { }, 0); // ProjectileHitEvent // ProjectileHitEvent#getHitBlock was added in 1.11 - if(Skript.methodExists(ProjectileHitEvent.class, "getHitBlock")) + if (Skript.methodExists(ProjectileHitEvent.class, "getHitBlock")) EventValues.registerEventValue(ProjectileHitEvent.class, Block.class, new Getter<Block, ProjectileHitEvent>() { @Nullable @Override @@ -669,7 +671,7 @@ public Entity get(LightningStrikeEvent event) { return event.getLightning(); } }, 0); - + // --- PlayerEvents --- EventValues.registerEventValue(PlayerEvent.class, Player.class, new Getter<Player, PlayerEvent>() { @Override @@ -929,7 +931,7 @@ public Location get(final HangingEvent e) { return e.getEntity().getLocation(); } }, 0); - + // HangingBreakEvent EventValues.registerEventValue(HangingBreakEvent.class, Entity.class, new Getter<Entity, HangingBreakEvent>() { @Nullable @@ -948,7 +950,7 @@ public Player get(final HangingPlaceEvent e) { return e.getPlayer(); } }, 0); - + // --- VehicleEvents --- EventValues.registerEventValue(VehicleEvent.class, Vehicle.class, new Getter<Vehicle, VehicleEvent>() { @Override @@ -971,7 +973,7 @@ public LivingEntity get(final VehicleExitEvent e) { return e.getExited(); } }, 0); - + EventValues.registerEventValue(VehicleEnterEvent.class, Entity.class, new Getter<Entity, VehicleEnterEvent>() { @Nullable @Override @@ -979,7 +981,7 @@ public Entity get(VehicleEnterEvent e) { return e.getEntered(); } }, 0); - + // We could error here instead but it's preferable to not do it in this case EventValues.registerEventValue(VehicleDamageEvent.class, Entity.class, new Getter<Entity, VehicleDamageEvent>() { @Nullable @@ -988,7 +990,7 @@ public Entity get(VehicleDamageEvent e) { return e.getAttacker(); } }, 0); - + EventValues.registerEventValue(VehicleDestroyEvent.class, Entity.class, new Getter<Entity, VehicleDestroyEvent>() { @Nullable @Override @@ -996,7 +998,7 @@ public Entity get(VehicleDestroyEvent e) { return e.getAttacker(); } }, 0); - + EventValues.registerEventValue(VehicleEvent.class, Entity.class, new Getter<Entity, VehicleEvent>() { @Override @Nullable @@ -1004,8 +1006,8 @@ public Entity get(final VehicleEvent e) { return e.getVehicle().getPassenger(); } }, 0); - - + + // === CommandEvents === // PlayerCommandPreprocessEvent is a PlayerEvent EventValues.registerEventValue(ServerCommandEvent.class, CommandSender.class, new Getter<CommandSender, ServerCommandEvent>() { @@ -1060,7 +1062,7 @@ public CommandSender get(SkriptStopEvent e) { return Bukkit.getConsoleSender(); } }, 0); - + // === InventoryEvents === // InventoryClickEvent EventValues.registerEventValue(InventoryClickEvent.class, Player.class, new Getter<Player, InventoryClickEvent>() { @@ -1093,7 +1095,7 @@ public ItemStack get(final InventoryClickEvent e) { public Slot get(final InventoryClickEvent e) { Inventory invi = e.getClickedInventory(); // getInventory is WRONG and dangerous int slotIndex = e.getSlot(); - + // Not all indices point to inventory slots. Equipment, for example if (invi instanceof PlayerInventory && slotIndex >= 36) { return new ch.njol.skript.util.slot.EquipmentSlot(((PlayerInventory) invi).getHolder(), slotIndex); @@ -1692,12 +1694,12 @@ public Slot get(EntityResurrectEvent event) { if (equipment == null || hand == null) return null; return new ch.njol.skript.util.slot.EquipmentSlot(equipment, - (hand == EquipmentSlot.HAND) ? ch.njol.skript.util.slot.EquipmentSlot.EquipSlot.TOOL + (hand == EquipmentSlot.HAND) ? ch.njol.skript.util.slot.EquipmentSlot.EquipSlot.TOOL : ch.njol.skript.util.slot.EquipmentSlot.EquipSlot.OFF_HAND); } }, EventValues.TIME_NOW); - // PlayerItemHeldEvent + // PlayerItemHeldEvent EventValues.registerEventValue(PlayerItemHeldEvent.class, Slot.class, new Getter<Slot, PlayerItemHeldEvent>() { @Override @Nullable @@ -1742,6 +1744,15 @@ public QuitReason get(PlayerQuitEvent event) { } }, EventValues.TIME_NOW); + // PlayerStonecutterRecipeSelectEvent + if (Skript.classExists("io.papermc.paper.event.player.PlayerStonecutterRecipeSelectEvent")) + EventValues.registerEventValue(PlayerStonecutterRecipeSelectEvent.class, ItemStack.class, new Getter<ItemStack, PlayerStonecutterRecipeSelectEvent>() { + @Override + public ItemStack get(PlayerStonecutterRecipeSelectEvent event) { + return event.getStonecuttingRecipe().getResult(); + } + }, EventValues.TIME_NOW); + } } diff --git a/src/main/java/ch/njol/skript/events/EvtItem.java b/src/main/java/ch/njol/skript/events/EvtItem.java index b8c891f7cc2..f00ccc58d36 100644 --- a/src/main/java/ch/njol/skript/events/EvtItem.java +++ b/src/main/java/ch/njol/skript/events/EvtItem.java @@ -18,8 +18,7 @@ */ package ch.njol.skript.events; -import ch.njol.skript.lang.util.SimpleEvent; -import ch.njol.skript.sections.EffSecSpawn; +import io.papermc.paper.event.player.PlayerStonecutterRecipeSelectEvent; import org.bukkit.event.Event; import org.bukkit.event.block.BlockDispenseEvent; import org.bukkit.event.entity.EntityDropItemEvent; @@ -38,12 +37,13 @@ import org.bukkit.inventory.Recipe; import org.eclipse.jdt.annotation.Nullable; +import ch.njol.skript.sections.EffSecSpawn; import ch.njol.skript.Skript; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptEvent; import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.util.Checker; +import ch.njol.skript.lang.util.SimpleEvent; import ch.njol.util.coll.CollectionUtils; @SuppressWarnings("deprecation") @@ -52,7 +52,8 @@ public class EvtItem extends SkriptEvent { private final static boolean hasConsumeEvent = Skript.classExists("org.bukkit.event.player.PlayerItemConsumeEvent"); private final static boolean hasPrepareCraftEvent = Skript.classExists("org.bukkit.event.inventory.PrepareItemCraftEvent"); private final static boolean hasEntityPickupItemEvent = Skript.classExists("org.bukkit.event.entity.EntityPickupItemEvent"); - + private final static boolean HAS_PLAYER_STONECUTTER_RECIPE_SELECT_EVENT = Skript.classExists("io.papermc.paper.event.player.PlayerStonecutterRecipeSelectEvent"); + static { Skript.registerEvent("Dispense", EvtItem.class, BlockDispenseEvent.class, "dispens(e|ing) [[of] %-itemtypes%]") .description("Called when a dispenser dispenses an item.") @@ -140,22 +141,35 @@ public class EvtItem extends SkriptEvent { "\tbroadcast \"%holder of past event-inventory% is transporting %event-item% to %holder of event-inventory%!\"" ) .since("INSERT VERSION"); + if (HAS_PLAYER_STONECUTTER_RECIPE_SELECT_EVENT) { + Skript.registerEvent("Stonecutter Recipe Select", EvtItem.class, PlayerStonecutterRecipeSelectEvent.class, "stonecutting [[of] %-itemtypes%]") + .description("Called when a player selects a recipe in a stonecutter.") + .examples( + "on stonecutting stone slabs", + "\tcancel the event", + "", + "on stonecutting:", + "\tbroadcast \"%player% is using stonecutter to craft %event-item%!\"" + ) + .since("INSERT VERSION") + .requiredPlugins("Paper 1.16+"); + } } @Nullable private Literal<ItemType> types; private boolean entity; - - @SuppressWarnings("unchecked") + @Override + @SuppressWarnings("unchecked") public boolean init(final Literal<?>[] args, final int matchedPattern, final ParseResult parser) { types = (Literal<ItemType>) args[0]; entity = parser.mark == 1; return true; } - - @SuppressWarnings("null") + @Override + @SuppressWarnings("null") public boolean check(final Event event) { if (event instanceof ItemSpawnEvent) // To make 'last dropped item' possible. EffSecSpawn.lastSpawned = ((ItemSpawnEvent) event).getEntity(); @@ -165,59 +179,54 @@ public boolean check(final Event event) { return false; if (types == null) return true; - final ItemStack is; + final ItemStack itemStack; if (event instanceof BlockDispenseEvent) { - is = ((BlockDispenseEvent) event).getItem(); + itemStack = ((BlockDispenseEvent) event).getItem(); } else if (event instanceof ItemSpawnEvent) { - is = ((ItemSpawnEvent) event).getEntity().getItemStack(); + itemStack = ((ItemSpawnEvent) event).getEntity().getItemStack(); } else if (event instanceof PlayerDropItemEvent) { - is = ((PlayerDropItemEvent) event).getItemDrop().getItemStack(); + itemStack = ((PlayerDropItemEvent) event).getItemDrop().getItemStack(); } else if (event instanceof EntityDropItemEvent) { - is = ((EntityDropItemEvent) event).getItemDrop().getItemStack(); + itemStack = ((EntityDropItemEvent) event).getItemDrop().getItemStack(); } else if (event instanceof CraftItemEvent) { - is = ((CraftItemEvent) event).getRecipe().getResult(); + itemStack = ((CraftItemEvent) event).getRecipe().getResult(); } else if (hasPrepareCraftEvent && event instanceof PrepareItemCraftEvent) { Recipe recipe = ((PrepareItemCraftEvent) event).getRecipe(); if (recipe != null) { - is = recipe.getResult(); + itemStack = recipe.getResult(); } else { return false; } + } else if (HAS_PLAYER_STONECUTTER_RECIPE_SELECT_EVENT && event instanceof PlayerStonecutterRecipeSelectEvent) { + itemStack = ((PlayerStonecutterRecipeSelectEvent) event).getStonecuttingRecipe().getResult(); } else if (event instanceof EntityPickupItemEvent) { - is = ((EntityPickupItemEvent) event).getItem().getItemStack(); + itemStack = ((EntityPickupItemEvent) event).getItem().getItemStack(); } else if (event instanceof PlayerPickupItemEvent) { - is = ((PlayerPickupItemEvent) event).getItem().getItemStack(); + itemStack = ((PlayerPickupItemEvent) event).getItem().getItemStack(); } else if (hasConsumeEvent && event instanceof PlayerItemConsumeEvent) { - is = ((PlayerItemConsumeEvent) event).getItem(); + itemStack = ((PlayerItemConsumeEvent) event).getItem(); // } else if (e instanceof BrewEvent) -// is = ((BrewEvent) e).getContents().getContents() +// itemStack = ((BrewEvent) e).getContents().getContents() } else if (event instanceof InventoryClickEvent) { - is = ((InventoryClickEvent) event).getCurrentItem(); + itemStack = ((InventoryClickEvent) event).getCurrentItem(); } else if (event instanceof ItemDespawnEvent) { - is = ((ItemDespawnEvent) event).getEntity().getItemStack(); + itemStack = ((ItemDespawnEvent) event).getEntity().getItemStack(); } else if (event instanceof ItemMergeEvent) { - is = ((ItemMergeEvent) event).getTarget().getItemStack(); + itemStack = ((ItemMergeEvent) event).getTarget().getItemStack(); } else if (event instanceof InventoryMoveItemEvent) { - is = ((InventoryMoveItemEvent) event).getItem(); + itemStack = ((InventoryMoveItemEvent) event).getItem(); } else { assert false; return false; } - - if (is == null) + if (itemStack == null) return false; - - return types.check(event, new Checker<ItemType>() { - @Override - public boolean check(final ItemType t) { - return t.isOfType(is); - } - }); + return types.check(event, itemType -> itemType.isOfType(itemStack)); } @Override public String toString(@Nullable Event event, boolean debug) { - return "dispense/spawn/drop/craft/pickup/consume/break/despawn/merge/move" + (types == null ? "" : " of " + types); + return "dispense/spawn/drop/craft/pickup/consume/break/despawn/merge/move/stonecutting" + (types == null ? "" : " of " + types); } } From 78116e75cd4b19b5211f260f5d91af81765c2302 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Mon, 25 Sep 2023 06:11:49 -0600 Subject: [PATCH 478/619] Pull request template defaults (#5665) Update pull_request_template.md --- .github/pull_request_template.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index ce87d77a996..698a65a0554 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -2,6 +2,6 @@ <!--- Describe your changes here. ---> --- -**Target Minecraft Versions:** <!-- 'any' means all supported versions --> -**Requirements:** <!-- Required plugins, Minecraft versions, server software... --> -**Related Issues:** <!-- Links to related issues --> +**Target Minecraft Versions:** any <!-- 'any' means all supported versions --> +**Requirements:** none <!-- Required plugins, server software... --> +**Related Issues:** none <!-- Links to related issues --> From 5941679f0c59007ac1c9a9a293403286d837c194 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Mon, 25 Sep 2023 15:16:45 +0300 Subject: [PATCH 479/619] =?UTF-8?q?=F0=9F=9A=80=20Add=20Item=20Damge=20eve?= =?UTF-8?q?nt=20to=20ExprDamage=20(#5678)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../njol/skript/expressions/ExprDamage.java | 88 ++++++++++++------- 1 file changed, 56 insertions(+), 32 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprDamage.java b/src/main/java/ch/njol/skript/expressions/ExprDamage.java index 2b0a5bd5c0a..20305cb05be 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprDamage.java +++ b/src/main/java/ch/njol/skript/expressions/ExprDamage.java @@ -20,6 +20,7 @@ import org.bukkit.event.Event; import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.player.PlayerItemDamageEvent; import org.bukkit.event.vehicle.VehicleDamageEvent; import org.eclipse.jdt.annotation.Nullable; @@ -39,14 +40,21 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -/** - * @author Peter Güttinger - */ @Name("Damage") -@Description("How much damage is done in a damage event, possibly ignoring armour, criticals and/or enchantments. Can be changed (remember that in Skript '1' is one full heart, not half a heart).") -@Examples({"increase the damage by 2"}) -@Since("1.3.5") -@Events("damage") +@Description({ + "How much damage is done in a entity/vehicle/item damage events.", + "For entity damage events, possibly ignoring armour, criticals and/or enchantments (remember that in Skript '1' is one full heart, not half a heart).", + "For items, it's the amount of durability damage the item will be taking." +}) +@Examples({ + "on item damage:", + "\tevent-item is any tool", + "\tclear damage # unbreakable tools as the damage will be 0", + "on damage:", + "\tincrease the damage by 2" +}) +@Since("1.3.5, INSERT VERSION (item damage event)") +@Events({"Damage", "Vehicle Damage", "Item Damage"}) public class ExprDamage extends SimpleExpression<Number> { static { @@ -57,9 +65,9 @@ public class ExprDamage extends SimpleExpression<Number> { private Kleenean delay; @Override - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { - if (!getParser().isCurrentEvent(EntityDamageEvent.class, VehicleDamageEvent.class)) { - Skript.error("The expression 'damage' may only be used in damage events", ErrorQuality.SEMANTIC_ERROR); + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(EntityDamageEvent.class, VehicleDamageEvent.class, PlayerItemDamageEvent.class)) { + Skript.error("The 'damage' expression may only be used in damage events"); return false; } delay = isDelayed; @@ -68,48 +76,64 @@ public boolean init(final Expression<?>[] exprs, final int matchedPattern, final @Override @Nullable - protected Number[] get(final Event e) { - if (!(e instanceof EntityDamageEvent || e instanceof VehicleDamageEvent)) + protected Number[] get(Event event) { + if (!(event instanceof EntityDamageEvent || event instanceof VehicleDamageEvent || event instanceof PlayerItemDamageEvent)) return new Number[0]; - if (e instanceof VehicleDamageEvent) - return CollectionUtils.array(((VehicleDamageEvent) e).getDamage()); - return CollectionUtils.array(HealthUtils.getDamage((EntityDamageEvent) e)); + if (event instanceof VehicleDamageEvent) + return CollectionUtils.array(((VehicleDamageEvent) event).getDamage()); + if (event instanceof PlayerItemDamageEvent) + return CollectionUtils.array(((PlayerItemDamageEvent) event).getDamage()); + + return CollectionUtils.array(HealthUtils.getDamage((EntityDamageEvent) event)); } @Override @Nullable - public Class<?>[] acceptChange(final ChangeMode mode) { + public Class<?>[] acceptChange(ChangeMode mode) { if (delay != Kleenean.FALSE) { Skript.error("Can't change the damage anymore after the event has already passed"); return null; } - if (mode == ChangeMode.REMOVE_ALL) - return null; - return CollectionUtils.array(Number.class); + switch (mode) { + case ADD: + case SET: + case DELETE: + case REMOVE: + return CollectionUtils.array(Number.class); + default: + return null; + } } @Override - public void change(final Event e, final @Nullable Object[] delta, final ChangeMode mode) throws UnsupportedOperationException { - if (!(e instanceof EntityDamageEvent || e instanceof VehicleDamageEvent)) + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) throws UnsupportedOperationException { + if (!(event instanceof EntityDamageEvent || event instanceof VehicleDamageEvent || event instanceof PlayerItemDamageEvent)) return; - double d = delta == null ? 0 : ((Number) delta[0]).doubleValue(); + + double value = delta == null ? 0 : ((Number) delta[0]).doubleValue(); switch (mode) { case SET: case DELETE: - if (e instanceof VehicleDamageEvent) - ((VehicleDamageEvent) e).setDamage(d); - else - HealthUtils.setDamage((EntityDamageEvent) e, d); + if (event instanceof VehicleDamageEvent) { + ((VehicleDamageEvent) event).setDamage(value); + } else if (event instanceof PlayerItemDamageEvent) { + ((PlayerItemDamageEvent) event).setDamage((int) value); + } else { + HealthUtils.setDamage((EntityDamageEvent) event, value); + } break; case REMOVE: - d = -d; + value = -value; //$FALL-THROUGH$ case ADD: - if (e instanceof VehicleDamageEvent) - ((VehicleDamageEvent) e).setDamage(((VehicleDamageEvent) e).getDamage() + d); - else - HealthUtils.setDamage((EntityDamageEvent) e, HealthUtils.getDamage((EntityDamageEvent) e) + d); + if (event instanceof VehicleDamageEvent) { + ((VehicleDamageEvent) event).setDamage(((VehicleDamageEvent) event).getDamage() + value); + } else if (event instanceof PlayerItemDamageEvent) { + ((PlayerItemDamageEvent) event).setDamage((int) (((PlayerItemDamageEvent) event).getDamage() + value)); + } else { + HealthUtils.setDamage((EntityDamageEvent) event, HealthUtils.getDamage((EntityDamageEvent) event) + value); + } break; case REMOVE_ALL: case RESET: @@ -128,7 +152,7 @@ public Class<? extends Number> getReturnType() { } @Override - public String toString(final @Nullable Event e, final boolean debug) { + public String toString(@Nullable Event event, boolean debug) { return "the damage"; } From 9d5211cff1f286af25bd3d7a80fbc32f6f2fd3d2 Mon Sep 17 00:00:00 2001 From: DelayedGaming <72163224+DelayedGaming@users.noreply.github.com> Date: Tue, 26 Sep 2023 00:45:10 +0800 Subject: [PATCH 480/619] Fix EvtPlayerChunkEnter Comparison & Cleanup (#5965) Initial (cherry picked from commit 389c0022ed177c3124cfd1884165c5c697becaac) Co-authored-by: Moderocky <admin@moderocky.com> --- .../classes/data/BukkitEventValues.java | 39 ++++++++++++------- .../skript/events/EvtPlayerChunkEnter.java | 7 ++-- 2 files changed, 27 insertions(+), 19 deletions(-) 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 c47d5ab2885..2b3fa65af5b 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -845,6 +845,30 @@ public Block get(PlayerMoveEvent event) { return event.getTo().clone().subtract(0, 0.5, 0).getBlock(); } }, EventValues.TIME_NOW); + EventValues.registerEventValue(PlayerMoveEvent.class, Location.class, new Getter<Location, PlayerMoveEvent>() { + @Override + public Location get(PlayerMoveEvent event) { + return event.getFrom(); + } + }, EventValues.TIME_PAST); + EventValues.registerEventValue(PlayerMoveEvent.class, Location.class, new Getter<Location, PlayerMoveEvent>() { + @Override + public Location get(PlayerMoveEvent event) { + return event.getTo(); + } + }, EventValues.TIME_NOW); + EventValues.registerEventValue(PlayerMoveEvent.class, Chunk.class, new Getter<Chunk, PlayerMoveEvent>() { + @Override + public Chunk get(PlayerMoveEvent event) { + return event.getFrom().getChunk(); + } + }, EventValues.TIME_PAST); + EventValues.registerEventValue(PlayerMoveEvent.class, Chunk.class, new Getter<Chunk, PlayerMoveEvent>() { + @Override + public Chunk get(PlayerMoveEvent event) { + return event.getTo().getChunk(); + } + }, EventValues.TIME_NOW); // PlayerItemDamageEvent EventValues.registerEventValue(PlayerItemDamageEvent.class, ItemStack.class, new Getter<ItemStack, PlayerItemDamageEvent>() { @Override @@ -1425,21 +1449,6 @@ public TeleportCause get(final PlayerTeleportEvent e) { return e.getCause(); } }, 0); - //PlayerMoveEvent - EventValues.registerEventValue(PlayerMoveEvent.class, Location.class, new Getter<Location, PlayerMoveEvent>() { - @Override - @Nullable - public Location get(PlayerMoveEvent e) { - return e.getFrom(); - } - }, EventValues.TIME_PAST); - EventValues.registerEventValue(PlayerMoveEvent.class, Location.class, new Getter<Location, PlayerMoveEvent>() { - @Override - @Nullable - public Location get(PlayerMoveEvent e) { - return e.getTo(); - } - }, EventValues.TIME_NOW); //EntityMoveEvent if (Skript.classExists("io.papermc.paper.event.entity.EntityMoveEvent")) { EventValues.registerEventValue(EntityMoveEvent.class, Location.class, new Getter<Location, EntityMoveEvent>() { diff --git a/src/main/java/ch/njol/skript/events/EvtPlayerChunkEnter.java b/src/main/java/ch/njol/skript/events/EvtPlayerChunkEnter.java index e79030c1553..d3caf6d325b 100644 --- a/src/main/java/ch/njol/skript/events/EvtPlayerChunkEnter.java +++ b/src/main/java/ch/njol/skript/events/EvtPlayerChunkEnter.java @@ -22,11 +22,9 @@ import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptEvent; import ch.njol.skript.lang.SkriptParser.ParseResult; - import org.bukkit.event.Event; import org.bukkit.event.player.PlayerMoveEvent; - -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; public class EvtPlayerChunkEnter extends SkriptEvent { @@ -46,7 +44,8 @@ public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResu @Override public boolean check(Event event) { - return ((PlayerMoveEvent) event).getFrom().getChunk() != ((PlayerMoveEvent) event).getTo().getChunk(); + PlayerMoveEvent moveEvent = ((PlayerMoveEvent) event); + return !moveEvent.getFrom().getChunk().equals(moveEvent.getTo().getChunk()); } @Override From d021f1a3cb0d9519b60771e558e25858dfe69b79 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Mon, 25 Sep 2023 09:54:37 -0700 Subject: [PATCH 481/619] Fixes EffSecSpawn not properly handling local variables created within the section (#6033) Communicate local variables between consumer calls thanks pickle Co-authored-by: Moderocky <admin@moderocky.com> --- src/main/java/ch/njol/skript/sections/EffSecSpawn.java | 5 ++--- .../regressions/6032-local-vars-created-in-effsecspawn.sk | 5 +++++ 2 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 src/test/skript/tests/regressions/6032-local-vars-created-in-effsecspawn.sk diff --git a/src/main/java/ch/njol/skript/sections/EffSecSpawn.java b/src/main/java/ch/njol/skript/sections/EffSecSpawn.java index 04e1bd2ad62..320b4d63c39 100644 --- a/src/main/java/ch/njol/skript/sections/EffSecSpawn.java +++ b/src/main/java/ch/njol/skript/sections/EffSecSpawn.java @@ -140,16 +140,15 @@ public boolean init(Expression<?>[] exprs, protected TriggerItem walk(Event event) { lastSpawned = null; - Object localVars = Variables.copyLocalVariables(event); - Consumer<? extends Entity> consumer; if (trigger != null) { consumer = o -> { lastSpawned = o; SpawnEvent spawnEvent = new SpawnEvent(o); // Copy the local variables from the calling code to this section - Variables.setLocalVariables(spawnEvent, localVars); + Variables.setLocalVariables(spawnEvent, Variables.copyLocalVariables(event)); TriggerItem.walk(trigger, spawnEvent); + // And copy our (possibly modified) local variables back to the calling code Variables.setLocalVariables(event, Variables.copyLocalVariables(spawnEvent)); // Clear spawnEvent's local variables as it won't be done automatically Variables.removeLocals(spawnEvent); diff --git a/src/test/skript/tests/regressions/6032-local-vars-created-in-effsecspawn.sk b/src/test/skript/tests/regressions/6032-local-vars-created-in-effsecspawn.sk new file mode 100644 index 00000000000..23033b01db0 --- /dev/null +++ b/src/test/skript/tests/regressions/6032-local-vars-created-in-effsecspawn.sk @@ -0,0 +1,5 @@ +test "local vars created in EffSecSpawn": + set {_spawn} to spawn of world "world" + spawn 4 zombies at {_spawn}: + add 1 to {_test} + assert {_test} is 4 with "local var created in EffSecSpawn was not properly incremented" From 040cb8569f7ad679a42ce8e36fc97b033a2829c5 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Tue, 26 Sep 2023 00:57:21 -0600 Subject: [PATCH 482/619] Error when multiple event-values are present for a default expression (#5769) --- .../base/EventValueExpression.java | 7 + .../skript/registrations/EventValues.java | 139 ++++++++++++------ src/main/resources/lang/default.lang | 2 +- 3 files changed, 104 insertions(+), 44 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/base/EventValueExpression.java b/src/main/java/ch/njol/skript/expressions/base/EventValueExpression.java index e5bb37b2c9a..bd2a602adb8 100644 --- a/src/main/java/ch/njol/skript/expressions/base/EventValueExpression.java +++ b/src/main/java/ch/njol/skript/expressions/base/EventValueExpression.java @@ -37,6 +37,7 @@ import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.skript.localization.Noun; import ch.njol.skript.log.ParseLogHandler; import ch.njol.skript.log.SkriptLogger; import ch.njol.skript.registrations.Classes; @@ -136,6 +137,12 @@ public boolean init() { hasValue = getters.get(event) != null; continue; } + if (EventValues.hasMultipleGetters(event, c, getTime()) == Kleenean.TRUE) { + Noun typeName = Classes.getExactClassInfo(componentType).getName(); + log.printError("There are multiple " + typeName.toString(true) + " in " + Utils.a(getParser().getCurrentEventName()) + " event. " + + "You must define which " + typeName + " to use."); + return false; + } Getter<? extends T, ?> getter; if (exact) { getter = EventValues.getExactEventValueGetter(event, c, getTime()); diff --git a/src/main/java/ch/njol/skript/registrations/EventValues.java b/src/main/java/ch/njol/skript/registrations/EventValues.java index 255dbfcb6a8..d6956f32ee1 100644 --- a/src/main/java/ch/njol/skript/registrations/EventValues.java +++ b/src/main/java/ch/njol/skript/registrations/EventValues.java @@ -31,6 +31,7 @@ import org.skriptlang.skript.lang.converter.Converters; import ch.njol.skript.expressions.base.EventValueExpression; import ch.njol.skript.util.Getter; +import ch.njol.util.Kleenean; /** * @author Peter Güttinger @@ -136,46 +137,53 @@ public String getExcludeErrorMessage() { return futureEventValues; throw new IllegalArgumentException("time must be -1, 0, or 1"); } - + /** * Registers an event value. * - * @param e the event type - * @param c the type of the default value - * @param g the getter to get the value - * @param time -1 if this is the value before the event, 1 if after, and 0 if it's the default or this value doesn't have distinct states. + * @param event the event type class. + * @param type the return type of the getter for the event value. + * @param getter the getter to get the value with the provided event. + * @param time value of TIME_PAST if this is the value before the event, TIME_FUTURE if after, and TIME_NOW if it's the default or this value doesn't have distinct states. * <b>Always register a default state!</b> You can leave out one of the other states instead, e.g. only register a default and a past state. The future state will * default to the default state in this case. */ - public static <T, E extends Event> void registerEventValue(Class<E> e, Class<T> c, Getter<T, E> g, int time) { - registerEventValue(e, c, g, time, null, (Class<? extends E>[]) null); + public static <T, E extends Event> void registerEventValue(Class<E> event, Class<T> type, Getter<T, E> getter, int time) { + registerEventValue(event, type, getter, time, null, (Class<? extends E>[]) null); } - + /** - * Same as {@link #registerEventValue(Class, Class, Getter, int)} + * Registers an event value and with excluded events. + * Excluded events are events that this event value can't operate in. * - * @param e - * @param c - * @param g - * @param time -1 if this is the value before the event, 1 if after, and 0 if it's the default or this value doesn't have distinct states. + * @param event the event type class. + * @param type the return type of the getter for the event value. + * @param getter the getter to get the value with the provided event. + * @param time value of TIME_PAST if this is the value before the event, TIME_FUTURE if after, and TIME_NOW if it's the default or this value doesn't have distinct states. * <b>Always register a default state!</b> You can leave out one of the other states instead, e.g. only register a default and a past state. The future state will * default to the default state in this case. - * @param excludes Subclasses of the event for which this event value should not be registered for + * @param excludeErrorMessage The error message to display when used in the excluded events. + * @param excludes subclasses of the event for which this event value should not be registered for */ @SafeVarargs - public static <T, E extends Event> void registerEventValue(Class<E> e, Class<T> c, Getter<T, E> g, int time, @Nullable String excludeErrorMessage, @Nullable Class<? extends E>... excludes) { + public static <T, E extends Event> void registerEventValue(Class<E> event, Class<T> type, Getter<T, E> getter, int time, @Nullable String excludeErrorMessage, @Nullable Class<? extends E>... excludes) { Skript.checkAcceptRegistrations(); List<EventValueInfo<?, ?>> eventValues = getEventValuesList(time); for (int i = 0; i < eventValues.size(); i++) { EventValueInfo<?, ?> info = eventValues.get(i); - if (info.event != e ? info.event.isAssignableFrom(e) : info.c.isAssignableFrom(c)) { - eventValues.add(i, new EventValueInfo<>(e, c, g, excludeErrorMessage, excludes)); + // We don't care for exact duplicates. Prefer Skript's over any addon. + if (info.event.equals(event) && info.c.equals(type)) + return; + // If the events don't match, we prefer the highest subclass event. + // If the events match, we prefer the highest subclass type. + if (!info.event.equals(event) ? info.event.isAssignableFrom(event) : info.c.isAssignableFrom(type)) { + eventValues.add(i, new EventValueInfo<>(event, type, getter, excludeErrorMessage, excludes)); return; } } - eventValues.add(new EventValueInfo<>(e, c, g, excludeErrorMessage, excludes)); + eventValues.add(new EventValueInfo<>(event, type, getter, excludeErrorMessage, excludes)); } - + /** * Gets a specific value from an event. Returns null if the event doesn't have such a value (conversions are done to try and get the desired value). * <p> @@ -225,42 +233,74 @@ public static <T, E extends Event> T getEventValue(E e, Class<T> c, int time) { return null; } + /** + * Checks if an event has multiple getters, including default ones. + * + * @param event the event class the getter will be getting from. + * @param type type of getter. + * @param time the event-value's time. + * @return true or false if the event and type have multiple getters. + */ + public static <T, E extends Event> Kleenean hasMultipleGetters(Class<E> event, Class<T> type, int time) { + List<Getter<? extends T, ? super E>> getters = getEventValueGetters(event, type, time, true); + if (getters == null) + return Kleenean.UNKNOWN; + return Kleenean.get(getters.size() > 1); + } + /** * Returns a getter to get a value from in an event. * <p> * Can print an error if the event value is blocked for the given event. * - * @param event the event class the getter will be getting from - * @param c type of getter - * @param time the event-value's time - * @return A getter to get values for a given type of events + * @param event the event class the getter will be getting from. + * @param type type of getter. + * @param time the event-value's time. + * @return A getter to get values for a given type of events. * @see #registerEventValue(Class, Class, Getter, int) * @see EventValueExpression#EventValueExpression(Class) */ @Nullable - public static <T, E extends Event> Getter<? extends T, ? super E> getEventValueGetter(Class<E> event, Class<T> c, int time) { - return getEventValueGetter(event, c, time, true); + public static <T, E extends Event> Getter<? extends T, ? super E> getEventValueGetter(Class<E> event, Class<T> type, int time) { + return getEventValueGetter(event, type, time, true); + } + + @Nullable + private static <T, E extends Event> Getter<? extends T, ? super E> getEventValueGetter(Class<E> event, Class<T> type, int time, boolean allowDefault) { + List<Getter<? extends T, ? super E>> list = getEventValueGetters(event, type, time, allowDefault); + if (list == null || list.isEmpty()) + return null; + return list.get(0); } + /* + * We need to be able to collect all possible event-values to a list for determining problematic collisions. + * Always return after the loop check if the list is not empty. + */ @Nullable @SuppressWarnings("unchecked") - private static <T, E extends Event> Getter<? extends T, ? super E> getEventValueGetter(Class<E> event, Class<T> c, int time, boolean allowDefault) { + private static <T, E extends Event> List<Getter<? extends T, ? super E>> getEventValueGetters(Class<E> event, Class<T> type, int time, boolean allowDefault) { List<EventValueInfo<?, ?>> eventValues = getEventValuesList(time); + List<Getter<? extends T, ? super E>> list = new ArrayList<>(); // First check for exact classes matching the parameters. - Getter<? extends T, ? super E> exact = (Getter<? extends T, ? super E>) getExactEventValueGetter(event, c, time); - if (exact != null) - return exact; + Getter<? extends T, ? super E> exact = (Getter<? extends T, ? super E>) getExactEventValueGetter(event, type, time); + if (exact != null) { + list.add(exact); + return list; + } // Second check for assignable subclasses. for (EventValueInfo<?, ?> eventValueInfo : eventValues) { - if (!c.isAssignableFrom(eventValueInfo.c)) + if (!type.isAssignableFrom(eventValueInfo.c)) continue; if (!checkExcludes(eventValueInfo, event)) return null; - if (eventValueInfo.event.isAssignableFrom(event)) - return (Getter<? extends T, ? super E>) eventValueInfo.getter; + if (eventValueInfo.event.isAssignableFrom(event)) { + list.add((Getter<? extends T, ? super E>) eventValueInfo.getter); + continue; + } if (!event.isAssignableFrom(eventValueInfo.event)) continue; - return new Getter<T, E>() { + list.add(new Getter<T, E>() { @Override @Nullable public T get(E event) { @@ -268,67 +308,80 @@ public T get(E event) { return null; return ((Getter<? extends T, E>) eventValueInfo.getter).get(event); } - }; + }); + continue; } + if (!list.isEmpty()) + return list; // Most checks have returned before this below is called, but Skript will attempt to convert or find an alternative. // Third check is if the returned object matches the class. for (EventValueInfo<?, ?> eventValueInfo : eventValues) { - if (!eventValueInfo.c.isAssignableFrom(c)) + if (!eventValueInfo.c.isAssignableFrom(type)) continue; boolean checkInstanceOf = !eventValueInfo.event.isAssignableFrom(event); if (checkInstanceOf && !event.isAssignableFrom(eventValueInfo.event)) continue; if (!checkExcludes(eventValueInfo, event)) return null; - return new Getter<T, E>() { + list.add(new Getter<T, E>() { @Override @Nullable public T get(E event) { if (checkInstanceOf && !eventValueInfo.event.isInstance(event)) return null; Object object = ((Getter<? super T, ? super E>) eventValueInfo.getter).get(event); - if (c.isInstance(object)) + if (type.isInstance(object)) return (T) object; return null; } - }; + }); + continue; } + if (!list.isEmpty()) + return list; // Fourth check will attempt to convert the event value to the requesting type. // This first for loop will check that the events are exact. See issue #5016 for (EventValueInfo<?, ?> eventValueInfo : eventValues) { if (!event.equals(eventValueInfo.event)) continue; - Getter<? extends T, ? super E> getter = (Getter<? extends T, ? super E>) getConvertedGetter(eventValueInfo, c, false); + Getter<? extends T, ? super E> getter = (Getter<? extends T, ? super E>) getConvertedGetter(eventValueInfo, type, false); if (getter == null) continue; if (!checkExcludes(eventValueInfo, event)) return null; - return getter; + list.add(getter); + continue; } + if (!list.isEmpty()) + return list; // This loop will attempt to look for converters assignable to the class of the provided event. for (EventValueInfo<?, ?> eventValueInfo : eventValues) { // The requesting event must be assignable to the event value's event. Otherwise it'll throw an error. if (!event.isAssignableFrom(eventValueInfo.event)) continue; - Getter<? extends T, ? super E> getter = (Getter<? extends T, ? super E>) getConvertedGetter(eventValueInfo, c, true); + Getter<? extends T, ? super E> getter = (Getter<? extends T, ? super E>) getConvertedGetter(eventValueInfo, type, true); if (getter == null) continue; if (!checkExcludes(eventValueInfo, event)) return null; - return getter; + list.add(getter); + continue; } + if (!list.isEmpty()) + return list; // If the check should try again matching event values with a 0 time (most event values). if (allowDefault && time != 0) - return getEventValueGetter(event, c, 0, false); + return getEventValueGetters(event, type, 0, false); return null; } /** * Check if the event value states to exclude events. + * False if the current EventValueInfo cannot operate in the provided event. * * @param info The event value info that will be used to grab the value from * @param event The event class to check the excludes against. diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 4d0bfe3dff3..cd51c2e1efe 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -1987,7 +1987,7 @@ types: inventory: inventor¦y¦ies @an player: player¦s @a offlineplayer: offline player¦s @a - commandsender: player¦¦s¦/console @a + commandsender: command sender¦s @a inventoryholder: inventory holder¦s @an gamemode: gamemode¦s @a material: material¦s @a From d77ca013e105b7c9e11c1bf901f90fcaa250f2d6 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Tue, 26 Sep 2023 10:05:13 -0700 Subject: [PATCH 483/619] Remove PlayerPreprocessCommandEvent listener and clean up Commands (#5966) * Remove PPCE listener and clean up Commands * Apply suggestions from code review Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> * Update Commands.java * we hate breaking changes --------- Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Co-authored-by: Moderocky <admin@moderocky.com> --- src/main/java/ch/njol/skript/Skript.java | 138 ++++++++------- .../java/ch/njol/skript/SkriptConfig.java | 12 +- .../java/ch/njol/skript/command/Commands.java | 161 +++++++----------- .../conditions/CondIsSkriptCommand.java | 4 +- src/main/resources/config.sk | 9 +- 5 files changed, 142 insertions(+), 182 deletions(-) diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 45d076b7846..9740ee27efa 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -18,76 +18,9 @@ */ package ch.njol.skript; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.lang.Thread.UncaughtExceptionHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.logging.Filter; -import java.util.logging.Level; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.zip.ZipEntry; -import java.util.zip.ZipException; -import java.util.zip.ZipFile; - -import org.bstats.bukkit.Metrics; -import org.bstats.charts.SimplePie; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.Material; -import org.bukkit.Server; -import org.bukkit.command.CommandSender; -import org.bukkit.command.PluginCommand; -import org.bukkit.entity.Player; -import org.bukkit.event.Event; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerCommandPreprocessEvent; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.event.server.PluginDisableEvent; -import org.bukkit.event.server.ServerCommandEvent; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.PluginDescriptionFile; -import org.bukkit.plugin.java.JavaPlugin; -import org.eclipse.jdt.annotation.Nullable; -import org.junit.runner.JUnitCore; -import org.junit.runner.Result; -import org.skriptlang.skript.lang.entry.EntryValidator; -import org.skriptlang.skript.lang.script.Script; -import org.skriptlang.skript.lang.structure.Structure; -import org.skriptlang.skript.lang.structure.StructureInfo; - -import com.google.common.collect.Lists; -import com.google.gson.Gson; - import ch.njol.skript.aliases.Aliases; import ch.njol.skript.bukkitutil.BurgerHelper; import ch.njol.skript.classes.ClassInfo; -import org.skriptlang.skript.lang.comparator.Comparator; -import org.skriptlang.skript.lang.converter.Converter; import ch.njol.skript.classes.data.BukkitClasses; import ch.njol.skript.classes.data.BukkitEventValues; import ch.njol.skript.classes.data.DefaultComparators; @@ -124,8 +57,6 @@ import ch.njol.skript.log.SkriptLogger; import ch.njol.skript.log.Verbosity; import ch.njol.skript.registrations.Classes; -import org.skriptlang.skript.lang.comparator.Comparators; -import org.skriptlang.skript.lang.converter.Converters; import ch.njol.skript.registrations.EventValues; import ch.njol.skript.test.runner.EffObjectives; import ch.njol.skript.test.runner.SkriptJUnitTest; @@ -153,6 +84,73 @@ import ch.njol.util.StringUtils; import ch.njol.util.coll.iterator.CheckedIterator; import ch.njol.util.coll.iterator.EnumerationIterable; +import com.google.common.collect.Lists; +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; +import org.bukkit.Server; +import org.bukkit.command.CommandSender; +import org.bukkit.command.PluginCommand; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.server.PluginDisableEvent; +import org.bukkit.event.server.ServerCommandEvent; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.plugin.java.JavaPlugin; +import org.eclipse.jdt.annotation.Nullable; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +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.script.Script; +import org.skriptlang.skript.lang.structure.Structure; +import org.skriptlang.skript.lang.structure.StructureInfo; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.Thread.UncaughtExceptionHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.logging.Filter; +import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; // TODO meaningful error if someone uses an %expression with percent signs% outside of text or a variable @@ -770,7 +768,7 @@ protected void afterErrors() { SkriptConfig.defaultEventPriority.value().name().toLowerCase(Locale.ENGLISH).replace('_', ' ') )); metrics.addCustomChart(new SimplePie("logPlayerCommands", () -> - SkriptConfig.logPlayerCommands.value().toString() + String.valueOf((SkriptConfig.logEffectCommands.value() || SkriptConfig.logPlayerCommands.value())) )); metrics.addCustomChart(new SimplePie("maxTargetDistance", () -> SkriptConfig.maxTargetBlockDistance.value().toString() diff --git a/src/main/java/ch/njol/skript/SkriptConfig.java b/src/main/java/ch/njol/skript/SkriptConfig.java index 2af2518149a..a96ed7bf3ca 100644 --- a/src/main/java/ch/njol/skript/SkriptConfig.java +++ b/src/main/java/ch/njol/skript/SkriptConfig.java @@ -126,7 +126,14 @@ public class SkriptConfig { public static final Option<Boolean> enableEffectCommands = new Option<>("enable effect commands", false); public static final Option<String> effectCommandToken = new Option<>("effect command token", "!"); public static final Option<Boolean> allowOpsToUseEffectCommands = new Option<>("allow ops to use effect commands", false); - + + /* + * @deprecated Will be removed in 2.8.0. Use {@link #logEffectCommands} instead. + */ + @Deprecated + public static final Option<Boolean> logPlayerCommands = new Option<>("log player commands", false).optional(true); + public static final Option<Boolean> logEffectCommands = new Option<>("log effect commands", false); + // everything handled by Variables public static final OptionSection databases = new OptionSection("databases"); @@ -164,8 +171,7 @@ public static String formatDate(final long timestamp) { return null; } }); - - public static final Option<Boolean> logPlayerCommands = new Option<Boolean>("log player commands", false); + /** * Maximum number of digits to display after the period for floats and doubles diff --git a/src/main/java/ch/njol/skript/command/Commands.java b/src/main/java/ch/njol/skript/command/Commands.java index 90a3f5d740a..35e12057bb1 100644 --- a/src/main/java/ch/njol/skript/command/Commands.java +++ b/src/main/java/ch/njol/skript/command/Commands.java @@ -21,9 +21,7 @@ import ch.njol.skript.ScriptLoader; import ch.njol.skript.Skript; import ch.njol.skript.SkriptConfig; -import ch.njol.skript.config.validate.SectionValidator; import ch.njol.skript.lang.Effect; -import org.skriptlang.skript.lang.script.Script; import ch.njol.skript.lang.TriggerItem; import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.skript.localization.ArgsMessage; @@ -39,17 +37,17 @@ import org.bukkit.command.CommandSender; import org.bukkit.command.ConsoleCommandSender; import org.bukkit.command.SimpleCommandMap; -import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.AsyncPlayerChatEvent; -import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.event.server.ServerCommandEvent; import org.bukkit.help.HelpMap; import org.bukkit.help.HelpTopic; import org.bukkit.plugin.SimplePluginManager; import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.NotNull; +import org.skriptlang.skript.lang.script.Script; import java.io.File; import java.lang.reflect.Field; @@ -59,7 +57,6 @@ import java.util.Locale; import java.util.Map; import java.util.Set; -import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.regex.Pattern; @@ -106,24 +103,24 @@ public static SimpleCommandMap getCommandMap(){ private static void init() { try { if (Bukkit.getPluginManager() instanceof SimplePluginManager) { - final Field commandMapField = SimplePluginManager.class.getDeclaredField("commandMap"); + Field commandMapField = SimplePluginManager.class.getDeclaredField("commandMap"); commandMapField.setAccessible(true); commandMap = (SimpleCommandMap) commandMapField.get(Bukkit.getPluginManager()); - final Field knownCommandsField = SimpleCommandMap.class.getDeclaredField("knownCommands"); + Field knownCommandsField = SimpleCommandMap.class.getDeclaredField("knownCommands"); knownCommandsField.setAccessible(true); cmKnownCommands = (Map<String, Command>) knownCommandsField.get(commandMap); try { - final Field aliasesField = SimpleCommandMap.class.getDeclaredField("aliases"); + Field aliasesField = SimpleCommandMap.class.getDeclaredField("aliases"); aliasesField.setAccessible(true); cmAliases = (Set<String>) aliasesField.get(commandMap); - } catch (final NoSuchFieldException e) {} + } catch (NoSuchFieldException ignored) {} } - } catch (final SecurityException e) { + } catch (SecurityException e) { Skript.error("Please disable the security manager"); commandMap = null; - } catch (final Exception e) { + } catch (Exception e) { Skript.outdatedError(e); commandMap = null; } @@ -137,71 +134,33 @@ private static void init() { @SuppressWarnings("null") private final static Pattern unescape = Pattern.compile("\\\\[" + Pattern.quote("(|)<>%\\") + "]"); - public static String escape(String s) { - return "" + escape.matcher(s).replaceAll("\\\\$0"); + public static String escape(String string) { + return "" + escape.matcher(string).replaceAll("\\\\$0"); } - public static String unescape(String s) { - return "" + unescape.matcher(s).replaceAll("$0"); + public static String unescape(String string) { + return "" + unescape.matcher(string).replaceAll("$0"); } private final static Listener commandListener = new Listener() { - @SuppressWarnings("null") - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void onPlayerCommand(final PlayerCommandPreprocessEvent e) { - if (handleCommand(e.getPlayer(), e.getMessage().substring(1))) - e.setCancelled(true); - } - @SuppressWarnings("null") @EventHandler(priority = EventPriority.HIGHEST) - public void onServerCommand(final ServerCommandEvent e) { - if (e.getCommand() == null || e.getCommand().isEmpty() || e.isCancelled()) - return; - if ((Skript.testing() || SkriptConfig.enableEffectCommands.value()) && e.getCommand().startsWith(SkriptConfig.effectCommandToken.value())) { - if (handleEffectCommand(e.getSender(), e.getCommand())) { - e.setCancelled(true); - } + public void onServerCommand(ServerCommandEvent event) { + if (event.getCommand().isEmpty() || event.isCancelled()) return; + if ((Skript.testing() || SkriptConfig.enableEffectCommands.value()) && event.getCommand().startsWith(SkriptConfig.effectCommandToken.value())) { + if (handleEffectCommand(event.getSender(), event.getCommand())) + event.setCancelled(true); } } }; - - /** - * @param sender - * @param command full command string without the slash - * @return whether to cancel the event - */ - static boolean handleCommand(final CommandSender sender, final String command) { - final String[] cmd = command.split("\\s+", 2); - cmd[0] = cmd[0].toLowerCase(Locale.ENGLISH); - if (cmd[0].endsWith("?")) { - final ScriptCommand c = commands.get(cmd[0].substring(0, cmd[0].length() - 1)); - if (c != null) { - c.sendHelp(sender); - return true; - } - } - final ScriptCommand c = commands.get(cmd[0]); - if (c != null) { -// if (cmd.length == 2 && cmd[1].equals("?")) { -// c.sendHelp(sender); -// return true; -// } - if (SkriptConfig.logPlayerCommands.value() && sender instanceof Player) - SkriptLogger.LOGGER.info(sender.getName() + " [" + ((Player) sender).getUniqueId() + "]: /" + command); - c.execute(sender, "" + cmd[0], cmd.length == 1 ? "" : "" + cmd[1]); - return true; - } - return false; - } - static boolean handleEffectCommand(final CommandSender sender, String command) { + static boolean handleEffectCommand(CommandSender sender, String command) { if (!(sender instanceof ConsoleCommandSender || sender.hasPermission("skript.effectcommands") || SkriptConfig.allowOpsToUseEffectCommands.value() && sender.isOp())) return false; try { command = "" + command.substring(SkriptConfig.effectCommandToken.value().length()).trim(); - final RetainingLogHandler log = SkriptLogger.startRetainingLog(); + RetainingLogHandler log = SkriptLogger.startRetainingLog(); try { // Call the event on the Bukkit API for addon developers. EffectCommandEvent effectCommand = new EffectCommandEvent(sender, command); @@ -217,7 +176,8 @@ static boolean handleEffectCommand(final CommandSender sender, String command) { log.printLog(); if (!effectCommand.isCancelled()) { sender.sendMessage(ChatColor.GRAY + "executing '" + SkriptColor.replaceColorChar(command) + "'"); - if (SkriptConfig.logPlayerCommands.value() && !(sender instanceof ConsoleCommandSender)) + // TODO: remove logPlayerCommands for 2.8.0 + if ((SkriptConfig.logEffectCommands.value() || SkriptConfig.logPlayerCommands.value()) && !(sender instanceof ConsoleCommandSender)) Skript.info(sender.getName() + " issued effect command: " + SkriptColor.replaceColorChar(command)); TriggerItem.walk(effect, effectCommand); Variables.removeLocals(effectCommand); @@ -235,7 +195,7 @@ static boolean handleEffectCommand(final CommandSender sender, String command) { log.stop(); } return true; - } catch (final Exception e) { + } catch (Exception e) { Skript.exception(e, "Unexpected error while executing effect command '" + SkriptColor.replaceColorChar(command) + "' by '" + sender.getName() + "'"); sender.sendMessage(ChatColor.RED + "An internal error occurred while executing this effect. Please refer to the server log for details."); return true; @@ -246,15 +206,23 @@ static boolean handleEffectCommand(final CommandSender sender, String command) { public static ScriptCommand getScriptCommand(String key) { return commands.get(key); } - - public static boolean skriptCommandExists(final String command) { - final ScriptCommand c = commands.get(command); - return c != null && c.getName().equals(command); + + /* + * @deprecated Use {@link #scriptCommandExists(String)} instead. + */ + @Deprecated + public static boolean skriptCommandExists(String command) { + return scriptCommandExists(command); + } + + public static boolean scriptCommandExists(String command) { + ScriptCommand scriptCommand = commands.get(command); + return scriptCommand != null && scriptCommand.getName().equals(command); } - public static void registerCommand(final ScriptCommand command) { + public static void registerCommand(ScriptCommand command) { // Validate that there are no duplicates - final ScriptCommand existingCommand = commands.get(command.getLabel()); + ScriptCommand existingCommand = commands.get(command.getLabel()); if (existingCommand != null && existingCommand.getLabel().equals(command.getLabel())) { Script script = existingCommand.getScript(); Skript.error("A command with the name /" + existingCommand.getName() + " is already defined" @@ -268,7 +236,7 @@ public static void registerCommand(final ScriptCommand command) { command.register(commandMap, cmKnownCommands, cmAliases); } commands.put(command.getLabel(), command); - for (final String alias : command.getActiveAliases()) { + for (String alias : command.getActiveAliases()) { commands.put(alias.toLowerCase(Locale.ENGLISH), command); } command.registerHelp(); @@ -303,30 +271,25 @@ public static void registerListeners() { Bukkit.getPluginManager().registerEvents(new Listener() { @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void onPlayerChat(final AsyncPlayerChatEvent e) { - if (!SkriptConfig.enableEffectCommands.value() || !e.getMessage().startsWith(SkriptConfig.effectCommandToken.value())) + public void onPlayerChat(AsyncPlayerChatEvent event) { + if (!SkriptConfig.enableEffectCommands.value() || !event.getMessage().startsWith(SkriptConfig.effectCommandToken.value())) return; - if (!e.isAsynchronous()) { - if (handleEffectCommand(e.getPlayer(), e.getMessage())) - e.setCancelled(true); + if (!event.isAsynchronous()) { + if (handleEffectCommand(event.getPlayer(), event.getMessage())) + event.setCancelled(true); } else { - final Future<Boolean> f = Bukkit.getScheduler().callSyncMethod(Skript.getInstance(), new Callable<Boolean>() { - @Override - public Boolean call() throws Exception { - return handleEffectCommand(e.getPlayer(), e.getMessage()); - } - }); + Future<Boolean> f = Bukkit.getScheduler().callSyncMethod(Skript.getInstance(), () -> handleEffectCommand(event.getPlayer(), event.getMessage())); try { while (true) { try { if (f.get()) - e.setCancelled(true); + event.setCancelled(true); break; - } catch (final InterruptedException e1) { + } catch (InterruptedException ignored) { } } - } catch (final ExecutionException e1) { - Skript.exception(e1); + } catch (ExecutionException e) { + Skript.exception(e); } } } @@ -344,7 +307,7 @@ public static final class CommandAliasHelpTopic extends HelpTopic { private final String aliasFor; private final HelpMap helpMap; - public CommandAliasHelpTopic(final String alias, final String aliasFor, final HelpMap helpMap) { + public CommandAliasHelpTopic(String alias, String aliasFor, HelpMap helpMap) { this.aliasFor = aliasFor.startsWith("/") ? aliasFor : "/" + aliasFor; this.helpMap = helpMap; name = alias.startsWith("/") ? alias : "/" + alias; @@ -353,29 +316,23 @@ public CommandAliasHelpTopic(final String alias, final String aliasFor, final He } @Override - public String getFullText(final CommandSender forWho) { - final StringBuilder sb = new StringBuilder(shortText); - final HelpTopic aliasForTopic = helpMap.getHelpTopic(aliasFor); + @NotNull + public String getFullText(CommandSender forWho) { + StringBuilder fullText = new StringBuilder(shortText); + HelpTopic aliasForTopic = helpMap.getHelpTopic(aliasFor); if (aliasForTopic != null) { - sb.append("\n"); - sb.append(aliasForTopic.getFullText(forWho)); + fullText.append("\n"); + fullText.append(aliasForTopic.getFullText(forWho)); } - return "" + sb.toString(); + return "" + fullText; } @Override - public boolean canSee(final CommandSender commandSender) { - if (amendedPermission == null) { - final HelpTopic aliasForTopic = helpMap.getHelpTopic(aliasFor); - if (aliasForTopic != null) { - return aliasForTopic.canSee(commandSender); - } else { - return false; - } - } else { - assert amendedPermission != null; + public boolean canSee(CommandSender commandSender) { + if (amendedPermission != null) return commandSender.hasPermission(amendedPermission); - } + HelpTopic aliasForTopic = helpMap.getHelpTopic(aliasFor); + return aliasForTopic != null && aliasForTopic.canSee(commandSender); } } diff --git a/src/main/java/ch/njol/skript/conditions/CondIsSkriptCommand.java b/src/main/java/ch/njol/skript/conditions/CondIsSkriptCommand.java index f8a6a2f7327..4c4f8097ba4 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsSkriptCommand.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsSkriptCommand.java @@ -18,7 +18,7 @@ */ package ch.njol.skript.conditions; -import static ch.njol.skript.command.Commands.skriptCommandExists; +import static ch.njol.skript.command.Commands.scriptCommandExists; import ch.njol.skript.conditions.base.PropertyCondition; import ch.njol.skript.doc.Description; @@ -44,7 +44,7 @@ public class CondIsSkriptCommand extends PropertyCondition<String> { @Override public boolean check(String cmd) { - return skriptCommandExists(cmd); + return scriptCommandExists(cmd); } @Override diff --git a/src/main/resources/config.sk b/src/main/resources/config.sk index a95cfe461b1..54a9bb7a070 100644 --- a/src/main/resources/config.sk +++ b/src/main/resources/config.sk @@ -76,6 +76,10 @@ allow ops to use effect commands: false # Whether server operators which do not have the permission "skript.effectcommands" should have access to effect commands. # This setting is mainly useful for servers that do not run any permissions plugin. +log effect commands: false +# Whether Skript should log the usage of effect commands. +# They will be logged as [INFORMATION] in this format: '<player> issued effect command: <command>' + 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 @@ -119,11 +123,6 @@ plugin priority: high # Skript removes drops it shouldn't => decrease priority or specify which item types to remove -log player commands: false -# Whether Skript should log the usage of custom commands. -# They will be logged as [INFORMATION] in this format: '<player>: /<command> <arguments>' - - number accuracy: 2 # How many digits should be displayed after the dot at maximum when displaying numbers. # Zeroes will never be displayed at all, so this setting only applies to numbers that actually have a decimal part with one or more non-zero digits. From d1f73b8d075bb70979496eb86935c760cca19d34 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Tue, 26 Sep 2023 10:05:13 -0700 Subject: [PATCH 484/619] Remove PlayerPreprocessCommandEvent listener and clean up Commands (#5966) * Remove PPCE listener and clean up Commands * Apply suggestions from code review Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> * Update Commands.java * we hate breaking changes --------- Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Co-authored-by: Moderocky <admin@moderocky.com> --- src/main/java/ch/njol/skript/Skript.java | 138 ++++++++------- .../java/ch/njol/skript/SkriptConfig.java | 12 +- .../java/ch/njol/skript/command/Commands.java | 161 +++++++----------- .../conditions/CondIsSkriptCommand.java | 4 +- src/main/resources/config.sk | 9 +- 5 files changed, 142 insertions(+), 182 deletions(-) diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 45d076b7846..9740ee27efa 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -18,76 +18,9 @@ */ package ch.njol.skript; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.lang.Thread.UncaughtExceptionHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.logging.Filter; -import java.util.logging.Level; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.zip.ZipEntry; -import java.util.zip.ZipException; -import java.util.zip.ZipFile; - -import org.bstats.bukkit.Metrics; -import org.bstats.charts.SimplePie; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.Material; -import org.bukkit.Server; -import org.bukkit.command.CommandSender; -import org.bukkit.command.PluginCommand; -import org.bukkit.entity.Player; -import org.bukkit.event.Event; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerCommandPreprocessEvent; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.event.server.PluginDisableEvent; -import org.bukkit.event.server.ServerCommandEvent; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.PluginDescriptionFile; -import org.bukkit.plugin.java.JavaPlugin; -import org.eclipse.jdt.annotation.Nullable; -import org.junit.runner.JUnitCore; -import org.junit.runner.Result; -import org.skriptlang.skript.lang.entry.EntryValidator; -import org.skriptlang.skript.lang.script.Script; -import org.skriptlang.skript.lang.structure.Structure; -import org.skriptlang.skript.lang.structure.StructureInfo; - -import com.google.common.collect.Lists; -import com.google.gson.Gson; - import ch.njol.skript.aliases.Aliases; import ch.njol.skript.bukkitutil.BurgerHelper; import ch.njol.skript.classes.ClassInfo; -import org.skriptlang.skript.lang.comparator.Comparator; -import org.skriptlang.skript.lang.converter.Converter; import ch.njol.skript.classes.data.BukkitClasses; import ch.njol.skript.classes.data.BukkitEventValues; import ch.njol.skript.classes.data.DefaultComparators; @@ -124,8 +57,6 @@ import ch.njol.skript.log.SkriptLogger; import ch.njol.skript.log.Verbosity; import ch.njol.skript.registrations.Classes; -import org.skriptlang.skript.lang.comparator.Comparators; -import org.skriptlang.skript.lang.converter.Converters; import ch.njol.skript.registrations.EventValues; import ch.njol.skript.test.runner.EffObjectives; import ch.njol.skript.test.runner.SkriptJUnitTest; @@ -153,6 +84,73 @@ import ch.njol.util.StringUtils; import ch.njol.util.coll.iterator.CheckedIterator; import ch.njol.util.coll.iterator.EnumerationIterable; +import com.google.common.collect.Lists; +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; +import org.bukkit.Server; +import org.bukkit.command.CommandSender; +import org.bukkit.command.PluginCommand; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.server.PluginDisableEvent; +import org.bukkit.event.server.ServerCommandEvent; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.plugin.java.JavaPlugin; +import org.eclipse.jdt.annotation.Nullable; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +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.script.Script; +import org.skriptlang.skript.lang.structure.Structure; +import org.skriptlang.skript.lang.structure.StructureInfo; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.Thread.UncaughtExceptionHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.logging.Filter; +import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; // TODO meaningful error if someone uses an %expression with percent signs% outside of text or a variable @@ -770,7 +768,7 @@ protected void afterErrors() { SkriptConfig.defaultEventPriority.value().name().toLowerCase(Locale.ENGLISH).replace('_', ' ') )); metrics.addCustomChart(new SimplePie("logPlayerCommands", () -> - SkriptConfig.logPlayerCommands.value().toString() + String.valueOf((SkriptConfig.logEffectCommands.value() || SkriptConfig.logPlayerCommands.value())) )); metrics.addCustomChart(new SimplePie("maxTargetDistance", () -> SkriptConfig.maxTargetBlockDistance.value().toString() diff --git a/src/main/java/ch/njol/skript/SkriptConfig.java b/src/main/java/ch/njol/skript/SkriptConfig.java index 552debddd5c..d3803407f2c 100644 --- a/src/main/java/ch/njol/skript/SkriptConfig.java +++ b/src/main/java/ch/njol/skript/SkriptConfig.java @@ -126,7 +126,14 @@ public class SkriptConfig { public static final Option<Boolean> enableEffectCommands = new Option<>("enable effect commands", false); public static final Option<String> effectCommandToken = new Option<>("effect command token", "!"); public static final Option<Boolean> allowOpsToUseEffectCommands = new Option<>("allow ops to use effect commands", false); - + + /* + * @deprecated Will be removed in 2.8.0. Use {@link #logEffectCommands} instead. + */ + @Deprecated + public static final Option<Boolean> logPlayerCommands = new Option<>("log player commands", false).optional(true); + public static final Option<Boolean> logEffectCommands = new Option<>("log effect commands", false); + // everything handled by Variables public static final OptionSection databases = new OptionSection("databases"); @@ -164,8 +171,7 @@ public static String formatDate(final long timestamp) { return null; } }); - - public static final Option<Boolean> logPlayerCommands = new Option<Boolean>("log player commands", false); + /** * Maximum number of digits to display after the period for floats and doubles diff --git a/src/main/java/ch/njol/skript/command/Commands.java b/src/main/java/ch/njol/skript/command/Commands.java index 90a3f5d740a..35e12057bb1 100644 --- a/src/main/java/ch/njol/skript/command/Commands.java +++ b/src/main/java/ch/njol/skript/command/Commands.java @@ -21,9 +21,7 @@ import ch.njol.skript.ScriptLoader; import ch.njol.skript.Skript; import ch.njol.skript.SkriptConfig; -import ch.njol.skript.config.validate.SectionValidator; import ch.njol.skript.lang.Effect; -import org.skriptlang.skript.lang.script.Script; import ch.njol.skript.lang.TriggerItem; import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.skript.localization.ArgsMessage; @@ -39,17 +37,17 @@ import org.bukkit.command.CommandSender; import org.bukkit.command.ConsoleCommandSender; import org.bukkit.command.SimpleCommandMap; -import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.AsyncPlayerChatEvent; -import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.event.server.ServerCommandEvent; import org.bukkit.help.HelpMap; import org.bukkit.help.HelpTopic; import org.bukkit.plugin.SimplePluginManager; import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.NotNull; +import org.skriptlang.skript.lang.script.Script; import java.io.File; import java.lang.reflect.Field; @@ -59,7 +57,6 @@ import java.util.Locale; import java.util.Map; import java.util.Set; -import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.regex.Pattern; @@ -106,24 +103,24 @@ public static SimpleCommandMap getCommandMap(){ private static void init() { try { if (Bukkit.getPluginManager() instanceof SimplePluginManager) { - final Field commandMapField = SimplePluginManager.class.getDeclaredField("commandMap"); + Field commandMapField = SimplePluginManager.class.getDeclaredField("commandMap"); commandMapField.setAccessible(true); commandMap = (SimpleCommandMap) commandMapField.get(Bukkit.getPluginManager()); - final Field knownCommandsField = SimpleCommandMap.class.getDeclaredField("knownCommands"); + Field knownCommandsField = SimpleCommandMap.class.getDeclaredField("knownCommands"); knownCommandsField.setAccessible(true); cmKnownCommands = (Map<String, Command>) knownCommandsField.get(commandMap); try { - final Field aliasesField = SimpleCommandMap.class.getDeclaredField("aliases"); + Field aliasesField = SimpleCommandMap.class.getDeclaredField("aliases"); aliasesField.setAccessible(true); cmAliases = (Set<String>) aliasesField.get(commandMap); - } catch (final NoSuchFieldException e) {} + } catch (NoSuchFieldException ignored) {} } - } catch (final SecurityException e) { + } catch (SecurityException e) { Skript.error("Please disable the security manager"); commandMap = null; - } catch (final Exception e) { + } catch (Exception e) { Skript.outdatedError(e); commandMap = null; } @@ -137,71 +134,33 @@ private static void init() { @SuppressWarnings("null") private final static Pattern unescape = Pattern.compile("\\\\[" + Pattern.quote("(|)<>%\\") + "]"); - public static String escape(String s) { - return "" + escape.matcher(s).replaceAll("\\\\$0"); + public static String escape(String string) { + return "" + escape.matcher(string).replaceAll("\\\\$0"); } - public static String unescape(String s) { - return "" + unescape.matcher(s).replaceAll("$0"); + public static String unescape(String string) { + return "" + unescape.matcher(string).replaceAll("$0"); } private final static Listener commandListener = new Listener() { - @SuppressWarnings("null") - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void onPlayerCommand(final PlayerCommandPreprocessEvent e) { - if (handleCommand(e.getPlayer(), e.getMessage().substring(1))) - e.setCancelled(true); - } - @SuppressWarnings("null") @EventHandler(priority = EventPriority.HIGHEST) - public void onServerCommand(final ServerCommandEvent e) { - if (e.getCommand() == null || e.getCommand().isEmpty() || e.isCancelled()) - return; - if ((Skript.testing() || SkriptConfig.enableEffectCommands.value()) && e.getCommand().startsWith(SkriptConfig.effectCommandToken.value())) { - if (handleEffectCommand(e.getSender(), e.getCommand())) { - e.setCancelled(true); - } + public void onServerCommand(ServerCommandEvent event) { + if (event.getCommand().isEmpty() || event.isCancelled()) return; + if ((Skript.testing() || SkriptConfig.enableEffectCommands.value()) && event.getCommand().startsWith(SkriptConfig.effectCommandToken.value())) { + if (handleEffectCommand(event.getSender(), event.getCommand())) + event.setCancelled(true); } } }; - - /** - * @param sender - * @param command full command string without the slash - * @return whether to cancel the event - */ - static boolean handleCommand(final CommandSender sender, final String command) { - final String[] cmd = command.split("\\s+", 2); - cmd[0] = cmd[0].toLowerCase(Locale.ENGLISH); - if (cmd[0].endsWith("?")) { - final ScriptCommand c = commands.get(cmd[0].substring(0, cmd[0].length() - 1)); - if (c != null) { - c.sendHelp(sender); - return true; - } - } - final ScriptCommand c = commands.get(cmd[0]); - if (c != null) { -// if (cmd.length == 2 && cmd[1].equals("?")) { -// c.sendHelp(sender); -// return true; -// } - if (SkriptConfig.logPlayerCommands.value() && sender instanceof Player) - SkriptLogger.LOGGER.info(sender.getName() + " [" + ((Player) sender).getUniqueId() + "]: /" + command); - c.execute(sender, "" + cmd[0], cmd.length == 1 ? "" : "" + cmd[1]); - return true; - } - return false; - } - static boolean handleEffectCommand(final CommandSender sender, String command) { + static boolean handleEffectCommand(CommandSender sender, String command) { if (!(sender instanceof ConsoleCommandSender || sender.hasPermission("skript.effectcommands") || SkriptConfig.allowOpsToUseEffectCommands.value() && sender.isOp())) return false; try { command = "" + command.substring(SkriptConfig.effectCommandToken.value().length()).trim(); - final RetainingLogHandler log = SkriptLogger.startRetainingLog(); + RetainingLogHandler log = SkriptLogger.startRetainingLog(); try { // Call the event on the Bukkit API for addon developers. EffectCommandEvent effectCommand = new EffectCommandEvent(sender, command); @@ -217,7 +176,8 @@ static boolean handleEffectCommand(final CommandSender sender, String command) { log.printLog(); if (!effectCommand.isCancelled()) { sender.sendMessage(ChatColor.GRAY + "executing '" + SkriptColor.replaceColorChar(command) + "'"); - if (SkriptConfig.logPlayerCommands.value() && !(sender instanceof ConsoleCommandSender)) + // TODO: remove logPlayerCommands for 2.8.0 + if ((SkriptConfig.logEffectCommands.value() || SkriptConfig.logPlayerCommands.value()) && !(sender instanceof ConsoleCommandSender)) Skript.info(sender.getName() + " issued effect command: " + SkriptColor.replaceColorChar(command)); TriggerItem.walk(effect, effectCommand); Variables.removeLocals(effectCommand); @@ -235,7 +195,7 @@ static boolean handleEffectCommand(final CommandSender sender, String command) { log.stop(); } return true; - } catch (final Exception e) { + } catch (Exception e) { Skript.exception(e, "Unexpected error while executing effect command '" + SkriptColor.replaceColorChar(command) + "' by '" + sender.getName() + "'"); sender.sendMessage(ChatColor.RED + "An internal error occurred while executing this effect. Please refer to the server log for details."); return true; @@ -246,15 +206,23 @@ static boolean handleEffectCommand(final CommandSender sender, String command) { public static ScriptCommand getScriptCommand(String key) { return commands.get(key); } - - public static boolean skriptCommandExists(final String command) { - final ScriptCommand c = commands.get(command); - return c != null && c.getName().equals(command); + + /* + * @deprecated Use {@link #scriptCommandExists(String)} instead. + */ + @Deprecated + public static boolean skriptCommandExists(String command) { + return scriptCommandExists(command); + } + + public static boolean scriptCommandExists(String command) { + ScriptCommand scriptCommand = commands.get(command); + return scriptCommand != null && scriptCommand.getName().equals(command); } - public static void registerCommand(final ScriptCommand command) { + public static void registerCommand(ScriptCommand command) { // Validate that there are no duplicates - final ScriptCommand existingCommand = commands.get(command.getLabel()); + ScriptCommand existingCommand = commands.get(command.getLabel()); if (existingCommand != null && existingCommand.getLabel().equals(command.getLabel())) { Script script = existingCommand.getScript(); Skript.error("A command with the name /" + existingCommand.getName() + " is already defined" @@ -268,7 +236,7 @@ public static void registerCommand(final ScriptCommand command) { command.register(commandMap, cmKnownCommands, cmAliases); } commands.put(command.getLabel(), command); - for (final String alias : command.getActiveAliases()) { + for (String alias : command.getActiveAliases()) { commands.put(alias.toLowerCase(Locale.ENGLISH), command); } command.registerHelp(); @@ -303,30 +271,25 @@ public static void registerListeners() { Bukkit.getPluginManager().registerEvents(new Listener() { @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void onPlayerChat(final AsyncPlayerChatEvent e) { - if (!SkriptConfig.enableEffectCommands.value() || !e.getMessage().startsWith(SkriptConfig.effectCommandToken.value())) + public void onPlayerChat(AsyncPlayerChatEvent event) { + if (!SkriptConfig.enableEffectCommands.value() || !event.getMessage().startsWith(SkriptConfig.effectCommandToken.value())) return; - if (!e.isAsynchronous()) { - if (handleEffectCommand(e.getPlayer(), e.getMessage())) - e.setCancelled(true); + if (!event.isAsynchronous()) { + if (handleEffectCommand(event.getPlayer(), event.getMessage())) + event.setCancelled(true); } else { - final Future<Boolean> f = Bukkit.getScheduler().callSyncMethod(Skript.getInstance(), new Callable<Boolean>() { - @Override - public Boolean call() throws Exception { - return handleEffectCommand(e.getPlayer(), e.getMessage()); - } - }); + Future<Boolean> f = Bukkit.getScheduler().callSyncMethod(Skript.getInstance(), () -> handleEffectCommand(event.getPlayer(), event.getMessage())); try { while (true) { try { if (f.get()) - e.setCancelled(true); + event.setCancelled(true); break; - } catch (final InterruptedException e1) { + } catch (InterruptedException ignored) { } } - } catch (final ExecutionException e1) { - Skript.exception(e1); + } catch (ExecutionException e) { + Skript.exception(e); } } } @@ -344,7 +307,7 @@ public static final class CommandAliasHelpTopic extends HelpTopic { private final String aliasFor; private final HelpMap helpMap; - public CommandAliasHelpTopic(final String alias, final String aliasFor, final HelpMap helpMap) { + public CommandAliasHelpTopic(String alias, String aliasFor, HelpMap helpMap) { this.aliasFor = aliasFor.startsWith("/") ? aliasFor : "/" + aliasFor; this.helpMap = helpMap; name = alias.startsWith("/") ? alias : "/" + alias; @@ -353,29 +316,23 @@ public CommandAliasHelpTopic(final String alias, final String aliasFor, final He } @Override - public String getFullText(final CommandSender forWho) { - final StringBuilder sb = new StringBuilder(shortText); - final HelpTopic aliasForTopic = helpMap.getHelpTopic(aliasFor); + @NotNull + public String getFullText(CommandSender forWho) { + StringBuilder fullText = new StringBuilder(shortText); + HelpTopic aliasForTopic = helpMap.getHelpTopic(aliasFor); if (aliasForTopic != null) { - sb.append("\n"); - sb.append(aliasForTopic.getFullText(forWho)); + fullText.append("\n"); + fullText.append(aliasForTopic.getFullText(forWho)); } - return "" + sb.toString(); + return "" + fullText; } @Override - public boolean canSee(final CommandSender commandSender) { - if (amendedPermission == null) { - final HelpTopic aliasForTopic = helpMap.getHelpTopic(aliasFor); - if (aliasForTopic != null) { - return aliasForTopic.canSee(commandSender); - } else { - return false; - } - } else { - assert amendedPermission != null; + public boolean canSee(CommandSender commandSender) { + if (amendedPermission != null) return commandSender.hasPermission(amendedPermission); - } + HelpTopic aliasForTopic = helpMap.getHelpTopic(aliasFor); + return aliasForTopic != null && aliasForTopic.canSee(commandSender); } } diff --git a/src/main/java/ch/njol/skript/conditions/CondIsSkriptCommand.java b/src/main/java/ch/njol/skript/conditions/CondIsSkriptCommand.java index f8a6a2f7327..4c4f8097ba4 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsSkriptCommand.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsSkriptCommand.java @@ -18,7 +18,7 @@ */ package ch.njol.skript.conditions; -import static ch.njol.skript.command.Commands.skriptCommandExists; +import static ch.njol.skript.command.Commands.scriptCommandExists; import ch.njol.skript.conditions.base.PropertyCondition; import ch.njol.skript.doc.Description; @@ -44,7 +44,7 @@ public class CondIsSkriptCommand extends PropertyCondition<String> { @Override public boolean check(String cmd) { - return skriptCommandExists(cmd); + return scriptCommandExists(cmd); } @Override diff --git a/src/main/resources/config.sk b/src/main/resources/config.sk index a95cfe461b1..54a9bb7a070 100644 --- a/src/main/resources/config.sk +++ b/src/main/resources/config.sk @@ -76,6 +76,10 @@ allow ops to use effect commands: false # Whether server operators which do not have the permission "skript.effectcommands" should have access to effect commands. # This setting is mainly useful for servers that do not run any permissions plugin. +log effect commands: false +# Whether Skript should log the usage of effect commands. +# They will be logged as [INFORMATION] in this format: '<player> issued effect command: <command>' + 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 @@ -119,11 +123,6 @@ plugin priority: high # Skript removes drops it shouldn't => decrease priority or specify which item types to remove -log player commands: false -# Whether Skript should log the usage of custom commands. -# They will be logged as [INFORMATION] in this format: '<player>: /<command> <arguments>' - - number accuracy: 2 # How many digits should be displayed after the dot at maximum when displaying numbers. # Zeroes will never be displayed at all, so this setting only applies to numbers that actually have a decimal part with one or more non-zero digits. From fd23bc0fbc7e5f6856369b1d2069a236d5381c2a Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Tue, 26 Sep 2023 15:23:58 -0600 Subject: [PATCH 485/619] Add support for InventoryClickEvent cursor (#5308) --- .../skript/expressions/ExprCursorSlot.java | 47 ++++++++++----- .../ch/njol/skript/util/slot/CursorSlot.java | 59 +++++++++++++------ 2 files changed, 74 insertions(+), 32 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprCursorSlot.java b/src/main/java/ch/njol/skript/expressions/ExprCursorSlot.java index 493b84c3d6e..c81e738ec53 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprCursorSlot.java +++ b/src/main/java/ch/njol/skript/expressions/ExprCursorSlot.java @@ -19,45 +19,62 @@ package ch.njol.skript.expressions; import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.inventory.InventoryClickEvent; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; -import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.expressions.base.PropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.util.slot.CursorSlot; import ch.njol.skript.util.slot.Slot; +import ch.njol.util.Kleenean; /** * Cursor item slot is not actually an inventory slot, but an item which the player * has in their cursor when any inventory is open for them. */ @Name("Cursor Slot") -@Description("The item which the player has on their cursor. This slot is always empty if player has no inventories open.") -@Examples({"cursor slot of player is dirt", - "set cursor slot of player to 64 diamonds"}) +@Description("The item which the player has on their inventory cursor. This slot is always empty if player has no inventory open.") +@Examples({ + "cursor slot of player is dirt", + "set cursor slot of player to 64 diamonds" +}) @Since("2.2-dev17") -public class ExprCursorSlot extends SimplePropertyExpression<Player, Slot> { - +public class ExprCursorSlot extends PropertyExpression<Player, Slot> { + static { register(ExprCursorSlot.class, Slot.class, "cursor slot", "players"); } @Override - public Class<? extends Slot> getReturnType() { - return Slot.class; + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + setExpr((Expression<? extends Player>) exprs[0]); + return true; + } + + @Override + protected Slot[] get(Event event, Player[] source) { + return get(source, player -> { + if (event instanceof InventoryClickEvent) + return new CursorSlot(player, ((InventoryClickEvent) event).getCursor()); + return new CursorSlot(player); + }); } - + @Override - protected String getPropertyName() { - return "cursor slot"; + public Class<? extends Slot> getReturnType() { + return Slot.class; } - + @Override - @Nullable - public Slot convert(final Player player) { - return new CursorSlot(player); + public String toString(@Nullable Event event, boolean debug) { + return "cursor slot of " + getExpr().toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/util/slot/CursorSlot.java b/src/main/java/ch/njol/skript/util/slot/CursorSlot.java index 97bc44b07fd..387f0603037 100644 --- a/src/main/java/ch/njol/skript/util/slot/CursorSlot.java +++ b/src/main/java/ch/njol/skript/util/slot/CursorSlot.java @@ -20,6 +20,7 @@ import org.bukkit.entity.Player; import org.bukkit.event.Event; +import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.inventory.ItemStack; import org.eclipse.jdt.annotation.Nullable; @@ -27,23 +28,42 @@ import ch.njol.skript.registrations.Classes; /** - * Item that is in player's cursor. + * Item that represents a player's inventory cursor. */ public class CursorSlot extends Slot { - + + /** + * Represents the cursor as it was used in an InventoryClickEvent. + */ + @Nullable + private final ItemStack eventItemStack; private final Player player; - - public CursorSlot(Player p) { - this.player = p; + + public CursorSlot(Player player) { + this(player, null); } - + + /** + * Represents the cursor as it was used in an InventoryClickEvent. + * Should use this constructor if the event was an InventoryClickEvent. + * + * @param player The player that this cursor slot belongs to. + * @param eventItemStack The ItemStack from {@link InventoryClickEvent#getCursor()} if event is an InventoryClickEvent. + */ + public CursorSlot(Player player, @Nullable ItemStack eventItemStack) { + this.eventItemStack = eventItemStack; + this.player = player; + } + public Player getPlayer() { return player; } - + @Override @Nullable public ItemStack getItem() { + if (eventItemStack != null) + return eventItemStack; return player.getItemOnCursor(); } @@ -52,27 +72,32 @@ public void setItem(@Nullable ItemStack item) { player.setItemOnCursor(item); PlayerUtils.updateInventory(player); } - + @Override public int getAmount() { - return player.getItemOnCursor().getAmount(); + return getItem().getAmount(); } - + @Override public void setAmount(int amount) { - player.getItemOnCursor().setAmount(amount); + getItem().setAmount(amount); + } + + public boolean isInventoryClick() { + return eventItemStack != null; } - + @Override - public boolean isSameSlot(Slot o) { - if (!(o instanceof CursorSlot)) + public boolean isSameSlot(Slot slot) { + if (!(slot instanceof CursorSlot)) return false; - return ((CursorSlot) o).getPlayer().equals(this.player); + CursorSlot cursor = (CursorSlot) slot; + return cursor.getPlayer().equals(this.player) && cursor.isInventoryClick() == isInventoryClick(); } @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { return "cursor slot of " + Classes.toString(player); } - + } From 58f1c9f5ff1ab3bf38c9960699e9c15738081dc4 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Tue, 29 Aug 2023 23:40:50 -0700 Subject: [PATCH 486/619] Clean up vector classes and fix a few bugs. --- .../effects/EffVectorRotateAroundAnother.java | 15 ++--- .../skript/effects/EffVectorRotateXYZ.java | 25 ++++---- .../expressions/ExprVectorAngleBetween.java | 13 ++-- .../expressions/ExprVectorArithmetic.java | 16 ++--- .../ExprVectorBetweenLocations.java | 13 ++-- .../expressions/ExprVectorCrossProduct.java | 13 ++-- .../expressions/ExprVectorCylindrical.java | 21 +++---- .../expressions/ExprVectorDotProduct.java | 20 ++---- .../skript/expressions/ExprVectorFromXYZ.java | 15 ++--- .../ExprVectorFromYawAndPitch.java | 13 ++-- .../skript/expressions/ExprVectorLength.java | 38 ++++++----- .../expressions/ExprVectorNormalize.java | 11 ++-- .../expressions/ExprVectorOfLocation.java | 11 ++-- .../expressions/ExprVectorSpherical.java | 21 +++---- .../expressions/ExprVectorSquaredLength.java | 12 ++-- .../skript/expressions/ExprVectorXYZ.java | 63 ++++++++++--------- 16 files changed, 134 insertions(+), 186 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffVectorRotateAroundAnother.java b/src/main/java/ch/njol/skript/effects/EffVectorRotateAroundAnother.java index ad94cfa9ad2..5b49c1a0888 100644 --- a/src/main/java/ch/njol/skript/effects/EffVectorRotateAroundAnother.java +++ b/src/main/java/ch/njol/skript/effects/EffVectorRotateAroundAnother.java @@ -33,9 +33,6 @@ import ch.njol.util.Kleenean; import ch.njol.util.VectorMath; -/** - * @author bi0qaw - */ @Name("Vectors - Rotate Around Vector") @Description("Rotates a vector around another vector") @Examples({"rotate {_v} around vector 1, 0, 0 by 90"}) @@ -63,18 +60,18 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean kleenean @SuppressWarnings("null") @Override - protected void execute(Event e) { - Vector v2 = second.getSingle(e); - Number d = degree.getSingle(e); + protected void execute(Event event) { + Vector v2 = second.getSingle(event); + Number d = degree.getSingle(event); if (v2 == null || d == null) return; - for (Vector v1 : first.getArray(e)) + for (Vector v1 : first.getArray(event)) VectorMath.rot(v1, v2, d.doubleValue()); } @Override - public String toString(@Nullable Event e, boolean debug) { - return "rotate " + first.toString(e, debug) + " around " + second.toString(e, debug) + " by " + degree + "degrees"; + public String toString(@Nullable Event event, boolean debug) { + return "rotate " + first.toString(event, debug) + " around " + second.toString(event, debug) + " by " + degree.toString(event, debug) + "degrees"; } } diff --git a/src/main/java/ch/njol/skript/effects/EffVectorRotateXYZ.java b/src/main/java/ch/njol/skript/effects/EffVectorRotateXYZ.java index 2c3cd5d3d94..06d4b3d98e2 100644 --- a/src/main/java/ch/njol/skript/effects/EffVectorRotateXYZ.java +++ b/src/main/java/ch/njol/skript/effects/EffVectorRotateXYZ.java @@ -33,9 +33,6 @@ import ch.njol.util.Kleenean; import ch.njol.util.VectorMath; -/** - * @author bi0qaw - */ @Name("Vectors - Rotate around XYZ") @Description("Rotates a vector around x, y, or z axis by some degrees") @Examples({"rotate {_v} around x-axis by 90", @@ -45,7 +42,7 @@ public class EffVectorRotateXYZ extends Effect { static { - Skript.registerEffect(EffVectorRotateXYZ.class, "rotate %vectors% around (1¦x|2¦y|3¦z)(-| )axis by %number% [degrees]"); + Skript.registerEffect(EffVectorRotateXYZ.class, "rotate %vectors% around (0¦x|1¦y|2¦z)(-| )axis by %number% [degrees]"); } private final static Character[] axes = new Character[] {'x', 'y', 'z'}; @@ -68,28 +65,28 @@ public boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean is @Override @SuppressWarnings("null") - protected void execute(Event e) { - Number d = degree.getSingle(e); + protected void execute(Event event) { + Number d = degree.getSingle(event); if (d == null) return; switch (axis) { - case 1: - for (Vector v : vectors.getArray(e)) + case 0: + for (Vector v : vectors.getArray(event)) VectorMath.rotX(v, d.doubleValue()); break; - case 2: - for (Vector v : vectors.getArray(e)) + case 1: + for (Vector v : vectors.getArray(event)) VectorMath.rotY(v, d.doubleValue()); break; - case 3: - for (Vector v : vectors.getArray(e)) + case 2: + for (Vector v : vectors.getArray(event)) VectorMath.rotZ(v, d.doubleValue()); } } @Override - public String toString(@Nullable Event e, boolean debug) { - return "rotate " + vectors.toString(e, debug) + " around " + axes[axis] + "-axis" + " by " + degree + "degrees"; + public String toString(@Nullable Event event, boolean debug) { + return "rotate " + vectors.toString(event, debug) + " around " + axes[axis] + "-axis" + " by " + degree.toString(event, debug) + "degrees"; } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorAngleBetween.java b/src/main/java/ch/njol/skript/expressions/ExprVectorAngleBetween.java index 07d3cb2867d..831ee61de67 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorAngleBetween.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorAngleBetween.java @@ -35,9 +35,6 @@ import ch.njol.util.VectorMath; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Angle Between") @Description("Gets the angle between two vectors.") @Examples({"send \"%the angle between vector 1, 0, 0 and vector 0, 1, 1%\""}) @@ -62,9 +59,9 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") - protected Number[] get(Event e) { - Vector v1 = first.getSingle(e); - Vector v2 = second.getSingle(e); + protected Number[] get(Event event) { + Vector v1 = first.getSingle(event); + Vector v2 = second.getSingle(event); if (v1 == null || v2 == null) return null; return CollectionUtils.array(v1.angle(v2) * (float) VectorMath.RAD_TO_DEG); @@ -81,8 +78,8 @@ public Class<? extends Number> getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return "the angle between " + first.toString(e, debug) + " and " + second.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "the angle between " + first.toString(event, debug) + " and " + second.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java b/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java index 1dc75efd574..4d1c7cf89ea 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java @@ -35,9 +35,6 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Arithmetic") @Description("Arithmetic expressions for vectors.") @Examples({"set {_v} to vector 1, 2, 3 // 5", @@ -119,12 +116,9 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye } @Override - protected Vector[] get(Event e) { - Vector v1 = first.getSingle(e), v2 = second.getSingle(e); - if (v1 == null) - v1 = new Vector(); - if (v2 == null) - v2 = new Vector(); + protected Vector[] get(Event event) { + Vector v1 = first.getOptionalSingle(event).orElse(new Vector()); + Vector v2 = second.getOptionalSingle(event).orElse(new Vector()); return CollectionUtils.array(op.calculate(v1, v2)); } @@ -139,8 +133,8 @@ public Class<? extends Vector> getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return first.toString(e, debug) + " " + op + " " + second.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return first.toString(event, debug) + " " + op + " " + second.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorBetweenLocations.java b/src/main/java/ch/njol/skript/expressions/ExprVectorBetweenLocations.java index 8b407c38dd2..11572102eed 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorBetweenLocations.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorBetweenLocations.java @@ -35,9 +35,6 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Vector Between Locations") @Description("Creates a vector between two locations.") @Examples({"set {_v} to vector between {_loc1} and {_loc2}"}) @@ -62,9 +59,9 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") - protected Vector[] get(Event e) { - Location l1 = from.getSingle(e); - Location l2 = to.getSingle(e); + protected Vector[] get(Event event) { + Location l1 = from.getSingle(event); + Location l2 = to.getSingle(event); if (l1 == null || l2 == null) return null; return CollectionUtils.array(new Vector(l2.getX() - l1.getX(), l2.getY() - l1.getY(), l2.getZ() - l1.getZ())); @@ -80,8 +77,8 @@ public Class<? extends Vector> getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return "vector from " + from.toString(e, debug) + " to " + to.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "vector from " + from.toString(event, debug) + " to " + to.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorCrossProduct.java b/src/main/java/ch/njol/skript/expressions/ExprVectorCrossProduct.java index 11d040f2855..8bb22ffd59a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorCrossProduct.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorCrossProduct.java @@ -34,9 +34,6 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Cross Product") @Description("Gets the cross product between two vectors.") @Examples({"send \"%vector 1, 0, 0 cross vector 0, 1, 0%\""}) @@ -60,9 +57,9 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") - protected Vector[] get(Event e) { - Vector v1 = first.getSingle(e); - Vector v2 = second.getSingle(e); + protected Vector[] get(Event event) { + Vector v1 = first.getSingle(event); + Vector v2 = second.getSingle(event); if (v1 == null || v2 == null) return null; return CollectionUtils.array(v1.clone().crossProduct(v2)); @@ -79,8 +76,8 @@ public Class<? extends Vector> getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return first.toString(e, debug) + " cross " + second.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return first.toString(event, debug) + " cross " + second.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorCylindrical.java b/src/main/java/ch/njol/skript/expressions/ExprVectorCylindrical.java index 65d4fc5134c..48e940a2450 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorCylindrical.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorCylindrical.java @@ -35,9 +35,6 @@ import ch.njol.util.VectorMath; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Cylindrical Shape") @Description("Forms a 'cylindrical shaped' vector using yaw to manipulate the current point.") @Examples({"loop 360 times:", @@ -65,13 +62,13 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") - protected Vector[] get(Event e) { - Number r = radius.getSingle(e); - Number y = yaw.getSingle(e); - Number h = height.getSingle(e); - if (r == null || y == null || h == null) + protected Vector[] get(Event event) { + Number radius = this.radius.getSingle(event); + Number yaw = this.yaw.getSingle(event); + Number height = this.height.getSingle(event); + if (radius == null || yaw == null || height == null) return null; - return CollectionUtils.array(VectorMath.fromCylindricalCoordinates(r.doubleValue(), VectorMath.fromSkriptYaw(y.floatValue()), h.doubleValue())); + return CollectionUtils.array(VectorMath.fromCylindricalCoordinates(radius.doubleValue(), VectorMath.fromSkriptYaw(yaw.floatValue()), height.doubleValue())); } @Override @@ -85,9 +82,9 @@ public Class<? extends Vector> getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return "cylindrical vector with radius " + radius.toString(e, debug) + ", yaw " + - yaw.toString(e, debug) + " and height " + height.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "cylindrical vector with radius " + radius.toString(event, debug) + ", yaw " + + yaw.toString(event, debug) + " and height " + height.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorDotProduct.java b/src/main/java/ch/njol/skript/expressions/ExprVectorDotProduct.java index d5eb0a5c911..a02dacc5d94 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorDotProduct.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorDotProduct.java @@ -34,20 +34,10 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Dot Product") @Description("Gets the dot product between two vectors.") @Examples({"set {_v} to {_v2} dot {_v3}"}) @Since("2.2-dev28") -/** - * NOTE vector 1, 2, 3 dot vector 1, 2, 3 does NOT work! - * it returns a new vector: 1, 2, 18. This should not happen - * and I have no idea why it does. I have also no idea why - * "z" takes the value 18. There must be some black magic - * going on. - */ public class ExprVectorDotProduct extends SimpleExpression<Number> { static { @@ -67,9 +57,9 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") - protected Number[] get(Event e) { - Vector v1 = first.getSingle(e); - Vector v2 = second.getSingle(e); + protected Number[] get(Event event) { + Vector v1 = first.getSingle(event); + Vector v2 = second.getSingle(event); if (v1 == null || v2 == null) return null; return CollectionUtils.array(v1.getX() * v2.getX() + v1.getY() * v2.getY() + v1.getZ() * v2.getZ()); @@ -86,8 +76,8 @@ public Class<? extends Number> getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return first.toString(e, debug) + " dot " + second.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return first.toString(event, debug) + " dot " + second.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorFromXYZ.java b/src/main/java/ch/njol/skript/expressions/ExprVectorFromXYZ.java index 71896818d71..53ab07108dc 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorFromXYZ.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorFromXYZ.java @@ -34,9 +34,6 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Create from XYZ") @Description("Creates a vector from x, y and z values.") @Examples({"set {_v} to vector 0, 1, 0"}) @@ -62,10 +59,10 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") - protected Vector[] get(Event e) { - Number x = this.x.getSingle(e); - Number y = this.y.getSingle(e); - Number z = this.z.getSingle(e); + protected Vector[] get(Event event) { + Number x = this.x.getSingle(event); + Number y = this.y.getSingle(event); + Number z = this.z.getSingle(event); if (x == null || y == null || z == null) return null; return CollectionUtils.array(new Vector(x.doubleValue(), y.doubleValue(), z.doubleValue())); @@ -82,8 +79,8 @@ public Class<? extends Vector> getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return "vector from x " + x.toString(e, debug) + ", y " + y.toString(e, debug) + ", z " + z.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "vector from x " + x.toString(event, debug) + ", y " + y.toString(event, debug) + ", z " + z.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorFromYawAndPitch.java b/src/main/java/ch/njol/skript/expressions/ExprVectorFromYawAndPitch.java index 75a65274d0d..16c8590a1a4 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorFromYawAndPitch.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorFromYawAndPitch.java @@ -35,9 +35,6 @@ import ch.njol.util.VectorMath; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Vector from Pitch and Yaw") @Description("Creates a vector from a yaw and pitch value.") @Examples({"set {_v} to vector from yaw 45 and pitch 45"}) @@ -62,9 +59,9 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") - protected Vector[] get(Event e) { - Number y = yaw.getSingle(e); - Number p = pitch.getSingle(e); + protected Vector[] get(Event event) { + Number y = yaw.getSingle(event); + Number p = pitch.getSingle(event); if (y == null || p == null) return null; float yaw = VectorMath.fromSkriptYaw(VectorMath.wrapAngleDeg(y.floatValue())); @@ -83,8 +80,8 @@ public Class<? extends Vector> getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return "vector from yaw " + yaw.toString(e, debug) + " and pitch " + pitch.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "vector from yaw " + yaw.toString(event, debug) + " and pitch " + pitch.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorLength.java b/src/main/java/ch/njol/skript/expressions/ExprVectorLength.java index 8c2d1094141..193e3efd809 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorLength.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorLength.java @@ -30,9 +30,6 @@ import ch.njol.skript.expressions.base.SimplePropertyExpression; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Length") @Description("Gets or sets the length of a vector.") @Examples({"send \"%standard length of vector 1, 2, 3%\"", @@ -61,43 +58,44 @@ public Class<?>[] acceptChange(ChangeMode mode) { } @Override - public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { assert delta != null; - final Vector v = getExpr().getSingle(e); + final Vector v = getExpr().getSingle(event); if (v == null) return; - double n = ((Number) delta[0]).doubleValue(); + double deltaLength = ((Number) delta[0]).doubleValue(); switch (mode) { + case REMOVE: + deltaLength = -deltaLength; + //$FALL-THROUGH$ case ADD: - if (n < 0 && v.lengthSquared() < n * n) { + if (deltaLength < 0 && v.lengthSquared() < deltaLength * deltaLength) { v.zero(); } else { - double l = n + v.length(); + double l = deltaLength + v.length(); v.normalize().multiply(l); } - getExpr().change(e, new Vector[]{v}, ChangeMode.SET); + getExpr().change(event, new Vector[]{v}, ChangeMode.SET); break; - case REMOVE: - n = -n; - //$FALL-THROUGH$ case SET: - if (n < 0) + if (deltaLength < 0) { v.zero(); - else - v.normalize().multiply(n); - getExpr().change(e, new Vector[]{v}, ChangeMode.SET); + } else { + v.normalize().multiply(deltaLength); + } + getExpr().change(event, new Vector[]{v}, ChangeMode.SET); break; } } @Override - protected String getPropertyName() { - return "vector length"; + public Class<? extends Number> getReturnType() { + return Number.class; } @Override - public Class<? extends Number> getReturnType() { - return Number.class; + protected String getPropertyName() { + return "vector length"; } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorNormalize.java b/src/main/java/ch/njol/skript/expressions/ExprVectorNormalize.java index c450c074ddd..53a79531bc1 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorNormalize.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorNormalize.java @@ -34,9 +34,6 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Normalized") @Description("Returns the same vector but with length 1.") @Examples({"set {_v} to normalized {_v}"}) @@ -61,8 +58,8 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") - protected Vector[] get(Event e) { - Vector v = vector.getSingle(e); + protected Vector[] get(Event event) { + Vector v = vector.getSingle(event); if (v == null) return null; return CollectionUtils.array(v.clone().normalize()); @@ -79,8 +76,8 @@ public Class<? extends Vector> getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return "normalized " + vector.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "normalized " + vector.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorOfLocation.java b/src/main/java/ch/njol/skript/expressions/ExprVectorOfLocation.java index 21f71a3bef8..9ec6e643868 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorOfLocation.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorOfLocation.java @@ -35,9 +35,6 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Vector from Location") @Description("Creates a vector from a location.") @Examples({"set {_v} to vector of {_loc}"}) @@ -62,8 +59,8 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") - protected Vector[] get(Event e) { - Location l = location.getSingle(e); + protected Vector[] get(Event event) { + Location l = location.getSingle(event); if (l == null) return null; return CollectionUtils.array(l.toVector()); @@ -80,8 +77,8 @@ public Class<? extends Vector> getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return "vector from " + location.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "vector from " + location.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorSpherical.java b/src/main/java/ch/njol/skript/expressions/ExprVectorSpherical.java index 493c023eb95..4da884bf48e 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorSpherical.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorSpherical.java @@ -35,9 +35,6 @@ import ch.njol.util.VectorMath; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Spherical Shape") @Description("Forms a 'spherical shaped' vector using yaw and pitch to manipulate the current point.") @Examples({"loop 360 times:", @@ -65,13 +62,13 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") - protected Vector[] get(Event e) { - Number r = radius.getSingle(e); - Number y = yaw.getSingle(e); - Number p = pitch.getSingle(e); - if (r == null || y == null || p == null) + protected Vector[] get(Event event) { + Number radius = this.radius.getSingle(event); + Number yaw = this.yaw.getSingle(event); + Number pitch = this.pitch.getSingle(event); + if (radius == null || yaw == null || pitch == null) return null; - return CollectionUtils.array(VectorMath.fromSphericalCoordinates(r.doubleValue(), VectorMath.fromSkriptYaw(y.floatValue()), p.floatValue() + 90)); + return CollectionUtils.array(VectorMath.fromSphericalCoordinates(radius.doubleValue(), VectorMath.fromSkriptYaw(yaw.floatValue()), pitch.floatValue() + 90)); } @Override @@ -85,9 +82,9 @@ public Class<? extends Vector> getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return "spherical vector with radius " + radius.toString(e, debug) + ", yaw " + yaw.toString(e, debug) + - " and pitch" + pitch.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "spherical vector with radius " + radius.toString(event, debug) + ", yaw " + yaw.toString(event, debug) + + " and pitch" + pitch.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorSquaredLength.java b/src/main/java/ch/njol/skript/expressions/ExprVectorSquaredLength.java index ef5444347ca..fd81a40a9cf 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorSquaredLength.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorSquaredLength.java @@ -26,9 +26,6 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.SimplePropertyExpression; -/** - * @author bi0qaw - */ @Name("Vectors - Squared Length") @Description("Gets the squared length of a vector.") @Examples({"send \"%squared length of vector 1, 2, 3%\""}) @@ -46,13 +43,14 @@ public Number convert(Vector vector) { } @Override - protected String getPropertyName() { - return "squared length of vector"; + public Class<? extends Number> getReturnType() { + return Number.class; } @Override - public Class<? extends Number> getReturnType() { - return Number.class; + protected String getPropertyName() { + return "squared length of vector"; } + } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorXYZ.java b/src/main/java/ch/njol/skript/expressions/ExprVectorXYZ.java index 76b5ab59fe2..95cbcff1645 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorXYZ.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorXYZ.java @@ -34,9 +34,6 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - XYZ Component") @Description("Gets or changes the x, y or z component of a vector.") @Examples({"set {_v} to vector 1, 2, 3", @@ -76,50 +73,54 @@ public Number convert(Vector v) { @SuppressWarnings("null") public Class<?>[] acceptChange(ChangeMode mode) { if ((mode == ChangeMode.ADD || mode == ChangeMode.REMOVE || mode == ChangeMode.SET) - && getExpr().isSingle() && Changer.ChangerUtils.acceptsChange(getExpr(), ChangeMode.SET, Vector.class)) + && Changer.ChangerUtils.acceptsChange(getExpr(), ChangeMode.SET, Vector.class)) return CollectionUtils.array(Number.class); return null; } @Override - public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { assert delta != null; - final Vector v = getExpr().getSingle(e); - if (v == null) - return; - double n = ((Number) delta[0]).doubleValue(); + Vector[] vectors = getExpr().getArray(event); + double deltaValue = ((Number) delta[0]).doubleValue(); switch (mode) { case REMOVE: - n = -n; + deltaValue = -deltaValue; //$FALL-THROUGH$ case ADD: - if (axis == 0) - v.setX(v.getX() + n); - else if (axis == 1) - v.setY(v.getY() + n); - else - v.setZ(v.getZ() + n); - getExpr().change(e, new Vector[] {v}, ChangeMode.SET); + for (Vector v : vectors) { + if (axis == 0) + v.setX(v.getX() + deltaValue); + else if (axis == 1) + v.setY(v.getY() + deltaValue); + else + v.setZ(v.getZ() + deltaValue); + } break; case SET: - if (axis == 0) - v.setX(n); - else if (axis == 1) - v.setY(n); - else - v.setZ(n); - getExpr().change(e, new Vector[] {v}, ChangeMode.SET); + for (Vector v : vectors) { + if (axis == 0) + v.setX(deltaValue); + else if (axis == 1) + v.setY(deltaValue); + else + v.setZ(deltaValue); + } + break; + default: + assert false; + return; } + getExpr().change(event, vectors, ChangeMode.SET); } - - @Override - protected String getPropertyName() { - return axes[axis] + " component"; - } - + @Override public Class<Number> getReturnType() { return Number.class; } - + + @Override + protected String getPropertyName() { + return axes[axis] + " component"; + } } From 50f0f3c6f46f8e715fa698470082f46e5abc52ce Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Wed, 30 Aug 2023 19:10:08 -0700 Subject: [PATCH 487/619] More improvements --- .../effects/EffVectorRotateAroundAnother.java | 14 +++--- .../skript/effects/EffVectorRotateXYZ.java | 20 +++++---- .../expressions/ExprVectorAngleBetween.java | 4 +- .../expressions/ExprVectorArithmetic.java | 24 +++++------ .../ExprVectorBetweenLocations.java | 12 +++--- .../expressions/ExprVectorCrossProduct.java | 4 +- .../expressions/ExprVectorCylindrical.java | 8 ++-- .../expressions/ExprVectorDotProduct.java | 4 +- .../skript/expressions/ExprVectorFromXYZ.java | 4 +- .../ExprVectorFromYawAndPitch.java | 14 +++--- .../skript/expressions/ExprVectorLength.java | 43 +++++++++++-------- .../expressions/ExprVectorNormalize.java | 9 +++- .../expressions/ExprVectorOfLocation.java | 10 +++-- .../expressions/ExprVectorSpherical.java | 8 ++-- .../expressions/ExprVectorSquaredLength.java | 4 +- .../skript/expressions/ExprVectorXYZ.java | 22 +++++----- 16 files changed, 120 insertions(+), 84 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffVectorRotateAroundAnother.java b/src/main/java/ch/njol/skript/effects/EffVectorRotateAroundAnother.java index 5b49c1a0888..1fba62a7ab8 100644 --- a/src/main/java/ch/njol/skript/effects/EffVectorRotateAroundAnother.java +++ b/src/main/java/ch/njol/skript/effects/EffVectorRotateAroundAnother.java @@ -34,8 +34,10 @@ import ch.njol.util.VectorMath; @Name("Vectors - Rotate Around Vector") -@Description("Rotates a vector around another vector") -@Examples({"rotate {_v} around vector 1, 0, 0 by 90"}) +@Description("Rotates one or more vectors around another vector") +@Examples({ + "rotate {_v} around vector 1, 0, 0 by 90" +}) @Since("2.2-dev28") public class EffVectorRotateAroundAnother extends Effect { @@ -61,12 +63,12 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean kleenean @SuppressWarnings("null") @Override protected void execute(Event event) { - Vector v2 = second.getSingle(event); - Number d = degree.getSingle(event); - if (v2 == null || d == null) + Vector axis = second.getSingle(event); + Number angle = degree.getSingle(event); + if (axis == null || angle == null) return; for (Vector v1 : first.getArray(event)) - VectorMath.rot(v1, v2, d.doubleValue()); + VectorMath.rot(v1, axis, angle.doubleValue()); } @Override diff --git a/src/main/java/ch/njol/skript/effects/EffVectorRotateXYZ.java b/src/main/java/ch/njol/skript/effects/EffVectorRotateXYZ.java index 06d4b3d98e2..0e95f79ca6f 100644 --- a/src/main/java/ch/njol/skript/effects/EffVectorRotateXYZ.java +++ b/src/main/java/ch/njol/skript/effects/EffVectorRotateXYZ.java @@ -34,10 +34,12 @@ import ch.njol.util.VectorMath; @Name("Vectors - Rotate around XYZ") -@Description("Rotates a vector around x, y, or z axis by some degrees") -@Examples({"rotate {_v} around x-axis by 90", - "rotate {_v} around y-axis by 90", - "rotate {_v} around z-axis by 90 degrees"}) +@Description("Rotates one or more vectors around the x, y, or z axis by some amount of degrees") +@Examples({ + "rotate {_v} around x-axis by 90", + "rotate {_v} around y-axis by 90", + "rotate {_v} around z-axis by 90 degrees" +}) @Since("2.2-dev28") public class EffVectorRotateXYZ extends Effect { @@ -66,21 +68,21 @@ public boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean is @Override @SuppressWarnings("null") protected void execute(Event event) { - Number d = degree.getSingle(event); - if (d == null) + Number angle = degree.getSingle(event); + if (angle == null) return; switch (axis) { case 0: for (Vector v : vectors.getArray(event)) - VectorMath.rotX(v, d.doubleValue()); + VectorMath.rotX(v, angle.doubleValue()); break; case 1: for (Vector v : vectors.getArray(event)) - VectorMath.rotY(v, d.doubleValue()); + VectorMath.rotY(v, angle.doubleValue()); break; case 2: for (Vector v : vectors.getArray(event)) - VectorMath.rotZ(v, d.doubleValue()); + VectorMath.rotZ(v, angle.doubleValue()); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorAngleBetween.java b/src/main/java/ch/njol/skript/expressions/ExprVectorAngleBetween.java index 831ee61de67..d6675ca2712 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorAngleBetween.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorAngleBetween.java @@ -37,7 +37,9 @@ @Name("Vectors - Angle Between") @Description("Gets the angle between two vectors.") -@Examples({"send \"%the angle between vector 1, 0, 0 and vector 0, 1, 1%\""}) +@Examples({ + "send \"%the angle between vector 1, 0, 0 and vector 0, 1, 1%\"" +}) @Since("2.2-dev28") public class ExprVectorAngleBetween extends SimpleExpression<Number> { diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java b/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java index 4d1c7cf89ea..7cfc91f6948 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java @@ -37,15 +37,13 @@ @Name("Vectors - Arithmetic") @Description("Arithmetic expressions for vectors.") -@Examples({"set {_v} to vector 1, 2, 3 // 5", - "set {_v} to {_v} ++ {_v}", - "set {_v} to {_v} ++ 5", - "set {_v} to {_v} -- {_v}", - "set {_v} to {_v} -- 5", - "set {_v} to {_v} ** {_v}", - "set {_v} to {_v} ** 5", - "set {_v} to {_v} // {_v}", - "set {_v} to {_v} // 5"}) +@Examples({ + "set {_v} to vector 1, 2, 3 // vector 5, 5, 5", + "set {_v} to {_v} ++ {_v}", + "set {_v} to {_v} -- {_v}", + "set {_v} to {_v} ** {_v}", + "set {_v} to {_v} // {_v}" +}) @Since("2.2-dev28") public class ExprVectorArithmetic extends SimpleExpression<Vector> { @@ -104,14 +102,14 @@ public String toString() { private Expression<Vector> first, second; @SuppressWarnings("null") - private Operator op; + private Operator operator; @Override @SuppressWarnings({"unchecked", "null"}) public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { first = (Expression<Vector>) exprs[0]; second = (Expression<Vector>) exprs[1]; - op = patterns.getInfo(matchedPattern); + operator = patterns.getInfo(matchedPattern); return true; } @@ -119,7 +117,7 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye protected Vector[] get(Event event) { Vector v1 = first.getOptionalSingle(event).orElse(new Vector()); Vector v2 = second.getOptionalSingle(event).orElse(new Vector()); - return CollectionUtils.array(op.calculate(v1, v2)); + return CollectionUtils.array(operator.calculate(v1, v2)); } @Override @@ -134,7 +132,7 @@ public Class<? extends Vector> getReturnType() { @Override public String toString(@Nullable Event event, boolean debug) { - return first.toString(event, debug) + " " + op + " " + second.toString(event, debug); + return first.toString(event, debug) + " " + operator + " " + second.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorBetweenLocations.java b/src/main/java/ch/njol/skript/expressions/ExprVectorBetweenLocations.java index 11572102eed..346e1f4e386 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorBetweenLocations.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorBetweenLocations.java @@ -37,7 +37,9 @@ @Name("Vectors - Vector Between Locations") @Description("Creates a vector between two locations.") -@Examples({"set {_v} to vector between {_loc1} and {_loc2}"}) +@Examples({ + "set {_v} to vector between {_loc1} and {_loc2}" +}) @Since("2.2-dev28") public class ExprVectorBetweenLocations extends SimpleExpression<Vector> { @@ -60,11 +62,11 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") protected Vector[] get(Event event) { - Location l1 = from.getSingle(event); - Location l2 = to.getSingle(event); - if (l1 == null || l2 == null) + Location from = this.from.getSingle(event); + Location to = this.to.getSingle(event); + if (from == null || to == null) return null; - return CollectionUtils.array(new Vector(l2.getX() - l1.getX(), l2.getY() - l1.getY(), l2.getZ() - l1.getZ())); + return CollectionUtils.array(new Vector(to.getX() - from.getX(), to.getY() - from.getY(), to.getZ() - from.getZ())); } @Override diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorCrossProduct.java b/src/main/java/ch/njol/skript/expressions/ExprVectorCrossProduct.java index 8bb22ffd59a..4afa99e2e02 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorCrossProduct.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorCrossProduct.java @@ -36,7 +36,9 @@ @Name("Vectors - Cross Product") @Description("Gets the cross product between two vectors.") -@Examples({"send \"%vector 1, 0, 0 cross vector 0, 1, 0%\""}) +@Examples({ + "send \"%vector 1, 0, 0 cross vector 0, 1, 0%\"" +}) @Since("2.2-dev28") public class ExprVectorCrossProduct extends SimpleExpression<Vector> { diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorCylindrical.java b/src/main/java/ch/njol/skript/expressions/ExprVectorCylindrical.java index 48e940a2450..40cd9edd963 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorCylindrical.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorCylindrical.java @@ -37,9 +37,11 @@ @Name("Vectors - Cylindrical Shape") @Description("Forms a 'cylindrical shaped' vector using yaw to manipulate the current point.") -@Examples({"loop 360 times:", - " set {_v} to cylindrical vector radius 1, yaw loop-value, height 2", - "set {_v} to cylindrical vector radius 1, yaw 90, height 2"}) +@Examples({ + "loop 360 times:", + "\tset {_v} to cylindrical vector radius 1, yaw loop-value, height 2", + "set {_v} to cylindrical vector radius 1, yaw 90, height 2" +}) @Since("2.2-dev28") public class ExprVectorCylindrical extends SimpleExpression<Vector> { diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorDotProduct.java b/src/main/java/ch/njol/skript/expressions/ExprVectorDotProduct.java index a02dacc5d94..d1f9154a254 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorDotProduct.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorDotProduct.java @@ -36,7 +36,9 @@ @Name("Vectors - Dot Product") @Description("Gets the dot product between two vectors.") -@Examples({"set {_v} to {_v2} dot {_v3}"}) +@Examples({ + "set {_dot} to {_v1} dot {_v2}" +}) @Since("2.2-dev28") public class ExprVectorDotProduct extends SimpleExpression<Number> { diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorFromXYZ.java b/src/main/java/ch/njol/skript/expressions/ExprVectorFromXYZ.java index 53ab07108dc..ff05bb78fb6 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorFromXYZ.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorFromXYZ.java @@ -36,7 +36,9 @@ @Name("Vectors - Create from XYZ") @Description("Creates a vector from x, y and z values.") -@Examples({"set {_v} to vector 0, 1, 0"}) +@Examples({ + "set {_v} to vector 0, 1, 0" +}) @Since("2.2-dev28") public class ExprVectorFromXYZ extends SimpleExpression<Vector> { diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorFromYawAndPitch.java b/src/main/java/ch/njol/skript/expressions/ExprVectorFromYawAndPitch.java index 16c8590a1a4..e3fa1989f9f 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorFromYawAndPitch.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorFromYawAndPitch.java @@ -37,7 +37,9 @@ @Name("Vectors - Vector from Pitch and Yaw") @Description("Creates a vector from a yaw and pitch value.") -@Examples({"set {_v} to vector from yaw 45 and pitch 45"}) +@Examples({ + "set {_v} to vector from yaw 45 and pitch 45" +}) @Since("2.2-dev28") public class ExprVectorFromYawAndPitch extends SimpleExpression<Vector> { @@ -60,12 +62,12 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") protected Vector[] get(Event event) { - Number y = yaw.getSingle(event); - Number p = pitch.getSingle(event); - if (y == null || p == null) + Number skriptYaw = yaw.getSingle(event); + Number skriptPitch = pitch.getSingle(event); + if (skriptYaw == null || skriptPitch == null) return null; - float yaw = VectorMath.fromSkriptYaw(VectorMath.wrapAngleDeg(y.floatValue())); - float pitch = VectorMath.fromSkriptPitch(VectorMath.wrapAngleDeg(p.floatValue())); + float yaw = VectorMath.fromSkriptYaw(VectorMath.wrapAngleDeg(skriptYaw.floatValue())); + float pitch = VectorMath.fromSkriptPitch(VectorMath.wrapAngleDeg(skriptPitch.floatValue())); return CollectionUtils.array(VectorMath.fromYawAndPitch(yaw, pitch)); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorLength.java b/src/main/java/ch/njol/skript/expressions/ExprVectorLength.java index 193e3efd809..490294384a5 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorLength.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorLength.java @@ -32,10 +32,12 @@ @Name("Vectors - Length") @Description("Gets or sets the length of a vector.") -@Examples({"send \"%standard length of vector 1, 2, 3%\"", - "set {_v} to vector 1, 2, 3", - "set standard length of {_v} to 2", - "send \"%standard length of {_v}%\""}) +@Examples({ + "send \"%standard length of vector 1, 2, 3%\"", + "set {_v} to vector 1, 2, 3", + "set standard length of {_v} to 2", + "send \"%standard length of {_v}%\"" +}) @Since("2.2-dev28") public class ExprVectorLength extends SimplePropertyExpression<Vector, Number> { @@ -60,32 +62,37 @@ public Class<?>[] acceptChange(ChangeMode mode) { @Override public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { assert delta != null; - final Vector v = getExpr().getSingle(event); - if (v == null) - return; + final Vector[] vectors = getExpr().getArray(event); double deltaLength = ((Number) delta[0]).doubleValue(); switch (mode) { case REMOVE: deltaLength = -deltaLength; //$FALL-THROUGH$ case ADD: - if (deltaLength < 0 && v.lengthSquared() < deltaLength * deltaLength) { - v.zero(); - } else { - double l = deltaLength + v.length(); - v.normalize().multiply(l); + for (Vector v : vectors) { + if (deltaLength < 0 && v.lengthSquared() < deltaLength * deltaLength) { + v.zero(); + } else { + double newLength = deltaLength + v.length(); + if (!v.isNormalized()) + v.normalize(); + v.multiply(newLength); + } } - getExpr().change(event, new Vector[]{v}, ChangeMode.SET); break; case SET: - if (deltaLength < 0) { - v.zero(); - } else { - v.normalize().multiply(deltaLength); + for (Vector v : vectors) { + if (deltaLength < 0) { + v.zero(); + } else { + if (!v.isNormalized()) + v.normalize(); + v.multiply(deltaLength); + } } - getExpr().change(event, new Vector[]{v}, ChangeMode.SET); break; } + getExpr().change(event, vectors, ChangeMode.SET); } @Override diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorNormalize.java b/src/main/java/ch/njol/skript/expressions/ExprVectorNormalize.java index 53a79531bc1..f734cc46956 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorNormalize.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorNormalize.java @@ -36,7 +36,9 @@ @Name("Vectors - Normalized") @Description("Returns the same vector but with length 1.") -@Examples({"set {_v} to normalized {_v}"}) +@Examples({ + "set {_v} to normalized {_v}" +}) @Since("2.2-dev28") public class ExprVectorNormalize extends SimpleExpression<Vector> { @@ -62,7 +64,10 @@ protected Vector[] get(Event event) { Vector v = vector.getSingle(event); if (v == null) return null; - return CollectionUtils.array(v.clone().normalize()); + v = v.clone(); + if (!v.isNormalized()) + v.normalize(); + return CollectionUtils.array(v); } @Override diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorOfLocation.java b/src/main/java/ch/njol/skript/expressions/ExprVectorOfLocation.java index 9ec6e643868..a0f50881f4f 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorOfLocation.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorOfLocation.java @@ -37,7 +37,9 @@ @Name("Vectors - Vector from Location") @Description("Creates a vector from a location.") -@Examples({"set {_v} to vector of {_loc}"}) +@Examples({ + "set {_v} to vector of {_loc}" +}) @Since("2.2-dev28") public class ExprVectorOfLocation extends SimpleExpression<Vector> { @@ -60,10 +62,10 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") protected Vector[] get(Event event) { - Location l = location.getSingle(event); - if (l == null) + Location location = this.location.getSingle(event); + if (location == null) return null; - return CollectionUtils.array(l.toVector()); + return CollectionUtils.array(location.toVector()); } @Override diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorSpherical.java b/src/main/java/ch/njol/skript/expressions/ExprVectorSpherical.java index 4da884bf48e..3507f8ab25a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorSpherical.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorSpherical.java @@ -37,9 +37,11 @@ @Name("Vectors - Spherical Shape") @Description("Forms a 'spherical shaped' vector using yaw and pitch to manipulate the current point.") -@Examples({"loop 360 times:", - " set {_v} to spherical vector radius 1, yaw loop-value, pitch loop-value", - "set {_v} to spherical vector radius 1, yaw 45, pitch 90"}) +@Examples({ + "loop 360 times:", + "\tset {_v} to spherical vector radius 1, yaw loop-value, pitch loop-value", + "set {_v} to spherical vector radius 1, yaw 45, pitch 90" +}) @Since("2.2-dev28") public class ExprVectorSpherical extends SimpleExpression<Vector> { diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorSquaredLength.java b/src/main/java/ch/njol/skript/expressions/ExprVectorSquaredLength.java index fd81a40a9cf..e5119fd09fe 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorSquaredLength.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorSquaredLength.java @@ -28,7 +28,9 @@ @Name("Vectors - Squared Length") @Description("Gets the squared length of a vector.") -@Examples({"send \"%squared length of vector 1, 2, 3%\""}) +@Examples({ + "send \"%squared length of vector 1, 2, 3%\"" +}) @Since("2.2-dev28") public class ExprVectorSquaredLength extends SimplePropertyExpression<Vector, Number> { diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorXYZ.java b/src/main/java/ch/njol/skript/expressions/ExprVectorXYZ.java index 95cbcff1645..90df3c546c8 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorXYZ.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorXYZ.java @@ -36,16 +36,18 @@ @Name("Vectors - XYZ Component") @Description("Gets or changes the x, y or z component of a vector.") -@Examples({"set {_v} to vector 1, 2, 3", - "send \"%x of {_v}%, %y of {_v}%, %z of {_v}%\"", - "add 1 to x of {_v}", - "add 2 to y of {_v}", - "add 3 to z of {_v}", - "send \"%x of {_v}%, %y of {_v}%, %z of {_v}%\"", - "set x component of {_v} to 1", - "set y component of {_v} to 2", - "set z component of {_v} to 3", - "send \"%x component of {_v}%, %y component of {_v}%, %z component of {_v}%\"",}) +@Examples({ + "set {_v} to vector 1, 2, 3", + "send \"%x of {_v}%, %y of {_v}%, %z of {_v}%\"", + "add 1 to x of {_v}", + "add 2 to y of {_v}", + "add 3 to z of {_v}", + "send \"%x of {_v}%, %y of {_v}%, %z of {_v}%\"", + "set x component of {_v::*} to 1", + "set y component of {_v::*} to 2", + "set z component of {_v::*} to 3", + "send \"%x component of {_v::*}%, %y component of {_v::*}%, %z component of {_v::*}%\"" +}) @Since("2.2-dev28") public class ExprVectorXYZ extends SimplePropertyExpression<Vector, Number> { From 4a7c510859749bf3b79583e6c590f09b121ae29b Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Thu, 31 Aug 2023 09:20:07 -0700 Subject: [PATCH 488/619] Apply suggestions from code review Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> --- .../ch/njol/skript/effects/EffVectorRotateAroundAnother.java | 4 +--- .../ch/njol/skript/expressions/ExprVectorAngleBetween.java | 4 +--- .../ch/njol/skript/expressions/ExprVectorCrossProduct.java | 4 +--- .../java/ch/njol/skript/expressions/ExprVectorDotProduct.java | 4 +--- .../ch/njol/skript/expressions/ExprVectorFromYawAndPitch.java | 4 +--- .../java/ch/njol/skript/expressions/ExprVectorNormalize.java | 4 +--- .../java/ch/njol/skript/expressions/ExprVectorOfLocation.java | 4 +--- .../ch/njol/skript/expressions/ExprVectorSquaredLength.java | 4 +--- 8 files changed, 8 insertions(+), 24 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffVectorRotateAroundAnother.java b/src/main/java/ch/njol/skript/effects/EffVectorRotateAroundAnother.java index 1fba62a7ab8..a82f95977a0 100644 --- a/src/main/java/ch/njol/skript/effects/EffVectorRotateAroundAnother.java +++ b/src/main/java/ch/njol/skript/effects/EffVectorRotateAroundAnother.java @@ -35,9 +35,7 @@ @Name("Vectors - Rotate Around Vector") @Description("Rotates one or more vectors around another vector") -@Examples({ - "rotate {_v} around vector 1, 0, 0 by 90" -}) +@Examples("rotate {_v} around vector 1, 0, 0 by 90") @Since("2.2-dev28") public class EffVectorRotateAroundAnother extends Effect { diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorAngleBetween.java b/src/main/java/ch/njol/skript/expressions/ExprVectorAngleBetween.java index d6675ca2712..8f03b6684f5 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorAngleBetween.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorAngleBetween.java @@ -37,9 +37,7 @@ @Name("Vectors - Angle Between") @Description("Gets the angle between two vectors.") -@Examples({ - "send \"%the angle between vector 1, 0, 0 and vector 0, 1, 1%\"" -}) +@Examples("send \"%the angle between vector 1, 0, 0 and vector 0, 1, 1%\"") @Since("2.2-dev28") public class ExprVectorAngleBetween extends SimpleExpression<Number> { diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorCrossProduct.java b/src/main/java/ch/njol/skript/expressions/ExprVectorCrossProduct.java index 4afa99e2e02..d2c59b63989 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorCrossProduct.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorCrossProduct.java @@ -36,9 +36,7 @@ @Name("Vectors - Cross Product") @Description("Gets the cross product between two vectors.") -@Examples({ - "send \"%vector 1, 0, 0 cross vector 0, 1, 0%\"" -}) +@Examples("send \"%vector 1, 0, 0 cross vector 0, 1, 0%\"") @Since("2.2-dev28") public class ExprVectorCrossProduct extends SimpleExpression<Vector> { diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorDotProduct.java b/src/main/java/ch/njol/skript/expressions/ExprVectorDotProduct.java index d1f9154a254..0403e64e541 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorDotProduct.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorDotProduct.java @@ -36,9 +36,7 @@ @Name("Vectors - Dot Product") @Description("Gets the dot product between two vectors.") -@Examples({ - "set {_dot} to {_v1} dot {_v2}" -}) +@Examples("set {_dot} to {_v1} dot {_v2}") @Since("2.2-dev28") public class ExprVectorDotProduct extends SimpleExpression<Number> { diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorFromYawAndPitch.java b/src/main/java/ch/njol/skript/expressions/ExprVectorFromYawAndPitch.java index e3fa1989f9f..8f7179e7d26 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorFromYawAndPitch.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorFromYawAndPitch.java @@ -37,9 +37,7 @@ @Name("Vectors - Vector from Pitch and Yaw") @Description("Creates a vector from a yaw and pitch value.") -@Examples({ - "set {_v} to vector from yaw 45 and pitch 45" -}) +@Examples("set {_v} to vector from yaw 45 and pitch 45") @Since("2.2-dev28") public class ExprVectorFromYawAndPitch extends SimpleExpression<Vector> { diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorNormalize.java b/src/main/java/ch/njol/skript/expressions/ExprVectorNormalize.java index f734cc46956..b1a25debf3c 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorNormalize.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorNormalize.java @@ -36,9 +36,7 @@ @Name("Vectors - Normalized") @Description("Returns the same vector but with length 1.") -@Examples({ - "set {_v} to normalized {_v}" -}) +@Examples("set {_v} to normalized {_v}") @Since("2.2-dev28") public class ExprVectorNormalize extends SimpleExpression<Vector> { diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorOfLocation.java b/src/main/java/ch/njol/skript/expressions/ExprVectorOfLocation.java index a0f50881f4f..e15974b78a7 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorOfLocation.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorOfLocation.java @@ -37,9 +37,7 @@ @Name("Vectors - Vector from Location") @Description("Creates a vector from a location.") -@Examples({ - "set {_v} to vector of {_loc}" -}) +@Examples("set {_v} to vector of {_loc}") @Since("2.2-dev28") public class ExprVectorOfLocation extends SimpleExpression<Vector> { diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorSquaredLength.java b/src/main/java/ch/njol/skript/expressions/ExprVectorSquaredLength.java index e5119fd09fe..bdfeeb33b74 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorSquaredLength.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorSquaredLength.java @@ -28,9 +28,7 @@ @Name("Vectors - Squared Length") @Description("Gets the squared length of a vector.") -@Examples({ - "send \"%squared length of vector 1, 2, 3%\"" -}) +@Examples("send \"%squared length of vector 1, 2, 3%\"") @Since("2.2-dev28") public class ExprVectorSquaredLength extends SimplePropertyExpression<Vector, Number> { From 729b0ecd55477bdfd2a24c7438209a2c8efbd2b2 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Thu, 31 Aug 2023 16:57:55 -0700 Subject: [PATCH 489/619] Budget Expansion --- .../effects/EffVectorRotateAroundAnother.java | 14 +++++----- .../skript/effects/EffVectorRotateXYZ.java | 12 ++++----- .../expressions/ExprVectorAngleBetween.java | 8 +++--- .../expressions/ExprVectorArithmetic.java | 26 +++++++++---------- .../ExprVectorBetweenLocations.java | 4 +-- .../expressions/ExprVectorCrossProduct.java | 8 +++--- .../expressions/ExprVectorDotProduct.java | 8 +++--- .../skript/expressions/ExprVectorFromXYZ.java | 4 +-- .../skript/expressions/ExprVectorLength.java | 24 ++++++++--------- .../expressions/ExprVectorNormalize.java | 12 ++++----- .../skript/expressions/ExprVectorXYZ.java | 20 +++++++------- 11 files changed, 68 insertions(+), 72 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffVectorRotateAroundAnother.java b/src/main/java/ch/njol/skript/effects/EffVectorRotateAroundAnother.java index a82f95977a0..10b83986f4d 100644 --- a/src/main/java/ch/njol/skript/effects/EffVectorRotateAroundAnother.java +++ b/src/main/java/ch/njol/skript/effects/EffVectorRotateAroundAnother.java @@ -44,7 +44,7 @@ public class EffVectorRotateAroundAnother extends Effect { } @SuppressWarnings("null") - private Expression<Vector> first, second; + private Expression<Vector> vectors, axis; @SuppressWarnings("null") private Expression<Number> degree; @@ -52,8 +52,8 @@ public class EffVectorRotateAroundAnother extends Effect { @SuppressWarnings({"unchecked", "null"}) @Override public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean kleenean, ParseResult parseResult) { - first = (Expression<Vector>) exprs[0]; - second = (Expression<Vector>) exprs[1]; + vectors = (Expression<Vector>) exprs[0]; + axis = (Expression<Vector>) exprs[1]; degree = (Expression<Number>) exprs[2]; return true; } @@ -61,17 +61,17 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean kleenean @SuppressWarnings("null") @Override protected void execute(Event event) { - Vector axis = second.getSingle(event); + Vector axis = this.axis.getSingle(event); Number angle = degree.getSingle(event); if (axis == null || angle == null) return; - for (Vector v1 : first.getArray(event)) - VectorMath.rot(v1, axis, angle.doubleValue()); + for (Vector vector : vectors.getArray(event)) + VectorMath.rot(vector, axis, angle.doubleValue()); } @Override public String toString(@Nullable Event event, boolean debug) { - return "rotate " + first.toString(event, debug) + " around " + second.toString(event, debug) + " by " + degree.toString(event, debug) + "degrees"; + return "rotate " + vectors.toString(event, debug) + " around " + axis.toString(event, debug) + " by " + degree.toString(event, debug) + "degrees"; } } diff --git a/src/main/java/ch/njol/skript/effects/EffVectorRotateXYZ.java b/src/main/java/ch/njol/skript/effects/EffVectorRotateXYZ.java index 0e95f79ca6f..6e24d2882bb 100644 --- a/src/main/java/ch/njol/skript/effects/EffVectorRotateXYZ.java +++ b/src/main/java/ch/njol/skript/effects/EffVectorRotateXYZ.java @@ -73,16 +73,16 @@ protected void execute(Event event) { return; switch (axis) { case 0: - for (Vector v : vectors.getArray(event)) - VectorMath.rotX(v, angle.doubleValue()); + for (Vector vector : vectors.getArray(event)) + VectorMath.rotX(vector, angle.doubleValue()); break; case 1: - for (Vector v : vectors.getArray(event)) - VectorMath.rotY(v, angle.doubleValue()); + for (Vector vector : vectors.getArray(event)) + VectorMath.rotY(vector, angle.doubleValue()); break; case 2: - for (Vector v : vectors.getArray(event)) - VectorMath.rotZ(v, angle.doubleValue()); + for (Vector vector : vectors.getArray(event)) + VectorMath.rotZ(vector, angle.doubleValue()); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorAngleBetween.java b/src/main/java/ch/njol/skript/expressions/ExprVectorAngleBetween.java index 8f03b6684f5..1cdf3626c8e 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorAngleBetween.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorAngleBetween.java @@ -60,11 +60,11 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") protected Number[] get(Event event) { - Vector v1 = first.getSingle(event); - Vector v2 = second.getSingle(event); - if (v1 == null || v2 == null) + Vector first = this.first.getSingle(event); + Vector second = this.second.getSingle(event); + if (first == null || second == null) return null; - return CollectionUtils.array(v1.angle(v2) * (float) VectorMath.RAD_TO_DEG); + return CollectionUtils.array(first.angle(second) * (float) VectorMath.RAD_TO_DEG); } @Override diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java b/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java index 7cfc91f6948..41c81c2d8d4 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java @@ -44,32 +44,32 @@ "set {_v} to {_v} ** {_v}", "set {_v} to {_v} // {_v}" }) -@Since("2.2-dev28") +@Since("2.2-desecond8") public class ExprVectorArithmetic extends SimpleExpression<Vector> { private enum Operator { PLUS("++") { @Override - public Vector calculate(final Vector v1, final Vector v2) { - return v1.clone().add(v2); + public Vector calculate(final Vector first, final Vector second) { + return first.clone().add(second); } }, MINUS("--") { @Override - public Vector calculate(final Vector v1, final Vector v2) { - return v1.clone().subtract(v2); + public Vector calculate(final Vector first, final Vector second) { + return first.clone().subtract(second); } }, MULT("**") { @Override - public Vector calculate(final Vector v1, final Vector v2) { - return v1.clone().multiply(v2); + public Vector calculate(final Vector first, final Vector second) { + return first.clone().multiply(second); } }, DIV("//") { @Override - public Vector calculate(final Vector v1, final Vector v2) { - return v1.clone().divide(v2); + public Vector calculate(final Vector first, final Vector second) { + return first.clone().divide(second); } }; @@ -79,7 +79,7 @@ public Vector calculate(final Vector v1, final Vector v2) { this.sign = sign; } - public abstract Vector calculate(Vector v1, Vector v2); + public abstract Vector calculate(Vector first, Vector second); @Override public String toString() { @@ -115,9 +115,9 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override protected Vector[] get(Event event) { - Vector v1 = first.getOptionalSingle(event).orElse(new Vector()); - Vector v2 = second.getOptionalSingle(event).orElse(new Vector()); - return CollectionUtils.array(operator.calculate(v1, v2)); + Vector first = this.first.getOptionalSingle(event).orElse(new Vector()); + Vector second = this.second.getOptionalSingle(event).orElse(new Vector()); + return CollectionUtils.array(operator.calculate(first, second)); } @Override diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorBetweenLocations.java b/src/main/java/ch/njol/skript/expressions/ExprVectorBetweenLocations.java index 346e1f4e386..75412c017b2 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorBetweenLocations.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorBetweenLocations.java @@ -37,9 +37,7 @@ @Name("Vectors - Vector Between Locations") @Description("Creates a vector between two locations.") -@Examples({ - "set {_v} to vector between {_loc1} and {_loc2}" -}) +@Examples("set {_v} to vector between {_loc1} and {_loc2}") @Since("2.2-dev28") public class ExprVectorBetweenLocations extends SimpleExpression<Vector> { diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorCrossProduct.java b/src/main/java/ch/njol/skript/expressions/ExprVectorCrossProduct.java index d2c59b63989..ae8ebe990e1 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorCrossProduct.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorCrossProduct.java @@ -58,11 +58,11 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") protected Vector[] get(Event event) { - Vector v1 = first.getSingle(event); - Vector v2 = second.getSingle(event); - if (v1 == null || v2 == null) + Vector first = this.first.getSingle(event); + Vector second = this.second.getSingle(event); + if (first == null || second == null) return null; - return CollectionUtils.array(v1.clone().crossProduct(v2)); + return CollectionUtils.array(first.clone().crossProduct(second)); } @Override diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorDotProduct.java b/src/main/java/ch/njol/skript/expressions/ExprVectorDotProduct.java index 0403e64e541..50ef5e9ea15 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorDotProduct.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorDotProduct.java @@ -58,11 +58,11 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") protected Number[] get(Event event) { - Vector v1 = first.getSingle(event); - Vector v2 = second.getSingle(event); - if (v1 == null || v2 == null) + Vector first = this.first.getSingle(event); + Vector second = this.second.getSingle(event); + if (first == null || second == null) return null; - return CollectionUtils.array(v1.getX() * v2.getX() + v1.getY() * v2.getY() + v1.getZ() * v2.getZ()); + return CollectionUtils.array(first.getX() * second.getX() + first.getY() * second.getY() + first.getZ() * second.getZ()); } @Override diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorFromXYZ.java b/src/main/java/ch/njol/skript/expressions/ExprVectorFromXYZ.java index ff05bb78fb6..6b553e6f50b 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorFromXYZ.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorFromXYZ.java @@ -36,9 +36,7 @@ @Name("Vectors - Create from XYZ") @Description("Creates a vector from x, y and z values.") -@Examples({ - "set {_v} to vector 0, 1, 0" -}) +@Examples("set {_v} to vector 0, 1, 0") @Since("2.2-dev28") public class ExprVectorFromXYZ extends SimpleExpression<Vector> { diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorLength.java b/src/main/java/ch/njol/skript/expressions/ExprVectorLength.java index 490294384a5..62f2a695e85 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorLength.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorLength.java @@ -69,25 +69,25 @@ public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { deltaLength = -deltaLength; //$FALL-THROUGH$ case ADD: - for (Vector v : vectors) { - if (deltaLength < 0 && v.lengthSquared() < deltaLength * deltaLength) { - v.zero(); + for (Vector vector : vectors) { + if (deltaLength < 0 && vector.lengthSquared() < deltaLength * deltaLength) { + vector.zero(); } else { - double newLength = deltaLength + v.length(); - if (!v.isNormalized()) - v.normalize(); - v.multiply(newLength); + double newLength = deltaLength + vector.length(); + if (!vector.isNormalized()) + vector.normalize(); + vector.multiply(newLength); } } break; case SET: - for (Vector v : vectors) { + for (Vector vector : vectors) { if (deltaLength < 0) { - v.zero(); + vector.zero(); } else { - if (!v.isNormalized()) - v.normalize(); - v.multiply(deltaLength); + if (!vector.isNormalized()) + vector.normalize(); + vector.multiply(deltaLength); } } break; diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorNormalize.java b/src/main/java/ch/njol/skript/expressions/ExprVectorNormalize.java index b1a25debf3c..00b80f7d109 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorNormalize.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorNormalize.java @@ -59,13 +59,13 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") protected Vector[] get(Event event) { - Vector v = vector.getSingle(event); - if (v == null) + Vector vector = this.vector.getSingle(event); + if (vector == null) return null; - v = v.clone(); - if (!v.isNormalized()) - v.normalize(); - return CollectionUtils.array(v); + vector = vector.clone(); + if (!vector.isNormalized()) + vector.normalize(); + return CollectionUtils.array(vector); } @Override diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorXYZ.java b/src/main/java/ch/njol/skript/expressions/ExprVectorXYZ.java index 90df3c546c8..2955fef5b97 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorXYZ.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorXYZ.java @@ -67,8 +67,8 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye } @Override - public Number convert(Vector v) { - return axis == 0 ? v.getX() : (axis == 1 ? v.getY() : v.getZ()); + public Number convert(Vector vector) { + return axis == 0 ? vector.getX() : (axis == 1 ? vector.getY() : vector.getZ()); } @Override @@ -90,23 +90,23 @@ public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { deltaValue = -deltaValue; //$FALL-THROUGH$ case ADD: - for (Vector v : vectors) { + for (Vector vector : vectors) { if (axis == 0) - v.setX(v.getX() + deltaValue); + vector.setX(vector.getX() + deltaValue); else if (axis == 1) - v.setY(v.getY() + deltaValue); + vector.setY(vector.getY() + deltaValue); else - v.setZ(v.getZ() + deltaValue); + vector.setZ(vector.getZ() + deltaValue); } break; case SET: - for (Vector v : vectors) { + for (Vector vector : vectors) { if (axis == 0) - v.setX(deltaValue); + vector.setX(deltaValue); else if (axis == 1) - v.setY(deltaValue); + vector.setY(deltaValue); else - v.setZ(deltaValue); + vector.setZ(deltaValue); } break; default: From be7701516edb3233465954898af7922aa7a69e73 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Sun, 1 Oct 2023 02:26:19 -0400 Subject: [PATCH 490/619] Fix Logging Issues In ExpressionEntryData (#6081) Fix duplicate logging --- .../skriptlang/skript/lang/entry/util/ExpressionEntryData.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/skriptlang/skript/lang/entry/util/ExpressionEntryData.java b/src/main/java/org/skriptlang/skript/lang/entry/util/ExpressionEntryData.java index e707d3337d3..42e964918f9 100644 --- a/src/main/java/org/skriptlang/skript/lang/entry/util/ExpressionEntryData.java +++ b/src/main/java/org/skriptlang/skript/lang/entry/util/ExpressionEntryData.java @@ -69,7 +69,7 @@ public ExpressionEntryData( @SuppressWarnings("unchecked") protected Expression<? extends T> getValue(String value) { Expression<? extends T> expression; - try (ParseLogHandler log = new ParseLogHandler()) { + try (ParseLogHandler log = new ParseLogHandler().start()) { expression = new SkriptParser(value, flags, ParseContext.DEFAULT) .parseExpression(returnType); if (expression == null) // print an error if it couldn't parse From 8a624652d0526483fd656b5ff4e6219ae65e88f5 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Sun, 1 Oct 2023 14:20:44 -0400 Subject: [PATCH 491/619] Prepare For Release 2.7.1 (#6082) --- skript-aliases | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skript-aliases b/skript-aliases index fb9c3044e55..1ee77d8573a 160000 --- a/skript-aliases +++ b/skript-aliases @@ -1 +1 @@ -Subproject commit fb9c3044e555667b4dc5558467608bd55fa32df0 +Subproject commit 1ee77d8573aa37456f1b49fe12aec7bb410d1dd7 From 886fb67dbdd829979a7a343dd9ab3def4e24a90f Mon Sep 17 00:00:00 2001 From: Spongecade <spongecade.129@gmail.com> Date: Mon, 2 Oct 2023 09:08:31 -0500 Subject: [PATCH 492/619] Update Minecraft wiki links to new domain (#6078) --- src/main/java/ch/njol/skript/classes/data/BukkitClasses.java | 4 ++-- src/main/java/ch/njol/skript/conditions/CondIsSlimeChunk.java | 2 +- src/main/java/ch/njol/skript/effects/EffExplosion.java | 2 +- src/main/java/ch/njol/skript/expressions/ExprAge.java | 2 +- .../java/ch/njol/skript/expressions/ExprExplosionYield.java | 2 +- .../java/ch/njol/skript/expressions/ExprExplosiveYield.java | 2 +- .../java/ch/njol/skript/expressions/ExprGlidingState.java | 2 +- .../java/ch/njol/skript/expressions/ExprScoreboardTags.java | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) 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 ccf12240d29..d674a18890d 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -1410,7 +1410,7 @@ public String toVariableNameString(FireworkEffect effect) { .user("(panda )?genes?") .name("Gene") .description("Represents a Panda's main or hidden gene. " + - "See <a href='https://minecraft.gamepedia.com/Panda#Genetics'>genetics</a> for more info.") + "See <a href='https://minecraft.wiki/w/Panda#Genetics'>genetics</a> for more info.") .since("2.4") .requiredPlugins("Minecraft 1.14 or newer")); } @@ -1486,7 +1486,7 @@ public String toVariableNameString(EnchantmentOffer eo) { .user("attribute ?types?") .name("Attribute Type") .description("Represents the type of an attribute. Note that this type does not contain any numerical values." - + "See <a href='https://minecraft.gamepedia.com/Attribute#Attributes'>attribute types</a> for more info.") + + "See <a href='https://minecraft.wiki/w/Attribute#Attributes'>attribute types</a> for more info.") .since("2.5")); Classes.registerClass(new EnumClassInfo<>(Environment.class, "environment", "environments") diff --git a/src/main/java/ch/njol/skript/conditions/CondIsSlimeChunk.java b/src/main/java/ch/njol/skript/conditions/CondIsSlimeChunk.java index 71dbbea2dcc..1b71258482b 100755 --- a/src/main/java/ch/njol/skript/conditions/CondIsSlimeChunk.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsSlimeChunk.java @@ -35,7 +35,7 @@ @Name("Is Slime Chunk") @Description({"Tests whether a chunk is a so-called slime chunk.", "Slimes can generally spawn in the swamp biome and in slime chunks.", - "For more info, see <a href='https://minecraft.gamepedia.com/Slime#.22Slime_chunks.22'>the Minecraft wiki</a>."}) + "For more info, see <a href='https://minecraft.wiki/w/Slime#.22Slime_chunks.22'>the Minecraft wiki</a>."}) @Examples({"command /slimey:", "\ttrigger:", "\t\tif chunk at player is a slime chunk:", diff --git a/src/main/java/ch/njol/skript/effects/EffExplosion.java b/src/main/java/ch/njol/skript/effects/EffExplosion.java index 4f0de85dd86..192119a54d6 100644 --- a/src/main/java/ch/njol/skript/effects/EffExplosion.java +++ b/src/main/java/ch/njol/skript/effects/EffExplosion.java @@ -37,7 +37,7 @@ * @author Peter Güttinger */ @Name("Explosion") -@Description({"Creates an explosion of a given force. The Minecraft Wiki has an <a href='https://www.minecraftwiki.net/wiki/Explosion'>article on explosions</a> " + +@Description({"Creates an explosion of a given force. The Minecraft Wiki has an <a href='https://www.minecraft.wiki/w/Explosion'>article on explosions</a> " + "which lists the explosion forces of TNT, creepers, etc.", "Hint: use a force of 0 to create a fake explosion that does no damage whatsoever, or use the explosion effect introduced in Skript 2.0.", "Starting with Bukkit 1.4.5 and Skript 2.0 you can use safe explosions which will damage entities but won't destroy any blocks."}) diff --git a/src/main/java/ch/njol/skript/expressions/ExprAge.java b/src/main/java/ch/njol/skript/expressions/ExprAge.java index 2a29300beb0..1be159daf08 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprAge.java +++ b/src/main/java/ch/njol/skript/expressions/ExprAge.java @@ -119,7 +119,7 @@ public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { setAge(obj, newValue); break; case RESET: - // baby animals takes 20 minutes to grow up - ref: https://minecraft.fandom.com/wiki/Breeding + // baby animals takes 20 minutes to grow up - ref: https://minecraft.wiki/w/Breeding if (obj instanceof org.bukkit.entity.Ageable) // it might change later on so removing entity age reset would be better unless // bukkit adds a method returning the default age diff --git a/src/main/java/ch/njol/skript/expressions/ExprExplosionYield.java b/src/main/java/ch/njol/skript/expressions/ExprExplosionYield.java index eea0c5fdd0a..cb0227ec419 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprExplosionYield.java +++ b/src/main/java/ch/njol/skript/expressions/ExprExplosionYield.java @@ -40,7 +40,7 @@ @Name("Explosion Yield") @Description({"The yield of the explosion in an explosion prime event. This is how big the explosion is.", " When changing the yield, values less than 0 will be ignored.", - " Read <a href='https://minecraft.gamepedia.com/Explosion'>this wiki page</a> for more information"}) + " Read <a href='https://minecraft.wiki/w/Explosion'>this wiki page</a> for more information"}) @Examples({"on explosion prime:", "\tset the yield of the explosion to 10"}) @Events("explosion prime") diff --git a/src/main/java/ch/njol/skript/expressions/ExprExplosiveYield.java b/src/main/java/ch/njol/skript/expressions/ExprExplosiveYield.java index b66b2f4e60d..3fa35bc76c7 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprExplosiveYield.java +++ b/src/main/java/ch/njol/skript/expressions/ExprExplosiveYield.java @@ -36,7 +36,7 @@ @Name("Explosive Yield") @Description({"The yield of an explosive (creeper, primed tnt, fireball, etc.). This is how big of an explosion is caused by the entity.", - "Read <a href='https://minecraft.gamepedia.com/Explosion'>this wiki page</a> for more information"}) + "Read <a href='https://minecraft.wiki/w/Explosion'>this wiki page</a> for more information"}) @Examples({"on spawn of a creeper:", "\tset the explosive yield of the event-entity to 10"}) @RequiredPlugins("Minecraft 1.12 or newer for creepers") diff --git a/src/main/java/ch/njol/skript/expressions/ExprGlidingState.java b/src/main/java/ch/njol/skript/expressions/ExprGlidingState.java index 6334e1af835..901ff127079 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprGlidingState.java +++ b/src/main/java/ch/njol/skript/expressions/ExprGlidingState.java @@ -31,7 +31,7 @@ import ch.njol.skript.expressions.base.SimplePropertyExpression; @Name("Gliding State") -@Description("Sets of gets gliding state of player. It allows you to set gliding state of entity even if they do not have an <a href=\"https://minecraft.gamepedia.com/Elytra\">Elytra</a> equipped.") +@Description("Sets of gets gliding state of player. It allows you to set gliding state of entity even if they do not have an <a href=\"https://minecraft.wiki/w/Elytra\">Elytra</a> equipped.") @Examples({"set gliding of player to off"}) @Since("2.2-dev21") public class ExprGlidingState extends SimplePropertyExpression<LivingEntity, Boolean> { diff --git a/src/main/java/ch/njol/skript/expressions/ExprScoreboardTags.java b/src/main/java/ch/njol/skript/expressions/ExprScoreboardTags.java index 14da4e073ca..47f4f4e5712 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprScoreboardTags.java +++ b/src/main/java/ch/njol/skript/expressions/ExprScoreboardTags.java @@ -41,7 +41,7 @@ @Name("Scoreboard Tags") @Description({"Scoreboard tags are simple list of texts stored directly in the data of an <a href='classes.html#entity'>entity</a>.", "So this is a Minecraft related thing, not Bukkit, so the tags will not get removed when the server stops. " + - "You can visit <a href='https://minecraft.gamepedia.com/Scoreboard#Tags'>visit Minecraft Wiki</a> for more info.", + "You can visit <a href='https://minecraft.wiki/w/Scoreboard#Tags'>visit Minecraft Wiki</a> for more info.", "This is changeable and valid for any type of entity. " + "Also you can use use the <a href='conditions.html#CondHasScoreboardTag'>Has Scoreboard Tag</a> condition to check whether an entity has the given tags.", "", From 1789a95925a5291166b8a645544d74fd655ba127 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Tue, 3 Oct 2023 10:10:26 +0300 Subject: [PATCH 493/619] =?UTF-8?q?=E2=9A=92=20Fix=20fake=20player=20count?= =?UTF-8?q?=20paper=20check=20error=20(#6090)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../expressions/ExprOnlinePlayersCount.java | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprOnlinePlayersCount.java b/src/main/java/ch/njol/skript/expressions/ExprOnlinePlayersCount.java index ad4a6055125..5fd03a7e9d8 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprOnlinePlayersCount.java +++ b/src/main/java/ch/njol/skript/expressions/ExprOnlinePlayersCount.java @@ -18,18 +18,12 @@ */ package ch.njol.skript.expressions; -import org.bukkit.Bukkit; -import org.bukkit.event.Event; -import org.bukkit.event.server.ServerListPingEvent; -import org.eclipse.jdt.annotation.Nullable; - -import com.destroystokyo.paper.event.server.PaperServerListPingEvent; import ch.njol.skript.Skript; -import ch.njol.skript.bukkitutil.PlayerUtils; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; @@ -37,23 +31,31 @@ 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 org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.bukkit.event.server.ServerListPingEvent; +import org.eclipse.jdt.annotation.Nullable; @Name("Online Player Count") -@Description({"The amount of online players. This can be changed in a", +@Description({ + "The amount of online players. This can be changed in a " + "<a href='events.html#server_list_ping'>server list ping</a> event only to show fake online player amount.", - "'real online player count' always returns the real count of online players and can't be changed.", - "", - "Fake online player count requires PaperSpigot 1.12.2+."}) -@Examples({"on server list ping:", - " # This will make the max players count 5 if there are 4 players online.", - " set the fake max players count to (online players count + 1)"}) + "<code>real online player count</code> always return the real count of online players and can't be changed." +}) +@Examples({ + "on server list ping:", + "\t# This will make the max players count 5 if there are 4 players online.", + "\tset the fake max players count to (online player count + 1)" +}) +@RequiredPlugins("Paper (fake count)") @Since("2.3") public class ExprOnlinePlayersCount extends SimpleExpression<Long> { static { Skript.registerExpression(ExprOnlinePlayersCount.class, Long.class, ExpressionType.PROPERTY, - "[the] [(1¦(real|default)|2¦(fake|shown|displayed))] [online] player (count|amount|number)", - "[the] [(1¦(real|default)|2¦(fake|shown|displayed))] (count|amount|number|size) of online players"); + "[the] [(1:(real|default)|2:(fake|shown|displayed))] [online] player (count|amount|number)", + "[the] [(1:(real|default)|2:(fake|shown|displayed))] (count|amount|number|size) of online players"); } private static final boolean PAPER_EVENT_EXISTS = Skript.classExists("com.destroystokyo.paper.event.server.PaperServerListPingEvent"); @@ -64,7 +66,7 @@ public class ExprOnlinePlayersCount extends SimpleExpression<Long> { public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { boolean isPaperEvent = PAPER_EVENT_EXISTS && getParser().isCurrentEvent(PaperServerListPingEvent.class); if (parseResult.mark == 2) { - if (getParser().isCurrentEvent(ServerListPingEvent.class)) { + if (!PAPER_EVENT_EXISTS && getParser().isCurrentEvent(ServerListPingEvent.class)) { Skript.error("The 'fake' online players count expression requires Paper 1.12.2 or newer"); return false; } else if (!isPaperEvent) { @@ -146,4 +148,4 @@ public String toString(@Nullable Event e, boolean debug) { return "the count of " + (isReal ? "real max players" : "max players"); } -} \ No newline at end of file +} From b774cafc88dac177df042fea550813c4f0a56a62 Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Tue, 3 Oct 2023 11:42:47 +0300 Subject: [PATCH 494/619] Fix Command Help (#6080) Fix issues and cleanup CommandHelp class Co-authored-by: Moderocky <admin@moderocky.com> --- .../ch/njol/skript/command/CommandHelp.java | 152 +++++++++--------- 1 file changed, 77 insertions(+), 75 deletions(-) diff --git a/src/main/java/ch/njol/skript/command/CommandHelp.java b/src/main/java/ch/njol/skript/command/CommandHelp.java index 8fcf6f0a3b4..4732ba2cde5 100644 --- a/src/main/java/ch/njol/skript/command/CommandHelp.java +++ b/src/main/java/ch/njol/skript/command/CommandHelp.java @@ -23,6 +23,7 @@ import java.util.LinkedHashMap; import java.util.Locale; +import java.util.Map; import java.util.Map.Entry; import org.bukkit.command.CommandSender; @@ -33,118 +34,119 @@ import ch.njol.skript.localization.Message; import ch.njol.skript.util.SkriptColor; -/** - * @author Peter Güttinger - */ public class CommandHelp { - + private final static String DEFAULTENTRY = "description"; - + private final static ArgsMessage m_invalid_argument = new ArgsMessage("commands.invalid argument"); private final static Message m_usage = new Message("skript command.usage"); - + + private final String actualCommand, actualNode, argsColor; private String command; + private String langNode; @Nullable - private Message description = null; - private final String argsColor; - - @Nullable - private String langNode = null; - - private final LinkedHashMap<String, Object> arguments = new LinkedHashMap<>(); - + private Message description; + + private final Map<String, Object> arguments = new LinkedHashMap<>(); + @Nullable - private Message wildcardArg = null; - - public CommandHelp(final String command, final SkriptColor argsColor, final String langNode) { - this.command = command; - this.argsColor = "" + argsColor.getFormattedChat(); - this.langNode = langNode; - description = new Message(langNode + "." + DEFAULTENTRY); + private ArgumentHolder wildcardArg = null; + + public CommandHelp(String command, SkriptColor argsColor, String langNode) { + this(command, argsColor.getFormattedChat(), langNode, new Message(langNode + "." + DEFAULTENTRY)); } - - public CommandHelp(final String command, final SkriptColor argsColor) { - this.command = command; - this.argsColor = "" + argsColor.getFormattedChat(); + + public CommandHelp(String command, SkriptColor argsColor) { + this(command, argsColor.getFormattedChat(), command, null); } - - public CommandHelp add(final String argument) { - if (langNode == null) { - if (argument.startsWith("<") && argument.endsWith(">")) { - final String carg = GRAY + "<" + argsColor + argument.substring(1, argument.length() - 1) + GRAY + ">"; - arguments.put(carg, argument); - } else { - arguments.put(argument, null); - } - } else { - if (argument.startsWith("<") && argument.endsWith(">")) { - final String carg = GRAY + "<" + argsColor + argument.substring(1, argument.length() - 1) + GRAY + ">"; - wildcardArg = new Message(langNode + "." + argument); - arguments.put(carg, wildcardArg); - } else { - arguments.put(argument, new Message(langNode + "." + argument)); - } + + private CommandHelp(String command, String argsColor, String node, @Nullable Message description) { + this.actualCommand = this.command = command; + this.actualNode = this.langNode = node; + this.argsColor = argsColor; + this.description = description; + } + + public CommandHelp add(String argument) { + ArgumentHolder holder = new ArgumentHolder(argument); + if (argument.startsWith("<") && argument.endsWith(">")) { + argument = GRAY + "<" + argsColor + argument.substring(1, argument.length() - 1) + GRAY + ">"; + wildcardArg = holder; } + arguments.put(argument, holder); return this; } - - public CommandHelp add(final CommandHelp help) { + + public CommandHelp add(CommandHelp help) { arguments.put(help.command, help); help.onAdd(this); return this; } - - protected void onAdd(final CommandHelp parent) { - langNode = parent.langNode + "." + command; + + protected void onAdd(CommandHelp parent) { + langNode = parent.langNode + "." + actualNode; description = new Message(langNode + "." + DEFAULTENTRY); - command = parent.command + " " + parent.argsColor + command; - for (final Entry<String, Object> e : arguments.entrySet()) { - if (e.getValue() instanceof CommandHelp) { - ((CommandHelp) e.getValue()).onAdd(this); - } else { - if (e.getValue() != null) { // wildcard arg - wildcardArg = new Message(langNode + "." + e.getValue()); - e.setValue(wildcardArg); - } else { - e.setValue(new Message(langNode + "." + e.getKey())); - } + command = parent.command + " " + parent.argsColor + actualCommand; + for (Entry<String, Object> entry : arguments.entrySet()) { + if (entry.getValue() instanceof CommandHelp) { + ((CommandHelp) entry.getValue()).onAdd(this); + continue; } + ((ArgumentHolder) entry.getValue()).update(); } } - - public boolean test(final CommandSender sender, final String[] args) { + + public boolean test(CommandSender sender, String[] args) { return test(sender, args, 0); } - - private boolean test(final CommandSender sender, final String[] args, final int index) { + + private boolean test(CommandSender sender, String[] args, int index) { if (index >= args.length) { showHelp(sender); return false; } - final Object help = arguments.get(args[index].toLowerCase(Locale.ENGLISH)); + Object help = arguments.get(args[index].toLowerCase(Locale.ENGLISH)); if (help == null && wildcardArg == null) { showHelp(sender, m_invalid_argument.toString(argsColor + args[index])); return false; } - if (help instanceof CommandHelp) - return ((CommandHelp) help).test(sender, args, index + 1); - return true; + return !(help instanceof CommandHelp) || ((CommandHelp) help).test(sender, args, index + 1); } - - public void showHelp(final CommandSender sender) { + + public void showHelp(CommandSender sender) { showHelp(sender, m_usage.toString()); } - - private void showHelp(final CommandSender sender, final String pre) { + + private void showHelp(CommandSender sender, String pre) { Skript.message(sender, pre + " " + command + " " + argsColor + "..."); - for (final Entry<String, Object> e : arguments.entrySet()) { - Skript.message(sender, " " + argsColor + e.getKey() + " " + GRAY + "-" + RESET + " " + e.getValue()); - } + for (Entry<String, Object> entry : arguments.entrySet()) + Skript.message(sender, " " + argsColor + entry.getKey() + " " + GRAY + "-" + RESET + " " + entry.getValue()); } - + @Override public String toString() { return "" + description; } - + + private class ArgumentHolder { + + private final String argument; + private Message description; + + private ArgumentHolder(String argument) { + this.argument = argument; + this.description = new Message(langNode + "." + argument); + } + + private void update() { + description = new Message(langNode + "." + argument); + } + + @Override + public String toString() { + return description.toString(); + } + + } + } From 71b05eb4588bcec387074edf14e19476e25648bc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Oct 2023 17:18:58 -0600 Subject: [PATCH 495/619] Bump net.kyori:adventure-text-serializer-bungeecord from 4.3.0 to 4.3.1 (#6084) Bumps [net.kyori:adventure-text-serializer-bungeecord](https://github.com/KyoriPowered/adventure-platform) from 4.3.0 to 4.3.1. - [Release notes](https://github.com/KyoriPowered/adventure-platform/releases) - [Commits](https://github.com/KyoriPowered/adventure-platform/compare/v4.3.0...v4.3.1) --- updated-dependencies: - dependency-name: net.kyori:adventure-text-serializer-bungeecord dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7ce7614c3b0..761244001d9 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ allprojects { dependencies { shadow group: 'io.papermc', name: 'paperlib', version: '1.0.8' shadow group: 'org.bstats', name: 'bstats-bukkit', version: '3.0.2' - shadow group: 'net.kyori', name: 'adventure-text-serializer-bungeecord', version: '4.3.0' + shadow group: 'net.kyori', name: 'adventure-text-serializer-bungeecord', version: '4.3.1' implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.20.2-R0.1-SNAPSHOT' implementation group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.700' From 83a41f6cd5a8b7d788efe93826639e86af57dec7 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Fri, 6 Oct 2023 23:14:40 -0600 Subject: [PATCH 496/619] Add WILL to property condition (#5955) --- .../conditions/base/PropertyCondition.java | 86 +++++++++++-------- 1 file changed, 49 insertions(+), 37 deletions(-) diff --git a/src/main/java/ch/njol/skript/conditions/base/PropertyCondition.java b/src/main/java/ch/njol/skript/conditions/base/PropertyCondition.java index 7a99c659d5d..8e73bf83d56 100644 --- a/src/main/java/ch/njol/skript/conditions/base/PropertyCondition.java +++ b/src/main/java/ch/njol/skript/conditions/base/PropertyCondition.java @@ -53,7 +53,7 @@ * the first one needs to be a non-negated one and a negated one. */ public abstract class PropertyCondition<T> extends Condition implements Checker<T> { - + /** * See {@link PropertyCondition} for more info */ @@ -74,100 +74,112 @@ public enum PropertyType { * Indicates that the condition is in a form of <code>something has/have something</code>, * also possibly in the negated form */ - HAVE + HAVE, + + /** + * Indicates that the condition is in a form of <code>something will/be something</code>, + * also possibly in the negated form + */ + WILL } - - @SuppressWarnings("null") + private Expression<? extends T> expr; - + /** - * @param c the class to register + * @param condition the class to register * @param property the property name, for example <i>fly</i> in <i>players can fly</i> * @param type must be plural, for example <i>players</i> in <i>players can fly</i> */ - public static void register(final Class<? extends Condition> c, final String property, final String type) { - register(c, PropertyType.BE, property, type); + public static void register(Class<? extends Condition> condition, String property, String type) { + register(condition, PropertyType.BE, property, type); } - + /** - * @param c the class to register + * @param condition the class to register * @param propertyType the property type, see {@link PropertyType} * @param property the property name, for example <i>fly</i> in <i>players can fly</i> * @param type must be plural, for example <i>players</i> in <i>players can fly</i> */ - public static void register(final Class<? extends Condition> c, final PropertyType propertyType, final String property, final String type) { - if (type.contains("%")) { + public static void register(Class<? extends Condition> condition, PropertyType propertyType, String property, String type) { + if (type.contains("%")) throw new SkriptAPIException("The type argument must not contain any '%'s"); - } + switch (propertyType) { case BE: - Skript.registerCondition(c, + Skript.registerCondition(condition, "%" + type + "% (is|are) " + property, "%" + type + "% (isn't|is not|aren't|are not) " + property); break; case CAN: - Skript.registerCondition(c, + Skript.registerCondition(condition, "%" + type + "% can " + property, "%" + type + "% (can't|cannot|can not) " + property); break; case HAVE: - Skript.registerCondition(c, + Skript.registerCondition(condition, "%" + type + "% (has|have) " + property, "%" + type + "% (doesn't|does not|do not|don't) have " + property); break; + case WILL: + Skript.registerCondition(condition, + "%" + type + "% will " + property, + "%" + type + "% (will (not|neither)|won't) " + property); + break; default: assert false; } } - - @SuppressWarnings({"unchecked", "null"}) + @Override - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { expr = (Expression<? extends T>) exprs[0]; setNegated(matchedPattern == 1); return true; } - + @Override - public final boolean check(final Event e) { - return expr.check(e, this, isNegated()); + public final boolean check(Event event) { + return expr.check(event, this, isNegated()); } - + @Override public abstract boolean check(T t); - + protected abstract String getPropertyName(); - + protected PropertyType getPropertyType() { return PropertyType.BE; } - + /** * Sets the expression this condition checks a property of. No reference to the expression should be kept. * - * @param expr + * @param expr The expression property of this property condition. */ - protected final void setExpr(final Expression<? extends T> expr) { + protected final void setExpr(Expression<? extends T> expr) { this.expr = expr; } - + @Override - public String toString(final @Nullable Event e, final boolean debug) { - return toString(this, getPropertyType(), e, debug, expr, getPropertyName()); + public String toString(@Nullable Event event, boolean debug) { + return toString(this, getPropertyType(), event, debug, expr, getPropertyName()); } - - public static String toString(Condition condition, PropertyType propertyType, @Nullable Event e, + + public static String toString(Condition condition, PropertyType propertyType, @Nullable Event event, boolean debug, Expression<?> expr, String property) { switch (propertyType) { case BE: - return expr.toString(e, debug) + (expr.isSingle() ? " is " : " are ") + (condition.isNegated() ? "not " : "") + property; + return expr.toString(event, debug) + (expr.isSingle() ? " is " : " are ") + (condition.isNegated() ? "not " : "") + property; case CAN: - return expr.toString(e, debug) + (condition.isNegated() ? " can't " : " can ") + property; + return expr.toString(event, debug) + (condition.isNegated() ? " can't " : " can ") + property; case HAVE: if (expr.isSingle()) - return expr.toString(e, debug) + (condition.isNegated() ? " doesn't have " : " has ") + property; + return expr.toString(event, debug) + (condition.isNegated() ? " doesn't have " : " has ") + property; else - return expr.toString(e, debug) + (condition.isNegated() ? " don't have " : " have ") + property; + return expr.toString(event, debug) + (condition.isNegated() ? " don't have " : " have ") + property; + case WILL: + return expr.toString(event, debug) + (condition.isNegated() ? " won't " : " will ") + "be " + property; default: assert false; throw new AssertionError(); From da97d3eebd7eea94cdbf3f781b8196d54d5d4dc1 Mon Sep 17 00:00:00 2001 From: Moderocky <admin@moderocky.com> Date: Sun, 8 Oct 2023 11:09:38 +0100 Subject: [PATCH 497/619] Fix unloading/reloading a directory in the scripts effect (#6106) * Moved unloading to a common method. * Accidental double space :grimacing: --- .../ch/njol/skript/effects/EffScriptFile.java | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffScriptFile.java b/src/main/java/ch/njol/skript/effects/EffScriptFile.java index 0a38111c5ce..ed15f625d2f 100644 --- a/src/main/java/ch/njol/skript/effects/EffScriptFile.java +++ b/src/main/java/ch/njol/skript/effects/EffScriptFile.java @@ -37,6 +37,7 @@ import java.io.File; import java.io.IOException; +import java.util.Set; @Name("Enable/Disable/Reload Script File") @Description("Enables, disables, or reloads a script file.") @@ -102,10 +103,8 @@ protected void execute(Event e) { case RELOAD: { if (ScriptLoader.getDisabledScriptsFilter().accept(scriptFile)) return; - - Script script = ScriptLoader.getScript(scriptFile); - if (script != null) - ScriptLoader.unloadScript(script); + + this.unloadScripts(scriptFile); ScriptLoader.loadScripts(scriptFile, OpenCloseable.EMPTY); break; @@ -114,9 +113,7 @@ protected void execute(Event e) { if (ScriptLoader.getDisabledScriptsFilter().accept(scriptFile)) return; - Script script = ScriptLoader.getScript(scriptFile); - if (script != null) - ScriptLoader.unloadScript(script); + this.unloadScripts(scriptFile); try { FileUtils.move( @@ -135,6 +132,19 @@ protected void execute(Event e) { assert false; } } + + private void unloadScripts(File file) { + if (file.isDirectory()) { + Set<Script> scripts = ScriptLoader.getScripts(file); + if (scripts.isEmpty()) + return; + ScriptLoader.unloadScripts(scripts); + } else { + Script script = ScriptLoader.getScript(file); + if (script != null) + ScriptLoader.unloadScript(script); + } + } @Override public String toString(@Nullable Event e, boolean debug) { From 54f1249009345bceb50b39efc8afa32d69468156 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Mon, 9 Oct 2023 04:12:16 -0400 Subject: [PATCH 498/619] Force UTF-8 encoding for Gradle daemon (#6103) Co-authored-by: Moderocky <admin@moderocky.com> --- gradle.properties | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gradle.properties b/gradle.properties index 1d5c2fb3e73..3c6d77ca224 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,6 @@ +# Ensure encoding is consistent across systems +org.gradle.jvmargs=-Dfile.encoding=UTF-8 + groupid=ch.njol name=skript version=2.7.1 From 8e1fd285811be866adbca12ee058bdbb2f5cd810 Mon Sep 17 00:00:00 2001 From: MihirKohli <55236890+MihirKohli@users.noreply.github.com> Date: Mon, 9 Oct 2023 13:49:47 +0530 Subject: [PATCH 499/619] Corrected Javadocs name, title (#6038) * Javadoc Title,Name * Update .gitignore Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> --------- Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Co-authored-by: Moderocky <admin@moderocky.com> --- .gitignore | 3 +++ build.gradle | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index fbddca8fd46..47df9cbc0de 100755 --- a/.gitignore +++ b/.gitignore @@ -223,3 +223,6 @@ gradle-app.setting # TODO remove this in the future after some time since https://github.com/SkriptLang/Skript/pull/4979 # This ensures developers don't upload their old existing test_runners/ folder. test_runners/ + +## Mac MetaData +**/.DS_Store diff --git a/build.gradle b/build.gradle index 15505bcbd3e..a6ebb813ecf 100644 --- a/build.gradle +++ b/build.gradle @@ -150,7 +150,7 @@ license { } task releaseJavadoc(type: Javadoc) { - title = project.property('version') + title = project.name + ' ' + project.property('version') source = sourceSets.main.allJava classpath = configurations.compileClasspath options.encoding = 'UTF-8' From 61201bd66143965d3f1aa121a08bc74fc5ce5758 Mon Sep 17 00:00:00 2001 From: MihirKohli <55236890+MihirKohli@users.noreply.github.com> Date: Mon, 9 Oct 2023 13:49:47 +0530 Subject: [PATCH 500/619] Corrected Javadocs name, title (#6038) * Javadoc Title,Name * Update .gitignore Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> --------- Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Co-authored-by: Moderocky <admin@moderocky.com> --- .gitignore | 3 +++ build.gradle | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index fbddca8fd46..47df9cbc0de 100755 --- a/.gitignore +++ b/.gitignore @@ -223,3 +223,6 @@ gradle-app.setting # TODO remove this in the future after some time since https://github.com/SkriptLang/Skript/pull/4979 # This ensures developers don't upload their old existing test_runners/ folder. test_runners/ + +## Mac MetaData +**/.DS_Store diff --git a/build.gradle b/build.gradle index 761244001d9..5184e444872 100644 --- a/build.gradle +++ b/build.gradle @@ -150,7 +150,7 @@ license { } task releaseJavadoc(type: Javadoc) { - title = project.property('version') + title = project.name + ' ' + project.property('version') source = sourceSets.main.allJava classpath = configurations.compileClasspath options.encoding = 'UTF-8' From c34b83a189ab93c37d36112c67c8ad9df8848100 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Tue, 10 Oct 2023 00:58:27 -0600 Subject: [PATCH 501/619] Rebase JUnit references fix for dev/patch (#6057) --- build.gradle | 2 +- src/main/java/ch/njol/skript/Skript.java | 9 +-- .../CondMinecraftVersion.java | 2 +- .../skript/test/runner/CondRunningJUnit.java | 60 +++++++++++++++++++ .../skript/test/runner/EffObjectives.java | 18 ++---- .../njol/skript/test/runner/TestTracker.java | 4 ++ .../junit/registration}/ExprJUnitTest.java | 6 +- .../test/junit/registration/package-info.java | 25 ++++++++ src/test/skript/{tests => }/junit/README.md | 0 .../{tests => }/junit/SimpleJUnitTest.sk | 2 +- 10 files changed, 106 insertions(+), 22 deletions(-) rename src/main/java/ch/njol/skript/{test/runner => conditions}/CondMinecraftVersion.java (98%) create mode 100644 src/main/java/ch/njol/skript/test/runner/CondRunningJUnit.java rename src/{main/java/ch/njol/skript/test/runner => test/java/org/skriptlang/skript/test/junit/registration}/ExprJUnitTest.java (92%) create mode 100644 src/test/java/org/skriptlang/skript/test/junit/registration/package-info.java rename src/test/skript/{tests => }/junit/README.md (100%) rename src/test/skript/{tests => }/junit/SimpleJUnitTest.sk (95%) diff --git a/build.gradle b/build.gradle index 5184e444872..8e673cbbb6a 100644 --- a/build.gradle +++ b/build.gradle @@ -223,7 +223,7 @@ void createTestTask(String name, String desc, String environments, int javaVersi main = 'ch.njol.skript.test.platform.PlatformMain' args = [ 'build/test_runners', - junit ? 'src/test/skript/tests/junit' : 'src/test/skript/tests', + junit ? 'src/test/skript/junit' : 'src/test/skript/tests', 'src/test/resources/runner_data', environments, modifiers.contains(Modifiers.DEV_MODE), diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 9740ee27efa..522a9e8133e 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -591,6 +591,8 @@ public void run() { tainted = true; try { getAddonInstance().loadClasses("ch.njol.skript.test.runner"); + if (TestMode.JUNIT) + getAddonInstance().loadClasses("org.skriptlang.skript.test.junit.registration"); } catch (IOException e) { Skript.exception("Failed to load testing environment."); Bukkit.getServer().shutdown(); @@ -684,7 +686,6 @@ protected void afterErrors() { TestTracker.testFailed("exception was thrown during execution"); } if (TestMode.JUNIT) { - SkriptLogger.setVerbosity(Verbosity.DEBUG); info("Running all JUnit tests..."); long milliseconds = 0, tests = 0, fails = 0, ignored = 0, size = 0; try { @@ -712,7 +713,7 @@ protected void afterErrors() { // If JUnit failures are present, add them to the TestTracker. junit.getFailures().forEach(failure -> { String message = failure.getMessage() == null ? "" : " " + failure.getMessage(); - TestTracker.testFailed("'" + test + "': " + message); + TestTracker.JUnitTestFailed(test, message); Skript.exception(failure.getException(), "JUnit test '" + failure.getTestHeader() + " failed."); }); SkriptJUnitTest.clearJUnitTest(); @@ -734,7 +735,7 @@ protected void afterErrors() { // Delay server shutdown to stop the server from crashing because the current tick takes a long time due to all the tests Bukkit.getScheduler().runTaskLater(Skript.this, () -> { if (TestMode.JUNIT && !EffObjectives.isJUnitComplete()) - TestTracker.testFailed(EffObjectives.getFailedObjectivesString()); + EffObjectives.fail(); info("Collecting results to " + TestMode.RESULTS_FILE); String results = new Gson().toJson(TestTracker.collectResults()); @@ -1261,7 +1262,7 @@ public static boolean isAcceptRegistrations() { } public static void checkAcceptRegistrations() { - if (!isAcceptRegistrations()) + if (!isAcceptRegistrations() && !Skript.testing()) throw new SkriptAPIException("Registration can only be done during plugin initialization"); } diff --git a/src/main/java/ch/njol/skript/test/runner/CondMinecraftVersion.java b/src/main/java/ch/njol/skript/conditions/CondMinecraftVersion.java similarity index 98% rename from src/main/java/ch/njol/skript/test/runner/CondMinecraftVersion.java rename to src/main/java/ch/njol/skript/conditions/CondMinecraftVersion.java index bc5f1436349..e2888c0cf80 100644 --- a/src/main/java/ch/njol/skript/test/runner/CondMinecraftVersion.java +++ b/src/main/java/ch/njol/skript/conditions/CondMinecraftVersion.java @@ -16,7 +16,7 @@ * * Copyright Peter Güttinger, SkriptLang team and contributors */ -package ch.njol.skript.test.runner; +package ch.njol.skript.conditions; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; diff --git a/src/main/java/ch/njol/skript/test/runner/CondRunningJUnit.java b/src/main/java/ch/njol/skript/test/runner/CondRunningJUnit.java new file mode 100644 index 00000000000..cfb7e2abe34 --- /dev/null +++ b/src/main/java/ch/njol/skript/test/runner/CondRunningJUnit.java @@ -0,0 +1,60 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.test.runner; + +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.NoDoc; +import ch.njol.skript.lang.Condition; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; + +@Name("Check JUnit") +@Description({ + "Returns true if the test runner is currently running a JUnit.", + "Useful for the EvtTestCase of JUnit exclusive syntaxes registered from within the test packages." +}) +@NoDoc +public class CondRunningJUnit extends Condition { + + static { + Skript.registerCondition(CondRunningJUnit.class, "running junit"); + } + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + return true; + } + + @Override + public boolean check(Event event) { + return TestMode.JUNIT; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "running JUnit"; + } + +} diff --git a/src/main/java/ch/njol/skript/test/runner/EffObjectives.java b/src/main/java/ch/njol/skript/test/runner/EffObjectives.java index 9a2d9a0e4ae..472abc4e7b5 100644 --- a/src/main/java/ch/njol/skript/test/runner/EffObjectives.java +++ b/src/main/java/ch/njol/skript/test/runner/EffObjectives.java @@ -92,31 +92,23 @@ public String toString(@Nullable Event event, boolean debug) { * @return boolean true if the test passed. */ public static boolean isJUnitComplete() { - if (requirements.isEmpty()) - return true; - if (completeness.isEmpty() && !requirements.isEmpty()) - return false; + assert !completeness.isEmpty() || !requirements.isEmpty(); return completeness.equals(requirements); } /** - * Returns an array string containing all the objectives that the current - * JUnit test failed to accomplish in the given time. - * - * @return + * Fails the JUnit testing system if any JUnit tests did not complete their checks. */ - public static String getFailedObjectivesString() { - StringBuilder builder = new StringBuilder(); + public static void fail() { for (String test : requirements.keySet()) { if (!completeness.containsKey(test)) { - builder.append("JUnit test '" + test + "' didn't complete any objectives."); + TestTracker.JUnitTestFailed("JUnit test '" + test + "'", "didn't complete any objectives."); continue; } List<String> failures = Lists.newArrayList(requirements.get(test)); failures.removeAll(completeness.get(test)); - builder.append("JUnit test '" + test + "' failed objectives: " + Arrays.toString(failures.toArray(new String[0]))); + TestTracker.JUnitTestFailed("JUnit test '" + test + "'", "failed objectives: " + Arrays.toString(failures.toArray(new String[0]))); } - return builder.toString(); } } diff --git a/src/main/java/ch/njol/skript/test/runner/TestTracker.java b/src/main/java/ch/njol/skript/test/runner/TestTracker.java index dde781c344f..c339e43e6c9 100644 --- a/src/main/java/ch/njol/skript/test/runner/TestTracker.java +++ b/src/main/java/ch/njol/skript/test/runner/TestTracker.java @@ -52,6 +52,10 @@ public static void testStarted(String name) { currentTest = name; } + public static void JUnitTestFailed(String currentTest, String msg) { + failedTests.put(currentTest, msg); + } + public static void testFailed(String msg) { failedTests.put(currentTest, msg); } diff --git a/src/main/java/ch/njol/skript/test/runner/ExprJUnitTest.java b/src/test/java/org/skriptlang/skript/test/junit/registration/ExprJUnitTest.java similarity index 92% rename from src/main/java/ch/njol/skript/test/runner/ExprJUnitTest.java rename to src/test/java/org/skriptlang/skript/test/junit/registration/ExprJUnitTest.java index 0f85a98e8aa..448638914e1 100644 --- a/src/main/java/ch/njol/skript/test/runner/ExprJUnitTest.java +++ b/src/test/java/org/skriptlang/skript/test/junit/registration/ExprJUnitTest.java @@ -16,7 +16,7 @@ * * Copyright Peter Güttinger, SkriptLang team and contributors */ -package ch.njol.skript.test.runner; +package org.skriptlang.skript.test.junit.registration; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @@ -29,6 +29,8 @@ import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.skript.test.runner.SkriptJUnitTest; +import ch.njol.skript.test.runner.TestMode; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; @@ -38,7 +40,7 @@ public class ExprJUnitTest extends SimpleExpression<String> { static { - if (TestMode.ENABLED) + if (TestMode.JUNIT) Skript.registerExpression(ExprJUnitTest.class, String.class, ExpressionType.SIMPLE, "[the] [current[[ly] running]] junit test [name]"); } diff --git a/src/test/java/org/skriptlang/skript/test/junit/registration/package-info.java b/src/test/java/org/skriptlang/skript/test/junit/registration/package-info.java new file mode 100644 index 00000000000..c8b678a11fa --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/junit/registration/package-info.java @@ -0,0 +1,25 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + * + * Place any and all custom syntaxes relating to the JUnit testJar in here to be exclusively ran on the test runner. + */ +@NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) +package org.skriptlang.skript.test.junit.registration; + +import org.eclipse.jdt.annotation.DefaultLocation; +import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/src/test/skript/tests/junit/README.md b/src/test/skript/junit/README.md similarity index 100% rename from src/test/skript/tests/junit/README.md rename to src/test/skript/junit/README.md diff --git a/src/test/skript/tests/junit/SimpleJUnitTest.sk b/src/test/skript/junit/SimpleJUnitTest.sk similarity index 95% rename from src/test/skript/tests/junit/SimpleJUnitTest.sk rename to src/test/skript/junit/SimpleJUnitTest.sk index 25a048ae372..a46d5024634 100644 --- a/src/test/skript/tests/junit/SimpleJUnitTest.sk +++ b/src/test/skript/junit/SimpleJUnitTest.sk @@ -1,4 +1,4 @@ -on script load: +test "SimpleJUnitTest" when running JUnit: # Setup our objective for this script test to complete with the JUnit test. ensure junit test "org.skriptlang.skript.test.tests.regression.SimpleJUnitTest" completes "piggy died" From 74c4d63bbb7da4815a815f72255b513ce62b8614 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Sun, 15 Oct 2023 03:52:54 -0400 Subject: [PATCH 502/619] Fix options issue in functions (#6121) --- .../skript/structures/StructFunction.java | 48 ++++++++++++------- .../syntaxes/structures/StructCommand.sk | 5 +- .../syntaxes/structures/StructFunction.sk | 5 +- 3 files changed, 39 insertions(+), 19 deletions(-) diff --git a/src/main/java/ch/njol/skript/structures/StructFunction.java b/src/main/java/ch/njol/skript/structures/StructFunction.java index 9b52d4fd94e..fe6b590dfe4 100644 --- a/src/main/java/ch/njol/skript/structures/StructFunction.java +++ b/src/main/java/ch/njol/skript/structures/StructFunction.java @@ -18,6 +18,7 @@ */ package ch.njol.skript.structures; +import ch.njol.skript.ScriptLoader; import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; @@ -35,7 +36,8 @@ import org.skriptlang.skript.lang.structure.Structure; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.regex.MatchResult; +import java.util.regex.Matcher; +import java.util.regex.Pattern; @Name("Function") @Description({ @@ -55,38 +57,48 @@ public class StructFunction extends Structure { public static final Priority PRIORITY = new Priority(400); + private static final Pattern SIGNATURE_PATTERN = + Pattern.compile("(?:local )?function (" + Functions.functionNamePattern + ")\\((.*)\\)(?:\\s*::\\s*(.+))?"); private static final AtomicBoolean VALIDATE_FUNCTIONS = new AtomicBoolean(); static { Skript.registerStructure(StructFunction.class, - "[:local] function <(" + Functions.functionNamePattern + ")\\((.*)\\)(?:\\s*::\\s*(.+))?>" + "[:local] function <.+>" ); } - @SuppressWarnings("NotNullFieldNotInitialized") + @Nullable private Signature<?> signature; private boolean local; @Override - @SuppressWarnings("all") public boolean init(Literal<?>[] literals, int matchedPattern, ParseResult parseResult, EntryContainer entryContainer) { local = parseResult.hasTag("local"); - MatchResult regex = parseResult.regexes.get(0); - String name = regex.group(1); - String args = regex.group(2); - String returnType = regex.group(3); - - getParser().setCurrentEvent((local ? "local " : "") + "function", FunctionEvent.class); - - signature = Functions.parseSignature(getParser().getCurrentScript().getConfig().getFileName(), name, args, returnType, local); - - getParser().deleteCurrentEvent(); - return signature != null; + return true; } @Override public boolean preLoad() { - return Functions.registerSignature(signature) != null; + // match signature against pattern + String rawSignature = getEntryContainer().getSource().getKey(); + assert rawSignature != null; + rawSignature = ScriptLoader.replaceOptions(rawSignature); + Matcher matcher = SIGNATURE_PATTERN.matcher(rawSignature); + if (!matcher.matches()) { + Skript.error("Invalid function signature: " + rawSignature); + return false; + } + + // parse signature + getParser().setCurrentEvent((local ? "local " : "") + "function", FunctionEvent.class); + signature = Functions.parseSignature( + getParser().getCurrentScript().getConfig().getFileName(), + matcher.group(1), matcher.group(2), matcher.group(3), local + ); + getParser().deleteCurrentEvent(); + + // attempt registration + return signature != null && Functions.registerSignature(signature) != null; } @Override @@ -94,6 +106,7 @@ public boolean load() { ParserInstance parser = getParser(); parser.setCurrentEvent((local ? "local " : "") + "function", FunctionEvent.class); + assert signature != null; Functions.loadFunction(parser.getCurrentScript(), getEntryContainer().getSource(), signature); parser.deleteCurrentEvent(); @@ -114,6 +127,7 @@ public boolean postLoad() { @Override public void unload() { + assert signature != null; Functions.unregisterFunction(signature); VALIDATE_FUNCTIONS.set(true); } @@ -124,7 +138,7 @@ public Priority getPriority() { } @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { return (local ? "local " : "") + "function"; } diff --git a/src/test/skript/tests/syntaxes/structures/StructCommand.sk b/src/test/skript/tests/syntaxes/structures/StructCommand.sk index dd20198002a..9df9e139247 100644 --- a/src/test/skript/tests/syntaxes/structures/StructCommand.sk +++ b/src/test/skript/tests/syntaxes/structures/StructCommand.sk @@ -2,7 +2,10 @@ test "commands": execute command "skriptcommand taco" execute command "//somecommand burrito is tasty" -command skriptcommand <text> [<text>] [<itemtype = %dirt block named "steve"%>]: +options: + command: skriptcommand + +command {@command} <text> [<text>] [<itemtype = %dirt block named "steve"%>]: trigger: set {_arg1} to arg-1 assert {_arg1} is "taco" with "arg-1 test failed (got '%{_arg1}%')" diff --git a/src/test/skript/tests/syntaxes/structures/StructFunction.sk b/src/test/skript/tests/syntaxes/structures/StructFunction.sk index c1e20e5ca70..8d11a83479b 100644 --- a/src/test/skript/tests/syntaxes/structures/StructFunction.sk +++ b/src/test/skript/tests/syntaxes/structures/StructFunction.sk @@ -1,6 +1,9 @@ -function foo() :: boolean: +function {@function}) :: boolean: return true +options: + function: foo( + function local() :: number: return 1 From 58b9f0bbc95ed7f387b3e77f10607c3625c76501 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Sun, 15 Oct 2023 22:24:41 -0600 Subject: [PATCH 503/619] Add the variable case node instead of being hidden (#5674) --- skript-aliases | 2 +- src/main/java/ch/njol/skript/SkriptConfig.java | 3 +-- src/main/resources/config.sk | 4 ++++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/skript-aliases b/skript-aliases index fb9c3044e55..1ee77d8573a 160000 --- a/skript-aliases +++ b/skript-aliases @@ -1 +1 @@ -Subproject commit fb9c3044e555667b4dc5558467608bd55fa32df0 +Subproject commit 1ee77d8573aa37456f1b49fe12aec7bb410d1dd7 diff --git a/src/main/java/ch/njol/skript/SkriptConfig.java b/src/main/java/ch/njol/skript/SkriptConfig.java index a96ed7bf3ca..13ce39f7620 100644 --- a/src/main/java/ch/njol/skript/SkriptConfig.java +++ b/src/main/java/ch/njol/skript/SkriptConfig.java @@ -246,8 +246,7 @@ public static String formatDate(final long timestamp) { }); public static final Option<Boolean> caseInsensitiveVariables = new Option<>("case-insensitive variables", true) - .setter(t -> Variables.caseInsensitiveVariables = t) - .optional(true); + .setter(t -> Variables.caseInsensitiveVariables = t); public static final Option<Boolean> colorResetCodes = new Option<>("color codes reset formatting", true) .setter(t -> { diff --git a/src/main/resources/config.sk b/src/main/resources/config.sk index 54a9bb7a070..765b7cb7159 100644 --- a/src/main/resources/config.sk +++ b/src/main/resources/config.sk @@ -140,6 +140,10 @@ case sensitive: false # This e.g. applies to the effect 'replace' and the conditions 'contains' and 'is/is not'. # Variable names are case-insensitive irrespective of this setting. +case-insensitive variables: true +# Whether Skript's variables should be case sensitive or not. +# When set to true, all variable names and indices case will be ignored. + disable variable will not be saved warnings: false # Disables the "... i.e contents cannot be saved ..." warning when reloading and something in your scripts sets a variable(non local) to a value that is not serializable. # By Mirre. From ed6174c92aea3fcd10fd37fd4d38b974f7b425d8 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Mon, 16 Oct 2023 04:15:30 -0700 Subject: [PATCH 504/619] Fix command permission messages (2.7.1 issue) (#6126) re-add permission handling during PPCE to get around spigot behavior. --- .../java/ch/njol/skript/command/Commands.java | 68 ++++++++++++------- .../ch/njol/skript/command/ScriptCommand.java | 40 +++++++---- 2 files changed, 68 insertions(+), 40 deletions(-) diff --git a/src/main/java/ch/njol/skript/command/Commands.java b/src/main/java/ch/njol/skript/command/Commands.java index 35e12057bb1..e6cb7456be5 100644 --- a/src/main/java/ch/njol/skript/command/Commands.java +++ b/src/main/java/ch/njol/skript/command/Commands.java @@ -41,6 +41,7 @@ import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.event.server.ServerCommandEvent; import org.bukkit.help.HelpMap; import org.bukkit.help.HelpTopic; @@ -68,7 +69,7 @@ */ @SuppressWarnings("deprecation") public abstract class Commands { - + public final static ArgsMessage m_too_many_arguments = new ArgsMessage("commands.too many arguments"); public final static Message m_internal_error = new Message("commands.internal error"); public final static Message m_correct_usage = new Message("commands.correct usage"); @@ -79,26 +80,26 @@ public abstract class Commands { public static final int CONVERTER_NO_COMMAND_ARGUMENTS = 4; private final static Map<String, ScriptCommand> commands = new HashMap<>(); - + @Nullable private static SimpleCommandMap commandMap = null; @Nullable private static Map<String, Command> cmKnownCommands; @Nullable private static Set<String> cmAliases; - + static { init(); // separate method for the annotation } public static Set<String> getScriptCommands(){ return commands.keySet(); } - + @Nullable public static SimpleCommandMap getCommandMap(){ return commandMap; } - + @SuppressWarnings("unchecked") private static void init() { try { @@ -106,11 +107,11 @@ private static void init() { Field commandMapField = SimplePluginManager.class.getDeclaredField("commandMap"); commandMapField.setAccessible(true); commandMap = (SimpleCommandMap) commandMapField.get(Bukkit.getPluginManager()); - + Field knownCommandsField = SimpleCommandMap.class.getDeclaredField("knownCommands"); knownCommandsField.setAccessible(true); cmKnownCommands = (Map<String, Command>) knownCommandsField.get(commandMap); - + try { Field aliasesField = SimpleCommandMap.class.getDeclaredField("aliases"); aliasesField.setAccessible(true); @@ -125,25 +126,42 @@ private static void init() { commandMap = null; } } - + @Nullable public static List<Argument<?>> currentArguments = null; - + @SuppressWarnings("null") private final static Pattern escape = Pattern.compile("[" + Pattern.quote("(|)<>%\\") + "]"); @SuppressWarnings("null") private final static Pattern unescape = Pattern.compile("\\\\[" + Pattern.quote("(|)<>%\\") + "]"); - + public static String escape(String string) { return "" + escape.matcher(string).replaceAll("\\\\$0"); } - + public static String unescape(String string) { return "" + unescape.matcher(string).replaceAll("$0"); } - + private final static Listener commandListener = new Listener() { - @SuppressWarnings("null") + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onPlayerCommand(PlayerCommandPreprocessEvent event) { + // Spigot will simply report that the command doesn't exist if a player does not have permission to use it. + // This is good security but, well, it's a breaking change for Skript. So we need to check for permissions + // ourselves and handle those messages, for every command. + + // parse command, see if it's a skript command + String[] cmd = event.getMessage().substring(1).split("\\s+", 2); + String label = cmd[0].toLowerCase(Locale.ENGLISH); + String arguments = cmd.length == 1 ? "" : "" + cmd[1]; + ScriptCommand command = commands.get(label); + + // if so, check permissions + if (command != null && !command.checkPermissions(event.getPlayer(), label, arguments)) + event.setCancelled(true); + } + @EventHandler(priority = EventPriority.HIGHEST) public void onServerCommand(ServerCommandEvent event) { if (event.getCommand().isEmpty() || event.isCancelled()) @@ -154,7 +172,7 @@ public void onServerCommand(ServerCommandEvent event) { } } }; - + static boolean handleEffectCommand(CommandSender sender, String command) { if (!(sender instanceof ConsoleCommandSender || sender.hasPermission("skript.effectcommands") || SkriptConfig.allowOpsToUseEffectCommands.value() && sender.isOp())) return false; @@ -170,7 +188,7 @@ static boolean handleEffectCommand(CommandSender sender, String command) { parserInstance.setCurrentEvent("effect command", EffectCommandEvent.class); Effect effect = Effect.parse(command, null); parserInstance.deleteCurrentEvent(); - + if (effect != null) { log.clear(); // ignore warnings and stuff log.printLog(); @@ -219,7 +237,7 @@ public static boolean scriptCommandExists(String command) { ScriptCommand scriptCommand = commands.get(command); return scriptCommand != null && scriptCommand.getName().equals(command); } - + public static void registerCommand(ScriptCommand command) { // Validate that there are no duplicates ScriptCommand existingCommand = commands.get(command.getLabel()); @@ -230,7 +248,7 @@ public static void registerCommand(ScriptCommand command) { ); return; } - + if (commandMap != null) { assert cmKnownCommands != null;// && cmAliases != null; command.register(commandMap, cmKnownCommands, cmAliases); @@ -262,9 +280,9 @@ public static void unregisterCommand(ScriptCommand scriptCommand) { } commands.values().removeIf(command -> command == scriptCommand); } - + private static boolean registeredListeners = false; - + public static void registerListeners() { if (!registeredListeners) { Bukkit.getPluginManager().registerEvents(commandListener, Skript.getInstance()); @@ -298,15 +316,15 @@ public void onPlayerChat(AsyncPlayerChatEvent event) { registeredListeners = true; } } - + /** * copied from CraftBukkit (org.bukkit.craftbukkit.help.CommandAliasHelpTopic) */ public static final class CommandAliasHelpTopic extends HelpTopic { - + private final String aliasFor; private final HelpMap helpMap; - + public CommandAliasHelpTopic(String alias, String aliasFor, HelpMap helpMap) { this.aliasFor = aliasFor.startsWith("/") ? aliasFor : "/" + aliasFor; this.helpMap = helpMap; @@ -314,7 +332,7 @@ public CommandAliasHelpTopic(String alias, String aliasFor, HelpMap helpMap) { Validate.isTrue(!name.equals(this.aliasFor), "Command " + name + " cannot be alias for itself"); shortText = ChatColor.YELLOW + "Alias for " + ChatColor.WHITE + this.aliasFor; } - + @Override @NotNull public String getFullText(CommandSender forWho) { @@ -326,7 +344,7 @@ public String getFullText(CommandSender forWho) { } return "" + fullText; } - + @Override public boolean canSee(CommandSender commandSender) { if (amendedPermission != null) @@ -335,5 +353,5 @@ public boolean canSee(CommandSender commandSender) { return aliasForTopic != null && aliasForTopic.canSee(commandSender); } } - + } diff --git a/src/main/java/ch/njol/skript/command/ScriptCommand.java b/src/main/java/ch/njol/skript/command/ScriptCommand.java index 2ce33ac7152..05d01680cc3 100644 --- a/src/main/java/ch/njol/skript/command/ScriptCommand.java +++ b/src/main/java/ch/njol/skript/command/ScriptCommand.java @@ -117,7 +117,7 @@ public class ScriptCommand implements TabExecutor { /** * Creates a new SkriptCommand. - * + * * @param name /name * @param pattern * @param arguments the list of Arguments this command takes @@ -237,16 +237,8 @@ public boolean execute(final CommandSender sender, final String commandLabel, fi final ScriptCommandEvent event = new ScriptCommandEvent(ScriptCommand.this, sender, commandLabel, rest); - if (!permission.isEmpty() && !sender.hasPermission(permission)) { - if (sender instanceof Player) { - List<MessageComponent> components = - permissionMessage.getMessageComponents(event); - ((Player) sender).spigot().sendMessage(BungeeConverter.convert(components)); - } else { - sender.sendMessage(permissionMessage.getSingle(event)); - } + if (!checkPermissions(sender, event)) return false; - } cooldownCheck : { if (sender instanceof Player && cooldown != null) { @@ -316,19 +308,37 @@ boolean execute2(final ScriptCommandEvent event, final CommandSender sender, fin } finally { log.stop(); } - + if (Skript.log(Verbosity.VERY_HIGH)) Skript.info("# /" + name + " " + rest); final long startTrigger = System.nanoTime(); - + if (!trigger.execute(event)) sender.sendMessage(Commands.m_internal_error.toString()); - + if (Skript.log(Verbosity.VERY_HIGH)) Skript.info("# " + name + " took " + 1. * (System.nanoTime() - startTrigger) / 1000000. + " milliseconds"); return true; } + public boolean checkPermissions(CommandSender sender, String commandLabel, String arguments) { + return checkPermissions(sender, new ScriptCommandEvent(this, sender, commandLabel, arguments)); + } + + public boolean checkPermissions(CommandSender sender, Event event) { + if (!permission.isEmpty() && !sender.hasPermission(permission)) { + if (sender instanceof Player) { + List<MessageComponent> components = + permissionMessage.getMessageComponents(event); + ((Player) sender).spigot().sendMessage(BungeeConverter.convert(components)); + } else { + sender.sendMessage(permissionMessage.getSingle(event)); + } + return false; + } + return true; + } + public void sendHelp(final CommandSender sender) { if (!description.isEmpty()) sender.sendMessage(description); @@ -337,7 +347,7 @@ public void sendHelp(final CommandSender sender) { /** * Gets the arguments this command takes. - * + * * @return The internal list of arguments. Do not modify it! */ public List<Argument<?>> getArguments() { @@ -575,7 +585,7 @@ public List<String> onTabComplete(@Nullable CommandSender sender, @Nullable Comm Class<?> argType = arg.getType(); if (argType.equals(Player.class) || argType.equals(OfflinePlayer.class)) return null; // Default completion - + return Collections.emptyList(); // No tab completion here! } From 86fe4498fc02d95ba0c0a6af25cbb89866b4a875 Mon Sep 17 00:00:00 2001 From: Moderocky <admin@moderocky.com> Date: Mon, 16 Oct 2023 13:29:12 +0100 Subject: [PATCH 505/619] Fix stack overflow when stringifying block inventories. (#6117) * Fix stack overflow when stringifying block inventories. * Blanket catch until a better solution is found. --- src/main/java/ch/njol/skript/classes/data/BukkitClasses.java | 5 +++++ 1 file changed, 5 insertions(+) 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 d674a18890d..f7e67d52610 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -64,6 +64,7 @@ import org.bukkit.event.inventory.InventoryType; import org.bukkit.event.player.PlayerResourcePackStatusEvent.Status; import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; +import org.bukkit.inventory.BlockInventoryHolder; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; @@ -873,6 +874,10 @@ public String toString(InventoryHolder holder, int flags) { return Classes.toString(((BlockState) holder).getBlock()); } else if (holder instanceof DoubleChest) { return Classes.toString(holder.getInventory().getLocation().getBlock()); + } else if (holder instanceof BlockInventoryHolder) { + return Classes.toString(((BlockInventoryHolder) holder).getBlock()); + } else if (Classes.getSuperClassInfo(holder.getClass()).getC() == InventoryHolder.class) { + return holder.getClass().getSimpleName(); // an inventory holder and only that } else { return Classes.toString(holder); } From f4084dc4cbc511f76e7dfe23d72fe3dae29aa60a Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Mon, 16 Oct 2023 19:52:51 +0300 Subject: [PATCH 506/619] ExprCommandInfo Enhancements (#5889) --- .../skript/expressions/ExprCommandInfo.java | 189 +++++++++++------- 1 file changed, 115 insertions(+), 74 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprCommandInfo.java b/src/main/java/ch/njol/skript/expressions/ExprCommandInfo.java index 1403494945d..f1bb3a5e2c3 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprCommandInfo.java +++ b/src/main/java/ch/njol/skript/expressions/ExprCommandInfo.java @@ -19,14 +19,21 @@ package ch.njol.skript.expressions; import java.util.ArrayList; +import java.util.List; import java.util.Locale; import java.util.Objects; +import java.util.function.Function; +import ch.njol.skript.command.ScriptCommand; +import ch.njol.skript.command.ScriptCommandEvent; +import ch.njol.skript.util.Utils; import org.bukkit.command.Command; import org.bukkit.command.CommandMap; import org.bukkit.command.PluginCommand; import org.bukkit.command.defaults.BukkitCommand; import org.bukkit.event.Event; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; +import org.bukkit.event.server.ServerCommandEvent; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; @@ -43,108 +50,108 @@ @Name("Command Info") @Description("Get information about a command.") -@Examples({"main name of command \"skript\"", +@Examples({ + "main command label of command \"skript\"", "description of command \"help\"", "label of command \"pl\"", "usage of command \"help\"", "aliases of command \"bukkit:help\"", "permission of command \"/op\"", "command \"op\"'s permission message", - "command \"sk\"'s plugin owner"}) + "command \"sk\"'s plugin owner", + "", + "command /greet <player>:", + "\tusage: /greet <target>", + "\ttrigger:", + "\t\tif arg-1 is sender:", + "\t\t\tsend \"&cYou can't greet yourself! Usage: %the usage%\"", + "\t\t\tstop", + "\t\tsend \"%sender% greets you!\" to arg-1", + "\t\tsend \"You greeted %arg-1%!\"" +}) @Since("2.6") public class ExprCommandInfo extends SimpleExpression<String> { private enum InfoType { - NAME, - DESCRIPTION, - LABEL, - USAGE, - ALIASES, - PERMISSION, - PERMISSION_MESSAGE, - PLUGIN, + NAME(Command::getName), + DESCRIPTION(Command::getDescription), + LABEL(Command::getLabel), + USAGE(Command::getUsage), + ALIASES(null), // Handled differently + PERMISSION(Command::getPermission), + PERMISSION_MESSAGE(Command::getPermissionMessage), + PLUGIN(command -> { + if (command instanceof PluginCommand) { + return ((PluginCommand) command).getPlugin().getName(); + } else if (command instanceof BukkitCommand) { + return "Bukkit"; + } else if (command.getClass().getPackage().getName().startsWith("org.spigot")) { + return "Spigot"; + } else if (command.getClass().getPackage().getName().startsWith("com.destroystokyo.paper")) { + return "Paper"; + } + return "Unknown"; + }); + + private final @Nullable Function<Command, String> function; + + InfoType(@Nullable Function<Command, String> function) { + this.function = function; + } + } static { - Skript.registerExpression(ExprCommandInfo.class, String.class, ExpressionType.SIMPLE, - "[the] main command [label] of command %strings%", "command %strings%'[s] main command [name]", - "[the] description of command %strings%", "command %strings%'[s] description", - "[the] label of command %strings%", "command %strings%'[s] label", - "[the] usage of command %strings%", "command %strings%'[s] usage", - "[(all|the|all [of] the)] aliases of command %strings%", "command %strings%'[s] aliases", - "[the] permission of command %strings%", "command %strings%'[s] permission", - "[the] permission message of command %strings%", "command %strings%'[s] permission message", - "[the] plugin [owner] of command %strings%", "command %strings%'[s] plugin [owner]"); + Skript.registerExpression(ExprCommandInfo.class, String.class, ExpressionType.PROPERTY, + "[the] main command [label|name] [of [[the] command[s] %-strings%]]", "command[s] %strings%'[s] main command [label|name]", + "[the] description [of [[the] command[s] %-strings%]]", "command[s] %strings%'[s] description", + "[the] label [of [[the] command[s] %-strings%]]", "command[s] %strings%'[s] label", + "[the] usage [of [[the] command[s] %-strings%]]", "command[s] %strings%'[s] usage", + "[(all|the|all [of] the)] aliases [of [[the] command[s] %-strings%]]", "command[s] %strings%'[s] aliases", + "[the] permission [of [[the] command[s] %-strings%]]", "command[s] %strings%'[s] permission", + "[the] permission message [of [[the] command[s] %-strings%]]", "command[s] %strings%'[s] permission message", + "[the] plugin [owner] [of [[the] command[s] %-strings%]]", "command[s] %strings%'[s] plugin [owner]"); } - @SuppressWarnings("null") - InfoType type; - @SuppressWarnings("null") - Expression<String> commandName; + @SuppressWarnings("NotNullFieldNotInitialized") + private InfoType type; + + @Nullable + private Expression<String> commandName; @Override + @SuppressWarnings("unchecked") public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { commandName = (Expression<String>) exprs[0]; + if (commandName == null && !getParser().isCurrentEvent(ScriptCommandEvent.class, PlayerCommandPreprocessEvent.class, ServerCommandEvent.class)) { + Skript.error("There's no command in " + Utils.a(getParser().getCurrentEventName()) + " event. Please provide a command"); + return false; + } type = InfoType.values()[Math.floorDiv(matchedPattern, 2)]; return true; } @Nullable @Override - @SuppressWarnings("null") - protected String[] get(Event e) { - CommandMap map = Commands.getCommandMap(); - Command[] commands = this.commandName.stream(e).map(map::getCommand).filter(Objects::nonNull).toArray(Command[]::new); - ArrayList<String> result = new ArrayList<>(); - switch (type) { - case NAME: - for (Command command : commands) - result.add(command.getName()); - break; - case DESCRIPTION: - for (Command command : commands) - result.add(command.getDescription()); - break; - case LABEL: - for (Command command : commands) - result.add(command.getLabel()); - break; - case USAGE: - for (Command command : commands) - result.add(command.getUsage()); - break; - case ALIASES: - for (Command command : commands) - result.addAll(command.getAliases()); - break; - case PERMISSION: - for (Command command : commands) - result.add(command.getPermission()); - break; - case PERMISSION_MESSAGE: - for (Command command : commands) - result.add(command.getPermissionMessage()); - break; - case PLUGIN: - for (Command command : commands) { - if (command instanceof PluginCommand) { - result.add(((PluginCommand) command).getPlugin().getName()); - } else if (command instanceof BukkitCommand) { - result.add("Bukkit"); - } else if (command.getClass().getPackage().getName().startsWith("org.spigot")) { - result.add("Spigot"); - } else if (command.getClass().getPackage().getName().startsWith("com.destroystokyo.paper")) { - result.add("Paper"); - } - } - break; + protected String[] get(Event event) { + Command[] commands = getCommands(event); + if (commands == null) + return new String[0]; + if (type == InfoType.ALIASES) { + ArrayList<String> result = new ArrayList<>(); + for (Command command : commands) + result.addAll(getAliases(command)); + return result.toArray(new String[0]); } - return result.toArray(new String[0]); + String[] result = new String[commands.length]; + for (int i = 0; i < commands.length; i++) + result[i] = type.function.apply(commands[i]); + return result; } @Override public boolean isSingle() { - return type == InfoType.ALIASES || commandName.isSingle(); + return type != InfoType.ALIASES && (commandName == null || commandName.isSingle()); } @Override @@ -153,7 +160,41 @@ public Class<? extends String> getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return "the " + type.name().toLowerCase(Locale.ENGLISH).replace("_", " ") + " of command " + commandName.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "the " + type.name().toLowerCase(Locale.ENGLISH).replace("_", " ") + + (commandName == null ? "" : " of command " + commandName.toString(event, debug)); } + + @Nullable + private Command[] getCommands(Event event) { + if (event instanceof ScriptCommandEvent && commandName == null) + return new Command[] {((ScriptCommandEvent) event).getScriptCommand().getBukkitCommand()}; + + CommandMap map = Commands.getCommandMap(); + if (map == null) + return null; + + if (commandName != null) + return commandName.stream(event).map(map::getCommand).filter(Objects::nonNull).toArray(Command[]::new); + + String commandName; + if (event instanceof ServerCommandEvent) { + commandName = ((ServerCommandEvent) event).getCommand(); + } else if (event instanceof PlayerCommandPreprocessEvent) { + commandName = ((PlayerCommandPreprocessEvent) event).getMessage().substring(1); + } else { + return null; + } + commandName = commandName.split(":")[0]; + Command command = map.getCommand(commandName); + return command != null ? new Command[] {command} : null; + } + + private static List<String> getAliases(Command command) { + if (!(command instanceof PluginCommand) || ((PluginCommand) command).getPlugin() != Skript.getInstance()) + return command.getAliases(); + ScriptCommand scriptCommand = Commands.getScriptCommand(command.getName()); + return scriptCommand == null ? command.getAliases() : scriptCommand.getAliases(); + } + } From 65059e1a4ce2b8133fb9b7a12154b912279bcabc Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Mon, 16 Oct 2023 11:11:58 -0600 Subject: [PATCH 507/619] Allow profilers to run (#5550) --- build.gradle | 28 +++++++++++-------- .../skript/test/platform/Environment.java | 11 ++------ .../skript/test/platform/PlatformMain.java | 7 +++-- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/build.gradle b/build.gradle index a6ebb813ecf..bc74eee6f9c 100644 --- a/build.gradle +++ b/build.gradle @@ -186,7 +186,10 @@ enum Modifiers { } // Create a test task with given name, environments dir/file, dev mode and java version. -void createTestTask(String name, String desc, String environments, int javaVersion, Modifiers... modifiers) { +// -1 on the timeout means it'll be disabled. +void createTestTask(String name, String desc, String environments, int javaVersion, long timeout, Modifiers... modifiers) { + if (timeout == 0) + timeout = 300000 // 5 minutes boolean junit = modifiers.contains(Modifiers.JUNIT) boolean releaseDocs = modifiers.contains(Modifiers.GEN_RELEASE_DOCS) boolean docs = modifiers.contains(Modifiers.GEN_NIGHTLY_DOCS) || releaseDocs @@ -230,7 +233,8 @@ void createTestTask(String name, String desc, String environments, int javaVersi docs, junit, modifiers.contains(Modifiers.DEBUG), - project.findProperty('verbosity') ?: "null" + project.findProperty('verbosity') ?: "null", + timeout ] // Do first is used when throwing exceptions. @@ -265,21 +269,21 @@ compileTestJava.options.encoding = 'UTF-8' String environments = 'src/test/skript/environments/'; String env = project.property('testEnv') == null ? latestEnv : project.property('testEnv') + '.json' int envJava = project.property('testEnvJavaVersion') == null ? latestJava : Integer.parseInt(project.property('testEnvJavaVersion') as String) -createTestTask('quickTest', 'Runs tests on one environment being the latest supported Java and Minecraft.', environments + latestEnv, latestJava) -createTestTask('skriptTestJava17', 'Runs tests on all Java 17 environments.', environments + 'java17', latestJava) -createTestTask('skriptTestJava8', 'Runs tests on all Java 8 environments.', environments + 'java8', oldestJava) -createTestTask('skriptTestDev', 'Runs testing server and uses \'system.in\' for command input, stop server to finish.', environments + env, envJava, Modifiers.DEV_MODE, Modifiers.DEBUG) -createTestTask('skriptProfile', 'Starts the testing server with JProfiler support.', environments + latestEnv, latestJava, Modifiers.PROFILE) -createTestTask('genNightlyDocs', 'Generates the Skript documentation website html files.', environments + env, envJava, Modifiers.GEN_NIGHTLY_DOCS) -createTestTask('genReleaseDocs', 'Generates the Skript documentation website html files for a release.', environments + env, envJava, Modifiers.GEN_RELEASE_DOCS) +createTestTask('quickTest', 'Runs tests on one environment being the latest supported Java and Minecraft.', environments + latestEnv, latestJava, 0) +createTestTask('skriptTestJava17', 'Runs tests on all Java 17 environments.', environments + 'java17', latestJava, 0) +createTestTask('skriptTestJava8', 'Runs tests on all Java 8 environments.', environments + 'java8', oldestJava, 0) +createTestTask('skriptTestDev', 'Runs testing server and uses \'system.in\' for command input, stop server to finish.', environments + env, envJava, 0, Modifiers.DEV_MODE, Modifiers.DEBUG) +createTestTask('skriptProfile', 'Starts the testing server with JProfiler support.', environments + latestEnv, latestJava, -1, Modifiers.PROFILE) +createTestTask('genNightlyDocs', 'Generates the Skript documentation website html files.', environments + env, envJava, 0, Modifiers.GEN_NIGHTLY_DOCS) +createTestTask('genReleaseDocs', 'Generates the Skript documentation website html files for a release.', environments + env, envJava, 0, Modifiers.GEN_RELEASE_DOCS) tasks.register('skriptTest') { description = 'Runs tests on all environments.' dependsOn skriptTestJava8, skriptTestJava17 } -createTestTask('JUnitQuick', 'Runs JUnit tests on one environment being the latest supported Java and Minecraft.', environments + latestEnv, latestJava, Modifiers.JUNIT) -createTestTask('JUnitJava17', 'Runs JUnit tests on all Java 17 environments.', environments + 'java17', latestJava, Modifiers.JUNIT) -createTestTask('JUnitJava8', 'Runs JUnit tests on all Java 8 environments.', environments + 'java8', oldestJava, Modifiers.JUNIT) +createTestTask('JUnitQuick', 'Runs JUnit tests on one environment being the latest supported Java and Minecraft.', environments + latestEnv, latestJava, 0, Modifiers.JUNIT) +createTestTask('JUnitJava17', 'Runs JUnit tests on all Java 17 environments.', environments + 'java17', latestJava, 0, Modifiers.JUNIT) +createTestTask('JUnitJava8', 'Runs JUnit tests on all Java 8 environments.', environments + 'java8', oldestJava, 0, Modifiers.JUNIT) tasks.register('JUnit') { description = 'Runs JUnit tests on all environments.' dependsOn JUnitJava8, JUnitJava17 diff --git a/src/main/java/ch/njol/skript/test/platform/Environment.java b/src/main/java/ch/njol/skript/test/platform/Environment.java index 6866db6e0c0..c7ae368af6b 100644 --- a/src/main/java/ch/njol/skript/test/platform/Environment.java +++ b/src/main/java/ch/njol/skript/test/platform/Environment.java @@ -50,11 +50,6 @@ */ public class Environment { - /** - * Time before the process is killed if there was a stack stace etc. - */ - private static final int TIMEOUT = 5 * 60_000; // 5 minutes. - private static final Gson gson = new Gson(); /** @@ -233,7 +228,7 @@ public void initialize(Path dataRoot, Path runnerRoot, boolean remake) throws IO @Nullable public TestResults runTests(Path runnerRoot, Path testsRoot, boolean devMode, boolean genDocs, boolean jUnit, boolean debug, - String verbosity, Set<String> jvmArgs) throws IOException, InterruptedException { + String verbosity, long timeout, Set<String> jvmArgs) throws IOException, InterruptedException { Path env = runnerRoot.resolve(name); Path resultsPath = env.resolve("test_results.json"); @@ -268,7 +263,7 @@ public TestResults runTests(Path runnerRoot, Path testsRoot, boolean devMode, bo Runtime.getRuntime().addShutdownHook(new Thread(process::destroy)); // Catch tests running for abnormally long time - if (!devMode) { + if (!devMode && timeout > 0) { new Timer("runner watchdog", true).schedule(new TimerTask() { @Override public void run() { @@ -277,7 +272,7 @@ public void run() { System.exit(1); } } - }, TIMEOUT); + }, timeout); } int code = process.waitFor(); diff --git a/src/main/java/ch/njol/skript/test/platform/PlatformMain.java b/src/main/java/ch/njol/skript/test/platform/PlatformMain.java index f5d95935844..7848180dde3 100644 --- a/src/main/java/ch/njol/skript/test/platform/PlatformMain.java +++ b/src/main/java/ch/njol/skript/test/platform/PlatformMain.java @@ -66,7 +66,10 @@ public static void main(String... args) throws IOException, InterruptedException boolean jUnit = "true".equals(args[6]); boolean debug = "true".equals(args[7]); String verbosity = args[8].toUpperCase(Locale.ENGLISH); - Set<String> jvmArgs = Sets.newHashSet(Arrays.copyOfRange(args, 9, args.length)); + long timeout = Long.parseLong(args[9]); + if (timeout < 0) + timeout = 0; + Set<String> jvmArgs = Sets.newHashSet(Arrays.copyOfRange(args, 10, args.length)); if (jvmArgs.stream().noneMatch(arg -> arg.contains("-Xmx"))) jvmArgs.add("-Xmx5G"); @@ -97,7 +100,7 @@ public static void main(String... args) throws IOException, InterruptedException for (Environment env : envs) { System.out.println("Starting testing on " + env.getName()); env.initialize(dataRoot, runnerRoot, false); - TestResults results = env.runTests(runnerRoot, testsRoot, devMode, genDocs, jUnit, debug, verbosity, jvmArgs); + TestResults results = env.runTests(runnerRoot, testsRoot, devMode, genDocs, jUnit, debug, verbosity, timeout, jvmArgs); if (results == null) { if (devMode) { // Nothing to report, it's the dev mode environment. From 84236521f37c21ec947f92baece5474ca096bf69 Mon Sep 17 00:00:00 2001 From: Moderocky <admin@moderocky.com> Date: Tue, 17 Oct 2023 08:45:27 +0100 Subject: [PATCH 508/619] Fix comparison of cyclical types (specifically comparing times) (#6128) * Add cyclical type helper. * Make time cyclical. * Add special comparison for cyclical types. * Add some tests. --- .../njol/skript/conditions/CondCompare.java | 32 +++++++++--- src/main/java/ch/njol/skript/util/Time.java | 13 ++++- .../skriptlang/skript/lang/util/Cyclical.java | 52 +++++++++++++++++++ .../skript/lang/util/package-info.java | 23 ++++++++ .../tests/syntaxes/conditions/CondCompare.sk | 11 ++++ 5 files changed, 122 insertions(+), 9 deletions(-) create mode 100644 src/main/java/org/skriptlang/skript/lang/util/Cyclical.java create mode 100644 src/main/java/org/skriptlang/skript/lang/util/package-info.java create mode 100644 src/test/skript/tests/syntaxes/conditions/CondCompare.sk diff --git a/src/main/java/ch/njol/skript/conditions/CondCompare.java b/src/main/java/ch/njol/skript/conditions/CondCompare.java index 707f2626e83..4e0917faa6d 100644 --- a/src/main/java/ch/njol/skript/conditions/CondCompare.java +++ b/src/main/java/ch/njol/skript/conditions/CondCompare.java @@ -18,6 +18,7 @@ */ package ch.njol.skript.conditions; +import ch.njol.skript.util.Time; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @@ -52,6 +53,7 @@ import ch.njol.skript.util.Utils; import ch.njol.util.Checker; import ch.njol.util.Kleenean; +import org.skriptlang.skript.lang.util.Cyclical; @Name("Comparison") @Description({"A very general condition, it simply compares two values. Usually you can only compare for equality (e.g. block is/isn't of <type>), " + @@ -351,15 +353,29 @@ public boolean check(final Event e) { return third.check(e, (Checker<Object>) o3 -> { boolean isBetween; if (comparator != null) { - isBetween = - (Relation.GREATER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o2)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o3))) - // Check OPPOSITE (switching o2 / o3) - || (Relation.GREATER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o3)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o2))); + if (o1 instanceof Cyclical<?> && o2 instanceof Cyclical<?> && o3 instanceof Cyclical<?>) { + if (Relation.GREATER_OR_EQUAL.isImpliedBy(comparator.compare(o2, o3))) + isBetween = Relation.GREATER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o2)) || Relation.SMALLER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o3)); + else + isBetween = Relation.GREATER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o2)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o3)); + } else { + isBetween = + (Relation.GREATER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o2)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o3))) + // Check OPPOSITE (switching o2 / o3) + || (Relation.GREATER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o3)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o2))); + } } else { - isBetween = - (Relation.GREATER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o2)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o3))) - // Check OPPOSITE (switching o2 / o3) - || (Relation.GREATER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o3)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o2))); + if (o1 instanceof Cyclical<?> && o2 instanceof Cyclical<?> && o3 instanceof Cyclical<?>) { + if (Relation.GREATER_OR_EQUAL.isImpliedBy(Comparators.compare(o2, o3))) + isBetween = Relation.GREATER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o2)) || Relation.SMALLER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o3)); + else + isBetween = Relation.GREATER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o2)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o3)); + } else { + isBetween = + (Relation.GREATER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o2)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o3))) + // Check OPPOSITE (switching o2 / o3) + || (Relation.GREATER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o3)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o2))); + } } return relation == Relation.NOT_EQUAL ^ isBetween; }); diff --git a/src/main/java/ch/njol/skript/util/Time.java b/src/main/java/ch/njol/skript/util/Time.java index 1572e512599..f957dd3edfe 100644 --- a/src/main/java/ch/njol/skript/util/Time.java +++ b/src/main/java/ch/njol/skript/util/Time.java @@ -27,11 +27,12 @@ import ch.njol.skript.localization.Message; import ch.njol.util.Math2; import ch.njol.yggdrasil.YggdrasilSerializable; +import org.skriptlang.skript.lang.util.Cyclical; /** * @author Peter Güttinger */ -public class Time implements YggdrasilSerializable { +public class Time implements YggdrasilSerializable, Cyclical<Integer> { private final static int TICKS_PER_HOUR = 1000, TICKS_PER_DAY = 24 * TICKS_PER_HOUR; private final static double TICKS_PER_MINUTE = 1000. / 60; @@ -154,4 +155,14 @@ public boolean equals(final @Nullable Object obj) { return time == other.time; } + @Override + public Integer getMaximum() { + return TICKS_PER_DAY; + } + + @Override + public Integer getMinimum() { + return 0; + } + } diff --git a/src/main/java/org/skriptlang/skript/lang/util/Cyclical.java b/src/main/java/org/skriptlang/skript/lang/util/Cyclical.java new file mode 100644 index 00000000000..7c5def1d96f --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/util/Cyclical.java @@ -0,0 +1,52 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.util; + +/** + * This is for a special type of numerical value that is compared in a cyclical (rather than a linear) way. + * <p> + * The current example of this in Skript is Time, + * since 23:59 can be both before XOR after 00:01 depending on the context. + * <p> + * In practice, cyclical types have to be compared in a special way (particularly for "is between") + * when we can use the order of operations to determine the context. + * <p> + * The minimum/maximum values are intended to help with unusual equality checks, (e.g. 11pm = 1am - 2h). + * + * @param <Value> the type of number this uses, to help with type coercion + */ +public interface Cyclical<Value extends Number> { + + /** + * The potential 'top' of the cycle, e.g. the highest value after which this should restart. + * In practice, nothing forces this, so you can write 24:00 or 361° instead of 00:00 and 1° respectively. + * + * @return the highest legal value + */ + Value getMaximum(); + + /** + * The potential 'bottom' of the cycle, e.g. the lowest value. + * In practice, nothing forces this, so 24:00 is synonymous with 00:00. + * + * @return the lowest legal value + */ + Value getMinimum(); + +} diff --git a/src/main/java/org/skriptlang/skript/lang/util/package-info.java b/src/main/java/org/skriptlang/skript/lang/util/package-info.java new file mode 100644 index 00000000000..43ad7f90fae --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/util/package-info.java @@ -0,0 +1,23 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +@NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) +package org.skriptlang.skript.lang.util; + +import org.eclipse.jdt.annotation.DefaultLocation; +import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/src/test/skript/tests/syntaxes/conditions/CondCompare.sk b/src/test/skript/tests/syntaxes/conditions/CondCompare.sk new file mode 100644 index 00000000000..ca436260431 --- /dev/null +++ b/src/test/skript/tests/syntaxes/conditions/CondCompare.sk @@ -0,0 +1,11 @@ +test "compare": + assert 10 is between 5 and 15 with "Number isn't between smaller and larger" + assert 10 is between 9 and 11 with "Number isn't between smaller and larger" + assert 10 is between 11 and 9 with "Number isn't between larger and smaller" + assert 10 is between 9 and 10 with "Number isn't between smaller and equal" + assert 10 is between 10 and 11 with "Number isn't between equal and larger" + assert 10 is between 10 and 10 with "Number isn't between equal and equal" + assert 19:00 is between 18:00 and 20:00 with "Time 19:00 isn't between 18:00 and 20:00" + assert 23:00 is between 20:00 and 24:00 with "Time 23:00 isn't between 20:00 and 24:00" + assert 23:00 is between 20:00 and 01:00 with "Time 23:00 isn't between 20:00 and 01:00 (cyclical)" + assert 23:00 is not between 01:00 and 20:00 with "Time 23:00 is between 01:00 and 20:00 (non-cyclical)" From 5ba4dbabcdeecfb763cd86414b4d8b6be5917829 Mon Sep 17 00:00:00 2001 From: Moderocky <admin@moderocky.com> Date: Tue, 17 Oct 2023 16:24:15 +0100 Subject: [PATCH 509/619] Fix floating point rounding error in loop N times (#6132) Fix floating point error in loop X times. --- src/main/java/ch/njol/skript/expressions/ExprTimes.java | 4 ++-- src/test/skript/tests/syntaxes/expressions/ExprTimes.sk | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprTimes.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprTimes.java b/src/main/java/ch/njol/skript/expressions/ExprTimes.java index 8c8cad634fa..170e00fd886 100755 --- a/src/main/java/ch/njol/skript/expressions/ExprTimes.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTimes.java @@ -107,8 +107,8 @@ public Iterator<? extends Long> iterator(final Event e) { Number end = this.end.getSingle(e); if (end == null) return null; - - return LongStream.range(1, end.longValue() + 1).iterator(); + long fixed = (long) (end.doubleValue() + Skript.EPSILON); + return LongStream.range(1, fixed + 1).iterator(); } @Override diff --git a/src/test/skript/tests/syntaxes/expressions/ExprTimes.sk b/src/test/skript/tests/syntaxes/expressions/ExprTimes.sk new file mode 100644 index 00000000000..5001a78ade0 --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprTimes.sk @@ -0,0 +1,8 @@ +test "times": + set {_seven} to 1.05 / 0.15 # = 7 + set {_three} to 10 - {_seven} # = 3 + set {_count} to 0 + loop {_three} times: + add 1 to {_count} + assert {_count} is 3 with "count was %{_count}% instead of 3" + assert {_three} is 3 with "original number wasn't 3" From db1dad711a2f117310e7f7b957ef444c4758531c Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Fri, 20 Oct 2023 17:34:42 +0300 Subject: [PATCH 510/619] Fix some issues with ExprParse (#5878) * Fix bugs in ExprParse and add a `PARSE` ParseContext * Make ExprParse use `ParseContext.PARSE` rather than `ParseContext.SCRIPT` * Add tests and fix isSingle * Improve #getReturnType * Change every 'int' -> 'integer' --------- Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Co-authored-by: Moderocky <admin@moderocky.com> --- .../skript/classes/data/BukkitClasses.java | 12 +-- .../njol/skript/classes/data/JavaClasses.java | 1 + .../ch/njol/skript/effects/EffChange.java | 24 ++++-- .../ch/njol/skript/expressions/ExprParse.java | 76 ++++++++++++++----- .../skript/hooks/regions/classes/Region.java | 1 + .../ch/njol/skript/lang/ParseContext.java | 4 + .../ch/njol/skript/lang/SkriptParser.java | 53 +++++++------ .../java/ch/njol/skript/lang/Variable.java | 2 +- .../ch/njol/skript/registrations/Classes.java | 2 +- .../tests/syntaxes/expressions/ExprParse.sk | 16 ++++ 10 files changed, 132 insertions(+), 59 deletions(-) create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprParse.sk diff --git a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java index 6e17db006da..70e6cdc038a 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -153,7 +153,7 @@ public Entity parse(final String s, final ParseContext context) { @Override public boolean canParse(final ParseContext context) { - return context == ParseContext.COMMAND; + return context == ParseContext.COMMAND || context == ParseContext.PARSE; } @Override @@ -536,7 +536,7 @@ protected boolean canBeInstantiated() { @Nullable public World parse(final String s, final ParseContext context) { // REMIND allow shortcuts '[over]world', 'nether' and '[the_]end' (server.properties: 'level-name=world') // inconsistent with 'world is "..."' - if (context == ParseContext.COMMAND || context == ParseContext.CONFIG) + if (context == ParseContext.COMMAND || context == ParseContext.PARSE || context == ParseContext.CONFIG) return Bukkit.getWorld(s); final Matcher m = parsePattern.matcher(s); if (m.matches()) @@ -673,7 +673,7 @@ public String toVariableNameString(final Inventory i) { @Override @Nullable public Player parse(String s, ParseContext context) { - if (context == ParseContext.COMMAND) { + if (context == ParseContext.COMMAND || context == ParseContext.PARSE) { if (s.isEmpty()) return null; if (UUID_PATTERN.matcher(s).matches()) @@ -693,7 +693,7 @@ public Player parse(String s, ParseContext context) { @Override public boolean canParse(final ParseContext context) { - return context == ParseContext.COMMAND; + return context == ParseContext.COMMAND || context == ParseContext.PARSE; } @Override @@ -733,7 +733,7 @@ public String getDebugMessage(final Player p) { @Nullable @SuppressWarnings("deprecation") public OfflinePlayer parse(final String s, final ParseContext context) { - if (context == ParseContext.COMMAND) { + if (context == ParseContext.COMMAND || context == ParseContext.PARSE) { if (UUID_PATTERN.matcher(s).matches()) return Bukkit.getOfflinePlayer(UUID.fromString(s)); else if (!SkriptConfig.playerNameRegexPattern.value().matcher(s).matches()) @@ -746,7 +746,7 @@ else if (!SkriptConfig.playerNameRegexPattern.value().matcher(s).matches()) @Override public boolean canParse(ParseContext context) { - return context == ParseContext.COMMAND; + return context == ParseContext.COMMAND || context == ParseContext.PARSE; } @Override diff --git a/src/main/java/ch/njol/skript/classes/data/JavaClasses.java b/src/main/java/ch/njol/skript/classes/data/JavaClasses.java index 1f463e87361..237b5db732c 100644 --- a/src/main/java/ch/njol/skript/classes/data/JavaClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/JavaClasses.java @@ -576,6 +576,7 @@ public String parse(String s, ParseContext context) { return Utils.replaceChatStyles("" + s.substring(1, s.length() - 1).replace("\"\"", "\"")); return null; case COMMAND: + case PARSE: return s; } assert false; diff --git a/src/main/java/ch/njol/skript/effects/EffChange.java b/src/main/java/ch/njol/skript/effects/EffChange.java index 6b16e36071f..9c355191ab2 100644 --- a/src/main/java/ch/njol/skript/effects/EffChange.java +++ b/src/main/java/ch/njol/skript/effects/EffChange.java @@ -21,7 +21,12 @@ import java.util.Arrays; import java.util.logging.Level; -import org.skriptlang.skript.lang.script.Script; +import ch.njol.skript.expressions.ExprParse; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionList; +import ch.njol.skript.lang.SkriptParser; +import ch.njol.skript.lang.Variable; import org.skriptlang.skript.lang.script.ScriptWarning; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @@ -35,11 +40,7 @@ import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; -import ch.njol.skript.lang.Effect; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.SkriptParser; import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.lang.Variable; import ch.njol.skript.log.CountingLogHandler; import ch.njol.skript.log.ErrorQuality; import ch.njol.skript.log.ParseLogHandler; @@ -254,7 +255,18 @@ else if (mode == ChangeMode.SET) Skript.error("only one " + Classes.getSuperClassInfo(x).getName() + " can be " + (mode == ChangeMode.ADD ? "added to" : "removed from") + " " + changed + ", not more", ErrorQuality.SEMANTIC_ERROR); return false; } - + + if (changed instanceof Variable && !changed.isSingle() && mode == ChangeMode.SET) { + if (ch instanceof ExprParse) { + ((ExprParse) ch).flatten = false; + } else if (ch instanceof ExpressionList) { + for (Expression<?> expression : ((ExpressionList<?>) ch).getExpressions()) { + if (expression instanceof ExprParse) + ((ExprParse) expression).flatten = false; + } + } + } + if (changed instanceof Variable && !((Variable<?>) changed).isLocal() && (mode == ChangeMode.SET || ((Variable<?>) changed).isList() && mode == ChangeMode.ADD)) { final ClassInfo<?> ci = Classes.getSuperClassInfo(ch.getReturnType()); if (ci.getC() != Object.class && ci.getSerializer() == null && ci.getSerializeAs() == null && !SkriptConfig.disableObjectCannotBeSavedWarnings.value()) { diff --git a/src/main/java/ch/njol/skript/expressions/ExprParse.java b/src/main/java/ch/njol/skript/expressions/ExprParse.java index 0cf481c7bdf..bea0909a304 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprParse.java +++ b/src/main/java/ch/njol/skript/expressions/ExprParse.java @@ -48,6 +48,9 @@ import org.eclipse.jdt.annotation.Nullable; import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; @Name("Parse") @Description({"Parses text as a given type, or as a given pattern.", @@ -88,8 +91,9 @@ public class ExprParse extends SimpleExpression<Object> { @Nullable private SkriptPattern pattern; @Nullable - private boolean[] patternExpressionPlurals; - private boolean patternHasSingleExpression = false; + private NonNullPair<ClassInfo<?>, Boolean>[] patternExpressions; + private boolean single = true; + public boolean flatten = true; @Nullable private ClassInfo<?> classInfo; @@ -106,7 +110,7 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye return false; } - NonNullPair<String, boolean[]> p = SkriptParser.validatePattern(pattern); + NonNullPair<String, NonNullPair<ClassInfo<?>, Boolean>[]> p = SkriptParser.validatePattern(pattern); if (p == null) { // Errored in validatePattern already return false; @@ -114,7 +118,15 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye // Make all types in the pattern plural pattern = p.getFirst(); - patternExpressionPlurals = p.getSecond(); + patternExpressions = p.getSecond(); + + // Check if all the types can actually parse + for (NonNullPair<ClassInfo<?>, Boolean> patternExpression : patternExpressions) { + if (!canParse(patternExpression.getFirst())) + return false; + if (patternExpression.getSecond()) + single = false; + } // Escape '¦' and ':' (used for parser tags/marks) pattern = escapeParseTags(pattern); @@ -128,8 +140,9 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye return false; } - // If the pattern contains at most 1 type, this expression is single - this.patternHasSingleExpression = this.pattern.countNonNullTypes() <= 1; + // If the pattern contains at most 1 type, and this type is single, this expression is single + if (single) + this.single = this.pattern.countNonNullTypes() <= 1; } else { classInfo = ((Literal<ClassInfo<?>>) exprs[1]).getSingle(); @@ -139,11 +152,7 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye } // Make sure the ClassInfo has a parser - Parser<?> parser = classInfo.getParser(); - if (parser == null || !parser.canParse(ParseContext.COMMAND)) { // TODO special parse context? - Skript.error("Text cannot be parsed as " + classInfo.getName().withIndefiniteArticle()); - return false; - } + return canParse(classInfo); } return true; } @@ -165,21 +174,36 @@ protected Object[] get(Event event) { assert parser != null; // checked in init() // Parse and return value - Object value = parser.parse(text, ParseContext.COMMAND); + Object value = parser.parse(text, ParseContext.PARSE); if (value != null) { Object[] valueArray = (Object[]) Array.newInstance(classInfo.getC(), 1); valueArray[0] = value; return valueArray; } } else { - assert pattern != null && patternExpressionPlurals != null; + assert pattern != null && patternExpressions != null; - MatchResult matchResult = pattern.match(text, SkriptParser.PARSE_LITERALS, ParseContext.COMMAND); + MatchResult matchResult = pattern.match(text, SkriptParser.PARSE_LITERALS, ParseContext.PARSE); if (matchResult != null) { Expression<?>[] exprs = matchResult.getExpressions(); - assert patternExpressionPlurals.length == exprs.length; + assert patternExpressions.length == exprs.length; + + if (flatten) { + List<Object> values = new ArrayList<>(); + for (int i = 0; i < exprs.length; i++) { + if (exprs[i] != null) { + if (patternExpressions[i].getSecond()) { + values.addAll(Arrays.asList(exprs[i].getArray(null))); + continue; + } + values.add(exprs[i].getSingle(null)); + } + } + return values.toArray(); + } + int nonNullExprCount = 0; for (Expression<?> expr : exprs) { if (expr != null) // Ignore missing optional parts @@ -192,8 +216,7 @@ protected Object[] get(Event event) { for (int i = 0; i < exprs.length; i++) { if (exprs[i] != null) { //noinspection DataFlowIssue - values[valueIndex] = patternExpressionPlurals[i] ? exprs[i].getArray(null) : exprs[i].getSingle(null); - + values[valueIndex] = patternExpressions[i].getSecond() ? exprs[i].getArray(null) : exprs[i].getSingle(null); valueIndex++; } } @@ -221,17 +244,28 @@ protected Object[] get(Event event) { @Override public boolean isSingle() { - return pattern == null || patternHasSingleExpression; + return single; } @Override public Class<?> getReturnType() { - return classInfo != null ? classInfo.getC() : Object.class; + if (classInfo != null) + return classInfo.getC(); + return patternExpressions.length == 1 ? patternExpressions[0].getFirst().getC() : Object.class; } @Override - public String toString(@Nullable Event e, boolean debug) { - return text.toString(e, debug) + " parsed as " + (classInfo != null ? classInfo.toString(Language.F_INDEFINITE_ARTICLE) : pattern); + public String toString(@Nullable Event event, boolean debug) { + return text.toString(event, debug) + " parsed as " + (classInfo != null ? classInfo.toString(Language.F_INDEFINITE_ARTICLE) : pattern); + } + + private static boolean canParse(ClassInfo<?> classInfo) { + Parser<?> parser = classInfo.getParser(); + if (parser == null || !parser.canParse(ParseContext.PARSE)) { + Skript.error("Text cannot be parsed as " + classInfo.getName().withIndefiniteArticle()); + return false; + } + return true; } /** diff --git a/src/main/java/ch/njol/skript/hooks/regions/classes/Region.java b/src/main/java/ch/njol/skript/hooks/regions/classes/Region.java index 1377337ede1..cb068ae1328 100644 --- a/src/main/java/ch/njol/skript/hooks/regions/classes/Region.java +++ b/src/main/java/ch/njol/skript/hooks/regions/classes/Region.java @@ -66,6 +66,7 @@ public Region parse(String s, final ParseContext context) { quoted = true; break; case COMMAND: + case PARSE: case CONFIG: quoted = false; break; diff --git a/src/main/java/ch/njol/skript/lang/ParseContext.java b/src/main/java/ch/njol/skript/lang/ParseContext.java index e3e648a4f23..7532d16859e 100644 --- a/src/main/java/ch/njol/skript/lang/ParseContext.java +++ b/src/main/java/ch/njol/skript/lang/ParseContext.java @@ -36,6 +36,10 @@ public enum ParseContext { * Only used for parsing arguments of commands */ COMMAND, + /** + * Used for parsing text in {@link ch.njol.skript.expressions.ExprParse} + */ + PARSE, /** * Used for parsing values from a config */ diff --git a/src/main/java/ch/njol/skript/lang/SkriptParser.java b/src/main/java/ch/njol/skript/lang/SkriptParser.java index d5f8ab99732..7f783e7d824 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptParser.java +++ b/src/main/java/ch/njol/skript/lang/SkriptParser.java @@ -317,7 +317,10 @@ private final <T> Expression<? extends T> parseSingleExpr(final boolean allowUnp assert types.length == 1 || !CollectionUtils.contains(types, Object.class); if (expr.isEmpty()) return null; - if (context != ParseContext.COMMAND && expr.startsWith("(") && expr.endsWith(")") && next(expr, 0, context) == expr.length()) + if (context != ParseContext.COMMAND && + context != ParseContext.PARSE && + expr.startsWith("(") && expr.endsWith(")") && + next(expr, 0, context) == expr.length()) return new SkriptParser(this, "" + expr.substring(1, expr.length() - 1)).parseSingleExpr(allowUnparsedLiteral, error, types); final ParseLogHandler log = SkriptLogger.startParseLogHandler(); try { @@ -410,7 +413,10 @@ private final Expression<?> parseSingleExpr(final boolean allowUnparsedLiteral, return null; // Command special parsing - if (context != ParseContext.COMMAND && expr.startsWith("(") && expr.endsWith(")") && next(expr, 0, context) == expr.length()) + if (context != ParseContext.COMMAND && + context != ParseContext.PARSE && + expr.startsWith("(") && expr.endsWith(")") && + next(expr, 0, context) == expr.length()) return new SkriptParser(this, "" + expr.substring(1, expr.length() - 1)).parseSingleExpr(allowUnparsedLiteral, error, vi); final ParseLogHandler log = SkriptLogger.startParseLogHandler(); try { @@ -658,7 +664,7 @@ public final <T> Expression<? extends T> parseExpression(final Class<? extends T } } if (i != expr.length()) { - assert i == -1 && context != ParseContext.COMMAND : i + "; " + expr; + assert i == -1 && context != ParseContext.COMMAND && context != ParseContext.PARSE : i + "; " + expr; log.printError("Invalid brackets/variables/text in '" + expr + "'", ErrorQuality.NOT_AN_EXPRESSION); return null; } @@ -782,7 +788,7 @@ public final Expression<?> parseExpression(final ExprInfo vi) { } } if (i != expr.length()) { - assert i == -1 && context != ParseContext.COMMAND : i + "; " + expr; + assert i == -1 && context != ParseContext.COMMAND && context != ParseContext.PARSE : i + "; " + expr; log.printError("Invalid brackets/variables/text in '" + expr + "'", ErrorQuality.NOT_AN_EXPRESSION); return null; } @@ -1159,7 +1165,7 @@ public static String notOfType(final ClassInfo<?>... cs) { /** * Returns the next character in the expression, skipping strings, * variables and parentheses - * (unless {@code context} is {@link ParseContext#COMMAND}). + * (unless {@code context} is {@link ParseContext#COMMAND} or {@link ParseContext#PARSE}). * * @param expr The expression to traverse. * @param startIndex The index to start at. @@ -1176,7 +1182,7 @@ public static int next(String expr, int startIndex, ParseContext context) { if (startIndex >= exprLength) return -1; - if (context == ParseContext.COMMAND) + if (context == ParseContext.COMMAND || context == ParseContext.PARSE) return startIndex + 1; int j; @@ -1201,7 +1207,8 @@ public static int next(String expr, int startIndex, ParseContext context) { /** * Returns the next occurrence of the needle in the haystack. * Similar to {@link #next(String, int, ParseContext)}, this method skips - * strings, variables and parentheses (unless <tt>context</tt> is {@link ParseContext#COMMAND}). + * strings, variables and parentheses (unless <tt>context</tt> is {@link ParseContext#COMMAND} + * or {@link ParseContext#PARSE}). * * @param haystack The string to search in. * @param needle The string to search for. @@ -1214,7 +1221,7 @@ public static int next(String expr, int startIndex, ParseContext context) { public static int nextOccurrence(String haystack, String needle, int startIndex, ParseContext parseContext, boolean caseSensitive) { if (startIndex < 0) throw new StringIndexOutOfBoundsException(startIndex); - if (parseContext == ParseContext.COMMAND) + if (parseContext == ParseContext.COMMAND || parseContext == ParseContext.PARSE) return haystack.indexOf(needle, startIndex); int haystackLength = haystack.length(); @@ -1285,11 +1292,11 @@ private ParseResult parse_i(String pattern, int i, int j) { * @return The pattern with %codenames% and a boolean array that contains whether the expressions are plural or not */ @Nullable - public static NonNullPair<String, boolean[]> validatePattern(final String pattern) { - final List<Boolean> ps = new ArrayList<>(); + public static NonNullPair<String, NonNullPair<ClassInfo<?>, Boolean>[]> validatePattern(final String pattern) { + final List<NonNullPair<ClassInfo<?>, Boolean>> pairs = new ArrayList<>(); int groupLevel = 0, optionalLevel = 0; final Deque<Character> groups = new LinkedList<>(); - final StringBuilder b = new StringBuilder(pattern.length()); + final StringBuilder stringBuilder = new StringBuilder(pattern.length()); int last = 0; for (int i = 0; i < pattern.length(); i++) { final char c = pattern.charAt(i); @@ -1332,13 +1339,13 @@ public static NonNullPair<String, boolean[]> validatePattern(final String patter final int j = pattern.indexOf('%', i + 1); if (j == -1) return error("Missing end sign '%' of expression. Escape the percent sign to match a literal '%': '\\%'"); - final NonNullPair<String, Boolean> p = Utils.getEnglishPlural("" + pattern.substring(i + 1, j)); - final ClassInfo<?> ci = Classes.getClassInfoFromUserInput(p.getFirst()); - if (ci == null) - return error("The type '" + p.getFirst() + "' could not be found. Please check your spelling or escape the percent signs if you want to match literal %s: \"\\%not an expression\\%\""); - ps.add(p.getSecond()); - b.append(pattern.substring(last, i + 1)); - b.append(Utils.toEnglishPlural(ci.getCodeName(), p.getSecond())); + final NonNullPair<String, Boolean> pair = Utils.getEnglishPlural("" + pattern.substring(i + 1, j)); + final ClassInfo<?> classInfo = Classes.getClassInfoFromUserInput(pair.getFirst()); + if (classInfo == null) + return error("The type '" + pair.getFirst() + "' could not be found. Please check your spelling or escape the percent signs if you want to match literal %s: \"\\%not an expression\\%\""); + pairs.add(new NonNullPair<>(classInfo, pair.getSecond())); + stringBuilder.append(pattern, last, i + 1); + stringBuilder.append(Utils.toEnglishPlural(classInfo.getCodeName(), pair.getSecond())); last = j; i = j; } else if (c == '\\') { @@ -1347,15 +1354,13 @@ public static NonNullPair<String, boolean[]> validatePattern(final String patter i++; } } - b.append(pattern.substring(last)); - final boolean[] plurals = new boolean[ps.size()]; - for (int i = 0; i < plurals.length; i++) - plurals[i] = ps.get(i); - return new NonNullPair<>("" + b.toString(), plurals); + stringBuilder.append(pattern.substring(last)); + //noinspection unchecked + return new NonNullPair<>(stringBuilder.toString(), pairs.toArray(new NonNullPair[0])); } @Nullable - private static NonNullPair<String, boolean[]> error(final String error) { + private static NonNullPair<String, NonNullPair<ClassInfo<?>, Boolean>[]> error(final String error) { Skript.error("Invalid pattern: " + error); return null; } diff --git a/src/main/java/ch/njol/skript/lang/Variable.java b/src/main/java/ch/njol/skript/lang/Variable.java index 0b3fc39aa1e..619418a1743 100644 --- a/src/main/java/ch/njol/skript/lang/Variable.java +++ b/src/main/java/ch/njol/skript/lang/Variable.java @@ -542,7 +542,7 @@ public void change(Event e, @Nullable Object[] delta, ChangeMode mode) throws Un for (Object d : delta) { if (d instanceof Object[]) { for (int j = 0; j < ((Object[]) d).length; j++) { - setIndex(e, "" + i + SEPARATOR + j, ((Object[]) d)[j]); + setIndex(e, "" + i + SEPARATOR + (j + 1), ((Object[]) d)[j]); } } else { setIndex(e, "" + i, d); diff --git a/src/main/java/ch/njol/skript/registrations/Classes.java b/src/main/java/ch/njol/skript/registrations/Classes.java index a2958b4cd04..fdb5ba2544c 100644 --- a/src/main/java/ch/njol/skript/registrations/Classes.java +++ b/src/main/java/ch/njol/skript/registrations/Classes.java @@ -500,7 +500,7 @@ public static <T> T parse(final String s, final Class<T> c, final ParseContext c return t; } for (final ConverterInfo<?, ?> conv : Converters.getConverterInfos()) { - if (context == ParseContext.COMMAND && (conv.getFlags() & Commands.CONVERTER_NO_COMMAND_ARGUMENTS) != 0) + if ((context == ParseContext.COMMAND || context == ParseContext.PARSE) && (conv.getFlags() & Commands.CONVERTER_NO_COMMAND_ARGUMENTS) != 0) continue; if (c.isAssignableFrom(conv.getTo())) { log.clear(); diff --git a/src/test/skript/tests/syntaxes/expressions/ExprParse.sk b/src/test/skript/tests/syntaxes/expressions/ExprParse.sk new file mode 100644 index 00000000000..4476ecf4512 --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprParse.sk @@ -0,0 +1,16 @@ +test "ExprParse": + assert "1" parsed as "%integer%" where [1 is 1] exists to fail with "Parsing as one single expression isn't single" + assert "1, 2" parsed as "%integers%" where [1 is 1] exists with "Parsing as one plural expression is single" + assert "1, 2" parsed as "%integer%, %integer%" where [1 is 1] exists with "Parsing as multiple expression is single" + + assert "hello" parsed as "%object%" exists to fail with "Parsing as a type that can't be parsed should fail" + assert "hello" parsed as object exists to fail with "Parsing as a type that can't be parsed should fail" + + assert "1" parsed as integer is 1 with "Failed parsing" + assert "1" parsed as "%integer%" is 1 with "Failed parsing with one single expression" + assert "1, 2" parsed as "%integers%" is 1 or 2 with "Failed parsing with one plural expression" + assert "1, 2" parsed as "%integer%, %integer%" is 1 or 2 with "Failed parsing with two single expressions" + + set {_parse::*} to "1, 2" parsed as "%integers%" + assert {_parse::1::*} is 1 or 2 with "Setting list to plural expression in parsing doesn't create sublist" + assert indices of {_parse::1::*} is "1" or "2" with "Sublist doesn't start from 1" From d1fd45347820b8acab33ca689a513e3012351640 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Sun, 22 Oct 2023 14:12:22 -0600 Subject: [PATCH 511/619] Allow numbers in the changer for ExprHotbarSlot (#6139) * Allow numbers in the changer for ExprHotbarSlot * Update ExprHotbarSlot.java * Update ExprHotbarSlot.java --------- Co-authored-by: Moderocky <admin@moderocky.com> --- .../skript/expressions/ExprHotbarSlot.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprHotbarSlot.java b/src/main/java/ch/njol/skript/expressions/ExprHotbarSlot.java index ced8c4df34f..032afc50501 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprHotbarSlot.java +++ b/src/main/java/ch/njol/skript/expressions/ExprHotbarSlot.java @@ -96,19 +96,25 @@ public Slot get(Player player) { @Nullable public Class<?>[] acceptChange(ChangeMode mode) { if (mode == ChangeMode.SET) - return new Class[] {Slot.class}; + return new Class[] {Slot.class, Number.class}; return null; } @Override public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { assert delta != null; - Slot slot = (Slot) delta[0]; - if (!(slot instanceof InventorySlot)) - return; // Only inventory slots can be hotbar slots + Object object = delta[0]; + Number number = null; + if (object instanceof InventorySlot) { + number = ((InventorySlot) object).getIndex(); + } else if (object instanceof Number) { + number = (Number) object; + } - int index = ((InventorySlot) slot).getIndex(); - if (index > 8) // Only slots in hotbar can be current hotbar slot + if (number == null) + return; + int index = number.intValue(); + if (index > 8 || index < 0) // Only slots in hotbar can be current hotbar slot return; for (Player player : getExpr().getArray(event)) From 8895157bae43ccb9a481067ce7fe1e2d7794c5e5 Mon Sep 17 00:00:00 2001 From: _tud <mmbakkar06@gmail.com> Date: Mon, 30 Oct 2023 11:15:01 +0300 Subject: [PATCH 512/619] Fix Sorted List Expression (#6102) * Fix ExprSortedList * Update src/main/java/ch/njol/skript/expressions/ExprSortedList.java Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> * Fix test Co-authored-by: Moderocky <admin@moderocky.com> --------- Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Co-authored-by: Moderocky <admin@moderocky.com> --- .../skript/expressions/ExprSortedList.java | 46 +++++++++---------- .../syntaxes/expressions/ExprSortedList.sk | 17 +++++++ 2 files changed, 38 insertions(+), 25 deletions(-) create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprSortedList.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprSortedList.java b/src/main/java/ch/njol/skript/expressions/ExprSortedList.java index a19779c0774..3cfed13b0a6 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprSortedList.java +++ b/src/main/java/ch/njol/skript/expressions/ExprSortedList.java @@ -32,19 +32,18 @@ import ch.njol.util.coll.CollectionUtils; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.comparator.Comparator; +import org.skriptlang.skript.lang.comparator.Comparators; import java.lang.reflect.Array; -import java.util.Arrays; @Name("Sorted List") -@Description({"Sorts given list in natural order. All objects in list must be comparable;", - "if they're not, this expression will return nothing." -}) -@Examples({"set {_sorted::*} to sorted {_players::*}"}) +@Description("Sorts given list in natural order. All objects in list must be comparable; if they're not, this expression will return nothing.") +@Examples("set {_sorted::*} to sorted {_players::*}") @Since("2.2-dev19") public class ExprSortedList extends SimpleExpression<Object> { - static{ + static { Skript.registerExpression(ExprSortedList.class, Object.class, ExpressionType.COMBINED, "sorted %objects%"); } @@ -67,29 +66,26 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable - protected Object[] get(Event e) { - Object[] unsorted = list.getArray(e); - Object[] sorted = (Object[]) Array.newInstance(getReturnType(), unsorted.length); // Not yet sorted... - - for (int i = 0; i < sorted.length; i++) { - Object value = unsorted[i]; - if (value instanceof Long) { - // Hope it fits to the double... - sorted[i] = (double) (Long) value; - } else { - // No conversion needed - sorted[i] = value; - } - } - + protected Object[] get(Event event) { try { - Arrays.sort(sorted); // Now sorted - } catch (IllegalArgumentException | ClassCastException ex) { // In case elements are not comparable - return new Object[]{}; // We don't have a sorted array available + return list.stream(event) + .sorted(ExprSortedList::compare) + .toArray(); + } catch (IllegalArgumentException | ClassCastException e) { + return (Object[]) Array.newInstance(getReturnType(), 0); } - return sorted; } + @SuppressWarnings("unchecked") + private static <A, B> int compare(A a, B b) throws IllegalArgumentException, ClassCastException { + Comparator<A, B> comparator = Comparators.getComparator((Class<A>) a.getClass(), (Class<B>) b.getClass()); + if (comparator != null && comparator.supportsOrdering()) + return comparator.compare(a, b).getRelation(); + if (!(a instanceof Comparable)) + throw new IllegalArgumentException(); + return ((Comparable<B>) a).compareTo(b); + } + @Override @Nullable @SuppressWarnings("unchecked") diff --git a/src/test/skript/tests/syntaxes/expressions/ExprSortedList.sk b/src/test/skript/tests/syntaxes/expressions/ExprSortedList.sk new file mode 100644 index 00000000000..819486dbaaa --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprSortedList.sk @@ -0,0 +1,17 @@ +test "sort": + # Populate list + set {_i} to 0 + loop 5 times: + set {_i} to {_i} + 1 + set {_list::%{_i}%} to a random integer from 1 to 100 + loop 5 times: + set {_i} to {_i} + 1 + set {_list::%{_i}%} to a random number from 1 to 100 + + # Test sorting + loop sorted {_list::*}: + if {_prev} is set: + assert loop-value >= {_prev} with "Couldn't sort correctly" + set {_prev} to loop-value + + assert (sorted 1 and "test") is not set with "Sorting incomparable values returned a value" From 9deb2888150c77d54b1b8135cfb6ee6d9205ae13 Mon Sep 17 00:00:00 2001 From: Moderocky <admin@moderocky.com> Date: Mon, 30 Oct 2023 18:53:18 +0000 Subject: [PATCH 513/619] Fix colour codes being reset in reload message. (#6150) Fix colour codes being reset. --- src/main/resources/lang/english.lang | 2 +- src/main/resources/lang/french.lang | 2 +- src/main/resources/lang/german.lang | 6 +++--- src/main/resources/lang/japanese.lang | 2 +- src/main/resources/lang/korean.lang | 4 ++-- src/main/resources/lang/polish.lang | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/resources/lang/english.lang b/src/main/resources/lang/english.lang index 40db6ddb7a0..b5684c1fedc 100644 --- a/src/main/resources/lang/english.lang +++ b/src/main/resources/lang/english.lang @@ -68,7 +68,7 @@ skript command: aliases: the aliases script: <gold>%s<reset> scripts in folder: all scripts in <gold>%s<reset> - x scripts in folder: <gold>%2$s <reset>script¦¦s¦ in <gold>%1$s<reset> + x scripts in folder: <gold>%2$s <lime>script¦¦s¦ in <gold>%1$s<reset> empty folder: <gold>%s<reset> does not contain any enabled scripts. enable: all: diff --git a/src/main/resources/lang/french.lang b/src/main/resources/lang/french.lang index 6aa7e26342d..39cc5555a2a 100644 --- a/src/main/resources/lang/french.lang +++ b/src/main/resources/lang/french.lang @@ -68,7 +68,7 @@ skript command: aliases: les alias script: <gold>%s<reset> scripts in folder: tous les scripts dans <gold>%s<reset> - x scripts in folder: <gold>%2$s <reset>script¦¦s¦ dans <gold>%1$s<reset> + x scripts in folder: <gold>%2$s <lime>script¦¦s¦ dans <gold>%1$s<reset> empty folder: <gold>%s<reset> ne contient aucun script activé. enable: all: diff --git a/src/main/resources/lang/german.lang b/src/main/resources/lang/german.lang index 0a8255f44e8..876dc857a92 100644 --- a/src/main/resources/lang/german.lang +++ b/src/main/resources/lang/german.lang @@ -47,7 +47,7 @@ skript command: info: Druckt eine Nachricht mit Links zu den Aliases und der Dokumentation von Skript. gen-docs: Generiert Dokumentation mithilfe von docs/templates im Plugin-Ordner test: Wird zum Ausführen von Skript-Tests verwendet - + invalid script: Das Skript <grey>'<gold>%s<grey>'<red> konnte nicht gefunden werden. invalid folder: Der Ordner <grey>'<gold>%s<grey>'<red> konnte nicht gefunden werden. reload: @@ -61,14 +61,14 @@ skript command: error details: <light red> %s<reset>\n other details: <white> %s<reset>\n line details: <gold> Linie: <gray>%s<reset>\n <reset> - + config, aliases and scripts: die Konfiguration, alle Itemnamen und alle Skripte scripts: alle Skripte main config: Konfiguration aliases: die Itemnamen script: <gold>%s<reset> scripts in folder: alle Skripte in <gold>%s<reset> - x scripts in folder: <gold>%2$s <reset>Skript¦¦e¦ in <gold>%1$s<reset> + x scripts in folder: <gold>%2$s <lime>Skript¦¦e¦ in <gold>%1$s<reset> empty folder: <gold>%s<reset> enthält keine aktivierten Skripte. enable: all: diff --git a/src/main/resources/lang/japanese.lang b/src/main/resources/lang/japanese.lang index 708df5a717d..50c27522db6 100644 --- a/src/main/resources/lang/japanese.lang +++ b/src/main/resources/lang/japanese.lang @@ -68,7 +68,7 @@ skript command: aliases: エイリアス script: <gold>%s<reset> scripts in folder: <gold>%s<reset>フォルダ内の全てのスクリプト - x scripts in folder: <gold>%1$s<reset>フォルダ内の<gold>%2$s<reset>個のスクリプト + x scripts in folder: <gold>%1$s<lime>フォルダ内の<gold>%2$s<reset>個のスクリプト empty folder: 有効化されたスクリプトが<gold>%s<reset>フォルダ内にありませんでした。 enable: all: diff --git a/src/main/resources/lang/korean.lang b/src/main/resources/lang/korean.lang index b2be122224b..580c78e3744 100644 --- a/src/main/resources/lang/korean.lang +++ b/src/main/resources/lang/korean.lang @@ -47,7 +47,7 @@ skript command: info: Skript의 별명 및 문서에 대한 링크가있는 메시지를 표시합니다 gen-docs: 플러그인 폴더의 문서 템플릿을 사용하여 문서를 생성합니다. test: Skript 테스트를 실행할 때 사용됩니다. - + invalid script: 스크립트 폴더에서 <grey>'<gold>%s<grey>'<red> 스크립트를 찾을 수 없습니다! invalid folder: 스크립트 폴더에서 <grey>'<gold>%s<grey>'<red> 폴더를 찾을 수 없습니다! reload: @@ -68,7 +68,7 @@ skript command: aliases: 별칭(aliases) script: <gold>%s<reset> scripts in folder: <gold>%s<reset>폴더 속 모든 스크립트 - x scripts in folder: <gold>%1$s<reset> 폴더 속 <gold>%2$s<reset>개의 스크립트 + x scripts in folder: <gold>%1$s<lime> 폴더 속 <gold>%2$s<reset>개의 스크립트 empty folder: <gold>%s<reset>에 활성화된 스크립트가 없습니다. enable: all: diff --git a/src/main/resources/lang/polish.lang b/src/main/resources/lang/polish.lang index 633f7e555a9..1f285032f0b 100644 --- a/src/main/resources/lang/polish.lang +++ b/src/main/resources/lang/polish.lang @@ -68,7 +68,7 @@ skript command: aliases: aliasy script: <gold>%s<reset> scripts in folder: wszystkie skrypty w <gold>%s<reset> - x scripts in folder: <gold>%2$s <reset>skrypt¦¦ów¦ w <gold>%1$s<reset> + x scripts in folder: <gold>%2$s <lime>skrypt¦¦ów¦ w <gold>%1$s<reset> empty folder: <gold>%s<reset> nie ma w sobie żadnych włączonych skryptów. enable: all: From f43c97e7eed3dd3a9cefb4ed75b181a2bab0a3be Mon Sep 17 00:00:00 2001 From: DelayedGaming <simmz0403@gmail.com> Date: Wed, 1 Nov 2023 01:02:27 +0800 Subject: [PATCH 514/619] improve pattern --- src/main/java/ch/njol/skript/events/EvtMove.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/ch/njol/skript/events/EvtMove.java b/src/main/java/ch/njol/skript/events/EvtMove.java index 6c76b6eaedb..4283a606d89 100644 --- a/src/main/java/ch/njol/skript/events/EvtMove.java +++ b/src/main/java/ch/njol/skript/events/EvtMove.java @@ -42,12 +42,12 @@ public class EvtMove extends SkriptEvent { else events = CollectionUtils.array(PlayerMoveEvent.class); Skript.registerEvent("Move / Rotate", EvtMove.class, events, - "%entitydata% (move|walk|step|rotate:(look[ing] around|rotate))", - "%entitydata% (move|walk|step) or (look[ing] around|rotate)", - "%entitydata% (look[ing] around|rotate) or (move|walk|step)") + "%entitydata% (move|walk|step|rotate:(turn[ing] around|rotate))", + "%entitydata% (move|walk|step) or (turn[ing] around|rotate)", + "%entitydata% (turn[ing] around|rotate) or (move|walk|step)") .description( "Called when a player or entity moves or rotates their head.", - "NOTE: Move event will only be called when the entity/player moves position, keyword 'rotate' is for orientation (ie: looking around), and the combined syntax listens for both.", + "NOTE: Move event will only be called when the entity/player moves position, keyword 'turn around' is for orientation (ie: looking around), and the combined syntax listens for both.", "NOTE: These events can be performance heavy as they are called quite often.") .examples( "on player move:", @@ -56,10 +56,10 @@ public class EvtMove extends SkriptEvent { "on skeleton move:", "\tif event-entity is not in world \"world\":", "\t\tkill event-entity", - "on player rotate:", - "send action bar \"You are currently looking around!\" to player") + "on player turning around:", + "send action bar \"You are currently turning your head around!\" to player") .requiredPlugins("Paper 1.16.5+ (entity move)") - .since("2.6, INSERT VERSION (rotate)"); + .since("2.6, INSERT VERSION (turn around)"); } private EntityData<?> entityData; From a91bf9a2ae1b0eefcd70929580c1e9233850ab2b Mon Sep 17 00:00:00 2001 From: _tud <98935832+UnderscoreTud@users.noreply.github.com> Date: Wed, 1 Nov 2023 11:51:17 +0300 Subject: [PATCH 515/619] Fix ExprDurability's Changer (#6154) * Fix ExprDurability's changer * Change method name. * Add simple test. --------- Co-authored-by: Moderocky <admin@moderocky.com> --- .../skript/expressions/ExprDurability.java | 106 +++++++++--------- .../syntaxes/expressions/ExprDurability.sk | 14 +++ 2 files changed, 68 insertions(+), 52 deletions(-) create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprDurability.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprDurability.java b/src/main/java/ch/njol/skript/expressions/ExprDurability.java index f592fd882f3..e946cdb02ec 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprDurability.java +++ b/src/main/java/ch/njol/skript/expressions/ExprDurability.java @@ -21,12 +21,14 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; +import org.bukkit.Material; import org.bukkit.event.Event; import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.Damageable; +import org.bukkit.inventory.meta.ItemMeta; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.aliases.ItemType; -import ch.njol.skript.bukkitutil.ItemUtils; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; @@ -44,33 +46,28 @@ "set durability of player's held item to 0" }) @Since("1.2, 2.7 (durability reversed)") -public class ExprDurability extends SimplePropertyExpression<Object, Long> { +public class ExprDurability extends SimplePropertyExpression<Object, Integer> { private boolean durability; static { - register(ExprDurability.class, Long.class, "(damage[s] [value[s]]|durability:durabilit(y|ies))", "itemtypes/slots"); + register(ExprDurability.class, Integer.class, "(damage[s] [value[s]]|1:durabilit(y|ies))", "itemtypes/slots"); } @Override public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - durability = parseResult.hasTag("durability"); + durability = parseResult.mark == 1; return super.init(exprs, matchedPattern, isDelayed, parseResult); } @Override @Nullable - public Long convert(Object object) { - ItemStack itemStack = null; - if (object instanceof Slot) { - itemStack = ((Slot) object).getItem(); - } else if (object instanceof ItemType) { - itemStack = ((ItemType) object).getRandom(); - } - if (itemStack == null) + public Integer convert(Object object) { + ItemType itemType = asItemType(object); + if (itemType == null) return null; - long damage = ItemUtils.getDamage(itemStack); - return durability ? itemStack.getType().getMaxDurability() - damage : damage; + ItemMeta meta = itemType.getItemMeta(); + return meta instanceof Damageable ? convertToDamage(itemType.getMaterial(), ((Damageable) meta).getDamage()) : 0; } @Override @@ -89,57 +86,52 @@ public Class<?>[] acceptChange(ChangeMode mode) { @Override public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { - int i = delta == null ? 0 : ((Number) delta[0]).intValue(); - Object[] objects = getExpr().getArray(event); - for (Object object : objects) { - ItemStack itemStack = null; - - if (object instanceof ItemType) { - itemStack = ((ItemType) object).getRandom(); - } else if (object instanceof Slot) { - itemStack = ((Slot) object).getItem(); - } - if (itemStack == null) - return; - - int changeValue = ItemUtils.getDamage(itemStack); - if (durability) - changeValue = itemStack.getType().getMaxDurability() - changeValue; - + int change = delta == null ? 0 : ((Number) delta[0]).intValue(); + if (mode == ChangeMode.REMOVE) + change = -change; + for (Object object : getExpr().getArray(event)) { + ItemType itemType = asItemType(object); + if (itemType == null) + continue; + + ItemMeta meta = itemType.getItemMeta(); + if (!(meta instanceof Damageable)) + continue; + Damageable damageable = (Damageable) meta; + + Material material = itemType.getMaterial(); switch (mode) { - case REMOVE: - i = -i; - //$FALL-THROUGH$ case ADD: - changeValue += i; + case REMOVE: + int current = convertToDamage(material, damageable.getDamage()); + damageable.setDamage(convertToDamage(material, current + change)); break; case SET: - changeValue = i; + damageable.setDamage(convertToDamage(material, change)); break; case DELETE: case RESET: - changeValue = 0; - break; - case REMOVE_ALL: - assert false; + damageable.setDamage(0); } - if (durability && mode != ChangeMode.RESET && mode != ChangeMode.DELETE) - changeValue = itemStack.getType().getMaxDurability() - changeValue; - - if (object instanceof ItemType) { - ItemUtils.setDamage(itemStack, changeValue); - ((ItemType) object).setTo(new ItemType(itemStack)); - } else { - ItemUtils.setDamage(itemStack, changeValue); - ((Slot) object).setItem(itemStack); - } + itemType.setItemMeta(meta); + if (object instanceof Slot) + ((Slot) object).setItem(itemType.getRandom()); } } + private int convertToDamage(Material material, int value) { + if (!durability) + return value; + int maxDurability = material.getMaxDurability(); + if (maxDurability == 0) + return 0; + return maxDurability - value; + } + @Override - public Class<? extends Long> getReturnType() { - return Long.class; + public Class<? extends Integer> getReturnType() { + return Integer.class; } @Override @@ -147,4 +139,14 @@ public String getPropertyName() { return durability ? "durability" : "damage"; } + @Nullable + private static ItemType asItemType(Object object) { + if (object instanceof ItemType) + return (ItemType) object; + ItemStack itemStack = ((Slot) object).getItem(); + if (itemStack == null) + return null; + return new ItemType(itemStack); + } + } diff --git a/src/test/skript/tests/syntaxes/expressions/ExprDurability.sk b/src/test/skript/tests/syntaxes/expressions/ExprDurability.sk new file mode 100644 index 00000000000..724000493e5 --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprDurability.sk @@ -0,0 +1,14 @@ +test "durability": + set {_i} to an iron sword + set {_max} to max durability of {_i} + assert damage of {_i} is 0 with "default item damage failed" + assert durability of {_i} is {_max} with "default item durability failed" + set damage of {_i} to 64 + assert damage of {_i} is 64 with "item damage failed" + assert durability of {_i} is {_max} - 64 with "item durability failed" + set durability of {_i} to 10 + assert damage of {_i} is {_max} - 10 with "inverse item damage failed" + assert durability of {_i} is 10 with "inverse item durability failed" + set durability of {_i} to 0 + assert damage of {_i} is {_max} with "max item damage failed" + assert durability of {_i} is 0 with "zero item durability failed" From 451b73942cea02e4b28045fff28796445f8d050c Mon Sep 17 00:00:00 2001 From: 3meraldK <48335651+3meraldK@users.noreply.github.com> Date: Wed, 19 Jul 2023 16:09:30 +0200 Subject: [PATCH 516/619] Catch the exception when pushing entity by non finite vector (#5765) --- src/main/java/ch/njol/skript/effects/EffPush.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/ch/njol/skript/effects/EffPush.java b/src/main/java/ch/njol/skript/effects/EffPush.java index c248a51acf4..8cda4d62ffe 100644 --- a/src/main/java/ch/njol/skript/effects/EffPush.java +++ b/src/main/java/ch/njol/skript/effects/EffPush.java @@ -77,6 +77,10 @@ protected void execute(final Event e) { final Vector mod = d.getDirection(en); if (v != null) mod.normalize().multiply(v.doubleValue()); + if (!(Double.isFinite(mod.getX()) && Double.isFinite(mod.getY()) && Double.isFinite(mod.getZ()))) { + // Some component of the mod vector is not finite, so just stop + return; + } en.setVelocity(en.getVelocity().add(mod)); // REMIND add NoCheatPlus exception to players } } From 8efc550c4c782481adbab44f1c36b382509a4f1b Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Wed, 1 Nov 2023 05:27:17 -0700 Subject: [PATCH 517/619] Rework EvtGrow (#5639) * Update EvtGrow.java * Cleanup, use blockstate directly instead of blockstateblock * Update src/main/java/ch/njol/skript/events/EvtGrow.java Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> * Rework EvtGrow (simple fix, my behind!) * Update EvtGrow.java * Fix upstream paper issue and add JUnit test for 1.18+ * Requested Changes * Update src/test/java/org/skriptlang/skript/test/tests/syntaxes/EvtGrowTest.java Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> --------- Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> --- .../ch/njol/skript/bukkitutil/ItemUtils.java | 60 +++++ .../java/ch/njol/skript/events/EvtGrow.java | 211 ++++++++++++++---- .../test/tests/syntaxes/EvtGrowTest.java | 70 ++++++ src/test/skript/tests/junit/EvtGrow.sk | 70 ++++++ 4 files changed, 369 insertions(+), 42 deletions(-) create mode 100644 src/test/java/org/skriptlang/skript/test/tests/syntaxes/EvtGrowTest.java create mode 100644 src/test/skript/tests/junit/EvtGrow.sk diff --git a/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java b/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java index 1fd542ed4db..ca373598eff 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java +++ b/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java @@ -19,6 +19,7 @@ package ch.njol.skript.bukkitutil; import org.bukkit.Material; +import org.bukkit.TreeType; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.Damageable; import org.bukkit.inventory.meta.ItemMeta; @@ -26,6 +27,8 @@ import ch.njol.skript.Skript; +import java.util.HashMap; + /** * Miscellaneous static utility methods related to items. */ @@ -105,5 +108,62 @@ public static boolean isAir(Material type) { return type.isAir(); return type == Material.AIR || type == Material.CAVE_AIR || type == Material.VOID_AIR; } + + // TreeType -> Sapling (Material) conversion for EvtGrow + private static final HashMap<TreeType, Material> TREE_TO_SAPLING_MAP = new HashMap<>(); + static { + // Populate TREE_TO_SAPLING_MAP + // oak + TREE_TO_SAPLING_MAP.put(TreeType.TREE, Material.OAK_SAPLING); + TREE_TO_SAPLING_MAP.put(TreeType.BIG_TREE, Material.OAK_SAPLING); + TREE_TO_SAPLING_MAP.put(TreeType.SWAMP, Material.OAK_SAPLING); + // spruce + TREE_TO_SAPLING_MAP.put(TreeType.REDWOOD, Material.SPRUCE_SAPLING); + TREE_TO_SAPLING_MAP.put(TreeType.TALL_REDWOOD, Material.SPRUCE_SAPLING); + TREE_TO_SAPLING_MAP.put(TreeType.MEGA_REDWOOD, Material.SPRUCE_SAPLING); + // birch + TREE_TO_SAPLING_MAP.put(TreeType.BIRCH, Material.BIRCH_SAPLING); + TREE_TO_SAPLING_MAP.put(TreeType.TALL_BIRCH, Material.BIRCH_SAPLING); + // jungle + TREE_TO_SAPLING_MAP.put(TreeType.JUNGLE, Material.JUNGLE_SAPLING); + TREE_TO_SAPLING_MAP.put(TreeType.SMALL_JUNGLE, Material.JUNGLE_SAPLING); + TREE_TO_SAPLING_MAP.put(TreeType.JUNGLE_BUSH, Material.JUNGLE_SAPLING); + TREE_TO_SAPLING_MAP.put(TreeType.COCOA_TREE, Material.JUNGLE_SAPLING); + // acacia + TREE_TO_SAPLING_MAP.put(TreeType.ACACIA, Material.ACACIA_SAPLING); + // dark oak + TREE_TO_SAPLING_MAP.put(TreeType.DARK_OAK, Material.DARK_OAK_SAPLING); + + // mushrooms + TREE_TO_SAPLING_MAP.put(TreeType.BROWN_MUSHROOM, Material.BROWN_MUSHROOM); + TREE_TO_SAPLING_MAP.put(TreeType.RED_MUSHROOM, Material.RED_MUSHROOM); + + // chorus + TREE_TO_SAPLING_MAP.put(TreeType.CHORUS_PLANT, Material.CHORUS_FLOWER); + + // nether + if (Skript.isRunningMinecraft(1, 16)) { + TREE_TO_SAPLING_MAP.put(TreeType.WARPED_FUNGUS, Material.WARPED_FUNGUS); + TREE_TO_SAPLING_MAP.put(TreeType.CRIMSON_FUNGUS, Material.CRIMSON_FUNGUS); + } + + // azalea + if (Skript.isRunningMinecraft(1, 17)) + TREE_TO_SAPLING_MAP.put(TreeType.AZALEA, Material.AZALEA); + + // mangrove + if (Skript.isRunningMinecraft(1, 19)) { + TREE_TO_SAPLING_MAP.put(TreeType.MANGROVE, Material.MANGROVE_PROPAGULE); + TREE_TO_SAPLING_MAP.put(TreeType.TALL_MANGROVE, Material.MANGROVE_PROPAGULE); + } + + // cherry + if (Skript.isRunningMinecraft(1, 19, 4)) + TREE_TO_SAPLING_MAP.put(TreeType.CHERRY, Material.CHERRY_SAPLING); + } + + public static Material getTreeSapling(TreeType treeType) { + return TREE_TO_SAPLING_MAP.get(treeType); + } } diff --git a/src/main/java/ch/njol/skript/events/EvtGrow.java b/src/main/java/ch/njol/skript/events/EvtGrow.java index aa9bdc10b11..e4e6b2207e1 100644 --- a/src/main/java/ch/njol/skript/events/EvtGrow.java +++ b/src/main/java/ch/njol/skript/events/EvtGrow.java @@ -18,85 +18,212 @@ */ package ch.njol.skript.events; -import org.bukkit.event.Event; -import org.bukkit.event.block.BlockGrowEvent; -import org.bukkit.event.world.StructureGrowEvent; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.bukkitutil.ItemUtils; import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.LiteralList; import ch.njol.skript.lang.SkriptEvent; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.util.StructureType; -import ch.njol.util.Checker; import ch.njol.util.coll.CollectionUtils; +import org.bukkit.Material; +import org.bukkit.TreeType; +import org.bukkit.block.BlockState; +import org.bukkit.block.data.BlockData; +import org.bukkit.event.Event; +import org.bukkit.event.block.BlockGrowEvent; +import org.bukkit.event.world.StructureGrowEvent; +import org.eclipse.jdt.annotation.Nullable; public class EvtGrow extends SkriptEvent { - + /** * Growth event restriction. - * * ANY means any grow event goes. - * * Structure/block restrict for structure/block grow events only. */ - public static final int ANY = 0, STRUCTURE = 1, BLOCK = 2; + private static final int ANY = 0, STRUCTURE = 1, BLOCK = 2; + + // Of (involves x in any way), From (x -> something), Into (something -> x), From_Into (x -> y) + private static final int OF = 0, FROM = 1, INTO = 2, FROM_INTO = 3; static { Skript.registerEvent("Grow", EvtGrow.class, CollectionUtils.array(StructureGrowEvent.class, BlockGrowEvent.class), - "grow [of (1¦%-structuretype%|2¦%-itemtype%)]") - .description("Called when a tree, giant mushroom or plant grows to next stage.") - .examples("on grow:", "on grow of a tree:", "on grow of a huge jungle tree:") - .since("1.0 (2.2-dev20 for plants)"); + "grow[th] [of (1:%-structuretypes%|2:%-itemtypes/blockdatas%)]", + "grow[th] from %itemtypes/blockdatas%", + "grow[th] [in]to (1:%structuretypes%|2:%itemtypes/blockdatas%)", + "grow[th] from %itemtypes/blockdatas% [in]to (1:%structuretypes%|2:%itemtypes/blockdatas%)" + ) + .description( + "Called when a tree, giant mushroom or plant grows to next stage.", + "\"of\" matches any grow event, \"from\" matches only the old state, \"into\" matches only the new state," + + "and \"from into\" requires matching both the old and new states.", + "Using \"and\" lists in this event is equivalent to using \"or\" lists. The event will trigger if any one of the elements is what grew.") + .examples( + "on grow:", + "on grow of tree:", + "on grow of wheat[age=7]:", + "on grow from a sapling:", + "on grow into tree:", + "on grow from a sapling into tree:", + "on grow of wheat, carrots, or potatoes:", + "on grow into tree, giant mushroom, cactus:", + "on grow from wheat[age=0] to wheat[age=1] or wheat[age=2]:") + .since("1.0, 2.2-dev20 (plants), INSERT VERSION (from, into, blockdata)"); } @Nullable - private Literal<StructureType> types; + private Literal<Object> toTypes; @Nullable - private Literal<ItemType> blocks; - private int evtType; + private Literal<Object> fromTypes; + + // Restriction on the type of grow event, ANY, STRUCTURE or BLOCK + private int eventRestriction; + + // Restriction on the type of action, OF, FROM, INTO, or FROM_INTO + private int actionRestriction; @SuppressWarnings("unchecked") @Override - public boolean init(final Literal<?>[] args, final int matchedPattern, final ParseResult parser) { - evtType = parser.mark; // ANY, STRUCTURE or BLOCK - if (evtType == STRUCTURE) - types = (Literal<StructureType>) args[0]; - else if (evtType == BLOCK) - blocks = (Literal<ItemType>) args[1]; // Arg 1 may not be present... but it is in the array still, as null - // Else: no type restrictions specified + public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult) { + eventRestriction = parseResult.mark; // ANY, STRUCTURE or BLOCK + actionRestriction = matchedPattern; // OF, FROM, INTO, or FROM_INTO + + switch (actionRestriction) { + case OF: + if (eventRestriction == STRUCTURE) { + fromTypes = (Literal<Object>) args[0]; + } else if (eventRestriction == BLOCK) { + fromTypes = (Literal<Object>) args[1]; + } + break; + case FROM: + fromTypes = (Literal<Object>) args[0]; + break; + case INTO: + if (eventRestriction == STRUCTURE) { + toTypes = (Literal<Object>) args[0]; + } else if (eventRestriction == BLOCK) { + toTypes = (Literal<Object>) args[1]; + } + break; + case FROM_INTO: + fromTypes = (Literal<Object>) args[0]; + if (eventRestriction == STRUCTURE) { + toTypes = (Literal<Object>) args[1]; + } else if (eventRestriction == BLOCK) { + toTypes = (Literal<Object>) args[2]; + } + break; + default: + assert false; + return false; + } return true; } @Override - public boolean check(final Event e) { - if (evtType == STRUCTURE && types != null && e instanceof StructureGrowEvent) { - return types.check(e, new Checker<StructureType>() { - @Override - public boolean check(final StructureType t) { - return t.is(((StructureGrowEvent) e).getSpecies()); + public boolean check(Event event) { + // Exit early if we need fromTypes, but don't have it + if (fromTypes == null && actionRestriction != INTO) + // We want true for "on grow:", false for anything else + // So check against "OF", which is the first pattern; the one that allows "on grow:" + return actionRestriction == OF; + + // Can exit early if we're checking against a structure, but the event isn't a structure grow event + // Can also exit early if we're checking against a block, but the event isn't a block grow event AND we're not checking for OF + // With OF, we can have `on grow of sapling` or `big mushroom` be a StructureGrowEvent + if (eventRestriction == STRUCTURE && !(event instanceof StructureGrowEvent)) { + return false; + } else if (eventRestriction == BLOCK && !(event instanceof BlockGrowEvent) && actionRestriction != OF) { + return false; + } + + switch (actionRestriction) { + case OF: + return checkFrom(event, fromTypes) || checkTo(event, fromTypes); + case FROM: + return checkFrom(event, fromTypes); + case INTO: + return checkTo(event, toTypes); + case FROM_INTO: + return checkFrom(event, fromTypes) && checkTo(event, toTypes); + default: + assert false; + return false; + } + } + + private static boolean checkFrom(Event event, Literal<Object> types) { + // treat and lists as or lists + if (types instanceof LiteralList) + ((LiteralList<Object>) types).setAnd(false); + if (event instanceof StructureGrowEvent) { + Material sapling = ItemUtils.getTreeSapling(((StructureGrowEvent) event).getSpecies()); + return types.check(event, type -> { + if (type instanceof ItemType) { + return ((ItemType) type).isOfType(sapling); + } else if (type instanceof BlockData) { + return ((BlockData) type).getMaterial() == sapling; } + return false; }); - } else if (evtType == BLOCK && blocks != null && e instanceof BlockGrowEvent) { - assert blocks != null; - return blocks.check(e, new Checker<ItemType>() { - @Override - public boolean check(final ItemType t) { - return t.isOfType(((BlockGrowEvent) e).getBlock()); + } else if (event instanceof BlockGrowEvent) { + BlockState oldState = ((BlockGrowEvent) event).getBlock().getState(); + return types.check(event, type -> { + if (type instanceof ItemType) { + return ((ItemType) type).isOfType(oldState); + } else if (type instanceof BlockData) { + return ((BlockData) type).matches(oldState.getBlockData()); } + return false; }); } + return false; + } + private static boolean checkTo(Event event, Literal<Object> types) { + // treat and lists as or lists + if (types instanceof LiteralList) + ((LiteralList<Object>) types).setAnd(false); + if (event instanceof StructureGrowEvent) { + TreeType species = ((StructureGrowEvent) event).getSpecies(); + return types.check(event, type -> { + if (type instanceof StructureType) { + return ((StructureType) type).is(species); + } + return false; + }); + } else if (event instanceof BlockGrowEvent) { + BlockState newState = ((BlockGrowEvent) event).getNewState(); + return types.check(event, type -> { + if (type instanceof ItemType) { + return ((ItemType) type).isOfType(newState); + } else if (type instanceof BlockData) { + return ((BlockData) type).matches(newState.getBlockData()); + } + return false; + }); + } return false; } - + @Override - public String toString(final @Nullable Event e, final boolean debug) { - if (evtType == STRUCTURE) - return "grow" + (types != null ? " of " + types.toString(e, debug) : ""); - else if (evtType == BLOCK) - return "grow" + (blocks != null ? " of " + blocks.toString(e, debug) : ""); + public String toString(@Nullable Event event, boolean debug) { + if (fromTypes == null && toTypes == null) + return "grow"; + + switch (actionRestriction) { + case OF: + return "grow of " + fromTypes.toString(event, debug); + case FROM: + return "grow from " + fromTypes.toString(event, debug); + case INTO: + return "grow into " + toTypes.toString(event, debug); + case FROM_INTO: + return "grow from " + fromTypes.toString(event, debug) + " into " + toTypes.toString(event, debug); + } return "grow"; } diff --git a/src/test/java/org/skriptlang/skript/test/tests/syntaxes/EvtGrowTest.java b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/EvtGrowTest.java new file mode 100644 index 00000000000..ea59b85db29 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/EvtGrowTest.java @@ -0,0 +1,70 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.test.tests.syntaxes; + +import ch.njol.skript.Skript; +import ch.njol.skript.test.runner.SkriptJUnitTest; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class EvtGrowTest extends SkriptJUnitTest { + + private Block plant, birch; + private static final boolean canRun = Skript.methodExists(Block.class, "applyBoneMeal", BlockFace.class); + + static { + setShutdownDelay(1); + } + + @Before + public void setBlocks() { + plant = setBlock(Material.WHEAT); + plant.getRelative(0,-1,0).setType(Material.FARMLAND); + birch = plant.getRelative(10,0,0); + birch.getRelative(0,-1,0).setType(Material.DIRT); + birch.setType(Material.BIRCH_SAPLING); + } + + @Test + public void testGrow() { + if (canRun) { + for (int i = 0; i < 10; i++) { + plant.applyBoneMeal(BlockFace.UP); + birch.applyBoneMeal(BlockFace.UP); + } + } + } + + @After + public void resetBlocks() { + plant.setType(Material.AIR); + plant.getRelative(0,-1,0).setType(Material.AIR); + birch.setType(Material.AIR); + birch.getRelative(0,-1,0).setType(Material.AIR); + for (int x = -4; x <5; x++) + for (int y = 0; y < 15; y++) + for (int z = -4; z < 5; z++) + birch.getRelative(x,y,z).setType(Material.AIR); + } + +} diff --git a/src/test/skript/tests/junit/EvtGrow.sk b/src/test/skript/tests/junit/EvtGrow.sk new file mode 100644 index 00000000000..e932519af9b --- /dev/null +++ b/src/test/skript/tests/junit/EvtGrow.sk @@ -0,0 +1,70 @@ +on script load: + # prior to 1.18, applyBoneMeal either did not exist or did not fire events + # so we need to complete the objectives manually to avoid the tests failing + if running below minecraft "1.18": + complete objective "grow of wheat" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + complete objective "grow from wheat" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + complete objective "grow to wheat" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + complete objective "grow of wheat (blockdata)" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + complete objective "grow from wheat (blockdata)" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + complete objective "grow to wheat (blockdata)" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + complete objective "grow of birch tree" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + complete objective "grow of birch sapling" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + complete objective "grow from birch sapling" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + complete objective "grow to birch tree" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + + # itemtype + ensure junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" completes "grow of wheat" + ensure junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" completes "grow from wheat" + ensure junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" completes "grow to wheat" + + # blockdata + ensure junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" completes "grow of wheat (blockdata)" + ensure junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" completes "grow from wheat (blockdata)" + ensure junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" completes "grow to wheat (blockdata)" + + # structures + ensure junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" completes "grow of birch tree" + ensure junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" completes "grow of birch sapling" + ensure junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" completes "grow from birch sapling" + ensure junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" completes "grow to birch tree" + +on grow of wheat: + junit test is "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + complete objective "grow of wheat" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + +on grow of wheat[age=0]: + junit test is "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + complete objective "grow of wheat (blockdata)" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + +on grow from wheat: + junit test is "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + complete objective "grow from wheat" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + +on grow from wheat[age=0]: + junit test is "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + complete objective "grow from wheat (blockdata)" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + +on grow to wheat: + junit test is "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + complete objective "grow to wheat" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + +on grow to wheat[age=7]: + junit test is "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + complete objective "grow to wheat (blockdata)" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + +on grow of birch tree: + junit test is "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + complete objective "grow of birch tree" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + +on grow of birch sapling: + junit test is "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + complete objective "grow of birch sapling" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + +on grow from birch sapling: + junit test is "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + complete objective "grow from birch sapling" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + +on grow to birch tree: + junit test is "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + complete objective "grow to birch tree" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" From 7eaacc8795a3d4df2bbb3aedff9662956c427b29 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Wed, 1 Nov 2023 06:34:07 -0700 Subject: [PATCH 518/619] Fix issues with ExprDrops (#6130) * refactor ExprDrops, fix bugs, add test fixed setting drops to multiple items/experience values at once fixed null values being left in drops after removing items maintained behavior but behavior needs a big update * small cleanup * Fix JUnit test location * import shenanigans --------- Co-authored-by: Moderocky <admin@moderocky.com> --- .../java/ch/njol/skript/aliases/ItemType.java | 324 +++++++++--------- .../ch/njol/skript/expressions/ExprDrops.java | 146 +++++--- .../test/tests/syntaxes/ExprDropsTest.java | 44 +++ src/test/skript/junit/ExprDrops.sk | 103 ++++++ 4 files changed, 402 insertions(+), 215 deletions(-) create mode 100644 src/test/java/org/skriptlang/skript/test/tests/syntaxes/ExprDropsTest.java create mode 100644 src/test/skript/junit/ExprDrops.sk diff --git a/src/main/java/ch/njol/skript/aliases/ItemType.java b/src/main/java/ch/njol/skript/aliases/ItemType.java index 7c189f72bf6..bc5b8aa6ca1 100644 --- a/src/main/java/ch/njol/skript/aliases/ItemType.java +++ b/src/main/java/ch/njol/skript/aliases/ItemType.java @@ -18,37 +18,6 @@ */ package ch.njol.skript.aliases; -import java.io.NotSerializableException; -import java.io.StreamCorruptedException; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.NoSuchElementException; -import java.util.Random; -import java.util.RandomAccess; -import java.util.Set; - -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.OfflinePlayer; -import org.bukkit.block.Block; -import org.bukkit.block.BlockState; -import org.bukkit.block.Skull; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.entity.Player; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.PlayerInventory; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.inventory.meta.SkullMeta; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.aliases.ItemData.OldItemData; import ch.njol.skript.bukkitutil.BukkitUnsafe; import ch.njol.skript.bukkitutil.ItemUtils; @@ -68,14 +37,44 @@ import ch.njol.yggdrasil.Fields; import ch.njol.yggdrasil.Fields.FieldContext; import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilExtendedSerializable; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.OfflinePlayer; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.block.Skull; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.SkullMeta; +import org.eclipse.jdt.annotation.Nullable; + +import java.io.NotSerializableException; +import java.io.StreamCorruptedException; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NoSuchElementException; +import java.util.Random; +import java.util.RandomAccess; +import java.util.Set; @ContainerType(ItemStack.class) public class ItemType implements Unit, Iterable<ItemData>, Container<ItemStack>, YggdrasilExtendedSerializable { - + static { // This handles updating ItemType and ItemData variable records Variables.yggdrasil.registerFieldHandler(new FieldHandler() { - + @Override public boolean missingField(Object o, Field field) throws StreamCorruptedException { if (!(o instanceof ItemType || o instanceof ItemData)) @@ -84,12 +83,12 @@ public boolean missingField(Object o, Field field) throws StreamCorruptedExcepti return true; // Just null, no need for updating that data return false; } - + @Override public boolean incompatibleField(Object o, Field f, FieldContext field) throws StreamCorruptedException { return false; } - + @Override public boolean excessiveField(Object o, FieldContext field) throws StreamCorruptedException { if (!(o instanceof ItemType || o instanceof ItemData)) @@ -101,7 +100,7 @@ public boolean excessiveField(Object o, FieldContext field) throws StreamCorrupt } }); } - + /** * DO NOT ADD ItemDatas to this list directly! * <p> @@ -109,31 +108,31 @@ public boolean excessiveField(Object o, FieldContext field) throws StreamCorrupt * can have its own ItemMeta. */ final ArrayList<ItemData> types = new ArrayList<>(2); - + /** * Whether this ItemType represents all types or not. */ private boolean all = false; - + /** * Amount determines how many items this type represents. Negative amounts * are treated as their absolute values when adding items to inventories * and otherwise used as "doesn't matter" flags. */ private int amount = -1; - + /** * ItemTypes to use instead of this one if adding to an inventory or setting a block. */ @Nullable private ItemType item = null, block = null; - + /** * Meta that applies for all ItemDatas there. */ @Nullable private ItemMeta globalMeta; - + void setItem(final @Nullable ItemType item) { if (equals(item)) { // can happen if someone defines a 'x' and 'x item/block' alias that have the same value, e.g. 'dirt' and 'dirt block' this.item = null; @@ -149,7 +148,7 @@ void setItem(final @Nullable ItemType item) { this.item = item; } } - + void setBlock(final @Nullable ItemType block) { if (equals(block)) { this.block = null; @@ -167,30 +166,30 @@ void setBlock(final @Nullable ItemType block) { } public ItemType() {} - + public ItemType(Material id) { add_(new ItemData(id)); } - + public ItemType(Material id, String tags) { add_(new ItemData(id, tags)); } - + public ItemType(ItemData d) { add_(d.clone()); } - + public ItemType(ItemStack i) { amount = i.getAmount(); add_(new ItemData(i)); } - + public ItemType(BlockState b) { // amount = 1; add_(new ItemData(b)); // TODO metadata - spawners, skulls, etc. } - + /** * Copy constructor. * @param i Another ItemType. @@ -198,7 +197,7 @@ public ItemType(BlockState b) { private ItemType(ItemType i) { setTo(i); } - + public void setTo(ItemType i) { all = i.all; amount = i.amount; @@ -221,7 +220,7 @@ public ItemType(Block block) { public void modified() { item = block = null; } - + /** * Returns amount of the item in stack that this type represents. * @return amount. @@ -230,22 +229,22 @@ public void modified() { public int getAmount() { return Math.abs(amount); } - + /** * Only use this method if you know what you're doing. - * + * * @return The internal amount, i.e. same as {@link #getAmount()} * or additive inverse number of it. */ public int getInternalAmount() { return amount; } - + @Override public void setAmount(final double amount) { setAmount((int) amount); } - + public void setAmount(final int amount) { this.amount = amount; if (item != null) @@ -253,7 +252,7 @@ public void setAmount(final int amount) { if (block != null) block.amount = amount; } - + /** * Checks if this item type represents one of its items (OR) or all of * them (AND). If this has only one item, it doesn't matter. @@ -262,30 +261,30 @@ public void setAmount(final int amount) { public boolean isAll() { return all; } - + public void setAll(final boolean all) { this.all = all; } - + public boolean isOfType(@Nullable ItemStack item) { if (item == null) return isOfType(Material.AIR, null); return isOfType(new ItemData(item)); } - + public boolean isOfType(@Nullable BlockState block) { if (block == null) return isOfType(Material.AIR, null); - + return isOfType(new ItemData(block)); } - + public boolean isOfType(@Nullable Block block) { if (block == null) return isOfType(Material.AIR, null); return isOfType(block.getState()); } - + public boolean isOfType(ItemData type) { for (final ItemData myType : types) { if (myType.equals(type)) { @@ -294,16 +293,16 @@ public boolean isOfType(ItemData type) { } return false; } - + public boolean isOfType(Material id, @Nullable String tags) { return isOfType(new ItemData(id, tags)); } - + public boolean isOfType(Material id) { // TODO avoid object creation return isOfType(new ItemData(id, null)); } - + /** * Checks if this type represents all the items represented by given * item type. This type may of course also represent other items. @@ -313,17 +312,17 @@ public boolean isOfType(Material id) { public boolean isSupertypeOf(ItemType other) { return types.containsAll(other.types); } - + public ItemType getItem() { final ItemType item = this.item; return item == null ? this : item; } - + public ItemType getBlock() { final ItemType block = this.block; return block == null ? this : block; } - + /** * @return Whether this ItemType has at least one ItemData that represents an item */ @@ -334,7 +333,7 @@ public boolean hasItem() { } return false; } - + /** * @return Whether this ItemType has at least one ItemData that represents a block */ @@ -345,10 +344,10 @@ public boolean hasBlock() { } return false; } - + /** * Sets the given block to this ItemType - * + * * @param block The block to set * @param applyPhysics Whether to run a physics check just after setting the block * @return Whether the block was successfully set @@ -374,7 +373,7 @@ public boolean setBlock(Block block, boolean applyPhysics) { } return false; } - + /** * Send a block change to a player * <p>This will send a fake block change to the player, and will not change the block on the server.</p> @@ -391,11 +390,11 @@ public void sendBlockChange(Player player, Location location) { BlockUtils.sendBlockChange(player, location, blockType, d.getBlockValues()); } } - + /** * Intersects all ItemDatas with all ItemDatas of the given ItemType, returning an ItemType with at most n*m ItemDatas, where n = #ItemDatas of this ItemType, and m = * #ItemDatas of the argument. - * + * * @see ItemData#intersection(ItemData) * @param other * @return A new item type which is the intersection of the two item types or null if the intersection is empty. @@ -413,7 +412,7 @@ public ItemType intersection(ItemType other) { return null; return r; } - + /** * @param type Some ItemData. Only a copy of it will be stored. */ @@ -422,7 +421,7 @@ public void add(@Nullable ItemData type) { add_(type.clone()); } } - + /** * @param type A cloned or newly created ItemData */ @@ -433,36 +432,36 @@ private void add_(@Nullable ItemData type) { modified(); } } - + public void addAll(Collection<ItemData> types) { this.types.addAll(types); modified(); } - + public void remove(ItemData type) { if (types.remove(type)) { //numItems -= type.numItems(); modified(); } } - + void remove(int index) { types.remove(index); //numItems -= type.numItems(); modified(); } - + @Override public Iterator<ItemStack> containerIterator() { return new Iterator<ItemStack>() { @SuppressWarnings("null") Iterator<ItemData> iter = types.iterator(); - + @Override public boolean hasNext() { return iter.hasNext(); } - + @Override public ItemStack next() { if (!hasNext()) @@ -471,17 +470,17 @@ public ItemStack next() { is.setAmount(getAmount()); return is; } - + @Override public void remove() { throw new UnsupportedOperationException(); } }; } - + /** * Gets all ItemStacks this ItemType represents. Only use this if you know what you're doing, as it returns only one element if this is not an 'every' alias. - * + * * @return An Iterable whose iterator will always return the same item(s) */ public Iterable<ItemStack> getAll() { @@ -498,7 +497,7 @@ public Iterator<ItemStack> iterator() { } }; } - + @Nullable public ItemStack removeAll(@Nullable ItemStack item) { boolean wasAll = all; @@ -512,10 +511,10 @@ public ItemStack removeAll(@Nullable ItemStack item) { amount = oldAmount; } } - + /** * Removes this type from the item stack if appropriate - * + * * @param item * @return The passed ItemStack or null if the resulting amount is <= 0 */ @@ -533,10 +532,10 @@ public ItemStack removeFrom(@Nullable ItemStack item) { item.setAmount(a); return item; } - + /** * Adds this ItemType to the given item stack - * + * * @param item * @return The passed ItemStack or a new one if the passed is null or air */ @@ -548,14 +547,14 @@ public ItemStack addTo(final @Nullable ItemStack item) { item.setAmount(Math.min(item.getAmount() + getAmount(), item.getMaxStackSize())); return item; } - + @Override public ItemType clone() { return new ItemType(this); } - + private final static Random random = new Random(); - + /** * @return One random ItemStack that this ItemType represents. If you have a List or an Inventory, use {@link #addTo(Inventory)} or {@link #addTo(List)} respectively. * @see #addTo(Inventory) @@ -573,13 +572,13 @@ public ItemStack getRandom() { is.setAmount(getAmount()); return is; } - + /** * Test whether this ItemType can be put into the given inventory completely. * <p> * REMIND If this ItemType represents multiple items with OR, this function will immediately return false.<br/> * CondCanHold currently blocks aliases without 'every'/'all' as temporary solution. - * + * * @param invi * @return Whether this item type can be added to the given inventory */ @@ -590,7 +589,7 @@ public boolean hasSpace(final Inventory invi) { } return addTo(getStorageContents(invi)); } - + public static ItemStack[] getCopiedContents(Inventory invi) { final ItemStack[] buf = invi.getContents(); for (int i = 0; i < buf.length; i++) @@ -598,7 +597,7 @@ public static ItemStack[] getCopiedContents(Inventory invi) { buf[i] = buf[i].clone(); return buf; } - + /** * Gets copy of storage contents, i.e. ignores armor and off hand. This is due to Spigot 1.9 * added armor slots, and off hand to default inventory index. @@ -615,7 +614,7 @@ public static ItemStack[] getStorageContents(final Inventory invi) { return tBuf; } else return getCopiedContents(invi); } - + /** * @return List of ItemDatas. The returned list is not modifiable, use {@link #add(ItemData)} and {@link #remove(ItemData)} if you need to change the list, or use the * {@link #iterator()}. @@ -624,28 +623,28 @@ public static ItemStack[] getStorageContents(final Inventory invi) { public List<ItemData> getTypes() { return Collections.unmodifiableList(types); } - + public int numTypes() { return types.size(); } - + /** * @return How many different items this item type represents */ public int numItems() { return types.size(); } - + @Override public Iterator<ItemData> iterator() { return new Iterator<ItemData>() { private int next = 0; - + @Override public boolean hasNext() { return next < types.size(); } - + @SuppressWarnings("null") @Override public ItemData next() { @@ -653,7 +652,7 @@ public ItemData next() { throw new NoSuchElementException(); return types.get(next++); } - + @Override public void remove() { if (next <= 0) @@ -662,7 +661,7 @@ public void remove() { } }; } - + public boolean isContainedIn(Iterable<ItemStack> items) { int needed = getAmount(); int found = 0; @@ -698,7 +697,7 @@ public boolean isContainedIn(ItemStack[] items) { return false; return all; } - + public boolean removeAll(Inventory invi) { final boolean wasAll = all; final int oldAmount = amount; @@ -711,22 +710,22 @@ public boolean removeAll(Inventory invi) { amount = oldAmount; } } - + /** * Removes this type from the given inventory. Does not call updateInventory for players. - * + * * @param invi * @return Whether everything could be removed from the inventory */ public boolean removeFrom(Inventory invi) { ItemStack[] buf = getCopiedContents(invi); - + final boolean ok = removeFrom(Arrays.asList(buf)); - + invi.setContents(buf); return ok; } - + @SafeVarargs public final boolean removeAll(List<ItemStack>... lists) { final boolean wasAll = all; @@ -740,16 +739,19 @@ public final boolean removeAll(List<ItemStack>... lists) { amount = oldAmount; } } - + /** - * @param lists The lists to remove this type from. Each list should implement {@link RandomAccess}. + * Removes this ItemType from given lists of ItemStacks. + * If an ItemStack is completely removed, that index in the list is set to null, instead of being removed. + * + * @param lists The lists to remove this type from. Each list should implement {@link RandomAccess}. Lists may contain null values after this method. * @return Whether this whole item type could be removed (i.e. returns false if the lists didn't contain this item type completely) */ @SafeVarargs public final boolean removeFrom(final List<ItemStack>... lists) { int removed = 0; boolean ok = true; - + for (final ItemData d : types) { if (all) removed = 0; @@ -762,7 +764,7 @@ public final boolean removeFrom(final List<ItemStack>... lists) { /* * Do NOT use equals()! It doesn't exactly match items * for historical reasons. This will change in future. - * + * * In Skript 2.3, equals() was used for getting closest * possible aliases for items. It was horribly hacky, and * is not done anymore. Still, some uses of equals() expect @@ -799,15 +801,15 @@ public final boolean removeFrom(final List<ItemStack>... lists) { if (all) ok &= removed == getAmount(); } - + if (!all) return false; return ok; } - + /** * Adds this ItemType to the given list, without filling existing stacks. - * + * * @param list */ public void addTo(final List<ItemStack> list) { @@ -818,17 +820,17 @@ public void addTo(final List<ItemStack> list) { for (final ItemStack is : getItem().getAll()) list.add(is); } - + /** * Tries to add this ItemType to the given inventory. Does not call updateInventory for players. - * + * * @param invi * @return Whether everything could be added to the inventory */ public boolean addTo(final Inventory invi) { // important: don't use inventory.add() - it ignores max stack sizes ItemStack[] buf = invi.getContents(); - + ItemStack[] tBuf = buf.clone(); if (invi instanceof PlayerInventory) { buf = new ItemStack[36]; @@ -836,21 +838,21 @@ public boolean addTo(final Inventory invi) { buf[i] = tBuf[i]; } } - + final boolean b = addTo(buf); - + if (invi instanceof PlayerInventory) { buf = Arrays.copyOf(buf, tBuf.length); for (int i = tBuf.length - 5; i < tBuf.length; ++i) { buf[i] = tBuf[i]; } } - + assert buf != null; invi.setContents(buf); return b; } - + private static boolean addTo(@Nullable ItemStack is, ItemStack[] buf) { if (is == null || is.getType() == Material.AIR) return true; @@ -876,7 +878,7 @@ private static boolean addTo(@Nullable ItemStack is, ItemStack[] buf) { } return false; } - + public boolean addTo(final ItemStack[] buf) { if (!isAll()) { return addTo(getItem().getRandom(), buf); @@ -887,12 +889,12 @@ public boolean addTo(final ItemStack[] buf) { } return ok; } - + /** * Tests whether a given set of ItemTypes is a subset of another set of ItemTypes. * <p> * This method works differently that normal set operations, as is e.g. returns true if set == {everything}. - * + * * @param set * @param sub * @return Whether all item types in <tt>sub</tt> have at least one {@link #isSupertypeOf(ItemType) super type} in <tt>set</tt> @@ -908,7 +910,7 @@ public static boolean isSubset(final ItemType[] set, final ItemType[] sub) { } return true; } - + @Override public boolean equals(final @Nullable Object obj) { if (this == obj) @@ -965,7 +967,7 @@ public boolean isSimilar(ItemType other) { } return false; } - + @Override public int hashCode() { final int prime = 31; @@ -975,21 +977,21 @@ public int hashCode() { result = prime * result + types.hashCode(); return result; } - + @Override public String toString() { return toString(false, 0, null); } - + @Override public String toString(final int flags) { return toString(false, flags, null); } - + public String toString(final int flags, final @Nullable Adjective a) { return toString(false, flags, a); } - + private String toString(final boolean debug, final int flags, final @Nullable Adjective a) { final StringBuilder b = new StringBuilder(); // if (types.size() == 1 && !types.get(0).hasDataRange()) { @@ -1050,33 +1052,33 @@ private String toString(final boolean debug, final int flags, final @Nullable Ad // } return "" + b.toString(); } - + public static String toString(final ItemStack i) { return new ItemType(i).toString(); } - + public static String toString(final ItemStack i, final int flags) { return new ItemType(i).toString(flags); } - + public static String toString(Block b, int flags) { return new ItemType(b).toString(flags); } - + public String getDebugMessage() { return toString(true, 0, null); } - + @Override public Fields serialize() throws NotSerializableException { final Fields f = new Fields(this); return f; } - + @Override public void deserialize(final Fields fields) throws StreamCorruptedException, NotSerializableException { fields.setFields(this); - + // Legacy data (before aliases rework) update if (!types.isEmpty()) { @SuppressWarnings("rawtypes") @@ -1095,7 +1097,7 @@ public void deserialize(final Fields fields) throws StreamCorruptedException, No } } } - + /** * Gets raw item names ("minecraft:some_item"). If they are not available, * empty list will be returned. @@ -1109,10 +1111,10 @@ public List<String> getRawNames() { if (id != null) rawNames.add(id); } - + return rawNames; } - + /** * Gets all enchantments of this item. * @return Enchantments. @@ -1129,7 +1131,7 @@ public Map<Enchantment,Integer> getEnchantments() { return null; return enchants; } - + /** * Adds enchantments to this item type. * @param enchantments Enchantments. @@ -1144,7 +1146,7 @@ public void addEnchantments(Map<Enchantment,Integer> enchantments) { globalMeta.addEnchant(entry.getKey(), entry.getValue(), true); } } - + /** * Gets all enchantments of this item. * @return the enchantments of this item type. @@ -1152,7 +1154,7 @@ public void addEnchantments(Map<Enchantment,Integer> enchantments) { @Nullable public EnchantmentType[] getEnchantmentTypes() { Set<Entry<Enchantment, Integer>> enchants = getItemMeta().getEnchants().entrySet(); - + return enchants.stream() .map(enchant -> new EnchantmentType(enchant.getKey(), enchant.getValue())) .toArray(EnchantmentType[]::new); @@ -1174,14 +1176,14 @@ public EnchantmentType getEnchantmentType(Enchantment enchantment) { .findFirst() .orElse(null); } - + /** * Checks whether this item type has enchantments. */ public boolean hasEnchantments() { return getItemMeta().hasEnchants(); } - + /** * Checks whether this item type has the given enchantments. * @param enchantments the enchantments to be checked. @@ -1190,14 +1192,14 @@ public boolean hasEnchantments(Enchantment... enchantments) { if (!hasEnchantments()) return false; ItemMeta meta = getItemMeta(); - + for (Enchantment enchantment : enchantments) { if (!meta.hasEnchant(enchantment)) return false; } return true; } - + /** * Checks whether this item type contains at most one of the given enchantments. * @param enchantments The enchantments to be checked. @@ -1206,7 +1208,7 @@ public boolean hasAnyEnchantments(Enchantment... enchantments) { if (!hasEnchantments()) return false; ItemMeta meta = getItemMeta(); - + for (Enchantment enchantment : enchantments) { assert enchantment != null; if (meta.hasEnchant(enchantment)) @@ -1214,7 +1216,7 @@ public boolean hasAnyEnchantments(Enchantment... enchantments) { } return false; } - + /** * Checks whether this item type contains the given enchantments. * Also checks the enchantment level. @@ -1234,14 +1236,14 @@ public boolean hasEnchantments(EnchantmentType... enchantments) { } return true; } - + /** * Adds the given enchantments to the item type. * @param enchantments The enchantments to be added. */ public void addEnchantments(EnchantmentType... enchantments) { ItemMeta meta = getItemMeta(); - + for (EnchantmentType enchantment : enchantments) { Enchantment type = enchantment.getType(); assert type != null; // Bukkit working different than we expect @@ -1249,14 +1251,14 @@ public void addEnchantments(EnchantmentType... enchantments) { } setItemMeta(meta); } - + /** * Removes the given enchantments from this item type. * @param enchantments The enchantments to be removed. */ public void removeEnchantments(EnchantmentType... enchantments) { ItemMeta meta = getItemMeta(); - + for (EnchantmentType enchantment : enchantments) { Enchantment type = enchantment.getType(); assert type != null; // Bukkit working different than we expect @@ -1264,14 +1266,14 @@ public void removeEnchantments(EnchantmentType... enchantments) { } setItemMeta(meta); } - + /** * Clears all enchantments from this item type except the ones that are * defined for individual item datas only. */ public void clearEnchantments() { ItemMeta meta = getItemMeta(); - + Set<Enchantment> enchants = meta.getEnchants().keySet(); for (Enchantment ench : enchants) { assert ench != null; @@ -1279,7 +1281,7 @@ public void clearEnchantments() { } setItemMeta(meta); } - + /** * Gets item meta that applies to all items represented by this type. * @return Item meta. @@ -1295,13 +1297,13 @@ public ItemMeta getItemMeta() { */ public void setItemMeta(ItemMeta meta) { globalMeta = meta; - + // Apply new meta to all datas for (ItemData data : types) { data.setItemMeta(meta); } } - + /** * Clears item meta from this type. Metas which individual item dates may * have will not be touched. diff --git a/src/main/java/ch/njol/skript/expressions/ExprDrops.java b/src/main/java/ch/njol/skript/expressions/ExprDrops.java index 89ae11cb8a5..2d57b4e9277 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprDrops.java +++ b/src/main/java/ch/njol/skript/expressions/ExprDrops.java @@ -18,13 +18,9 @@ */ package ch.njol.skript.expressions; +import java.util.ArrayList; import java.util.List; - -import org.bukkit.event.Event; -import org.bukkit.event.entity.EntityDeathEvent; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.eclipse.jdt.annotation.Nullable; +import java.util.Objects; import ch.njol.skript.Skript; import ch.njol.skript.aliases.ItemType; @@ -42,7 +38,11 @@ import ch.njol.skript.util.Experience; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -import ch.njol.util.coll.iterator.IteratorIterable; +import org.bukkit.event.Event; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.eclipse.jdt.annotation.Nullable; /** * @author Peter Güttinger @@ -85,7 +85,7 @@ protected ItemType[] get(Event e) { @Nullable public Class<?>[] acceptChange(ChangeMode mode) { if (getParser().getHasDelayBefore().isTrue()) { - Skript.error("Can't change the drops anymore after the event has already passed"); + Skript.error("Can't change the drops after the event has already passed"); return null; } switch (mode) { @@ -94,7 +94,7 @@ public Class<?>[] acceptChange(ChangeMode mode) { case REMOVE_ALL: case SET: return CollectionUtils.array(ItemType[].class, Inventory[].class, Experience[].class); - case DELETE: + case DELETE: // handled by EffClearDrops case RESET: default: return null; @@ -102,59 +102,97 @@ public Class<?>[] acceptChange(ChangeMode mode) { } @Override - public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { - if (!(e instanceof EntityDeathEvent)) + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + if (!(event instanceof EntityDeathEvent)) return; - List<ItemStack> drops = ((EntityDeathEvent) e).getDrops(); + List<ItemStack> drops = ((EntityDeathEvent) event).getDrops(); + int originalExperience = ((EntityDeathEvent) event).getDroppedExp(); assert delta != null; + + // separate the delta into experience and drops to make it easier to handle + int deltaExperience = -1; // Skript does not support negative experience, so -1 is a safe "unset" value + boolean removeAllExperience = false; + List<ItemType> deltaDrops = new ArrayList<>(); for (Object o : delta) { if (o instanceof Experience) { - if (mode == ChangeMode.REMOVE_ALL || mode == ChangeMode.REMOVE && ((Experience) o).getInternalXP() == -1) { - ((EntityDeathEvent) e).setDroppedExp(0); - } else if (mode == ChangeMode.SET) { - ((EntityDeathEvent) e).setDroppedExp(((Experience) o).getXP()); + // Special case for `remove xp from the drops` + if ((((Experience) o).getInternalXP() == -1 && mode == ChangeMode.REMOVE) || mode == ChangeMode.REMOVE_ALL) { + removeAllExperience = true; + } + // add the value even if we're removing all experience, just so we know that experience was changed + if (deltaExperience == -1) { + deltaExperience = ((Experience) o).getXP(); } else { - ((EntityDeathEvent) e).setDroppedExp(Math.max(0, ((EntityDeathEvent) e).getDroppedExp() + (mode == ChangeMode.ADD ? 1 : -1) * ((Experience) o).getXP())); + deltaExperience += ((Experience) o).getXP(); } - } else { - switch (mode) { - case SET: - drops.clear(); - //$FALL-THROUGH$ - case ADD: - if (o instanceof Inventory) { - for (ItemStack is : new IteratorIterable<>(((Inventory) o).iterator())) { - if (is != null) - drops.add(is); - } - } else { - ((ItemType) o).addTo(drops); - } - break; - case REMOVE: - case REMOVE_ALL: - if (o instanceof Inventory) { - for (ItemStack is : new IteratorIterable<>(((Inventory) o).iterator())) { - if (is == null) - continue; - if (mode == ChangeMode.REMOVE) - new ItemType(is).removeFrom(drops); - else - new ItemType(is).removeAll(drops); - } - } else { - if (mode == ChangeMode.REMOVE) - ((ItemType) o).removeFrom(drops); - else - ((ItemType) o).removeAll(drops); - } - break; - case DELETE: - case RESET: - assert false; + } else if (o instanceof Inventory) { + // inventories are unrolled into their contents + for (ItemStack item : ((Inventory) o).getContents()) { + if (item != null) + deltaDrops.add(new ItemType(item)); } + } else if (o instanceof ItemType) { + deltaDrops.add((ItemType) o); + } else { + assert false; + } + } + + // handle items and experience separately to maintain current behavior + // `set drops to iron sword` should not affect experience + // and `set drops to 1 xp` should not affect items + // todo: All the experience stuff should be removed from this class for 2.8 and given to ExprExperience + + // handle experience + if (deltaExperience > -1) { + switch (mode) { + case SET: + ((EntityDeathEvent) event).setDroppedExp(deltaExperience); + break; + case ADD: + ((EntityDeathEvent) event).setDroppedExp(originalExperience + deltaExperience); + break; + case REMOVE: + ((EntityDeathEvent) event).setDroppedExp(originalExperience - deltaExperience); + // fallthrough to check for removeAllExperience + case REMOVE_ALL: + if (removeAllExperience) + ((EntityDeathEvent) event).setDroppedExp(0); + break; + case DELETE: + case RESET: + assert false; + } + } + + // handle items + if (!deltaDrops.isEmpty()) { + switch (mode) { + case SET: + // clear drops and fallthrough to add + drops.clear(); + case ADD: + for (ItemType item : deltaDrops) { + item.addTo(drops); + } + break; + case REMOVE: + for (ItemType item : deltaDrops) { + item.removeFrom(drops); + } + break; + case REMOVE_ALL: + for (ItemType item : deltaDrops) { + item.removeAll(drops); + } + break; + case DELETE: + case RESET: + assert false; } + // remove stray nulls caused by ItemType#removeFrom() and ItemType#removeAll() + drops.removeIf(Objects::isNull); } } @@ -169,7 +207,7 @@ public Class<? extends ItemType> getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { return "the drops"; } diff --git a/src/test/java/org/skriptlang/skript/test/tests/syntaxes/ExprDropsTest.java b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/ExprDropsTest.java new file mode 100644 index 00000000000..f9216d5352c --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/ExprDropsTest.java @@ -0,0 +1,44 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.test.tests.syntaxes; + +import ch.njol.skript.test.runner.SkriptJUnitTest; +import org.bukkit.entity.Pig; +import org.junit.Before; +import org.junit.Test; + +public class ExprDropsTest extends SkriptJUnitTest { + + private Pig pig; + + static { + setShutdownDelay(1); + } + + @Before + public void spawnPig() { + pig = spawnTestPig(); + } + + @Test + public void killPig() { + pig.damage(100); + } + +} diff --git a/src/test/skript/junit/ExprDrops.sk b/src/test/skript/junit/ExprDrops.sk new file mode 100644 index 00000000000..3df908f9f03 --- /dev/null +++ b/src/test/skript/junit/ExprDrops.sk @@ -0,0 +1,103 @@ +test "ExprDropsJUnit" when running JUnit: + set {_tests::1} to "clear drops" + set {_tests::2} to "set drops to two items" + set {_tests::3} to "add item to drops" + set {_tests::4} to "remove item from drops" + set {_tests::5} to "add multiple items to drops" + set {_tests::6} to "remove all of an item from drops" + set {_tests::7} to "remove multiple items from drops" + set {_tests::8} to "set drops to experience doesn't modify items" + set {_tests::9} to "add experience to drops doesn't modify items" + set {_tests::10} to "remove experience (special case) from drops doesn't modify items" + set {_tests::11} to "add and remove experience from drops doesn't modify items" + set {_tests::12} to "remove all experience from drops doesn't modify items" + set {_tests::13} to "drops test complete" + ensure junit test "org.skriptlang.skript.test.tests.syntaxes.ExprDropsTest" completes {_tests::*} + +# NOTE: Do NOT take the behavior described in this test as a guide for how ExprDrops SHOULD work, only for how it DOES work in 2.7.x. +# The behavior should change in 2.8 and this test will be updated accordingly. + +on death of pig: + set {_test} to "org.skriptlang.skript.test.tests.syntaxes.ExprDropsTest" + junit test is {_test} + + # Items + + clear drops + if size of drops is 0: + complete objective "clear drops" for {_test} + + set drops to 1 stick and 1 carrot + # this is stupid but comparing lists directly doesn't work + set {_i::*} to drops + if: + {_i::1} is 1 stick + {_i::2} is 1 carrot + then: + complete objective "set drops to two items" for {_test} + + add 1 dirt to drops + set {_i::*} to drops + if: + {_i::1} is 1 stick + {_i::2} is 1 carrot + {_i::3} is 1 dirt + then: + complete objective "add item to drops" for {_test} + + remove dirt from drops + set {_i::*} to drops + if: + {_i::1} is 1 stick + {_i::2} is 1 carrot + then: + complete objective "remove item from drops" for {_test} + + add 1 dirt and 2 dirt to drops + set {_i::*} to drops + if: + {_i::1} is 1 stick + {_i::2} is 1 carrot + {_i::3} is 1 dirt + {_i::4} is 2 dirt + then: + complete objective "add multiple items to drops" for {_test} + + remove all dirt from drops + set {_i::*} to drops + if: + {_i::1} is 1 stick + {_i::2} is 1 carrot + then: + complete objective "remove all of an item from drops" for {_test} + + remove 1 stick and 1 carrot from drops + if size of drops is 0: + complete objective "remove multiple items from drops" for {_test} + + # Experience + # to-do: add checks against experience changes once `on death` has access to dropped experience amount + + set drops to 1 porkchop + set drops to 10 experience + if drops is 1 porkchop: + complete objective "set drops to experience doesn't modify items" for {_test} + + add 10 experience to drops + if drops is 1 porkchop: + complete objective "add experience to drops doesn't modify items" for {_test} + + remove xp from drops + if drops is 1 porkchop: + complete objective "remove experience (special case) from drops doesn't modify items" for {_test} + + add 100 experience to drops + remove 50 experience from drops + if drops is 1 porkchop: + complete objective "add and remove experience from drops doesn't modify items" for {_test} + + remove all 10 experience from drops + if drops is 1 porkchop: + complete objective "remove all experience from drops doesn't modify items" for {_test} + + complete objective "drops test complete" for {_test} From 0e6be2d3077ca3ee58b0eeef1aaf502b7d5e8a84 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Wed, 1 Nov 2023 14:11:05 -0400 Subject: [PATCH 519/619] Prepare For Release (2.7.2) (#6166) --- gradle.properties | 2 +- skript-aliases | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 3c6d77ca224..ff0925c17ea 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ org.gradle.jvmargs=-Dfile.encoding=UTF-8 groupid=ch.njol name=skript -version=2.7.1 +version=2.7.2 jarName=Skript.jar testEnv=java17/paper-1.20.2 testEnvJavaVersion=17 diff --git a/skript-aliases b/skript-aliases index 1ee77d8573a..0884ede0fdf 160000 --- a/skript-aliases +++ b/skript-aliases @@ -1 +1 @@ -Subproject commit 1ee77d8573aa37456f1b49fe12aec7bb410d1dd7 +Subproject commit 0884ede0fdf69e914b944500d9f24f1c000a90a2 From 021788e9f996bd65e3e5cba669cd56eb4ef7b7ec Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Thu, 2 Nov 2023 17:37:12 -0600 Subject: [PATCH 520/619] Update SimpleEvents.java --- src/main/java/ch/njol/skript/events/SimpleEvents.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/events/SimpleEvents.java b/src/main/java/ch/njol/skript/events/SimpleEvents.java index a9b880a9f1e..a294c6f7b98 100644 --- a/src/main/java/ch/njol/skript/events/SimpleEvents.java +++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java @@ -604,7 +604,7 @@ public class SimpleEvents { "\tsend \"Fertilized %size of fertilized blocks% blocks got fertilized.\"") .since("2.5"); Skript.registerEvent("Arm Swing", SimpleEvent.class, PlayerAnimationEvent.class, "[player] arm swing") - .description("Called when a player swings his arm.") + .description("Called when a player swings their arm.") .examples("on arm swing:", "\tsend \"You swung your arm!\"") .since("2.5.1"); From 981f9027dd822f1a64da9fb9e7d64ec6e5d9e1b4 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Thu, 9 Nov 2023 11:41:20 -0800 Subject: [PATCH 521/619] Update EvtGrowTest.java --- .../skript/test/tests/syntaxes/EvtGrowTest.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/skriptlang/skript/test/tests/syntaxes/EvtGrowTest.java b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/EvtGrowTest.java index ea59b85db29..96baf1b5c32 100644 --- a/src/test/java/org/skriptlang/skript/test/tests/syntaxes/EvtGrowTest.java +++ b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/EvtGrowTest.java @@ -23,6 +23,7 @@ import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; +import org.bukkit.block.data.Ageable; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -48,9 +49,18 @@ public void setBlocks() { @Test public void testGrow() { if (canRun) { - for (int i = 0; i < 10; i++) { + int maxIterations = 100; + int iterations = 0; + while (((Ageable) plant.getBlockData()).getAge() != ((Ageable) plant.getBlockData()).getMaximumAge()) { plant.applyBoneMeal(BlockFace.UP); + if (iterations++ > maxIterations) + return; + } + iterations = 0; + while (birch.getType() == Material.BIRCH_SAPLING) { birch.applyBoneMeal(BlockFace.UP); + if (iterations++ > maxIterations) + return; } } } From 90d211aabe7d22b11e16c259ed136447c0d18936 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Fri, 17 Nov 2023 15:01:12 -0800 Subject: [PATCH 522/619] add isNaN() function (#6162) * is nan bread? * remove silly example --- .../ch/njol/skript/classes/data/DefaultFunctions.java | 9 +++++++++ .../ch/njol/skript/expressions/ExprSpecialNumber.java | 2 +- src/test/skript/tests/syntaxes/functions/isNaN.sk | 7 +++++++ 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 src/test/skript/tests/syntaxes/functions/isNaN.sk diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java b/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java index ed95a1a4bc5..e9cb7dc1e97 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java @@ -541,6 +541,15 @@ public OfflinePlayer[] executeSimple(Object[][] params) { }).description("Returns a offline player from their name or UUID. This function will still return the player if they're online.") .examples("set {_p} to offlineplayer(\"Notch\")", "set {_p} to offlineplayer(\"069a79f4-44e9-4726-a5be-fca90e38aaf5\")") .since("INSERT VERSION"); + + Functions.registerFunction(new SimpleJavaFunction<Boolean>("isNaN", numberParam, DefaultClasses.BOOLEAN, true) { + @Override + public Boolean[] executeSimple(Object[][] params) { + return new Boolean[] {Double.isNaN(((Number) params[0][0]).doubleValue())}; + } + }).description("Returns true if the input is NaN (not a number).") + .examples("isNaN(0) # false", "isNaN(0/0) # true", "isNaN(sqrt(-1)) # true") + .since("INSERT VERSION"); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprSpecialNumber.java b/src/main/java/ch/njol/skript/expressions/ExprSpecialNumber.java index 3026f62a59a..1a71225a10a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprSpecialNumber.java +++ b/src/main/java/ch/njol/skript/expressions/ExprSpecialNumber.java @@ -33,7 +33,7 @@ @Name("Special Number") @Description("Special number values, namely NaN, Infinity and -Infinity") -@Examples({"if {_number} is NaN value:"}) +@Examples({"if {_number} is infinity value:"}) @Since("2.2-dev32d") public class ExprSpecialNumber extends SimpleExpression<Number> { private int value; diff --git a/src/test/skript/tests/syntaxes/functions/isNaN.sk b/src/test/skript/tests/syntaxes/functions/isNaN.sk new file mode 100644 index 00000000000..1a30e773942 --- /dev/null +++ b/src/test/skript/tests/syntaxes/functions/isNaN.sk @@ -0,0 +1,7 @@ +test "is NaN function": + assert isNaN(0) is false with "0 should not be NaN" + assert isNaN(NaN value) is true with "NaN should be NaN" + assert isNaN(infinity value) is false with "Infinity should not be NaN" + assert isNaN(0/0) is true with "0/0 should be NaN" + assert isNaN(sqrt(2)) is false with "sqrt(2) should not be NaN" + assert isNaN(sqrt(-2)) is true with "sqrt(-2) should be NaN" From 4cc45e784508f1d038fab61c9bc34ae23a611248 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Fri, 24 Nov 2023 17:11:18 -0800 Subject: [PATCH 523/619] Prevent InventoryHolder -> X chaining (#6171) * Prevent InventoryHolder -> X chaining --- .../java/ch/njol/skript/classes/data/DefaultConverters.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java b/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java index a8b2688e94e..8bd6dc906f8 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java @@ -158,14 +158,14 @@ public DefaultConverters() {} if (holder instanceof DoubleChest) return holder.getInventory().getLocation().getBlock(); return null; - }); + }, Converter.NO_CHAINING); // InventoryHolder - Entity Converters.registerConverter(InventoryHolder.class, Entity.class, holder -> { if (holder instanceof Entity) return (Entity) holder; return null; - }); + }, Converter.NO_CHAINING); // Enchantment - EnchantmentType Converters.registerConverter(Enchantment.class, EnchantmentType.class, e -> new EnchantmentType(e, -1)); From fccb68ea903b5fbfe5e714364a5a9092c8b7e111 Mon Sep 17 00:00:00 2001 From: _tud <98935832+UnderscoreTud@users.noreply.github.com> Date: Thu, 30 Nov 2023 20:16:47 +0300 Subject: [PATCH 524/619] Make event parsing more reliable (#5900) * Make event parsing more reliable * Switch to parse marks * Requested Changes Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> * Switch to parse tags --------- Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Co-authored-by: Moderocky <admin@moderocky.com> Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- src/main/java/ch/njol/skript/Skript.java | 13 +- .../ch/njol/skript/doc/HTMLGenerator.java | 3 +- .../java/ch/njol/skript/lang/SkriptEvent.java | 36 ++---- .../ch/njol/skript/lang/SkriptEventInfo.java | 2 - .../njol/skript/structures/StructEvent.java | 117 ++++++++++++++++++ .../skript/lang/structure/Structure.java | 9 +- 6 files changed, 140 insertions(+), 40 deletions(-) create mode 100644 src/main/java/ch/njol/skript/structures/StructEvent.java diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 9740ee27efa..ae35c08fbf7 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -1436,6 +1436,7 @@ public boolean check(final @Nullable ExpressionInfo<?, ?> i) { // ================ EVENTS ================ + private static final List<SkriptEventInfo<?>> events = new ArrayList<>(50); private static final List<StructureInfo<? extends Structure>> structures = new ArrayList<>(10); /** @@ -1468,10 +1469,10 @@ public static <E extends SkriptEvent> SkriptEventInfo<E> registerEvent(String na String[] transformedPatterns = new String[patterns.length]; for (int i = 0; i < patterns.length; i++) - transformedPatterns[i] = "[on] " + SkriptEvent.fixPattern(patterns[i]) + SkriptEventInfo.EVENT_PRIORITY_SYNTAX; + transformedPatterns[i] = SkriptEvent.fixPattern(patterns[i]); SkriptEventInfo<E> r = new SkriptEventInfo<>(name, transformedPatterns, c, originClassPath, events); - structures.add(r); + Skript.events.add(r); return r; } @@ -1489,14 +1490,8 @@ public static <E extends Structure> void registerStructure(Class<E> c, EntryVali structures.add(structureInfo); } - /** - * Modifications made to the returned Collection will not be reflected in the events available for parsing. - */ public static Collection<SkriptEventInfo<?>> getEvents() { - // Only used in documentation generation, so generating a new list each time is fine - return (Collection<SkriptEventInfo<?>>) (Collection<?>) structures.stream() - .filter(info -> info instanceof SkriptEventInfo) - .collect(Collectors.toList()); + return events; } public static List<StructureInfo<? extends Structure>> getStructures() { diff --git a/src/main/java/ch/njol/skript/doc/HTMLGenerator.java b/src/main/java/ch/njol/skript/doc/HTMLGenerator.java index 9a4adca6d7b..01a217cc5fb 100644 --- a/src/main/java/ch/njol/skript/doc/HTMLGenerator.java +++ b/src/main/java/ch/njol/skript/doc/HTMLGenerator.java @@ -662,8 +662,7 @@ private String generateEvent(String descTemp, SkriptEventInfo<?> info, @Nullable StringBuilder patterns = new StringBuilder(); for (String line : getDefaultIfNullOrEmpty(info.patterns, "Missing patterns.")) { assert line != null; - line = cleanPatterns(line); - line = line.replace(SkriptEventInfo.EVENT_PRIORITY_SYNTAX, ""); // replace priority syntax in event syntaxes + line = "[on] " + cleanPatterns(line); String parsed = pattern.replace("${element.pattern}", line); patterns.append(parsed); } diff --git a/src/main/java/ch/njol/skript/lang/SkriptEvent.java b/src/main/java/ch/njol/skript/lang/SkriptEvent.java index 1d89b2c6aa9..bbdbd7d170a 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptEvent.java +++ b/src/main/java/ch/njol/skript/lang/SkriptEvent.java @@ -25,15 +25,14 @@ import ch.njol.skript.config.SectionNode; import ch.njol.skript.events.EvtClick; import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.structures.StructEvent.EventData; import org.skriptlang.skript.lang.script.Script; import org.skriptlang.skript.lang.entry.EntryContainer; import org.skriptlang.skript.lang.structure.Structure; -import ch.njol.util.StringUtils; import org.bukkit.event.Event; import org.bukkit.event.EventPriority; import org.eclipse.jdt.annotation.Nullable; -import java.util.Arrays; import java.util.List; import java.util.Locale; @@ -64,30 +63,14 @@ public abstract class SkriptEvent extends Structure { @Override public final boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult, EntryContainer entryContainer) { - String expr = parseResult.expr; - if (StringUtils.startsWithIgnoreCase(expr, "on ")) - expr = expr.substring("on ".length()); - - String[] split = expr.split(" with priority "); - if (split.length != 1) { - if (!isEventPrioritySupported()) { - Skript.error("This event doesn't support event priority"); - return false; - } - - expr = String.join(" with priority ", Arrays.copyOfRange(split, 0, split.length - 1)); + this.expr = parseResult.expr; - String priorityString = split[split.length - 1]; - try { - eventPriority = EventPriority.valueOf(priorityString.toUpperCase()); - } catch (IllegalArgumentException e) { - throw new IllegalStateException(e); - } - } else { - eventPriority = null; + EventPriority priority = getParser().getData(EventData.class).getPriority(); + if (priority != null && !isEventPrioritySupported()) { + Skript.error("This event doesn't support event priority"); + return false; } - - this.expr = parseResult.expr = expr; + eventPriority = priority; SyntaxElementInfo<? extends Structure> syntaxElementInfo = getParser().getData(StructureData.class).getStructureInfo(); if (!(syntaxElementInfo instanceof SkriptEventInfo)) @@ -253,4 +236,9 @@ public static String fixPattern(String pattern) { return stringBuilder.toString(); } + @Nullable + public static SkriptEvent parse(String expr, SectionNode sectionNode, @Nullable String defaultError) { + return (SkriptEvent) Structure.parse(expr, sectionNode, defaultError, Skript.getEvents().iterator()); + } + } diff --git a/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java b/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java index 3581e655d70..a437bd6e32b 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java +++ b/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java @@ -29,8 +29,6 @@ public final class SkriptEventInfo<E extends SkriptEvent> extends StructureInfo<E> { - public static final String EVENT_PRIORITY_SYNTAX = " [with priority (lowest|low|normal|high|highest|monitor)]"; - public Class<? extends Event>[] events; public final String name; diff --git a/src/main/java/ch/njol/skript/structures/StructEvent.java b/src/main/java/ch/njol/skript/structures/StructEvent.java new file mode 100644 index 00000000000..ceb6fe53f9f --- /dev/null +++ b/src/main/java/ch/njol/skript/structures/StructEvent.java @@ -0,0 +1,117 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.structures; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.NoDoc; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptEvent; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.parser.ParserInstance; +import org.bukkit.event.Event; +import org.bukkit.event.EventPriority; +import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.entry.EntryContainer; +import org.skriptlang.skript.lang.structure.Structure; + +import java.util.Locale; + +@NoDoc +public class StructEvent extends Structure { + + static { + Skript.registerStructure(StructEvent.class, + "[on] <.+> [with priority (:(lowest|low|normal|high|highest|monitor))]"); + } + + private SkriptEvent event; + + @Override + @SuppressWarnings("ConstantConditions") + public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult, EntryContainer entryContainer) { + String expr = parseResult.regexes.get(0).group(); + if (!parseResult.tags.isEmpty()) + getParser().getData(EventData.class).priority = EventPriority.valueOf(parseResult.tags.get(0).toUpperCase(Locale.ENGLISH)); + event = SkriptEvent.parse(expr, entryContainer.getSource(), null); + return event != null; + } + + @Override + public boolean preLoad() { + getParser().setCurrentStructure(event); + return event.preLoad(); + } + + @Override + public boolean load() { + getParser().setCurrentStructure(event); + return event.load(); + } + + @Override + public boolean postLoad() { + getParser().setCurrentStructure(event); + return event.postLoad(); + } + + @Override + public void unload() { + event.unload(); + } + + @Override + public void postUnload() { + event.postUnload(); + } + + @Override + public Priority getPriority() { + return event.getPriority(); + } + + public SkriptEvent getSkriptEvent() { + return event; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return this.event.toString(event, debug); + } + + static { + ParserInstance.registerData(EventData.class, EventData::new); + } + + public static class EventData extends ParserInstance.Data { + + @Nullable + private EventPriority priority; + + public EventData(ParserInstance parserInstance) { + super(parserInstance); + } + + @Nullable + public EventPriority getPriority() { + return priority; + } + + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/structure/Structure.java b/src/main/java/org/skriptlang/skript/lang/structure/Structure.java index 17de96e446c..4852942aafe 100644 --- a/src/main/java/org/skriptlang/skript/lang/structure/Structure.java +++ b/src/main/java/org/skriptlang/skript/lang/structure/Structure.java @@ -174,11 +174,14 @@ public String toString() { @Nullable public static Structure parse(String expr, SectionNode sectionNode, @Nullable String defaultError) { + return parse(expr, sectionNode, defaultError, Skript.getStructures().iterator()); + } + + @Nullable + public static Structure parse(String expr, SectionNode sectionNode, @Nullable String defaultError, Iterator<? extends StructureInfo<? extends Structure>> iterator) { ParserInstance.get().getData(StructureData.class).sectionNode = sectionNode; - Iterator<StructureInfo<? extends Structure>> iterator = - new ConsumingIterator<>(Skript.getStructures().iterator(), - elementInfo -> ParserInstance.get().getData(StructureData.class).structureInfo = elementInfo); + iterator = new ConsumingIterator<>(iterator, elementInfo -> ParserInstance.get().getData(StructureData.class).structureInfo = elementInfo); try (ParseLogHandler parseLogHandler = SkriptLogger.startParseLogHandler()) { Structure structure = SkriptParser.parseStatic(expr, iterator, ParseContext.EVENT, defaultError); From f30f02351405537fa048d1f2f29424dc9af238f7 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Fri, 1 Dec 2023 10:55:54 -0800 Subject: [PATCH 525/619] Improve Location Comparison (#6205) --- .../classes/data/DefaultComparators.java | 29 +++++++++++++++++-- .../regressions/6205-location comparison.sk | 9 ++++++ 2 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 src/test/skript/tests/regressions/6205-location comparison.sk diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java b/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java index 7f96bab14d8..e59a5678403 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java @@ -24,27 +24,26 @@ import ch.njol.skript.aliases.ItemData; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.classes.ClassInfo; -import org.skriptlang.skript.lang.comparator.Comparator; import ch.njol.skript.entity.BoatChestData; import ch.njol.skript.entity.BoatData; import ch.njol.skript.entity.EntityData; import ch.njol.skript.entity.RabbitData; -import org.skriptlang.skript.lang.comparator.Comparators; import ch.njol.skript.util.BlockUtils; import ch.njol.skript.util.Date; import ch.njol.skript.util.EnchantmentType; import ch.njol.skript.util.Experience; -import ch.njol.skript.util.WeatherType; import ch.njol.skript.util.GameruleValue; import ch.njol.skript.util.StructureType; import ch.njol.skript.util.Time; import ch.njol.skript.util.Timeperiod; import ch.njol.skript.util.Timespan; +import ch.njol.skript.util.WeatherType; import ch.njol.skript.util.slot.EquipmentSlot; import ch.njol.skript.util.slot.Slot; import ch.njol.skript.util.slot.SlotWithIndex; import ch.njol.util.StringUtils; import ch.njol.util.coll.CollectionUtils; +import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.OfflinePlayer; import org.bukkit.World; @@ -61,6 +60,8 @@ import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; +import org.skriptlang.skript.lang.comparator.Comparator; +import org.skriptlang.skript.lang.comparator.Comparators; import org.skriptlang.skript.lang.comparator.Relation; import java.util.Objects; @@ -630,6 +631,28 @@ public boolean supportsOrdering() { return false; } }); + + // Location - Location + Comparators.registerComparator(Location.class, Location.class, new Comparator<Location, Location>() { + @Override + public Relation compare(Location first, Location second) { + return Relation.get( + // compare worlds + Objects.equals(first.getWorld(), second.getWorld()) && + // compare xyz coords + first.toVector().equals(second.toVector()) && + // normalize yaw and pitch to [-180, 180) and [-90, 90] respectively + // before comparing them + Location.normalizeYaw(first.getYaw()) == Location.normalizeYaw(second.getYaw()) && + Location.normalizePitch(first.getPitch()) == Location.normalizePitch(second.getPitch()) + ); + } + + @Override + public boolean supportsOrdering() { + return false; + } + }); } } diff --git a/src/test/skript/tests/regressions/6205-location comparison.sk b/src/test/skript/tests/regressions/6205-location comparison.sk new file mode 100644 index 00000000000..9e9df7cf30f --- /dev/null +++ b/src/test/skript/tests/regressions/6205-location comparison.sk @@ -0,0 +1,9 @@ +test "compare similar locations": + set {_world} to world "world" + assert location(1, 2, 3, {_world}, 4, 5) = location(1, 2, 3, {_world}, 4, 5) with "basic location comparison failed" + + assert location(1, 2, 3, {_world}, 270, 0) = location(1, 2, 3, {_world}, -90, 0) with "yaw normalization failed when comparing locations" + assert location(1, 2, 3, {_world}, 0, 270) = location(1, 2, 3, {_world}, 0, 90) with "pitch normalization failed when comparing locations" + assert location(1, 2, 3, {_world}, 270, 270) = location(1, 2, 3, {_world}, -90, 90) with "yaw and pitch normalization failed when comparing locations" + + assert location(1, (-1/infinity value), 3, {_world}, (-1/infinity value), (-1/infinity value)) = location(1, 0, 3, {_world}, 0, 0) with "0 and -0.0 are not equal when comparing locations" From ac5ff5b485ca8f5e4f51f8201e32a69f0157461f Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Fri, 1 Dec 2023 14:05:32 -0500 Subject: [PATCH 526/619] Allow asynchronous SkriptEvent#check execution (#6201) --- .../ch/njol/skript/SkriptEventHandler.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/ch/njol/skript/SkriptEventHandler.java b/src/main/java/ch/njol/skript/SkriptEventHandler.java index 28eaff9b23c..b3ad81d8d4e 100644 --- a/src/main/java/ch/njol/skript/SkriptEventHandler.java +++ b/src/main/java/ch/njol/skript/SkriptEventHandler.java @@ -120,7 +120,10 @@ private static void check(Event event, EventPriority priority) { boolean hasTrigger = false; for (Trigger trigger : triggers) { SkriptEvent triggerEvent = trigger.getEvent(); - if (triggerEvent.getEventPriority() == priority && Boolean.TRUE.equals(Task.callSync(() -> triggerEvent.check(event)))) { + if ( + triggerEvent.getEventPriority() == priority + && triggerEvent.canExecuteAsynchronously() ? triggerEvent.check(event) : Boolean.TRUE.equals(Task.callSync(() -> triggerEvent.check(event))) + ) { hasTrigger = true; break; } @@ -130,7 +133,7 @@ private static void check(Event event, EventPriority priority) { logEventStart(event); } - + boolean isCancelled = event instanceof Cancellable && ((Cancellable) event).isCancelled() && !listenCancelled.contains(event.getClass()); boolean isResultDeny = !(event instanceof PlayerInteractEvent && (((PlayerInteractEvent) event).getAction() == Action.LEFT_CLICK_AIR || ((PlayerInteractEvent) event).getAction() == Action.RIGHT_CLICK_AIR) && ((PlayerInteractEvent) event).useItemInHand() != Result.DENY); @@ -155,15 +158,12 @@ private static void check(Event event, EventPriority priority) { }; if (trigger.getEvent().canExecuteAsynchronously()) { - // check should be performed on the main thread - if (Boolean.FALSE.equals(Task.callSync(() -> triggerEvent.check(event)))) - continue; - execute.run(); + if (triggerEvent.check(event)) + execute.run(); } else { // Ensure main thread Task.callSync(() -> { - if (!triggerEvent.check(event)) - return null; - execute.run(); + if (triggerEvent.check(event)) + execute.run(); return null; // we don't care about a return value }); } From e5c4d4abbe7e9978c34b0d887f0f5ef7667c69a9 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Fri, 1 Dec 2023 12:25:48 -0700 Subject: [PATCH 527/619] Fix ExprSets conflicting (#6123) --- .../ch/njol/skript/expressions/ExprSets.java | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprSets.java b/src/main/java/ch/njol/skript/expressions/ExprSets.java index f5e3a48c538..4458c16bdf0 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprSets.java +++ b/src/main/java/ch/njol/skript/expressions/ExprSets.java @@ -18,6 +18,14 @@ */ package ch.njol.skript.expressions; +import java.util.Iterator; +import java.util.function.Supplier; + +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import com.google.common.collect.Lists; + import ch.njol.skript.Skript; import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.doc.Description; @@ -31,33 +39,26 @@ import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.skript.util.Utils; import ch.njol.util.Kleenean; -import com.google.common.collect.Lists; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - -import java.util.Iterator; -import java.util.List; -import java.util.function.Supplier; @Name("Sets") -@Description("Returns a list of all the values of a type; useful for looping.") +@Description("Returns a list of all the values of a type. Useful for looping.") @Examples({ "loop all attribute types:", - "\tset loop-value attribute of player to 10", - "\tmessage \"Set attribute %loop-value% to 10!\"" + "\tset loop-value attribute of player to 10", + "\tmessage \"Set attribute %loop-value% to 10!\"" }) -@Since("<i>unknown</i> (before 1.4.2), 2.7 (colors)") +// Class history rename order: LoopItems.class -> ExprItems.class (renamed in 2.3-alpha1) -> ExprSets.class (renamed in 2.7.0) +@Since("1.0 pre-5, 2.7 (classinfo)") public class ExprSets extends SimpleExpression<Object> { static { - Skript.registerExpression(ExprSets.class, Object.class, ExpressionType.COMBINED, - "[all [[of] the]|the|every] %*classinfo%" - ); + Skript.registerExpression(ExprSets.class, Object.class, ExpressionType.PATTERN_MATCHES_EVERYTHING, + "[all [[of] the]|the|every] %*classinfo%"); } - private ClassInfo<?> classInfo; @Nullable private Supplier<? extends Iterator<?>> supplier; + private ClassInfo<?> classInfo; @Override @SuppressWarnings("unchecked") @@ -78,16 +79,13 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override protected Object[] get(Event event) { - assert supplier != null; Iterator<?> iterator = supplier.get(); - List<?> elements = Lists.newArrayList(iterator); - return elements.toArray(new Object[0]); + return Lists.newArrayList(iterator).toArray(new Object[0]); } @Override @Nullable public Iterator<?> iterator(Event event) { - assert supplier != null; return supplier.get(); } From 01ae4e74cf6ee24a4dce0373e5d717e8fb78eb04 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Fri, 1 Dec 2023 14:38:40 -0500 Subject: [PATCH 528/619] Prepare for Release (2.7.3) (#6208) --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index ff0925c17ea..7a524a71e5b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ org.gradle.jvmargs=-Dfile.encoding=UTF-8 groupid=ch.njol name=skript -version=2.7.2 +version=2.7.3 jarName=Skript.jar testEnv=java17/paper-1.20.2 testEnvJavaVersion=17 From d25ae56c98fb3ca968ba9047577b4981ee95d1e0 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Sun, 10 Dec 2023 02:13:34 -0800 Subject: [PATCH 529/619] Fix incorrect examples for EffPotion (#6224) * Update EffPotion.java * Update src/main/java/ch/njol/skript/effects/EffPotion.java Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> --------- Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> --- src/main/java/ch/njol/skript/effects/EffPotion.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/effects/EffPotion.java b/src/main/java/ch/njol/skript/effects/EffPotion.java index e418c8c0523..78d2c86d089 100644 --- a/src/main/java/ch/njol/skript/effects/EffPotion.java +++ b/src/main/java/ch/njol/skript/effects/EffPotion.java @@ -47,7 +47,7 @@ "\tapply potion of strength of tier {strength::%uuid of player%} to the player for 999 days # Before 1.19.4", "", "apply potion effects of player's tool to player", - "apply haste potion of tier 3 without any particles to the player whilst hiding the potion icon # Hide potions" + "apply haste potion of tier 3 without any particles whilst hiding the potion icon to the player # Hide potions" }) @Since( "2.0, 2.2-dev27 (ambient and particle-less potion effects), " + From da651a577ce8ecc9bed057908b958c115e2b4c20 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Thu, 14 Dec 2023 13:15:23 -0700 Subject: [PATCH 530/619] Fix BlockUtils with BlockStateBlock (#6231) --- src/main/java/ch/njol/skript/util/BlockUtils.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/ch/njol/skript/util/BlockUtils.java b/src/main/java/ch/njol/skript/util/BlockUtils.java index 1e9c12fed01..39811c435cd 100644 --- a/src/main/java/ch/njol/skript/util/BlockUtils.java +++ b/src/main/java/ch/njol/skript/util/BlockUtils.java @@ -28,6 +28,7 @@ import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; +import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.block.data.BlockData; @@ -130,6 +131,7 @@ public static BlockData createBlockData(String dataString) { /** * Get the string version of a block, including type and location. * ex: 'stone' at 1.5, 1.5, 1.5 in world 'world' + * World can be null if the Block is Skript's BlockStateBlock. * * @param block Block to get string of * @param flags @@ -139,16 +141,17 @@ public static BlockData createBlockData(String dataString) { public static String blockToString(Block block, int flags) { String type = ItemType.toString(block, flags); Location location = getLocation(block); - if (location == null) { + if (location == null) return null; - } double x = location.getX(); double y = location.getY(); double z = location.getZ(); - String world = location.getWorld().getName(); - return String.format("'%s' at %s, %s, %s in world '%s'", type, x, y, z, world); + World world = location.getWorld(); + if (world == null) + return String.format("'%s' at %s, %s, %s", type, x, y, z); + return String.format("'%s' at %s, %s, %s in world '%s'", type, x, y, z, world.getName()); } /** From 09e8527d9e03e1c12385c966430c6fed947a29cd Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Sun, 17 Dec 2023 00:18:11 -0500 Subject: [PATCH 531/619] Merge `master` into `dev/feature` (#6211) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix ExprRandomNumber using a method from Java 17 (#6022) * Fix changing remaining time of command cooldown (#6021) Update ScriptCommand.java Co-authored-by: Moderocky <admin@moderocky.com> * Bump version to 2.7.1 (#5993) Co-authored-by: Moderocky <admin@moderocky.com> * fix 3 stray INSERT VERSIONs from 2.7.0 (#6027) correct incorrect values * Fix Documentation Actions on dev/patch (#6042) * Tidy up parts of config class. (#6025) * Add Release Model Document (#6041) Add release model document Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Co-authored-by: Moderocky <admin@moderocky.com> * (Cherry Pick) Fix cast throwing if existing variable for command storage exists (#5942) (#6026) Fix cast throwing if existing variable for command storage exists (#5942) * Fix cast throwing if existing variable for command storage exists * Update src/main/java/ch/njol/skript/command/ScriptCommand.java --------- Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> * (Cherry Pick) Fix NPE with invalid attributes and clean up ExprEntityAttribute (#5978) (#6023) Fix NPE with invalid attributes and clean up ExprEntityAttribute (#5978) * Avoid NPE and clean up class * Update ExprEntityAttribute.java * Update src/main/java/ch/njol/skript/expressions/ExprEntityAttribute.java * Update src/main/java/ch/njol/skript/expressions/ExprEntityAttribute.java --------- Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> * Fix multiple aliases sections not working (#6050) * Fix error when unloading a script with multiple variables sections (#6047) * Returns the old 2.6.4 duplicate variables section behaviour. * Add an error but i don't know what it's for * Add lots of brackets to keep walrus happy :}}} * Add load tracker to prevent multiple loading. * Prevent variable data wipe, fix another bug * Support IDEs from the dark ages that don't know what a star is and think it orbits the earth or something * add a test --------- Co-authored-by: APickledWalrus <apickledwalrus@gmail.com> * Bump actions/checkout from 3 to 4 (#6029) Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... * ⚒ Disable Javadocs generation for nightly docs & improvements (#6059) * Let's see if I am good at GH actions 🤞 * ops! * Use proper docs template reference when possible * Disable nightly javadocs generation with an option Each javadoc is ~50mb, which was causing the big size of the docs! while each docs generation is ~2mb only * Fix building * Revert pull changes They are not what fixed the issue, probably the old PRs aren't syncing for some reason * Update build.gradle --------- Co-authored-by: Moderocky <admin@moderocky.com> * Change the target branch of dependabot (#6063) Update dependabot.yml * ⚒ Fix stop all sounds NPE (#6067) * Bump actions/checkout from 3 to 4 (#6069) Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> * Bump org.gradle.toolchains.foojay-resolver-convention from 0.5.0 to 0.7.0 (#6070) Bump org.gradle.toolchains.foojay-resolver-convention Bumps org.gradle.toolchains.foojay-resolver-convention from 0.5.0 to 0.7.0. --- updated-dependencies: - dependency-name: org.gradle.toolchains.foojay-resolver-convention dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> * Bump org.easymock:easymock from 5.1.0 to 5.2.0 (#6071) Bumps [org.easymock:easymock](https://github.com/easymock/easymock) from 5.1.0 to 5.2.0. - [Release notes](https://github.com/easymock/easymock/releases) - [Changelog](https://github.com/easymock/easymock/blob/master/ReleaseNotes.md) - [Commits](https://github.com/easymock/easymock/compare/easymock-5.1.0...easymock-5.2.0) --- updated-dependencies: - dependency-name: org.easymock:easymock dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> * Bump io.papermc.paper:paper-api from 1.20.1-R0.1-SNAPSHOT to 1.20.2-R0.1-SNAPSHOT (#6072) * Bump io.papermc.paper:paper-api Bumps io.papermc.paper:paper-api from 1.20.1-R0.1-SNAPSHOT to 1.20.2-R0.1-SNAPSHOT. --- updated-dependencies: - dependency-name: io.papermc.paper:paper-api dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * Apply 1.20.2 to the test runner * Deprecate org.bukkit.util.Consumer usage in EntityData * Adapt against the Java Consumer instead of Bukkit's * Resolve existing method deprecation * Adapt against the Java Consumer instead of Bukkit's * Update developer note * Result in reflection for Bukkit Consumer * Resolve ThrownPotion Consumer * Result in reflection for Bukkit Consumer * Pretty else if * Add common reflective spawn method. * Use common spawn method in potion class. * Remove old suppression! * Whoops I forgot about the consumer * Don't need reflection import anymore :) * Thrown potion class --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Co-authored-by: TheLimeGlass <seantgrover@gmail.com> Co-authored-by: Moderocky <admin@moderocky.com> * Pull request template defaults (#5665) Update pull_request_template.md * Fix EvtPlayerChunkEnter Comparison & Cleanup (#5965) Initial (cherry picked from commit 389c0022ed177c3124cfd1884165c5c697becaac) Co-authored-by: Moderocky <admin@moderocky.com> * Fixes EffSecSpawn not properly handling local variables created within the section (#6033) Communicate local variables between consumer calls thanks pickle Co-authored-by: Moderocky <admin@moderocky.com> * Remove PlayerPreprocessCommandEvent listener and clean up Commands (#5966) * Remove PPCE listener and clean up Commands * Apply suggestions from code review Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> * Update Commands.java * we hate breaking changes --------- Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Co-authored-by: Moderocky <admin@moderocky.com> * Clean up vector classes and fix a few bugs. * More improvements * Apply suggestions from code review Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> * Budget Expansion * Fix Logging Issues In ExpressionEntryData (#6081) Fix duplicate logging * Prepare For Release 2.7.1 (#6082) * Update Minecraft wiki links to new domain (#6078) * ⚒ Fix fake player count paper check error (#6090) * Fix Command Help (#6080) Fix issues and cleanup CommandHelp class Co-authored-by: Moderocky <admin@moderocky.com> * Bump net.kyori:adventure-text-serializer-bungeecord from 4.3.0 to 4.3.1 (#6084) Bumps [net.kyori:adventure-text-serializer-bungeecord](https://github.com/KyoriPowered/adventure-platform) from 4.3.0 to 4.3.1. - [Release notes](https://github.com/KyoriPowered/adventure-platform/releases) - [Commits](https://github.com/KyoriPowered/adventure-platform/compare/v4.3.0...v4.3.1) --- updated-dependencies: - dependency-name: net.kyori:adventure-text-serializer-bungeecord dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> * Fix unloading/reloading a directory in the scripts effect (#6106) * Moved unloading to a common method. * Accidental double space :grimacing: * Force UTF-8 encoding for Gradle daemon (#6103) Co-authored-by: Moderocky <admin@moderocky.com> * Corrected Javadocs name, title (#6038) * Javadoc Title,Name * Update .gitignore Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> --------- Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Co-authored-by: Moderocky <admin@moderocky.com> * Rebase JUnit references fix for dev/patch (#6057) * Fix options issue in functions (#6121) * Fix command permission messages (2.7.1 issue) (#6126) re-add permission handling during PPCE to get around spigot behavior. * Fix stack overflow when stringifying block inventories. (#6117) * Fix stack overflow when stringifying block inventories. * Blanket catch until a better solution is found. * Fix comparison of cyclical types (specifically comparing times) (#6128) * Add cyclical type helper. * Make time cyclical. * Add special comparison for cyclical types. * Add some tests. * Fix floating point rounding error in loop N times (#6132) Fix floating point error in loop X times. * Fix Sorted List Expression (#6102) * Fix ExprSortedList * Update src/main/java/ch/njol/skript/expressions/ExprSortedList.java Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> * Fix test Co-authored-by: Moderocky <admin@moderocky.com> --------- Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Co-authored-by: Moderocky <admin@moderocky.com> * Fix colour codes being reset in reload message. (#6150) Fix colour codes being reset. * Fix ExprDurability's Changer (#6154) * Fix ExprDurability's changer * Change method name. * Add simple test. --------- Co-authored-by: Moderocky <admin@moderocky.com> * Catch the exception when pushing entity by non finite vector (#5765) * Fix issues with ExprDrops (#6130) * refactor ExprDrops, fix bugs, add test fixed setting drops to multiple items/experience values at once fixed null values being left in drops after removing items maintained behavior but behavior needs a big update * small cleanup * Fix JUnit test location * import shenanigans --------- Co-authored-by: Moderocky <admin@moderocky.com> * Prepare For Release (2.7.2) (#6166) * Prevent InventoryHolder -> X chaining (#6171) * Prevent InventoryHolder -> X chaining * Improve Location Comparison (#6205) * Allow asynchronous SkriptEvent#check execution (#6201) * Fix ExprSets conflicting (#6123) * Prepare for Release (2.7.3) (#6208) * Further corrections * Fix NPE issue with drops in 1.20.2 * Update StructFunction.java --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: _tud <mmbakkar06@gmail.com> Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> Co-authored-by: Moderocky <admin@moderocky.com> Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: TheLimeGlass <seantgrover@gmail.com> Co-authored-by: DelayedGaming <72163224+DelayedGaming@users.noreply.github.com> Co-authored-by: Spongecade <spongecade.129@gmail.com> Co-authored-by: MihirKohli <55236890+MihirKohli@users.noreply.github.com> Co-authored-by: _tud <98935832+UnderscoreTud@users.noreply.github.com> Co-authored-by: 3meraldK <48335651+3meraldK@users.noreply.github.com> --- .github/pull_request_template.md | 6 +- build.gradle | 10 +- gradle.properties | 5 +- skript-aliases | 2 +- src/main/java/ch/njol/skript/Skript.java | 9 +- .../ch/njol/skript/SkriptEventHandler.java | 18 +- .../java/ch/njol/skript/aliases/ItemType.java | 374 ++++++++++-------- .../skript/classes/data/BukkitClasses.java | 9 +- .../classes/data/BukkitEventValues.java | 39 +- .../classes/data/DefaultComparators.java | 29 +- .../classes/data/DefaultConverters.java | 4 +- .../ch/njol/skript/command/CommandHelp.java | 152 +++---- .../java/ch/njol/skript/command/Commands.java | 65 +-- .../ch/njol/skript/command/ScriptCommand.java | 40 +- .../njol/skript/conditions/CondCompare.java | 32 +- .../skript/conditions/CondIsSlimeChunk.java | 2 +- .../CondMinecraftVersion.java | 4 +- .../ch/njol/skript/effects/EffExplosion.java | 2 +- .../ch/njol/skript/effects/EffScriptFile.java | 24 +- .../ch/njol/skript/effects/EffStopSound.java | 11 +- .../effects/EffVectorRotateAroundAnother.java | 29 +- .../skript/effects/EffVectorRotateXYZ.java | 41 +- .../ch/njol/skript/entity/EntityData.java | 157 +++++--- .../njol/skript/entity/FallingBlockData.java | 8 +- .../njol/skript/entity/ThrownPotionData.java | 20 +- .../java/ch/njol/skript/entity/XpOrbData.java | 6 +- .../skript/events/EvtPlayerChunkEnter.java | 7 +- .../ch/njol/skript/expressions/ExprAge.java | 2 +- .../ch/njol/skript/expressions/ExprDrops.java | 147 ++++--- .../skript/expressions/ExprDurability.java | 106 ++--- .../expressions/ExprExplosionYield.java | 2 +- .../expressions/ExprExplosiveYield.java | 2 +- .../skript/expressions/ExprGlidingState.java | 2 +- .../expressions/ExprOnlinePlayersCount.java | 38 +- .../expressions/ExprScoreboardTags.java | 2 +- .../ch/njol/skript/expressions/ExprSets.java | 36 +- .../skript/expressions/ExprSortedList.java | 46 +-- .../ch/njol/skript/expressions/ExprTimes.java | 4 +- .../expressions/ExprVectorAngleBetween.java | 19 +- .../expressions/ExprVectorArithmetic.java | 56 ++- .../ExprVectorBetweenLocations.java | 19 +- .../expressions/ExprVectorCrossProduct.java | 19 +- .../expressions/ExprVectorCylindrical.java | 29 +- .../expressions/ExprVectorDotProduct.java | 26 +- .../skript/expressions/ExprVectorFromXYZ.java | 17 +- .../ExprVectorFromYawAndPitch.java | 21 +- .../skript/expressions/ExprVectorLength.java | 65 +-- .../expressions/ExprVectorNormalize.java | 20 +- .../expressions/ExprVectorOfLocation.java | 17 +- .../expressions/ExprVectorSpherical.java | 29 +- .../expressions/ExprVectorSquaredLength.java | 14 +- .../skript/expressions/ExprVectorXYZ.java | 89 +++-- .../ch/njol/skript/sections/EffSecSpawn.java | 34 +- .../skript/structures/StructFunction.java | 48 ++- .../skript/test/runner/CondRunningJUnit.java | 60 +++ .../skript/test/runner/EffObjectives.java | 18 +- .../njol/skript/test/runner/TestTracker.java | 4 + src/main/java/ch/njol/skript/util/Time.java | 13 +- .../lang/entry/util/ExpressionEntryData.java | 2 +- .../skriptlang/skript/lang/util/Cyclical.java | 52 +++ .../skript/lang/util/package-info.java | 23 ++ src/main/resources/lang/default.lang | 1 + src/main/resources/lang/english.lang | 2 +- src/main/resources/lang/french.lang | 2 +- src/main/resources/lang/german.lang | 6 +- src/main/resources/lang/japanese.lang | 2 +- src/main/resources/lang/korean.lang | 4 +- src/main/resources/lang/polish.lang | 2 +- .../junit/registration}/ExprJUnitTest.java | 6 +- .../test/junit/registration/package-info.java | 25 ++ .../test/tests/syntaxes/ExprDropsTest.java | 44 +++ .../{paper-1.20.1.json => paper-1.20.2.json} | 4 +- src/test/skript/{tests => }/junit/EvtGrow.sk | 0 src/test/skript/junit/ExprDrops.sk | 103 +++++ src/test/skript/{tests => }/junit/README.md | 0 .../{tests => }/junit/SimpleJUnitTest.sk | 2 +- .../6032-local-vars-created-in-effsecspawn.sk | 5 + .../regressions/6205-location comparison.sk | 9 + .../tests/syntaxes/conditions/CondCompare.sk | 11 + .../syntaxes/expressions/ExprDurability.sk | 14 + .../syntaxes/expressions/ExprSortedList.sk | 17 + .../tests/syntaxes/expressions/ExprTimes.sk | 8 + .../syntaxes/structures/StructCommand.sk | 5 +- .../syntaxes/structures/StructFunction.sk | 5 +- 84 files changed, 1524 insertions(+), 939 deletions(-) rename src/main/java/ch/njol/skript/{test/runner => conditions}/CondMinecraftVersion.java (96%) create mode 100644 src/main/java/ch/njol/skript/test/runner/CondRunningJUnit.java create mode 100644 src/main/java/org/skriptlang/skript/lang/util/Cyclical.java create mode 100644 src/main/java/org/skriptlang/skript/lang/util/package-info.java rename src/{main/java/ch/njol/skript/test/runner => test/java/org/skriptlang/skript/test/junit/registration}/ExprJUnitTest.java (92%) create mode 100644 src/test/java/org/skriptlang/skript/test/junit/registration/package-info.java create mode 100644 src/test/java/org/skriptlang/skript/test/tests/syntaxes/ExprDropsTest.java rename src/test/skript/environments/java17/{paper-1.20.1.json => paper-1.20.2.json} (85%) rename src/test/skript/{tests => }/junit/EvtGrow.sk (100%) create mode 100644 src/test/skript/junit/ExprDrops.sk rename src/test/skript/{tests => }/junit/README.md (100%) rename src/test/skript/{tests => }/junit/SimpleJUnitTest.sk (95%) create mode 100644 src/test/skript/tests/regressions/6032-local-vars-created-in-effsecspawn.sk create mode 100644 src/test/skript/tests/regressions/6205-location comparison.sk create mode 100644 src/test/skript/tests/syntaxes/conditions/CondCompare.sk create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprDurability.sk create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprSortedList.sk create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprTimes.sk diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index ce87d77a996..698a65a0554 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -2,6 +2,6 @@ <!--- Describe your changes here. ---> --- -**Target Minecraft Versions:** <!-- 'any' means all supported versions --> -**Requirements:** <!-- Required plugins, Minecraft versions, server software... --> -**Related Issues:** <!-- Links to related issues --> +**Target Minecraft Versions:** any <!-- 'any' means all supported versions --> +**Requirements:** none <!-- Required plugins, server software... --> +**Related Issues:** none <!-- Links to related issues --> diff --git a/build.gradle b/build.gradle index bc74eee6f9c..42baba93c72 100644 --- a/build.gradle +++ b/build.gradle @@ -27,9 +27,9 @@ allprojects { dependencies { shadow group: 'io.papermc', name: 'paperlib', version: '1.0.8' shadow group: 'org.bstats', name: 'bstats-bukkit', version: '3.0.2' - shadow group: 'net.kyori', name: 'adventure-text-serializer-bungeecord', version: '4.3.0' + shadow group: 'net.kyori', name: 'adventure-text-serializer-bungeecord', version: '4.3.1' - implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.20.1-R0.1-SNAPSHOT' + implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.20.2-R0.1-SNAPSHOT' implementation group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.700' implementation group: 'com.google.code.findbugs', name: 'findbugs', version: '3.0.1' implementation group: 'com.sk89q.worldguard', name: 'worldguard-legacy', version: '7.0.0-SNAPSHOT' @@ -226,7 +226,7 @@ void createTestTask(String name, String desc, String environments, int javaVersi main = 'ch.njol.skript.test.platform.PlatformMain' args = [ 'build/test_runners', - junit ? 'src/test/skript/tests/junit' : 'src/test/skript/tests', + junit ? 'src/test/skript/junit' : 'src/test/skript/tests', 'src/test/resources/runner_data', environments, modifiers.contains(Modifiers.DEV_MODE), @@ -250,7 +250,7 @@ void createTestTask(String name, String desc, String environments, int javaVersi } } -def latestEnv = 'java17/paper-1.20.1.json' +def latestEnv = 'java17/paper-1.20.2.json' def latestJava = 17 def oldestJava = 8 @@ -393,7 +393,7 @@ task nightlyRelease(type: ShadowJar) { javadoc { dependsOn nightlyResources - + source = sourceSets.main.allJava exclude("ch/njol/skript/conditions/**") diff --git a/gradle.properties b/gradle.properties index 847cfde2fcc..7af67b25858 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,10 +1,11 @@ # Done to increase the memory available to gradle. -org.gradle.jvmargs=-Xmx1G +# Ensure encoding is consistent across systems. +org.gradle.jvmargs=-Xmx1G -Dfile.encoding=UTF-8 org.gradle.parallel=true groupid=ch.njol name=skript version=2.8.0-dev jarName=Skript.jar -testEnv=java17/paper-1.20.1 +testEnv=java17/paper-1.20.2 testEnvJavaVersion=17 diff --git a/skript-aliases b/skript-aliases index 1ee77d8573a..0884ede0fdf 160000 --- a/skript-aliases +++ b/skript-aliases @@ -1 +1 @@ -Subproject commit 1ee77d8573aa37456f1b49fe12aec7bb410d1dd7 +Subproject commit 0884ede0fdf69e914b944500d9f24f1c000a90a2 diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index ae35c08fbf7..878dfe12666 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -591,6 +591,8 @@ public void run() { tainted = true; try { getAddonInstance().loadClasses("ch.njol.skript.test.runner"); + if (TestMode.JUNIT) + getAddonInstance().loadClasses("org.skriptlang.skript.test.junit.registration"); } catch (IOException e) { Skript.exception("Failed to load testing environment."); Bukkit.getServer().shutdown(); @@ -684,7 +686,6 @@ protected void afterErrors() { TestTracker.testFailed("exception was thrown during execution"); } if (TestMode.JUNIT) { - SkriptLogger.setVerbosity(Verbosity.DEBUG); info("Running all JUnit tests..."); long milliseconds = 0, tests = 0, fails = 0, ignored = 0, size = 0; try { @@ -712,7 +713,7 @@ protected void afterErrors() { // If JUnit failures are present, add them to the TestTracker. junit.getFailures().forEach(failure -> { String message = failure.getMessage() == null ? "" : " " + failure.getMessage(); - TestTracker.testFailed("'" + test + "': " + message); + TestTracker.JUnitTestFailed(test, message); Skript.exception(failure.getException(), "JUnit test '" + failure.getTestHeader() + " failed."); }); SkriptJUnitTest.clearJUnitTest(); @@ -734,7 +735,7 @@ protected void afterErrors() { // Delay server shutdown to stop the server from crashing because the current tick takes a long time due to all the tests Bukkit.getScheduler().runTaskLater(Skript.this, () -> { if (TestMode.JUNIT && !EffObjectives.isJUnitComplete()) - TestTracker.testFailed(EffObjectives.getFailedObjectivesString()); + EffObjectives.fail(); info("Collecting results to " + TestMode.RESULTS_FILE); String results = new Gson().toJson(TestTracker.collectResults()); @@ -1261,7 +1262,7 @@ public static boolean isAcceptRegistrations() { } public static void checkAcceptRegistrations() { - if (!isAcceptRegistrations()) + if (!isAcceptRegistrations() && !Skript.testing()) throw new SkriptAPIException("Registration can only be done during plugin initialization"); } diff --git a/src/main/java/ch/njol/skript/SkriptEventHandler.java b/src/main/java/ch/njol/skript/SkriptEventHandler.java index 28eaff9b23c..b3ad81d8d4e 100644 --- a/src/main/java/ch/njol/skript/SkriptEventHandler.java +++ b/src/main/java/ch/njol/skript/SkriptEventHandler.java @@ -120,7 +120,10 @@ private static void check(Event event, EventPriority priority) { boolean hasTrigger = false; for (Trigger trigger : triggers) { SkriptEvent triggerEvent = trigger.getEvent(); - if (triggerEvent.getEventPriority() == priority && Boolean.TRUE.equals(Task.callSync(() -> triggerEvent.check(event)))) { + if ( + triggerEvent.getEventPriority() == priority + && triggerEvent.canExecuteAsynchronously() ? triggerEvent.check(event) : Boolean.TRUE.equals(Task.callSync(() -> triggerEvent.check(event))) + ) { hasTrigger = true; break; } @@ -130,7 +133,7 @@ private static void check(Event event, EventPriority priority) { logEventStart(event); } - + boolean isCancelled = event instanceof Cancellable && ((Cancellable) event).isCancelled() && !listenCancelled.contains(event.getClass()); boolean isResultDeny = !(event instanceof PlayerInteractEvent && (((PlayerInteractEvent) event).getAction() == Action.LEFT_CLICK_AIR || ((PlayerInteractEvent) event).getAction() == Action.RIGHT_CLICK_AIR) && ((PlayerInteractEvent) event).useItemInHand() != Result.DENY); @@ -155,15 +158,12 @@ private static void check(Event event, EventPriority priority) { }; if (trigger.getEvent().canExecuteAsynchronously()) { - // check should be performed on the main thread - if (Boolean.FALSE.equals(Task.callSync(() -> triggerEvent.check(event)))) - continue; - execute.run(); + if (triggerEvent.check(event)) + execute.run(); } else { // Ensure main thread Task.callSync(() -> { - if (!triggerEvent.check(event)) - return null; - execute.run(); + if (triggerEvent.check(event)) + execute.run(); return null; // we don't care about a return value }); } diff --git a/src/main/java/ch/njol/skript/aliases/ItemType.java b/src/main/java/ch/njol/skript/aliases/ItemType.java index 83d58f1ca4c..6b56e9cee17 100644 --- a/src/main/java/ch/njol/skript/aliases/ItemType.java +++ b/src/main/java/ch/njol/skript/aliases/ItemType.java @@ -18,37 +18,6 @@ */ package ch.njol.skript.aliases; -import java.io.NotSerializableException; -import java.io.StreamCorruptedException; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.NoSuchElementException; -import java.util.Random; -import java.util.RandomAccess; -import java.util.Set; - -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.OfflinePlayer; -import org.bukkit.block.Block; -import org.bukkit.block.BlockState; -import org.bukkit.block.Skull; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.entity.Player; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.PlayerInventory; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.inventory.meta.SkullMeta; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.aliases.ItemData.OldItemData; import ch.njol.skript.bukkitutil.BukkitUnsafe; import ch.njol.skript.bukkitutil.ItemUtils; @@ -68,14 +37,44 @@ import ch.njol.yggdrasil.Fields; import ch.njol.yggdrasil.Fields.FieldContext; import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilExtendedSerializable; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.OfflinePlayer; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.block.Skull; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.SkullMeta; +import org.eclipse.jdt.annotation.Nullable; + +import java.io.NotSerializableException; +import java.io.StreamCorruptedException; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NoSuchElementException; +import java.util.Random; +import java.util.RandomAccess; +import java.util.Set; @ContainerType(ItemStack.class) public class ItemType implements Unit, Iterable<ItemData>, Container<ItemStack>, YggdrasilExtendedSerializable { - + static { // This handles updating ItemType and ItemData variable records Variables.yggdrasil.registerFieldHandler(new FieldHandler() { - + @Override public boolean missingField(Object o, Field field) throws StreamCorruptedException { if (!(o instanceof ItemType || o instanceof ItemData)) @@ -84,12 +83,12 @@ public boolean missingField(Object o, Field field) throws StreamCorruptedExcepti return true; // Just null, no need for updating that data return false; } - + @Override public boolean incompatibleField(Object o, Field f, FieldContext field) throws StreamCorruptedException { return false; } - + @Override public boolean excessiveField(Object o, FieldContext field) throws StreamCorruptedException { if (!(o instanceof ItemType || o instanceof ItemData)) @@ -101,7 +100,7 @@ public boolean excessiveField(Object o, FieldContext field) throws StreamCorrupt } }); } - + /** * DO NOT ADD ItemDatas to this list directly! * <p> @@ -109,31 +108,31 @@ public boolean excessiveField(Object o, FieldContext field) throws StreamCorrupt * can have its own ItemMeta. */ final ArrayList<ItemData> types = new ArrayList<>(2); - + /** * Whether this ItemType represents all types or not. */ private boolean all = false; - + /** * Amount determines how many items this type represents. Negative amounts * are treated as their absolute values when adding items to inventories * and otherwise used as "doesn't matter" flags. */ private int amount = -1; - + /** * ItemTypes to use instead of this one if adding to an inventory or setting a block. */ @Nullable private ItemType item = null, block = null; - + /** * Meta that applies for all ItemDatas there. */ @Nullable private ItemMeta globalMeta; - + void setItem(final @Nullable ItemType item) { if (equals(item)) { // can happen if someone defines a 'x' and 'x item/block' alias that have the same value, e.g. 'dirt' and 'dirt block' this.item = null; @@ -149,7 +148,7 @@ void setItem(final @Nullable ItemType item) { this.item = item; } } - + void setBlock(final @Nullable ItemType block) { if (equals(block)) { this.block = null; @@ -167,30 +166,30 @@ void setBlock(final @Nullable ItemType block) { } public ItemType() {} - + public ItemType(Material id) { add_(new ItemData(id)); } - + public ItemType(Material id, String tags) { add_(new ItemData(id, tags)); } - + public ItemType(ItemData d) { add_(d.clone()); } - + public ItemType(ItemStack i) { amount = i.getAmount(); add_(new ItemData(i)); } - + public ItemType(BlockState b) { // amount = 1; add_(new ItemData(b)); // TODO metadata - spawners, skulls, etc. } - + /** * Copy constructor. * @param i Another ItemType. @@ -198,7 +197,7 @@ public ItemType(BlockState b) { private ItemType(ItemType i) { setTo(i); } - + public void setTo(ItemType i) { all = i.all; amount = i.amount; @@ -221,7 +220,7 @@ public ItemType(Block block) { public void modified() { item = block = null; } - + /** * Returns amount of the item in stack that this type represents. * @return amount. @@ -230,22 +229,22 @@ public void modified() { public int getAmount() { return Math.abs(amount); } - + /** * Only use this method if you know what you're doing. - * + * * @return The internal amount, i.e. same as {@link #getAmount()} * or additive inverse number of it. */ public int getInternalAmount() { return amount; } - + @Override public void setAmount(final double amount) { setAmount((int) amount); } - + public void setAmount(final int amount) { this.amount = amount; if (item != null) @@ -253,7 +252,7 @@ public void setAmount(final int amount) { if (block != null) block.amount = amount; } - + /** * Checks if this item type represents one of its items (OR) or all of * them (AND). If this has only one item, it doesn't matter. @@ -262,30 +261,30 @@ public void setAmount(final int amount) { public boolean isAll() { return all; } - + public void setAll(final boolean all) { this.all = all; } - + public boolean isOfType(@Nullable ItemStack item) { if (item == null) return isOfType(Material.AIR, null); return isOfType(new ItemData(item)); } - + public boolean isOfType(@Nullable BlockState block) { if (block == null) return isOfType(Material.AIR, null); - + return isOfType(new ItemData(block)); } - + public boolean isOfType(@Nullable Block block) { if (block == null) return isOfType(Material.AIR, null); return isOfType(block.getState()); } - + public boolean isOfType(ItemData type) { for (final ItemData myType : types) { if (myType.equals(type)) { @@ -294,16 +293,16 @@ public boolean isOfType(ItemData type) { } return false; } - + public boolean isOfType(Material id, @Nullable String tags) { return isOfType(new ItemData(id, tags)); } - + public boolean isOfType(Material id) { // TODO avoid object creation return isOfType(new ItemData(id, null)); } - + /** * Checks if this type represents all the items represented by given * item type. This type may of course also represent other items. @@ -313,17 +312,17 @@ public boolean isOfType(Material id) { public boolean isSupertypeOf(ItemType other) { return types.containsAll(other.types); } - + public ItemType getItem() { final ItemType item = this.item; return item == null ? this : item; } - + public ItemType getBlock() { final ItemType block = this.block; return block == null ? this : block; } - + /** * @return Whether this ItemType has at least one ItemData that represents an item */ @@ -334,7 +333,7 @@ public boolean hasItem() { } return false; } - + /** * @return Whether this ItemType has at least one ItemData that represents a block */ @@ -353,10 +352,10 @@ public boolean hasBlock() { public boolean hasType() { return !types.isEmpty(); } - + /** * Sets the given block to this ItemType - * + * * @param block The block to set * @param applyPhysics Whether to run a physics check just after setting the block * @return Whether the block was successfully set @@ -382,7 +381,7 @@ public boolean setBlock(Block block, boolean applyPhysics) { } return false; } - + /** * Send a block change to a player * <p>This will send a fake block change to the player, and will not change the block on the server.</p> @@ -399,11 +398,11 @@ public void sendBlockChange(Player player, Location location) { BlockUtils.sendBlockChange(player, location, blockType, d.getBlockValues()); } } - + /** * Intersects all ItemDatas with all ItemDatas of the given ItemType, returning an ItemType with at most n*m ItemDatas, where n = #ItemDatas of this ItemType, and m = * #ItemDatas of the argument. - * + * * @see ItemData#intersection(ItemData) * @param other * @return A new item type which is the intersection of the two item types or null if the intersection is empty. @@ -421,7 +420,7 @@ public ItemType intersection(ItemType other) { return null; return r; } - + /** * @param type Some ItemData. Only a copy of it will be stored. */ @@ -430,7 +429,7 @@ public void add(@Nullable ItemData type) { add_(type.clone()); } } - + /** * @param type A cloned or newly created ItemData */ @@ -441,36 +440,36 @@ private void add_(@Nullable ItemData type) { modified(); } } - + public void addAll(Collection<ItemData> types) { this.types.addAll(types); modified(); } - + public void remove(ItemData type) { if (types.remove(type)) { //numItems -= type.numItems(); modified(); } } - + void remove(int index) { types.remove(index); //numItems -= type.numItems(); modified(); } - + @Override public Iterator<ItemStack> containerIterator() { return new Iterator<ItemStack>() { @SuppressWarnings("null") Iterator<ItemData> iter = types.iterator(); - + @Override public boolean hasNext() { return iter.hasNext(); } - + @Override public ItemStack next() { if (!hasNext()) @@ -479,17 +478,17 @@ public ItemStack next() { is.setAmount(getAmount()); return is; } - + @Override public void remove() { throw new UnsupportedOperationException(); } }; } - + /** * Gets all ItemStacks this ItemType represents. Only use this if you know what you're doing, as it returns only one element if this is not an 'every' alias. - * + * * @return An Iterable whose iterator will always return the same item(s) */ public Iterable<ItemStack> getAll() { @@ -506,7 +505,7 @@ public Iterator<ItemStack> iterator() { } }; } - + @Nullable public ItemStack removeAll(@Nullable ItemStack item) { boolean wasAll = all; @@ -520,10 +519,10 @@ public ItemStack removeAll(@Nullable ItemStack item) { amount = oldAmount; } } - + /** * Removes this type from the item stack if appropriate - * + * * @param item * @return The passed ItemStack or null if the resulting amount is <= 0 */ @@ -541,10 +540,10 @@ public ItemStack removeFrom(@Nullable ItemStack item) { item.setAmount(a); return item; } - + /** * Adds this ItemType to the given item stack - * + * * @param item * @return The passed ItemStack or a new one if the passed is null or air */ @@ -556,14 +555,14 @@ public ItemStack addTo(final @Nullable ItemStack item) { item.setAmount(Math.min(item.getAmount() + getAmount(), item.getMaxStackSize())); return item; } - + @Override public ItemType clone() { return new ItemType(this); } - + private final static Random random = new Random(); - + /** * @return One random ItemStack that this ItemType represents. If you have a List or an Inventory, use {@link #addTo(Inventory)} or {@link #addTo(List)} respectively. * @see #addTo(Inventory) @@ -581,13 +580,13 @@ public ItemStack getRandom() { is.setAmount(getAmount()); return is; } - + /** * Test whether this ItemType can be put into the given inventory completely. * <p> * REMIND If this ItemType represents multiple items with OR, this function will immediately return false.<br/> * CondCanHold currently blocks aliases without 'every'/'all' as temporary solution. - * + * * @param invi * @return Whether this item type can be added to the given inventory */ @@ -598,7 +597,7 @@ public boolean hasSpace(final Inventory invi) { } return addTo(getStorageContents(invi)); } - + public static ItemStack[] getCopiedContents(Inventory invi) { final ItemStack[] buf = invi.getContents(); for (int i = 0; i < buf.length; i++) @@ -606,7 +605,7 @@ public static ItemStack[] getCopiedContents(Inventory invi) { buf[i] = buf[i].clone(); return buf; } - + /** * Gets copy of storage contents, i.e. ignores armor and off hand. This is due to Spigot 1.9 * added armor slots, and off hand to default inventory index. @@ -623,7 +622,7 @@ public static ItemStack[] getStorageContents(final Inventory invi) { return tBuf; } else return getCopiedContents(invi); } - + /** * @return List of ItemDatas. The returned list is not modifiable, use {@link #add(ItemData)} and {@link #remove(ItemData)} if you need to change the list, or use the * {@link #iterator()}. @@ -632,28 +631,28 @@ public static ItemStack[] getStorageContents(final Inventory invi) { public List<ItemData> getTypes() { return Collections.unmodifiableList(types); } - + public int numTypes() { return types.size(); } - + /** * @return How many different items this item type represents */ public int numItems() { return types.size(); } - + @Override public Iterator<ItemData> iterator() { return new Iterator<ItemData>() { private int next = 0; - + @Override public boolean hasNext() { return next < types.size(); } - + @SuppressWarnings("null") @Override public ItemData next() { @@ -661,7 +660,7 @@ public ItemData next() { throw new NoSuchElementException(); return types.get(next++); } - + @Override public void remove() { if (next <= 0) @@ -670,7 +669,7 @@ public void remove() { } }; } - + public boolean isContainedIn(Iterable<ItemStack> items) { int needed = getAmount(); int found = 0; @@ -706,7 +705,7 @@ public boolean isContainedIn(ItemStack[] items) { return false; return all; } - + public boolean removeAll(Inventory invi) { final boolean wasAll = all; final int oldAmount = amount; @@ -719,58 +718,86 @@ public boolean removeAll(Inventory invi) { amount = oldAmount; } } - + /** * Removes this type from the given inventory. Does not call updateInventory for players. - * + * * @param invi * @return Whether everything could be removed from the inventory */ public boolean removeFrom(Inventory invi) { ItemStack[] buf = getCopiedContents(invi); - + final boolean ok = removeFrom(Arrays.asList(buf)); - + invi.setContents(buf); return ok; } - + @SafeVarargs public final boolean removeAll(List<ItemStack>... lists) { + return removeAll(true, lists); + } + + + @SafeVarargs + public final boolean removeAll(boolean replaceWithNull, List<ItemStack>...lists) { final boolean wasAll = all; final int oldAmount = amount; all = true; amount = -1; try { - return removeFrom(lists); + return removeFrom(replaceWithNull, lists); } finally { all = wasAll; amount = oldAmount; } } - + /** - * @param lists The lists to remove this type from. Each list should implement {@link RandomAccess}. + * Removes this ItemType from given lists of ItemStacks. + * If an ItemStack is completely removed, that index in the list is set to null, instead of being removed. + * + * @param lists The lists to remove this type from. Each list should implement {@link RandomAccess}. Lists may contain null values after this method. * @return Whether this whole item type could be removed (i.e. returns false if the lists didn't contain this item type completely) */ @SafeVarargs public final boolean removeFrom(final List<ItemStack>... lists) { + return removeFrom(true, lists); + } + + /** + * Removes this ItemType from given lists of ItemStacks. + * If replaceWithNull is true, then if an ItemStack is completely removed, that index in the list is set to null, instead of being removed. + * + * @param replaceWithNull Whether to replace removed ItemStacks with null, or to remove them completely + * @param lists The lists to remove this type from. Each list should implement {@link RandomAccess}. Lists may contain null values after this method if replaceWithNull is true. + * @return Whether this whole item type could be removed (i.e. returns false if the lists didn't contain this item type completely) + */ + @SafeVarargs + public final boolean removeFrom(boolean replaceWithNull, List<ItemStack>... lists) { int removed = 0; boolean ok = true; - - for (final ItemData d : types) { + + for (ItemData d : types) { if (all) removed = 0; - for (final List<ItemStack> list : lists) { + for (List<ItemStack> list : lists) { if (list == null) continue; assert list instanceof RandomAccess; - for (int i = 0; i < list.size(); i++) { - final ItemStack is = list.get(i); + + Iterator<ItemStack> listIterator = list.iterator(); + int index = -1; // only reliable if replaceWithNull is true. Will be -1 if replaceWithNull is false. + while (listIterator.hasNext()) { + ItemStack is = listIterator.next(); + // index is only reliable if replaceWithNull is true + if (replaceWithNull) + index++; /* * Do NOT use equals()! It doesn't exactly match items * for historical reasons. This will change in future. - * + * * In Skript 2.3, equals() was used for getting closest * possible aliases for items. It was horribly hacky, and * is not done anymore. Still, some uses of equals() expect @@ -784,15 +811,22 @@ public final boolean removeFrom(final List<ItemStack>... lists) { boolean plain = d.isPlain() != other.isPlain(); if (d.matchPlain(other) || other.matchAlias(d).isAtLeast(plain ? MatchQuality.EXACT : (d.isAlias() && !other.isAlias() ? MatchQuality.SAME_MATERIAL : MatchQuality.SAME_ITEM))) { if (all && amount == -1) { - list.set(i, null); + if (replaceWithNull) { + list.set(index, null); + } else { + listIterator.remove(); + } removed = 1; continue; } - assert is != null; - final int toRemove = Math.min(is.getAmount(), getAmount() - removed); + int toRemove = Math.min(is.getAmount(), getAmount() - removed); removed += toRemove; if (toRemove == is.getAmount()) { - list.set(i, null); + if (replaceWithNull) { + list.set(index, null); + } else { + listIterator.remove(); + } } else { is.setAmount(is.getAmount() - toRemove); } @@ -807,15 +841,15 @@ public final boolean removeFrom(final List<ItemStack>... lists) { if (all) ok &= removed == getAmount(); } - + if (!all) return false; return ok; } - + /** * Adds this ItemType to the given list, without filling existing stacks. - * + * * @param list */ public void addTo(final List<ItemStack> list) { @@ -826,17 +860,17 @@ public void addTo(final List<ItemStack> list) { for (final ItemStack is : getItem().getAll()) list.add(is); } - + /** * Tries to add this ItemType to the given inventory. Does not call updateInventory for players. - * + * * @param invi * @return Whether everything could be added to the inventory */ public boolean addTo(final Inventory invi) { // important: don't use inventory.add() - it ignores max stack sizes ItemStack[] buf = invi.getContents(); - + ItemStack[] tBuf = buf.clone(); if (invi instanceof PlayerInventory) { buf = new ItemStack[36]; @@ -844,21 +878,21 @@ public boolean addTo(final Inventory invi) { buf[i] = tBuf[i]; } } - + final boolean b = addTo(buf); - + if (invi instanceof PlayerInventory) { buf = Arrays.copyOf(buf, tBuf.length); for (int i = tBuf.length - 5; i < tBuf.length; ++i) { buf[i] = tBuf[i]; } } - + assert buf != null; invi.setContents(buf); return b; } - + private static boolean addTo(@Nullable ItemStack is, ItemStack[] buf) { if (is == null || is.getType() == Material.AIR) return true; @@ -884,7 +918,7 @@ private static boolean addTo(@Nullable ItemStack is, ItemStack[] buf) { } return false; } - + public boolean addTo(final ItemStack[] buf) { if (!isAll()) { return addTo(getItem().getRandom(), buf); @@ -895,12 +929,12 @@ public boolean addTo(final ItemStack[] buf) { } return ok; } - + /** * Tests whether a given set of ItemTypes is a subset of another set of ItemTypes. * <p> * This method works differently that normal set operations, as is e.g. returns true if set == {everything}. - * + * * @param set * @param sub * @return Whether all item types in <tt>sub</tt> have at least one {@link #isSupertypeOf(ItemType) super type} in <tt>set</tt> @@ -916,7 +950,7 @@ public static boolean isSubset(final ItemType[] set, final ItemType[] sub) { } return true; } - + @Override public boolean equals(final @Nullable Object obj) { if (this == obj) @@ -973,7 +1007,7 @@ public boolean isSimilar(ItemType other) { } return false; } - + @Override public int hashCode() { final int prime = 31; @@ -983,21 +1017,21 @@ public int hashCode() { result = prime * result + types.hashCode(); return result; } - + @Override public String toString() { return toString(false, 0, null); } - + @Override public String toString(final int flags) { return toString(false, flags, null); } - + public String toString(final int flags, final @Nullable Adjective a) { return toString(false, flags, a); } - + private String toString(final boolean debug, final int flags, final @Nullable Adjective a) { final StringBuilder b = new StringBuilder(); // if (types.size() == 1 && !types.get(0).hasDataRange()) { @@ -1058,33 +1092,33 @@ private String toString(final boolean debug, final int flags, final @Nullable Ad // } return "" + b.toString(); } - + public static String toString(final ItemStack i) { return new ItemType(i).toString(); } - + public static String toString(final ItemStack i, final int flags) { return new ItemType(i).toString(flags); } - + public static String toString(Block b, int flags) { return new ItemType(b).toString(flags); } - + public String getDebugMessage() { return toString(true, 0, null); } - + @Override public Fields serialize() throws NotSerializableException { final Fields f = new Fields(this); return f; } - + @Override public void deserialize(final Fields fields) throws StreamCorruptedException, NotSerializableException { fields.setFields(this); - + // Legacy data (before aliases rework) update if (!types.isEmpty()) { @SuppressWarnings("rawtypes") @@ -1103,7 +1137,7 @@ public void deserialize(final Fields fields) throws StreamCorruptedException, No } } } - + /** * Gets raw item names ("minecraft:some_item"). If they are not available, * empty list will be returned. @@ -1117,10 +1151,10 @@ public List<String> getRawNames() { if (id != null) rawNames.add(id); } - + return rawNames; } - + /** * Gets all enchantments of this item. * @return Enchantments. @@ -1137,7 +1171,7 @@ public Map<Enchantment,Integer> getEnchantments() { return null; return enchants; } - + /** * Adds enchantments to this item type. * @param enchantments Enchantments. @@ -1152,7 +1186,7 @@ public void addEnchantments(Map<Enchantment,Integer> enchantments) { globalMeta.addEnchant(entry.getKey(), entry.getValue(), true); } } - + /** * Gets all enchantments of this item. * @return the enchantments of this item type. @@ -1160,7 +1194,7 @@ public void addEnchantments(Map<Enchantment,Integer> enchantments) { @Nullable public EnchantmentType[] getEnchantmentTypes() { Set<Entry<Enchantment, Integer>> enchants = getItemMeta().getEnchants().entrySet(); - + return enchants.stream() .map(enchant -> new EnchantmentType(enchant.getKey(), enchant.getValue())) .toArray(EnchantmentType[]::new); @@ -1182,14 +1216,14 @@ public EnchantmentType getEnchantmentType(Enchantment enchantment) { .findFirst() .orElse(null); } - + /** * Checks whether this item type has enchantments. */ public boolean hasEnchantments() { return getItemMeta().hasEnchants(); } - + /** * Checks whether this item type has the given enchantments. * @param enchantments the enchantments to be checked. @@ -1198,14 +1232,14 @@ public boolean hasEnchantments(Enchantment... enchantments) { if (!hasEnchantments()) return false; ItemMeta meta = getItemMeta(); - + for (Enchantment enchantment : enchantments) { if (!meta.hasEnchant(enchantment)) return false; } return true; } - + /** * Checks whether this item type contains at most one of the given enchantments. * @param enchantments The enchantments to be checked. @@ -1214,7 +1248,7 @@ public boolean hasAnyEnchantments(Enchantment... enchantments) { if (!hasEnchantments()) return false; ItemMeta meta = getItemMeta(); - + for (Enchantment enchantment : enchantments) { assert enchantment != null; if (meta.hasEnchant(enchantment)) @@ -1222,7 +1256,7 @@ public boolean hasAnyEnchantments(Enchantment... enchantments) { } return false; } - + /** * Checks whether this item type contains the given enchantments. * Also checks the enchantment level. @@ -1242,14 +1276,14 @@ public boolean hasEnchantments(EnchantmentType... enchantments) { } return true; } - + /** * Adds the given enchantments to the item type. * @param enchantments The enchantments to be added. */ public void addEnchantments(EnchantmentType... enchantments) { ItemMeta meta = getItemMeta(); - + for (EnchantmentType enchantment : enchantments) { Enchantment type = enchantment.getType(); assert type != null; // Bukkit working different than we expect @@ -1257,14 +1291,14 @@ public void addEnchantments(EnchantmentType... enchantments) { } setItemMeta(meta); } - + /** * Removes the given enchantments from this item type. * @param enchantments The enchantments to be removed. */ public void removeEnchantments(EnchantmentType... enchantments) { ItemMeta meta = getItemMeta(); - + for (EnchantmentType enchantment : enchantments) { Enchantment type = enchantment.getType(); assert type != null; // Bukkit working different than we expect @@ -1272,14 +1306,14 @@ public void removeEnchantments(EnchantmentType... enchantments) { } setItemMeta(meta); } - + /** * Clears all enchantments from this item type except the ones that are * defined for individual item datas only. */ public void clearEnchantments() { ItemMeta meta = getItemMeta(); - + Set<Enchantment> enchants = meta.getEnchants().keySet(); for (Enchantment ench : enchants) { assert ench != null; @@ -1287,7 +1321,7 @@ public void clearEnchantments() { } setItemMeta(meta); } - + /** * Gets item meta that applies to all items represented by this type. * @return Item meta. @@ -1303,13 +1337,13 @@ public ItemMeta getItemMeta() { */ public void setItemMeta(ItemMeta meta) { globalMeta = meta; - + // Apply new meta to all datas for (ItemData data : types) { data.setItemMeta(meta); } } - + /** * Clears item meta from this type. Metas which individual item dates may * have will not be touched. diff --git a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java index 70e6cdc038a..b90442fd8c1 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -66,6 +66,7 @@ import org.bukkit.event.player.PlayerQuitEvent.QuitReason; import org.bukkit.event.player.PlayerResourcePackStatusEvent.Status; import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; +import org.bukkit.inventory.BlockInventoryHolder; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; @@ -874,6 +875,10 @@ public String toString(InventoryHolder holder, int flags) { return Classes.toString(((BlockState) holder).getBlock()); } else if (holder instanceof DoubleChest) { return Classes.toString(holder.getInventory().getLocation().getBlock()); + } else if (holder instanceof BlockInventoryHolder) { + return Classes.toString(((BlockInventoryHolder) holder).getBlock()); + } else if (Classes.getSuperClassInfo(holder.getClass()).getC() == InventoryHolder.class) { + return holder.getClass().getSimpleName(); // an inventory holder and only that } else { return Classes.toString(holder); } @@ -1411,7 +1416,7 @@ public String toVariableNameString(FireworkEffect effect) { .user("(panda )?genes?") .name("Gene") .description("Represents a Panda's main or hidden gene. " + - "See <a href='https://minecraft.gamepedia.com/Panda#Genetics'>genetics</a> for more info.") + "See <a href='https://minecraft.wiki/w/Panda#Genetics'>genetics</a> for more info.") .since("2.4") .requiredPlugins("Minecraft 1.14 or newer")); } @@ -1487,7 +1492,7 @@ public String toVariableNameString(EnchantmentOffer eo) { .user("attribute ?types?") .name("Attribute Type") .description("Represents the type of an attribute. Note that this type does not contain any numerical values." - + "See <a href='https://minecraft.gamepedia.com/Attribute#Attributes'>attribute types</a> for more info.") + + "See <a href='https://minecraft.wiki/w/Attribute#Attributes'>attribute types</a> for more info.") .since("2.5")); Classes.registerClass(new EnumClassInfo<>(Environment.class, "environment", "environments") diff --git a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java index ace15fc3a5f..5477fd95a45 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -877,6 +877,30 @@ public Block get(PlayerMoveEvent event) { return event.getTo().clone().subtract(0, 0.5, 0).getBlock(); } }, EventValues.TIME_NOW); + EventValues.registerEventValue(PlayerMoveEvent.class, Location.class, new Getter<Location, PlayerMoveEvent>() { + @Override + public Location get(PlayerMoveEvent event) { + return event.getFrom(); + } + }, EventValues.TIME_PAST); + EventValues.registerEventValue(PlayerMoveEvent.class, Location.class, new Getter<Location, PlayerMoveEvent>() { + @Override + public Location get(PlayerMoveEvent event) { + return event.getTo(); + } + }, EventValues.TIME_NOW); + EventValues.registerEventValue(PlayerMoveEvent.class, Chunk.class, new Getter<Chunk, PlayerMoveEvent>() { + @Override + public Chunk get(PlayerMoveEvent event) { + return event.getFrom().getChunk(); + } + }, EventValues.TIME_PAST); + EventValues.registerEventValue(PlayerMoveEvent.class, Chunk.class, new Getter<Chunk, PlayerMoveEvent>() { + @Override + public Chunk get(PlayerMoveEvent event) { + return event.getTo().getChunk(); + } + }, EventValues.TIME_NOW); // PlayerItemDamageEvent EventValues.registerEventValue(PlayerItemDamageEvent.class, ItemStack.class, new Getter<ItemStack, PlayerItemDamageEvent>() { @Override @@ -1464,21 +1488,6 @@ public TeleportCause get(final PlayerTeleportEvent e) { return e.getCause(); } }, 0); - //PlayerMoveEvent - EventValues.registerEventValue(PlayerMoveEvent.class, Location.class, new Getter<Location, PlayerMoveEvent>() { - @Override - @Nullable - public Location get(PlayerMoveEvent e) { - return e.getFrom(); - } - }, EventValues.TIME_PAST); - EventValues.registerEventValue(PlayerMoveEvent.class, Location.class, new Getter<Location, PlayerMoveEvent>() { - @Override - @Nullable - public Location get(PlayerMoveEvent e) { - return e.getTo(); - } - }, EventValues.TIME_NOW); //EntityMoveEvent if (Skript.classExists("io.papermc.paper.event.entity.EntityMoveEvent")) { EventValues.registerEventValue(EntityMoveEvent.class, Location.class, new Getter<Location, EntityMoveEvent>() { diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java b/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java index 7f96bab14d8..e59a5678403 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java @@ -24,27 +24,26 @@ import ch.njol.skript.aliases.ItemData; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.classes.ClassInfo; -import org.skriptlang.skript.lang.comparator.Comparator; import ch.njol.skript.entity.BoatChestData; import ch.njol.skript.entity.BoatData; import ch.njol.skript.entity.EntityData; import ch.njol.skript.entity.RabbitData; -import org.skriptlang.skript.lang.comparator.Comparators; import ch.njol.skript.util.BlockUtils; import ch.njol.skript.util.Date; import ch.njol.skript.util.EnchantmentType; import ch.njol.skript.util.Experience; -import ch.njol.skript.util.WeatherType; import ch.njol.skript.util.GameruleValue; import ch.njol.skript.util.StructureType; import ch.njol.skript.util.Time; import ch.njol.skript.util.Timeperiod; import ch.njol.skript.util.Timespan; +import ch.njol.skript.util.WeatherType; import ch.njol.skript.util.slot.EquipmentSlot; import ch.njol.skript.util.slot.Slot; import ch.njol.skript.util.slot.SlotWithIndex; import ch.njol.util.StringUtils; import ch.njol.util.coll.CollectionUtils; +import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.OfflinePlayer; import org.bukkit.World; @@ -61,6 +60,8 @@ import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; +import org.skriptlang.skript.lang.comparator.Comparator; +import org.skriptlang.skript.lang.comparator.Comparators; import org.skriptlang.skript.lang.comparator.Relation; import java.util.Objects; @@ -630,6 +631,28 @@ public boolean supportsOrdering() { return false; } }); + + // Location - Location + Comparators.registerComparator(Location.class, Location.class, new Comparator<Location, Location>() { + @Override + public Relation compare(Location first, Location second) { + return Relation.get( + // compare worlds + Objects.equals(first.getWorld(), second.getWorld()) && + // compare xyz coords + first.toVector().equals(second.toVector()) && + // normalize yaw and pitch to [-180, 180) and [-90, 90] respectively + // before comparing them + Location.normalizeYaw(first.getYaw()) == Location.normalizeYaw(second.getYaw()) && + Location.normalizePitch(first.getPitch()) == Location.normalizePitch(second.getPitch()) + ); + } + + @Override + public boolean supportsOrdering() { + return false; + } + }); } } diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java b/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java index a8b2688e94e..8bd6dc906f8 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultConverters.java @@ -158,14 +158,14 @@ public DefaultConverters() {} if (holder instanceof DoubleChest) return holder.getInventory().getLocation().getBlock(); return null; - }); + }, Converter.NO_CHAINING); // InventoryHolder - Entity Converters.registerConverter(InventoryHolder.class, Entity.class, holder -> { if (holder instanceof Entity) return (Entity) holder; return null; - }); + }, Converter.NO_CHAINING); // Enchantment - EnchantmentType Converters.registerConverter(Enchantment.class, EnchantmentType.class, e -> new EnchantmentType(e, -1)); diff --git a/src/main/java/ch/njol/skript/command/CommandHelp.java b/src/main/java/ch/njol/skript/command/CommandHelp.java index 8fcf6f0a3b4..4732ba2cde5 100644 --- a/src/main/java/ch/njol/skript/command/CommandHelp.java +++ b/src/main/java/ch/njol/skript/command/CommandHelp.java @@ -23,6 +23,7 @@ import java.util.LinkedHashMap; import java.util.Locale; +import java.util.Map; import java.util.Map.Entry; import org.bukkit.command.CommandSender; @@ -33,118 +34,119 @@ import ch.njol.skript.localization.Message; import ch.njol.skript.util.SkriptColor; -/** - * @author Peter Güttinger - */ public class CommandHelp { - + private final static String DEFAULTENTRY = "description"; - + private final static ArgsMessage m_invalid_argument = new ArgsMessage("commands.invalid argument"); private final static Message m_usage = new Message("skript command.usage"); - + + private final String actualCommand, actualNode, argsColor; private String command; + private String langNode; @Nullable - private Message description = null; - private final String argsColor; - - @Nullable - private String langNode = null; - - private final LinkedHashMap<String, Object> arguments = new LinkedHashMap<>(); - + private Message description; + + private final Map<String, Object> arguments = new LinkedHashMap<>(); + @Nullable - private Message wildcardArg = null; - - public CommandHelp(final String command, final SkriptColor argsColor, final String langNode) { - this.command = command; - this.argsColor = "" + argsColor.getFormattedChat(); - this.langNode = langNode; - description = new Message(langNode + "." + DEFAULTENTRY); + private ArgumentHolder wildcardArg = null; + + public CommandHelp(String command, SkriptColor argsColor, String langNode) { + this(command, argsColor.getFormattedChat(), langNode, new Message(langNode + "." + DEFAULTENTRY)); } - - public CommandHelp(final String command, final SkriptColor argsColor) { - this.command = command; - this.argsColor = "" + argsColor.getFormattedChat(); + + public CommandHelp(String command, SkriptColor argsColor) { + this(command, argsColor.getFormattedChat(), command, null); } - - public CommandHelp add(final String argument) { - if (langNode == null) { - if (argument.startsWith("<") && argument.endsWith(">")) { - final String carg = GRAY + "<" + argsColor + argument.substring(1, argument.length() - 1) + GRAY + ">"; - arguments.put(carg, argument); - } else { - arguments.put(argument, null); - } - } else { - if (argument.startsWith("<") && argument.endsWith(">")) { - final String carg = GRAY + "<" + argsColor + argument.substring(1, argument.length() - 1) + GRAY + ">"; - wildcardArg = new Message(langNode + "." + argument); - arguments.put(carg, wildcardArg); - } else { - arguments.put(argument, new Message(langNode + "." + argument)); - } + + private CommandHelp(String command, String argsColor, String node, @Nullable Message description) { + this.actualCommand = this.command = command; + this.actualNode = this.langNode = node; + this.argsColor = argsColor; + this.description = description; + } + + public CommandHelp add(String argument) { + ArgumentHolder holder = new ArgumentHolder(argument); + if (argument.startsWith("<") && argument.endsWith(">")) { + argument = GRAY + "<" + argsColor + argument.substring(1, argument.length() - 1) + GRAY + ">"; + wildcardArg = holder; } + arguments.put(argument, holder); return this; } - - public CommandHelp add(final CommandHelp help) { + + public CommandHelp add(CommandHelp help) { arguments.put(help.command, help); help.onAdd(this); return this; } - - protected void onAdd(final CommandHelp parent) { - langNode = parent.langNode + "." + command; + + protected void onAdd(CommandHelp parent) { + langNode = parent.langNode + "." + actualNode; description = new Message(langNode + "." + DEFAULTENTRY); - command = parent.command + " " + parent.argsColor + command; - for (final Entry<String, Object> e : arguments.entrySet()) { - if (e.getValue() instanceof CommandHelp) { - ((CommandHelp) e.getValue()).onAdd(this); - } else { - if (e.getValue() != null) { // wildcard arg - wildcardArg = new Message(langNode + "." + e.getValue()); - e.setValue(wildcardArg); - } else { - e.setValue(new Message(langNode + "." + e.getKey())); - } + command = parent.command + " " + parent.argsColor + actualCommand; + for (Entry<String, Object> entry : arguments.entrySet()) { + if (entry.getValue() instanceof CommandHelp) { + ((CommandHelp) entry.getValue()).onAdd(this); + continue; } + ((ArgumentHolder) entry.getValue()).update(); } } - - public boolean test(final CommandSender sender, final String[] args) { + + public boolean test(CommandSender sender, String[] args) { return test(sender, args, 0); } - - private boolean test(final CommandSender sender, final String[] args, final int index) { + + private boolean test(CommandSender sender, String[] args, int index) { if (index >= args.length) { showHelp(sender); return false; } - final Object help = arguments.get(args[index].toLowerCase(Locale.ENGLISH)); + Object help = arguments.get(args[index].toLowerCase(Locale.ENGLISH)); if (help == null && wildcardArg == null) { showHelp(sender, m_invalid_argument.toString(argsColor + args[index])); return false; } - if (help instanceof CommandHelp) - return ((CommandHelp) help).test(sender, args, index + 1); - return true; + return !(help instanceof CommandHelp) || ((CommandHelp) help).test(sender, args, index + 1); } - - public void showHelp(final CommandSender sender) { + + public void showHelp(CommandSender sender) { showHelp(sender, m_usage.toString()); } - - private void showHelp(final CommandSender sender, final String pre) { + + private void showHelp(CommandSender sender, String pre) { Skript.message(sender, pre + " " + command + " " + argsColor + "..."); - for (final Entry<String, Object> e : arguments.entrySet()) { - Skript.message(sender, " " + argsColor + e.getKey() + " " + GRAY + "-" + RESET + " " + e.getValue()); - } + for (Entry<String, Object> entry : arguments.entrySet()) + Skript.message(sender, " " + argsColor + entry.getKey() + " " + GRAY + "-" + RESET + " " + entry.getValue()); } - + @Override public String toString() { return "" + description; } - + + private class ArgumentHolder { + + private final String argument; + private Message description; + + private ArgumentHolder(String argument) { + this.argument = argument; + this.description = new Message(langNode + "." + argument); + } + + private void update() { + description = new Message(langNode + "." + argument); + } + + @Override + public String toString() { + return description.toString(); + } + + } + } diff --git a/src/main/java/ch/njol/skript/command/Commands.java b/src/main/java/ch/njol/skript/command/Commands.java index 35e12057bb1..66427affa0e 100644 --- a/src/main/java/ch/njol/skript/command/Commands.java +++ b/src/main/java/ch/njol/skript/command/Commands.java @@ -41,6 +41,7 @@ import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.event.server.ServerCommandEvent; import org.bukkit.help.HelpMap; import org.bukkit.help.HelpTopic; @@ -68,7 +69,7 @@ */ @SuppressWarnings("deprecation") public abstract class Commands { - + public final static ArgsMessage m_too_many_arguments = new ArgsMessage("commands.too many arguments"); public final static Message m_internal_error = new Message("commands.internal error"); public final static Message m_correct_usage = new Message("commands.correct usage"); @@ -79,26 +80,26 @@ public abstract class Commands { public static final int CONVERTER_NO_COMMAND_ARGUMENTS = 4; private final static Map<String, ScriptCommand> commands = new HashMap<>(); - + @Nullable private static SimpleCommandMap commandMap = null; @Nullable private static Map<String, Command> cmKnownCommands; @Nullable private static Set<String> cmAliases; - + static { init(); // separate method for the annotation } public static Set<String> getScriptCommands(){ return commands.keySet(); } - + @Nullable public static SimpleCommandMap getCommandMap(){ return commandMap; } - + @SuppressWarnings("unchecked") private static void init() { try { @@ -106,11 +107,11 @@ private static void init() { Field commandMapField = SimplePluginManager.class.getDeclaredField("commandMap"); commandMapField.setAccessible(true); commandMap = (SimpleCommandMap) commandMapField.get(Bukkit.getPluginManager()); - + Field knownCommandsField = SimpleCommandMap.class.getDeclaredField("knownCommands"); knownCommandsField.setAccessible(true); cmKnownCommands = (Map<String, Command>) knownCommandsField.get(commandMap); - + try { Field aliasesField = SimpleCommandMap.class.getDeclaredField("aliases"); aliasesField.setAccessible(true); @@ -125,10 +126,10 @@ private static void init() { commandMap = null; } } - + @Nullable public static List<Argument<?>> currentArguments = null; - + @SuppressWarnings("null") private final static Pattern escape = Pattern.compile("[" + Pattern.quote("(|)<>%\\") + "]"); @SuppressWarnings("null") @@ -137,12 +138,30 @@ private static void init() { public static String escape(String string) { return "" + escape.matcher(string).replaceAll("\\\\$0"); } - + public static String unescape(String string) { return "" + unescape.matcher(string).replaceAll("$0"); } - + private final static Listener commandListener = new Listener() { + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onPlayerCommand(PlayerCommandPreprocessEvent event) { + // Spigot will simply report that the command doesn't exist if a player does not have permission to use it. + // This is good security but, well, it's a breaking change for Skript. So we need to check for permissions + // ourselves and handle those messages, for every command. + + // parse command, see if it's a skript command + String[] cmd = event.getMessage().substring(1).split("\\s+", 2); + String label = cmd[0].toLowerCase(Locale.ENGLISH); + String arguments = cmd.length == 1 ? "" : "" + cmd[1]; + ScriptCommand command = commands.get(label); + + // if so, check permissions + if (command != null && !command.checkPermissions(event.getPlayer(), label, arguments)) + event.setCancelled(true); + } + @SuppressWarnings("null") @EventHandler(priority = EventPriority.HIGHEST) public void onServerCommand(ServerCommandEvent event) { @@ -154,7 +173,7 @@ public void onServerCommand(ServerCommandEvent event) { } } }; - + static boolean handleEffectCommand(CommandSender sender, String command) { if (!(sender instanceof ConsoleCommandSender || sender.hasPermission("skript.effectcommands") || SkriptConfig.allowOpsToUseEffectCommands.value() && sender.isOp())) return false; @@ -170,7 +189,7 @@ static boolean handleEffectCommand(CommandSender sender, String command) { parserInstance.setCurrentEvent("effect command", EffectCommandEvent.class); Effect effect = Effect.parse(command, null); parserInstance.deleteCurrentEvent(); - + if (effect != null) { log.clear(); // ignore warnings and stuff log.printLog(); @@ -219,7 +238,7 @@ public static boolean scriptCommandExists(String command) { ScriptCommand scriptCommand = commands.get(command); return scriptCommand != null && scriptCommand.getName().equals(command); } - + public static void registerCommand(ScriptCommand command) { // Validate that there are no duplicates ScriptCommand existingCommand = commands.get(command.getLabel()); @@ -230,7 +249,7 @@ public static void registerCommand(ScriptCommand command) { ); return; } - + if (commandMap != null) { assert cmKnownCommands != null;// && cmAliases != null; command.register(commandMap, cmKnownCommands, cmAliases); @@ -262,9 +281,9 @@ public static void unregisterCommand(ScriptCommand scriptCommand) { } commands.values().removeIf(command -> command == scriptCommand); } - + private static boolean registeredListeners = false; - + public static void registerListeners() { if (!registeredListeners) { Bukkit.getPluginManager().registerEvents(commandListener, Skript.getInstance()); @@ -298,15 +317,15 @@ public void onPlayerChat(AsyncPlayerChatEvent event) { registeredListeners = true; } } - + /** * copied from CraftBukkit (org.bukkit.craftbukkit.help.CommandAliasHelpTopic) */ public static final class CommandAliasHelpTopic extends HelpTopic { - + private final String aliasFor; private final HelpMap helpMap; - + public CommandAliasHelpTopic(String alias, String aliasFor, HelpMap helpMap) { this.aliasFor = aliasFor.startsWith("/") ? aliasFor : "/" + aliasFor; this.helpMap = helpMap; @@ -314,7 +333,7 @@ public CommandAliasHelpTopic(String alias, String aliasFor, HelpMap helpMap) { Validate.isTrue(!name.equals(this.aliasFor), "Command " + name + " cannot be alias for itself"); shortText = ChatColor.YELLOW + "Alias for " + ChatColor.WHITE + this.aliasFor; } - + @Override @NotNull public String getFullText(CommandSender forWho) { @@ -326,7 +345,7 @@ public String getFullText(CommandSender forWho) { } return "" + fullText; } - + @Override public boolean canSee(CommandSender commandSender) { if (amendedPermission != null) @@ -335,5 +354,5 @@ public boolean canSee(CommandSender commandSender) { return aliasForTopic != null && aliasForTopic.canSee(commandSender); } } - + } diff --git a/src/main/java/ch/njol/skript/command/ScriptCommand.java b/src/main/java/ch/njol/skript/command/ScriptCommand.java index 2ce33ac7152..05d01680cc3 100644 --- a/src/main/java/ch/njol/skript/command/ScriptCommand.java +++ b/src/main/java/ch/njol/skript/command/ScriptCommand.java @@ -117,7 +117,7 @@ public class ScriptCommand implements TabExecutor { /** * Creates a new SkriptCommand. - * + * * @param name /name * @param pattern * @param arguments the list of Arguments this command takes @@ -237,16 +237,8 @@ public boolean execute(final CommandSender sender, final String commandLabel, fi final ScriptCommandEvent event = new ScriptCommandEvent(ScriptCommand.this, sender, commandLabel, rest); - if (!permission.isEmpty() && !sender.hasPermission(permission)) { - if (sender instanceof Player) { - List<MessageComponent> components = - permissionMessage.getMessageComponents(event); - ((Player) sender).spigot().sendMessage(BungeeConverter.convert(components)); - } else { - sender.sendMessage(permissionMessage.getSingle(event)); - } + if (!checkPermissions(sender, event)) return false; - } cooldownCheck : { if (sender instanceof Player && cooldown != null) { @@ -316,19 +308,37 @@ boolean execute2(final ScriptCommandEvent event, final CommandSender sender, fin } finally { log.stop(); } - + if (Skript.log(Verbosity.VERY_HIGH)) Skript.info("# /" + name + " " + rest); final long startTrigger = System.nanoTime(); - + if (!trigger.execute(event)) sender.sendMessage(Commands.m_internal_error.toString()); - + if (Skript.log(Verbosity.VERY_HIGH)) Skript.info("# " + name + " took " + 1. * (System.nanoTime() - startTrigger) / 1000000. + " milliseconds"); return true; } + public boolean checkPermissions(CommandSender sender, String commandLabel, String arguments) { + return checkPermissions(sender, new ScriptCommandEvent(this, sender, commandLabel, arguments)); + } + + public boolean checkPermissions(CommandSender sender, Event event) { + if (!permission.isEmpty() && !sender.hasPermission(permission)) { + if (sender instanceof Player) { + List<MessageComponent> components = + permissionMessage.getMessageComponents(event); + ((Player) sender).spigot().sendMessage(BungeeConverter.convert(components)); + } else { + sender.sendMessage(permissionMessage.getSingle(event)); + } + return false; + } + return true; + } + public void sendHelp(final CommandSender sender) { if (!description.isEmpty()) sender.sendMessage(description); @@ -337,7 +347,7 @@ public void sendHelp(final CommandSender sender) { /** * Gets the arguments this command takes. - * + * * @return The internal list of arguments. Do not modify it! */ public List<Argument<?>> getArguments() { @@ -575,7 +585,7 @@ public List<String> onTabComplete(@Nullable CommandSender sender, @Nullable Comm Class<?> argType = arg.getType(); if (argType.equals(Player.class) || argType.equals(OfflinePlayer.class)) return null; // Default completion - + return Collections.emptyList(); // No tab completion here! } diff --git a/src/main/java/ch/njol/skript/conditions/CondCompare.java b/src/main/java/ch/njol/skript/conditions/CondCompare.java index 707f2626e83..4e0917faa6d 100644 --- a/src/main/java/ch/njol/skript/conditions/CondCompare.java +++ b/src/main/java/ch/njol/skript/conditions/CondCompare.java @@ -18,6 +18,7 @@ */ package ch.njol.skript.conditions; +import ch.njol.skript.util.Time; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @@ -52,6 +53,7 @@ import ch.njol.skript.util.Utils; import ch.njol.util.Checker; import ch.njol.util.Kleenean; +import org.skriptlang.skript.lang.util.Cyclical; @Name("Comparison") @Description({"A very general condition, it simply compares two values. Usually you can only compare for equality (e.g. block is/isn't of <type>), " + @@ -351,15 +353,29 @@ public boolean check(final Event e) { return third.check(e, (Checker<Object>) o3 -> { boolean isBetween; if (comparator != null) { - isBetween = - (Relation.GREATER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o2)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o3))) - // Check OPPOSITE (switching o2 / o3) - || (Relation.GREATER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o3)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o2))); + if (o1 instanceof Cyclical<?> && o2 instanceof Cyclical<?> && o3 instanceof Cyclical<?>) { + if (Relation.GREATER_OR_EQUAL.isImpliedBy(comparator.compare(o2, o3))) + isBetween = Relation.GREATER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o2)) || Relation.SMALLER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o3)); + else + isBetween = Relation.GREATER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o2)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o3)); + } else { + isBetween = + (Relation.GREATER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o2)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o3))) + // Check OPPOSITE (switching o2 / o3) + || (Relation.GREATER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o3)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(comparator.compare(o1, o2))); + } } else { - isBetween = - (Relation.GREATER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o2)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o3))) - // Check OPPOSITE (switching o2 / o3) - || (Relation.GREATER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o3)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o2))); + if (o1 instanceof Cyclical<?> && o2 instanceof Cyclical<?> && o3 instanceof Cyclical<?>) { + if (Relation.GREATER_OR_EQUAL.isImpliedBy(Comparators.compare(o2, o3))) + isBetween = Relation.GREATER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o2)) || Relation.SMALLER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o3)); + else + isBetween = Relation.GREATER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o2)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o3)); + } else { + isBetween = + (Relation.GREATER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o2)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o3))) + // Check OPPOSITE (switching o2 / o3) + || (Relation.GREATER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o3)) && Relation.SMALLER_OR_EQUAL.isImpliedBy(Comparators.compare(o1, o2))); + } } return relation == Relation.NOT_EQUAL ^ isBetween; }); diff --git a/src/main/java/ch/njol/skript/conditions/CondIsSlimeChunk.java b/src/main/java/ch/njol/skript/conditions/CondIsSlimeChunk.java index 71dbbea2dcc..1b71258482b 100755 --- a/src/main/java/ch/njol/skript/conditions/CondIsSlimeChunk.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsSlimeChunk.java @@ -35,7 +35,7 @@ @Name("Is Slime Chunk") @Description({"Tests whether a chunk is a so-called slime chunk.", "Slimes can generally spawn in the swamp biome and in slime chunks.", - "For more info, see <a href='https://minecraft.gamepedia.com/Slime#.22Slime_chunks.22'>the Minecraft wiki</a>."}) + "For more info, see <a href='https://minecraft.wiki/w/Slime#.22Slime_chunks.22'>the Minecraft wiki</a>."}) @Examples({"command /slimey:", "\ttrigger:", "\t\tif chunk at player is a slime chunk:", diff --git a/src/main/java/ch/njol/skript/test/runner/CondMinecraftVersion.java b/src/main/java/ch/njol/skript/conditions/CondMinecraftVersion.java similarity index 96% rename from src/main/java/ch/njol/skript/test/runner/CondMinecraftVersion.java rename to src/main/java/ch/njol/skript/conditions/CondMinecraftVersion.java index 24a47479a49..e2888c0cf80 100644 --- a/src/main/java/ch/njol/skript/test/runner/CondMinecraftVersion.java +++ b/src/main/java/ch/njol/skript/conditions/CondMinecraftVersion.java @@ -16,9 +16,8 @@ * * Copyright Peter Güttinger, SkriptLang team and contributors */ -package ch.njol.skript.test.runner; +package ch.njol.skript.conditions; -import ch.njol.skript.doc.NoDoc; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @@ -37,7 +36,6 @@ @Description("Checks if current Minecraft version is given version or newer.") @Examples("running minecraft \"1.14\"") @Since("2.5") -@NoDoc public class CondMinecraftVersion extends Condition { static { diff --git a/src/main/java/ch/njol/skript/effects/EffExplosion.java b/src/main/java/ch/njol/skript/effects/EffExplosion.java index 4f0de85dd86..192119a54d6 100644 --- a/src/main/java/ch/njol/skript/effects/EffExplosion.java +++ b/src/main/java/ch/njol/skript/effects/EffExplosion.java @@ -37,7 +37,7 @@ * @author Peter Güttinger */ @Name("Explosion") -@Description({"Creates an explosion of a given force. The Minecraft Wiki has an <a href='https://www.minecraftwiki.net/wiki/Explosion'>article on explosions</a> " + +@Description({"Creates an explosion of a given force. The Minecraft Wiki has an <a href='https://www.minecraft.wiki/w/Explosion'>article on explosions</a> " + "which lists the explosion forces of TNT, creepers, etc.", "Hint: use a force of 0 to create a fake explosion that does no damage whatsoever, or use the explosion effect introduced in Skript 2.0.", "Starting with Bukkit 1.4.5 and Skript 2.0 you can use safe explosions which will damage entities but won't destroy any blocks."}) diff --git a/src/main/java/ch/njol/skript/effects/EffScriptFile.java b/src/main/java/ch/njol/skript/effects/EffScriptFile.java index 0a38111c5ce..ed15f625d2f 100644 --- a/src/main/java/ch/njol/skript/effects/EffScriptFile.java +++ b/src/main/java/ch/njol/skript/effects/EffScriptFile.java @@ -37,6 +37,7 @@ import java.io.File; import java.io.IOException; +import java.util.Set; @Name("Enable/Disable/Reload Script File") @Description("Enables, disables, or reloads a script file.") @@ -102,10 +103,8 @@ protected void execute(Event e) { case RELOAD: { if (ScriptLoader.getDisabledScriptsFilter().accept(scriptFile)) return; - - Script script = ScriptLoader.getScript(scriptFile); - if (script != null) - ScriptLoader.unloadScript(script); + + this.unloadScripts(scriptFile); ScriptLoader.loadScripts(scriptFile, OpenCloseable.EMPTY); break; @@ -114,9 +113,7 @@ protected void execute(Event e) { if (ScriptLoader.getDisabledScriptsFilter().accept(scriptFile)) return; - Script script = ScriptLoader.getScript(scriptFile); - if (script != null) - ScriptLoader.unloadScript(script); + this.unloadScripts(scriptFile); try { FileUtils.move( @@ -135,6 +132,19 @@ protected void execute(Event e) { assert false; } } + + private void unloadScripts(File file) { + if (file.isDirectory()) { + Set<Script> scripts = ScriptLoader.getScripts(file); + if (scripts.isEmpty()) + return; + ScriptLoader.unloadScripts(scripts); + } else { + Script script = ScriptLoader.getScript(file); + if (script != null) + ScriptLoader.unloadScript(script); + } + } @Override public String toString(@Nullable Event e, boolean debug) { diff --git a/src/main/java/ch/njol/skript/effects/EffStopSound.java b/src/main/java/ch/njol/skript/effects/EffStopSound.java index fe0af9ef26c..dc39449a06a 100644 --- a/src/main/java/ch/njol/skript/effects/EffStopSound.java +++ b/src/main/java/ch/njol/skript/effects/EffStopSound.java @@ -80,14 +80,9 @@ public class EffStopSound extends Effect { @SuppressWarnings("unchecked") public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { allSounds = parseResult.hasTag("all"); - if (allSounds) { - category = (Expression<SoundCategory>) exprs[0]; - players = (Expression<Player>) exprs[1]; - } else { - sounds = (Expression<String>) exprs[0]; - category = (Expression<SoundCategory>) exprs[1]; - players = (Expression<Player>) exprs[2]; - } + sounds = (Expression<String>) exprs[0]; + category = (Expression<SoundCategory>) exprs[1]; + players = (Expression<Player>) exprs[2]; return true; } diff --git a/src/main/java/ch/njol/skript/effects/EffVectorRotateAroundAnother.java b/src/main/java/ch/njol/skript/effects/EffVectorRotateAroundAnother.java index ad94cfa9ad2..10b83986f4d 100644 --- a/src/main/java/ch/njol/skript/effects/EffVectorRotateAroundAnother.java +++ b/src/main/java/ch/njol/skript/effects/EffVectorRotateAroundAnother.java @@ -33,12 +33,9 @@ import ch.njol.util.Kleenean; import ch.njol.util.VectorMath; -/** - * @author bi0qaw - */ @Name("Vectors - Rotate Around Vector") -@Description("Rotates a vector around another vector") -@Examples({"rotate {_v} around vector 1, 0, 0 by 90"}) +@Description("Rotates one or more vectors around another vector") +@Examples("rotate {_v} around vector 1, 0, 0 by 90") @Since("2.2-dev28") public class EffVectorRotateAroundAnother extends Effect { @@ -47,7 +44,7 @@ public class EffVectorRotateAroundAnother extends Effect { } @SuppressWarnings("null") - private Expression<Vector> first, second; + private Expression<Vector> vectors, axis; @SuppressWarnings("null") private Expression<Number> degree; @@ -55,26 +52,26 @@ public class EffVectorRotateAroundAnother extends Effect { @SuppressWarnings({"unchecked", "null"}) @Override public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean kleenean, ParseResult parseResult) { - first = (Expression<Vector>) exprs[0]; - second = (Expression<Vector>) exprs[1]; + vectors = (Expression<Vector>) exprs[0]; + axis = (Expression<Vector>) exprs[1]; degree = (Expression<Number>) exprs[2]; return true; } @SuppressWarnings("null") @Override - protected void execute(Event e) { - Vector v2 = second.getSingle(e); - Number d = degree.getSingle(e); - if (v2 == null || d == null) + protected void execute(Event event) { + Vector axis = this.axis.getSingle(event); + Number angle = degree.getSingle(event); + if (axis == null || angle == null) return; - for (Vector v1 : first.getArray(e)) - VectorMath.rot(v1, v2, d.doubleValue()); + for (Vector vector : vectors.getArray(event)) + VectorMath.rot(vector, axis, angle.doubleValue()); } @Override - public String toString(@Nullable Event e, boolean debug) { - return "rotate " + first.toString(e, debug) + " around " + second.toString(e, debug) + " by " + degree + "degrees"; + public String toString(@Nullable Event event, boolean debug) { + return "rotate " + vectors.toString(event, debug) + " around " + axis.toString(event, debug) + " by " + degree.toString(event, debug) + "degrees"; } } diff --git a/src/main/java/ch/njol/skript/effects/EffVectorRotateXYZ.java b/src/main/java/ch/njol/skript/effects/EffVectorRotateXYZ.java index 2c3cd5d3d94..6e24d2882bb 100644 --- a/src/main/java/ch/njol/skript/effects/EffVectorRotateXYZ.java +++ b/src/main/java/ch/njol/skript/effects/EffVectorRotateXYZ.java @@ -33,19 +33,18 @@ import ch.njol.util.Kleenean; import ch.njol.util.VectorMath; -/** - * @author bi0qaw - */ @Name("Vectors - Rotate around XYZ") -@Description("Rotates a vector around x, y, or z axis by some degrees") -@Examples({"rotate {_v} around x-axis by 90", - "rotate {_v} around y-axis by 90", - "rotate {_v} around z-axis by 90 degrees"}) +@Description("Rotates one or more vectors around the x, y, or z axis by some amount of degrees") +@Examples({ + "rotate {_v} around x-axis by 90", + "rotate {_v} around y-axis by 90", + "rotate {_v} around z-axis by 90 degrees" +}) @Since("2.2-dev28") public class EffVectorRotateXYZ extends Effect { static { - Skript.registerEffect(EffVectorRotateXYZ.class, "rotate %vectors% around (1¦x|2¦y|3¦z)(-| )axis by %number% [degrees]"); + Skript.registerEffect(EffVectorRotateXYZ.class, "rotate %vectors% around (0¦x|1¦y|2¦z)(-| )axis by %number% [degrees]"); } private final static Character[] axes = new Character[] {'x', 'y', 'z'}; @@ -68,28 +67,28 @@ public boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean is @Override @SuppressWarnings("null") - protected void execute(Event e) { - Number d = degree.getSingle(e); - if (d == null) + protected void execute(Event event) { + Number angle = degree.getSingle(event); + if (angle == null) return; switch (axis) { + case 0: + for (Vector vector : vectors.getArray(event)) + VectorMath.rotX(vector, angle.doubleValue()); + break; case 1: - for (Vector v : vectors.getArray(e)) - VectorMath.rotX(v, d.doubleValue()); + for (Vector vector : vectors.getArray(event)) + VectorMath.rotY(vector, angle.doubleValue()); break; case 2: - for (Vector v : vectors.getArray(e)) - VectorMath.rotY(v, d.doubleValue()); - break; - case 3: - for (Vector v : vectors.getArray(e)) - VectorMath.rotZ(v, d.doubleValue()); + for (Vector vector : vectors.getArray(event)) + VectorMath.rotZ(vector, angle.doubleValue()); } } @Override - public String toString(@Nullable Event e, boolean debug) { - return "rotate " + vectors.toString(e, debug) + " around " + axes[axis] + "-axis" + " by " + degree + "degrees"; + public String toString(@Nullable Event event, boolean debug) { + return "rotate " + vectors.toString(event, debug) + " around " + axes[axis] + "-axis" + " by " + degree.toString(event, debug) + "degrees"; } } diff --git a/src/main/java/ch/njol/skript/entity/EntityData.java b/src/main/java/ch/njol/skript/entity/EntityData.java index d49668a8af3..ae02901b769 100644 --- a/src/main/java/ch/njol/skript/entity/EntityData.java +++ b/src/main/java/ch/njol/skript/entity/EntityData.java @@ -18,6 +18,28 @@ */ package ch.njol.skript.entity; +import java.io.NotSerializableException; +import java.io.StreamCorruptedException; +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.function.Consumer; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.RegionAccessor; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.eclipse.jdt.annotation.Nullable; + import ch.njol.skript.Skript; import ch.njol.skript.SkriptAPIException; import ch.njol.skript.bukkitutil.EntityUtils; @@ -44,37 +66,40 @@ import ch.njol.util.coll.iterator.SingleItemIterator; import ch.njol.yggdrasil.Fields; import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilExtendedSerializable; -import org.bukkit.Bukkit; -import org.bukkit.Chunk; -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; -import org.bukkit.util.Consumer; -import org.eclipse.jdt.annotation.Nullable; -import java.io.NotSerializableException; -import java.io.StreamCorruptedException; -import java.lang.reflect.Array; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -/** - * @author Peter Güttinger - */ @SuppressWarnings("rawtypes") public abstract class EntityData<E extends Entity> implements SyntaxElement, YggdrasilExtendedSerializable {// TODO extended horse support, zombie villagers // REMIND unit + /* + * In 1.20.2 Spigot deprecated org.bukkit.util.Consumer. + * From the class header: "API methods which use this consumer will be remapped to Java's consumer at runtime, resulting in an error." + * But in 1.13-1.16 the only way to use a consumer was World#spawn(Location, Class, org.bukkit.util.Consumer). + */ + @Nullable + protected static Method WORLD_1_13_CONSUMER_METHOD; + protected static final boolean WORLD_1_13_CONSUMER = Skript.methodExists(World.class, "spawn", Location.class, Class.class, org.bukkit.util.Consumer.class); + + @Nullable + protected static Method WORLD_1_17_CONSUMER_METHOD; + protected static boolean WORLD_1_17_CONSUMER; + + static { + try { + if (WORLD_1_13_CONSUMER) { + WORLD_1_13_CONSUMER_METHOD = World.class.getDeclaredMethod("spawn", Location.class, Class.class, org.bukkit.util.Consumer.class); + } else if (Skript.classExists("org.bukkit.RegionAccessor")) { + if (WORLD_1_17_CONSUMER = Skript.methodExists(RegionAccessor.class, "spawn", Location.class, Class.class, org.bukkit.util.Consumer.class)) + WORLD_1_17_CONSUMER_METHOD = RegionAccessor.class.getDeclaredMethod("spawn", Location.class, Class.class, org.bukkit.util.Consumer.class); + } + } catch (NoSuchMethodException | SecurityException ignored) { /* We already checked if the method exists */ } + } + public final static String LANGUAGE_NODE = "entities"; - + public final static Message m_age_pattern = new Message(LANGUAGE_NODE + ".age pattern"); public final static Adjective m_baby = new Adjective(LANGUAGE_NODE + ".age adjectives.baby"), m_adult = new Adjective(LANGUAGE_NODE + ".age adjectives.adult"); - + // must be here to be initialised before 'new SimpleLiteral' is called in the register block below private final static List<EntityDataInfo<EntityData<?>>> infos = new ArrayList<>(); @@ -312,7 +337,7 @@ public final boolean init(final Expression<?>[] exprs, final int matchedPattern, /** * Returns the super type of this entity data, e.g. 'wolf' for 'angry wolf'. - * + * * @return The supertype of this entity data. Must not be null. */ public abstract EntityData getSuperType(); @@ -401,7 +426,7 @@ public static EntityDataInfo<?> getInfo(final String codeName) { /** * Prints errors. - * + * * @param s String with optional indefinite article at the beginning * @return The parsed entity data */ @@ -416,11 +441,10 @@ public static EntityData<?> parse(String s) { /** * Prints errors. - * + * * @param s * @return The parsed entity data */ - @SuppressWarnings("null") @Nullable public static EntityData<?> parseWithoutIndefiniteArticle(String s) { if (!REGEX_PATTERN.matcher(s).matches()) @@ -428,11 +452,6 @@ public static EntityData<?> parseWithoutIndefiniteArticle(String s) { Iterator<EntityDataInfo<EntityData<?>>> it = infos.iterator(); return SkriptParser.parseStatic(s, it, null); } - - @Nullable - public final E spawn(Location loc) { - return spawn(loc, null); - } private E apply(E entity) { if (baby.isTrue()) { @@ -444,24 +463,54 @@ private E apply(E entity) { return entity; } + /** + * Spawn this entity data at a location. + * + * @param location The {@link Location} to spawn the entity at. + * @return The Entity object that is spawned. + */ + @Nullable + public final E spawn(Location location) { + return spawn(location, (Consumer<E>) null); + } + + /** + * Spawn this entity data at a location. + * The consumer allows for modiciation to the entity before it actually gets spawned. + * <p> + * Bukkit's own {@link org.bukkit.util.Consumer} is deprecated. + * Use {@link #spawn(Location, Consumer)} + * + * @param location The {@link Location} to spawn the entity at. + * @param consumer A {@link Consumer} to apply the entity changes to. + * @return The Entity object that is spawned. + */ + @Nullable + @Deprecated + @SuppressWarnings("deprecation") + public E spawn(Location location, org.bukkit.util.@Nullable Consumer<E> consumer) { + return spawn(location, (Consumer<E>) e -> consumer.accept(e)); + } + + /** + * Spawn this entity data at a location. + * The consumer allows for modiciation to the entity before it actually gets spawned. + * + * @param location The {@link Location} to spawn the entity at. + * @param consumer A {@link Consumer} to apply the entity changes to. + * @return The Entity object that is spawned. + */ @Nullable - @SuppressWarnings("unchecked") public E spawn(Location location, @Nullable Consumer<E> consumer) { assert location != null; - try { - if (consumer != null) { - return location.getWorld().spawn(location, (Class<E>) getType(), e -> consumer.accept(apply(e))); - } else { - return apply(location.getWorld().spawn(location, getType())); - } - } catch (IllegalArgumentException e) { - if (Skript.testing()) - Skript.error("Can't spawn " + getType().getName()); - return null; + if (consumer != null) { + return EntityData.spawn(location, getType(), e -> consumer.accept(this.apply(e))); + } else { + return apply(location.getWorld().spawn(location, getType())); } } - - @SuppressWarnings({"null", "unchecked"}) + + @SuppressWarnings("unchecked") public E[] getAll(final World... worlds) { assert worlds != null && worlds.length > 0 : Arrays.toString(worlds); final List<E> list = new ArrayList<>(); @@ -598,4 +647,22 @@ protected boolean deserialize(final String s) { return false; } + @SuppressWarnings({"unchecked", "deprecation"}) + protected static <E extends Entity> @Nullable E spawn(Location location, Class<E> type, Consumer<E> consumer) { + try { + if (WORLD_1_17_CONSUMER) { + return (@Nullable E) WORLD_1_17_CONSUMER_METHOD.invoke(location.getWorld(), location, type, + (org.bukkit.util.Consumer<E>) consumer::accept); + } else if (WORLD_1_13_CONSUMER) { + return (@Nullable E) WORLD_1_13_CONSUMER_METHOD.invoke(location.getWorld(), location, type, + (org.bukkit.util.Consumer<E>) consumer::accept); + } + } catch (InvocationTargetException | IllegalAccessException e) { + if (Skript.testing()) + Skript.exception(e, "Can't spawn " + type.getName()); + return null; + } + return location.getWorld().spawn(location, type, consumer); + } + } diff --git a/src/main/java/ch/njol/skript/entity/FallingBlockData.java b/src/main/java/ch/njol/skript/entity/FallingBlockData.java index b3dcfbbcadf..2f937148fe9 100644 --- a/src/main/java/ch/njol/skript/entity/FallingBlockData.java +++ b/src/main/java/ch/njol/skript/entity/FallingBlockData.java @@ -19,12 +19,13 @@ package ch.njol.skript.entity; import java.util.Arrays; + import java.util.Iterator; +import java.util.function.Consumer; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.entity.FallingBlock; -import org.bukkit.util.Consumer; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; @@ -42,9 +43,6 @@ import org.skriptlang.skript.lang.converter.Converters; import ch.njol.util.coll.CollectionUtils; -/** - * @author Peter Güttinger - */ public class FallingBlockData extends EntityData<FallingBlock> { static { EntityData.register(FallingBlockData.class, "falling block", FallingBlock.class, "falling block"); @@ -117,11 +115,11 @@ public FallingBlock spawn(Location loc, @Nullable Consumer<FallingBlock> consume ItemType t = types == null ? new ItemType(Material.STONE) : CollectionUtils.getRandom(types); assert t != null; Material material = t.getMaterial(); - if (!material.isBlock()) { assert false : t; return null; } + FallingBlock fallingBlock = loc.getWorld().spawnFallingBlock(loc, material.createBlockData()); if (consumer != null) consumer.accept(fallingBlock); diff --git a/src/main/java/ch/njol/skript/entity/ThrownPotionData.java b/src/main/java/ch/njol/skript/entity/ThrownPotionData.java index 2f60014a310..407460d56c5 100644 --- a/src/main/java/ch/njol/skript/entity/ThrownPotionData.java +++ b/src/main/java/ch/njol/skript/entity/ThrownPotionData.java @@ -19,13 +19,13 @@ package ch.njol.skript.entity; import java.util.Arrays; +import java.util.function.Consumer; import org.bukkit.Location; import org.bukkit.entity.LingeringPotion; import org.bukkit.entity.ThrownPotion; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.util.Consumer; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; @@ -40,9 +40,6 @@ import org.skriptlang.skript.lang.converter.Converters; import ch.njol.util.coll.CollectionUtils; -/** - * @author Peter Güttinger - */ public class ThrownPotionData extends EntityData<ThrownPotion> { static { EntityData.register(ThrownPotionData.class, "thrown potion", ThrownPotion.class, "thrown potion"); @@ -103,9 +100,9 @@ protected boolean match(ThrownPotion entity) { return true; } - @SuppressWarnings({"unchecked", "rawtypes"}) @Override - public @Nullable ThrownPotion spawn(Location loc, @Nullable Consumer<ThrownPotion> consumer) { + @SuppressWarnings({"unchecked", "rawtypes"}) + public @Nullable ThrownPotion spawn(Location location, @Nullable Consumer<ThrownPotion> consumer) { ItemType t = CollectionUtils.getRandom(types); assert t != null; ItemStack i = t.getRandom(); @@ -114,11 +111,14 @@ protected boolean match(ThrownPotion entity) { Class<ThrownPotion> thrownPotionClass = (Class) (LINGER_POTION.isOfType(i) ? LINGERING_POTION_ENTITY_CLASS : ThrownPotion.class); ThrownPotion potion; - if (consumer != null) - potion = loc.getWorld().spawn(loc, thrownPotionClass, consumer); - else - potion = loc.getWorld().spawn(loc, thrownPotionClass); + if (consumer != null) { + potion = EntityData.spawn(location, thrownPotionClass, consumer); + } else { + potion = location.getWorld().spawn(location, thrownPotionClass); + } + if (potion == null) + return null; potion.setItem(i); return potion; } diff --git a/src/main/java/ch/njol/skript/entity/XpOrbData.java b/src/main/java/ch/njol/skript/entity/XpOrbData.java index 8582d513c33..69aa7d8c9f2 100644 --- a/src/main/java/ch/njol/skript/entity/XpOrbData.java +++ b/src/main/java/ch/njol/skript/entity/XpOrbData.java @@ -18,18 +18,16 @@ */ package ch.njol.skript.entity; +import java.util.function.Consumer; + import org.bukkit.Location; import org.bukkit.entity.ExperienceOrb; -import org.bukkit.util.Consumer; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.localization.ArgsMessage; -/** - * @author Peter Güttinger - */ public class XpOrbData extends EntityData<ExperienceOrb> { static { EntityData.register(XpOrbData.class, "xporb", ExperienceOrb.class, "xp-orb"); diff --git a/src/main/java/ch/njol/skript/events/EvtPlayerChunkEnter.java b/src/main/java/ch/njol/skript/events/EvtPlayerChunkEnter.java index bd4f59eb7c3..0507cefeb35 100644 --- a/src/main/java/ch/njol/skript/events/EvtPlayerChunkEnter.java +++ b/src/main/java/ch/njol/skript/events/EvtPlayerChunkEnter.java @@ -22,11 +22,9 @@ import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptEvent; import ch.njol.skript.lang.SkriptParser.ParseResult; - import org.bukkit.event.Event; import org.bukkit.event.player.PlayerMoveEvent; - -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; public class EvtPlayerChunkEnter extends SkriptEvent { @@ -46,7 +44,8 @@ public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResu @Override public boolean check(Event event) { - return ((PlayerMoveEvent) event).getFrom().getChunk() != ((PlayerMoveEvent) event).getTo().getChunk(); + PlayerMoveEvent moveEvent = ((PlayerMoveEvent) event); + return !moveEvent.getFrom().getChunk().equals(moveEvent.getTo().getChunk()); } @Override diff --git a/src/main/java/ch/njol/skript/expressions/ExprAge.java b/src/main/java/ch/njol/skript/expressions/ExprAge.java index 2a29300beb0..1be159daf08 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprAge.java +++ b/src/main/java/ch/njol/skript/expressions/ExprAge.java @@ -119,7 +119,7 @@ public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { setAge(obj, newValue); break; case RESET: - // baby animals takes 20 minutes to grow up - ref: https://minecraft.fandom.com/wiki/Breeding + // baby animals takes 20 minutes to grow up - ref: https://minecraft.wiki/w/Breeding if (obj instanceof org.bukkit.entity.Ageable) // it might change later on so removing entity age reset would be better unless // bukkit adds a method returning the default age diff --git a/src/main/java/ch/njol/skript/expressions/ExprDrops.java b/src/main/java/ch/njol/skript/expressions/ExprDrops.java index 89ae11cb8a5..85f3f423a22 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprDrops.java +++ b/src/main/java/ch/njol/skript/expressions/ExprDrops.java @@ -18,14 +18,6 @@ */ package ch.njol.skript.expressions; -import java.util.List; - -import org.bukkit.event.Event; -import org.bukkit.event.entity.EntityDeathEvent; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.classes.Changer.ChangeMode; @@ -42,7 +34,14 @@ import ch.njol.skript.util.Experience; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -import ch.njol.util.coll.iterator.IteratorIterable; +import org.bukkit.event.Event; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.ArrayList; +import java.util.List; /** * @author Peter Güttinger @@ -85,7 +84,7 @@ protected ItemType[] get(Event e) { @Nullable public Class<?>[] acceptChange(ChangeMode mode) { if (getParser().getHasDelayBefore().isTrue()) { - Skript.error("Can't change the drops anymore after the event has already passed"); + Skript.error("Can't change the drops after the event has already passed"); return null; } switch (mode) { @@ -94,7 +93,7 @@ public Class<?>[] acceptChange(ChangeMode mode) { case REMOVE_ALL: case SET: return CollectionUtils.array(ItemType[].class, Inventory[].class, Experience[].class); - case DELETE: + case DELETE: // handled by EffClearDrops case RESET: default: return null; @@ -102,58 +101,94 @@ public Class<?>[] acceptChange(ChangeMode mode) { } @Override - public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { - if (!(e instanceof EntityDeathEvent)) + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + if (!(event instanceof EntityDeathEvent)) return; - List<ItemStack> drops = ((EntityDeathEvent) e).getDrops(); + List<ItemStack> drops = ((EntityDeathEvent) event).getDrops(); + int originalExperience = ((EntityDeathEvent) event).getDroppedExp(); assert delta != null; + + // separate the delta into experience and drops to make it easier to handle + int deltaExperience = -1; // Skript does not support negative experience, so -1 is a safe "unset" value + boolean removeAllExperience = false; + List<ItemType> deltaDrops = new ArrayList<>(); for (Object o : delta) { if (o instanceof Experience) { - if (mode == ChangeMode.REMOVE_ALL || mode == ChangeMode.REMOVE && ((Experience) o).getInternalXP() == -1) { - ((EntityDeathEvent) e).setDroppedExp(0); - } else if (mode == ChangeMode.SET) { - ((EntityDeathEvent) e).setDroppedExp(((Experience) o).getXP()); + // Special case for `remove xp from the drops` + if ((((Experience) o).getInternalXP() == -1 && mode == ChangeMode.REMOVE) || mode == ChangeMode.REMOVE_ALL) { + removeAllExperience = true; + } + // add the value even if we're removing all experience, just so we know that experience was changed + if (deltaExperience == -1) { + deltaExperience = ((Experience) o).getXP(); } else { - ((EntityDeathEvent) e).setDroppedExp(Math.max(0, ((EntityDeathEvent) e).getDroppedExp() + (mode == ChangeMode.ADD ? 1 : -1) * ((Experience) o).getXP())); + deltaExperience += ((Experience) o).getXP(); } - } else { - switch (mode) { - case SET: - drops.clear(); - //$FALL-THROUGH$ - case ADD: - if (o instanceof Inventory) { - for (ItemStack is : new IteratorIterable<>(((Inventory) o).iterator())) { - if (is != null) - drops.add(is); - } - } else { - ((ItemType) o).addTo(drops); - } - break; - case REMOVE: - case REMOVE_ALL: - if (o instanceof Inventory) { - for (ItemStack is : new IteratorIterable<>(((Inventory) o).iterator())) { - if (is == null) - continue; - if (mode == ChangeMode.REMOVE) - new ItemType(is).removeFrom(drops); - else - new ItemType(is).removeAll(drops); - } - } else { - if (mode == ChangeMode.REMOVE) - ((ItemType) o).removeFrom(drops); - else - ((ItemType) o).removeAll(drops); - } - break; - case DELETE: - case RESET: - assert false; + } else if (o instanceof Inventory) { + // inventories are unrolled into their contents + for (ItemStack item : ((Inventory) o).getContents()) { + if (item != null) + deltaDrops.add(new ItemType(item)); } + } else if (o instanceof ItemType) { + deltaDrops.add((ItemType) o); + } else { + assert false; + } + } + + // handle items and experience separately to maintain current behavior + // `set drops to iron sword` should not affect experience + // and `set drops to 1 xp` should not affect items + // todo: All the experience stuff should be removed from this class for 2.8 and given to ExprExperience + + // handle experience + if (deltaExperience > -1) { + switch (mode) { + case SET: + ((EntityDeathEvent) event).setDroppedExp(deltaExperience); + break; + case ADD: + ((EntityDeathEvent) event).setDroppedExp(originalExperience + deltaExperience); + break; + case REMOVE: + ((EntityDeathEvent) event).setDroppedExp(originalExperience - deltaExperience); + // fallthrough to check for removeAllExperience + case REMOVE_ALL: + if (removeAllExperience) + ((EntityDeathEvent) event).setDroppedExp(0); + break; + case DELETE: + case RESET: + assert false; + } + } + + // handle items + if (!deltaDrops.isEmpty()) { + switch (mode) { + case SET: + // clear drops and fallthrough to add + drops.clear(); + case ADD: + for (ItemType item : deltaDrops) { + item.addTo(drops); + } + break; + case REMOVE: + for (ItemType item : deltaDrops) { + item.removeFrom(false, drops); + } + break; + case REMOVE_ALL: + for (ItemType item : deltaDrops) { + item.removeAll(false, drops); + } + break; + case DELETE: + case RESET: + assert false; } } } @@ -169,7 +204,7 @@ public Class<? extends ItemType> getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { return "the drops"; } diff --git a/src/main/java/ch/njol/skript/expressions/ExprDurability.java b/src/main/java/ch/njol/skript/expressions/ExprDurability.java index f592fd882f3..e946cdb02ec 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprDurability.java +++ b/src/main/java/ch/njol/skript/expressions/ExprDurability.java @@ -21,12 +21,14 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; +import org.bukkit.Material; import org.bukkit.event.Event; import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.Damageable; +import org.bukkit.inventory.meta.ItemMeta; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.aliases.ItemType; -import ch.njol.skript.bukkitutil.ItemUtils; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; @@ -44,33 +46,28 @@ "set durability of player's held item to 0" }) @Since("1.2, 2.7 (durability reversed)") -public class ExprDurability extends SimplePropertyExpression<Object, Long> { +public class ExprDurability extends SimplePropertyExpression<Object, Integer> { private boolean durability; static { - register(ExprDurability.class, Long.class, "(damage[s] [value[s]]|durability:durabilit(y|ies))", "itemtypes/slots"); + register(ExprDurability.class, Integer.class, "(damage[s] [value[s]]|1:durabilit(y|ies))", "itemtypes/slots"); } @Override public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - durability = parseResult.hasTag("durability"); + durability = parseResult.mark == 1; return super.init(exprs, matchedPattern, isDelayed, parseResult); } @Override @Nullable - public Long convert(Object object) { - ItemStack itemStack = null; - if (object instanceof Slot) { - itemStack = ((Slot) object).getItem(); - } else if (object instanceof ItemType) { - itemStack = ((ItemType) object).getRandom(); - } - if (itemStack == null) + public Integer convert(Object object) { + ItemType itemType = asItemType(object); + if (itemType == null) return null; - long damage = ItemUtils.getDamage(itemStack); - return durability ? itemStack.getType().getMaxDurability() - damage : damage; + ItemMeta meta = itemType.getItemMeta(); + return meta instanceof Damageable ? convertToDamage(itemType.getMaterial(), ((Damageable) meta).getDamage()) : 0; } @Override @@ -89,57 +86,52 @@ public Class<?>[] acceptChange(ChangeMode mode) { @Override public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { - int i = delta == null ? 0 : ((Number) delta[0]).intValue(); - Object[] objects = getExpr().getArray(event); - for (Object object : objects) { - ItemStack itemStack = null; - - if (object instanceof ItemType) { - itemStack = ((ItemType) object).getRandom(); - } else if (object instanceof Slot) { - itemStack = ((Slot) object).getItem(); - } - if (itemStack == null) - return; - - int changeValue = ItemUtils.getDamage(itemStack); - if (durability) - changeValue = itemStack.getType().getMaxDurability() - changeValue; - + int change = delta == null ? 0 : ((Number) delta[0]).intValue(); + if (mode == ChangeMode.REMOVE) + change = -change; + for (Object object : getExpr().getArray(event)) { + ItemType itemType = asItemType(object); + if (itemType == null) + continue; + + ItemMeta meta = itemType.getItemMeta(); + if (!(meta instanceof Damageable)) + continue; + Damageable damageable = (Damageable) meta; + + Material material = itemType.getMaterial(); switch (mode) { - case REMOVE: - i = -i; - //$FALL-THROUGH$ case ADD: - changeValue += i; + case REMOVE: + int current = convertToDamage(material, damageable.getDamage()); + damageable.setDamage(convertToDamage(material, current + change)); break; case SET: - changeValue = i; + damageable.setDamage(convertToDamage(material, change)); break; case DELETE: case RESET: - changeValue = 0; - break; - case REMOVE_ALL: - assert false; + damageable.setDamage(0); } - if (durability && mode != ChangeMode.RESET && mode != ChangeMode.DELETE) - changeValue = itemStack.getType().getMaxDurability() - changeValue; - - if (object instanceof ItemType) { - ItemUtils.setDamage(itemStack, changeValue); - ((ItemType) object).setTo(new ItemType(itemStack)); - } else { - ItemUtils.setDamage(itemStack, changeValue); - ((Slot) object).setItem(itemStack); - } + itemType.setItemMeta(meta); + if (object instanceof Slot) + ((Slot) object).setItem(itemType.getRandom()); } } + private int convertToDamage(Material material, int value) { + if (!durability) + return value; + int maxDurability = material.getMaxDurability(); + if (maxDurability == 0) + return 0; + return maxDurability - value; + } + @Override - public Class<? extends Long> getReturnType() { - return Long.class; + public Class<? extends Integer> getReturnType() { + return Integer.class; } @Override @@ -147,4 +139,14 @@ public String getPropertyName() { return durability ? "durability" : "damage"; } + @Nullable + private static ItemType asItemType(Object object) { + if (object instanceof ItemType) + return (ItemType) object; + ItemStack itemStack = ((Slot) object).getItem(); + if (itemStack == null) + return null; + return new ItemType(itemStack); + } + } diff --git a/src/main/java/ch/njol/skript/expressions/ExprExplosionYield.java b/src/main/java/ch/njol/skript/expressions/ExprExplosionYield.java index eea0c5fdd0a..cb0227ec419 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprExplosionYield.java +++ b/src/main/java/ch/njol/skript/expressions/ExprExplosionYield.java @@ -40,7 +40,7 @@ @Name("Explosion Yield") @Description({"The yield of the explosion in an explosion prime event. This is how big the explosion is.", " When changing the yield, values less than 0 will be ignored.", - " Read <a href='https://minecraft.gamepedia.com/Explosion'>this wiki page</a> for more information"}) + " Read <a href='https://minecraft.wiki/w/Explosion'>this wiki page</a> for more information"}) @Examples({"on explosion prime:", "\tset the yield of the explosion to 10"}) @Events("explosion prime") diff --git a/src/main/java/ch/njol/skript/expressions/ExprExplosiveYield.java b/src/main/java/ch/njol/skript/expressions/ExprExplosiveYield.java index b66b2f4e60d..3fa35bc76c7 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprExplosiveYield.java +++ b/src/main/java/ch/njol/skript/expressions/ExprExplosiveYield.java @@ -36,7 +36,7 @@ @Name("Explosive Yield") @Description({"The yield of an explosive (creeper, primed tnt, fireball, etc.). This is how big of an explosion is caused by the entity.", - "Read <a href='https://minecraft.gamepedia.com/Explosion'>this wiki page</a> for more information"}) + "Read <a href='https://minecraft.wiki/w/Explosion'>this wiki page</a> for more information"}) @Examples({"on spawn of a creeper:", "\tset the explosive yield of the event-entity to 10"}) @RequiredPlugins("Minecraft 1.12 or newer for creepers") diff --git a/src/main/java/ch/njol/skript/expressions/ExprGlidingState.java b/src/main/java/ch/njol/skript/expressions/ExprGlidingState.java index 6334e1af835..901ff127079 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprGlidingState.java +++ b/src/main/java/ch/njol/skript/expressions/ExprGlidingState.java @@ -31,7 +31,7 @@ import ch.njol.skript.expressions.base.SimplePropertyExpression; @Name("Gliding State") -@Description("Sets of gets gliding state of player. It allows you to set gliding state of entity even if they do not have an <a href=\"https://minecraft.gamepedia.com/Elytra\">Elytra</a> equipped.") +@Description("Sets of gets gliding state of player. It allows you to set gliding state of entity even if they do not have an <a href=\"https://minecraft.wiki/w/Elytra\">Elytra</a> equipped.") @Examples({"set gliding of player to off"}) @Since("2.2-dev21") public class ExprGlidingState extends SimplePropertyExpression<LivingEntity, Boolean> { diff --git a/src/main/java/ch/njol/skript/expressions/ExprOnlinePlayersCount.java b/src/main/java/ch/njol/skript/expressions/ExprOnlinePlayersCount.java index ad4a6055125..5fd03a7e9d8 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprOnlinePlayersCount.java +++ b/src/main/java/ch/njol/skript/expressions/ExprOnlinePlayersCount.java @@ -18,18 +18,12 @@ */ package ch.njol.skript.expressions; -import org.bukkit.Bukkit; -import org.bukkit.event.Event; -import org.bukkit.event.server.ServerListPingEvent; -import org.eclipse.jdt.annotation.Nullable; - -import com.destroystokyo.paper.event.server.PaperServerListPingEvent; import ch.njol.skript.Skript; -import ch.njol.skript.bukkitutil.PlayerUtils; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; @@ -37,23 +31,31 @@ 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 org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.bukkit.event.server.ServerListPingEvent; +import org.eclipse.jdt.annotation.Nullable; @Name("Online Player Count") -@Description({"The amount of online players. This can be changed in a", +@Description({ + "The amount of online players. This can be changed in a " + "<a href='events.html#server_list_ping'>server list ping</a> event only to show fake online player amount.", - "'real online player count' always returns the real count of online players and can't be changed.", - "", - "Fake online player count requires PaperSpigot 1.12.2+."}) -@Examples({"on server list ping:", - " # This will make the max players count 5 if there are 4 players online.", - " set the fake max players count to (online players count + 1)"}) + "<code>real online player count</code> always return the real count of online players and can't be changed." +}) +@Examples({ + "on server list ping:", + "\t# This will make the max players count 5 if there are 4 players online.", + "\tset the fake max players count to (online player count + 1)" +}) +@RequiredPlugins("Paper (fake count)") @Since("2.3") public class ExprOnlinePlayersCount extends SimpleExpression<Long> { static { Skript.registerExpression(ExprOnlinePlayersCount.class, Long.class, ExpressionType.PROPERTY, - "[the] [(1¦(real|default)|2¦(fake|shown|displayed))] [online] player (count|amount|number)", - "[the] [(1¦(real|default)|2¦(fake|shown|displayed))] (count|amount|number|size) of online players"); + "[the] [(1:(real|default)|2:(fake|shown|displayed))] [online] player (count|amount|number)", + "[the] [(1:(real|default)|2:(fake|shown|displayed))] (count|amount|number|size) of online players"); } private static final boolean PAPER_EVENT_EXISTS = Skript.classExists("com.destroystokyo.paper.event.server.PaperServerListPingEvent"); @@ -64,7 +66,7 @@ public class ExprOnlinePlayersCount extends SimpleExpression<Long> { public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { boolean isPaperEvent = PAPER_EVENT_EXISTS && getParser().isCurrentEvent(PaperServerListPingEvent.class); if (parseResult.mark == 2) { - if (getParser().isCurrentEvent(ServerListPingEvent.class)) { + if (!PAPER_EVENT_EXISTS && getParser().isCurrentEvent(ServerListPingEvent.class)) { Skript.error("The 'fake' online players count expression requires Paper 1.12.2 or newer"); return false; } else if (!isPaperEvent) { @@ -146,4 +148,4 @@ public String toString(@Nullable Event e, boolean debug) { return "the count of " + (isReal ? "real max players" : "max players"); } -} \ No newline at end of file +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprScoreboardTags.java b/src/main/java/ch/njol/skript/expressions/ExprScoreboardTags.java index 14da4e073ca..47f4f4e5712 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprScoreboardTags.java +++ b/src/main/java/ch/njol/skript/expressions/ExprScoreboardTags.java @@ -41,7 +41,7 @@ @Name("Scoreboard Tags") @Description({"Scoreboard tags are simple list of texts stored directly in the data of an <a href='classes.html#entity'>entity</a>.", "So this is a Minecraft related thing, not Bukkit, so the tags will not get removed when the server stops. " + - "You can visit <a href='https://minecraft.gamepedia.com/Scoreboard#Tags'>visit Minecraft Wiki</a> for more info.", + "You can visit <a href='https://minecraft.wiki/w/Scoreboard#Tags'>visit Minecraft Wiki</a> for more info.", "This is changeable and valid for any type of entity. " + "Also you can use use the <a href='conditions.html#CondHasScoreboardTag'>Has Scoreboard Tag</a> condition to check whether an entity has the given tags.", "", diff --git a/src/main/java/ch/njol/skript/expressions/ExprSets.java b/src/main/java/ch/njol/skript/expressions/ExprSets.java index f5e3a48c538..4458c16bdf0 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprSets.java +++ b/src/main/java/ch/njol/skript/expressions/ExprSets.java @@ -18,6 +18,14 @@ */ package ch.njol.skript.expressions; +import java.util.Iterator; +import java.util.function.Supplier; + +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import com.google.common.collect.Lists; + import ch.njol.skript.Skript; import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.doc.Description; @@ -31,33 +39,26 @@ import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.skript.util.Utils; import ch.njol.util.Kleenean; -import com.google.common.collect.Lists; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - -import java.util.Iterator; -import java.util.List; -import java.util.function.Supplier; @Name("Sets") -@Description("Returns a list of all the values of a type; useful for looping.") +@Description("Returns a list of all the values of a type. Useful for looping.") @Examples({ "loop all attribute types:", - "\tset loop-value attribute of player to 10", - "\tmessage \"Set attribute %loop-value% to 10!\"" + "\tset loop-value attribute of player to 10", + "\tmessage \"Set attribute %loop-value% to 10!\"" }) -@Since("<i>unknown</i> (before 1.4.2), 2.7 (colors)") +// Class history rename order: LoopItems.class -> ExprItems.class (renamed in 2.3-alpha1) -> ExprSets.class (renamed in 2.7.0) +@Since("1.0 pre-5, 2.7 (classinfo)") public class ExprSets extends SimpleExpression<Object> { static { - Skript.registerExpression(ExprSets.class, Object.class, ExpressionType.COMBINED, - "[all [[of] the]|the|every] %*classinfo%" - ); + Skript.registerExpression(ExprSets.class, Object.class, ExpressionType.PATTERN_MATCHES_EVERYTHING, + "[all [[of] the]|the|every] %*classinfo%"); } - private ClassInfo<?> classInfo; @Nullable private Supplier<? extends Iterator<?>> supplier; + private ClassInfo<?> classInfo; @Override @SuppressWarnings("unchecked") @@ -78,16 +79,13 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override protected Object[] get(Event event) { - assert supplier != null; Iterator<?> iterator = supplier.get(); - List<?> elements = Lists.newArrayList(iterator); - return elements.toArray(new Object[0]); + return Lists.newArrayList(iterator).toArray(new Object[0]); } @Override @Nullable public Iterator<?> iterator(Event event) { - assert supplier != null; return supplier.get(); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprSortedList.java b/src/main/java/ch/njol/skript/expressions/ExprSortedList.java index a19779c0774..3cfed13b0a6 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprSortedList.java +++ b/src/main/java/ch/njol/skript/expressions/ExprSortedList.java @@ -32,19 +32,18 @@ import ch.njol.util.coll.CollectionUtils; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.comparator.Comparator; +import org.skriptlang.skript.lang.comparator.Comparators; import java.lang.reflect.Array; -import java.util.Arrays; @Name("Sorted List") -@Description({"Sorts given list in natural order. All objects in list must be comparable;", - "if they're not, this expression will return nothing." -}) -@Examples({"set {_sorted::*} to sorted {_players::*}"}) +@Description("Sorts given list in natural order. All objects in list must be comparable; if they're not, this expression will return nothing.") +@Examples("set {_sorted::*} to sorted {_players::*}") @Since("2.2-dev19") public class ExprSortedList extends SimpleExpression<Object> { - static{ + static { Skript.registerExpression(ExprSortedList.class, Object.class, ExpressionType.COMBINED, "sorted %objects%"); } @@ -67,29 +66,26 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable - protected Object[] get(Event e) { - Object[] unsorted = list.getArray(e); - Object[] sorted = (Object[]) Array.newInstance(getReturnType(), unsorted.length); // Not yet sorted... - - for (int i = 0; i < sorted.length; i++) { - Object value = unsorted[i]; - if (value instanceof Long) { - // Hope it fits to the double... - sorted[i] = (double) (Long) value; - } else { - // No conversion needed - sorted[i] = value; - } - } - + protected Object[] get(Event event) { try { - Arrays.sort(sorted); // Now sorted - } catch (IllegalArgumentException | ClassCastException ex) { // In case elements are not comparable - return new Object[]{}; // We don't have a sorted array available + return list.stream(event) + .sorted(ExprSortedList::compare) + .toArray(); + } catch (IllegalArgumentException | ClassCastException e) { + return (Object[]) Array.newInstance(getReturnType(), 0); } - return sorted; } + @SuppressWarnings("unchecked") + private static <A, B> int compare(A a, B b) throws IllegalArgumentException, ClassCastException { + Comparator<A, B> comparator = Comparators.getComparator((Class<A>) a.getClass(), (Class<B>) b.getClass()); + if (comparator != null && comparator.supportsOrdering()) + return comparator.compare(a, b).getRelation(); + if (!(a instanceof Comparable)) + throw new IllegalArgumentException(); + return ((Comparable<B>) a).compareTo(b); + } + @Override @Nullable @SuppressWarnings("unchecked") diff --git a/src/main/java/ch/njol/skript/expressions/ExprTimes.java b/src/main/java/ch/njol/skript/expressions/ExprTimes.java index 8c8cad634fa..170e00fd886 100755 --- a/src/main/java/ch/njol/skript/expressions/ExprTimes.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTimes.java @@ -107,8 +107,8 @@ public Iterator<? extends Long> iterator(final Event e) { Number end = this.end.getSingle(e); if (end == null) return null; - - return LongStream.range(1, end.longValue() + 1).iterator(); + long fixed = (long) (end.doubleValue() + Skript.EPSILON); + return LongStream.range(1, fixed + 1).iterator(); } @Override diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorAngleBetween.java b/src/main/java/ch/njol/skript/expressions/ExprVectorAngleBetween.java index 07d3cb2867d..1cdf3626c8e 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorAngleBetween.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorAngleBetween.java @@ -35,12 +35,9 @@ import ch.njol.util.VectorMath; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Angle Between") @Description("Gets the angle between two vectors.") -@Examples({"send \"%the angle between vector 1, 0, 0 and vector 0, 1, 1%\""}) +@Examples("send \"%the angle between vector 1, 0, 0 and vector 0, 1, 1%\"") @Since("2.2-dev28") public class ExprVectorAngleBetween extends SimpleExpression<Number> { @@ -62,12 +59,12 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") - protected Number[] get(Event e) { - Vector v1 = first.getSingle(e); - Vector v2 = second.getSingle(e); - if (v1 == null || v2 == null) + protected Number[] get(Event event) { + Vector first = this.first.getSingle(event); + Vector second = this.second.getSingle(event); + if (first == null || second == null) return null; - return CollectionUtils.array(v1.angle(v2) * (float) VectorMath.RAD_TO_DEG); + return CollectionUtils.array(first.angle(second) * (float) VectorMath.RAD_TO_DEG); } @Override @@ -81,8 +78,8 @@ public Class<? extends Number> getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return "the angle between " + first.toString(e, debug) + " and " + second.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "the angle between " + first.toString(event, debug) + " and " + second.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java b/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java index 1dc75efd574..312b1d15b32 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java @@ -35,46 +35,41 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Arithmetic") @Description("Arithmetic expressions for vectors.") -@Examples({"set {_v} to vector 1, 2, 3 // 5", - "set {_v} to {_v} ++ {_v}", - "set {_v} to {_v} ++ 5", - "set {_v} to {_v} -- {_v}", - "set {_v} to {_v} -- 5", - "set {_v} to {_v} ** {_v}", - "set {_v} to {_v} ** 5", - "set {_v} to {_v} // {_v}", - "set {_v} to {_v} // 5"}) +@Examples({ + "set {_v} to vector 1, 2, 3 // vector 5, 5, 5", + "set {_v} to {_v} ++ {_v}", + "set {_v} to {_v} -- {_v}", + "set {_v} to {_v} ** {_v}", + "set {_v} to {_v} // {_v}" +}) @Since("2.2-dev28") public class ExprVectorArithmetic extends SimpleExpression<Vector> { private enum Operator { PLUS("++") { @Override - public Vector calculate(final Vector v1, final Vector v2) { - return v1.clone().add(v2); + public Vector calculate(final Vector first, final Vector second) { + return first.clone().add(second); } }, MINUS("--") { @Override - public Vector calculate(final Vector v1, final Vector v2) { - return v1.clone().subtract(v2); + public Vector calculate(final Vector first, final Vector second) { + return first.clone().subtract(second); } }, MULT("**") { @Override - public Vector calculate(final Vector v1, final Vector v2) { - return v1.clone().multiply(v2); + public Vector calculate(final Vector first, final Vector second) { + return first.clone().multiply(second); } }, DIV("//") { @Override - public Vector calculate(final Vector v1, final Vector v2) { - return v1.clone().divide(v2); + public Vector calculate(final Vector first, final Vector second) { + return first.clone().divide(second); } }; @@ -84,7 +79,7 @@ public Vector calculate(final Vector v1, final Vector v2) { this.sign = sign; } - public abstract Vector calculate(Vector v1, Vector v2); + public abstract Vector calculate(Vector first, Vector second); @Override public String toString() { @@ -107,25 +102,22 @@ public String toString() { private Expression<Vector> first, second; @SuppressWarnings("null") - private Operator op; + private Operator operator; @Override @SuppressWarnings({"unchecked", "null"}) public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { first = (Expression<Vector>) exprs[0]; second = (Expression<Vector>) exprs[1]; - op = patterns.getInfo(matchedPattern); + operator = patterns.getInfo(matchedPattern); return true; } @Override - protected Vector[] get(Event e) { - Vector v1 = first.getSingle(e), v2 = second.getSingle(e); - if (v1 == null) - v1 = new Vector(); - if (v2 == null) - v2 = new Vector(); - return CollectionUtils.array(op.calculate(v1, v2)); + protected Vector[] get(Event event) { + Vector first = this.first.getOptionalSingle(event).orElse(new Vector()); + Vector second = this.second.getOptionalSingle(event).orElse(new Vector()); + return CollectionUtils.array(operator.calculate(first, second)); } @Override @@ -139,8 +131,8 @@ public Class<? extends Vector> getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return first.toString(e, debug) + " " + op + " " + second.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return first.toString(event, debug) + " " + operator + " " + second.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorBetweenLocations.java b/src/main/java/ch/njol/skript/expressions/ExprVectorBetweenLocations.java index 8b407c38dd2..75412c017b2 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorBetweenLocations.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorBetweenLocations.java @@ -35,12 +35,9 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Vector Between Locations") @Description("Creates a vector between two locations.") -@Examples({"set {_v} to vector between {_loc1} and {_loc2}"}) +@Examples("set {_v} to vector between {_loc1} and {_loc2}") @Since("2.2-dev28") public class ExprVectorBetweenLocations extends SimpleExpression<Vector> { @@ -62,12 +59,12 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") - protected Vector[] get(Event e) { - Location l1 = from.getSingle(e); - Location l2 = to.getSingle(e); - if (l1 == null || l2 == null) + protected Vector[] get(Event event) { + Location from = this.from.getSingle(event); + Location to = this.to.getSingle(event); + if (from == null || to == null) return null; - return CollectionUtils.array(new Vector(l2.getX() - l1.getX(), l2.getY() - l1.getY(), l2.getZ() - l1.getZ())); + return CollectionUtils.array(new Vector(to.getX() - from.getX(), to.getY() - from.getY(), to.getZ() - from.getZ())); } @Override @@ -80,8 +77,8 @@ public Class<? extends Vector> getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return "vector from " + from.toString(e, debug) + " to " + to.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "vector from " + from.toString(event, debug) + " to " + to.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorCrossProduct.java b/src/main/java/ch/njol/skript/expressions/ExprVectorCrossProduct.java index 11d040f2855..ae8ebe990e1 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorCrossProduct.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorCrossProduct.java @@ -34,12 +34,9 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Cross Product") @Description("Gets the cross product between two vectors.") -@Examples({"send \"%vector 1, 0, 0 cross vector 0, 1, 0%\""}) +@Examples("send \"%vector 1, 0, 0 cross vector 0, 1, 0%\"") @Since("2.2-dev28") public class ExprVectorCrossProduct extends SimpleExpression<Vector> { @@ -60,12 +57,12 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") - protected Vector[] get(Event e) { - Vector v1 = first.getSingle(e); - Vector v2 = second.getSingle(e); - if (v1 == null || v2 == null) + protected Vector[] get(Event event) { + Vector first = this.first.getSingle(event); + Vector second = this.second.getSingle(event); + if (first == null || second == null) return null; - return CollectionUtils.array(v1.clone().crossProduct(v2)); + return CollectionUtils.array(first.clone().crossProduct(second)); } @Override @@ -79,8 +76,8 @@ public Class<? extends Vector> getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return first.toString(e, debug) + " cross " + second.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return first.toString(event, debug) + " cross " + second.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorCylindrical.java b/src/main/java/ch/njol/skript/expressions/ExprVectorCylindrical.java index 65d4fc5134c..40cd9edd963 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorCylindrical.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorCylindrical.java @@ -35,14 +35,13 @@ import ch.njol.util.VectorMath; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Cylindrical Shape") @Description("Forms a 'cylindrical shaped' vector using yaw to manipulate the current point.") -@Examples({"loop 360 times:", - " set {_v} to cylindrical vector radius 1, yaw loop-value, height 2", - "set {_v} to cylindrical vector radius 1, yaw 90, height 2"}) +@Examples({ + "loop 360 times:", + "\tset {_v} to cylindrical vector radius 1, yaw loop-value, height 2", + "set {_v} to cylindrical vector radius 1, yaw 90, height 2" +}) @Since("2.2-dev28") public class ExprVectorCylindrical extends SimpleExpression<Vector> { @@ -65,13 +64,13 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") - protected Vector[] get(Event e) { - Number r = radius.getSingle(e); - Number y = yaw.getSingle(e); - Number h = height.getSingle(e); - if (r == null || y == null || h == null) + protected Vector[] get(Event event) { + Number radius = this.radius.getSingle(event); + Number yaw = this.yaw.getSingle(event); + Number height = this.height.getSingle(event); + if (radius == null || yaw == null || height == null) return null; - return CollectionUtils.array(VectorMath.fromCylindricalCoordinates(r.doubleValue(), VectorMath.fromSkriptYaw(y.floatValue()), h.doubleValue())); + return CollectionUtils.array(VectorMath.fromCylindricalCoordinates(radius.doubleValue(), VectorMath.fromSkriptYaw(yaw.floatValue()), height.doubleValue())); } @Override @@ -85,9 +84,9 @@ public Class<? extends Vector> getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return "cylindrical vector with radius " + radius.toString(e, debug) + ", yaw " + - yaw.toString(e, debug) + " and height " + height.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "cylindrical vector with radius " + radius.toString(event, debug) + ", yaw " + + yaw.toString(event, debug) + " and height " + height.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorDotProduct.java b/src/main/java/ch/njol/skript/expressions/ExprVectorDotProduct.java index d5eb0a5c911..50ef5e9ea15 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorDotProduct.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorDotProduct.java @@ -34,20 +34,10 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Dot Product") @Description("Gets the dot product between two vectors.") -@Examples({"set {_v} to {_v2} dot {_v3}"}) +@Examples("set {_dot} to {_v1} dot {_v2}") @Since("2.2-dev28") -/** - * NOTE vector 1, 2, 3 dot vector 1, 2, 3 does NOT work! - * it returns a new vector: 1, 2, 18. This should not happen - * and I have no idea why it does. I have also no idea why - * "z" takes the value 18. There must be some black magic - * going on. - */ public class ExprVectorDotProduct extends SimpleExpression<Number> { static { @@ -67,12 +57,12 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") - protected Number[] get(Event e) { - Vector v1 = first.getSingle(e); - Vector v2 = second.getSingle(e); - if (v1 == null || v2 == null) + protected Number[] get(Event event) { + Vector first = this.first.getSingle(event); + Vector second = this.second.getSingle(event); + if (first == null || second == null) return null; - return CollectionUtils.array(v1.getX() * v2.getX() + v1.getY() * v2.getY() + v1.getZ() * v2.getZ()); + return CollectionUtils.array(first.getX() * second.getX() + first.getY() * second.getY() + first.getZ() * second.getZ()); } @Override @@ -86,8 +76,8 @@ public Class<? extends Number> getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return first.toString(e, debug) + " dot " + second.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return first.toString(event, debug) + " dot " + second.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorFromXYZ.java b/src/main/java/ch/njol/skript/expressions/ExprVectorFromXYZ.java index 71896818d71..6b553e6f50b 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorFromXYZ.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorFromXYZ.java @@ -34,12 +34,9 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Create from XYZ") @Description("Creates a vector from x, y and z values.") -@Examples({"set {_v} to vector 0, 1, 0"}) +@Examples("set {_v} to vector 0, 1, 0") @Since("2.2-dev28") public class ExprVectorFromXYZ extends SimpleExpression<Vector> { @@ -62,10 +59,10 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") - protected Vector[] get(Event e) { - Number x = this.x.getSingle(e); - Number y = this.y.getSingle(e); - Number z = this.z.getSingle(e); + protected Vector[] get(Event event) { + Number x = this.x.getSingle(event); + Number y = this.y.getSingle(event); + Number z = this.z.getSingle(event); if (x == null || y == null || z == null) return null; return CollectionUtils.array(new Vector(x.doubleValue(), y.doubleValue(), z.doubleValue())); @@ -82,8 +79,8 @@ public Class<? extends Vector> getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return "vector from x " + x.toString(e, debug) + ", y " + y.toString(e, debug) + ", z " + z.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "vector from x " + x.toString(event, debug) + ", y " + y.toString(event, debug) + ", z " + z.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorFromYawAndPitch.java b/src/main/java/ch/njol/skript/expressions/ExprVectorFromYawAndPitch.java index 75a65274d0d..8f7179e7d26 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorFromYawAndPitch.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorFromYawAndPitch.java @@ -35,12 +35,9 @@ import ch.njol.util.VectorMath; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Vector from Pitch and Yaw") @Description("Creates a vector from a yaw and pitch value.") -@Examples({"set {_v} to vector from yaw 45 and pitch 45"}) +@Examples("set {_v} to vector from yaw 45 and pitch 45") @Since("2.2-dev28") public class ExprVectorFromYawAndPitch extends SimpleExpression<Vector> { @@ -62,13 +59,13 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") - protected Vector[] get(Event e) { - Number y = yaw.getSingle(e); - Number p = pitch.getSingle(e); - if (y == null || p == null) + protected Vector[] get(Event event) { + Number skriptYaw = yaw.getSingle(event); + Number skriptPitch = pitch.getSingle(event); + if (skriptYaw == null || skriptPitch == null) return null; - float yaw = VectorMath.fromSkriptYaw(VectorMath.wrapAngleDeg(y.floatValue())); - float pitch = VectorMath.fromSkriptPitch(VectorMath.wrapAngleDeg(p.floatValue())); + float yaw = VectorMath.fromSkriptYaw(VectorMath.wrapAngleDeg(skriptYaw.floatValue())); + float pitch = VectorMath.fromSkriptPitch(VectorMath.wrapAngleDeg(skriptPitch.floatValue())); return CollectionUtils.array(VectorMath.fromYawAndPitch(yaw, pitch)); } @@ -83,8 +80,8 @@ public Class<? extends Vector> getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return "vector from yaw " + yaw.toString(e, debug) + " and pitch " + pitch.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "vector from yaw " + yaw.toString(event, debug) + " and pitch " + pitch.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorLength.java b/src/main/java/ch/njol/skript/expressions/ExprVectorLength.java index 8c2d1094141..62f2a695e85 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorLength.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorLength.java @@ -30,15 +30,14 @@ import ch.njol.skript.expressions.base.SimplePropertyExpression; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Length") @Description("Gets or sets the length of a vector.") -@Examples({"send \"%standard length of vector 1, 2, 3%\"", - "set {_v} to vector 1, 2, 3", - "set standard length of {_v} to 2", - "send \"%standard length of {_v}%\""}) +@Examples({ + "send \"%standard length of vector 1, 2, 3%\"", + "set {_v} to vector 1, 2, 3", + "set standard length of {_v} to 2", + "send \"%standard length of {_v}%\"" +}) @Since("2.2-dev28") public class ExprVectorLength extends SimplePropertyExpression<Vector, Number> { @@ -61,43 +60,49 @@ public Class<?>[] acceptChange(ChangeMode mode) { } @Override - public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { assert delta != null; - final Vector v = getExpr().getSingle(e); - if (v == null) - return; - double n = ((Number) delta[0]).doubleValue(); + final Vector[] vectors = getExpr().getArray(event); + double deltaLength = ((Number) delta[0]).doubleValue(); switch (mode) { + case REMOVE: + deltaLength = -deltaLength; + //$FALL-THROUGH$ case ADD: - if (n < 0 && v.lengthSquared() < n * n) { - v.zero(); - } else { - double l = n + v.length(); - v.normalize().multiply(l); + for (Vector vector : vectors) { + if (deltaLength < 0 && vector.lengthSquared() < deltaLength * deltaLength) { + vector.zero(); + } else { + double newLength = deltaLength + vector.length(); + if (!vector.isNormalized()) + vector.normalize(); + vector.multiply(newLength); + } } - getExpr().change(e, new Vector[]{v}, ChangeMode.SET); break; - case REMOVE: - n = -n; - //$FALL-THROUGH$ case SET: - if (n < 0) - v.zero(); - else - v.normalize().multiply(n); - getExpr().change(e, new Vector[]{v}, ChangeMode.SET); + for (Vector vector : vectors) { + if (deltaLength < 0) { + vector.zero(); + } else { + if (!vector.isNormalized()) + vector.normalize(); + vector.multiply(deltaLength); + } + } break; } + getExpr().change(event, vectors, ChangeMode.SET); } @Override - protected String getPropertyName() { - return "vector length"; + public Class<? extends Number> getReturnType() { + return Number.class; } @Override - public Class<? extends Number> getReturnType() { - return Number.class; + protected String getPropertyName() { + return "vector length"; } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorNormalize.java b/src/main/java/ch/njol/skript/expressions/ExprVectorNormalize.java index c450c074ddd..00b80f7d109 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorNormalize.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorNormalize.java @@ -34,12 +34,9 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Normalized") @Description("Returns the same vector but with length 1.") -@Examples({"set {_v} to normalized {_v}"}) +@Examples("set {_v} to normalized {_v}") @Since("2.2-dev28") public class ExprVectorNormalize extends SimpleExpression<Vector> { @@ -61,11 +58,14 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") - protected Vector[] get(Event e) { - Vector v = vector.getSingle(e); - if (v == null) + protected Vector[] get(Event event) { + Vector vector = this.vector.getSingle(event); + if (vector == null) return null; - return CollectionUtils.array(v.clone().normalize()); + vector = vector.clone(); + if (!vector.isNormalized()) + vector.normalize(); + return CollectionUtils.array(vector); } @Override @@ -79,8 +79,8 @@ public Class<? extends Vector> getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return "normalized " + vector.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "normalized " + vector.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorOfLocation.java b/src/main/java/ch/njol/skript/expressions/ExprVectorOfLocation.java index 21f71a3bef8..e15974b78a7 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorOfLocation.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorOfLocation.java @@ -35,12 +35,9 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Vector from Location") @Description("Creates a vector from a location.") -@Examples({"set {_v} to vector of {_loc}"}) +@Examples("set {_v} to vector of {_loc}") @Since("2.2-dev28") public class ExprVectorOfLocation extends SimpleExpression<Vector> { @@ -62,11 +59,11 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") - protected Vector[] get(Event e) { - Location l = location.getSingle(e); - if (l == null) + protected Vector[] get(Event event) { + Location location = this.location.getSingle(event); + if (location == null) return null; - return CollectionUtils.array(l.toVector()); + return CollectionUtils.array(location.toVector()); } @Override @@ -80,8 +77,8 @@ public Class<? extends Vector> getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return "vector from " + location.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "vector from " + location.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorSpherical.java b/src/main/java/ch/njol/skript/expressions/ExprVectorSpherical.java index 493c023eb95..3507f8ab25a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorSpherical.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorSpherical.java @@ -35,14 +35,13 @@ import ch.njol.util.VectorMath; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - Spherical Shape") @Description("Forms a 'spherical shaped' vector using yaw and pitch to manipulate the current point.") -@Examples({"loop 360 times:", - " set {_v} to spherical vector radius 1, yaw loop-value, pitch loop-value", - "set {_v} to spherical vector radius 1, yaw 45, pitch 90"}) +@Examples({ + "loop 360 times:", + "\tset {_v} to spherical vector radius 1, yaw loop-value, pitch loop-value", + "set {_v} to spherical vector radius 1, yaw 45, pitch 90" +}) @Since("2.2-dev28") public class ExprVectorSpherical extends SimpleExpression<Vector> { @@ -65,13 +64,13 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @SuppressWarnings("null") - protected Vector[] get(Event e) { - Number r = radius.getSingle(e); - Number y = yaw.getSingle(e); - Number p = pitch.getSingle(e); - if (r == null || y == null || p == null) + protected Vector[] get(Event event) { + Number radius = this.radius.getSingle(event); + Number yaw = this.yaw.getSingle(event); + Number pitch = this.pitch.getSingle(event); + if (radius == null || yaw == null || pitch == null) return null; - return CollectionUtils.array(VectorMath.fromSphericalCoordinates(r.doubleValue(), VectorMath.fromSkriptYaw(y.floatValue()), p.floatValue() + 90)); + return CollectionUtils.array(VectorMath.fromSphericalCoordinates(radius.doubleValue(), VectorMath.fromSkriptYaw(yaw.floatValue()), pitch.floatValue() + 90)); } @Override @@ -85,9 +84,9 @@ public Class<? extends Vector> getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return "spherical vector with radius " + radius.toString(e, debug) + ", yaw " + yaw.toString(e, debug) + - " and pitch" + pitch.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return "spherical vector with radius " + radius.toString(event, debug) + ", yaw " + yaw.toString(event, debug) + + " and pitch" + pitch.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorSquaredLength.java b/src/main/java/ch/njol/skript/expressions/ExprVectorSquaredLength.java index ef5444347ca..bdfeeb33b74 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorSquaredLength.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorSquaredLength.java @@ -26,12 +26,9 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.SimplePropertyExpression; -/** - * @author bi0qaw - */ @Name("Vectors - Squared Length") @Description("Gets the squared length of a vector.") -@Examples({"send \"%squared length of vector 1, 2, 3%\""}) +@Examples("send \"%squared length of vector 1, 2, 3%\"") @Since("2.2-dev28") public class ExprVectorSquaredLength extends SimplePropertyExpression<Vector, Number> { @@ -46,13 +43,14 @@ public Number convert(Vector vector) { } @Override - protected String getPropertyName() { - return "squared length of vector"; + public Class<? extends Number> getReturnType() { + return Number.class; } @Override - public Class<? extends Number> getReturnType() { - return Number.class; + protected String getPropertyName() { + return "squared length of vector"; } + } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorXYZ.java b/src/main/java/ch/njol/skript/expressions/ExprVectorXYZ.java index 76b5ab59fe2..2955fef5b97 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorXYZ.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorXYZ.java @@ -34,21 +34,20 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -/** - * @author bi0qaw - */ @Name("Vectors - XYZ Component") @Description("Gets or changes the x, y or z component of a vector.") -@Examples({"set {_v} to vector 1, 2, 3", - "send \"%x of {_v}%, %y of {_v}%, %z of {_v}%\"", - "add 1 to x of {_v}", - "add 2 to y of {_v}", - "add 3 to z of {_v}", - "send \"%x of {_v}%, %y of {_v}%, %z of {_v}%\"", - "set x component of {_v} to 1", - "set y component of {_v} to 2", - "set z component of {_v} to 3", - "send \"%x component of {_v}%, %y component of {_v}%, %z component of {_v}%\"",}) +@Examples({ + "set {_v} to vector 1, 2, 3", + "send \"%x of {_v}%, %y of {_v}%, %z of {_v}%\"", + "add 1 to x of {_v}", + "add 2 to y of {_v}", + "add 3 to z of {_v}", + "send \"%x of {_v}%, %y of {_v}%, %z of {_v}%\"", + "set x component of {_v::*} to 1", + "set y component of {_v::*} to 2", + "set z component of {_v::*} to 3", + "send \"%x component of {_v::*}%, %y component of {_v::*}%, %z component of {_v::*}%\"" +}) @Since("2.2-dev28") public class ExprVectorXYZ extends SimplePropertyExpression<Vector, Number> { @@ -68,58 +67,62 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye } @Override - public Number convert(Vector v) { - return axis == 0 ? v.getX() : (axis == 1 ? v.getY() : v.getZ()); + public Number convert(Vector vector) { + return axis == 0 ? vector.getX() : (axis == 1 ? vector.getY() : vector.getZ()); } @Override @SuppressWarnings("null") public Class<?>[] acceptChange(ChangeMode mode) { if ((mode == ChangeMode.ADD || mode == ChangeMode.REMOVE || mode == ChangeMode.SET) - && getExpr().isSingle() && Changer.ChangerUtils.acceptsChange(getExpr(), ChangeMode.SET, Vector.class)) + && Changer.ChangerUtils.acceptsChange(getExpr(), ChangeMode.SET, Vector.class)) return CollectionUtils.array(Number.class); return null; } @Override - public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { assert delta != null; - final Vector v = getExpr().getSingle(e); - if (v == null) - return; - double n = ((Number) delta[0]).doubleValue(); + Vector[] vectors = getExpr().getArray(event); + double deltaValue = ((Number) delta[0]).doubleValue(); switch (mode) { case REMOVE: - n = -n; + deltaValue = -deltaValue; //$FALL-THROUGH$ case ADD: - if (axis == 0) - v.setX(v.getX() + n); - else if (axis == 1) - v.setY(v.getY() + n); - else - v.setZ(v.getZ() + n); - getExpr().change(e, new Vector[] {v}, ChangeMode.SET); + for (Vector vector : vectors) { + if (axis == 0) + vector.setX(vector.getX() + deltaValue); + else if (axis == 1) + vector.setY(vector.getY() + deltaValue); + else + vector.setZ(vector.getZ() + deltaValue); + } break; case SET: - if (axis == 0) - v.setX(n); - else if (axis == 1) - v.setY(n); - else - v.setZ(n); - getExpr().change(e, new Vector[] {v}, ChangeMode.SET); + for (Vector vector : vectors) { + if (axis == 0) + vector.setX(deltaValue); + else if (axis == 1) + vector.setY(deltaValue); + else + vector.setZ(deltaValue); + } + break; + default: + assert false; + return; } + getExpr().change(event, vectors, ChangeMode.SET); } - - @Override - protected String getPropertyName() { - return axes[axis] + " component"; - } - + @Override public Class<Number> getReturnType() { return Number.class; } - + + @Override + protected String getPropertyName() { + return axes[axis] + " component"; + } } diff --git a/src/main/java/ch/njol/skript/sections/EffSecSpawn.java b/src/main/java/ch/njol/skript/sections/EffSecSpawn.java index 878fe70d1db..320b4d63c39 100644 --- a/src/main/java/ch/njol/skript/sections/EffSecSpawn.java +++ b/src/main/java/ch/njol/skript/sections/EffSecSpawn.java @@ -18,6 +18,17 @@ */ package ch.njol.skript.sections; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; + +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + import ch.njol.skript.Skript; import ch.njol.skript.config.SectionNode; import ch.njol.skript.doc.Description; @@ -35,16 +46,6 @@ import ch.njol.skript.util.Getter; import ch.njol.skript.variables.Variables; import ch.njol.util.Kleenean; -import org.bukkit.Location; -import org.bukkit.entity.Entity; -import org.bukkit.event.Event; -import org.bukkit.event.HandlerList; -import org.bukkit.util.Consumer; -import org.eclipse.jdt.annotation.Nullable; -import org.jetbrains.annotations.NotNull; - -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; @Name("Spawn") @Description({ @@ -93,16 +94,18 @@ public Entity get(SpawnEvent spawnEvent) { }, EventValues.TIME_NOW); } - @Nullable - public static Entity lastSpawned = null; - @SuppressWarnings("NotNullFieldNotInitialized") private Expression<Location> locations; + @SuppressWarnings("NotNullFieldNotInitialized") private Expression<EntityType> types; + @Nullable private Expression<Number> amount; + @Nullable + public static Entity lastSpawned; + @Nullable private Trigger trigger; @@ -137,16 +140,15 @@ public boolean init(Expression<?>[] exprs, protected TriggerItem walk(Event event) { lastSpawned = null; - Object localVars = Variables.copyLocalVariables(event); - Consumer<? extends Entity> consumer; if (trigger != null) { consumer = o -> { lastSpawned = o; SpawnEvent spawnEvent = new SpawnEvent(o); // Copy the local variables from the calling code to this section - Variables.setLocalVariables(spawnEvent, localVars); + Variables.setLocalVariables(spawnEvent, Variables.copyLocalVariables(event)); TriggerItem.walk(trigger, spawnEvent); + // And copy our (possibly modified) local variables back to the calling code Variables.setLocalVariables(event, Variables.copyLocalVariables(spawnEvent)); // Clear spawnEvent's local variables as it won't be done automatically Variables.removeLocals(spawnEvent); diff --git a/src/main/java/ch/njol/skript/structures/StructFunction.java b/src/main/java/ch/njol/skript/structures/StructFunction.java index ee0fbf5f76e..003a149b8fc 100644 --- a/src/main/java/ch/njol/skript/structures/StructFunction.java +++ b/src/main/java/ch/njol/skript/structures/StructFunction.java @@ -18,6 +18,7 @@ */ package ch.njol.skript.structures; +import ch.njol.skript.ScriptLoader; import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; @@ -35,7 +36,8 @@ import org.skriptlang.skript.lang.structure.Structure; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.regex.MatchResult; +import java.util.regex.Matcher; +import java.util.regex.Pattern; @Name("Function") @Description({ @@ -58,38 +60,48 @@ public class StructFunction extends Structure { public static final Priority PRIORITY = new Priority(400); + private static final Pattern SIGNATURE_PATTERN = + Pattern.compile("(?:local )?function (" + Functions.functionNamePattern + ")\\((.*)\\)(?:\\s*(?:::| returns )\\s*(.+))?"); private static final AtomicBoolean VALIDATE_FUNCTIONS = new AtomicBoolean(); static { Skript.registerStructure(StructFunction.class, - "[:local] function <(" + Functions.functionNamePattern + ")\\((.*)\\)(?:\\s*(?:::| returns )\\s*(.+))?>" + "[:local] function <.+>" ); } - @SuppressWarnings("NotNullFieldNotInitialized") + @Nullable private Signature<?> signature; private boolean local; @Override - @SuppressWarnings("all") public boolean init(Literal<?>[] literals, int matchedPattern, ParseResult parseResult, EntryContainer entryContainer) { local = parseResult.hasTag("local"); - MatchResult regex = parseResult.regexes.get(0); - String name = regex.group(1); - String args = regex.group(2); - String returnType = regex.group(3); - - getParser().setCurrentEvent((local ? "local " : "") + "function", FunctionEvent.class); - - signature = Functions.parseSignature(getParser().getCurrentScript().getConfig().getFileName(), name, args, returnType, local); - - getParser().deleteCurrentEvent(); - return signature != null; + return true; } @Override public boolean preLoad() { - return Functions.registerSignature(signature) != null; + // match signature against pattern + String rawSignature = getEntryContainer().getSource().getKey(); + assert rawSignature != null; + rawSignature = ScriptLoader.replaceOptions(rawSignature); + Matcher matcher = SIGNATURE_PATTERN.matcher(rawSignature); + if (!matcher.matches()) { + Skript.error("Invalid function signature: " + rawSignature); + return false; + } + + // parse signature + getParser().setCurrentEvent((local ? "local " : "") + "function", FunctionEvent.class); + signature = Functions.parseSignature( + getParser().getCurrentScript().getConfig().getFileName(), + matcher.group(1), matcher.group(2), matcher.group(3), local + ); + getParser().deleteCurrentEvent(); + + // attempt registration + return signature != null && Functions.registerSignature(signature) != null; } @Override @@ -97,6 +109,7 @@ public boolean load() { ParserInstance parser = getParser(); parser.setCurrentEvent((local ? "local " : "") + "function", FunctionEvent.class); + assert signature != null; Functions.loadFunction(parser.getCurrentScript(), getEntryContainer().getSource(), signature); parser.deleteCurrentEvent(); @@ -117,6 +130,7 @@ public boolean postLoad() { @Override public void unload() { + assert signature != null; Functions.unregisterFunction(signature); VALIDATE_FUNCTIONS.set(true); } @@ -127,7 +141,7 @@ public Priority getPriority() { } @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { return (local ? "local " : "") + "function"; } diff --git a/src/main/java/ch/njol/skript/test/runner/CondRunningJUnit.java b/src/main/java/ch/njol/skript/test/runner/CondRunningJUnit.java new file mode 100644 index 00000000000..cfb7e2abe34 --- /dev/null +++ b/src/main/java/ch/njol/skript/test/runner/CondRunningJUnit.java @@ -0,0 +1,60 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.test.runner; + +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.NoDoc; +import ch.njol.skript.lang.Condition; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; + +@Name("Check JUnit") +@Description({ + "Returns true if the test runner is currently running a JUnit.", + "Useful for the EvtTestCase of JUnit exclusive syntaxes registered from within the test packages." +}) +@NoDoc +public class CondRunningJUnit extends Condition { + + static { + Skript.registerCondition(CondRunningJUnit.class, "running junit"); + } + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + return true; + } + + @Override + public boolean check(Event event) { + return TestMode.JUNIT; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "running JUnit"; + } + +} diff --git a/src/main/java/ch/njol/skript/test/runner/EffObjectives.java b/src/main/java/ch/njol/skript/test/runner/EffObjectives.java index 9a2d9a0e4ae..472abc4e7b5 100644 --- a/src/main/java/ch/njol/skript/test/runner/EffObjectives.java +++ b/src/main/java/ch/njol/skript/test/runner/EffObjectives.java @@ -92,31 +92,23 @@ public String toString(@Nullable Event event, boolean debug) { * @return boolean true if the test passed. */ public static boolean isJUnitComplete() { - if (requirements.isEmpty()) - return true; - if (completeness.isEmpty() && !requirements.isEmpty()) - return false; + assert !completeness.isEmpty() || !requirements.isEmpty(); return completeness.equals(requirements); } /** - * Returns an array string containing all the objectives that the current - * JUnit test failed to accomplish in the given time. - * - * @return + * Fails the JUnit testing system if any JUnit tests did not complete their checks. */ - public static String getFailedObjectivesString() { - StringBuilder builder = new StringBuilder(); + public static void fail() { for (String test : requirements.keySet()) { if (!completeness.containsKey(test)) { - builder.append("JUnit test '" + test + "' didn't complete any objectives."); + TestTracker.JUnitTestFailed("JUnit test '" + test + "'", "didn't complete any objectives."); continue; } List<String> failures = Lists.newArrayList(requirements.get(test)); failures.removeAll(completeness.get(test)); - builder.append("JUnit test '" + test + "' failed objectives: " + Arrays.toString(failures.toArray(new String[0]))); + TestTracker.JUnitTestFailed("JUnit test '" + test + "'", "failed objectives: " + Arrays.toString(failures.toArray(new String[0]))); } - return builder.toString(); } } diff --git a/src/main/java/ch/njol/skript/test/runner/TestTracker.java b/src/main/java/ch/njol/skript/test/runner/TestTracker.java index dde781c344f..c339e43e6c9 100644 --- a/src/main/java/ch/njol/skript/test/runner/TestTracker.java +++ b/src/main/java/ch/njol/skript/test/runner/TestTracker.java @@ -52,6 +52,10 @@ public static void testStarted(String name) { currentTest = name; } + public static void JUnitTestFailed(String currentTest, String msg) { + failedTests.put(currentTest, msg); + } + public static void testFailed(String msg) { failedTests.put(currentTest, msg); } diff --git a/src/main/java/ch/njol/skript/util/Time.java b/src/main/java/ch/njol/skript/util/Time.java index 1572e512599..f957dd3edfe 100644 --- a/src/main/java/ch/njol/skript/util/Time.java +++ b/src/main/java/ch/njol/skript/util/Time.java @@ -27,11 +27,12 @@ import ch.njol.skript.localization.Message; import ch.njol.util.Math2; import ch.njol.yggdrasil.YggdrasilSerializable; +import org.skriptlang.skript.lang.util.Cyclical; /** * @author Peter Güttinger */ -public class Time implements YggdrasilSerializable { +public class Time implements YggdrasilSerializable, Cyclical<Integer> { private final static int TICKS_PER_HOUR = 1000, TICKS_PER_DAY = 24 * TICKS_PER_HOUR; private final static double TICKS_PER_MINUTE = 1000. / 60; @@ -154,4 +155,14 @@ public boolean equals(final @Nullable Object obj) { return time == other.time; } + @Override + public Integer getMaximum() { + return TICKS_PER_DAY; + } + + @Override + public Integer getMinimum() { + return 0; + } + } diff --git a/src/main/java/org/skriptlang/skript/lang/entry/util/ExpressionEntryData.java b/src/main/java/org/skriptlang/skript/lang/entry/util/ExpressionEntryData.java index e707d3337d3..42e964918f9 100644 --- a/src/main/java/org/skriptlang/skript/lang/entry/util/ExpressionEntryData.java +++ b/src/main/java/org/skriptlang/skript/lang/entry/util/ExpressionEntryData.java @@ -69,7 +69,7 @@ public ExpressionEntryData( @SuppressWarnings("unchecked") protected Expression<? extends T> getValue(String value) { Expression<? extends T> expression; - try (ParseLogHandler log = new ParseLogHandler()) { + try (ParseLogHandler log = new ParseLogHandler().start()) { expression = new SkriptParser(value, flags, ParseContext.DEFAULT) .parseExpression(returnType); if (expression == null) // print an error if it couldn't parse diff --git a/src/main/java/org/skriptlang/skript/lang/util/Cyclical.java b/src/main/java/org/skriptlang/skript/lang/util/Cyclical.java new file mode 100644 index 00000000000..7c5def1d96f --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/util/Cyclical.java @@ -0,0 +1,52 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.util; + +/** + * This is for a special type of numerical value that is compared in a cyclical (rather than a linear) way. + * <p> + * The current example of this in Skript is Time, + * since 23:59 can be both before XOR after 00:01 depending on the context. + * <p> + * In practice, cyclical types have to be compared in a special way (particularly for "is between") + * when we can use the order of operations to determine the context. + * <p> + * The minimum/maximum values are intended to help with unusual equality checks, (e.g. 11pm = 1am - 2h). + * + * @param <Value> the type of number this uses, to help with type coercion + */ +public interface Cyclical<Value extends Number> { + + /** + * The potential 'top' of the cycle, e.g. the highest value after which this should restart. + * In practice, nothing forces this, so you can write 24:00 or 361° instead of 00:00 and 1° respectively. + * + * @return the highest legal value + */ + Value getMaximum(); + + /** + * The potential 'bottom' of the cycle, e.g. the lowest value. + * In practice, nothing forces this, so 24:00 is synonymous with 00:00. + * + * @return the lowest legal value + */ + Value getMinimum(); + +} diff --git a/src/main/java/org/skriptlang/skript/lang/util/package-info.java b/src/main/java/org/skriptlang/skript/lang/util/package-info.java new file mode 100644 index 00000000000..43ad7f90fae --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/util/package-info.java @@ -0,0 +1,23 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +@NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) +package org.skriptlang.skript.lang.util; + +import org.eclipse.jdt.annotation.DefaultLocation; +import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index cd51c2e1efe..176cc9b94ce 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -1923,6 +1923,7 @@ attribute types: generic_follow_range: generic follow range, follow range generic_knockback_resistance: generic knockback resistance, knockback resistance generic_luck: generic luck, luck + generic_max_absorption: generic max absorption, max absorption generic_max_health: generic max health, max health generic_movement_speed: generic movement speed, movement speed horse_jump_strength: horse jump strength diff --git a/src/main/resources/lang/english.lang b/src/main/resources/lang/english.lang index 40db6ddb7a0..b5684c1fedc 100644 --- a/src/main/resources/lang/english.lang +++ b/src/main/resources/lang/english.lang @@ -68,7 +68,7 @@ skript command: aliases: the aliases script: <gold>%s<reset> scripts in folder: all scripts in <gold>%s<reset> - x scripts in folder: <gold>%2$s <reset>script¦¦s¦ in <gold>%1$s<reset> + x scripts in folder: <gold>%2$s <lime>script¦¦s¦ in <gold>%1$s<reset> empty folder: <gold>%s<reset> does not contain any enabled scripts. enable: all: diff --git a/src/main/resources/lang/french.lang b/src/main/resources/lang/french.lang index 6aa7e26342d..39cc5555a2a 100644 --- a/src/main/resources/lang/french.lang +++ b/src/main/resources/lang/french.lang @@ -68,7 +68,7 @@ skript command: aliases: les alias script: <gold>%s<reset> scripts in folder: tous les scripts dans <gold>%s<reset> - x scripts in folder: <gold>%2$s <reset>script¦¦s¦ dans <gold>%1$s<reset> + x scripts in folder: <gold>%2$s <lime>script¦¦s¦ dans <gold>%1$s<reset> empty folder: <gold>%s<reset> ne contient aucun script activé. enable: all: diff --git a/src/main/resources/lang/german.lang b/src/main/resources/lang/german.lang index 0a8255f44e8..876dc857a92 100644 --- a/src/main/resources/lang/german.lang +++ b/src/main/resources/lang/german.lang @@ -47,7 +47,7 @@ skript command: info: Druckt eine Nachricht mit Links zu den Aliases und der Dokumentation von Skript. gen-docs: Generiert Dokumentation mithilfe von docs/templates im Plugin-Ordner test: Wird zum Ausführen von Skript-Tests verwendet - + invalid script: Das Skript <grey>'<gold>%s<grey>'<red> konnte nicht gefunden werden. invalid folder: Der Ordner <grey>'<gold>%s<grey>'<red> konnte nicht gefunden werden. reload: @@ -61,14 +61,14 @@ skript command: error details: <light red> %s<reset>\n other details: <white> %s<reset>\n line details: <gold> Linie: <gray>%s<reset>\n <reset> - + config, aliases and scripts: die Konfiguration, alle Itemnamen und alle Skripte scripts: alle Skripte main config: Konfiguration aliases: die Itemnamen script: <gold>%s<reset> scripts in folder: alle Skripte in <gold>%s<reset> - x scripts in folder: <gold>%2$s <reset>Skript¦¦e¦ in <gold>%1$s<reset> + x scripts in folder: <gold>%2$s <lime>Skript¦¦e¦ in <gold>%1$s<reset> empty folder: <gold>%s<reset> enthält keine aktivierten Skripte. enable: all: diff --git a/src/main/resources/lang/japanese.lang b/src/main/resources/lang/japanese.lang index 708df5a717d..50c27522db6 100644 --- a/src/main/resources/lang/japanese.lang +++ b/src/main/resources/lang/japanese.lang @@ -68,7 +68,7 @@ skript command: aliases: エイリアス script: <gold>%s<reset> scripts in folder: <gold>%s<reset>フォルダ内の全てのスクリプト - x scripts in folder: <gold>%1$s<reset>フォルダ内の<gold>%2$s<reset>個のスクリプト + x scripts in folder: <gold>%1$s<lime>フォルダ内の<gold>%2$s<reset>個のスクリプト empty folder: 有効化されたスクリプトが<gold>%s<reset>フォルダ内にありませんでした。 enable: all: diff --git a/src/main/resources/lang/korean.lang b/src/main/resources/lang/korean.lang index b2be122224b..580c78e3744 100644 --- a/src/main/resources/lang/korean.lang +++ b/src/main/resources/lang/korean.lang @@ -47,7 +47,7 @@ skript command: info: Skript의 별명 및 문서에 대한 링크가있는 메시지를 표시합니다 gen-docs: 플러그인 폴더의 문서 템플릿을 사용하여 문서를 생성합니다. test: Skript 테스트를 실행할 때 사용됩니다. - + invalid script: 스크립트 폴더에서 <grey>'<gold>%s<grey>'<red> 스크립트를 찾을 수 없습니다! invalid folder: 스크립트 폴더에서 <grey>'<gold>%s<grey>'<red> 폴더를 찾을 수 없습니다! reload: @@ -68,7 +68,7 @@ skript command: aliases: 별칭(aliases) script: <gold>%s<reset> scripts in folder: <gold>%s<reset>폴더 속 모든 스크립트 - x scripts in folder: <gold>%1$s<reset> 폴더 속 <gold>%2$s<reset>개의 스크립트 + x scripts in folder: <gold>%1$s<lime> 폴더 속 <gold>%2$s<reset>개의 스크립트 empty folder: <gold>%s<reset>에 활성화된 스크립트가 없습니다. enable: all: diff --git a/src/main/resources/lang/polish.lang b/src/main/resources/lang/polish.lang index 633f7e555a9..1f285032f0b 100644 --- a/src/main/resources/lang/polish.lang +++ b/src/main/resources/lang/polish.lang @@ -68,7 +68,7 @@ skript command: aliases: aliasy script: <gold>%s<reset> scripts in folder: wszystkie skrypty w <gold>%s<reset> - x scripts in folder: <gold>%2$s <reset>skrypt¦¦ów¦ w <gold>%1$s<reset> + x scripts in folder: <gold>%2$s <lime>skrypt¦¦ów¦ w <gold>%1$s<reset> empty folder: <gold>%s<reset> nie ma w sobie żadnych włączonych skryptów. enable: all: diff --git a/src/main/java/ch/njol/skript/test/runner/ExprJUnitTest.java b/src/test/java/org/skriptlang/skript/test/junit/registration/ExprJUnitTest.java similarity index 92% rename from src/main/java/ch/njol/skript/test/runner/ExprJUnitTest.java rename to src/test/java/org/skriptlang/skript/test/junit/registration/ExprJUnitTest.java index 0f85a98e8aa..448638914e1 100644 --- a/src/main/java/ch/njol/skript/test/runner/ExprJUnitTest.java +++ b/src/test/java/org/skriptlang/skript/test/junit/registration/ExprJUnitTest.java @@ -16,7 +16,7 @@ * * Copyright Peter Güttinger, SkriptLang team and contributors */ -package ch.njol.skript.test.runner; +package org.skriptlang.skript.test.junit.registration; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @@ -29,6 +29,8 @@ import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.skript.test.runner.SkriptJUnitTest; +import ch.njol.skript.test.runner.TestMode; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; @@ -38,7 +40,7 @@ public class ExprJUnitTest extends SimpleExpression<String> { static { - if (TestMode.ENABLED) + if (TestMode.JUNIT) Skript.registerExpression(ExprJUnitTest.class, String.class, ExpressionType.SIMPLE, "[the] [current[[ly] running]] junit test [name]"); } diff --git a/src/test/java/org/skriptlang/skript/test/junit/registration/package-info.java b/src/test/java/org/skriptlang/skript/test/junit/registration/package-info.java new file mode 100644 index 00000000000..c8b678a11fa --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/junit/registration/package-info.java @@ -0,0 +1,25 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + * + * Place any and all custom syntaxes relating to the JUnit testJar in here to be exclusively ran on the test runner. + */ +@NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) +package org.skriptlang.skript.test.junit.registration; + +import org.eclipse.jdt.annotation.DefaultLocation; +import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/src/test/java/org/skriptlang/skript/test/tests/syntaxes/ExprDropsTest.java b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/ExprDropsTest.java new file mode 100644 index 00000000000..f9216d5352c --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/ExprDropsTest.java @@ -0,0 +1,44 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.test.tests.syntaxes; + +import ch.njol.skript.test.runner.SkriptJUnitTest; +import org.bukkit.entity.Pig; +import org.junit.Before; +import org.junit.Test; + +public class ExprDropsTest extends SkriptJUnitTest { + + private Pig pig; + + static { + setShutdownDelay(1); + } + + @Before + public void spawnPig() { + pig = spawnTestPig(); + } + + @Test + public void killPig() { + pig.damage(100); + } + +} diff --git a/src/test/skript/environments/java17/paper-1.20.1.json b/src/test/skript/environments/java17/paper-1.20.2.json similarity index 85% rename from src/test/skript/environments/java17/paper-1.20.1.json rename to src/test/skript/environments/java17/paper-1.20.2.json index 3a117b97397..0512ae142b0 100644 --- a/src/test/skript/environments/java17/paper-1.20.1.json +++ b/src/test/skript/environments/java17/paper-1.20.2.json @@ -1,11 +1,11 @@ { - "name": "paper-1.20.1", + "name": "paper-1.20.2", "resources": [ {"source": "server.properties.generic", "target": "server.properties"} ], "paperDownloads": [ { - "version": "1.20.1", + "version": "1.20.2", "target": "paperclip.jar" } ], diff --git a/src/test/skript/tests/junit/EvtGrow.sk b/src/test/skript/junit/EvtGrow.sk similarity index 100% rename from src/test/skript/tests/junit/EvtGrow.sk rename to src/test/skript/junit/EvtGrow.sk diff --git a/src/test/skript/junit/ExprDrops.sk b/src/test/skript/junit/ExprDrops.sk new file mode 100644 index 00000000000..3df908f9f03 --- /dev/null +++ b/src/test/skript/junit/ExprDrops.sk @@ -0,0 +1,103 @@ +test "ExprDropsJUnit" when running JUnit: + set {_tests::1} to "clear drops" + set {_tests::2} to "set drops to two items" + set {_tests::3} to "add item to drops" + set {_tests::4} to "remove item from drops" + set {_tests::5} to "add multiple items to drops" + set {_tests::6} to "remove all of an item from drops" + set {_tests::7} to "remove multiple items from drops" + set {_tests::8} to "set drops to experience doesn't modify items" + set {_tests::9} to "add experience to drops doesn't modify items" + set {_tests::10} to "remove experience (special case) from drops doesn't modify items" + set {_tests::11} to "add and remove experience from drops doesn't modify items" + set {_tests::12} to "remove all experience from drops doesn't modify items" + set {_tests::13} to "drops test complete" + ensure junit test "org.skriptlang.skript.test.tests.syntaxes.ExprDropsTest" completes {_tests::*} + +# NOTE: Do NOT take the behavior described in this test as a guide for how ExprDrops SHOULD work, only for how it DOES work in 2.7.x. +# The behavior should change in 2.8 and this test will be updated accordingly. + +on death of pig: + set {_test} to "org.skriptlang.skript.test.tests.syntaxes.ExprDropsTest" + junit test is {_test} + + # Items + + clear drops + if size of drops is 0: + complete objective "clear drops" for {_test} + + set drops to 1 stick and 1 carrot + # this is stupid but comparing lists directly doesn't work + set {_i::*} to drops + if: + {_i::1} is 1 stick + {_i::2} is 1 carrot + then: + complete objective "set drops to two items" for {_test} + + add 1 dirt to drops + set {_i::*} to drops + if: + {_i::1} is 1 stick + {_i::2} is 1 carrot + {_i::3} is 1 dirt + then: + complete objective "add item to drops" for {_test} + + remove dirt from drops + set {_i::*} to drops + if: + {_i::1} is 1 stick + {_i::2} is 1 carrot + then: + complete objective "remove item from drops" for {_test} + + add 1 dirt and 2 dirt to drops + set {_i::*} to drops + if: + {_i::1} is 1 stick + {_i::2} is 1 carrot + {_i::3} is 1 dirt + {_i::4} is 2 dirt + then: + complete objective "add multiple items to drops" for {_test} + + remove all dirt from drops + set {_i::*} to drops + if: + {_i::1} is 1 stick + {_i::2} is 1 carrot + then: + complete objective "remove all of an item from drops" for {_test} + + remove 1 stick and 1 carrot from drops + if size of drops is 0: + complete objective "remove multiple items from drops" for {_test} + + # Experience + # to-do: add checks against experience changes once `on death` has access to dropped experience amount + + set drops to 1 porkchop + set drops to 10 experience + if drops is 1 porkchop: + complete objective "set drops to experience doesn't modify items" for {_test} + + add 10 experience to drops + if drops is 1 porkchop: + complete objective "add experience to drops doesn't modify items" for {_test} + + remove xp from drops + if drops is 1 porkchop: + complete objective "remove experience (special case) from drops doesn't modify items" for {_test} + + add 100 experience to drops + remove 50 experience from drops + if drops is 1 porkchop: + complete objective "add and remove experience from drops doesn't modify items" for {_test} + + remove all 10 experience from drops + if drops is 1 porkchop: + complete objective "remove all experience from drops doesn't modify items" for {_test} + + complete objective "drops test complete" for {_test} diff --git a/src/test/skript/tests/junit/README.md b/src/test/skript/junit/README.md similarity index 100% rename from src/test/skript/tests/junit/README.md rename to src/test/skript/junit/README.md diff --git a/src/test/skript/tests/junit/SimpleJUnitTest.sk b/src/test/skript/junit/SimpleJUnitTest.sk similarity index 95% rename from src/test/skript/tests/junit/SimpleJUnitTest.sk rename to src/test/skript/junit/SimpleJUnitTest.sk index 25a048ae372..a46d5024634 100644 --- a/src/test/skript/tests/junit/SimpleJUnitTest.sk +++ b/src/test/skript/junit/SimpleJUnitTest.sk @@ -1,4 +1,4 @@ -on script load: +test "SimpleJUnitTest" when running JUnit: # Setup our objective for this script test to complete with the JUnit test. ensure junit test "org.skriptlang.skript.test.tests.regression.SimpleJUnitTest" completes "piggy died" diff --git a/src/test/skript/tests/regressions/6032-local-vars-created-in-effsecspawn.sk b/src/test/skript/tests/regressions/6032-local-vars-created-in-effsecspawn.sk new file mode 100644 index 00000000000..23033b01db0 --- /dev/null +++ b/src/test/skript/tests/regressions/6032-local-vars-created-in-effsecspawn.sk @@ -0,0 +1,5 @@ +test "local vars created in EffSecSpawn": + set {_spawn} to spawn of world "world" + spawn 4 zombies at {_spawn}: + add 1 to {_test} + assert {_test} is 4 with "local var created in EffSecSpawn was not properly incremented" diff --git a/src/test/skript/tests/regressions/6205-location comparison.sk b/src/test/skript/tests/regressions/6205-location comparison.sk new file mode 100644 index 00000000000..9e9df7cf30f --- /dev/null +++ b/src/test/skript/tests/regressions/6205-location comparison.sk @@ -0,0 +1,9 @@ +test "compare similar locations": + set {_world} to world "world" + assert location(1, 2, 3, {_world}, 4, 5) = location(1, 2, 3, {_world}, 4, 5) with "basic location comparison failed" + + assert location(1, 2, 3, {_world}, 270, 0) = location(1, 2, 3, {_world}, -90, 0) with "yaw normalization failed when comparing locations" + assert location(1, 2, 3, {_world}, 0, 270) = location(1, 2, 3, {_world}, 0, 90) with "pitch normalization failed when comparing locations" + assert location(1, 2, 3, {_world}, 270, 270) = location(1, 2, 3, {_world}, -90, 90) with "yaw and pitch normalization failed when comparing locations" + + assert location(1, (-1/infinity value), 3, {_world}, (-1/infinity value), (-1/infinity value)) = location(1, 0, 3, {_world}, 0, 0) with "0 and -0.0 are not equal when comparing locations" diff --git a/src/test/skript/tests/syntaxes/conditions/CondCompare.sk b/src/test/skript/tests/syntaxes/conditions/CondCompare.sk new file mode 100644 index 00000000000..ca436260431 --- /dev/null +++ b/src/test/skript/tests/syntaxes/conditions/CondCompare.sk @@ -0,0 +1,11 @@ +test "compare": + assert 10 is between 5 and 15 with "Number isn't between smaller and larger" + assert 10 is between 9 and 11 with "Number isn't between smaller and larger" + assert 10 is between 11 and 9 with "Number isn't between larger and smaller" + assert 10 is between 9 and 10 with "Number isn't between smaller and equal" + assert 10 is between 10 and 11 with "Number isn't between equal and larger" + assert 10 is between 10 and 10 with "Number isn't between equal and equal" + assert 19:00 is between 18:00 and 20:00 with "Time 19:00 isn't between 18:00 and 20:00" + assert 23:00 is between 20:00 and 24:00 with "Time 23:00 isn't between 20:00 and 24:00" + assert 23:00 is between 20:00 and 01:00 with "Time 23:00 isn't between 20:00 and 01:00 (cyclical)" + assert 23:00 is not between 01:00 and 20:00 with "Time 23:00 is between 01:00 and 20:00 (non-cyclical)" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprDurability.sk b/src/test/skript/tests/syntaxes/expressions/ExprDurability.sk new file mode 100644 index 00000000000..724000493e5 --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprDurability.sk @@ -0,0 +1,14 @@ +test "durability": + set {_i} to an iron sword + set {_max} to max durability of {_i} + assert damage of {_i} is 0 with "default item damage failed" + assert durability of {_i} is {_max} with "default item durability failed" + set damage of {_i} to 64 + assert damage of {_i} is 64 with "item damage failed" + assert durability of {_i} is {_max} - 64 with "item durability failed" + set durability of {_i} to 10 + assert damage of {_i} is {_max} - 10 with "inverse item damage failed" + assert durability of {_i} is 10 with "inverse item durability failed" + set durability of {_i} to 0 + assert damage of {_i} is {_max} with "max item damage failed" + assert durability of {_i} is 0 with "zero item durability failed" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprSortedList.sk b/src/test/skript/tests/syntaxes/expressions/ExprSortedList.sk new file mode 100644 index 00000000000..819486dbaaa --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprSortedList.sk @@ -0,0 +1,17 @@ +test "sort": + # Populate list + set {_i} to 0 + loop 5 times: + set {_i} to {_i} + 1 + set {_list::%{_i}%} to a random integer from 1 to 100 + loop 5 times: + set {_i} to {_i} + 1 + set {_list::%{_i}%} to a random number from 1 to 100 + + # Test sorting + loop sorted {_list::*}: + if {_prev} is set: + assert loop-value >= {_prev} with "Couldn't sort correctly" + set {_prev} to loop-value + + assert (sorted 1 and "test") is not set with "Sorting incomparable values returned a value" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprTimes.sk b/src/test/skript/tests/syntaxes/expressions/ExprTimes.sk new file mode 100644 index 00000000000..5001a78ade0 --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprTimes.sk @@ -0,0 +1,8 @@ +test "times": + set {_seven} to 1.05 / 0.15 # = 7 + set {_three} to 10 - {_seven} # = 3 + set {_count} to 0 + loop {_three} times: + add 1 to {_count} + assert {_count} is 3 with "count was %{_count}% instead of 3" + assert {_three} is 3 with "original number wasn't 3" diff --git a/src/test/skript/tests/syntaxes/structures/StructCommand.sk b/src/test/skript/tests/syntaxes/structures/StructCommand.sk index dd20198002a..9df9e139247 100644 --- a/src/test/skript/tests/syntaxes/structures/StructCommand.sk +++ b/src/test/skript/tests/syntaxes/structures/StructCommand.sk @@ -2,7 +2,10 @@ test "commands": execute command "skriptcommand taco" execute command "//somecommand burrito is tasty" -command skriptcommand <text> [<text>] [<itemtype = %dirt block named "steve"%>]: +options: + command: skriptcommand + +command {@command} <text> [<text>] [<itemtype = %dirt block named "steve"%>]: trigger: set {_arg1} to arg-1 assert {_arg1} is "taco" with "arg-1 test failed (got '%{_arg1}%')" diff --git a/src/test/skript/tests/syntaxes/structures/StructFunction.sk b/src/test/skript/tests/syntaxes/structures/StructFunction.sk index c1e20e5ca70..8d11a83479b 100644 --- a/src/test/skript/tests/syntaxes/structures/StructFunction.sk +++ b/src/test/skript/tests/syntaxes/structures/StructFunction.sk @@ -1,6 +1,9 @@ -function foo() :: boolean: +function {@function}) :: boolean: return true +options: + function: foo( + function local() :: number: return 1 From 23a58f33bb82fb4a5960caa1ea57bb6e5a5fedd7 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Sat, 16 Dec 2023 21:25:22 -0800 Subject: [PATCH 532/619] Add characters between and random characters expressions (#5867) * Add characters between and random characters expressions, as well as tests. * Update ExprCharacters.sk * Update src/main/java/ch/njol/skript/expressions/ExprRandomCharacter.java Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> * Requested Changes * Ascii table link * Apply suggestions from code review Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> * Requested Changes --------- Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> --- .../skript/expressions/ExprCharacters.java | 120 ++++++++++++++++ .../expressions/ExprRandomCharacter.java | 133 ++++++++++++++++++ .../syntaxes/expressions/ExprCharacters.sk | 9 ++ .../expressions/ExprRandomCharacters.sk | 8 ++ 4 files changed, 270 insertions(+) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprCharacters.java create mode 100644 src/main/java/ch/njol/skript/expressions/ExprRandomCharacter.java create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprCharacters.sk create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprRandomCharacters.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprCharacters.java b/src/main/java/ch/njol/skript/expressions/ExprCharacters.java new file mode 100644 index 00000000000..4c97fa79c39 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprCharacters.java @@ -0,0 +1,120 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.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 org.apache.commons.lang.ArrayUtils; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Characters Between") +@Description({ + "All characters between two given characters, useful for generating random strings. This expression uses the Unicode numerical code " + + "of a character to determine which characters are between the two given characters. The <a href=\"https://www.asciitable.com/\">ASCII table linked here</a> " + + "shows this ordering for the first 256 characters.", + "If you would like only alphanumeric characters you can use the 'alphanumeric' option in the expression.", + "If strings of more than one character are given, only the first character of each is used." +}) +@Examples({ + "loop characters from \"a\" to \"f\":", + "\tbroadcast \"%loop-value%\"", + "", + "# 0123456789:;<=>?@ABC... ...uvwxyz", + "send characters between \"0\" and \"z\"", + "", + "# 0123456789ABC... ...uvwxyz", + "send alphanumeric characters between \"0\" and \"z\"" +}) +@Since("INSERT VERSION") +public class ExprCharacters extends SimpleExpression<String> { + + static { + Skript.registerExpression(ExprCharacters.class, String.class, ExpressionType.COMBINED, + "[(all [[of] the]|the)] [:alphanumeric] characters (between|from) %string% (and|to) %string%"); + } + + private Expression<String> start, end; + private boolean isAlphanumeric; + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + start = (Expression<String>) exprs[0]; + end = (Expression<String>) exprs[1]; + isAlphanumeric = parseResult.hasTag("alphanumeric"); + return true; + } + + @Override + @Nullable + protected String[] get(Event event) { + String start = this.start.getSingle(event); + String end = this.end.getSingle(event); + if (start == null || end == null) + return new String[0]; + + if (start.length() < 1 || end.length() < 1) + return new String[0]; + + char startChar = start.charAt(0); + char endChar = end.charAt(0); + + boolean reversed = startChar > endChar; + char delta = reversed ? (char) -1 : (char) 1; + + int min = Math.min(startChar, endChar); + int max = Math.max(startChar, endChar); + + String[] chars = new String[max - min + 1]; + + for (char c = startChar; min <= c && c <= max; c += delta) { + if (isAlphanumeric && !Character.isLetterOrDigit(c)) + continue; + chars[c - min] = String.valueOf(c); + } + + if (reversed) + ArrayUtils.reverse(chars); + + return chars; + } + + @Override + public boolean isSingle() { + return false; + } + + @Override + public Class<? extends String> getReturnType() { + return String.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "all the " + (isAlphanumeric ? "alphanumeric " : "") + "characters between " + start.toString(event, debug) + " and " + end.toString(event, debug); + } +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprRandomCharacter.java b/src/main/java/ch/njol/skript/expressions/ExprRandomCharacter.java new file mode 100644 index 00000000000..6f82051d2ea --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprRandomCharacter.java @@ -0,0 +1,133 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.Literal; +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.eclipse.jdt.annotation.Nullable; + +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + +@Name("Random Character") +@Description({ + "One or more random characters between two given characters. Use 'alphanumeric' if you want only alphanumeric characters.", + "This expression uses the Unicode numerical code of a character to determine which characters are between the two given characters.", + "If strings of more than one character are given, only the first character of each is used." +}) +@Examples({ + "set {_captcha} to join (5 random characters between \"a\" and \"z\") with \"\"", + "send 3 random alphanumeric characters between \"0\" and \"z\"" +}) +@Since("INSERT VERSION") +public class ExprRandomCharacter extends SimpleExpression<String> { + + static { + Skript.registerExpression(ExprRandomCharacter.class, String.class, ExpressionType.COMBINED, + "[a|%-number%] random [:alphanumeric] character[s] (from|between) %string% (to|and) %string%"); + } + + @Nullable + private Expression<Number> amount; + private Expression<String> from, to; + private boolean isAlphanumeric; + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + amount = (Expression<Number>) exprs[0]; + from = (Expression<String>) exprs[1]; + to = (Expression<String>) exprs[2]; + isAlphanumeric = parseResult.hasTag("alphanumeric"); + return true; + } + + @Override + @Nullable + protected String[] get(Event event) { + int amount = this.amount == null ? 1 : this.amount.getOptionalSingle(event).orElse(1).intValue(); + String from = this.from.getSingle(event); + String to = this.to.getSingle(event); + if (from == null || to == null) + return new String[0]; + + if (from.length() < 1 || to.length() < 1) + return new String[0]; + + Random random = ThreadLocalRandom.current(); + char fromChar = from.charAt(0); + char toChar = to.charAt(0); + int min = Math.min(fromChar, toChar); + int max = Math.max(fromChar, toChar); + + String[] chars = new String[amount]; + + // If isAlphanumeric, we need to find the valid characters. + // We can't just repeat the random character generation because + // it's possible that there are no valid characters in the range. + if (isAlphanumeric) { + StringBuilder validChars = new StringBuilder(); + for (int c = min; c <= max; c++) { + if (Character.isLetterOrDigit(c)) + validChars.append((char) c); + } + + if (validChars.length() == 0) + return new String[0]; + + for (int i = 0; i < amount; i++) { + chars[i] = String.valueOf(validChars.charAt(random.nextInt(validChars.length()))); + } + return chars; + } + + for (int i = 0; i < amount; i++) { + chars[i] = String.valueOf((char) (random.nextInt(max - min + 1) + min)); + } + + return chars; + } + + + @Override + public boolean isSingle() { + if (amount instanceof Literal) + return ((Literal<Number>) amount).getSingle().intValue() == 1; + return amount == null; + } + + @Override + public Class<? extends String> getReturnType() { + return String.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return (amount != null ? amount.toString(event, debug) : "a") + " random character between " + from.toString(event, debug) + " and " + to.toString(event, debug); + } +} diff --git a/src/test/skript/tests/syntaxes/expressions/ExprCharacters.sk b/src/test/skript/tests/syntaxes/expressions/ExprCharacters.sk new file mode 100644 index 00000000000..5c57a474378 --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprCharacters.sk @@ -0,0 +1,9 @@ +test "characters between": + set {_a} to 1 random character between "a" and "z" + assert join characters between "a" and "d" is "abcd" with "Invalid characters between a and d" + assert join characters between "d" and "a" is "dcba" with "Invalid characters between d and a" + assert join characters between "Z" and "a" is "Z[\]^_`a" with "Invalid characters between Z and a" + assert join characters between "a" and "Z" is "a`_^]\[Z" with "Invalid characters between a and Z" + assert join alphanumeric characters between "Z" and "a" is "Za" with "Invalid alphanumeric characters between Z and a" + assert join alphanumeric characters between "a" and "Z" is "aZ" with "Invalid alphanumeric characters between a and Z" + assert join characters between "alphabet" and "b12_" is "ab" with "Invalid characters between a and b when given strings of length >1" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprRandomCharacters.sk b/src/test/skript/tests/syntaxes/expressions/ExprRandomCharacters.sk new file mode 100644 index 00000000000..92cbf28ea4c --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprRandomCharacters.sk @@ -0,0 +1,8 @@ +test "random characters": + assert random character between "a" and "a" is "a" with "Failed to generate random character between a and a" + assert random character between "a" and "b" is "a" or "b" with "Failed to generate random character between a and b" + assert size of (5 random characters between "a" and "b") is 5 with "Failed to generate 5 random characters between a and b" + assert size of (5 random characters between "A" and "a") is 5 with "Failed to generate 5 random characters between A and a" + assert random alphanumeric character from "Z" to "a" is "Z" or "a" with "Failed to generate random alphanumeric character from Z to a" + assert size of (5 random alphanumeric characters from "Z" to "a") is 5 with "Failed to generate 5 random alphanumeric characters from Z to a" + assert random character from "baby" to "alpine" is "a" or "b" with "Failed to generate random character from a to b when given strings of length >1" From 4a3290fbaa36bd9fd9228551ddfb7fdea0cb2ce7 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Sat, 16 Dec 2023 21:44:05 -0800 Subject: [PATCH 533/619] Removes Projectile Bounce State Expr and Cond (#5958) Removes Projectile Bounce State --- .../conditions/CondProjectileCanBounce.java | 51 ------------- .../ExprProjectileBounceState.java | 75 ------------------- 2 files changed, 126 deletions(-) delete mode 100644 src/main/java/ch/njol/skript/conditions/CondProjectileCanBounce.java delete mode 100644 src/main/java/ch/njol/skript/expressions/ExprProjectileBounceState.java diff --git a/src/main/java/ch/njol/skript/conditions/CondProjectileCanBounce.java b/src/main/java/ch/njol/skript/conditions/CondProjectileCanBounce.java deleted file mode 100644 index 08becff95c3..00000000000 --- a/src/main/java/ch/njol/skript/conditions/CondProjectileCanBounce.java +++ /dev/null @@ -1,51 +0,0 @@ -/** - * This file is part of Skript. - * - * Skript is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Skript is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Skript. If not, see <http://www.gnu.org/licenses/>. - * - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.conditions; - -import org.bukkit.entity.Projectile; - -import ch.njol.skript.conditions.base.PropertyCondition; -import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Examples; -import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.Since; - -@Name("Projectile Can Bounce") -@Description("Whether or not a projectile can bounce.") -@Examples({"on shoot:", - "\tsend \"Boing!\" to all players if projectile can bounce"}) -@Since("2.5.1") -public class CondProjectileCanBounce extends PropertyCondition<Projectile> { - - static { - register(CondProjectileCanBounce.class, PropertyType.CAN, "bounce", "projectiles"); - } - - @Override - public boolean check(Projectile projectile) { - return projectile.doesBounce(); - } - - @Override - public String getPropertyName() { - return "bounce"; - } - -} diff --git a/src/main/java/ch/njol/skript/expressions/ExprProjectileBounceState.java b/src/main/java/ch/njol/skript/expressions/ExprProjectileBounceState.java deleted file mode 100644 index f3baf2c5e38..00000000000 --- a/src/main/java/ch/njol/skript/expressions/ExprProjectileBounceState.java +++ /dev/null @@ -1,75 +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 <http://www.gnu.org/licenses/>. - * - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.expressions; - -import org.bukkit.entity.Projectile; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - -import ch.njol.skript.classes.Changer.ChangeMode; -import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Examples; -import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.Since; -import ch.njol.skript.expressions.base.SimplePropertyExpression; -import ch.njol.util.coll.CollectionUtils; - -@Name("Projectile Bounce State") -@Description("A projectile's bounce state.") -@Examples({"on projectile hit:", - "\tset projectile bounce mode of event-projectile to true"}) -@Since("2.5.1") -public class ExprProjectileBounceState extends SimplePropertyExpression<Projectile, Boolean> { - - static { - register(ExprProjectileBounceState.class, Boolean.class, "projectile bounce (state|ability|mode)", "projectiles"); - } - - @Nullable - @Override - public Boolean convert(Projectile projectile) { - return projectile.doesBounce(); - } - - @Nullable - @Override - public Class<?>[] acceptChange(ChangeMode mode) { - return (mode == ChangeMode.SET) ? CollectionUtils.array(Boolean.class) : null; - } - - @Override - public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { - if (delta == null) return; - boolean state = (Boolean) delta[0]; - for (Projectile entity : getExpr().getArray(e)) - entity.setBounce(state); - } - - @Override - public Class<? extends Boolean> getReturnType() { - return Boolean.class; - } - - @Override - protected String getPropertyName() { - return "projectile bounce state"; - } - -} From 18736a99bf26856b57f3230dc42d3dec9cdd4263 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Mon, 18 Dec 2023 15:04:28 -0500 Subject: [PATCH 534/619] More Converter and Comparator Improvements (#5815) --- .../ch/njol/skript/lang/ExpressionList.java | 8 +- .../java/ch/njol/skript/lang/LiteralList.java | 7 +- .../ch/njol/skript/lang/SkriptParser.java | 8 +- .../ch/njol/skript/registrations/Classes.java | 13 ++ .../ch/njol/skript/util/LiteralUtils.java | 3 +- .../lang/comparator/ComparatorInfo.java | 6 +- .../skript/lang/comparator/Comparators.java | 151 +++++++++++++----- .../lang/comparator/ConvertedComparator.java | 25 +-- .../lang/comparator/InverseComparator.java | 10 +- .../lang/converter/ChainedConverter.java | 30 +++- .../skript/lang/converter/Converter.java | 8 +- .../skript/lang/converter/ConverterInfo.java | 2 +- .../skript/lang/converter/Converters.java | 135 +++++++++------- .../5848-converter comparator issues.sk | 16 ++ 14 files changed, 282 insertions(+), 140 deletions(-) create mode 100644 src/test/skript/tests/regressions/5848-converter comparator issues.sk diff --git a/src/main/java/ch/njol/skript/lang/ExpressionList.java b/src/main/java/ch/njol/skript/lang/ExpressionList.java index 8a6a26fc909..7265477d5a1 100644 --- a/src/main/java/ch/njol/skript/lang/ExpressionList.java +++ b/src/main/java/ch/njol/skript/lang/ExpressionList.java @@ -22,6 +22,7 @@ import ch.njol.skript.conditions.CondCompare; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleLiteral; +import ch.njol.skript.registrations.Classes; import ch.njol.skript.util.Utils; import ch.njol.util.Checker; import ch.njol.util.Kleenean; @@ -169,10 +170,13 @@ public boolean check(Event event, Checker<? super T> checker) { @Nullable public <R> Expression<? extends R> getConvertedExpression(Class<R>... to) { Expression<? extends R>[] exprs = new Expression[expressions.length]; - for (int i = 0; i < exprs.length; i++) + Class<?>[] returnTypes = new Class[expressions.length]; + for (int i = 0; i < exprs.length; i++) { if ((exprs[i] = expressions[i].getConvertedExpression(to)) == null) return null; - return new ExpressionList<>(exprs, (Class<R>) Utils.getSuperType(to), and, this); + returnTypes[i] = exprs[i].getReturnType(); + } + return new ExpressionList<>(exprs, (Class<R>) Classes.getSuperClassInfo(returnTypes).getC(), and, this); } @Override diff --git a/src/main/java/ch/njol/skript/lang/LiteralList.java b/src/main/java/ch/njol/skript/lang/LiteralList.java index 21b478bf59d..118627adf98 100644 --- a/src/main/java/ch/njol/skript/lang/LiteralList.java +++ b/src/main/java/ch/njol/skript/lang/LiteralList.java @@ -20,6 +20,7 @@ import java.lang.reflect.Array; +import ch.njol.skript.registrations.Classes; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.lang.util.SimpleLiteral; @@ -63,13 +64,13 @@ public T[] getAll() { @Nullable public <R> Literal<? extends R> getConvertedExpression(final Class<R>... to) { final Literal<? extends R>[] exprs = new Literal[expressions.length]; - final Class<?>[] classes = new Class[expressions.length]; + final Class<?>[] returnTypes = new Class[expressions.length]; for (int i = 0; i < exprs.length; i++) { if ((exprs[i] = (Literal<? extends R>) expressions[i].getConvertedExpression(to)) == null) return null; - classes[i] = exprs[i].getReturnType(); + returnTypes[i] = exprs[i].getReturnType(); } - return new LiteralList<>(exprs, (Class<R>) Utils.getSuperType(classes), and, this); + return new LiteralList<>(exprs, (Class<R>) Classes.getSuperClassInfo(returnTypes).getC(), and, this); } @Override diff --git a/src/main/java/ch/njol/skript/lang/SkriptParser.java b/src/main/java/ch/njol/skript/lang/SkriptParser.java index 7f783e7d824..7d153d21aaf 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptParser.java +++ b/src/main/java/ch/njol/skript/lang/SkriptParser.java @@ -744,11 +744,11 @@ public final <T> Expression<? extends T> parseExpression(final Class<? extends T if (isLiteralList) { final Literal<T>[] ls = ts.toArray(new Literal[ts.size()]); assert ls != null; - return new LiteralList<>(ls, (Class<T>) Utils.getSuperType(exprRetTypes), !and.isFalse()); + return new LiteralList<>(ls, (Class<T>) Classes.getSuperClassInfo(exprRetTypes).getC(), !and.isFalse()); } else { final Expression<T>[] es = ts.toArray(new Expression[ts.size()]); assert es != null; - return new ExpressionList<>(es, (Class<T>) Utils.getSuperType(exprRetTypes), !and.isFalse()); + return new ExpressionList<>(es, (Class<T>) Classes.getSuperClassInfo(exprRetTypes).getC(), !and.isFalse()); } } finally { log.stop(); @@ -885,11 +885,11 @@ public final Expression<?> parseExpression(final ExprInfo vi) { if (isLiteralList) { final Literal<?>[] ls = ts.toArray(new Literal[ts.size()]); assert ls != null; - return new LiteralList(ls, Utils.getSuperType(exprRetTypes), !and.isFalse()); + return new LiteralList(ls, Classes.getSuperClassInfo(exprRetTypes).getC(), !and.isFalse()); } else { final Expression<?>[] es = ts.toArray(new Expression[ts.size()]); assert es != null; - return new ExpressionList(es, Utils.getSuperType(exprRetTypes), !and.isFalse()); + return new ExpressionList(es, Classes.getSuperClassInfo(exprRetTypes).getC(), !and.isFalse()); } } finally { log.stop(); diff --git a/src/main/java/ch/njol/skript/registrations/Classes.java b/src/main/java/ch/njol/skript/registrations/Classes.java index fdb5ba2544c..cf68757d755 100644 --- a/src/main/java/ch/njol/skript/registrations/Classes.java +++ b/src/main/java/ch/njol/skript/registrations/Classes.java @@ -39,6 +39,7 @@ import ch.njol.skript.command.Commands; import ch.njol.skript.entity.EntityData; +import ch.njol.skript.util.Utils; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Chunk; @@ -319,6 +320,18 @@ public static <T> ClassInfo<? super T> getSuperClassInfo(final Class<T> c) { return null; } + /** + * Gets the class info of the super type of given classes or its closest registered superclass. + * This method is useful for Skript to avoid passing around "unknown" super types. + * + * @param classes The classes to determine a super type from. + * @return The closest info for the super type of <code>classes</code>. + */ + // This method is used to avoid issues like https://github.com/SkriptLang/Skript/issues/5848 + public static ClassInfo<?> getSuperClassInfo(Class<?>... classes) { + return getSuperClassInfo(Utils.getSuperType(classes)); + } + /** * Gets all the class info of the given class in closest order to ending on object. This list will never be empty unless <tt>c</tt> is null. * diff --git a/src/main/java/ch/njol/skript/util/LiteralUtils.java b/src/main/java/ch/njol/skript/util/LiteralUtils.java index c004157b48e..59f58b09800 100644 --- a/src/main/java/ch/njol/skript/util/LiteralUtils.java +++ b/src/main/java/ch/njol/skript/util/LiteralUtils.java @@ -24,6 +24,7 @@ import ch.njol.skript.lang.ExpressionList; import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.UnparsedLiteral; +import ch.njol.skript.registrations.Classes; /** * A class that contains methods based around @@ -52,7 +53,7 @@ public static <T> Expression<T> defendExpression(Expression<?> expr) { newExpressions[i] = LiteralUtils.defendExpression(oldExpressions[i]); returnTypes[i] = newExpressions[i].getReturnType(); } - return new ExpressionList<>(newExpressions, (Class<T>) Utils.getSuperType(returnTypes), expr.getAnd()); + return new ExpressionList<>(newExpressions, (Class<T>) Classes.getSuperClassInfo(returnTypes).getC(), expr.getAnd()); } else if (expr instanceof UnparsedLiteral) { Literal<?> parsedLiteral = ((UnparsedLiteral) expr).getConvertedExpression(Object.class); return (Expression<T>) (parsedLiteral == null ? expr : parsedLiteral); diff --git a/src/main/java/org/skriptlang/skript/lang/comparator/ComparatorInfo.java b/src/main/java/org/skriptlang/skript/lang/comparator/ComparatorInfo.java index 3f63bab9cbc..261f499a494 100644 --- a/src/main/java/org/skriptlang/skript/lang/comparator/ComparatorInfo.java +++ b/src/main/java/org/skriptlang/skript/lang/comparator/ComparatorInfo.java @@ -26,9 +26,9 @@ */ public final class ComparatorInfo<T1, T2> { - final Class<T1> firstType; - final Class<T2> secondType; - final Comparator<T1, T2> comparator; + private final Class<T1> firstType; + private final Class<T2> secondType; + private final Comparator<T1, T2> comparator; ComparatorInfo(Class<T1> firstType, Class<T2> secondType, Comparator<T1, T2> comparator) { this.firstType = firstType; diff --git a/src/main/java/org/skriptlang/skript/lang/comparator/Comparators.java b/src/main/java/org/skriptlang/skript/lang/comparator/Comparators.java index 8e3aa23de72..7ad516030ff 100644 --- a/src/main/java/org/skriptlang/skript/lang/comparator/Comparators.java +++ b/src/main/java/org/skriptlang/skript/lang/comparator/Comparators.java @@ -20,10 +20,12 @@ import ch.njol.skript.Skript; import ch.njol.skript.SkriptAPIException; +import ch.njol.skript.util.Utils; import ch.njol.util.Pair; import org.eclipse.jdt.annotation.Nullable; import org.jetbrains.annotations.Unmodifiable; import org.skriptlang.skript.lang.converter.Converter; +import org.skriptlang.skript.lang.converter.ConverterInfo; import org.skriptlang.skript.lang.converter.Converters; import java.util.ArrayList; @@ -79,7 +81,7 @@ private Comparators() {} * Registers a new Comparator with Skript's collection of Comparators. * @param firstType The first type for comparison. * @param secondType The second type for comparison. - * @param comparator A Comparator for comparing objects of 'firstType' and 'secondType'. + * @param comparator A Comparator for comparing objects of <code>firstType</code> and <code>secondType</code>. */ public static <T1, T2> void registerComparator( Class<T1> firstType, @@ -93,17 +95,56 @@ public static <T1, T2> void registerComparator( } synchronized (COMPARATORS) { - for (ComparatorInfo<?, ?> info : COMPARATORS) { - if (info.firstType == firstType && info.secondType == secondType) { - throw new SkriptAPIException( - "A Comparator comparing '" + firstType + "' and '" + secondType + "' already exists!" - ); - } + if (exactComparatorExists_i(firstType, secondType)) { + throw new SkriptAPIException( + "A Comparator comparing '" + firstType + "' and '" + secondType + "' already exists!" + ); } COMPARATORS.add(new ComparatorInfo<>(firstType, secondType, comparator)); } } + /** + * Internal method. All calling locations are expected to manually synchronize this method if necessary. + * @return Whether a Comparator exists that EXACTLY matches the provided types. + */ + private static boolean exactComparatorExists_i(Class<?> firstType, Class<?> secondType) { + for (ComparatorInfo<?, ?> info : COMPARATORS) { + if (info.getFirstType() == firstType && info.getSecondType() == secondType) { + return true; + } + } + return false; + } + + /** + * A method for determining whether a direct Comparator of <code>firstType</code> and <code>secondType</code> exists. + * Unlike other methods of this class, it is not the case that + * {@link Skript#isAcceptRegistrations()} must return <code>false</code> for this method to be used. + * @param firstType The first type for comparison. + * @param secondType The second type for comparison. + * @return Whether a direct Comparator of <code>firstType</code> and <code>secondType</code> exists. + */ + public static boolean exactComparatorExists(Class<?> firstType, Class<?> secondType) { + synchronized (COMPARATORS) { + return exactComparatorExists_i(firstType, secondType); + } + } + + /** + * A method for determining whether a Comparator of <code>firstType</code> and <code>secondType</code> exists. + * @param firstType The first type for comparison. + * @param secondType The second type for comparison. + * @return Whether a Comparator of <code>firstType</code> and <code>secondType</code> exists. + */ + public static boolean comparatorExists(Class<?> firstType, Class<?> secondType) { + assertIsDoneLoading(); + if (firstType != Object.class && firstType == secondType) { // Would use the default comparator + return true; + } + return getComparator(firstType, secondType) != null; + } + /** * Compares two objects to see if a Relation exists between them. * @param first The first object for comparison. @@ -132,26 +173,26 @@ public static <T1, T2> Relation compare(@Nullable T1 first, @Nullable T2 second) } /** - * A method for obtaining a Comparator that can compare two objects of 'firstType' and 'secondType'. + * A method for obtaining a Comparator that can compare two objects of <code>firstType</code> and <code>secondType</code>. * Please note that comparators may convert objects if necessary for comparisons. * @param firstType The first type for comparison. * @param secondType The second type for comparison. - * @return A Comparator capable of determine the {@link Relation} between two objects of 'firstType' and 'secondType'. - * Will be null if no comparator capable of comparing two objects of 'firstType' and 'secondType' was found. + * @return A Comparator capable of determine the {@link Relation} between two objects of <code>firstType</code> and <code>secondType</code>. + * Will be null if no comparator capable of comparing two objects of <code>firstType</code> and <code>secondType</code> was found. */ @Nullable public static <T1, T2> Comparator<T1, T2> getComparator(Class<T1> firstType, Class<T2> secondType) { ComparatorInfo<T1, T2> info = getComparatorInfo(firstType, secondType); - return info != null ? info.comparator : null; + return info != null ? info.getComparator() : null; } /** - * A method for obtaining the info of a Comparator that can compare two objects of 'firstType' and 'secondType'. + * A method for obtaining the info of a Comparator that can compare two objects of <code>firstType</code> and <code>secondType</code>. * Please note that comparators may convert objects if necessary for comparisons. * @param firstType The first type for comparison. * @param secondType The second type for comparison. - * @return The info of a Comparator capable of determine the {@link Relation} between two objects of 'firstType' and 'secondType'. - * Will be null if no info for comparing two objects of 'firstType' and 'secondType' was found. + * @return The info of a Comparator capable of determine the {@link Relation} between two objects of <code>firstType</code> and <code>secondType</code>. + * Will be null if no info for comparing two objects of <code>firstType</code> and <code>secondType</code> was found. */ @Nullable @SuppressWarnings("unchecked") @@ -174,18 +215,18 @@ public static <T1, T2> ComparatorInfo<T1, T2> getComparatorInfo(Class<T1> firstT } /** - * The internal method for obtaining a comparator that can compare two objects of 'firstType' and 'secondType'. + * The internal method for obtaining a comparator that can compare two objects of <code>firstType</code> and <code>secondType</code>. * This method handles regular {@link Comparator}s, {@link ConvertedComparator}s, and {@link InverseComparator}s. * @param firstType The first type for comparison. * @param secondType The second type for comparison. - * @return The info of the comparator capable of determine the {@link Relation} between two objects of 'firstType' and 'secondType'. - * Will be null if no comparator capable of comparing two objects of 'firstType' and 'secondType' was found. + * @return The info of the comparator capable of determine the {@link Relation} between two objects of <code>firstType</code> and <code>secondType</code>. + * Will be null if no comparator capable of comparing two objects of <code>firstType</code> and <code>secondType</code> was found. * @param <T1> The first type for comparison. * @param <T2> The second type for comparison. * @param <C1> The first type for any {@link ComparatorInfo}. - * This is also used in organizing the conversion process of arguments (ex: 'T1' to 'C1' converter). + * This is also used in organizing the conversion process of arguments (ex: <code>T1</code> to <code>C1</code> converter). * @param <C2> The second type for any {@link ComparatorInfo}. - * This is also used in organizing the conversion process of arguments (ex: 'T2' to 'C2' converter). + * This is also used in organizing the conversion process of arguments (ex: <code>T2</code> to <code>C2</code> converter). */ @Nullable @SuppressWarnings("unchecked") @@ -195,25 +236,47 @@ private static <T1, T2, C1, C2> ComparatorInfo<T1, T2> getComparatorInfo_i( ) { // Look for an exact match for (ComparatorInfo<?, ?> info : COMPARATORS) { - if (info.firstType == firstType && info.secondType == secondType) { + if (info.getFirstType() == firstType && info.getSecondType() == secondType) { return (ComparatorInfo<T1, T2>) info; } } // Look for a basically perfect match for (ComparatorInfo<?, ?> info : COMPARATORS) { - if (info.firstType.isAssignableFrom(firstType) && info.secondType.isAssignableFrom(secondType)) { + if (info.getFirstType().isAssignableFrom(firstType) && info.getSecondType().isAssignableFrom(secondType)) { return (ComparatorInfo<T1, T2>) info; } } // Try to match and create an InverseComparator for (ComparatorInfo<?, ?> info : COMPARATORS) { - if (info.comparator.supportsInversion() && info.firstType.isAssignableFrom(secondType) && info.secondType.isAssignableFrom(firstType)) { + if (info.getComparator().supportsInversion() && info.getFirstType().isAssignableFrom(secondType) && info.getSecondType().isAssignableFrom(firstType)) { + return new ComparatorInfo<>( + firstType, + secondType, + new InverseComparator<>((ComparatorInfo<T2, T1>) info) + ); + } + } + + // Attempt the simplest conversion (first -> second, second -> first) + if (Utils.getSuperType(firstType, secondType) == Object.class) { // ensure unrelated classes + ConverterInfo<T1, T2> fs = Converters.getConverterInfo(firstType, secondType); + if (fs != null) { + //noinspection ConstantConditions - getComparator call will never return null + return new ComparatorInfo<>( + firstType, + secondType, + new ConvertedComparator<>(fs, Comparators.getComparatorInfo(secondType, secondType), null) + ); + } + ConverterInfo<T2, T1> sf = Converters.getConverterInfo(secondType, firstType); + if (sf != null) { + //noinspection ConstantConditions - getComparator call will never return null return new ComparatorInfo<>( firstType, secondType, - new InverseComparator<>((Comparator<T2, T1>) info.comparator) + new ConvertedComparator<>(null, Comparators.getComparatorInfo(firstType, firstType), sf) ); } } @@ -222,24 +285,24 @@ private static <T1, T2, C1, C2> ComparatorInfo<T1, T2> getComparatorInfo_i( for (ComparatorInfo<?, ?> unknownInfo : COMPARATORS) { ComparatorInfo<C1, C2> info = (ComparatorInfo<C1, C2>) unknownInfo; - if (info.firstType.isAssignableFrom(firstType)) { // Attempt to convert the second argument to the second comparator type - Converter<T2, C2> sc2 = Converters.getConverter(secondType, info.secondType); + if (info.getFirstType().isAssignableFrom(firstType)) { // Attempt to convert the second argument to the second comparator type + ConverterInfo<T2, C2> sc2 = Converters.getConverterInfo(secondType, info.getSecondType()); if (sc2 != null) { return new ComparatorInfo<>( firstType, secondType, - new ConvertedComparator<>(null, info.comparator, sc2) + new ConvertedComparator<>(null, info, sc2) ); } } - if (info.secondType.isAssignableFrom(secondType)) { // Attempt to convert the first argument to the first comparator type - Converter<T1, C1> fc1 = Converters.getConverter(firstType, info.firstType); + if (info.getSecondType().isAssignableFrom(secondType)) { // Attempt to convert the first argument to the first comparator type + ConverterInfo<T1, C1> fc1 = Converters.getConverterInfo(firstType, info.getFirstType()); if (fc1 != null) { return new ComparatorInfo<>( firstType, secondType, - new ConvertedComparator<>(fc1, info.comparator, null) + new ConvertedComparator<>(fc1, info, null) ); } } @@ -248,30 +311,30 @@ private static <T1, T2, C1, C2> ComparatorInfo<T1, T2> getComparatorInfo_i( // Attempt converting one parameter but with reversed types for (ComparatorInfo<?, ?> unknownInfo : COMPARATORS) { - if (!unknownInfo.comparator.supportsInversion()) { // Unsupported for reversing types + if (!unknownInfo.getComparator().supportsInversion()) { // Unsupported for reversing types continue; } ComparatorInfo<C1, C2> info = (ComparatorInfo<C1, C2>) unknownInfo; - if (info.secondType.isAssignableFrom(firstType)) { // Attempt to convert the second argument to the first comparator type - Converter<T2, C1> sc1 = Converters.getConverter(secondType, info.firstType); + if (info.getSecondType().isAssignableFrom(firstType)) { // Attempt to convert the second argument to the first comparator type + ConverterInfo<T2, C1> sc1 = Converters.getConverterInfo(secondType, info.getFirstType()); if (sc1 != null) { return new ComparatorInfo<>( firstType, secondType, - new InverseComparator<>(new ConvertedComparator<>(sc1, info.comparator, null)) + new InverseComparator<>(new ComparatorInfo<>(secondType, firstType, new ConvertedComparator<>(sc1, info, null))) ); } } - if (info.firstType.isAssignableFrom(secondType)) { // Attempt to convert the first argument to the second comparator type - Converter<T1, C2> fc2 = Converters.getConverter(firstType, info.secondType); + if (info.getFirstType().isAssignableFrom(secondType)) { // Attempt to convert the first argument to the second comparator type + ConverterInfo<T1, C2> fc2 = Converters.getConverterInfo(firstType, info.getSecondType()); if (fc2 != null) { return new ComparatorInfo<>( firstType, secondType, - new InverseComparator<>(new ConvertedComparator<>(null, info.comparator, fc2)) + new InverseComparator<>(new ComparatorInfo<>(secondType, firstType, new ConvertedComparator<>(null, info, fc2))) ); } } @@ -282,13 +345,13 @@ private static <T1, T2, C1, C2> ComparatorInfo<T1, T2> getComparatorInfo_i( for (ComparatorInfo<?, ?> unknownInfo : COMPARATORS) { ComparatorInfo<C1, C2> info = (ComparatorInfo<C1, C2>) unknownInfo; - Converter<T1, C1> c1 = Converters.getConverter(firstType, info.firstType); - Converter<T2, C2> c2 = Converters.getConverter(secondType, info.secondType); + ConverterInfo<T1, C1> c1 = Converters.getConverterInfo(firstType, info.getFirstType()); + ConverterInfo<T2, C2> c2 = Converters.getConverterInfo(secondType, info.getSecondType()); if (c1 != null && c2 != null) { return new ComparatorInfo<>( firstType, secondType, - new ConvertedComparator<>(c1, info.comparator, c2) + new ConvertedComparator<>(c1, info, c2) ); } @@ -296,26 +359,26 @@ private static <T1, T2, C1, C2> ComparatorInfo<T1, T2> getComparatorInfo_i( // Attempt converting both parameters but with reversed types for (ComparatorInfo<?, ?> unknownInfo : COMPARATORS) { - if (!unknownInfo.comparator.supportsInversion()) { // Unsupported for reversing types + if (!unknownInfo.getComparator().supportsInversion()) { // Unsupported for reversing types continue; } ComparatorInfo<C1, C2> info = (ComparatorInfo<C1, C2>) unknownInfo; - Converter<T1, C2> c1 = Converters.getConverter(firstType, info.secondType); - Converter<T2, C1> c2 = Converters.getConverter(secondType, info.firstType); + ConverterInfo<T1, C2> c1 = Converters.getConverterInfo(firstType, info.getSecondType()); + ConverterInfo<T2, C1> c2 = Converters.getConverterInfo(secondType, info.getFirstType()); if (c1 != null && c2 != null) { return new ComparatorInfo<>( firstType, secondType, - new InverseComparator<>(new ConvertedComparator<>(c2, info.comparator, c1)) + new InverseComparator<>(new ComparatorInfo<>(secondType, firstType, new ConvertedComparator<>(c2, info, c1))) ); } } // Same class but no comparator - if (firstType != Object.class && secondType == firstType) { + if (firstType != Object.class && firstType == secondType) { return (ComparatorInfo<T1, T2>) EQUALS_COMPARATOR_INFO; } diff --git a/src/main/java/org/skriptlang/skript/lang/comparator/ConvertedComparator.java b/src/main/java/org/skriptlang/skript/lang/comparator/ConvertedComparator.java index eced387a77c..cb83d383c95 100644 --- a/src/main/java/org/skriptlang/skript/lang/comparator/ConvertedComparator.java +++ b/src/main/java/org/skriptlang/skript/lang/comparator/ConvertedComparator.java @@ -19,7 +19,9 @@ package org.skriptlang.skript.lang.comparator; import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Contract; import org.skriptlang.skript.lang.converter.Converter; +import org.skriptlang.skript.lang.converter.ConverterInfo; /** * A ConvertedComparator is a comparator that converts its parameters so that they may be used @@ -34,16 +36,17 @@ */ final class ConvertedComparator<T1, T2, C1, C2> implements Comparator<T1, T2> { - private final Comparator<C1, C2> comparator; + private final ComparatorInfo<C1, C2> comparator; @Nullable - private final Converter<T1, C1> firstConverter; + private final ConverterInfo<T1, C1> firstConverter; @Nullable - private final Converter<T2, C2> secondConverter; + private final ConverterInfo<T2, C2> secondConverter; + @Contract("null, _, null -> fail") ConvertedComparator( - @Nullable Converter<T1, C1> firstConverter, - Comparator<C1, C2> c, - @Nullable Converter<T2, C2> secondConverter + @Nullable ConverterInfo<T1, C1> firstConverter, + ComparatorInfo<C1, C2> c, + @Nullable ConverterInfo<T2, C2> secondConverter ) { if (firstConverter == null && secondConverter == null) throw new IllegalArgumentException("firstConverter and secondConverter must not BOTH be null!"); @@ -56,26 +59,26 @@ final class ConvertedComparator<T1, T2, C1, C2> implements Comparator<T1, T2> { @SuppressWarnings("unchecked") public Relation compare(T1 o1, T2 o2) { // null converter means 'comparator' is actually Comparator<T1, C2> - C1 t1 = firstConverter == null ? (C1) o1 : firstConverter.convert(o1); + C1 t1 = firstConverter == null ? (C1) o1 : firstConverter.getConverter().convert(o1); if (t1 == null) return Relation.NOT_EQUAL; // null converter means 'comparator' is actually Comparator<C1, T2> - C2 t2 = secondConverter == null ? (C2) o2 : secondConverter.convert(o2); + C2 t2 = secondConverter == null ? (C2) o2 : secondConverter.getConverter().convert(o2); if (t2 == null) return Relation.NOT_EQUAL; - return comparator.compare(t1, t2); + return comparator.getComparator().compare(t1, t2); } @Override public boolean supportsOrdering() { - return comparator.supportsOrdering(); + return comparator.getComparator().supportsOrdering(); } @Override public String toString() { - return "ConvertedComparator(" + firstConverter + "," + comparator + "," + secondConverter + ")"; + return "ConvertedComparator{first=" + firstConverter + ",comparator=" + comparator + ",second=" + secondConverter + "}"; } } diff --git a/src/main/java/org/skriptlang/skript/lang/comparator/InverseComparator.java b/src/main/java/org/skriptlang/skript/lang/comparator/InverseComparator.java index b5e22c17d8d..4207020ef65 100644 --- a/src/main/java/org/skriptlang/skript/lang/comparator/InverseComparator.java +++ b/src/main/java/org/skriptlang/skript/lang/comparator/InverseComparator.java @@ -27,20 +27,20 @@ */ final class InverseComparator<T1, T2> implements Comparator<T1, T2> { - private final Comparator<T2, T1> comparator; + private final ComparatorInfo<T2, T1> comparator; - InverseComparator(Comparator<T2, T1> comparator) { + InverseComparator(ComparatorInfo<T2, T1> comparator) { this.comparator = comparator; } @Override public Relation compare(T1 o1, T2 o2) { - return comparator.compare(o2, o1).getSwitched(); + return comparator.getComparator().compare(o2, o1).getSwitched(); } @Override public boolean supportsOrdering() { - return comparator.supportsOrdering(); + return comparator.getComparator().supportsOrdering(); } @Override @@ -50,7 +50,7 @@ public boolean supportsInversion() { @Override public String toString() { - return "InverseComparator{" + comparator + "}"; + return "InverseComparator{comparator=" + comparator + "}"; } } diff --git a/src/main/java/org/skriptlang/skript/lang/converter/ChainedConverter.java b/src/main/java/org/skriptlang/skript/lang/converter/ChainedConverter.java index 6be4fb80ce0..55a71c717b3 100644 --- a/src/main/java/org/skriptlang/skript/lang/converter/ChainedConverter.java +++ b/src/main/java/org/skriptlang/skript/lang/converter/ChainedConverter.java @@ -40,10 +40,10 @@ */ final class ChainedConverter<F, M, T> implements Converter<F, T> { - private final Converter<F, M> first; - private final Converter<M, T> second; + private final ConverterInfo<F, M> first; + private final ConverterInfo<M, T> second; - ChainedConverter(Converter<F, M> first, Converter<M, T> second) { + ChainedConverter(ConverterInfo<F, M> first, ConverterInfo<M, T> second) { this.first = first; this.second = second; } @@ -51,16 +51,34 @@ final class ChainedConverter<F, M, T> implements Converter<F, T> { @Override @Nullable public T convert(F from) { - M middle = first.convert(from); + M middle = first.getConverter().convert(from); if (middle == null) { return null; } - return second.convert(middle); + return second.getConverter().convert(middle); } @Override public String toString() { - return "ChainedConverter{first=" + first + ",second=" + second + "}"; + StringBuilder builder = new StringBuilder(); + + builder.append("ChainedConverter{("); + if (first.getConverter() instanceof ChainedConverter) { + builder.append(first.getConverter()); + } else { + builder.append(first.getFrom()).append(" -> ").append(first.getTo()); + } + + builder.append(") -> ("); + + if (second.getConverter() instanceof ChainedConverter) { + builder.append(second.getConverter()); + } else { + builder.append(second.getFrom()).append(" -> ").append(second.getTo()); + } + builder.append(")}"); + + return builder.toString(); } } diff --git a/src/main/java/org/skriptlang/skript/lang/converter/Converter.java b/src/main/java/org/skriptlang/skript/lang/converter/Converter.java index 32bf8aa6c06..7ffe65f43c5 100644 --- a/src/main/java/org/skriptlang/skript/lang/converter/Converter.java +++ b/src/main/java/org/skriptlang/skript/lang/converter/Converter.java @@ -30,24 +30,24 @@ public interface Converter<F, T> { /** - * A Converter flag declaring that this Converter may be chained in any way with another Converter. + * A Converter flag declaring that this Converter can be any part of a chain. */ int ALL_CHAINING = 0; /** - * A Converter flag declaring that another Converter cannot be chained to this Converter. + * A Converter flag declaring this Converter cannot be chained to another Converter. * This means that this Converter must be the beginning of a chain. */ int NO_LEFT_CHAINING = 1; /** - * A Converter flag declaring this Converter cannot be chained to another Converter. + * A Converter flag declaring that another Converter cannot be chained to this Converter. * This means that this Converter must be the end of a chain. */ int NO_RIGHT_CHAINING = 2; /** - * A Converter flag declaring that this Converter cannot be chained in any way with another Converter. + * A Converter flag declaring that this Converter cannot be a part of a chain. */ int NO_CHAINING = NO_LEFT_CHAINING | NO_RIGHT_CHAINING; diff --git a/src/main/java/org/skriptlang/skript/lang/converter/ConverterInfo.java b/src/main/java/org/skriptlang/skript/lang/converter/ConverterInfo.java index 8faa29c1419..1b00d7041a3 100644 --- a/src/main/java/org/skriptlang/skript/lang/converter/ConverterInfo.java +++ b/src/main/java/org/skriptlang/skript/lang/converter/ConverterInfo.java @@ -56,7 +56,7 @@ public int getFlags() { @Override public String toString() { - return "ConverterInfo{from=" + from + ",to=" + to + ",converter=" + converter + ",flag=" + flags + "}"; + return "ConverterInfo{from=" + from + ",to=" + to + ",converter=" + converter + ",flags=" + flags + "}"; } } diff --git a/src/main/java/org/skriptlang/skript/lang/converter/Converters.java b/src/main/java/org/skriptlang/skript/lang/converter/Converters.java index e8d6a98f8e9..1f97983fab8 100644 --- a/src/main/java/org/skriptlang/skript/lang/converter/Converters.java +++ b/src/main/java/org/skriptlang/skript/lang/converter/Converters.java @@ -66,30 +66,30 @@ private Converters() {} /** * Registers a new Converter with Skript's collection of Converters. - * @param from The type to convert from. - * @param to The type to convert to. - * @param converter A Converter for converting objects of type 'from' to type 'to'. + * @param fromType The type to convert from. + * @param toType The type to convert to. + * @param converter A Converter for converting objects of <code>fromType</code> to <code>toType</code>. */ - public static <F, T> void registerConverter(Class<F> from, Class<T> to, Converter<F, T> converter) { - registerConverter(from, to, converter, Converter.ALL_CHAINING); + public static <F, T> void registerConverter(Class<F> fromType, Class<T> toType, Converter<F, T> converter) { + registerConverter(fromType, toType, converter, Converter.ALL_CHAINING); } /** * Registers a new Converter with Skript's collection of Converters. - * @param from The type to convert from. - * @param to The type to convert to. - * @param converter A Converter for converting objects of type 'from' to type 'to'. + * @param fromType The type to convert from. + * @param toType The type to convert to. + * @param converter A Converter for converting objects of <code>fromType</code> to <code>toType</code>. * @param flags Flags to set for the Converter. Flags can be found under {@link Converter}. */ - public static <F, T> void registerConverter(Class<F> from, Class<T> to, Converter<F, T> converter, int flags) { + public static <F, T> void registerConverter(Class<F> fromType, Class<T> toType, Converter<F, T> converter, int flags) { Skript.checkAcceptRegistrations(); - ConverterInfo<F, T> info = new ConverterInfo<>(from, to, converter, flags); + ConverterInfo<F, T> info = new ConverterInfo<>(fromType, toType, converter, flags); synchronized (CONVERTERS) { - if (exactConverterExists(from, to)) { + if (exactConverterExists_i(fromType, toType)) { throw new SkriptAPIException( - "A Converter from '" + from + "' to '" + to + "' already exists!" + "A Converter from '" + fromType + "' to '" + toType + "' already exists!" ); } CONVERTERS.add(info); @@ -117,10 +117,11 @@ public static <F, M, T> void createChainedConverters() { // chain info -> info2 if ( unknownInfo2.getFrom() != Object.class // Object can only exist at the beginning of a chain + && unknownInfo1.getFrom() != unknownInfo2.getTo() && (unknownInfo1.getFlags() & Converter.NO_RIGHT_CHAINING) == 0 && (unknownInfo2.getFlags() & Converter.NO_LEFT_CHAINING) == 0 && unknownInfo2.getFrom().isAssignableFrom(unknownInfo1.getTo()) - && !exactConverterExists(unknownInfo1.getFrom(), unknownInfo2.getTo()) + && !exactConverterExists_i(unknownInfo1.getFrom(), unknownInfo2.getTo()) ) { ConverterInfo<F, M> info1 = (ConverterInfo<F, M>) unknownInfo1; ConverterInfo<M, T> info2 = (ConverterInfo<M, T>) unknownInfo2; @@ -128,7 +129,7 @@ public static <F, M, T> void createChainedConverters() { CONVERTERS.add(new ConverterInfo<>( info1.getFrom(), info2.getTo(), - new ChainedConverter<>(info1.getConverter(), info2.getConverter()), + new ChainedConverter<>(info1, info2), info1.getFlags() | info2.getFlags() )); } @@ -136,10 +137,11 @@ public static <F, M, T> void createChainedConverters() { // chain info2 -> info else if ( unknownInfo1.getFrom() != Object.class // Object can only exist at the beginning of a chain + && unknownInfo2.getFrom() != unknownInfo1.getTo() && (unknownInfo1.getFlags() & Converter.NO_LEFT_CHAINING) == 0 && (unknownInfo2.getFlags() & Converter.NO_RIGHT_CHAINING) == 0 && unknownInfo1.getFrom().isAssignableFrom(unknownInfo2.getTo()) - && !exactConverterExists(unknownInfo2.getFrom(), unknownInfo1.getTo()) + && !exactConverterExists_i(unknownInfo2.getFrom(), unknownInfo1.getTo()) ) { ConverterInfo<M, T> info1 = (ConverterInfo<M, T>) unknownInfo1; ConverterInfo<F, M> info2 = (ConverterInfo<F, M>) unknownInfo2; @@ -147,7 +149,7 @@ else if ( CONVERTERS.add(new ConverterInfo<>( info2.getFrom(), info1.getTo(), - new ChainedConverter<>(info2.getConverter(), info1.getConverter()), + new ChainedConverter<>(info2, info1), info2.getFlags() | info1.getFlags() )); } @@ -162,9 +164,9 @@ else if ( * Internal method. All calling locations are expected to manually synchronize this method if necessary. * @return Whether a Converter exists that EXACTLY matches the provided types. */ - private static boolean exactConverterExists(Class<?> from, Class<?> to) { + private static boolean exactConverterExists_i(Class<?> fromType, Class<?> toType) { for (ConverterInfo<?, ?> info : CONVERTERS) { - if (from == info.getFrom() && to == info.getTo()) { + if (fromType == info.getFrom() && toType == info.getTo()) { return true; } } @@ -172,18 +174,39 @@ private static boolean exactConverterExists(Class<?> from, Class<?> to) { } /** - * @return Whether a Converter capable of converting 'fromType' to 'toType' exists. + * A method for determining whether a direct Converter of <code>fromType</code> to <code>toType</code> exists. + * Unlike other methods of this class, it is not the case that + * {@link Skript#isAcceptRegistrations()} must return <code>false</code> for this method to be used. + * @param fromType The type to convert from. + * @param toType The type to convert to. + * @return Whether a direct Converter of <code>fromType</code> to <code>toType</code> exists. + */ + public static boolean exactConverterExists(Class<?> fromType, Class<?> toType) { + synchronized (CONVERTERS) { + return exactConverterExists_i(fromType, toType); + } + } + + /** + * A method for determining whether a Converter of <code>fromType</code> to <code>toType</code> exists. + * @param fromType The type to convert from. + * @param toType The type to convert to. + * @return Whether a Converter of <code>fromType</code> to <code>toType</code> exists. */ public static boolean converterExists(Class<?> fromType, Class<?> toType) { assertIsDoneLoading(); - if (toType.isAssignableFrom(fromType) || fromType.isAssignableFrom(toType)) { + if (toType.isAssignableFrom(fromType)) { // no converter needed return true; } return getConverter(fromType, toType) != null; } /** - * @return Whether a Converter capable of converting 'fromType' to one of the provided 'toTypes' exists. + * A method for determining whether a direct Converter of <code>fromType</code> to + * one of the provided <code>toTypes</code> exists. + * @param fromType The type to convert from. + * @param toTypes The types to attempt converting to. + * @return Whether a Converter of <code>fromType</code> to one of the provided <code>toTypes</code> exists. */ public static boolean converterExists(Class<?> fromType, Class<?>... toTypes) { assertIsDoneLoading(); @@ -196,10 +219,10 @@ public static boolean converterExists(Class<?> fromType, Class<?>... toTypes) { } /** - * A method for obtaining a Converter that can convert an object of 'fromType' into an object of 'toType'. + * A method for obtaining a Converter that can convert an object of <code>fromType</code> into an object of <code>toType</code>. * @param fromType The type to convert from. * @param toType The type to convert to. - * @return A Converter capable of converting an object of 'fromType' into an object of 'toType'. + * @return A Converter capable of converting an object of <code>fromType</code> into an object of <code>toType</code>. * Will return null if no such Converter exists. */ @Nullable @@ -210,10 +233,10 @@ public static <F, T> Converter<F, T> getConverter(Class<F> fromType, Class<T> to /** * A method for obtaining the ConverterInfo of a Converter that can convert - * an object of 'fromType' into an object of 'toType'. + * an object of <code>fromType</code> into an object of <code>toType</code>. * @param fromType The type to convert from. * @param toType The type to convert to. - * @return The ConverterInfo of a Converter capable of converting an object of 'fromType' into an object of 'toType'. + * @return The ConverterInfo of a Converter capable of converting an object of <code>fromType</code> into an object of <code>toType</code>. * Will return null if no such Converter exists. */ @Nullable @@ -238,18 +261,18 @@ public static <F, T> ConverterInfo<F, T> getConverterInfo(Class<F> fromType, Cla /** * The internal method for obtaining the ConverterInfo of a Converter that can convert - * an object of 'fromType' into an object of 'toType'. + * an object of <code>fromType</code> into an object of <code>toType</code>. * * @param fromType The type to convert from. * @param toType The type to convert to. - * @return The ConverterInfo of a Converter capable of converting an object of 'fromType' into an object of 'toType'. + * @return The ConverterInfo of a Converter capable of converting an object of <code>fromType</code> into an object of <code>toType</code>. * Will return null if no such Converter exists. * * @param <F> The type to convert from. * @param <T> The type to convert to. - * @param <SubType> The 'fromType' for a Converter that may only convert certain objects of 'fromType' - * @param <ParentType> The 'toType' for a Converter that may only sometimes convert objects of 'fromType' - * into objects of 'toType' (e.g. the converted object may only share a parent with 'toType') + * @param <SubType> The <code>fromType</code> for a Converter that may only convert certain objects of <code>fromType</code> + * @param <ParentType> The <code>toType</code> for a Converter that may only sometimes convert objects of <code>fromType</code> + * into objects of <code>toType</code> (e.g. the converted object may only share a parent with <code>toType</code>) */ @Nullable @SuppressWarnings("unchecked") @@ -300,7 +323,7 @@ private static <F, T extends ParentType, SubType extends F, ParentType> Converte // 'from' doesn't exactly match and needs to be filtered // Basically, this converter will only convert certain 'F' objects return new ConverterInfo<>(fromType, toType, fromObject -> { - if (!info.getFrom().isInstance(fromType)) { + if (!info.getFrom().isInstance(fromObject)) { return null; } return info.getConverter().convert((SubType) fromObject); @@ -339,8 +362,8 @@ private static <F, T extends ParentType, SubType extends F, ParentType> Converte /** * Standard method for converting an object into a different type. * @param from The object to convert. - * @param toType The type that 'from' should be converted into. - * @return An object of 'toType', or null if 'from' couldn't be successfully converted. + * @param toType The type that <code>from</code> should be converted into. + * @return An object of <code>toType</code>, or null if <code>from</code> couldn't be successfully converted. */ @Nullable @SuppressWarnings("unchecked") @@ -365,8 +388,8 @@ public static <From, To> To convert(@Nullable From from, Class<To> toType) { /** * A method for converting an object into one of several provided types. * @param from The object to convert. - * @param toTypes A list of types that should be tried for converting 'from'. - * @return An object of one of the provided 'toTypes', or null if 'from' couldn't successfully be converted. + * @param toTypes A list of types that should be tried for converting <code>from</code>. + * @return An object of one of the provided <code>toTypes</code>, or null if <code>from</code> couldn't successfully be converted. */ @Nullable @SuppressWarnings("unchecked") @@ -396,10 +419,10 @@ public static <From, To> To convert(@Nullable From from, Class<? extends To>[] t /** * Standard method for bulk-conversion of objects into a different type. * @param from The objects to convert. - * @param toType The type that 'from' should be converted into. - * @return Objects of 'toType'. Will return null if 'from' is null. - * Please note that the returned array may not be the same size as 'from'. - * This can happen if an object contained within 'from' is not successfully converted. + * @param toType The type that <code>from</code> should be converted into. + * @return Objects of <code>toType</code>. Will return null if <code>from</code> is null. + * Please note that the returned array may not be the same size as <code>from</code>. + * This can happen if an object contained within <code>from</code> is not successfully converted. */ @SuppressWarnings("unchecked") public static <To> To[] convert(Object @Nullable [] from, Class<To> toType) { @@ -427,10 +450,10 @@ public static <To> To[] convert(Object @Nullable [] from, Class<To> toType) { * A method for bulk-conversion of objects into one of several provided types. * @param from The objects to convert. * @param toTypes A list of types that should be tried for converting each object. - * @param superType A parent type of all provided 'toTypes'. - * @return Objects of 'superType'. Will return any empty array if 'from' is null. - * Please note that the returned array may not be the same size as 'from'. - * This can happen if an object contained within 'from' is not successfully converted. + * @param superType A parent type of all provided <code>toTypes</code>. + * @return Objects of <code>superType</code>. Will return any empty array if <code>from</code> is null. + * Please note that the returned array may not be the same size as <code>from</code>. + * This can happen if an object contained within <code>from</code> is not successfully converted. * And, of course, the returned array may contain objects of a different type. */ @SuppressWarnings("unchecked") @@ -464,9 +487,9 @@ public static <To> To[] convert(Object @Nullable [] from, Class<? extends To>[] * @param from The objects to convert. * @param toType The type to convert into. * @param converter The converter to use for conversion. - * @return Objects of 'toType'. - * Please note that the returned array may not be the same size as 'from'. - * This can happen if an object contained within 'from' is not successfully converted. + * @return Objects of <code>toType</code>. + * Please note that the returned array may not be the same size as <code>from</code>. + * This can happen if an object contained within <code>from</code> is not successfully converted. */ @SuppressWarnings("unchecked") public static <From, To> To[] convert(From[] from, Class<To> toType, Converter<? super From, ? extends To> converter) { @@ -489,11 +512,11 @@ public static <From, To> To[] convert(From[] from, Class<To> toType, Converter<? } /** - * A method that guarantees an object of 'toType' is returned. + * A method that guarantees an object of <code>toType</code> is returned. * @param from The object to convert. * @param toType The type to convert into. - * @return An object of 'toType'. - * @throws ClassCastException If 'from' cannot be converted. + * @return An object of <code>toType</code>. + * @throws ClassCastException If <code>from</code> cannot be converted. */ public static <To> To convertStrictly(Object from, Class<To> toType) { To converted = convert(from, toType); @@ -504,10 +527,10 @@ public static <To> To convertStrictly(Object from, Class<To> toType) { } /** - * A method for bulk-conversion that guarantees objects of 'toType' are returned. + * A method for bulk-conversion that guarantees objects of <code>toType</code> are returned. * @param from The object to convert. * @param toType The type to convert into. - * @return Objects of 'toType'. The returned array will be the same size as 'from'. + * @return Objects of <code>toType</code>. The returned array will be the same size as <code>from</code>. * @throws ClassCastException If any of the provided objects cannot be converted. */ @SuppressWarnings("unchecked") @@ -531,11 +554,11 @@ public static <To> To[] convertStrictly(Object[] from, Class<To> toType) { * @param from The objects to convert. * @param toType A superclass for all objects to be converted. * @param converter The converter to use for conversion. - * @return Objects of 'toType'. - * Please note that the returned array may not be the same size as 'from'. - * This can happen if an object contained within 'from' is not successfully converted. - * @throws ArrayStoreException If 'toType' is not a superclass of all objects returned by the converter. - * @throws ClassCastException If 'toType' is not of 'T'. + * @return Objects of <code>toType</code>. + * Please note that the returned array may not be the same size as <code>from</code>. + * This can happen if an object contained within <code>from</code> is not successfully converted. + * @throws ArrayStoreException If <code>toType</code> is not a superclass of all objects returned by the converter. + * @throws ClassCastException If <code>toType</code> is not of <code>To</code>. */ @SuppressWarnings("unchecked") public static <From, To> To[] convertUnsafe(From[] from, Class<?> toType, Converter<? super From, ? extends To> converter) { diff --git a/src/test/skript/tests/regressions/5848-converter comparator issues.sk b/src/test/skript/tests/regressions/5848-converter comparator issues.sk new file mode 100644 index 00000000000..d59ca71d9a5 --- /dev/null +++ b/src/test/skript/tests/regressions/5848-converter comparator issues.sk @@ -0,0 +1,16 @@ +test "5848-converter comparator issues": + + # a little oopsie (oops) + loop 2 and "dog": + if loop-value is not 2 or "dog": + assert true is false with "loop-value was not '2' or 'dog' (got '%loop-value%')!" + + # the byte bites back! + loop 256 and "dog": + if loop-value is 0: + assert true is false with "loop-value was somehow '0'!?" + + # curse you time type + loop 9 hours and 2pm: + if loop-value is not 9 hours or 2pm: + assert true is false with "loop-value was not '9 hours' or '2pm' (got '%loop-value%')!" From e1216bfe6ddf4c67ddb0b497a4efa01b68c07f56 Mon Sep 17 00:00:00 2001 From: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> Date: Tue, 19 Dec 2023 00:26:17 -0600 Subject: [PATCH 535/619] Add parse section for testing (#5440) * Add SecParse * Address reviews * Address reviews * Locate new test syntaxes into the correct package * Apply changes * Locate new test syntaxes into the correct package * Format --------- Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Co-authored-by: TheLimeGlass <seantgrover@gmail.com> --- .../njol/skript/log/RetainingLogHandler.java | 2 + .../skript/test/runner/ExprParseLogs.java | 68 +++++++++++++++ .../ch/njol/skript/test/runner/SecParse.java | 86 +++++++++++++++++++ .../tests/syntaxes/sections/SecParse.sk | 8 ++ 4 files changed, 164 insertions(+) create mode 100644 src/main/java/ch/njol/skript/test/runner/ExprParseLogs.java create mode 100644 src/main/java/ch/njol/skript/test/runner/SecParse.java create mode 100644 src/test/skript/tests/syntaxes/sections/SecParse.sk diff --git a/src/main/java/ch/njol/skript/log/RetainingLogHandler.java b/src/main/java/ch/njol/skript/log/RetainingLogHandler.java index 35177f1c8f4..6f0a9bb97b1 100644 --- a/src/main/java/ch/njol/skript/log/RetainingLogHandler.java +++ b/src/main/java/ch/njol/skript/log/RetainingLogHandler.java @@ -179,6 +179,8 @@ public int size() { @SuppressWarnings("null") public Collection<LogEntry> getLog() { + // if something is grabbing the log entries, they're probably handling them manually + printedErrorOrLog = true; return Collections.unmodifiableCollection(log); } diff --git a/src/main/java/ch/njol/skript/test/runner/ExprParseLogs.java b/src/main/java/ch/njol/skript/test/runner/ExprParseLogs.java new file mode 100644 index 00000000000..192240b623b --- /dev/null +++ b/src/main/java/ch/njol/skript/test/runner/ExprParseLogs.java @@ -0,0 +1,68 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.test.runner; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.NoDoc; +import ch.njol.skript.lang.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 org.bukkit.event.Event; + +import javax.annotation.Nullable; + +@Name("Parse Logs") +@Description("Returns the last known parse logs from a parse section, if any.") +@NoDoc +public class ExprParseLogs extends SimpleExpression<String> { + + static { + Skript.registerExpression(ExprParseLogs.class, String.class, ExpressionType.SIMPLE, "[the] [last] parse logs"); + } + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + return true; + } + + @Override + protected String[] get(Event event) { + return SecParse.lastLogs; + } + + @Override + public boolean isSingle() { + return false; + } + + @Override + public Class<? extends String> getReturnType() { + return String.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "last parse logs"; + } + +} diff --git a/src/main/java/ch/njol/skript/test/runner/SecParse.java b/src/main/java/ch/njol/skript/test/runner/SecParse.java new file mode 100644 index 00000000000..834ed010fb6 --- /dev/null +++ b/src/main/java/ch/njol/skript/test/runner/SecParse.java @@ -0,0 +1,86 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.test.runner; + +import ch.njol.skript.Skript; +import ch.njol.skript.config.SectionNode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.NoDoc; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.Section; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.TriggerItem; +import ch.njol.skript.log.LogEntry; +import ch.njol.skript.log.RetainingLogHandler; +import ch.njol.skript.log.SkriptLogger; +import ch.njol.util.Kleenean; +import com.google.common.collect.Iterables; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.List; + +@Name("Parse Section") +@Description("Parse code inside this section and use 'parse logs' to grab any logs from it.") +@NoDoc +public class SecParse extends Section { + + static { + Skript.registerSection(SecParse.class, "parse"); + } + + @Nullable + public static String[] lastLogs; + private String[] logs; + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult, SectionNode sectionNode, List<TriggerItem> triggerItems) { + if (Iterables.size(sectionNode) == 0) { + Skript.error("A parse section must contain code"); + return false; + } + + RetainingLogHandler handler = SkriptLogger.startRetainingLog(); + // we need to do this before loadCode because loadCode will add this section to the current sections + boolean inParseSection = getParser().isCurrentSection(SecParse.class); + loadCode(sectionNode); + if (!inParseSection) { + // only store logs if we're not in another parse section. + // this way you can access the parse logs of the outermost parse section + logs = handler.getLog().stream() + .map(LogEntry::getMessage) + .toArray(String[]::new); + } + handler.stop(); + return true; + } + + @Override + protected @Nullable TriggerItem walk(Event event) { + lastLogs = logs; + return walk(event, false); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "parse"; + } + +} diff --git a/src/test/skript/tests/syntaxes/sections/SecParse.sk b/src/test/skript/tests/syntaxes/sections/SecParse.sk new file mode 100644 index 00000000000..4478e781ff3 --- /dev/null +++ b/src/test/skript/tests/syntaxes/sections/SecParse.sk @@ -0,0 +1,8 @@ +test "parsing section": + parse: + parse: + # intentionally empty to cause an error + assert last parse logs contain "A parse section must contain code" with "Parse section didn't return expected error" + parse: + set {_x} to {_y} # valid code + assert last parse logs is not set with "Parse section contained errors when code was valid" From cc19ac074f37193d93cf132c05b5c992d8527705 Mon Sep 17 00:00:00 2001 From: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> Date: Tue, 19 Dec 2023 00:42:27 -0600 Subject: [PATCH 536/619] Add glowing sign text support (#5367) * Add glowing text syntaxes * Fix example and only register when supported * Address reviews * Address reviews * Address reviews * Address reviews * Address reviews * Update src/main/java/ch/njol/skript/effects/EffGlowingText.java Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> --------- Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Co-authored-by: Moderocky <admin@moderocky.com> --- .../skript/conditions/CondGlowingText.java | 71 ++++++++++++++ .../njol/skript/effects/EffGlowingText.java | 98 +++++++++++++++++++ .../tests/syntaxes/effects/EffGlowingText.sk | 18 ++++ 3 files changed, 187 insertions(+) create mode 100644 src/main/java/ch/njol/skript/conditions/CondGlowingText.java create mode 100644 src/main/java/ch/njol/skript/effects/EffGlowingText.java create mode 100644 src/test/skript/tests/syntaxes/effects/EffGlowingText.sk diff --git a/src/main/java/ch/njol/skript/conditions/CondGlowingText.java b/src/main/java/ch/njol/skript/conditions/CondGlowingText.java new file mode 100644 index 00000000000..7d12e477712 --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondGlowingText.java @@ -0,0 +1,71 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.conditions; + +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.ItemType; +import org.bukkit.block.Block; + +import ch.njol.skript.conditions.base.PropertyCondition; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import org.bukkit.block.BlockState; +import org.bukkit.block.Sign; +import org.bukkit.inventory.meta.BlockStateMeta; +import org.bukkit.inventory.meta.ItemMeta; + +@Name("Has Glowing Text") +@Description("Checks whether a sign (either a block or an item) has glowing text") +@Examples("if target block has glowing text") +@Since("INSERT VERSION") +public class CondGlowingText extends PropertyCondition<Object> { + + static { + if (Skript.methodExists(Sign.class, "isGlowingText")) + register(CondGlowingText.class, PropertyType.HAVE, "glowing text", "blocks/itemtypes"); + } + + @Override + public boolean check(Object obj) { + if (obj instanceof Block) { + BlockState state = ((Block) obj).getState(); + return state instanceof Sign && ((Sign) state).isGlowingText(); + } else if (obj instanceof ItemType) { + ItemMeta meta = ((ItemType) obj).getItemMeta(); + if (meta instanceof BlockStateMeta) { + BlockState state = ((BlockStateMeta) meta).getBlockState(); + return state instanceof Sign && ((Sign) state).isGlowingText(); + } + } + return false; + } + + @Override + protected PropertyType getPropertyType() { + return PropertyType.HAVE; + } + + @Override + protected String getPropertyName() { + return "glowing text"; + } + +} diff --git a/src/main/java/ch/njol/skript/effects/EffGlowingText.java b/src/main/java/ch/njol/skript/effects/EffGlowingText.java new file mode 100644 index 00000000000..576a2d58348 --- /dev/null +++ b/src/main/java/ch/njol/skript/effects/EffGlowingText.java @@ -0,0 +1,98 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.effects; + +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.block.Sign; +import org.bukkit.event.Event; +import org.bukkit.inventory.meta.BlockStateMeta; +import org.bukkit.inventory.meta.ItemMeta; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Make Sign Glow") +@Description("Makes a sign (either a block or item) have glowing text or normal text") +@Examples("make target block of player have glowing text") +@Since("INSERT VERSION") +public class EffGlowingText extends Effect { + + static { + if (Skript.methodExists(Sign.class, "setGlowingText", boolean.class)) { + Skript.registerEffect(EffGlowingText.class, + "make %blocks/itemtypes% have glowing text", + "make %blocks/itemtypes% have (normal|non[-| ]glowing) text" + ); + } + } + + @SuppressWarnings("NotNullFieldNotInitialized") + private Expression<?> objects; + + private boolean glowing; + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + objects = exprs[0]; + glowing = matchedPattern == 0; + return true; + } + + @Override + protected void execute(Event event) { + for (Object obj : objects.getArray(event)) { + if (obj instanceof Block) { + BlockState state = ((Block) obj).getState(); + if (state instanceof Sign) { + ((Sign) state).setGlowingText(glowing); + state.update(); + } + } else if (obj instanceof ItemType) { + ItemType item = (ItemType) obj; + ItemMeta meta = item.getItemMeta(); + if (!(meta instanceof BlockStateMeta)) + return; + BlockStateMeta blockMeta = (BlockStateMeta) meta; + BlockState state = blockMeta.getBlockState(); + if (!(state instanceof Sign)) + return; + ((Sign) state).setGlowingText(glowing); + state.update(); + blockMeta.setBlockState(state); + item.setItemMeta(meta); + } + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "make " + objects.toString(event, debug) + " have " + (glowing ? "glowing text" : "normal text"); + } + +} diff --git a/src/test/skript/tests/syntaxes/effects/EffGlowingText.sk b/src/test/skript/tests/syntaxes/effects/EffGlowingText.sk new file mode 100644 index 00000000000..b43a5786755 --- /dev/null +++ b/src/test/skript/tests/syntaxes/effects/EffGlowingText.sk @@ -0,0 +1,18 @@ +test "glowing sign blocks" when running minecraft "1.17.1": + set {_loc} to spawn of "world" + set {_original block} to type of block at {_loc} + set block at {_loc} to sign + assert block at {_loc} doesn't have glowing text with "Sign had glowing text erroneously (1)" + make block at {_loc} have glowing text + assert block at {_loc} has glowing text with "Sign had normal text erroneously" + make block at {_loc} have normal text + assert block at {_loc} doesn't have glowing text with "Sign had glowing text erroneously (2)" + set block at {_loc} to {_original block} + +test "glowing sign items" when running minecraft "1.17.1": + set {_sign} to sign + assert {_sign} doesn't have glowing text with "Sign had glowing text erroneously (1)" + make {_sign} have glowing text + assert {_sign} has glowing text with "Sign had normal text erroneously" + make {_sign} have normal text + assert {_sign} doesn't have glowing text with "Sign had glowing text erroneously (2)" From 2b6191b7472e0bd87b10f36934dcf540202acce8 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Tue, 19 Dec 2023 14:10:45 -0800 Subject: [PATCH 537/619] Active Item API (#5365) * initial * MaxUseTime and pivot to ActiveItem * cancel item use * Update ExprActiveItem.java * Clean up + Docs * i'm a fool, forgot two checks * Apply suggestions from code review Co-authored-by: _tud <mmbakkar06@gmail.com> Co-authored-by: Ayham Al Ali <alali_ayham@yahoo.com> * More requested changes * Apply suggestions from code review Co-authored-by: Ayham Al Ali <alali_ayham@yahoo.com> * fix doc brackets, import, and required version tag * Apply suggestions from code review Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> * Rest of suggested changes * Requested Changes * PlayerStopUsingItemEvent * Add PlayerReadyArrowEvent * Create ExprConsumedItem.java Without a changer for now, as Spigot is bugged. * Delete ExprConsumedItem.java * Requested Changes * Update ExprEntityItemUseTime.java * Update BukkitEventValues.java * Apply suggestions from code review Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> * Update src/main/java/ch/njol/skript/conditions/CondIsHandRaised.java Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Patrick Miller <apickledwalrus@gmail.com> * Requested Changes * Update src/main/java/ch/njol/skript/conditions/CondIsHandRaised.java Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: _tud <mmbakkar06@gmail.com> * Suggestions and Fix CondIsHandRaised * Suggested ready syntax * Apply suggestions from code review Co-authored-by: Patrick Miller <apickledwalrus@gmail.com> * requested changes * Update EffCancelItemUse.java * Update src/main/java/ch/njol/skript/effects/EffCancelItemUse.java Co-authored-by: Patrick Miller <apickledwalrus@gmail.com> --------- Co-authored-by: _tud <mmbakkar06@gmail.com> Co-authored-by: Ayham Al Ali <alali_ayham@yahoo.com> Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Co-authored-by: Patrick Miller <apickledwalrus@gmail.com> Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> --- .../classes/data/BukkitEventValues.java | 18 ++++ .../skript/conditions/CondIsHandRaised.java | 97 +++++++++++++++++++ .../njol/skript/effects/EffCancelItemUse.java | 77 +++++++++++++++ .../ch/njol/skript/events/SimpleEvents.java | 48 +++++++-- .../skript/expressions/ExprActiveItem.java | 70 +++++++++++++ .../expressions/ExprEntityItemUseTime.java | 80 +++++++++++++++ .../expressions/ExprMaxItemUseTime.java | 67 +++++++++++++ .../skript/expressions/ExprReadiedArrow.java | 91 +++++++++++++++++ 8 files changed, 540 insertions(+), 8 deletions(-) create mode 100644 src/main/java/ch/njol/skript/conditions/CondIsHandRaised.java create mode 100644 src/main/java/ch/njol/skript/effects/EffCancelItemUse.java create mode 100644 src/main/java/ch/njol/skript/expressions/ExprActiveItem.java create mode 100644 src/main/java/ch/njol/skript/expressions/ExprEntityItemUseTime.java create mode 100644 src/main/java/ch/njol/skript/expressions/ExprMaxItemUseTime.java create mode 100644 src/main/java/ch/njol/skript/expressions/ExprReadiedArrow.java 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 5477fd95a45..1543a5c2d1d 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -37,12 +37,14 @@ import ch.njol.skript.util.Direction; import ch.njol.skript.util.EnchantmentType; import ch.njol.skript.util.Getter; +import ch.njol.skript.util.Timespan; import ch.njol.skript.util.slot.InventorySlot; import ch.njol.skript.util.slot.Slot; import com.destroystokyo.paper.event.block.AnvilDamagedEvent; import com.destroystokyo.paper.event.entity.ProjectileCollideEvent; import com.destroystokyo.paper.event.player.PlayerArmorChangeEvent; import io.papermc.paper.event.entity.EntityMoveEvent; +import io.papermc.paper.event.player.PlayerStopUsingItemEvent; import io.papermc.paper.event.player.PlayerInventorySlotChangeEvent; import io.papermc.paper.event.player.PlayerStonecutterRecipeSelectEvent; import io.papermc.paper.event.player.PlayerTradeEvent; @@ -1675,6 +1677,22 @@ public Egg get(PlayerEggThrowEvent event) { } }, EventValues.TIME_NOW); + // PlayerStopUsingItemEvent + if (Skript.classExists("io.papermc.paper.event.player.PlayerStopUsingItemEvent")) { + EventValues.registerEventValue(PlayerStopUsingItemEvent.class, Timespan.class, new Getter<Timespan, PlayerStopUsingItemEvent>() { + @Override + public Timespan get(PlayerStopUsingItemEvent event) { + return Timespan.fromTicks(event.getTicksHeldFor()); + } + }, EventValues.TIME_NOW); + EventValues.registerEventValue(PlayerStopUsingItemEvent.class, ItemType.class, new Getter<ItemType, PlayerStopUsingItemEvent>() { + @Override + public ItemType get(PlayerStopUsingItemEvent event) { + return new ItemType(event.getItem()); + } + }, EventValues.TIME_NOW); + } + // LootGenerateEvent if (Skript.classExists("org.bukkit.event.world.LootGenerateEvent")) { EventValues.registerEventValue(LootGenerateEvent.class, Entity.class, new Getter<Entity, LootGenerateEvent>() { diff --git a/src/main/java/ch/njol/skript/conditions/CondIsHandRaised.java b/src/main/java/ch/njol/skript/conditions/CondIsHandRaised.java new file mode 100644 index 00000000000..897b7f6b351 --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondIsHandRaised.java @@ -0,0 +1,97 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.conditions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Condition; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Event; +import org.bukkit.inventory.EquipmentSlot; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Is Hand Raised") +@Description({ + "Checks whether an entity has one or both of their hands raised.", + "Hands are raised when an entity is using an item (eg: blocking, drawing a bow, eating)." +}) +@Examples({ + "on damage of player:", + "\tif victim's main hand is raised:", + "\t\tdrop player's tool at player", + "\t\tset player's tool to air" +}) +@Since("INSERT VERSION") +@RequiredPlugins("Paper") +public class CondIsHandRaised extends Condition { + + static { + Skript.registerCondition(CondIsHandRaised.class, + "%livingentities%'[s] [:main] hand[s] (is|are) raised", + "%livingentities%'[s] [:main] hand[s] (isn't|is not|aren't|are not) raised", + "[:main] hand[s] of %livingentities% (is|are) raised", + "[:main] hand[s] of %livingentities% (isn't|is not|aren't|are not) raised", + + "%livingentities%'[s] off[ |-]hand[s] (is|are) raised", + "%livingentities%'[s] off[ |-]hand[s] (isn't|is not|aren't|are not) raised", + "off[ |-]hand[s] of %livingentities% (is|are) raised", + "off[ |-]hand[s] of %livingentities% (isn't|is not|aren't|are not) raised" + ); + } + + private Expression<LivingEntity> entities; + @Nullable + private EquipmentSlot hand; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + entities = (Expression<LivingEntity>) exprs[0]; + setNegated(matchedPattern % 2 == 1); + if (matchedPattern >= 4) { + hand = EquipmentSlot.OFF_HAND; + } else if (parseResult.hasTag("main")) { + hand = EquipmentSlot.HAND; + } + return true; + } + + @Override + public boolean check(Event event) { + // True if hand is raised AND hand matches the hand we're checking for (null for both) + return entities.check(event, livingEntity -> + livingEntity.isHandRaised() && ((hand == null) || livingEntity.getHandRaised().equals(hand)), + isNegated() + ); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return entities.toString(event, debug) + "'s " + (hand == null ? "" : (hand == EquipmentSlot.HAND ? "main " : "off ")) + "hand" + + (entities.isSingle() ? " is" : "s are") + (isNegated() ? " not " : "") + " raised"; + } + +} diff --git a/src/main/java/ch/njol/skript/effects/EffCancelItemUse.java b/src/main/java/ch/njol/skript/effects/EffCancelItemUse.java new file mode 100644 index 00000000000..bda21e00683 --- /dev/null +++ b/src/main/java/ch/njol/skript/effects/EffCancelItemUse.java @@ -0,0 +1,77 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.effects; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Cancel Active Item") +@Description({ + "Interrupts the action entities may be trying to complete.", + "For example, interrupting eating, or drawing back a bow." +}) +@Examples({ + "on damage of player:", + "\tif the victim's active tool is a bow:", + "\t\tinterrupt the usage of the player's active item" +}) +@Since("INSERT VERSION") +@RequiredPlugins("Paper 1.16+") +public class EffCancelItemUse extends Effect { + + static { + if (Skript.methodExists(LivingEntity.class, "clearActiveItem")) + Skript.registerEffect(EffCancelItemUse.class, + "(cancel|interrupt) [the] us[ag]e of %livingentities%'[s] [active|current] item" + ); + } + + private Expression<LivingEntity> entities; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + entities = (Expression<LivingEntity>) exprs[0]; + return true; + } + + @Override + protected void execute(Event event) { + for (LivingEntity entity : entities.getArray(event)) { + entity.clearActiveItem(); + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "cancel the usage of " + entities.toString(event, debug) + "'s active item"; + } + +} diff --git a/src/main/java/ch/njol/skript/events/SimpleEvents.java b/src/main/java/ch/njol/skript/events/SimpleEvents.java index a294c6f7b98..f4897b19e30 100644 --- a/src/main/java/ch/njol/skript/events/SimpleEvents.java +++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java @@ -19,6 +19,8 @@ package ch.njol.skript.events; import com.destroystokyo.paper.event.block.AnvilDamagedEvent; +import com.destroystokyo.paper.event.player.PlayerReadyArrowEvent; +import io.papermc.paper.event.player.PlayerStopUsingItemEvent; import io.papermc.paper.event.player.PlayerDeepSleepEvent; import io.papermc.paper.event.player.PlayerInventorySlotChangeEvent; import org.bukkit.event.block.BlockCanBuildEvent; @@ -654,17 +656,47 @@ public class SimpleEvents { "\tcancel the event") .since("2.7"); } + + if (Skript.classExists("io.papermc.paper.event.player.PlayerStopUsingItemEvent")) { + Skript.registerEvent("Stop Using Item", SimpleEvent.class, PlayerStopUsingItemEvent.class, + "[player] (stop|end) (using item|item use)") + .description("Called when a player stops using an item. For example, when the player releases the " + + "interact button when holding a bow, an edible item, or a spyglass.", + "Note that event-timespan will return the time the item was used for.") + .requiredPlugins("Paper 1.18.2+") + .examples( + "on player stop using item:", + "\tbroadcast \"%player% used %event-item% for %event-timespan%.\"") + .since("INSERT VERSION"); + } + + if (Skript.classExists("com.destroystokyo.paper.event.player.PlayerReadyArrowEvent")) { + Skript.registerEvent("Ready Arrow", SimpleEvent.class, PlayerReadyArrowEvent.class, "[player] ((ready|choose|draw|load) arrow|arrow (choose|draw|load))") + .description("Called when a player is firing a bow and the server is choosing an arrow to use.", + "Cancelling this event will skip the current arrow item and fire a new event for the next arrow item.", + "The arrow and bow in the event can be accessed with the Readied Arrow/Bow expression.") + .requiredPlugins("Paper") + .examples( + "on player ready arrow:", + "\tselected bow's name is \"Spectral Bow\"", + "\tif selected arrow is not a spectral arrow:", + "\t\tcancel event" + ) + .since("INSERT VERSION"); + } + if (Skript.classExists("io.papermc.paper.event.player.PlayerInventorySlotChangeEvent")) { Skript.registerEvent("Inventory Slot Change", SimpleEvent.class, PlayerInventorySlotChangeEvent.class, "[player] inventory slot chang(e|ing)") - .description("Called when a slot in a player's inventory is changed.", "Warning: setting the event-slot to a new item can result in an infinite loop.") - .requiredPlugins("Paper 1.19.2+") - .examples( - "on inventory slot change:", - "\tif event-item is a diamond:", - "\t\tsend \"You obtained a diamond!\" to player" - ) - .since("2.7"); + .description("Called when a slot in a player's inventory is changed.", "Warning: setting the event-slot to a new item can result in an infinite loop.") + .requiredPlugins("Paper 1.19.2+") + .examples( + "on inventory slot change:", + "\tif event-item is a diamond:", + "\t\tsend \"You obtained a diamond!\" to player" + ) + .since("2.7"); } + //noinspection deprecation Skript.registerEvent("Chat", SimpleEvent.class, AsyncPlayerChatEvent.class, "chat") .description( diff --git a/src/main/java/ch/njol/skript/expressions/ExprActiveItem.java b/src/main/java/ch/njol/skript/expressions/ExprActiveItem.java new file mode 100644 index 00000000000..c568e5f9b95 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprActiveItem.java @@ -0,0 +1,70 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import org.bukkit.Material; +import org.bukkit.entity.LivingEntity; +import org.bukkit.inventory.ItemStack; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Active Item") +@Description( + "Returns the item the entities are currently using (ie: the food they're eating, " + + "the bow they're drawing back, etc.). This cannot be changed. " + + "If an entity is not using any item, this will return null." +) +@Examples({ + "on damage of player:", + "\tif victim's active tool is a bow:", + "\t\tinterrupt player's active item use" +}) +@Since("INSERT VERSION") +@RequiredPlugins("Paper") +public class ExprActiveItem extends SimplePropertyExpression<LivingEntity, ItemStack> { + + static { + if (Skript.methodExists(LivingEntity.class, "getActiveItem")) + register(ExprActiveItem.class, ItemStack.class, "(raised|active) (tool|item|weapon)", "livingentities"); + } + + @Override + @Nullable + public ItemStack convert(LivingEntity livingEntity) { + ItemStack item = livingEntity.getActiveItem(); + return item.getType() == Material.AIR ? null : item; + } + + @Override + public Class<? extends ItemStack> getReturnType() { + return ItemStack.class; + } + + @Override + protected String getPropertyName() { + return "active item"; + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprEntityItemUseTime.java b/src/main/java/ch/njol/skript/expressions/ExprEntityItemUseTime.java new file mode 100644 index 00000000000..393b9475bca --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprEntityItemUseTime.java @@ -0,0 +1,80 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.util.Timespan; +import ch.njol.util.Kleenean; +import org.bukkit.entity.LivingEntity; + +@Name("Active Item Use Time") +@Description({ + "Returns the time that the entities have either spent using an item, " + + "or the time left for them to finish using an item.", + "If an entity is not using any item, this will return 0 seconds." +}) +@Examples({ + "on right click:", + "\tbroadcast player's remaining item use time", + "\twait 1 second", + "\tbroadcast player's item use time" +}) +@Since("INSERT VERSION") +@RequiredPlugins("Paper") +public class ExprEntityItemUseTime extends SimplePropertyExpression<LivingEntity, Timespan> { + + static { + if (Skript.methodExists(LivingEntity.class, "getItemUseRemainingTime")) + register(ExprEntityItemUseTime.class, Timespan.class, "[elapsed|:remaining] (item|tool) us[ag]e time", "livingentities"); + } + + private boolean remaining; + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + remaining = parseResult.hasTag("remaining"); + return super.init(exprs, matchedPattern, isDelayed, parseResult); + } + + @Override + public Timespan convert(LivingEntity livingEntity) { + if (remaining) + return Timespan.fromTicks(livingEntity.getItemUseRemainingTime()); + return Timespan.fromTicks(livingEntity.getHandRaisedTime()); + } + + @Override + public Class<? extends Timespan> getReturnType() { + return Timespan.class; + } + + @Override + protected String getPropertyName() { + return (remaining ? "remaining" : "elapsed") + " item usage time"; + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprMaxItemUseTime.java b/src/main/java/ch/njol/skript/expressions/ExprMaxItemUseTime.java new file mode 100644 index 00000000000..2521fa65760 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprMaxItemUseTime.java @@ -0,0 +1,67 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.util.Timespan; +import org.bukkit.inventory.ItemStack; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Max Item Use Time") +@Description({ + "Returns the max duration an item can be used for before the action completes. " + + "E.g. it takes 1.6 seconds to drink a potion, or 1.4 seconds to load an unenchanted crossbow.", + "Some items, like bows and shields, do not have a limit to their use. They will return 1 hour." +}) +@Examples({ + "on right click:", + "\tbroadcast max usage duration of player's tool" +}) +@Since("INSERT VERSION") +@RequiredPlugins("Paper") +public class ExprMaxItemUseTime extends SimplePropertyExpression<ItemStack, Timespan> { + + static { + if (Skript.methodExists(ItemStack.class, "getMaxItemUseDuration")) + register(ExprMaxItemUseTime.class, Timespan.class, "max[imum] [item] us(e|age) (time|duration)", "itemstacks"); + } + + @Override + @Nullable + public Timespan convert(ItemStack item) { + return Timespan.fromTicks(item.getMaxItemUseDuration()); + } + + @Override + public Class<? extends Timespan> getReturnType() { + return Timespan.class; + } + + @Override + protected String getPropertyName() { + return "maximum usage time"; + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprReadiedArrow.java b/src/main/java/ch/njol/skript/expressions/ExprReadiedArrow.java new file mode 100644 index 00000000000..00301cbb9fe --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprReadiedArrow.java @@ -0,0 +1,91 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Events; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import com.destroystokyo.paper.event.player.PlayerReadyArrowEvent; +import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Readied Arrow/Bow") +@Description("The bow or arrow in a <a href='events.html#ready_arrow'>Ready Arrow event</a>.") +@Examples({ + "on player ready arrow:", + "\tselected bow's name is \"Spectral Bow\"", + "\tif selected arrow is not a spectral arrow:", + "\t\tcancel event" +}) +@Since("INSERT VERSION") +@Events("ready arrow") +public class ExprReadiedArrow extends SimpleExpression<ItemStack> { + + static { + if (Skript.classExists("com.destroystokyo.paper.event.player.PlayerReadyArrowEvent")) + Skript.registerExpression(ExprReadiedArrow.class, ItemStack.class, ExpressionType.SIMPLE, "[the] (readied|selected|drawn) (:arrow|bow)"); + } + + private boolean isArrow; + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + isArrow = parseResult.hasTag("arrow"); + if (!getParser().isCurrentEvent(PlayerReadyArrowEvent.class)) { + Skript.error("'the readied " + (isArrow ? "arrow" : "bow") + "' can only be used in a ready arrow event"); + return false; + } + return true; + } + + @Override + @Nullable + protected ItemStack[] get(Event event) { + if (!(event instanceof PlayerReadyArrowEvent)) + return null; + if (isArrow) + return new ItemStack[]{((PlayerReadyArrowEvent) event).getArrow()}; + return new ItemStack[]{((PlayerReadyArrowEvent) event).getBow()}; + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class<? extends ItemStack> getReturnType() { + return ItemStack.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the readied " + (isArrow ? "arrow" : "bow"); + } + +} From f96267f559b3dad6613d54fe3a9b327481fcf0a1 Mon Sep 17 00:00:00 2001 From: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> Date: Wed, 20 Dec 2023 00:21:26 -0600 Subject: [PATCH 538/619] Add support for getting and modifying ItemType inventories (#5366) * Add support for getting inventories of Container items * Add comments explaining why the proxy is used * Address reviews * Fix typo * Remove useless import --- .../skript/expressions/ExprInventory.java | 47 ++++++++++++++++--- .../syntaxes/expressions/ExprInventory.sk | 12 +++++ 2 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprInventory.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprInventory.java b/src/main/java/ch/njol/skript/expressions/ExprInventory.java index 6268e5df1ae..34f4618ccba 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprInventory.java +++ b/src/main/java/ch/njol/skript/expressions/ExprInventory.java @@ -18,6 +18,8 @@ */ package ch.njol.skript.expressions; +import ch.njol.skript.aliases.ItemData; +import ch.njol.skript.aliases.ItemType; import ch.njol.skript.config.Node; import ch.njol.skript.expressions.base.PropertyExpression; import ch.njol.skript.lang.Expression; @@ -26,9 +28,14 @@ import ch.njol.skript.log.SkriptLogger; import ch.njol.skript.util.slot.Slot; import ch.njol.util.Kleenean; +import org.bukkit.block.BlockState; +import org.bukkit.block.Container; import org.bukkit.event.Event; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.BlockStateMeta; +import org.bukkit.inventory.meta.ItemMeta; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; @@ -38,6 +45,9 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.SimplePropertyExpression; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.List; @@ -54,10 +64,10 @@ public class ExprInventory extends SimpleExpression<Object> { private boolean inLoop; @SuppressWarnings("null") - private Expression<InventoryHolder> holders; + private Expression<?> holders; static { - PropertyExpression.register(ExprInventory.class, Object.class, "inventor(y|ies)", "inventoryholders"); + PropertyExpression.register(ExprInventory.class, Object.class, "inventor(y|ies)", "inventoryholders/itemtypes"); } @Override @@ -66,16 +76,41 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye // if we're dealing with a loop of just this expression Node n = SkriptLogger.getNode(); inLoop = n != null && ("loop " + parseResult.expr).equals(n.getKey()); - holders = (Expression<InventoryHolder>) exprs[0]; + holders = exprs[0]; return true; } - @Override protected Object[] get(Event e) { List<Inventory> inventories = new ArrayList<>(); - for (InventoryHolder holder : holders.getArray(e)) { - inventories.add(holder.getInventory()); + for (Object holder : holders.getArray(e)) { + if (holder instanceof InventoryHolder) { + inventories.add(((InventoryHolder) holder).getInventory()); + } else if (holder instanceof ItemType) { + ItemMeta meta = ((ItemType) holder).getItemMeta(); + if (!(meta instanceof BlockStateMeta)) + continue; + BlockState state = ((BlockStateMeta) meta).getBlockState(); + if (!(state instanceof Container)) + continue; + Inventory underlyingInv = ((Container) state).getInventory(); + // The proxy is used here to ensure that any changes to the inventory are reflected in the + // BlockStateMeta and ItemMeta of `holder` + Inventory proxy = (Inventory) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Inventory.class}, new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + Object returnValue = method.invoke(underlyingInv, args); + // calling update here causes the changes to the inventory to be synced to the meta + boolean updateSucceeded = state.update(); + if (updateSucceeded) { + ((BlockStateMeta) meta).setBlockState(state); + ((ItemType) holder).setItemMeta(meta); + } + return returnValue; + } + }); + inventories.add(proxy); + } } Inventory[] invArray = inventories.toArray(new Inventory[0]); if (inLoop) { diff --git a/src/test/skript/tests/syntaxes/expressions/ExprInventory.sk b/src/test/skript/tests/syntaxes/expressions/ExprInventory.sk new file mode 100644 index 00000000000..b4165f8eddd --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprInventory.sk @@ -0,0 +1,12 @@ +test "item inventory": + set {_shulker} to any shulker box + assert inventory of {_shulker} is set with "Failed to get shulker inventory" + set slot 1 of inventory of {_shulker} to dirt + assert slot 1 of inventory of {_shulker} is dirt with "Failed to set slot in shulker inventory" + + set {_chest} to chest + assert inventory of {_chest} is set with "Failed to get chest inventory" + set slot 1 of inventory of {_chest} to dirt + assert slot 1 of inventory of {_chest} is dirt with "Failed to set slot in chest inventory" + + assert inventory of dirt is not set with "Inventory was found for an item without an inventory" From 2daaa6fa326c8748b1df8ec41f5cab96c45737d4 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Wed, 20 Dec 2023 20:36:28 -0500 Subject: [PATCH 539/619] (Patch) Fix NPE issue with drops in 1.20.2 (#6239) Fix NPE issue with drops in 1.20.2 Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- .../java/ch/njol/skript/aliases/ItemType.java | 50 +++++++++++++++---- .../ch/njol/skript/expressions/ExprDrops.java | 13 ++--- 2 files changed, 46 insertions(+), 17 deletions(-) diff --git a/src/main/java/ch/njol/skript/aliases/ItemType.java b/src/main/java/ch/njol/skript/aliases/ItemType.java index bc5b8aa6ca1..96f4f7adfb4 100644 --- a/src/main/java/ch/njol/skript/aliases/ItemType.java +++ b/src/main/java/ch/njol/skript/aliases/ItemType.java @@ -728,12 +728,18 @@ public boolean removeFrom(Inventory invi) { @SafeVarargs public final boolean removeAll(List<ItemStack>... lists) { + return removeAll(true, lists); + } + + + @SafeVarargs + public final boolean removeAll(boolean replaceWithNull, List<ItemStack>...lists) { final boolean wasAll = all; final int oldAmount = amount; all = true; amount = -1; try { - return removeFrom(lists); + return removeFrom(replaceWithNull, lists); } finally { all = wasAll; amount = oldAmount; @@ -749,18 +755,37 @@ public final boolean removeAll(List<ItemStack>... lists) { */ @SafeVarargs public final boolean removeFrom(final List<ItemStack>... lists) { + return removeFrom(true, lists); + } + + /** + * Removes this ItemType from given lists of ItemStacks. + * If replaceWithNull is true, then if an ItemStack is completely removed, that index in the list is set to null, instead of being removed. + * + * @param replaceWithNull Whether to replace removed ItemStacks with null, or to remove them completely + * @param lists The lists to remove this type from. Each list should implement {@link RandomAccess}. Lists may contain null values after this method if replaceWithNull is true. + * @return Whether this whole item type could be removed (i.e. returns false if the lists didn't contain this item type completely) + */ + @SafeVarargs + public final boolean removeFrom(boolean replaceWithNull, List<ItemStack>... lists) { int removed = 0; boolean ok = true; - for (final ItemData d : types) { + for (ItemData d : types) { if (all) removed = 0; - for (final List<ItemStack> list : lists) { + for (List<ItemStack> list : lists) { if (list == null) continue; assert list instanceof RandomAccess; - for (int i = 0; i < list.size(); i++) { - final ItemStack is = list.get(i); + + Iterator<ItemStack> listIterator = list.iterator(); + int index = -1; // only reliable if replaceWithNull is true. Will be -1 if replaceWithNull is false. + while (listIterator.hasNext()) { + ItemStack is = listIterator.next(); + // index is only reliable if replaceWithNull is true + if (replaceWithNull) + index++; /* * Do NOT use equals()! It doesn't exactly match items * for historical reasons. This will change in future. @@ -778,15 +803,22 @@ public final boolean removeFrom(final List<ItemStack>... lists) { boolean plain = d.isPlain() != other.isPlain(); if (d.matchPlain(other) || other.matchAlias(d).isAtLeast(plain ? MatchQuality.EXACT : (d.isAlias() && !other.isAlias() ? MatchQuality.SAME_MATERIAL : MatchQuality.SAME_ITEM))) { if (all && amount == -1) { - list.set(i, null); + if (replaceWithNull) { + list.set(index, null); + } else { + listIterator.remove(); + } removed = 1; continue; } - assert is != null; - final int toRemove = Math.min(is.getAmount(), getAmount() - removed); + int toRemove = Math.min(is.getAmount(), getAmount() - removed); removed += toRemove; if (toRemove == is.getAmount()) { - list.set(i, null); + if (replaceWithNull) { + list.set(index, null); + } else { + listIterator.remove(); + } } else { is.setAmount(is.getAmount() - toRemove); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprDrops.java b/src/main/java/ch/njol/skript/expressions/ExprDrops.java index 2d57b4e9277..85f3f423a22 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprDrops.java +++ b/src/main/java/ch/njol/skript/expressions/ExprDrops.java @@ -18,10 +18,6 @@ */ package ch.njol.skript.expressions; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - import ch.njol.skript.Skript; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.classes.Changer.ChangeMode; @@ -44,6 +40,9 @@ import org.bukkit.inventory.ItemStack; import org.eclipse.jdt.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; + /** * @author Peter Güttinger */ @@ -179,20 +178,18 @@ public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { break; case REMOVE: for (ItemType item : deltaDrops) { - item.removeFrom(drops); + item.removeFrom(false, drops); } break; case REMOVE_ALL: for (ItemType item : deltaDrops) { - item.removeAll(drops); + item.removeAll(false, drops); } break; case DELETE: case RESET: assert false; } - // remove stray nulls caused by ItemType#removeFrom() and ItemType#removeAll() - drops.removeIf(Objects::isNull); } } From be39093f1fc11f14b56b0d1226ee9252ec2ae0c3 Mon Sep 17 00:00:00 2001 From: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> Date: Thu, 21 Dec 2023 13:21:00 -0600 Subject: [PATCH 540/619] Add filter support to ExprItemsIn (#4614) * Add filtering support to ExprItemsIn * Address reviews * Fix syntax conflict once and for all * Fix syntax conflict twice and for all --- .../njol/skript/expressions/ExprItemsIn.java | 140 +++++++++++------- .../tests/syntaxes/expressions/ExprItemsIn.sk | 21 +++ 2 files changed, 107 insertions(+), 54 deletions(-) create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprItemsIn.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprItemsIn.java b/src/main/java/ch/njol/skript/expressions/ExprItemsIn.java index 684012df8d5..4ba8ebbb6fd 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprItemsIn.java +++ b/src/main/java/ch/njol/skript/expressions/ExprItemsIn.java @@ -20,10 +20,13 @@ import java.util.ArrayList; import java.util.Iterator; +import java.util.List; import java.util.NoSuchElementException; +import ch.njol.skript.aliases.ItemType; import org.bukkit.event.Event; import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; @@ -40,109 +43,138 @@ import ch.njol.skript.util.slot.Slot; import ch.njol.util.Kleenean; -/** - * @author Peter Güttinger - */ @Name("Items In") -@Description({"All items in an inventory. Useful for looping or storing in a list variable.", - "Please note that the positions of the items in the inventory are not saved, only their order is preserved."}) -@Examples({"loop all items in the player's inventory:", - " loop-item is enchanted", - " remove loop-item from the player", - "set {inventory::%uuid of player%::*} to items in the player's inventory"}) -@Since("2.0") +@Description({ + "All items or specific type(s) of items in an inventory. Useful for looping or storing in a list variable.", + "Please note that the positions of the items in the inventory are not saved, only their order is preserved." +}) +@Examples({ + "loop all items in the player's inventory:", + "\tloop-item is enchanted", + "\tremove loop-item from the player", + "set {inventory::%uuid of player%::*} to items in the player's inventory" +}) +@Since("2.0, INSERT VERSION (specific types of items)") public class ExprItemsIn extends SimpleExpression<Slot> { + static { - Skript.registerExpression(ExprItemsIn.class, Slot.class, ExpressionType.PROPERTY, "[(all [[of] the]|the)] items ([with]in|of|contained in|out of) (|1¦inventor(y|ies)) %inventories%"); + Skript.registerExpression(ExprItemsIn.class, Slot.class, ExpressionType.PROPERTY, + "[all [[of] the]] items ([with]in|of|contained in|out of) [1:inventor(y|ies)] %inventories%", + "all [[of] the] %itemtypes% ([with]in|of|contained in|out of) [1:inventor(y|ies)] %inventories%" + ); } - - @SuppressWarnings("null") - private Expression<Inventory> invis; - - @SuppressWarnings({"unchecked", "null"}) + + @SuppressWarnings("NotNullFieldNotInitialized") + private Expression<Inventory> inventories; + + @Nullable + private Expression<ItemType> types; + @Override + @SuppressWarnings("unchecked") /* * the parse result will be null if it is used via the ExprInventory expression, however the expression will never - * be a variable when used with that expression (it is always a anonymous SimpleExpression) + * be a variable when used with that expression (it is always an anonymous SimpleExpression) */ - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, @Nullable final ParseResult parseResult) { - invis = (Expression<Inventory>) exprs[0]; - if (invis instanceof Variable && !invis.isSingle() && parseResult.mark != 1) + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (matchedPattern == 0) { + inventories = (Expression<Inventory>) exprs[0]; + } else { + types = (Expression<ItemType>) exprs[0]; + inventories = (Expression<Inventory>) exprs[1]; + } + if (inventories instanceof Variable && !inventories.isSingle() && parseResult.mark != 1) Skript.warning("'items in {variable::*}' does not actually represent the items stored in the variable. Use either '{variable::*}' (e.g. 'loop {variable::*}') if the variable contains items, or 'items in inventories {variable::*}' if the variable contains inventories."); return true; } - - @SuppressWarnings("null") + @Override - protected Slot[] get(final Event e) { - final ArrayList<Slot> r = new ArrayList<>(); - for (final Inventory invi : invis.getArray(e)) { - for (int i = 0; i < invi.getSize(); i++) { - if (invi.getItem(i) != null) - r.add(new InventorySlot(invi, i)); + protected Slot[] get(Event event) { + List<Slot> itemSlots = new ArrayList<>(); + ItemType[] types = this.types == null ? null : this.types.getArray(event); + for (Inventory inventory : inventories.getArray(event)) { + for (int i = 0; i < inventory.getSize(); i++) { + if (isAllowedItem(types, inventory.getItem(i))) + itemSlots.add(new InventorySlot(inventory, i)); } } - return r.toArray(new Slot[r.size()]); + return itemSlots.toArray(new Slot[itemSlots.size()]); } - + @Override @Nullable - public Iterator<Slot> iterator(final Event e) { - final Iterator<? extends Inventory> is = invis.iterator(e); - if (is == null || !is.hasNext()) + public Iterator<Slot> iterator(Event event) { + Iterator<? extends Inventory> inventoryIterator = inventories.iterator(event); + ItemType[] types = this.types == null ? null : this.types.getArray(event); + if (inventoryIterator == null || !inventoryIterator.hasNext()) return null; return new Iterator<Slot>() { - @SuppressWarnings("null") - Inventory current = is.next(); - + Inventory currentInventory = inventoryIterator.next(); + int next = 0; - - @SuppressWarnings("null") + @Override public boolean hasNext() { - while (next < current.getSize() && current.getItem(next) == null) + while (next < currentInventory.getSize() && !isAllowedItem(types, currentInventory.getItem(next))) next++; - while (next >= current.getSize() && is.hasNext()) { - current = is.next(); + while (next >= currentInventory.getSize() && inventoryIterator.hasNext()) { + currentInventory = inventoryIterator.next(); next = 0; - while (next < current.getSize() && current.getItem(next) == null) + while (next < currentInventory.getSize() && !isAllowedItem(types, currentInventory.getItem(next))) next++; } - return next < current.getSize(); + return next < currentInventory.getSize(); } - + @Override public Slot next() { if (!hasNext()) throw new NoSuchElementException(); - return new InventorySlot(current, next++); + return new InventorySlot(currentInventory, next++); } - + @Override public void remove() { throw new UnsupportedOperationException(); } }; } - + @Override - public boolean isLoopOf(final String s) { + public boolean isLoopOf(String s) { return s.equalsIgnoreCase("item"); } - + @Override - public String toString(final @Nullable Event e, final boolean debug) { - return "items in " + invis.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + if (types == null) + return "items in " + inventories.toString(event, debug); + return "all " + types.toString(event, debug) + " in " + inventories.toString(event, debug); } - + @Override public boolean isSingle() { return false; } - + @Override public Class<Slot> getReturnType() { return Slot.class; } - + + private boolean isAllowedItem(@Nullable ItemType[] types, @Nullable ItemStack item) { + if (types == null) + return item != null; + if (item == null) + return false; + + ItemType potentiallyAllowedItem = new ItemType(item); + for (ItemType type : types) { + if (potentiallyAllowedItem.isSimilar(type)) + return true; + } + + return false; + } + } diff --git a/src/test/skript/tests/syntaxes/expressions/ExprItemsIn.sk b/src/test/skript/tests/syntaxes/expressions/ExprItemsIn.sk new file mode 100644 index 00000000000..9b2c90fb25f --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprItemsIn.sk @@ -0,0 +1,21 @@ +test "filtering ExprItemsIn": + set {_world} to random world out of all worlds + set block at spawn of {_world} to chest + set {_inv} to inventory of (block at spawn of {_world}) + set slot 1 of {_inv} to dirt + set slot 2 of {_inv} to stone + set slot 3 of {_inv} to bucket + assert all blocks in inventory {_inv} are dirt or stone with "found correct items with ExprItemsIn##get" + assert (all blocks in inventory {_inv} where [true is true]) are dirt or stone with "found correct items with ExprItemsIn##iterator" + set {_dirt} to dirt + assert all {_dirt} in inventory {_inv} is dirt with "found incorrect items with variable itemtypes" + +test "unfiltered ExprItemsIn": + set {_world} to random world out of all worlds + set block at spawn of {_world} to chest + set {_inv} to inventory of (block at spawn of {_world}) + set slot 1 of {_inv} to dirt + set slot 2 of {_inv} to stone + set slot 3 of {_inv} to bucket + assert all items in inventory {_inv} are dirt, stone or bucket with "found correct items with ExprItemsIn##get" + assert (all items in inventory {_inv} where [true is true]) are dirt, stone or bucket with "found correct items with ExprItemsIn##iterator" From 7d57297731feebbf824f81085f8e86a401e1b5bd Mon Sep 17 00:00:00 2001 From: Moderocky <admin@moderocky.com> Date: Fri, 29 Dec 2023 17:04:41 +0000 Subject: [PATCH 541/619] Remove init override in anvil text expression. (#6229) --- src/main/java/ch/njol/skript/expressions/ExprAnvilText.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprAnvilText.java b/src/main/java/ch/njol/skript/expressions/ExprAnvilText.java index ed37573183e..acc05ae124c 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprAnvilText.java +++ b/src/main/java/ch/njol/skript/expressions/ExprAnvilText.java @@ -45,11 +45,6 @@ public class ExprAnvilText extends SimplePropertyExpression<Inventory, String> { register(ExprAnvilText.class, String.class, "anvil [inventory] (rename|text) input", "inventories"); } - @Override - public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - return true; - } - @Override @Nullable public String convert(Inventory inv) { From 94ef31aa52bf090c4b4e12c2d8ec516c13d1e6aa Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Fri, 29 Dec 2023 10:10:14 -0700 Subject: [PATCH 542/619] Bumps actions/upload-artifact from 3 to 4. (#6265) --- .github/workflows/java-17-builds.yml | 2 +- .github/workflows/java-8-builds.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/java-17-builds.yml b/.github/workflows/java-17-builds.yml index 36ddf386257..248d72002a6 100644 --- a/.github/workflows/java-17-builds.yml +++ b/.github/workflows/java-17-builds.yml @@ -28,7 +28,7 @@ jobs: - name: Build Skript and run test scripts run: ./gradlew clean skriptTestJava17 - name: Upload Nightly Build - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: success() with: name: skript-nightly diff --git a/.github/workflows/java-8-builds.yml b/.github/workflows/java-8-builds.yml index 50b42dc0491..5dc4e1728c5 100644 --- a/.github/workflows/java-8-builds.yml +++ b/.github/workflows/java-8-builds.yml @@ -28,7 +28,7 @@ jobs: - name: Build Skript and run test scripts run: ./gradlew clean skriptTestJava8 - name: Upload Nightly Build - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: success() with: name: skript-nightly From de2779303f375bd6b21273132e28c95b62d35194 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 Dec 2023 12:14:27 -0500 Subject: [PATCH 543/619] Bump net.kyori:adventure-text-serializer-bungeecord from 4.3.1 to 4.3.2 (#6255) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 42baba93c72..44388645ff1 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ allprojects { dependencies { shadow group: 'io.papermc', name: 'paperlib', version: '1.0.8' shadow group: 'org.bstats', name: 'bstats-bukkit', version: '3.0.2' - shadow group: 'net.kyori', name: 'adventure-text-serializer-bungeecord', version: '4.3.1' + shadow group: 'net.kyori', name: 'adventure-text-serializer-bungeecord', version: '4.3.2' implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.20.2-R0.1-SNAPSHOT' implementation group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.700' From b832ca557235f9403de0511160073dea6b919b9c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 Dec 2023 12:25:11 -0500 Subject: [PATCH 544/619] Bump org.eclipse.jdt:org.eclipse.jdt.annotation from 2.2.700 to 2.2.800 (#6226) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 44388645ff1..708f7274a36 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,7 @@ dependencies { shadow group: 'net.kyori', name: 'adventure-text-serializer-bungeecord', version: '4.3.2' implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.20.2-R0.1-SNAPSHOT' - implementation group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.700' + implementation group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.800' implementation group: 'com.google.code.findbugs', name: 'findbugs', version: '3.0.1' implementation group: 'com.sk89q.worldguard', name: 'worldguard-legacy', version: '7.0.0-SNAPSHOT' implementation group: 'net.milkbowl.vault', name: 'Vault', version: '1.7.1', { From 03abc8c50ebab41829e9766dc82aa06e60fdc5a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 Dec 2023 12:31:08 -0500 Subject: [PATCH 545/619] Bump actions/setup-java from 3 to 4 (#6218) --- .github/workflows/java-8-builds.yml | 2 +- .github/workflows/junit-17-builds.yml | 2 +- .github/workflows/junit-8-builds.yml | 2 +- .github/workflows/repo.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/java-8-builds.yml b/.github/workflows/java-8-builds.yml index 5dc4e1728c5..adae34978ec 100644 --- a/.github/workflows/java-8-builds.yml +++ b/.github/workflows/java-8-builds.yml @@ -18,7 +18,7 @@ jobs: - name: validate gradle wrapper uses: gradle/wrapper-validation-action@v1 - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '17' distribution: 'adopt' diff --git a/.github/workflows/junit-17-builds.yml b/.github/workflows/junit-17-builds.yml index 64cd93ec47f..d737c152b86 100644 --- a/.github/workflows/junit-17-builds.yml +++ b/.github/workflows/junit-17-builds.yml @@ -18,7 +18,7 @@ jobs: - name: validate gradle wrapper uses: gradle/wrapper-validation-action@v1 - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '17' distribution: 'adopt' diff --git a/.github/workflows/junit-8-builds.yml b/.github/workflows/junit-8-builds.yml index cafa1c0f501..bc23d73ba72 100644 --- a/.github/workflows/junit-8-builds.yml +++ b/.github/workflows/junit-8-builds.yml @@ -18,7 +18,7 @@ jobs: - name: validate gradle wrapper uses: gradle/wrapper-validation-action@v1 - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '17' distribution: 'adopt' diff --git a/.github/workflows/repo.yml b/.github/workflows/repo.yml index 2499cf67f74..d3527c2c458 100644 --- a/.github/workflows/repo.yml +++ b/.github/workflows/repo.yml @@ -14,7 +14,7 @@ jobs: - name: validate gradle wrapper uses: gradle/wrapper-validation-action@v1 - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '17' distribution: 'adopt' From 03e912c17ac53bf09af779596c63e0b5dda3f9fd Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Fri, 29 Dec 2023 10:39:51 -0700 Subject: [PATCH 546/619] Proper plurality on EffCommand (#6172) --- src/main/java/ch/njol/skript/effects/EffCommand.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffCommand.java b/src/main/java/ch/njol/skript/effects/EffCommand.java index 3a26fd82dd7..8dca06d4638 100644 --- a/src/main/java/ch/njol/skript/effects/EffCommand.java +++ b/src/main/java/ch/njol/skript/effects/EffCommand.java @@ -55,9 +55,9 @@ public class EffCommand extends Effect { static { Skript.registerEffect(EffCommand.class, - "[execute] [the] [bungee:bungee[cord]] command %strings% [by %-commandsenders%]", - "[execute] [the] %commandsenders% [bungee:bungee[cord]] command %strings%", - "(let|make) %commandsenders% execute [[the] [bungee:bungee[cord]] command] %strings%"); + "[execute] [the] [bungee:bungee[cord]] command[s] %strings% [by %-commandsenders%]", + "[execute] [the] %commandsenders% [bungee:bungee[cord]] command[s] %strings%", + "(let|make) %commandsenders% execute [[the] [bungee:bungee[cord]] command[s]] %strings%"); } @Nullable From 48e18f95bdd959d551fd297e9b3c756cb7327a2e Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Sat, 30 Dec 2023 17:03:44 -0500 Subject: [PATCH 547/619] Fix CondCompare Logging (#6266) --- .../ch/njol/skript/conditions/CondCompare.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/main/java/ch/njol/skript/conditions/CondCompare.java b/src/main/java/ch/njol/skript/conditions/CondCompare.java index 4e0917faa6d..f725c1bd6f8 100644 --- a/src/main/java/ch/njol/skript/conditions/CondCompare.java +++ b/src/main/java/ch/njol/skript/conditions/CondCompare.java @@ -18,6 +18,7 @@ */ package ch.njol.skript.conditions; +import ch.njol.skript.log.ParseLogHandler; import ch.njol.skript.util.Time; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @@ -166,9 +167,8 @@ public static String f(final Expression<?> e) { @SuppressWarnings("unchecked") private boolean init(String expr) { - RetainingLogHandler log = SkriptLogger.startRetainingLog(); Expression<?> third = this.third; - try { + try (ParseLogHandler log = SkriptLogger.startParseLogHandler()) { if (first.getReturnType() == Object.class) { Expression<?> expression = null; if (first instanceof UnparsedLiteral) @@ -176,7 +176,7 @@ private boolean init(String expr) { if (expression == null) expression = first.getConvertedExpression(Object.class); if (expression == null) { - log.printErrors(); + log.printError(); return false; } first = expression; @@ -188,7 +188,7 @@ private boolean init(String expr) { if (expression == null) expression = second.getConvertedExpression(Object.class); if (expression == null) { - log.printErrors(); + log.printError(); return false; } second = expression; @@ -200,14 +200,13 @@ private boolean init(String expr) { if (expression == null) expression = third.getConvertedExpression(Object.class); if (expression == null) { - log.printErrors(); + log.printError(); return false; } this.third = third = expression; } - log.printLog(); - } finally { - log.stop(); + // we do not want to print any errors as they are not applicable + log.printLog(false); } Class<?> firstReturnType = first.getReturnType(); Class<?> secondReturnType = third == null ? second.getReturnType() : Utils.getSuperType(second.getReturnType(), third.getReturnType()); From c3b0f44d4777416d3d5f99dcc3bcb65391bf5cb1 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Sat, 30 Dec 2023 15:16:35 -0700 Subject: [PATCH 548/619] Remove final on 'cleanup' method and provide JUnit implementation warnings. (#6261) --- src/main/java/ch/njol/skript/Skript.java | 20 ++++++++++++++++++- .../skript/test/runner/SkriptJUnitTest.java | 5 ++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 878dfe12666..7b1ac6c0eab 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -82,6 +82,7 @@ import ch.njol.util.Kleenean; import ch.njol.util.NullableChecker; import ch.njol.util.StringUtils; +import ch.njol.util.coll.CollectionUtils; import ch.njol.util.coll.iterator.CheckedIterator; import ch.njol.util.coll.iterator.EnumerationIterable; import com.google.common.collect.Lists; @@ -106,6 +107,7 @@ import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.java.JavaPlugin; import org.eclipse.jdt.annotation.Nullable; +import org.junit.After; import org.junit.runner.JUnitCore; import org.junit.runner.Result; import org.skriptlang.skript.lang.comparator.Comparator; @@ -147,7 +149,6 @@ import java.util.logging.Level; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipException; import java.util.zip.ZipFile; @@ -703,6 +704,23 @@ protected void afterErrors() { Result junit = JUnitCore.runClasses(clazz); TestTracker.testStarted("JUnit: '" + test + "'"); + /** + * Usage of @After is pointless if the JUnit class requires delay. As the @After will happen instantly. + * The JUnit must override the 'cleanup' method to avoid Skript automatically cleaning up the test data. + */ + boolean overrides = false; + for (Method method : clazz.getDeclaredMethods()) { + if (!method.isAnnotationPresent(After.class)) + continue; + if (SkriptJUnitTest.getShutdownDelay() > 1) + warning("Using @After in JUnit classes, happens instantaneously, and JUnit class '" + test + "' requires a delay. Do your test cleanup in the script junit file or 'cleanup' method."); + if (method.getName().equals("cleanup")) + overrides = true; + } + if (SkriptJUnitTest.getShutdownDelay() > 1 && !overrides) + error("The JUnit class '" + test + "' does not override the method 'cleanup' thus the test data will instantly be cleaned up. " + + "This JUnit test requires longer shutdown time: " + SkriptJUnitTest.getShutdownDelay()); + // Collect all data from the current JUnit test. shutdownDelay = Math.max(shutdownDelay, SkriptJUnitTest.getShutdownDelay()); tests += junit.getRunCount(); diff --git a/src/main/java/ch/njol/skript/test/runner/SkriptJUnitTest.java b/src/main/java/ch/njol/skript/test/runner/SkriptJUnitTest.java index 8431dc9a6a3..38ec51d116c 100644 --- a/src/main/java/ch/njol/skript/test/runner/SkriptJUnitTest.java +++ b/src/main/java/ch/njol/skript/test/runner/SkriptJUnitTest.java @@ -76,9 +76,12 @@ public static void setShutdownDelay(long delay) { SkriptJUnitTest.delay = delay; } + /** + * Override this method if your JUnit test requires block modification with delay over 1 tick. + */ @Before @After - public final void cleanup() { + public void cleanup() { getTestWorld().getEntities().forEach(Entity::remove); setBlock(Material.AIR); } From 967f9ee816b8ea296013f5d939a84a1d0675ce76 Mon Sep 17 00:00:00 2001 From: Moderocky <admin@moderocky.com> Date: Sat, 30 Dec 2023 22:23:03 +0000 Subject: [PATCH 549/619] Allow multiple hashtags to start comments at the beginning of a line. (#6146) --- src/main/java/ch/njol/skript/config/Node.java | 2 ++ .../skript/test/tests/config/NodeTest.java | 12 ++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/java/ch/njol/skript/config/Node.java b/src/main/java/ch/njol/skript/config/Node.java index 170fd38cf42..afb3ea3d038 100644 --- a/src/main/java/ch/njol/skript/config/Node.java +++ b/src/main/java/ch/njol/skript/config/Node.java @@ -128,6 +128,8 @@ public void move(final SectionNode newParent) { * @return A pair (value, comment). */ public static NonNullPair<String, String> splitLine(final String line) { + if (line.trim().startsWith("#")) + return new NonNullPair<>("", line.substring(line.indexOf('#'))); final Matcher m = linePattern.matcher(line); boolean matches = false; try { diff --git a/src/test/java/org/skriptlang/skript/test/tests/config/NodeTest.java b/src/test/java/org/skriptlang/skript/test/tests/config/NodeTest.java index e24d28405f2..c7e0eb3b29f 100644 --- a/src/test/java/org/skriptlang/skript/test/tests/config/NodeTest.java +++ b/src/test/java/org/skriptlang/skript/test/tests/config/NodeTest.java @@ -39,12 +39,12 @@ public void splitLineTest() { {"ab#cd", "ab", "#cd"}, {"ab##cd", "ab#cd", ""}, {"ab###cd", "ab#", "#cd"}, - {"######", "###", ""}, - {"#######", "###", "#"}, - {"#### # ####", "## ", "# ####"}, - {"##### ####", "##", "# ####"}, - {"#### #####", "## ##", "#"}, - {"#########", "####", "#"}, + {"######", "", "######"}, + {"#######", "", "#######"}, + {"#### # ####", "", "#### # ####"}, + {"##### ####", "", "##### ####"}, + {"#### #####", "", "#### #####"}, + {"#########", "", "#########"}, {"a##b#c##d#e", "a#b", "#c##d#e"}, {" a ## b # c ## d # e ", " a # b ", "# c ## d # e "}, }; From 58ca4695e24ba0330feaceb41f1440b36d4f6082 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 31 Dec 2023 16:47:20 -0800 Subject: [PATCH 550/619] Bump io.papermc.paper:paper-api from 1.20.2-R0.1-SNAPSHOT to 1.20.4-R0.1-SNAPSHOT (#6227) * Start working towards 1.20.4 * Update aliases to master * Add missing enums for 1.20.3 * Update default.lang --------- Co-authored-by: TheLimeGlass <seantgrover@gmail.com> Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- build.gradle | 6 +++--- gradle.properties | 2 +- src/main/resources/lang/default.lang | 6 ++++++ .../java17/{paper-1.20.2.json => paper-1.20.4.json} | 4 ++-- .../skript/tests/syntaxes/conditions/CondIsPreferredTool.sk | 4 ++-- 5 files changed, 14 insertions(+), 8 deletions(-) rename src/test/skript/environments/java17/{paper-1.20.2.json => paper-1.20.4.json} (85%) diff --git a/build.gradle b/build.gradle index 708f7274a36..dfe1f3284b7 100644 --- a/build.gradle +++ b/build.gradle @@ -29,8 +29,8 @@ dependencies { shadow group: 'org.bstats', name: 'bstats-bukkit', version: '3.0.2' shadow group: 'net.kyori', name: 'adventure-text-serializer-bungeecord', version: '4.3.2' - implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.20.2-R0.1-SNAPSHOT' - implementation group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.800' + implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.20.4-R0.1-SNAPSHOT' + implementation group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.700' implementation group: 'com.google.code.findbugs', name: 'findbugs', version: '3.0.1' implementation group: 'com.sk89q.worldguard', name: 'worldguard-legacy', version: '7.0.0-SNAPSHOT' implementation group: 'net.milkbowl.vault', name: 'Vault', version: '1.7.1', { @@ -250,7 +250,7 @@ void createTestTask(String name, String desc, String environments, int javaVersi } } -def latestEnv = 'java17/paper-1.20.2.json' +def latestEnv = 'java17/paper-1.20.4.json' def latestJava = 17 def oldestJava = 8 diff --git a/gradle.properties b/gradle.properties index 7af67b25858..4542c647233 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,5 +7,5 @@ groupid=ch.njol name=skript version=2.8.0-dev jarName=Skript.jar -testEnv=java17/paper-1.20.2 +testEnv=java17/paper-1.20.4 testEnvJavaVersion=17 diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 176cc9b94ce..e1a2dccc4ac 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -1819,6 +1819,8 @@ inventory types: smithing: smithing inventory @a composter: composter inventory @a chiseled_bookshelf: chiseled bookshelf @a, bookshelf @a + decorated_pot: decorated pot @a + crafter: crafter inventory @a jukebox: jukebox @a # The smithing new table is required for Skript to work, but this is a draft API for 1.20 @@ -1888,6 +1890,10 @@ resource pack states: declined: decline, refuse, reject, declined, refused, rejected failed_download: download fail, fail, failed to download, failed successfully_loaded: successfully load, successfully install, success, successfully loaded, successfully installed + downloaded: downloaded + invalid_url: invalid url + failed_reload: failed reload, failed to reload + discarded: discarded # -- Sound Categories -- sound categories: diff --git a/src/test/skript/environments/java17/paper-1.20.2.json b/src/test/skript/environments/java17/paper-1.20.4.json similarity index 85% rename from src/test/skript/environments/java17/paper-1.20.2.json rename to src/test/skript/environments/java17/paper-1.20.4.json index 0512ae142b0..02be65e56ac 100644 --- a/src/test/skript/environments/java17/paper-1.20.2.json +++ b/src/test/skript/environments/java17/paper-1.20.4.json @@ -1,11 +1,11 @@ { - "name": "paper-1.20.2", + "name": "paper-1.20.4", "resources": [ {"source": "server.properties.generic", "target": "server.properties"} ], "paperDownloads": [ { - "version": "1.20.2", + "version": "1.20.4", "target": "paperclip.jar" } ], diff --git a/src/test/skript/tests/syntaxes/conditions/CondIsPreferredTool.sk b/src/test/skript/tests/syntaxes/conditions/CondIsPreferredTool.sk index 3a3e9f5149a..585055cdf88 100644 --- a/src/test/skript/tests/syntaxes/conditions/CondIsPreferredTool.sk +++ b/src/test/skript/tests/syntaxes/conditions/CondIsPreferredTool.sk @@ -1,12 +1,12 @@ test "CondIsPreferredTool - BlockData" when running minecraft "1.19.2": assert wooden pickaxe is preferred tool for minecraft:stone[] with "failed wooden pickaxe for stone blockdata" - assert wooden pickaxe is preferred tool for minecraft:grass[] with "failed wooden pickaxe for grass blockdata" + assert wooden pickaxe is preferred tool for minecraft:dirt[] with "failed wooden pickaxe for dirt blockdata" assert wooden pickaxe is not preferred tool for minecraft:obsidian[] with "failed wooden pickaxe for obsidian blockdata" assert diamond pickaxe is preferred tool for minecraft:obsidian[] with "failed diamond pickaxe for obsidian blockdata" assert wooden axe is not preferred tool for minecraft:stone[] with "failed wooden axe for stone blockdata" - assert wooden axe is preferred tool for minecraft:grass[] with "failed wooden axe for grass blockdata" + assert wooden axe is preferred tool for minecraft:dirt[] with "failed wooden axe for dirt blockdata" test "CondIsPreferredTool - Block" when running minecraft "1.16.5": set {_block} to block at location(0,0,0, "world") From 5843ec835fd22126d335aad122fb6650df68ecfb Mon Sep 17 00:00:00 2001 From: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> Date: Sun, 31 Dec 2023 18:53:50 -0600 Subject: [PATCH 551/619] Add tests for effects (#6204) * Add tests for EffActionBar * Fix order of operations issue in EffApplyBoneMeal * Add tests for EffApplyBoneMeal * Add tests for EffDoIf * Add tests for EffSwingHand * Replace static imports * Add tests for EffFeed * Fix EffSwingHandTest on unsupported versions * Add tests for EffMakeFly * Add tests for EffPvP * Remove double whitespace * Add tests for EffOp --------- Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- build.gradle | 2 +- src/main/java/ch/njol/skript/Skript.java | 3 + .../njol/skript/effects/EffApplyBoneMeal.java | 2 +- .../ch/njol/skript/effects/EffSwingHand.java | 2 +- .../skript/test/runner/SkriptJUnitTest.java | 2 +- .../syntaxes/effects/EffActionBarTest.java | 97 +++++++++++++++++++ .../effects/EffApplyBoneMealTest.java | 96 ++++++++++++++++++ .../tests/syntaxes/effects/EffFeedTest.java | 84 ++++++++++++++++ .../syntaxes/effects/EffMakeFlyTest.java | 73 ++++++++++++++ .../tests/syntaxes/effects/EffOpTest.java | 69 +++++++++++++ .../syntaxes/effects/EffSwingHandTest.java | 75 ++++++++++++++ .../syntaxes/{ => events}/EvtGrowTest.java | 2 +- src/test/skript/junit/EvtGrow.sk | 80 +++++++-------- .../skript/tests/syntaxes/effects/EffDoIf.sk | 6 ++ .../skript/tests/syntaxes/effects/EffPvP.sk | 6 ++ 15 files changed, 554 insertions(+), 45 deletions(-) create mode 100644 src/test/java/org/skriptlang/skript/test/tests/syntaxes/effects/EffActionBarTest.java create mode 100644 src/test/java/org/skriptlang/skript/test/tests/syntaxes/effects/EffApplyBoneMealTest.java create mode 100644 src/test/java/org/skriptlang/skript/test/tests/syntaxes/effects/EffFeedTest.java create mode 100644 src/test/java/org/skriptlang/skript/test/tests/syntaxes/effects/EffMakeFlyTest.java create mode 100644 src/test/java/org/skriptlang/skript/test/tests/syntaxes/effects/EffOpTest.java create mode 100644 src/test/java/org/skriptlang/skript/test/tests/syntaxes/effects/EffSwingHandTest.java rename src/test/java/org/skriptlang/skript/test/tests/syntaxes/{ => events}/EvtGrowTest.java (97%) create mode 100644 src/test/skript/tests/syntaxes/effects/EffDoIf.sk create mode 100644 src/test/skript/tests/syntaxes/effects/EffPvP.sk diff --git a/build.gradle b/build.gradle index dfe1f3284b7..5ab26a2e631 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.2.0' + testShadow group: 'org.easymock', name: 'easymock', version: '5.0.1' } task checkAliases { diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 7b1ac6c0eab..291ebff5ae4 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -691,6 +691,9 @@ protected void afterErrors() { long milliseconds = 0, tests = 0, fails = 0, ignored = 0, size = 0; try { List<Class<?>> classes = Lists.newArrayList(Utils.getClasses(Skript.getInstance(), "org.skriptlang.skript.test", "tests")); + // Don't attempt to run inner/anonymous classes as tests + classes.removeIf(Class::isAnonymousClass); + classes.removeIf(Class::isLocalClass); // Test that requires package access. This is only present when compiling with src/test. classes.add(Class.forName("ch.njol.skript.variables.FlatFileStorageTest")); size = classes.size(); diff --git a/src/main/java/ch/njol/skript/effects/EffApplyBoneMeal.java b/src/main/java/ch/njol/skript/effects/EffApplyBoneMeal.java index 484654a14f6..080f1cd3982 100644 --- a/src/main/java/ch/njol/skript/effects/EffApplyBoneMeal.java +++ b/src/main/java/ch/njol/skript/effects/EffApplyBoneMeal.java @@ -71,7 +71,7 @@ protected void execute(Event event) { @Override public String toString(@Nullable Event event, boolean debug) { - return "apply " + amount != null ? amount.toString(event, debug) + " " : "" + "bone meal to " + blocks.toString(event, debug); + return "apply " + (amount != null ? amount.toString(event, debug) + " " : "" + "bone meal to " + blocks.toString(event, debug)); } } diff --git a/src/main/java/ch/njol/skript/effects/EffSwingHand.java b/src/main/java/ch/njol/skript/effects/EffSwingHand.java index a129254de9f..826cce82978 100644 --- a/src/main/java/ch/njol/skript/effects/EffSwingHand.java +++ b/src/main/java/ch/njol/skript/effects/EffSwingHand.java @@ -46,7 +46,7 @@ public class EffSwingHand extends Effect { "make %livingentities% swing [their] off[ ]hand"); } - private static final boolean SWINGING_IS_SUPPORTED = Skript.methodExists(LivingEntity.class, "swingMainHand"); + public static final boolean SWINGING_IS_SUPPORTED = Skript.methodExists(LivingEntity.class, "swingMainHand"); @SuppressWarnings("null") private Expression<LivingEntity> entities; diff --git a/src/main/java/ch/njol/skript/test/runner/SkriptJUnitTest.java b/src/main/java/ch/njol/skript/test/runner/SkriptJUnitTest.java index 38ec51d116c..65a52f0ac1a 100644 --- a/src/main/java/ch/njol/skript/test/runner/SkriptJUnitTest.java +++ b/src/main/java/ch/njol/skript/test/runner/SkriptJUnitTest.java @@ -38,7 +38,7 @@ public abstract class SkriptJUnitTest { static { - World world = Bukkit.getWorlds().get(0); + World world = getTestWorld(); world.setGameRule(GameRule.MAX_ENTITY_CRAMMING, 1000); world.setGameRule(GameRule.DO_WEATHER_CYCLE, false); // Natural entity spawning diff --git a/src/test/java/org/skriptlang/skript/test/tests/syntaxes/effects/EffActionBarTest.java b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/effects/EffActionBarTest.java new file mode 100644 index 00000000000..125f22e86e8 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/effects/EffActionBarTest.java @@ -0,0 +1,97 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.test.tests.syntaxes.effects; + + +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.TriggerItem; +import ch.njol.skript.lang.util.ContextlessEvent; +import ch.njol.skript.test.runner.SkriptJUnitTest; +import ch.njol.skript.variables.Variables; +import net.md_5.bungee.api.ChatMessageType; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.TextComponent; +import org.bukkit.entity.Player; +import org.easymock.EasyMock; +import org.easymock.IArgumentMatcher; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + + +@SuppressWarnings("deprecation") +public class EffActionBarTest extends SkriptJUnitTest { + + private Player testPlayer; + private Player.Spigot testSpigotPlayer; + private Effect actionBarEffect; + + @Before + public void setup() { + testPlayer = EasyMock.niceMock(Player.class); + testSpigotPlayer = EasyMock.niceMock(Player.Spigot.class); + actionBarEffect = Effect.parse("send actionbar {_content} to {_player}", null); + } + + @Test + public void test() { + if (actionBarEffect == null) + Assert.fail("Effect is null"); + + String expectedActionBarContent = "hello world"; + + EasyMock.expect(testPlayer.spigot()).andAnswer(() -> testSpigotPlayer); + + testSpigotPlayer.sendMessage( + EasyMock.eq(ChatMessageType.ACTION_BAR), + (BaseComponent[]) componentMatcher(expectedActionBarContent) + ); + + EasyMock.expectLastCall(); + + EasyMock.replay(testPlayer, testSpigotPlayer); + + ContextlessEvent event = ContextlessEvent.get(); + Variables.setVariable("content", expectedActionBarContent, event, true); + Variables.setVariable("player", testPlayer, event, true); + TriggerItem.walk(actionBarEffect, event); + + EasyMock.verify(testPlayer, testSpigotPlayer); + } + + private <T> T componentMatcher(String expectedContent) { + EasyMock.reportMatcher(new IArgumentMatcher() { + @Override + public boolean matches(Object argument) { + if (argument instanceof TextComponent) { + return ((TextComponent) argument).getText().equals(expectedContent); + } + return false; + } + + @Override + public void appendTo(StringBuffer buffer) { + buffer.append("[component matcher]"); + } + }); + + return null; + } + +} diff --git a/src/test/java/org/skriptlang/skript/test/tests/syntaxes/effects/EffApplyBoneMealTest.java b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/effects/EffApplyBoneMealTest.java new file mode 100644 index 00000000000..5b7a5b8aa1f --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/effects/EffApplyBoneMealTest.java @@ -0,0 +1,96 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.test.tests.syntaxes.effects; + + +import ch.njol.skript.Skript; +import ch.njol.skript.effects.EffApplyBoneMeal; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.SyntaxElementInfo; +import ch.njol.skript.lang.TriggerItem; +import ch.njol.skript.lang.util.ContextlessEvent; +import ch.njol.skript.test.runner.SkriptJUnitTest; +import ch.njol.skript.variables.Variables; +import org.bukkit.block.Block; +import org.easymock.EasyMock; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; + +public class EffApplyBoneMealTest { + + private Block stubTestBlock; + private Effect applyBonemealEffect; + private Effect applyMultipleBonemealEffect; + + @Before + public void setup() { + stubTestBlock = EasyMock.niceMock(Block.class); + applyBonemealEffect = Effect.parse("apply bonemeal to {_block}", null); + applyMultipleBonemealEffect = Effect.parse("apply {_times} bonemeal to {_block}", null); + } + + @Test + public void test() { + boolean bonemealEffectRegistered = Skript.getEffects().stream() + .map(SyntaxElementInfo::getElementClass) + .anyMatch(EffApplyBoneMeal.class::equals); + if (!bonemealEffectRegistered) + return; + if (applyBonemealEffect == null) + Assert.fail("Effect is null"); + if (applyMultipleBonemealEffect == null) + Assert.fail("Multiple effect is null"); + + int countOfBonemealToApply = 5; + ContextlessEvent event = ContextlessEvent.get(); + Variables.setVariable("block", getMockBlock(), event, true); + Variables.setVariable("times", countOfBonemealToApply, event, true); + + EasyMock.expect(stubTestBlock.applyBoneMeal(EasyMock.notNull())).andReturn(true).times(1); + EasyMock.replay(stubTestBlock); + TriggerItem.walk(applyBonemealEffect, event); + EasyMock.verify(stubTestBlock); + + EasyMock.resetToNice(stubTestBlock); + EasyMock.expect(stubTestBlock.applyBoneMeal(EasyMock.notNull())).andReturn(true).times(2); + EasyMock.replay(stubTestBlock); + TriggerItem.walk(applyMultipleBonemealEffect, event); + EasyMock.verify(stubTestBlock); + } + + private Block getMockBlock() { + Block realBlock = SkriptJUnitTest.getBlock(); + + // we need to intercept applyBoneMeal calls so that easymock can detect them + // but we need to pass the other calls to a real block so that a real blockdata, + // material, location, etc are available + InvocationHandler handler = (proxy, method, args) -> { + if (method.getName().equals("applyBoneMeal")) { + return method.invoke(stubTestBlock, args); + } + return method.invoke(realBlock, args); + }; + + return (Block) Proxy.newProxyInstance(getClass().getClassLoader(), new Class<?>[] { Block.class }, handler); + } +} diff --git a/src/test/java/org/skriptlang/skript/test/tests/syntaxes/effects/EffFeedTest.java b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/effects/EffFeedTest.java new file mode 100644 index 00000000000..1235eea38ab --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/effects/EffFeedTest.java @@ -0,0 +1,84 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.test.tests.syntaxes.effects; + +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.TriggerItem; +import ch.njol.skript.lang.util.ContextlessEvent; +import ch.njol.skript.test.runner.SkriptJUnitTest; +import ch.njol.skript.variables.Variables; +import org.bukkit.entity.Player; +import org.easymock.EasyMock; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; + +public class EffFeedTest extends SkriptJUnitTest { + + private Player easyMockPlayer; + private Effect feedFullyEffect; + private Effect feedPartiallyEffect; + + @Before + public void setup() { + easyMockPlayer = EasyMock.niceMock(Player.class); + feedFullyEffect = Effect.parse("feed {_player}", null); + feedPartiallyEffect = Effect.parse("feed {_player} by {_amount} beef", null); + } + + @Test + public void test() { + if (feedFullyEffect == null) + Assert.fail("Fully effect is null"); + if (feedPartiallyEffect == null) + Assert.fail("Partially effect is null"); + + int amountToFeed = 1; + int maxFoodLevel = 20; + ContextlessEvent event = ContextlessEvent.get(); + Variables.setVariable("player", getMockPlayer(), event, true); + Variables.setVariable("amount", amountToFeed, event, true); + + easyMockPlayer.setFoodLevel(EasyMock.eq(maxFoodLevel)); + EasyMock.expectLastCall(); + EasyMock.replay(easyMockPlayer); + TriggerItem.walk(feedFullyEffect, event); + EasyMock.verify(easyMockPlayer); + + EasyMock.resetToNice(easyMockPlayer); + easyMockPlayer.setFoodLevel(EasyMock.eq(amountToFeed)); + EasyMock.expectLastCall(); + EasyMock.replay(easyMockPlayer); + TriggerItem.walk(feedPartiallyEffect, event); + EasyMock.verify(easyMockPlayer); + } + + private Player getMockPlayer() { + InvocationHandler handler = (proxy, method, args) -> { + if (method.getName().equals("getFoodLevel")) + return 0; + return method.invoke(easyMockPlayer, args); + }; + return (Player) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] { Player.class }, handler); + } + +} diff --git a/src/test/java/org/skriptlang/skript/test/tests/syntaxes/effects/EffMakeFlyTest.java b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/effects/EffMakeFlyTest.java new file mode 100644 index 00000000000..28497059e53 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/effects/EffMakeFlyTest.java @@ -0,0 +1,73 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.test.tests.syntaxes.effects; + +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.TriggerItem; +import ch.njol.skript.lang.util.ContextlessEvent; +import ch.njol.skript.test.runner.SkriptJUnitTest; +import ch.njol.skript.variables.Variables; +import org.bukkit.entity.Player; +import org.easymock.EasyMock; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class EffMakeFlyTest extends SkriptJUnitTest { + + private Player testPlayer; + private Effect startFlyingEffect; + private Effect stopFlyingEffect; + + @Before + public void setup() { + testPlayer = EasyMock.niceMock(Player.class); + startFlyingEffect = Effect.parse("make {_player} start flying", null); + stopFlyingEffect = Effect.parse("make {_player} stop flying", null); + } + + @Test + public void test() { + if (startFlyingEffect == null) + Assert.fail("Start flying effect is null"); + if (stopFlyingEffect == null) + Assert.fail("Stop flying effect is null"); + + ContextlessEvent event = ContextlessEvent.get(); + Variables.setVariable("player", testPlayer, event, true); + + testPlayer.setAllowFlight(true); + EasyMock.expectLastCall(); + testPlayer.setFlying(true); + EasyMock.expectLastCall(); + EasyMock.replay(testPlayer); + TriggerItem.walk(startFlyingEffect, event); + EasyMock.verify(testPlayer); + + EasyMock.resetToNice(testPlayer); + testPlayer.setAllowFlight(false); + EasyMock.expectLastCall(); + testPlayer.setFlying(false); + EasyMock.expectLastCall(); + EasyMock.replay(testPlayer); + TriggerItem.walk(stopFlyingEffect, event); + EasyMock.verify(testPlayer); + } + +} diff --git a/src/test/java/org/skriptlang/skript/test/tests/syntaxes/effects/EffOpTest.java b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/effects/EffOpTest.java new file mode 100644 index 00000000000..69b237292d9 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/effects/EffOpTest.java @@ -0,0 +1,69 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.test.tests.syntaxes.effects; + +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.TriggerItem; +import ch.njol.skript.lang.util.ContextlessEvent; +import ch.njol.skript.test.runner.SkriptJUnitTest; +import ch.njol.skript.variables.Variables; +import org.bukkit.entity.Player; +import org.easymock.EasyMock; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class EffOpTest extends SkriptJUnitTest { + + private Player testPlayer; + private Effect opPlayerEffect; + private Effect deopPlayerEffect; + + @Before + public void setup() { + testPlayer = EasyMock.niceMock(Player.class); + opPlayerEffect = Effect.parse("op {_player}", null); + deopPlayerEffect = Effect.parse("deop {_player}", null); + } + + @Test + public void test() { + if (opPlayerEffect == null) + Assert.fail("Op player effect is null"); + if (deopPlayerEffect == null) + Assert.fail("Deop player effect is null"); + + ContextlessEvent event = ContextlessEvent.get(); + Variables.setVariable("player", testPlayer, event, true); + + testPlayer.setOp(true); + EasyMock.expectLastCall(); + EasyMock.replay(testPlayer); + TriggerItem.walk(opPlayerEffect, event); + EasyMock.verify(testPlayer); + + EasyMock.resetToNice(testPlayer); + testPlayer.setOp(false); + EasyMock.expectLastCall(); + EasyMock.replay(testPlayer); + TriggerItem.walk(deopPlayerEffect, event); + EasyMock.verify(testPlayer); + } + +} diff --git a/src/test/java/org/skriptlang/skript/test/tests/syntaxes/effects/EffSwingHandTest.java b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/effects/EffSwingHandTest.java new file mode 100644 index 00000000000..1b5da4d1a46 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/effects/EffSwingHandTest.java @@ -0,0 +1,75 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.test.tests.syntaxes.effects; + +import ch.njol.skript.Skript; +import ch.njol.skript.effects.EffSwingHand; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.SyntaxElementInfo; +import ch.njol.skript.lang.TriggerItem; +import ch.njol.skript.lang.util.ContextlessEvent; +import ch.njol.skript.test.runner.SkriptJUnitTest; +import ch.njol.skript.variables.Variables; +import org.bukkit.entity.LivingEntity; +import org.easymock.EasyMock; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + + +public class EffSwingHandTest extends SkriptJUnitTest { + + private LivingEntity testEntity; + private Effect swingMainHandEffect; + private Effect swingOffhandEffect; + + @Before + public void setup() { + testEntity = EasyMock.niceMock(LivingEntity.class); + swingMainHandEffect = Effect.parse("make {_entity} swing their main hand", null); + swingOffhandEffect = Effect.parse("make {_entity} swing their offhand", null); + } + + @Test + public void test() { + if (!EffSwingHand.SWINGING_IS_SUPPORTED) + return; + if (swingMainHandEffect == null) + Assert.fail("Main hand is null"); + if (swingOffhandEffect == null) + Assert.fail("Offhand effect is null"); + + ContextlessEvent event = ContextlessEvent.get(); + Variables.setVariable("entity", testEntity, event, true); + + testEntity.swingMainHand(); + EasyMock.expectLastCall(); + EasyMock.replay(testEntity); + TriggerItem.walk(swingMainHandEffect, event); + EasyMock.verify(testEntity); + + EasyMock.resetToNice(testEntity); + testEntity.swingOffHand(); + EasyMock.expectLastCall(); + EasyMock.replay(testEntity); + TriggerItem.walk(swingOffhandEffect, event); + EasyMock.verify(testEntity); + } + +} diff --git a/src/test/java/org/skriptlang/skript/test/tests/syntaxes/EvtGrowTest.java b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/events/EvtGrowTest.java similarity index 97% rename from src/test/java/org/skriptlang/skript/test/tests/syntaxes/EvtGrowTest.java rename to src/test/java/org/skriptlang/skript/test/tests/syntaxes/events/EvtGrowTest.java index 96baf1b5c32..44cb566c33f 100644 --- a/src/test/java/org/skriptlang/skript/test/tests/syntaxes/EvtGrowTest.java +++ b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/events/EvtGrowTest.java @@ -16,7 +16,7 @@ * * Copyright Peter Güttinger, SkriptLang team and contributors */ -package org.skriptlang.skript.test.tests.syntaxes; +package org.skriptlang.skript.test.tests.syntaxes.events; import ch.njol.skript.Skript; import ch.njol.skript.test.runner.SkriptJUnitTest; diff --git a/src/test/skript/junit/EvtGrow.sk b/src/test/skript/junit/EvtGrow.sk index e932519af9b..080dfd819b9 100644 --- a/src/test/skript/junit/EvtGrow.sk +++ b/src/test/skript/junit/EvtGrow.sk @@ -2,69 +2,69 @@ on script load: # prior to 1.18, applyBoneMeal either did not exist or did not fire events # so we need to complete the objectives manually to avoid the tests failing if running below minecraft "1.18": - complete objective "grow of wheat" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" - complete objective "grow from wheat" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" - complete objective "grow to wheat" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" - complete objective "grow of wheat (blockdata)" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" - complete objective "grow from wheat (blockdata)" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" - complete objective "grow to wheat (blockdata)" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" - complete objective "grow of birch tree" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" - complete objective "grow of birch sapling" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" - complete objective "grow from birch sapling" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" - complete objective "grow to birch tree" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + complete objective "grow of wheat" for junit test "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" + complete objective "grow from wheat" for junit test "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" + complete objective "grow to wheat" for junit test "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" + complete objective "grow of wheat (blockdata)" for junit test "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" + complete objective "grow from wheat (blockdata)" for junit test "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" + complete objective "grow to wheat (blockdata)" for junit test "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" + complete objective "grow of birch tree" for junit test "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" + complete objective "grow of birch sapling" for junit test "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" + complete objective "grow from birch sapling" for junit test "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" + complete objective "grow to birch tree" for junit test "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" # itemtype - ensure junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" completes "grow of wheat" - ensure junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" completes "grow from wheat" - ensure junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" completes "grow to wheat" + ensure junit test "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" completes "grow of wheat" + ensure junit test "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" completes "grow from wheat" + ensure junit test "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" completes "grow to wheat" # blockdata - ensure junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" completes "grow of wheat (blockdata)" - ensure junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" completes "grow from wheat (blockdata)" - ensure junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" completes "grow to wheat (blockdata)" + ensure junit test "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" completes "grow of wheat (blockdata)" + ensure junit test "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" completes "grow from wheat (blockdata)" + ensure junit test "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" completes "grow to wheat (blockdata)" # structures - ensure junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" completes "grow of birch tree" - ensure junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" completes "grow of birch sapling" - ensure junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" completes "grow from birch sapling" - ensure junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" completes "grow to birch tree" + ensure junit test "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" completes "grow of birch tree" + ensure junit test "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" completes "grow of birch sapling" + ensure junit test "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" completes "grow from birch sapling" + ensure junit test "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" completes "grow to birch tree" on grow of wheat: - junit test is "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" - complete objective "grow of wheat" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + junit test is "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" + complete objective "grow of wheat" for junit test "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" on grow of wheat[age=0]: - junit test is "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" - complete objective "grow of wheat (blockdata)" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + junit test is "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" + complete objective "grow of wheat (blockdata)" for junit test "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" on grow from wheat: - junit test is "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" - complete objective "grow from wheat" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + junit test is "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" + complete objective "grow from wheat" for junit test "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" on grow from wheat[age=0]: - junit test is "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" - complete objective "grow from wheat (blockdata)" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + junit test is "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" + complete objective "grow from wheat (blockdata)" for junit test "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" on grow to wheat: - junit test is "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" - complete objective "grow to wheat" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + junit test is "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" + complete objective "grow to wheat" for junit test "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" on grow to wheat[age=7]: - junit test is "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" - complete objective "grow to wheat (blockdata)" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + junit test is "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" + complete objective "grow to wheat (blockdata)" for junit test "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" on grow of birch tree: - junit test is "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" - complete objective "grow of birch tree" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + junit test is "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" + complete objective "grow of birch tree" for junit test "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" on grow of birch sapling: - junit test is "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" - complete objective "grow of birch sapling" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + junit test is "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" + complete objective "grow of birch sapling" for junit test "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" on grow from birch sapling: - junit test is "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" - complete objective "grow from birch sapling" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + junit test is "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" + complete objective "grow from birch sapling" for junit test "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" on grow to birch tree: - junit test is "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" - complete objective "grow to birch tree" for junit test "org.skriptlang.skript.test.tests.syntaxes.EvtGrowTest" + junit test is "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" + complete objective "grow to birch tree" for junit test "org.skriptlang.skript.test.tests.syntaxes.events.EvtGrowTest" diff --git a/src/test/skript/tests/syntaxes/effects/EffDoIf.sk b/src/test/skript/tests/syntaxes/effects/EffDoIf.sk new file mode 100644 index 00000000000..da17471d8d5 --- /dev/null +++ b/src/test/skript/tests/syntaxes/effects/EffDoIf.sk @@ -0,0 +1,6 @@ +test "do if": + set {_false} to false if 1 is 1 + assert {_false} is false with "Do if didn't run when it should have" + + set {_unset} to true if 1 is 2 + assert {_unset} is not set with "Do if ran when it shouldn't have" diff --git a/src/test/skript/tests/syntaxes/effects/EffPvP.sk b/src/test/skript/tests/syntaxes/effects/EffPvP.sk new file mode 100644 index 00000000000..bb41b776670 --- /dev/null +++ b/src/test/skript/tests/syntaxes/effects/EffPvP.sk @@ -0,0 +1,6 @@ +test "pvp effect": + set {_world} to first element out of all worlds + disable pvp in {_world} + assert pvp is disabled in {_world} with "PvP was not disabled" + enable pvp in {_world} + assert pvp is enabled in {_world} with "PvP was not enabled" From 27d1a304683375579d99fc01ff587b95bb2f2f4b Mon Sep 17 00:00:00 2001 From: Moderocky <admin@moderocky.com> Date: Mon, 1 Jan 2024 01:02:06 +0000 Subject: [PATCH 552/619] Re-enable the vector from location syntax (#6180) * Re-enable (and fix) location from vector. * Add test for location from vector. * Add better support for direction. * Add direction tests. * Update src/main/java/ch/njol/skript/expressions/ExprLocationFromVector.java Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> * Fix silly sovde's broken tests * Update src/main/java/ch/njol/skript/expressions/ExprLocationFromVector.java Co-authored-by: Patrick Miller <apickledwalrus@gmail.com> --------- Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> Co-authored-by: Patrick Miller <apickledwalrus@gmail.com> --- .../expressions/ExprLocationFromVector.java | 55 +++++++++++-------- .../expressions/ExprLocationFromVector.sk | 24 ++++++++ 2 files changed, 55 insertions(+), 24 deletions(-) create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprLocationFromVector.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprLocationFromVector.java b/src/main/java/ch/njol/skript/expressions/ExprLocationFromVector.java index 145604fa8fb..f8dd3fc9c81 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprLocationFromVector.java +++ b/src/main/java/ch/njol/skript/expressions/ExprLocationFromVector.java @@ -18,11 +18,12 @@ */ package ch.njol.skript.expressions; +import ch.njol.skript.Skript; +import ch.njol.skript.lang.ExpressionType; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.event.Event; import org.bukkit.util.Vector; -import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; @@ -33,6 +34,7 @@ import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; +import org.jetbrains.annotations.Nullable; /** * @author bi0qaw @@ -46,11 +48,12 @@ public class ExprLocationFromVector extends SimpleExpression<Location> { static { - // TODO fix slowdowns and enable again, for now nuked for greater good -// Skript.registerExpression(ExprLocationFromVector.class, Location.class, ExpressionType.SIMPLE, -// "%vector% [to location] [in] %world%", "location (from|of) %vector% [(from|in)] %world%", -// "%vector% [to location] [in] %world% with yaw %number% and pitch %number%", -// "location (from|of) %vector% [(in|from)] %world% with yaw %number% and pitch %number%"); + Skript.registerExpression(ExprLocationFromVector.class, Location.class, ExpressionType.SIMPLE, + "%vector% [to location] in %world%", + "location (from|of) %vector% in %world%", + "%vector% [to location] in %world% with yaw %number% and pitch %number%", + "location (from|of) %vector% in %world% with yaw %number% and pitch %number%" + ); } @SuppressWarnings("null") @@ -60,17 +63,17 @@ public class ExprLocationFromVector extends SimpleExpression<Location> { private Expression<World> world; @SuppressWarnings("null") - private Expression<Number> yaw, pitch; - private boolean yawpitch; + private @Nullable Expression<Number> yaw, pitch; + private boolean hasDirection; @Override @SuppressWarnings({"unchecked", "null"}) public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { if (exprs.length > 3) - yawpitch = true; + hasDirection = true; vector = (Expression<Vector>) exprs[0]; world = (Expression<World>) exprs[1]; - if (yawpitch) { + if (hasDirection) { yaw = (Expression<Number>) exprs[2]; pitch = (Expression<Number>) exprs[3]; } @@ -79,17 +82,21 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @SuppressWarnings("null") @Override - protected Location[] get(Event e) { - Vector v = vector.getSingle(e); - World w = world.getSingle(e); - Number y = yaw != null ? yaw.getSingle(e) : null; - Number p = pitch != null ? pitch.getSingle(e) : null; - if (v == null || w == null) + protected Location[] get(Event event) { + Vector vector = this.vector.getSingle(event); + World world = this.world.getSingle(event); + if (vector == null || world == null) return null; - if (y == null || p == null) - return CollectionUtils.array(v.toLocation(w)); - else - return CollectionUtils.array(v.toLocation(w, y.floatValue(), p.floatValue())); + direction: + if (hasDirection) { + assert yaw != null && pitch != null; + Number yaw = this.yaw.getSingle(event); + Number pitch = this.pitch.getSingle(event); + if (yaw == null && pitch == null) + break direction; + return CollectionUtils.array(vector.toLocation(world, yaw == null ? 0 : yaw.floatValue(), pitch == null ? 0 : pitch.floatValue())); + } + return CollectionUtils.array(vector.toLocation(world)); } @Override @@ -103,10 +110,10 @@ public Class<? extends Location> getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { - if (yawpitch) - return "location from " + vector.toString(e, debug) + " with yaw " + yaw.toString() + " and pitch " + pitch.toString(e, debug); - return "location from " + vector.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + if (hasDirection) + return "location of " + vector.toString(event, debug) + " in " + world.toString(event, debug) + " with yaw " + yaw.toString(event, debug) + " and pitch " + pitch.toString(event, debug); + return "location of " + vector.toString(event, debug) + " in " + world.toString(event, debug); } } diff --git a/src/test/skript/tests/syntaxes/expressions/ExprLocationFromVector.sk b/src/test/skript/tests/syntaxes/expressions/ExprLocationFromVector.sk new file mode 100644 index 00000000000..46ca2a34633 --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprLocationFromVector.sk @@ -0,0 +1,24 @@ +test "location from vector": + set {_world} to world "world" + + set {_vector} to vector(x coordinate of spawn of {_world}, y coordinate of spawn of {_world}, z coordinate of spawn of {_world}) + set {_location} to location of {_vector} in {_world} + assert {_location} is spawn of {_world} with "location from vector failed, expected %spawn of {_world}%, got %{_vector}%" + + set {_vector} to vector of {_location} + assert the x component of {_vector} is x coordinate of spawn of {_world} with "re-creating vector x failed" + assert the y component of {_vector} is y coordinate of spawn of {_world} with "re-creating vector y failed" + assert the z component of {_vector} is z coordinate of spawn of {_world} with "re-creating vector z failed" + + set {_location} to location of vector(0, -5, 43.5) in {_world} + assert the x coordinate of {_location} is 0 with "vector to location x failed" + assert the y coordinate of {_location} is -5 with "vector to location y failed" + assert the z coordinate of {_location} is 43.5 with "vector to location z failed" + + set {_location} to location of vector(0, 0, 0) in {_world} with yaw 25 and pitch -90 + assert the yaw of {_location} is 25 with "vector to location yaw failed" + assert the pitch of {_location} is -90 with "vector to location pitch failed" + + set {_location} to location of vector(0, 0, 0) in {_world} with yaw 25 and pitch {_null} + assert the yaw of {_location} is 25 with "vector to location absent yaw failed" + assert the pitch of {_location} is 0 with "vector to location absent pitch failed" From 3b3ebb2d9cb925bdaee2d4e9c2f72df7f63b320c Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Sun, 31 Dec 2023 18:09:54 -0700 Subject: [PATCH 553/619] Fix ExprTarget not accounting for Spigot servers and add ignore blocks (#6157) * Fix ExprTarget not accounting for Spigot servers and add ignore blocks * Apply suggestions * Apply suggestions * Revert change * Update src/main/java/ch/njol/skript/expressions/ExprTarget.java Co-authored-by: _tud <mmbakkar06@gmail.com> * Update ExprTarget.java * Update ExprTarget.java * Set the max target block distance * Paper not needed and add Display entity support * Update ExprTarget.java * Update ExprTarget.java * Update ExprTarget.java * Ignore spectators * Apply changes * Fix null pointer --------- Co-authored-by: _tud <mmbakkar06@gmail.com> Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- .../njol/skript/expressions/ExprTarget.java | 110 +++++++++++++----- 1 file changed, 84 insertions(+), 26 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprTarget.java b/src/main/java/ch/njol/skript/expressions/ExprTarget.java index 1d8097aceb5..8fba8caee04 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTarget.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTarget.java @@ -18,6 +18,22 @@ */ package ch.njol.skript.expressions; +import java.util.function.Predicate; + +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Mob; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.entity.EntityTargetEvent; +import org.bukkit.util.RayTraceResult; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.ApiStatus.ScheduledForRemoval; +import org.jetbrains.annotations.Nullable; + import ch.njol.skript.Skript; import ch.njol.skript.SkriptConfig; import ch.njol.skript.classes.Changer.ChangeMode; @@ -34,23 +50,13 @@ import ch.njol.skript.registrations.EventValues; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; -import org.bukkit.OfflinePlayer; -import org.bukkit.entity.Entity; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Mob; -import org.bukkit.entity.Player; -import org.bukkit.event.Event; -import org.bukkit.event.entity.EntityTargetEvent; -import org.bukkit.util.RayTraceResult; -import org.bukkit.util.Vector; -import org.eclipse.jdt.annotation.Nullable; - -import java.util.List; @Name("Target") @Description({ "For players this is the entity at the crosshair.", - "For mobs and experience orbs this is the entity they are attacking/following (if any)." + "For mobs and experience orbs this is the entity they are attacking/following (if any).", + "Display entities have a hit box of 0, so you should use 'target display' to collect Display entities", + "May grab entities in unloaded chunks." }) @Examples({ "on entity target:", @@ -61,15 +67,22 @@ "delete targeted entity of player # for players it will delete the target", "delete target of last spawned zombie # for entities it will make them target-less" }) -@Since("1.4.2, 2.7 (Reset)") +@Since("1.4.2, 2.7 (Reset), INSERT VERSION (ignore blocks)") public class ExprTarget extends PropertyExpression<LivingEntity, Entity> { static { Skript.registerExpression(ExprTarget.class, Entity.class, ExpressionType.PROPERTY, - "[the] target[[ed] %-*entitydata%] [of %livingentities%]", - "%livingentities%'[s] target[[ed] %-*entitydata%]"); + "[the] target[[ed] %-*entitydata%] [of %livingentities%] [blocks:ignoring blocks] [[with|at] ray[ ]size %-number%]", // TODO add a where filter when extendable https://github.com/SkriptLang/Skript/issues/4856 + "%livingentities%'[s] target[[ed] %-*entitydata%] [blocks:ignoring blocks] [[with|at] ray[ ]size %-number%]" + ); } + private static boolean ignoreBlocks; + private static int targetBlockDistance; + + @Nullable + private Expression<Number> raysize; + @Nullable private EntityData<?> type; @@ -78,11 +91,17 @@ public class ExprTarget extends PropertyExpression<LivingEntity, Entity> { public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) { type = exprs[matchedPattern] == null ? null : (EntityData<?>) exprs[matchedPattern].getSingle(null); setExpr((Expression<? extends LivingEntity>) exprs[1 - matchedPattern]); + targetBlockDistance = SkriptConfig.maxTargetBlockDistance.value(); + if (targetBlockDistance < 0) + targetBlockDistance = 100; + ignoreBlocks = parser.hasTag("blocks"); + raysize = (Expression<Number>) exprs[2]; return true; } @Override protected Entity[] get(Event event, LivingEntity[] source) { + double raysize = this.raysize != null ? this.raysize.getOptionalSingle(event).orElse(0.0).doubleValue() : 0.0D; return get(source, entity -> { if (event instanceof EntityTargetEvent && entity.equals(((EntityTargetEvent) event).getEntity()) && !Delay.isDelayed(event)) { Entity target = ((EntityTargetEvent) event).getTarget(); @@ -90,7 +109,7 @@ protected Entity[] get(Event event, LivingEntity[] source) { return null; return target; } - return getTarget(entity, type); + return getTarget(entity, type, raysize); }); } @@ -118,11 +137,12 @@ public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { targetEvent.setTarget(target); } } else { + double raysize = this.raysize != null ? this.raysize.getOptionalSingle(event).orElse(0.0).doubleValue() : 0.0D; for (LivingEntity entity : getExpr().getArray(event)) { if (entity instanceof Mob) { ((Mob) entity).setTarget(target); } else if (entity instanceof Player && mode == ChangeMode.DELETE) { - Entity playerTarget = getTarget(entity, type); + Entity playerTarget = getTarget(entity, type, raysize); if (playerTarget != null && !(playerTarget instanceof OfflinePlayer)) playerTarget.remove(); } @@ -153,21 +173,59 @@ public String toString(@Nullable Event event, boolean debug) { /** * Gets an entity's target. * - * @param entity The entity to get the target of + * @param origin The entity to get the target of. + * @param type The exact EntityData to find. Can be null for any entity. + * @return The entity's target. + * @deprecated Use {@link #getTarget(LivingEntity, EntityData, double)} to include raysize. + */ + @Deprecated + @ScheduledForRemoval + public static <T extends Entity> T getTarget(LivingEntity origin, @Nullable EntityData<T> type) { + return getTarget(origin, type, 0.0D); + } + + /** + * Gets an entity's target entity. + * + * @param origin The entity to get the target of. * @param type The exact EntityData to find. Can be null for any entity. - * @return The entity's target + * @param raysize The size of the ray for the raytrace. + * @return The entity's target. */ @Nullable @SuppressWarnings("unchecked") - public static <T extends Entity> T getTarget(LivingEntity entity, @Nullable EntityData<T> type) { - if (entity instanceof Mob) - return ((Mob) entity).getTarget() == null || type != null && !type.isInstance(((Mob) entity).getTarget()) ? null : (T) ((Mob) entity).getTarget(); - - RayTraceResult result = entity.rayTraceEntities(SkriptConfig.maxTargetBlockDistance.value()); + public static <T extends Entity> T getTarget(LivingEntity origin, @Nullable EntityData<T> type, double raysize) { + if (origin instanceof Mob) + return ((Mob) origin).getTarget() == null || type != null && !type.isInstance(((Mob) origin).getTarget()) ? null : (T) ((Mob) origin).getTarget(); + Location location = origin.getLocation(); + RayTraceResult result = null; + // TODO when DisplayData is added. +// if (type.getClass().equals(DisplayData.class)) +// raysize = 1.0D; + Predicate<Entity> predicate = entity -> { + if (entity.equals(origin)) + return false; + if (type != null && !type.isInstance(entity)) + return false; + if (entity instanceof Player && ((Player) entity).getGameMode() == GameMode.SPECTATOR) + return false; + return true; + }; + if (!ignoreBlocks) { + RayTraceResult blockResult = origin.getWorld().rayTraceBlocks(origin.getEyeLocation(), location.getDirection(), targetBlockDistance); + if (blockResult != null) { + Vector hit = blockResult.getHitPosition(); + Location eyes = origin.getEyeLocation(); + if (hit != null) + result = origin.getWorld().rayTraceEntities(eyes, location.getDirection(), eyes.toVector().distance(hit), raysize, predicate); + } + } else { + result = origin.getWorld().rayTraceEntities(origin.getEyeLocation(), location.getDirection(), targetBlockDistance, raysize, predicate); + } if (result == null) return null; Entity hitEntity = result.getHitEntity(); - if (type != null && !type.isInstance(hitEntity)) + if (hitEntity == null) return null; return (T) result.getHitEntity(); } From d0ed4fc4996e21e8cc82b142efceb0be41b1811a Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Sun, 31 Dec 2023 18:14:21 -0700 Subject: [PATCH 554/619] Make the multiple event-values less strict (#6122) * Make the multiple event-values less strict * Debug message * Remove debug message see issue 6124 --------- Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- .../skript/registrations/EventValues.java | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/main/java/ch/njol/skript/registrations/EventValues.java b/src/main/java/ch/njol/skript/registrations/EventValues.java index d6956f32ee1..47b784d3ac2 100644 --- a/src/main/java/ch/njol/skript/registrations/EventValues.java +++ b/src/main/java/ch/njol/skript/registrations/EventValues.java @@ -82,7 +82,6 @@ public Class<T> getValueClass() { * @return The classes of the Excludes associated with this event value */ @Nullable - @SuppressWarnings("null") public Class<? extends E>[] getExcludes() { if (excludes != null) return Arrays.copyOf(excludes, excludes.length); @@ -98,26 +97,26 @@ public String getExcludeErrorMessage() { return excludeErrorMessage; } } - + private final static List<EventValueInfo<?, ?>> defaultEventValues = new ArrayList<>(30); private final static List<EventValueInfo<?, ?>> futureEventValues = new ArrayList<>(); private final static List<EventValueInfo<?, ?>> pastEventValues = new ArrayList<>(); - + /** * The past time of an event value. Represented by "past" or "former". */ public static final int TIME_PAST = -1; - + /** * The current time of an event value. */ public static final int TIME_NOW = 0; - + /** * The future time of an event value. */ public static final int TIME_FUTURE = 1; - + /** * Get Event Values list for the specified time * @param time The time of the event values. One of @@ -127,7 +126,7 @@ public String getExcludeErrorMessage() { public static List<EventValueInfo<?, ?>> getEventValuesListForTime(int time) { return ImmutableList.copyOf(getEventValuesList(time)); } - + private static List<EventValueInfo<?, ?>> getEventValuesList(int time) { if (time == -1) return pastEventValues; @@ -242,7 +241,7 @@ public static <T, E extends Event> T getEventValue(E e, Class<T> c, int time) { * @return true or false if the event and type have multiple getters. */ public static <T, E extends Event> Kleenean hasMultipleGetters(Class<E> event, Class<T> type, int time) { - List<Getter<? extends T, ? super E>> getters = getEventValueGetters(event, type, time, true); + List<Getter<? extends T, ? super E>> getters = getEventValueGetters(event, type, time, true, false); if (getters == null) return Kleenean.UNKNOWN; return Kleenean.get(getters.size() > 1); @@ -273,13 +272,18 @@ public static <T, E extends Event> Kleenean hasMultipleGetters(Class<E> event, C return list.get(0); } + @Nullable + private static <T, E extends Event> List<Getter<? extends T, ? super E>> getEventValueGetters(Class<E> event, Class<T> type, int time, boolean allowDefault) { + return getEventValueGetters(event, type, time, allowDefault, true); + } + /* * We need to be able to collect all possible event-values to a list for determining problematic collisions. * Always return after the loop check if the list is not empty. */ @Nullable @SuppressWarnings("unchecked") - private static <T, E extends Event> List<Getter<? extends T, ? super E>> getEventValueGetters(Class<E> event, Class<T> type, int time, boolean allowDefault) { + private static <T, E extends Event> List<Getter<? extends T, ? super E>> getEventValueGetters(Class<E> event, Class<T> type, int time, boolean allowDefault, boolean allowConverting) { List<EventValueInfo<?, ?>> eventValues = getEventValuesList(time); List<Getter<? extends T, ? super E>> list = new ArrayList<>(); // First check for exact classes matching the parameters. @@ -313,6 +317,8 @@ public T get(E event) { } if (!list.isEmpty()) return list; + if (!allowConverting) + return null; // Most checks have returned before this below is called, but Skript will attempt to convert or find an alternative. // Third check is if the returned object matches the class. for (EventValueInfo<?, ?> eventValueInfo : eventValues) { @@ -361,11 +367,11 @@ public T get(E event) { // The requesting event must be assignable to the event value's event. Otherwise it'll throw an error. if (!event.isAssignableFrom(eventValueInfo.event)) continue; - + Getter<? extends T, ? super E> getter = (Getter<? extends T, ? super E>) getConvertedGetter(eventValueInfo, type, true); if (getter == null) continue; - + if (!checkExcludes(eventValueInfo, event)) return null; list.add(getter); From 293727ff591d022e6c41c6441ce64741734dc155 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Mon, 1 Jan 2024 04:19:28 +0300 Subject: [PATCH 555/619] =?UTF-8?q?=F0=9F=9B=A0=20Change=20player=20parsin?= =?UTF-8?q?g=20to=20use=20`name=20starting=20with`=20(#5875)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Enhancement: Change player parsing to use 'starting with' * Little improvement * Address Review * Remove redundant variable * Update src/main/java/ch/njol/skript/classes/data/BukkitClasses.java Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> * Update src/main/java/ch/njol/skript/classes/data/BukkitClasses.java Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> * Add usage to player and offlineplayer types * Indentation --------- Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- .../skript/classes/data/BukkitClasses.java | 65 ++++++++++++------- 1 file changed, 41 insertions(+), 24 deletions(-) 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 b90442fd8c1..8e943ce4c7b 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -19,6 +19,7 @@ package ch.njol.skript.classes.data; import java.io.StreamCorruptedException; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Locale; @@ -660,32 +661,46 @@ public String toVariableNameString(final Inventory i) { Classes.registerClass(new ClassInfo<>(Player.class, "player") .user("players?") .name("Player") - .description("A player. Depending on whether a player is online or offline several actions can be performed with them, " + - "though you won't get any errors when using effects that only work if the player is online (e.g. changing their inventory) on an offline player.", + .description( + "A player. Depending on whether a player is online or offline several actions can be performed with them, " + + "though you won't get any errors when using effects that only work if the player is online (e.g. changing their inventory) on an offline player.", "You have two possibilities to use players as command arguments: <player> and <offline player>. " + - "The first requires that the player is online and also accepts only part of the name, " + - "while the latter doesn't require that the player is online, but the player's name has to be entered exactly.") - .usage("") - .examples("") - .since("1.0") + "The first requires that the player is online and also accepts only part of the name, " + + "while the latter doesn't require that the player is online, but the player's name has to be entered exactly." + ).usage( + "Parsing an offline player as a player (online) will return nothing (none), for that case you would need to parse as " + + "offlineplayer which only returns nothing (none) if player doesn't exist in Minecraft databases (name not taken) otherwise it will return the player regardless of their online status." + ).examples( + "set {_p} to \"Notch\" parsed as a player # returns <none> unless Notch is actually online or starts with Notch like Notchan", + "set {_p} to \"N\" parsed as a player # returns Notch if Notch is online because their name starts with 'N' (case insensitive) however, it would return nothing if no player whose name starts with 'N' is online." + ).since("1.0") .defaultExpression(new EventValueExpression<>(Player.class)) .after("string", "world") .parser(new Parser<Player>() { @Override @Nullable - public Player parse(String s, ParseContext context) { - if (context == ParseContext.COMMAND || context == ParseContext.PARSE) { - if (s.isEmpty()) + public Player parse(String string, ParseContext context) { + if (context == ParseContext.COMMAND) { + if (string.isEmpty()) return null; - if (UUID_PATTERN.matcher(s).matches()) - return Bukkit.getPlayer(UUID.fromString(s)); - List<Player> ps = Bukkit.matchPlayer(s); - if (ps.size() == 1) - return ps.get(0); - if (ps.size() == 0) - Skript.error(String.format(Language.get("commands.no player starts with"), s)); + if (UUID_PATTERN.matcher(string).matches()) + return Bukkit.getPlayer(UUID.fromString(string)); + String name = string.toLowerCase(Locale.ENGLISH); + int nameLength = name.length(); // caching + List<Player> players = new ArrayList<>(); + for (Player player : Bukkit.getOnlinePlayers()) { + if (player.getName().toLowerCase(Locale.ENGLISH).startsWith(name)) { + if (player.getName().length() == nameLength) // a little better in performance than String#equals() + return player; + players.add(player); + } + } + if (players.size() == 1) + return players.get(0); + if (players.size() == 0) + Skript.error(String.format(Language.get("commands.no player starts with"), string)); else - Skript.error(String.format(Language.get("commands.multiple players start with"), s)); + Skript.error(String.format(Language.get("commands.multiple players start with"), string)); return null; } assert false; @@ -721,18 +736,20 @@ public String getDebugMessage(final Player p) { Classes.registerClass(new ClassInfo<>(OfflinePlayer.class, "offlineplayer") .user("offline ?players?") .name("Offline Player") - .description("A player that is possibly offline. See <a href='#player'>player</a> for more information. " + + .description( + "A player that is possibly offline. See <a href='#player'>player</a> for more information. " + "Please note that while all effects and conditions that require a player can be used with an " + - "offline player as well, they will not work if the player is not actually online.") - .usage("") - .examples("") - .since("") + "offline player as well, they will not work if the player is not actually online." + ).usage( + "Parsing an offline player as a player (online) will return nothing (none), for that case you would need to parse as " + + "offlineplayer which only returns nothing (none) if player doesn't exist in Minecraft databases (name not taken) otherwise it will return the player regardless of their online status." + ).examples("set {_p} to \"Notch\" parsed as an offlineplayer # returns Notch even if they're offline") + .since("2.0 beta 8") .defaultExpression(new EventValueExpression<>(OfflinePlayer.class)) .after("string", "world") .parser(new Parser<OfflinePlayer>() { @Override @Nullable - @SuppressWarnings("deprecation") public OfflinePlayer parse(final String s, final ParseContext context) { if (context == ParseContext.COMMAND || context == ParseContext.PARSE) { if (UUID_PATTERN.matcher(s).matches()) From 4291e5b6d0f0519f84c0722302ae45e61bea14d1 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Sun, 31 Dec 2023 18:24:52 -0700 Subject: [PATCH 556/619] Entity transform events (#5800) * Entity transform events * Add missing enum * Apply changes * Apply plurality to the transform reason syntax * Cleanup EvtEntityTransform toString * Update src/main/java/ch/njol/skript/events/EvtEntityTransform.java Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> * Update src/main/java/ch/njol/skript/events/EvtEntityTransform.java Co-authored-by: Shane Bee <shanebolenback@me.com> * Update src/main/resources/lang/default.lang Co-authored-by: Shane Bee <shanebolenback@me.com> * Apply changes * Organize imports * Update src/main/java/ch/njol/skript/events/EvtEntityTransform.java Co-authored-by: Shane Bee <shanebolenback@me.com> * Update src/main/resources/lang/default.lang Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --------- Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Co-authored-by: Shane Bee <shanebolenback@me.com> Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- .../skript/classes/data/BukkitClasses.java | 7 ++ .../classes/data/BukkitEventValues.java | 17 ++++ .../skript/events/EvtEntityTransform.java | 79 +++++++++++++++++++ .../expressions/ExprTransformReason.java | 55 +++++++++++++ src/main/resources/lang/default.lang | 14 ++++ 5 files changed, 172 insertions(+) create mode 100644 src/main/java/ch/njol/skript/events/EvtEntityTransform.java create mode 100644 src/main/java/ch/njol/skript/expressions/ExprTransformReason.java 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 8e943ce4c7b..1588be715ea 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -60,6 +60,7 @@ import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; import org.bukkit.event.entity.EntityDamageEvent.DamageCause; import org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason; +import org.bukkit.event.entity.EntityTransformEvent.TransformReason; import org.bukkit.event.inventory.ClickType; import org.bukkit.event.inventory.InventoryAction; import org.bukkit.event.inventory.InventoryCloseEvent; @@ -1541,6 +1542,12 @@ public String toVariableNameString(EnchantmentOffer eo) { .description("The inventory close reason in an <a href='/events.html#inventory_close'>inventory close event</a>.") .requiredPlugins("Paper") .since("INSERT VERSION")); + + Classes.registerClass(new EnumClassInfo<>(TransformReason.class, "transformreason", "transform reasons") + .user("(entity)? ?transform ?(reason|cause)s?") + .name("Transform Reason") + .description("Represents a transform reason of an <a href='events.html#entity transform'>entity transform event</a>.") + .since("INSERT VERSION")); } } 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 1543a5c2d1d..1998e124aa4 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -98,6 +98,8 @@ import org.bukkit.event.entity.EntityPickupItemEvent; import org.bukkit.event.entity.EntityResurrectEvent; import org.bukkit.event.entity.EntityTameEvent; +import org.bukkit.event.entity.EntityTransformEvent; +import org.bukkit.event.entity.EntityTransformEvent.TransformReason; import org.bukkit.event.entity.FireworkExplodeEvent; import org.bukkit.event.entity.HorseJumpEvent; import org.bukkit.event.entity.ItemDespawnEvent; @@ -1780,6 +1782,21 @@ public ItemStack get(PlayerStonecutterRecipeSelectEvent event) { } }, EventValues.TIME_NOW); + // EntityTransformEvent + EventValues.registerEventValue(EntityTransformEvent.class, Entity[].class, new Getter<Entity[], EntityTransformEvent>() { + @Override + @Nullable + public Entity[] get(EntityTransformEvent event) { + return event.getTransformedEntities().stream().toArray(Entity[]::new); + } + }, EventValues.TIME_NOW); + EventValues.registerEventValue(EntityTransformEvent.class, TransformReason.class, new Getter<TransformReason, EntityTransformEvent>() { + @Override + @Nullable + public TransformReason get(EntityTransformEvent event) { + return event.getTransformReason(); + } + }, EventValues.TIME_NOW); } } diff --git a/src/main/java/ch/njol/skript/events/EvtEntityTransform.java b/src/main/java/ch/njol/skript/events/EvtEntityTransform.java new file mode 100644 index 00000000000..3d3a8a26325 --- /dev/null +++ b/src/main/java/ch/njol/skript/events/EvtEntityTransform.java @@ -0,0 +1,79 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.events; + +import org.bukkit.event.Event; +import org.bukkit.event.entity.EntityTransformEvent; +import org.bukkit.event.entity.EntityTransformEvent.TransformReason; +import org.eclipse.jdt.annotation.Nullable; + +import ch.njol.skript.Skript; +import ch.njol.skript.entity.EntityData; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptEvent; +import ch.njol.skript.lang.SkriptParser.ParseResult; + +public class EvtEntityTransform extends SkriptEvent { + + static { + Skript.registerEvent("Entity Transform", EvtEntityTransform.class, EntityTransformEvent.class, "(entit(y|ies)|%*-entitydatas%) transform[ing] [due to %-transformreasons%]") + .description("Called when an entity is about to be replaced by another entity.", + "Examples when it's called include; when a zombie gets cured and a villager spawns, " + + "an entity drowns in water like a zombie that turns to a drown, " + + "an entity that gets frozen in powder snow, " + + "a mooshroom that when sheared, spawns a new cow.") + .examples("on a zombie transforming due to curing:", "on mooshroom transforming:", "on zombie, skeleton or slime transform:") + .keywords("entity transform") + .since("INSERT VERSION"); + } + + @Nullable + private Literal<TransformReason> reasons; + + @Nullable + private Literal<EntityData<?>> datas; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult) { + datas = (Literal<EntityData<?>>) args[0]; + reasons = (Literal<TransformReason>) args[1]; + return true; + } + + @Override + public boolean check(Event event) { + if (!(event instanceof EntityTransformEvent)) + return false; + EntityTransformEvent transformEvent = (EntityTransformEvent) event; + if (reasons != null && !reasons.check(event, reason -> transformEvent.getTransformReason().equals(reason))) + return false; + if (datas != null && !datas.check(event, data -> data.isInstance(transformEvent.getEntity()))) + return false; + return true; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + if (datas == null) + return "entities transforming" + (reasons == null ? "" : " due to " + reasons.toString(event, debug)); + return datas.toString(event, debug) + " transforming" + (reasons == null ? "" : " due to " + reasons.toString(event, debug)); + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprTransformReason.java b/src/main/java/ch/njol/skript/expressions/ExprTransformReason.java new file mode 100644 index 00000000000..09d36074b93 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprTransformReason.java @@ -0,0 +1,55 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import org.bukkit.event.Event; +import org.bukkit.event.entity.EntityTransformEvent.TransformReason; +import org.eclipse.jdt.annotation.Nullable; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.EventValueExpression; +import ch.njol.skript.lang.ExpressionType; + +@Name("Transform Reason") +@Description("The <a href='classes.html#transformreason'>transform reason</a> within an entity <a href='events.html#entity transform'>entity transform</a> event.") +@Examples({ + "on entity transform:", + "\ttransform reason is infection, drowned or frozen" +}) +@Since("INSERT VERSION") +public class ExprTransformReason extends EventValueExpression<TransformReason> { + + static { + Skript.registerExpression(ExprTransformReason.class, TransformReason.class, ExpressionType.SIMPLE, "[the] transform[ing] (cause|reason|type)"); + } + + public ExprTransformReason() { + super(TransformReason.class); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "transform reason"; + } + +} diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index e1a2dccc4ac..07f1a1dc45b 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -1960,6 +1960,19 @@ quit reasons: kicked: kicked timed_out: timed out +# -- Transform Reasons -- +transform reasons: + cured: zombie cure, zombie curing, curing + drowned: entity drowning, drowning, zombie converting to drowned, zombie drowning + frozen: entity freezing, entity freeze, skeleton freezing, skeleton freeze, skeleton converting to stray + lightning: lightning, creeper super charge + metamorphosis: tadpole converting, tadpole converting to frog, tadpole metamorphosis, metamorphosis + piglin_zombified: piglin zombification + sheared: mooshroom shear, mooshroom shearing + split: slime split, slime splitting, split, magma slime split, magma slime splitting + unknown: unknown + infection: infection, villager infection + # -- Boolean -- boolean: true: @@ -2030,6 +2043,7 @@ types: gamerulevalue: gamerule value¦s @a quitreason: quit reason¦s @a inventoryclosereason: inventory close reason¦s @an + transformreason: transform reason¦s @a # Skript weathertype: weather type¦s @a From 7b669c97eec25a16562717ac718a4d329f6b6d5e Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Sun, 31 Dec 2023 20:28:55 -0500 Subject: [PATCH 557/619] Cleanup EnumUtils (#5744) * Cleanup and remove excessive validation This commit removes some validation performed by the class. Notably, validation will only be performed when the language updates, meaning this class no longer supports runtime Enum changes. * Use existing variable for obtaining array length Co-authored-by: Fusezion <fusezionstream@gmail.com> --------- Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Co-authored-by: Fusezion <fusezionstream@gmail.com> Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- .../java/ch/njol/skript/util/EnumUtils.java | 113 +++++++++--------- 1 file changed, 58 insertions(+), 55 deletions(-) diff --git a/src/main/java/ch/njol/skript/util/EnumUtils.java b/src/main/java/ch/njol/skript/util/EnumUtils.java index 41e86ad7b66..31c64e1b23b 100644 --- a/src/main/java/ch/njol/skript/util/EnumUtils.java +++ b/src/main/java/ch/njol/skript/util/EnumUtils.java @@ -28,90 +28,93 @@ import java.util.HashMap; import java.util.Locale; +/** + * A language utility class to be used for easily handling language values representing an Enum. + * @param <E> Generic representing the Enum. + * @see ch.njol.skript.classes.EnumClassInfo + */ public final class EnumUtils<E extends Enum<E>> { - private final Class<E> c; + private final Class<E> enumClass; private final String languageNode; + @SuppressWarnings("NotNullFieldNotInitialized") // initialized in constructor's refresh() call private String[] names; private final HashMap<String, E> parseMap = new HashMap<>(); - public EnumUtils(Class<E> c, String languageNode) { - assert c != null && c.isEnum() : c; - assert languageNode != null && !languageNode.isEmpty() && !languageNode.endsWith(".") : languageNode; + public EnumUtils(Class<E> enumClass, String languageNode) { + assert enumClass.isEnum() : enumClass; + assert !languageNode.isEmpty() && !languageNode.endsWith(".") : languageNode; - this.c = c; + this.enumClass = enumClass; this.languageNode = languageNode; - names = new String[c.getEnumConstants().length]; + refresh(); - Language.addListener(() -> validate(true)); + Language.addListener(this::refresh); } /** - * Updates the names if the language has changed or the enum was modified (using reflection). + * Refreshes the representation of this Enum based on the currently stored language entries. */ - void validate(boolean force) { - boolean update = force; - - E[] constants = c.getEnumConstants(); + void refresh() { + E[] constants = enumClass.getEnumConstants(); + names = new String[constants.length]; + parseMap.clear(); + for (E constant : constants) { + String key = languageNode + "." + constant.name(); + int ordinal = constant.ordinal(); - if (constants.length != names.length) { // Simple check - names = new String[constants.length]; - update = true; - } else { // Deeper check - for (E constant : constants) { - if (!parseMap.containsValue(constant)) { // A new value was added to the enum - update = true; - break; + String[] options = Language.getList(key); + for (String option : options) { + option = option.toLowerCase(Locale.ENGLISH); + if (options.length == 1 && option.equals(key.toLowerCase(Locale.ENGLISH))) { + Skript.debug("Missing lang enum constant for '" + key + "'"); + continue; } - } - } - if (update) { - parseMap.clear(); - for (E e : constants) { - String key = languageNode + "." + e.name(); - int ordinal = e.ordinal(); + // Isolate the gender if one is present + NonNullPair<String, Integer> strippedOption = Noun.stripGender(option, key); + String first = strippedOption.getFirst(); + Integer second = strippedOption.getSecond(); - String[] values = Language.getList(key); - for (String option : values) { - option = option.toLowerCase(Locale.ENGLISH); - if (values.length == 1 && option.equals(key.toLowerCase(Locale.ENGLISH))) { - Skript.warning("Missing lang enum constant for '" + key + "'"); - continue; - } - - NonNullPair<String, Integer> strippedOption = Noun.stripGender(option, key); - String first = strippedOption.getFirst(); - Integer second = strippedOption.getSecond(); - - if (names[ordinal] == null) { // Add to name array if needed - names[ordinal] = first; - } + if (names[ordinal] == null) { // Add to name array if needed + names[ordinal] = first; + } - parseMap.put(first, e); - if (second != -1) { // There is a gender present - parseMap.put(Noun.getArticleWithSpace(second, Language.F_INDEFINITE_ARTICLE) + first, e); - } + parseMap.put(first, constant); + if (second != -1) { // There is a gender present + parseMap.put(Noun.getArticleWithSpace(second, Language.F_INDEFINITE_ARTICLE) + first, constant); } } } } - + + /** + * This method attempts to match the string input against one of the string representations of the enumerators. + * @param input a string to attempt to match against one the enumerators. + * @return The enumerator matching the input, or null if no match could be made. + */ @Nullable - public E parse(String s) { - validate(false); - return parseMap.get(s.toLowerCase(Locale.ENGLISH)); + public E parse(String input) { + return parseMap.get(input.toLowerCase(Locale.ENGLISH)); } - public String toString(E e, int flags) { - validate(false); - return names[e.ordinal()]; + /** + * This method returns the string representation of an enumerator. + * @param enumerator The enumerator to represent as a string. + * @param flags not currently used + * @return A string representation of the enumerator. + */ + public String toString(E enumerator, int flags) { + return names[enumerator.ordinal()]; } - + + /** + * @return A comma-separated string containing a list of all names representing the enumerators. + * Note that some entries may represent the same enumerator. + */ public String getAllNames() { - validate(false); return StringUtils.join(parseMap.keySet(), ", "); } From 7d2a5b5779a559d1e90f3fe9dfdec8430198d436 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Sun, 31 Dec 2023 20:38:51 -0500 Subject: [PATCH 558/619] Fix ExprDifference issues (#5406) * Rework ExprDifference init * Improve resolution of math when both types are known We will now attempt to convert into the type that has a math. If the first type has a math but the second type can't be converted, we will then try the second type's math * Avoid modifying syntax fields at runtime * Add a test --------- Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- .../skript/expressions/ExprDifference.java | 224 +++++++++++------- .../syntaxes/expressions/ExprDifference.sk | 19 ++ 2 files changed, 156 insertions(+), 87 deletions(-) create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprDifference.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprDifference.java b/src/main/java/ch/njol/skript/expressions/ExprDifference.java index 944fe5c22ee..cfaf10f07df 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprDifference.java +++ b/src/main/java/ch/njol/skript/expressions/ExprDifference.java @@ -18,11 +18,6 @@ */ package ch.njol.skript.expressions; -import java.lang.reflect.Array; - -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.classes.Arithmetic; import ch.njol.skript.classes.ClassInfo; @@ -33,123 +28,178 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; -import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.lang.Variable; import ch.njol.skript.lang.util.SimpleExpression; -import ch.njol.skript.log.ErrorQuality; import ch.njol.skript.registrations.Classes; -import ch.njol.skript.registrations.DefaultClasses; +import ch.njol.skript.util.LiteralUtils; import ch.njol.skript.util.Utils; import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.lang.reflect.Array; -/** - * @author Peter Güttinger - */ @Name("Difference") -@Description("The difference between two values, e.g. <a href='./classes.html#number'>numbers</a>, <a href='./classes/#date'>dates</a> or <a href='./classes/#time'>times</a>.") -@Examples({"if difference between {command::%player%::lastuse} and now is smaller than a minute:", - "\tmessage \"You have to wait a minute before using this command again!\""}) +@Description({ + "The difference between two values", + "Supported types include <a href='./classes.html#number'>numbers</a>, <a href='./classes/#date'>dates</a> and <a href='./classes/#time'>times</a>." +}) +@Examples({ + "if difference between {command::%player%::lastuse} and now is smaller than a minute:", + "\tmessage \"You have to wait a minute before using this command again!\"" +}) @Since("1.4") public class ExprDifference extends SimpleExpression<Object> { - + static { - Skript.registerExpression(ExprDifference.class, Object.class, ExpressionType.COMBINED, "difference (between|of) %object% and %object%"); + Skript.registerExpression(ExprDifference.class, Object.class, ExpressionType.COMBINED, + "difference (between|of) %object% and %object%" + ); } - - @SuppressWarnings("null") + + @SuppressWarnings("NotNullFieldNotInitialized") private Expression<?> first, second; - - @SuppressWarnings("rawtypes") + @Nullable + @SuppressWarnings("rawtypes") private Arithmetic math; - @SuppressWarnings("null") + @SuppressWarnings("NotNullFieldNotInitialized") private Class<?> relativeType; - - @SuppressWarnings({"unchecked", "null", "unused"}) + @Override - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { - first = exprs[0]; - second = exprs[1]; - final ClassInfo<?> ci; - if (first instanceof Variable && second instanceof Variable) { - ci = DefaultClasses.OBJECT; - } else if (first instanceof Literal<?> && second instanceof Literal<?>) { - first = first.getConvertedExpression(Object.class); - second = second.getConvertedExpression(Object.class); - if (first == null || second == null) - return false; - ci = Classes.getSuperClassInfo(Utils.getSuperType(first.getReturnType(), second.getReturnType())); - } else { - if (first instanceof Literal<?>) { - first = first.getConvertedExpression(second.getReturnType()); - if (first == null) - return false; - } else if (second instanceof Literal<?>) { - second = second.getConvertedExpression(first.getReturnType()); - if (second == null) - return false; - } - if (first instanceof Variable) { - first = first.getConvertedExpression(second.getReturnType()); - } else if (second instanceof Variable) { - second = second.getConvertedExpression(first.getReturnType()); - } - assert first != null && second != null; - ci = Classes.getSuperClassInfo(Utils.getSuperType(first.getReturnType(), second.getReturnType())); - } - assert ci != null; - if (!ci.getC().equals(Object.class) && ci.getMath() == null) { - Skript.error("Can't get the difference of " + CondCompare.f(first) + " and " + CondCompare.f(second), ErrorQuality.SEMANTIC_ERROR); + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + Expression<?> first = LiteralUtils.defendExpression(exprs[0]); + Expression<?> second = LiteralUtils.defendExpression(exprs[1]); + if (!LiteralUtils.canInitSafely(first, second)) { return false; } - if (ci.getC().equals(Object.class)) { - // Initialize less stuff, basically - relativeType = Object.class; // Relative math type would be null which the parser doesn't like + + Class<?> firstReturnType = first.getReturnType(); + Class<?> secondReturnType = second.getReturnType(); + ClassInfo<?> classInfo = Classes.getSuperClassInfo(Utils.getSuperType(firstReturnType, secondReturnType)); + + boolean fail = false; + + if (classInfo.getC() == Object.class && (firstReturnType != Object.class || secondReturnType != Object.class)) { + // We may not have a way to obtain the difference between these two values. Further checks needed. + + // These two types are unrelated, meaning conversion is needed + if (firstReturnType != Object.class && secondReturnType != Object.class) { + + // We will work our way out of failure + fail = true; + + // Attempt to use first type's math + classInfo = Classes.getSuperClassInfo(firstReturnType); + if (classInfo.getMath() != null) { // Try to convert second to first + Expression<?> secondConverted = second.getConvertedExpression(firstReturnType); + if (secondConverted != null) { + second = secondConverted; + fail = false; + } + } + + if (fail) { // First type won't work, try second type + classInfo = Classes.getSuperClassInfo(secondReturnType); + if (classInfo.getMath() != null) { // Try to convert first to second + Expression<?> firstConverted = first.getConvertedExpression(secondReturnType); + if (firstConverted != null) { + first = firstConverted; + fail = false; + } + } + } + + } else { // It may just be the case that the type of one of our values cannot be known at parse time + Expression<?> converted; + if (firstReturnType == Object.class) { + converted = first.getConvertedExpression(secondReturnType); + if (converted != null) { // This may fail if both types are Object + first = converted; + } + } else { // This is an else statement to avoid X->Object conversions + converted = second.getConvertedExpression(firstReturnType); + if (converted != null) { + second = converted; + } + } + + if (converted == null) { // It's unlikely that these two can be compared + fail = true; + } else { // Attempt to resolve a better class info + classInfo = Classes.getSuperClassInfo(Utils.getSuperType(first.getReturnType(), second.getReturnType())); + } + } + + } + + if (classInfo.getC() == Object.class) { // We will have to determine the type during runtime + relativeType = Object.class; + } else if (classInfo.getMath() == null || classInfo.getMathRelativeType() == null) { + fail = true; } else { - math = ci.getMath(); - relativeType = ci.getMathRelativeType(); + math = classInfo.getMath(); + relativeType = classInfo.getMathRelativeType(); + } + + if (fail) { + Skript.error("Can't get the difference of " + CondCompare.f(first) + " and " + CondCompare.f(second)); + return false; } + + this.first = first; + this.second = second; + return true; } - - @SuppressWarnings("unchecked") + @Override @Nullable - protected Object[] get(final Event e) { - final Object f = first.getSingle(e), s = second.getSingle(e); - if (f == null || s == null) - return null; - final Object[] one = (Object[]) Array.newInstance(relativeType, 1); - - // If we're comparing object expressions, such as variables, math is null right now - if (relativeType.equals(Object.class)) { - ClassInfo<?> info = Classes.getSuperClassInfo(Utils.getSuperType(f.getClass(), s.getClass())); + @SuppressWarnings({"unchecked", "rawtypes"}) + protected Object[] get(Event event) { + Object first = this.first.getSingle(event); + Object second = this.second.getSingle(event); + if (first == null || second == null) { + return new Object[0]; + } + + Arithmetic math = this.math; + Class<?> relativeType = this.relativeType; + + if (relativeType == Object.class) { // Try to determine now that actual types are known + ClassInfo<?> info = Classes.getSuperClassInfo(Utils.getSuperType(first.getClass(), second.getClass())); math = info.getMath(); if (math == null) { // User did something stupid, just return <none> for them - return one; + return new Object[0]; + } + relativeType = info.getMathRelativeType(); + if (relativeType == null) { // Unlikely to be the case, but math is not null meaning we can calculate the difference + relativeType = Object.class; } } - - assert math != null; // NOW it cannot be null - one[0] = math.difference(f, s); + + Object[] one = (Object[]) Array.newInstance(relativeType, 1); + + assert math != null; // it cannot be null here + one[0] = math.difference(first, second); return one; } - + @Override - public Class<? extends Object> getReturnType() { - return relativeType; + public boolean isSingle() { + return true; } - + @Override - public String toString(final @Nullable Event e, final boolean debug) { - return "difference between " + first.toString(e, debug) + " and " + second.toString(e, debug); + public Class<?> getReturnType() { + return relativeType; } - + @Override - public boolean isSingle() { - return true; + public String toString(@Nullable Event event, boolean debug) { + return "difference between " + first.toString(event, debug) + " and " + second.toString(event, debug); } - + } diff --git a/src/test/skript/tests/syntaxes/expressions/ExprDifference.sk b/src/test/skript/tests/syntaxes/expressions/ExprDifference.sk new file mode 100644 index 00000000000..6492ead2da4 --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprDifference.sk @@ -0,0 +1,19 @@ +test "ExprDifference": + + # basic tests + + set {_val} to difference between 1 and 2 + assert {_val} is 1 with "(1) expected '1', got '%{_val}%'" + delete {_val} + + set {_val} to difference between 2 and 1 + assert {_val} is 1 with "(2) expected '1', got '%{_val}%'" + delete {_val} + + # ensure proper parsing order + set {_a} to "5" + set {_b} to "10" + set {_val} to difference between "%{_a}%" parsed as a number and "%{_b}%" parsed as a number + set {_exp} to difference between ("%{_a}%" parsed as a number) and ("%{_b}%" parsed as a number) + assert {_exp} is 5 with "(5) expected '5', but got '%{_exp}%'" + assert {_val} is 5 with "(6) expected '5', but got '%{_val}%'" From 5bc25d6742c4e73ba5055ab6f218225c80219cc5 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Sun, 31 Dec 2023 18:20:45 -0800 Subject: [PATCH 559/619] Add warnings for using %player% without using the UUID config setting. (#6271) * Update VariableString.java * messed up imports * Catch offline players --- .../ch/njol/skript/lang/VariableString.java | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/main/java/ch/njol/skript/lang/VariableString.java b/src/main/java/ch/njol/skript/lang/VariableString.java index f9ae8f4ab2f..ccfc0c14c5d 100644 --- a/src/main/java/ch/njol/skript/lang/VariableString.java +++ b/src/main/java/ch/njol/skript/lang/VariableString.java @@ -18,21 +18,8 @@ */ package ch.njol.skript.lang; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import java.util.stream.Collectors; - -import org.bukkit.ChatColor; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; -import org.jetbrains.annotations.NotNull; -import org.skriptlang.skript.lang.script.Script; - -import com.google.common.collect.Lists; - import ch.njol.skript.Skript; +import ch.njol.skript.SkriptConfig; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.expressions.ExprColoured; @@ -54,6 +41,19 @@ import ch.njol.util.StringUtils; import ch.njol.util.coll.CollectionUtils; import ch.njol.util.coll.iterator.SingleItemIterator; +import com.google.common.collect.Lists; +import org.bukkit.ChatColor; +import org.bukkit.OfflinePlayer; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.NotNull; +import org.skriptlang.skript.lang.script.Script; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; /** * Represents a string that may contain expressions, and is thus "variable". @@ -229,6 +229,12 @@ public static VariableString newInstance(String orig, StringMode mode) { log.printErrors("Can't understand this expression: " + s.substring(c + 1, c2)); return null; } else { + if (!SkriptConfig.usePlayerUUIDsInVariableNames.value() && OfflinePlayer.class.isAssignableFrom(expr.getReturnType())) { + Skript.warning( + "In the future, players in variable names will use the player's UUID instead of their name. " + + "For information on how to make sure your scripts won't be impacted by this change, see https://github.com/SkriptLang/Skript/discussions/6270." + ); + } string.add(expr); } log.printLog(); From b6b63e64365026d0e78db323ca3f49b2cfb4cf5f Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Sun, 31 Dec 2023 18:27:24 -0800 Subject: [PATCH 560/619] Add ExprSentCommands and EvtPlayerCommandSend (#5948) * ExprSentCommands and EvtPlayerCommandSend * add links * Requested Changes and Prevent Delayed Changes * Apply suggestions from code review Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> * Suggestions * Requested Changes * silly mistake * Requested Changes * Apply suggestions from code review Co-authored-by: Patrick Miller <apickledwalrus@gmail.com> * Update ExprSentCommands.java --------- Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Co-authored-by: Patrick Miller <apickledwalrus@gmail.com> --- .../skript/events/EvtPlayerCommandSend.java | 75 +++++++++ .../skript/expressions/ExprSentCommands.java | 155 ++++++++++++++++++ 2 files changed, 230 insertions(+) create mode 100644 src/main/java/ch/njol/skript/events/EvtPlayerCommandSend.java create mode 100644 src/main/java/ch/njol/skript/expressions/ExprSentCommands.java diff --git a/src/main/java/ch/njol/skript/events/EvtPlayerCommandSend.java b/src/main/java/ch/njol/skript/events/EvtPlayerCommandSend.java new file mode 100644 index 00000000000..9ef380ada7a --- /dev/null +++ b/src/main/java/ch/njol/skript/events/EvtPlayerCommandSend.java @@ -0,0 +1,75 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.events; + +import ch.njol.skript.Skript; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptEvent; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableList; +import org.bukkit.event.Event; +import org.bukkit.event.player.PlayerCommandSendEvent; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.ArrayList; +import java.util.Collection; + +public class EvtPlayerCommandSend extends SkriptEvent { + + static { + Skript.registerEvent("Send Command List", EvtPlayerCommandSend.class, PlayerCommandSendEvent.class, "send[ing] [of [the]] [server] command[s] list", "[server] command list send") + .description( + "Called when the server sends a list of commands to the player. This usually happens on join. The sent commands " + + "can be modified via the <a href='expressions.html#ExprSentCommands'>sent commands expression</a>.", + "Modifications will affect what commands show up for the player to tab complete. They will not affect what commands the player can actually run.", + "Adding new commands to the list is illegal behavior and will be ignored." + ) + .examples( + "on send command list:", + "\tset command list to command list where [input does not contain \":\"]", + "\tremove \"help\" from command list" + ) + .since("INSERT VERSION"); + } + + private final Collection<String> originalCommands = new ArrayList<>(); + + @Override + public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult) { + return true; + } + + @Override + public boolean check(Event event) { + originalCommands.clear(); + originalCommands.addAll(((PlayerCommandSendEvent) event).getCommands()); + return true; + } + + public ImmutableCollection<String> getOriginalCommands() { + return ImmutableList.copyOf(originalCommands); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "sending of the server command list"; + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprSentCommands.java b/src/main/java/ch/njol/skript/expressions/ExprSentCommands.java new file mode 100644 index 00000000000..5c4f17ab16e --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprSentCommands.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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Events; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.events.EvtPlayerCommandSend; +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 com.google.common.collect.Lists; +import org.bukkit.event.Event; +import org.bukkit.event.player.PlayerCommandSendEvent; +import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.structure.Structure; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +@Name("Sent Command List") +@Description({ + "The commands that will be sent to the player in a <a href='events.html#send_command_list'>send commands to player event</a>.", + "Modifications will affect what commands show up for the player to tab complete. They will not affect what commands the player can actually run.", + "Adding new commands to the list is illegal behavior and will be ignored." +}) +@Examples({ + "on send command list:", + "\tset command list to command list where [input does not contain \":\"]", + "\tremove \"help\" from command list" +}) +@Since("INSERT VERSION") +@Events("send command list") +public class ExprSentCommands extends SimpleExpression<String> { + + static { + Skript.registerExpression(ExprSentCommands.class, String.class, ExpressionType.SIMPLE, "[the] [sent] [server] command[s] list"); + } + + private EvtPlayerCommandSend parent; + + @Override + public boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + Structure structure = getParser().getCurrentStructure(); + if (!(structure instanceof EvtPlayerCommandSend)) { + Skript.error("The 'command list' expression can only be used in a 'send command list' event"); + return false; + } + + if (!isDelayed.isFalse()) { + Skript.error("Can't change the command list after the event has already passed"); + return false; + } + parent = (EvtPlayerCommandSend) structure; + return true; + } + + @Override + @Nullable + protected String[] get(Event event) { + if (!(event instanceof PlayerCommandSendEvent)) + return null; + return ((PlayerCommandSendEvent) event).getCommands().toArray(new String[0]); + } + + @Override + @Nullable + public Class<?>[] acceptChange(ChangeMode mode) { + switch (mode) { + case REMOVE: + case DELETE: + case SET: + case RESET: + return new Class[]{String[].class}; + // note that ADD is not supported, as adding new commands to the commands collection is illegal behaviour + default: + return null; + } + } + + @Override + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + if (!(event instanceof PlayerCommandSendEvent)) + return; + + Collection<String> commands = ((PlayerCommandSendEvent) event).getCommands(); + + // short circuit if we're just clearing the list + if (mode == ChangeMode.DELETE) { + commands.clear(); + return; + } + + List<String> deltaCommands = (delta != null && delta.length > 0) ? Lists.newArrayList((String[]) delta) : new ArrayList<>(); + switch (mode) { + case REMOVE: + commands.removeAll(deltaCommands); + break; + case SET: + // remove all completely new commands, as adding new commands to the commands collection is illegal behaviour + List<String> newCommands = new ArrayList<>(deltaCommands); + newCommands.removeAll(parent.getOriginalCommands()); + deltaCommands.removeAll(newCommands); + commands.clear(); + commands.addAll(deltaCommands); + break; + case RESET: + commands.clear(); + commands.addAll(parent.getOriginalCommands()); + break; + default: + assert false; + break; + } + } + + @Override + public boolean isSingle() { + return false; + } + + @Override + public Class<? extends String> getReturnType() { + return String.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the sent server command list"; + } + +} From a9e9a78b430ee168527ea7da12e858c6877d86ac Mon Sep 17 00:00:00 2001 From: Fusezion <fusezionstream@gmail.com> Date: Sun, 31 Dec 2023 21:35:14 -0500 Subject: [PATCH 561/619] Repeat Expression (#5098) * Adds new repeat() function which utilizes java's built in <string>.repeat(<int>) * Invalid negative error * param name changes * Removed Java11 repeat * Convert to multiply() for consistency * Update branch Removed function repeat Added repeat expression * Add missing annotations * Mild change * Addressed requested changes * Added support for multiple strings * Add missing ending whitespace Co-authored-by: Patrick Miller <apickledwalrus@gmail.com> * Requested changed Co-Authored-By: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> * Requested change Forgot about this one Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> * Fix nullable count Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> * Mild clean up Optional s is now required for `times` Added test skript Co-Authored-By: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> * Array revert * Update ExprRepeat.java Co-Authored-By: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> * Revisiting after a couple months :sparkles: Updated test to be a little more informative :sparkles: Added an additional test for ensuring 0/null return nothing :sparkles: When inputting 0 or null for number we return null for strings :sparkles: Refactored some parts :sparkles: Changed `%number%` to `%integer%` zero reason not to anymore with new number converters * Apply suggestions from code review Woops thanks! Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> * Use a stream Personally I'm not for this entirely Co-authored-by: TheLimeGlass <seantgrover@gmail.com> * Use #collect instead of Java16 #toList * Use Expression#stream instead * Update test with an additional 14 or so For the love of god fix the list to list comparisons * Oops didn't see this * Remove duplicate var * Additional example showcasing comparison --------- Co-authored-by: Patrick Miller <apickledwalrus@gmail.com> Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Co-authored-by: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- .../njol/skript/expressions/ExprRepeat.java | 83 +++++++++++++++++++ .../tests/syntaxes/expressions/ExprRepeat.sk | 72 ++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprRepeat.java create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprRepeat.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprRepeat.java b/src/main/java/ch/njol/skript/expressions/ExprRepeat.java new file mode 100644 index 00000000000..831c448ead0 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprRepeat.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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.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.StringUtils; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Repeat String") +@Description("Repeats inputted strings a given amount of times.") +@Examples({ + "broadcast nl and nl repeated 200 times", + "broadcast \"Hello World \" repeated 5 times", + "if \"aa\" repeated 2 times is \"aaaa\":", + "\tbroadcast \"Ahhhh\" repeated 100 times" +}) +@Since("INSERT VERSION") +public class ExprRepeat extends SimpleExpression<String> { + + static { + Skript.registerExpression(ExprRepeat.class, String.class, ExpressionType.COMBINED, "%strings% repeated %integer% time[s]"); + } + + private Expression<String> strings; + private Expression<Integer> repeatCount; + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + strings = (Expression<String>) exprs[0]; + repeatCount = (Expression<Integer>) exprs[1]; + return true; + } + + @Override + protected @Nullable String[] get(Event event) { + int repeatCount = this.repeatCount.getOptionalSingle(event).orElse(0); + if (repeatCount < 1) + return new String[0]; + return strings.stream(event).map(string -> StringUtils.multiply(string, repeatCount)).toArray(String[]::new); + } + + @Override + public boolean isSingle() { + return strings.isSingle(); + } + + @Override + public Class<? extends String> getReturnType() { + return String.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return strings.toString(event, debug) + " repeated " + repeatCount.toString(event, debug) + " times"; + } + +} diff --git a/src/test/skript/tests/syntaxes/expressions/ExprRepeat.sk b/src/test/skript/tests/syntaxes/expressions/ExprRepeat.sk new file mode 100644 index 00000000000..fc2cbac598b --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprRepeat.sk @@ -0,0 +1,72 @@ +test "repeat expression": + + # Literal Test + assert "a" repeated 1 times is "a" with "ExprRepeat - 'a' repeated once is not 'a'" + assert length of "a a" repeated 10 times is less than 30 to fail with "ExprRepeat - length of 'a a' repeated 10 times is not less than 30" + assert nl repeated 3 times is "%nl%%nl%%nl%" with "ExprRepeat - 'nl' repeated 3 times is not '%nl%%nl%%nl%'" + assert "string" repeated 0 times is "string" to fail with "ExprRepeat - 'string' repeated 0 times is 'string'" + assert "" repeated 2 times is "" with "ExprRepeat - '' repeated 2 times is not ''" + + # Variable Test + set {_string} to "a" + set {_repeat} to 1 + assert {_string} repeated {_repeat} times is "a" with "ExprRepeat Var - 'a' repeated once is not 'a'" + + set {_string} to "a a" + set {_repeat} to 10 + assert length of {_string} repeated {_repeat} times is less than 30 to fail with "ExprRepeat Var - length of 'a a' repeated 10 times is not less than 30" + + set {_string} to nl + set {_repeat} to 3 + assert {_string} repeated {_repeat} times is "%nl%%nl%%nl%" with "ExprRepeat Var - 'nl' repeated 3 times is not '%nl%%nl%%nl%'" + + set {_string} to "string" + set {_repeat} to 0 + assert {_string} repeated {_repeat} times is "string" to fail with "ExprRepeat Var - 'string' repeated 0 times is 'string'" + + set {_string} to "" + set {_repeat} to 2 + assert {_string} repeated {_repeat} times is "" with "ExprRepeat Var - '' repeated times is not ''" + + # Null Test + assert {_null} repeated 1 times is set to fail with "ExprRepeat Nulls - null repeated 1 time is set" + assert "1" repeated {_null} times is set to fail with "ExprRepeat Nulls - '1' repeated null times is set" + assert {_null} repeated {_null} times is set to fail with "ExprRepeat Nulls - null repeated null times is set" + + # Multi Test + set {_repeat} to 3 + + set {_strings::*} to "aa" and "b" repeated 3 times + if any: + {_strings::1} is not "aaaaaa" + {_strings::2} is not "bbb" + then: + assert false is true with "ExprRepeat Multi - 1) 'aa' and 'b' repeated 3 times is not 'aaaaaa' and 'bbb'" + + set {_strings::*} to "aa", "b" + set {_strings::*} to {_strings::*} repeated {_repeat} times + if any: + {_strings::1} is not "aaaaaa" + {_strings::2} is not "bbb" + then: + assert false is true with "ExprRepeat Multi - 2) 'aa' and 'b' repeated 3 times is not 'aaaaaa' and 'bbb'" + + set {_strings::*} to "aa", "b" + set {_strings::*} to {_strings::*} repeated 3 times + if any: + {_strings::1} is not "aaaaaa" + {_strings::2} is not "bbb" + then: + assert false is true with "ExprRepeat Multi - 3) 'aa' and 'b' repeated 3 times is not 'aaaaaa' and 'bbb'" + + set {_strings::*} to "aa", "b" repeated {_repeat} times + if any: + {_strings::1} is not "aaaaaa" + {_strings::2} is not "bbb" + then: + assert false is true with "ExprRepeat Multi - 4) 'aa' and 'b' repeated 3 times is not 'aaaaaa' and 'bbb'" + + + # More Usage Test + assert "&8 &1 &2 &3 &6" repeated 2 times is "&8 &1 &2 &3 &6&8 &1 &2 &3 &6" with "ExprRepeat Extra - '&8 &1 &2 &3 &6' is not properly repeated" + assert "aa" repeated -5 times is set to fail with "ExprRepeat Extra - 'aa' repeated -5 times was set" From a52236e32da385b752fb41b8d241afab50bce9f3 Mon Sep 17 00:00:00 2001 From: Eren <67760502+erenkarakal@users.noreply.github.com> Date: Mon, 1 Jan 2024 05:40:06 +0300 Subject: [PATCH 562/619] Turkish Translation (#6198) * Create turkish.lang * Update config.sk * Fix for grammar (foreign words must be split with a quote before suffixes). and improvements to make it more formal. - hamza-cskn --------- Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- src/main/resources/config.sk | 2 +- src/main/resources/lang/turkish.lang | 188 +++++++++++++++++++++++++++ 2 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/lang/turkish.lang diff --git a/src/main/resources/config.sk b/src/main/resources/config.sk index 765b7cb7159..81545ae169c 100644 --- a/src/main/resources/config.sk +++ b/src/main/resources/config.sk @@ -31,7 +31,7 @@ # ==== General Options ==== language: english -# Which language to use. Currently English, German, Korean, French, Polish, Japanese and Simplified Chinese +# Which language to use. Currently English, German, Korean, French, Polish, Japanese, Simplified Chinese and Turkish # are included in the download, but custom languages can be created as well. Use the name in lowercase and no spaces as the value. # Please note that not everything can be translated yet, i.e. parts of Skript will still be english if you use another language. # If you want to translate Skript to your language please read the readme.txt located in the /lang/ folder in the jar diff --git a/src/main/resources/lang/turkish.lang b/src/main/resources/lang/turkish.lang new file mode 100644 index 00000000000..4df33a52998 --- /dev/null +++ b/src/main/resources/lang/turkish.lang @@ -0,0 +1,188 @@ +# Which version of Skript this language file was written for +version: @version@ + +# What null (nothing) should show up as in a string/text +none: <tanımsız> + +# -- Skript -- +skript: + copyright: ~ © Peter Güttinger yani Njol tarafından yapıldı ~ + prefix: <gray>[<gold>Skript<gray>] <reset> + quotes error: Tırnak işaretlerinin (") geçersiz kullanımı. "Tırnak işareti içindeki metinde" tırnak işaretleri kullanmak istiyorsanız 2 tane kullanın: "". + brackets error: Geçersiz miktar veya parantez yerleşimi. Lütfen her açılan paranteze karşılık gelen bir kapatma parantezine sahip olduğundan emin olun. + invalid reload: Skript sadece Bukkit'in '/reload' veya Skript'in '/skript reload' komuduyla tekrar yüklenebilir. + no scripts: Hiçbir script bulunamadı, belki birkaç tane yazmalısın ;) + no errors: Tüm scriptler hatasız yüklendi. + scripts loaded: %s tane script toplam %s tane structure ile %s'de yüklendi. + finished loading: Yükleme tamamlandı. + +# -- Skript command -- +skript command: + usage: Kullanım: + help: + description: Skript'in ana komutu + help: Bu yardım mesajını gösterir. Daha fazla bilgi almak için '/skript reload/enable/disable/update' kullanın. + reload: + description: Belirli bir script'i, tüm script'leri veya her şeyi tekrar yükler. + all: Configi, tüm alias configlerini ve tüm script'leri tekrar yükler. + config: Yapılandırma dosyasını yeniden yükler. + aliases: Aliases yapılandırma dosyasını yeniden yükler. (aliases-english.zip veya eklenti dosyası) + scripts: Tüm script'leri yeniden yükler + <script>: Belirli bir script'i veya bir klasörün içindeki tüm script'leri yeniden yükler. + enable: + description: Devre dışı bırakılmış tüm script'leri veya belirli birini aktif eder. + all: Tüm script'leri aktif eder. + <script>: Belirli bir script'i veya bir klasörün içindeki tüm script'leri tekrar yükler. + disable: + description: Tüm script'leri veya belirli bir script'i devre dışı bırakır. + all: Tüm script'leri devre dışı bırakır. + <script>: Belirli bir script'i veya bir klasörün içindeki tüm script'leri devre dışı bırakır. + update: + description: Skript eklentisinin güncelliğini yönetmenizi sağlar. + check: Yeni bir sürüm olup olmadığını kontrol eder. + changes: Son sürümdeki tüm değişiklikleri listeler. + download: En yeni sürümü indirir. + info: Skript'in alias ve dökümantasyon bağlantılarını gösterir. + gen-docs: Eklenti klasöründeki şablonları kullanarak bir dokümantasyon oluşturur. + test: Skript'in dahili testleri için kullanılır. + + invalid script: <grey>'<gold>%s<grey>'<red> adlı script scripts klasöründe bulunamadı! + invalid folder: <grey>'<gold>%s<grey>'<red> adlı klasör scripts klasöründe bulunamadı! + reload: + warning line info: <gold><bold>Satır %s:<gray> (%s)<reset>\n + error line info: <light red><bold>Satır %s:<gray> (%s)<reset>\n + reloading: <gold>%s<reset> yeniden yükleniyor... + reloaded: <gold>%s<lime> başarıyla yeniden yüklendi. <gray>(<gold>%2$sms<gray>) + error: <gold>%1$s <light red>adlı script yüklenirken <gold>%2$s <light red>adet hatayla karşılaşıldı! <gray>(<gold>%3$sms<gray>) + script disabled: <gold>%s<reset> şu anda devre dışı. Aktif etmek için <gray>/<gold>skript <cyan>enable <red>%s<reset> komutunu kullanın. + warning details: <yellow> %s<reset>\n + error details: <light red> %s<reset>\n + other details: <white> %s<reset>\n + line details: <gold> Satır: <gray>%s<reset>\n <reset> + + config, aliases and scripts: config, alias'lar ve tüm script'ler + scripts: tüm scriptler + main config: ana yapılandırma dosyası + aliases: alias'lar + script: <gold>%s<reset> + scripts in folder: <gold>%s<reset> klasöründeki tüm script'ler + x scripts in folder: <gold>%1$s<reset> klasöründeki <gold>%2$s <lime>script + empty folder: <gold>%s<reset> aktif script içermiyor. + enable: + all: + enabling: Tüm script'ler aktif ediliyor... + enabled: Önceden devre dışı olan tüm script'ler başarıyla aktif edildi ve yüklendi. + error: Devre dışı script'ler yüklenirken %s adet hata ile karşılaşıldı! + io error: Bir veya daha fazla script yüklenemedi - bazı scriptler zaten yeniden adlandırılmış olabilir ve sunucu tekrar başlatılınca aktif edilecektir: %s + single: + already enabled: <gold>%s<reset> zaten aktif! Değişiklikleri uygulamak için <gray>/<gold>skript <cyan>reload <red>%s<reset> komutunu kullanın. + enabling: <gold>%s<reset> aktif ediliyor... + enabled: <gold>%s<reset> başarıyla aktif edildi ve yüklendi. + error: <gold>%1$s<red> yüklenirken %2$s adet hata ile karşılaşıldı! + io error: <gold>%s<red> aktif edilemedi: %s + folder: + empty: <gold>%s<reset> devre dışı script içermiyor. + enabling: <gold>%1$s<reset> klasöründeki <gold>%2$s <reset>script aktif ediliyor... + enabled: <gold>%1$s<reset> klasöründeki önceden devre dışı olan <gold>%2$s<reset> başarıyla yüklendi ve aktifleştirildi. + error: <gold>%1$s<red> klasöründeki script'ler yüklenirken %2$s tane hatayla karşılaşıldı! + io error: <gold>%s<red> klasöründeki bir veya daha fazla script aktif edilirken hatayla karşılaşıldı. (bazı script'ler sunucu tekrar başlatılınca aktifleşebilir): %s + disable: + all: + disabled: Tüm script'ler devre dışı bırakıldı. + io error: Bir veya daha fazla script yeniden adlandırılamadı - bazı scriptler zaten yeniden adlandırılmış olabilir ve sunucu tekrar başlatılınca devre dışı bırakılacaktır: %s + single: + already disabled: <gold>%s<reset> zaten devre dışı! + disabled: <gold>%s<reset> başarıyla devre dışı bırakıldı. + io error: <gold>%s<red> yeniden adlandırılamadı, sunucuyu tekrar başlattığında tekrar aktifleşecek: %s + folder: + empty: <gold>%s<reset> aktif script içermiyor. + disabled: <gold>%1$s<reset> klasöründeki <gold>%2$s<reset> script başarıyla devre dışı bırakıldı. + io error: <gold>%s<red> klasöründeki bir veya daha fazla script devre dışı bırakılamadı. (bazı script'ler sunucu tekrar başladığında devre dışı olabilir): %s + update: + # check/download: see Updater + changes: + # multiple versions: + # title: <gold>%s<r> update¦ has¦s have¦ been released since this version (<gold>%s<r>) of Skript: + # footer: To show the changelog of a version type <gold>/skript update changes <version><reset> + # invalid version: No changelog for the version <gold>%s<red> available + title: <bold><cyan>%s<reset> (%s) + next page: <grey>sayfa %s / %s. Bir sonraki sayfa için <gold>/skript update changes %s<gray> kullan. (ipucu: yukarı ok tuşunu kullan) + info: + aliases: Skript alias'ları: <aqua>https://github.com/SkriptLang/skript-aliases + documentation: Skript dökümantasyonları: <aqua>https://docs.skriptlang.org/ + tutorials: Skript dersleri: <aqua>https://docs.skriptlang.org/tutorials + version: Skript sürümleri: <aqua>%s + server: Sunucu sürümü: <aqua>%s + addons: Skript addon'ları: <aqua>%s + dependencies: Bağımlılıklar: <aqua>%s + +# -- Updater -- +updater: + not started: Skript henüz en son sürümü kontrol etmedi. Kontrol etmek için <gold>/skript update check<reset> komutunu kullanın. + checking: Skript'in en son sürümünü kontrol ediliyor... + check in progress: Yeni sürüm kontrolü devam ediyor. + updater disabled: Güncelleyici devre dışı olduğundan Skript'in en son sürümü kontrol edilmedi. + check error: <red>Skript'in en son sürümü kontrol edilirken bir hata oluştu:<light red> %s + running latest version: Şu anda Skript'in stabil olan en son sürümünü kullanıyorsunuz. + running latest version (beta): Şu anda Skript'in <i>beta<r> sürümünü kullanıyorsunuz ve yeni bir <i>stabil<r> sürümü mevcut değil. Daha yeni beta sürümlerine manuel olarak güncelleme yapmanız gerektiğini lütfen unutmayın! + update available: Skript'in yeni bir sürümü mevcut: <gold>%s<reset> (şu anda <gold>%s<reset> sürümündesiniz) + downloading: Skript <gold>%s<reset> indiriliyor... + download in progress: Skript'in en son sürümü şu anda indiriliyor. + download error: <red>Skript'in en son sürümünü indirirken bir hata oluştu:<light red> %s + downloaded: Skript'in son sürümü indirildi! Değişiklikleri uygulamak için sunucuyu yeniden başlatın veya /reload komutunu kullanın. + internal error: Skript'in en son sürümünü kontrol ederken dahili bir hata oluştu. Ayrıntılar için lütfen sunucu konsoluna bakın. + custom version: Şu anda özel bir Skript sürümünü kullanıyorsunuz. Hiçbir güncelleme otomatik olarak yüklenmeyecektir. + nightly build: Şu anda Skript'in geliştirme sürümünü kullanıyorsunuz. Hiçbir güncelleme otomatik olarak yüklenmeyecektir. + +# -- Commands -- +commands: + no permission message: Bu komutu kullanmak için gerekli yetkiye sahip değilsiniz. + cooldown message: Bu komutu çok sık kullanıyorsunuz, lütfen daha sonra tekrar deneyin. + executable by players: Bu komut yalnızca oyuncular tarafından kullanılabilir. + executable by console: Bu komut yalnızca konsol tarafından kullanılabilir. + correct usage: Doğru kullanım: + invalid argument: Yanlış argüman <gray>'%s<gray>'<reset>. İzin verilenler: + too many arguments: Bu komut yanlızca bir tane %2$s kabul eder. + internal error: Bu komut gerçekleştirilmeye çalışılırken dahili bir hata oluştu. + no player starts with: Adı '%s' ile başlayan oyuncu bulunamadı. + multiple players start with: Adı '%s' ile başlayan birden fazla oyuncu var. + +# -- Hooks -- +hooks: + hooked: %s'e başarıyla bağlanıldı. + error: %1$s'a bağlanılamadı. Skript %1$s'in yüklü sürümünü desteklemiyorsa bu durum meydana gelebilir + +# -- Aliases -- +aliases: + # Errors and warnings + empty string: '' bir eşya türü değil. + invalid item type: '%s' bir eşya türü değil. + empty name: Her Alias'ın bir ismi olmak zorundadır. + brackets error: Hatalı parantez kullanımı. + not enough brackets: %s ('%s') karakteriyle başlayan her section'ın kapatılması gerekir. + too many brackets: Karakter %s ('%s') olmayan bir section'u kapatıyor. + unknown variation: %s varyasyonu daha önce tanımlanmadı. + missing aliases: Bu Minecraft ID'lerinin herhangi bir alias'ı yoktur: + empty alias: Alias'ın tanımlı bir Minecraft ID'si veya tag'i yok. + invalid minecraft id: Minecraft ID %s geçerli değil + useless variation: Varyasyonun Minecraft ID'si veya tag'i yok, bu yüzden gereksiz. + invalid tags: Belirtilen tagler geçerli JSON'da tanımlı değil. + unexpected section: Section'lara burada izin verilmiyor. + invalid variation section: Bir section'un varyasyon section'u olması gerek, ama %s geçerli bir varyasyon ismi değil. + outside section: Alias'lar section'a koyulmalı. + + # Other messages + loaded x aliases from: %2$s'ten %1$s tane ingilizce alias yüklendi. + loaded x aliases: Toplamda %s ingilizce alias yüklendi. + +# -- Time -- +time: + errors: + 24 hours: Bir günde sadece 24 saat var. + 12 hours: 12-saat formatında en fazla 12 kullanabilirsiniz. + 60 minutes: Bir saatte sadece 60 dakika var. + +# -- IO Exceptions -- +io exceptions: + unknownhostexception: %s'ya bağlanılamadı. + accessdeniedexception: %s için izin reddedildi. From 98abf5d7773df11c65d66e4e9485d0f1e3ef8821 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Sun, 31 Dec 2023 18:48:13 -0800 Subject: [PATCH 563/619] Cleanup lang (#6000) * Clean up lang package, base syntax classes. I apologize to all reviewers * Update .git-blame-ignore-revs * Re-add mistakenly removed setTime * missed a "t" * Update src/main/java/ch/njol/skript/expressions/base/WrapperExpression.java Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> * Update EventValueExpression.java * Accidentally screwed up the merge conflicts * fix evtgrow * Apply suggestions from code review Co-authored-by: Patrick Miller <apickledwalrus@gmail.com> * Apply suggestions from code review Co-authored-by: Patrick Miller <apickledwalrus@gmail.com> * requested changes * try with resources * I'm so bad at resolving conflicts. --------- Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Co-authored-by: Patrick Miller <apickledwalrus@gmail.com> --- .git-blame-ignore-revs | 1 + code-conventions.md | 4 +- .../ch/njol/skript/classes/ClassInfo.java | 27 +- .../ch/njol/skript/classes/Converter.java | 13 +- .../conditions/base/PropertyCondition.java | 12 +- .../ch/njol/skript/doc/Documentation.java | 48 +- .../ch/njol/skript/doc/HTMLGenerator.java | 46 +- .../ch/njol/skript/entity/EntityData.java | 12 +- .../java/ch/njol/skript/events/EvtGrow.java | 12 +- .../ch/njol/skript/expressions/ExprItems.java | 8 +- .../base/EventValueExpression.java | 64 +- .../expressions/base/PropertyExpression.java | 12 +- .../base/SimplePropertyExpression.java | 10 +- .../expressions/base/WrapperExpression.java | 53 +- .../java/ch/njol/skript/lang/Condition.java | 47 +- .../java/ch/njol/skript/lang/Debuggable.java | 14 +- .../njol/skript/lang/DefaultExpression.java | 19 +- src/main/java/ch/njol/skript/lang/Effect.java | 41 +- .../ch/njol/skript/lang/EffectSection.java | 17 +- .../njol/skript/lang/EffectSectionEffect.java | 17 +- .../java/ch/njol/skript/lang/Expression.java | 160 ++- .../ch/njol/skript/lang/ExpressionInfo.java | 24 +- .../ch/njol/skript/lang/ExpressionList.java | 99 +- .../java/ch/njol/skript/lang/Literal.java | 19 +- .../java/ch/njol/skript/lang/LiteralList.java | 44 +- .../ch/njol/skript/lang/ParseContext.java | 10 +- .../java/ch/njol/skript/lang/Section.java | 25 +- .../njol/skript/lang/SectionSkriptEvent.java | 4 +- .../java/ch/njol/skript/lang/SkriptEvent.java | 10 +- .../ch/njol/skript/lang/SkriptEventInfo.java | 104 +- .../ch/njol/skript/lang/SkriptParser.java | 983 +++++++++--------- .../java/ch/njol/skript/lang/Statement.java | 21 +- .../ch/njol/skript/lang/SyntaxElement.java | 6 +- .../njol/skript/lang/SyntaxElementInfo.java | 25 +- .../java/ch/njol/skript/lang/Trigger.java | 30 +- .../java/ch/njol/skript/lang/TriggerItem.java | 119 +-- .../ch/njol/skript/lang/TriggerSection.java | 43 +- src/main/java/ch/njol/skript/lang/Unit.java | 25 +- .../ch/njol/skript/lang/UnparsedLiteral.java | 256 ++--- .../java/ch/njol/skript/lang/Variable.java | 405 ++++---- .../ch/njol/skript/lang/VariableString.java | 416 ++++---- .../skript/lang/util/ContainerExpression.java | 79 +- .../skript/lang/util/ConvertedExpression.java | 204 ++-- .../skript/lang/util/ConvertedLiteral.java | 71 +- .../ch/njol/skript/lang/util/SimpleEvent.java | 16 +- .../skript/lang/util/SimpleExpression.java | 230 ++-- .../njol/skript/lang/util/SimpleLiteral.java | 165 ++- .../njol/skript/structures/StructCommand.java | 4 +- .../skript/lang/structure/Structure.java | 4 +- 49 files changed, 1918 insertions(+), 2160 deletions(-) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 00000000000..a3ad5118388 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1 @@ +# .git-blame-ignore-revs diff --git a/code-conventions.md b/code-conventions.md index 37ac182cd62..327951d2299 100644 --- a/code-conventions.md +++ b/code-conventions.md @@ -127,9 +127,11 @@ If we need to remove or alter contributed code due to a licensing issue we will - Static constant fields should be named in `UPPER_SNAKE_CASE` * Localised messages should be named in `lower_snake_case` - And that is the only place where snake_case is acceptable -* Use prefixes only where their use has been already estabilished (such as `ExprSomeRandomThing`) +* Use prefixes only where their use has been already established (such as `ExprSomeRandomThing`) - Otherwise, use postfixes where necessary - Common occurrences include: Struct (Structure), Sec (Section), EffSec (EffectSection), Eff (Effect), Cond (Condition), Expr (Expression) +* Ensure variable/field names are descriptive. Avoid using shorthand names like `e`, or `c`. + - e.g. Event should be `event`, not `e`. `e` is ambiguous and could mean a number of things. ### Comments * Prefer to comment *why* you're doing things instead of how you're doing them diff --git a/src/main/java/ch/njol/skript/classes/ClassInfo.java b/src/main/java/ch/njol/skript/classes/ClassInfo.java index 3dce029edb5..253ca2d3cb2 100644 --- a/src/main/java/ch/njol/skript/classes/ClassInfo.java +++ b/src/main/java/ch/njol/skript/classes/ClassInfo.java @@ -18,26 +18,25 @@ */ package ch.njol.skript.classes; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; -import java.util.function.Supplier; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; - import ch.njol.skript.SkriptAPIException; -import ch.njol.util.coll.iterator.ArrayIterator; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.expressions.base.EventValueExpression; import ch.njol.skript.lang.Debuggable; import ch.njol.skript.lang.DefaultExpression; import ch.njol.skript.lang.util.SimpleLiteral; import ch.njol.skript.localization.Noun; +import ch.njol.util.coll.iterator.ArrayIterator; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.function.Supplier; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; /** * @author Peter Güttinger @@ -515,7 +514,7 @@ public String toString(final int flags) { @Override @NonNull - public String toString(final @Nullable Event e, final boolean debug) { + public String toString(final @Nullable Event event, final boolean debug) { if (debug) return codeName + " (" + c.getCanonicalName() + ")"; return getName().getSingular(); diff --git a/src/main/java/ch/njol/skript/classes/Converter.java b/src/main/java/ch/njol/skript/classes/Converter.java index d2162d6eccf..0cb0da707b9 100644 --- a/src/main/java/ch/njol/skript/classes/Converter.java +++ b/src/main/java/ch/njol/skript/classes/Converter.java @@ -18,16 +18,15 @@ */ package ch.njol.skript.classes; -import java.util.Arrays; -import java.util.stream.Collectors; - -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.lang.Debuggable; import ch.njol.skript.registrations.Classes; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; import org.skriptlang.skript.lang.converter.Converters; +import java.util.Arrays; +import java.util.stream.Collectors; + /** * Converts data from type to another. * @@ -98,7 +97,7 @@ public ConverterInfo(ConverterInfo<?,?> first, ConverterInfo<?,?> second, Conver } @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { if (debug) { String str = Arrays.stream(chain).map(c -> Classes.getExactClassName(c)).collect(Collectors.joining(" -> ")); assert str != null; diff --git a/src/main/java/ch/njol/skript/conditions/base/PropertyCondition.java b/src/main/java/ch/njol/skript/conditions/base/PropertyCondition.java index 8e73bf83d56..528b216ffc7 100644 --- a/src/main/java/ch/njol/skript/conditions/base/PropertyCondition.java +++ b/src/main/java/ch/njol/skript/conditions/base/PropertyCondition.java @@ -90,6 +90,7 @@ public enum PropertyType { * @param property the property name, for example <i>fly</i> in <i>players can fly</i> * @param type must be plural, for example <i>players</i> in <i>players can fly</i> */ + public static void register(Class<? extends Condition> condition, String property, String type) { register(condition, PropertyType.BE, property, type); } @@ -100,6 +101,7 @@ public static void register(Class<? extends Condition> condition, String propert * @param property the property name, for example <i>fly</i> in <i>players can fly</i> * @param type must be plural, for example <i>players</i> in <i>players can fly</i> */ + public static void register(Class<? extends Condition> condition, PropertyType propertyType, String property, String type) { if (type.contains("%")) throw new SkriptAPIException("The type argument must not contain any '%'s"); @@ -131,9 +133,9 @@ public static void register(Class<? extends Condition> condition, PropertyType p } @Override - @SuppressWarnings("unchecked") - public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - expr = (Expression<? extends T>) exprs[0]; + public boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + expr = (Expression<? extends T>) expressions[0]; + setNegated(matchedPattern == 1); return true; } @@ -144,8 +146,8 @@ public final boolean check(Event event) { } @Override - public abstract boolean check(T t); - + public abstract boolean check(T value); + protected abstract String getPropertyName(); protected PropertyType getPropertyType() { diff --git a/src/main/java/ch/njol/skript/doc/Documentation.java b/src/main/java/ch/njol/skript/doc/Documentation.java index c7b9ebebbb1..9797e1b2188 100644 --- a/src/main/java/ch/njol/skript/doc/Documentation.java +++ b/src/main/java/ch/njol/skript/doc/Documentation.java @@ -18,18 +18,6 @@ */ package ch.njol.skript.doc; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.conditions.CondCompare; @@ -47,6 +35,17 @@ import ch.njol.util.coll.CollectionUtils; import ch.njol.util.coll.iterator.IteratorIterable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.eclipse.jdt.annotation.Nullable; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * TODO list special expressions for events and event values @@ -305,28 +304,29 @@ public String run(final Matcher m) { } private static void insertSyntaxElement(final PrintWriter pw, final SyntaxElementInfo<?> info, final String type) { - if (info.c.getAnnotation(NoDoc.class) != null) + Class<?> elementClass = info.getElementClass(); + if (elementClass.getAnnotation(NoDoc.class) != null) return; - if (info.c.getAnnotation(Name.class) == null || info.c.getAnnotation(Description.class) == null || info.c.getAnnotation(Examples.class) == null || info.c.getAnnotation(Since.class) == null) { - Skript.warning("" + info.c.getSimpleName() + " is missing information"); + if (elementClass.getAnnotation(Name.class) == null || elementClass.getAnnotation(Description.class) == null || elementClass.getAnnotation(Examples.class) == null || elementClass.getAnnotation(Since.class) == null) { + Skript.warning("" + elementClass.getSimpleName() + " is missing information"); return; } - final String desc = validateHTML(StringUtils.join(info.c.getAnnotation(Description.class).value(), "<br/>"), type + "s"); - final String since = validateHTML(info.c.getAnnotation(Since.class).value(), type + "s"); + final String desc = validateHTML(StringUtils.join(elementClass.getAnnotation(Description.class).value(), "<br/>"), type + "s"); + final String since = validateHTML(elementClass.getAnnotation(Since.class).value(), type + "s"); if (desc == null || since == null) { - Skript.warning("" + info.c.getSimpleName() + "'s description or 'since' is invalid"); + Skript.warning("" + elementClass.getSimpleName() + "'s description or 'since' is invalid"); return; } - final String patterns = cleanPatterns(StringUtils.join(info.patterns, "\n", 0, info.c == CondCompare.class ? 8 : info.patterns.length)); + final String patterns = cleanPatterns(StringUtils.join(info.patterns, "\n", 0, elementClass == CondCompare.class ? 8 : info.patterns.length)); insertOnDuplicateKeyUpdate(pw, "syntax_elements", "id, name, type, patterns, description, examples, since", "patterns = TRIM(LEADING '\n' FROM CONCAT(patterns, '\n', '" + escapeSQL(patterns) + "'))", - escapeHTML("" + info.c.getSimpleName()), - escapeHTML(info.c.getAnnotation(Name.class).value()), + escapeHTML("" + elementClass.getSimpleName()), + escapeHTML(elementClass.getAnnotation(Name.class).value()), type, patterns, desc, - escapeHTML(StringUtils.join(info.c.getAnnotation(Examples.class).value(), "\n")), + escapeHTML(StringUtils.join(elementClass.getAnnotation(Examples.class).value(), "\n")), since); } @@ -334,7 +334,7 @@ private static void insertEvent(final PrintWriter pw, final SkriptEventInfo<?> i if (info.getDescription() == SkriptEventInfo.NO_DOC) return; if (info.getDescription() == null || info.getExamples() == null || info.getSince() == null) { - Skript.warning("" + info.getName() + " (" + info.c.getSimpleName() + ") is missing information"); + Skript.warning("" + info.getName() + " (" + info.getElementClass().getSimpleName() + ") is missing information"); return; } for (final SkriptEventInfo<?> i : Skript.getEvents()) { @@ -346,7 +346,7 @@ private static void insertEvent(final PrintWriter pw, final SkriptEventInfo<?> i final String desc = validateHTML(StringUtils.join(info.getDescription(), "<br/>"), "events"); final String since = validateHTML(info.getSince(), "events"); if (desc == null || since == null) { - Skript.warning("description or 'since' of " + info.getName() + " (" + info.c.getSimpleName() + ") is invalid"); + Skript.warning("description or 'since' of " + info.getName() + " (" + info.getElementClass().getSimpleName() + ") is invalid"); return; } final String patterns = cleanPatterns(info.getName().startsWith("On ") ? "[on] " + StringUtils.join(info.patterns, "\n[on] ") : StringUtils.join(info.patterns, "\n")); diff --git a/src/main/java/ch/njol/skript/doc/HTMLGenerator.java b/src/main/java/ch/njol/skript/doc/HTMLGenerator.java index 01a217cc5fb..e882057cebd 100644 --- a/src/main/java/ch/njol/skript/doc/HTMLGenerator.java +++ b/src/main/java/ch/njol/skript/doc/HTMLGenerator.java @@ -86,19 +86,19 @@ public HTMLGenerator(File templateDir, File outputDir) { throw new NullPointerException(); } - if (o1.c.getAnnotation(NoDoc.class) != null) { - if (o2.c.getAnnotation(NoDoc.class) != null) + if (o1.getElementClass().getAnnotation(NoDoc.class) != null) { + if (o2.getElementClass().getAnnotation(NoDoc.class) != null) return 0; return 1; - } else if (o2.c.getAnnotation(NoDoc.class) != null) + } else if (o2.getElementClass().getAnnotation(NoDoc.class) != null) return -1; - Name name1 = o1.c.getAnnotation(Name.class); - Name name2 = o2.c.getAnnotation(Name.class); + Name name1 = o1.getElementClass().getAnnotation(Name.class); + Name name2 = o2.getElementClass().getAnnotation(Name.class); if (name1 == null) - throw new SkriptAPIException("Name annotation expected: " + o1.c); + throw new SkriptAPIException("Name annotation expected: " + o1.getElementClass()); if (name2 == null) - throw new SkriptAPIException("Name annotation expected: " + o2.c); + throw new SkriptAPIException("Name annotation expected: " + o2.getElementClass()); return name1.value().compareTo(name2.value()); }; @@ -115,8 +115,8 @@ private static <T> Iterator<SyntaxElementInfo<? extends T>> sortedAnnotatedItera while (it.hasNext()) { SyntaxElementInfo<? extends T> item = it.next(); // Filter unnamed expressions (mostly caused by addons) to avoid throwing exceptions and stop the generation process - if (item.c.getAnnotation(Name.class) == null && item.c.getAnnotation(NoDoc.class) == null) { - Skript.warning("Skipped generating '" + item.c + "' class due to missing Name annotation"); + if (item.getElementClass().getAnnotation(Name.class) == null && item.getElementClass().getAnnotation(NoDoc.class) == null) { + Skript.warning("Skipped generating '" + item.getElementClass() + "' class due to missing Name annotation"); continue; } list.add(item); @@ -141,9 +141,9 @@ public int compare(@Nullable SkriptEventInfo<?> o1, @Nullable SkriptEventInfo<?> throw new NullPointerException(); } - if (o1.c.getAnnotation(NoDoc.class) != null) + if (o1.getElementClass().getAnnotation(NoDoc.class) != null) return 1; - else if (o2.c.getAnnotation(NoDoc.class) != null) + else if (o2.getElementClass().getAnnotation(NoDoc.class) != null) return -1; return o1.name.compareTo(o2.name); @@ -285,7 +285,7 @@ else if (!filesInside.getName().matches("(?i)(.*)\\.(html?|js|css|json)")) { StructureInfo<?> info = it.next(); assert info != null; - if (info.c.getAnnotation(NoDoc.class) != null) + if (info.getElementClass().getAnnotation(NoDoc.class) != null) continue; String desc = generateAnnotated(descTemp, info, generated.toString(), "Structure"); generated.append(desc); @@ -296,7 +296,7 @@ else if (!filesInside.getName().matches("(?i)(.*)\\.(html?|js|css|json)")) { for (Iterator<ExpressionInfo<?,?>> it = sortedAnnotatedIterator((Iterator) Skript.getExpressions()); it.hasNext(); ) { ExpressionInfo<?,?> info = it.next(); assert info != null; - if (info.c.getAnnotation(NoDoc.class) != null) + if (info.getElementClass().getAnnotation(NoDoc.class) != null) continue; String desc = generateAnnotated(descTemp, info, generated.toString(), "Expression"); generated.append(desc); @@ -306,7 +306,7 @@ else if (!filesInside.getName().matches("(?i)(.*)\\.(html?|js|css|json)")) { for (Iterator<SyntaxElementInfo<? extends Effect>> it = sortedAnnotatedIterator(Skript.getEffects().iterator()); it.hasNext(); ) { SyntaxElementInfo<? extends Effect> info = it.next(); assert info != null; - if (info.c.getAnnotation(NoDoc.class) != null) + if (info.getElementClass().getAnnotation(NoDoc.class) != null) continue; generated.append(generateAnnotated(descTemp, info, generated.toString(), "Effect")); } @@ -314,8 +314,8 @@ else if (!filesInside.getName().matches("(?i)(.*)\\.(html?|js|css|json)")) { for (Iterator<SyntaxElementInfo<? extends Section>> it = sortedAnnotatedIterator(Skript.getSections().iterator()); it.hasNext(); ) { SyntaxElementInfo<? extends Section> info = it.next(); assert info != null; - if (EffectSection.class.isAssignableFrom(info.c)) { - if (info.c.getAnnotation(NoDoc.class) != null) + if (EffectSection.class.isAssignableFrom(info.getElementClass())) { + if (info.getElementClass().getAnnotation(NoDoc.class) != null) continue; generated.append(generateAnnotated(descTemp, info, generated.toString(), "EffectSection")); } @@ -325,7 +325,7 @@ else if (!filesInside.getName().matches("(?i)(.*)\\.(html?|js|css|json)")) { for (Iterator<SyntaxElementInfo<? extends Condition>> it = sortedAnnotatedIterator(Skript.getConditions().iterator()); it.hasNext(); ) { SyntaxElementInfo<? extends Condition> info = it.next(); assert info != null; - if (info.c.getAnnotation(NoDoc.class) != null) + if (info.getElementClass().getAnnotation(NoDoc.class) != null) continue; generated.append(generateAnnotated(descTemp, info, generated.toString(), "Condition")); } @@ -334,9 +334,9 @@ else if (!filesInside.getName().matches("(?i)(.*)\\.(html?|js|css|json)")) { for (Iterator<SyntaxElementInfo<? extends Section>> it = sortedAnnotatedIterator(Skript.getSections().iterator()); it.hasNext(); ) { SyntaxElementInfo<? extends Section> info = it.next(); assert info != null; - boolean isEffectSection = EffectSection.class.isAssignableFrom(info.c); + boolean isEffectSection = EffectSection.class.isAssignableFrom(info.getElementClass()); // exclude sections that are EffectSection from isDocsPage, they are added by the effects block above - if ((isEffectSection && isDocsPage) || info.c.getAnnotation(NoDoc.class) != null) + if ((isEffectSection && isDocsPage) || info.getElementClass().getAnnotation(NoDoc.class) != null) continue; generated.append(generateAnnotated(descTemp, info, generated.toString(), (isEffectSection ? "Effect" : "") + "Section")); } @@ -346,7 +346,7 @@ else if (!filesInside.getName().matches("(?i)(.*)\\.(html?|js|css|json)")) { events.sort(eventComparator); for (SkriptEventInfo<?> info : events) { assert info != null; - if (info.c.getAnnotation(NoDoc.class) != null) + if (info.getElementClass().getAnnotation(NoDoc.class) != null) continue; generated.append(generateEvent(descTemp, info, generated.toString())); } @@ -444,7 +444,7 @@ private static String handleIf(String desc, String start, boolean value) { * @return Generated HTML entry. */ private String generateAnnotated(String descTemp, SyntaxElementInfo<?> info, @Nullable String page, String type) { - Class<?> c = info.c; + Class<?> c = info.getElementClass(); String desc; // Name @@ -472,7 +472,7 @@ private String generateAnnotated(String descTemp, SyntaxElementInfo<?> info, @Nu // Documentation ID DocumentationId docId = c.getAnnotation(DocumentationId.class); - String ID = docId != null ? (docId != null ? docId.value() : null) : info.c.getSimpleName(); + String ID = docId != null ? (docId != null ? docId.value() : null) : c.getSimpleName(); // Fix duplicated IDs if (page != null) { if (page.contains("href=\"#" + ID + "\"")) { @@ -569,7 +569,7 @@ private String generateAnnotated(String descTemp, SyntaxElementInfo<?> info, @Nu } private String generateEvent(String descTemp, SkriptEventInfo<?> info, @Nullable String page) { - Class<?> c = info.c; + Class<?> c = info.getElementClass(); String desc; // Name diff --git a/src/main/java/ch/njol/skript/entity/EntityData.java b/src/main/java/ch/njol/skript/entity/EntityData.java index ae02901b769..435415b8c91 100644 --- a/src/main/java/ch/njol/skript/entity/EntityData.java +++ b/src/main/java/ch/njol/skript/entity/EntityData.java @@ -134,7 +134,7 @@ protected EntityData deserialize(final Fields fields) throws StreamCorruptedExce if (info == null) throw new StreamCorruptedException("Invalid EntityData code name " + codeName); try { - final EntityData<?> d = info.c.newInstance(); + final EntityData<?> d = info.getElementClass().newInstance(); d.deserialize(fields); return d; } catch (final InstantiationException e) { @@ -159,9 +159,9 @@ public EntityData deserialize(final String s) { return null; EntityData<?> d; try { - d = i.c.newInstance(); + d = i.getElementClass().newInstance(); } catch (final Exception e) { - Skript.exception(e, "Can't create an instance of " + i.c.getCanonicalName()); + Skript.exception(e, "Can't create an instance of " + i.getElementClass().getCanonicalName()); return null; } if (!d.deserialize(split[1])) @@ -298,7 +298,7 @@ public static <E extends Entity, T extends EntityData<E>> void register(final Cl public EntityData() { for (final EntityDataInfo<?> i : infos) { - if (getClass() == i.c) { + if (getClass() == i.getElementClass()) { info = i; matchedPattern = i.defaultName; return; @@ -409,7 +409,7 @@ public final boolean equals(final @Nullable Object obj) { public static EntityDataInfo<?> getInfo(final Class<? extends EntityData<?>> c) { for (final EntityDataInfo<?> i : infos) { - if (i.c == c) + if (i.getElementClass() == c) return i; } throw new SkriptAPIException("Unregistered EntityData class " + c.getName()); @@ -581,7 +581,7 @@ private static <E extends Entity> EntityData<? super E> getData(final @Nullable if (info.entityClass != Entity.class && (e == null ? info.entityClass.isAssignableFrom(c) : info.entityClass.isInstance(e))) { try { @SuppressWarnings("unchecked") - final EntityData<E> d = (EntityData<E>) info.c.newInstance(); + final EntityData<E> d = (EntityData<E>) info.getElementClass().newInstance(); if (d.init(c, e)) return d; } catch (final Exception ex) { diff --git a/src/main/java/ch/njol/skript/events/EvtGrow.java b/src/main/java/ch/njol/skript/events/EvtGrow.java index e4e6b2207e1..1d10c5d09ce 100644 --- a/src/main/java/ch/njol/skript/events/EvtGrow.java +++ b/src/main/java/ch/njol/skript/events/EvtGrow.java @@ -157,8 +157,9 @@ public boolean check(Event event) { private static boolean checkFrom(Event event, Literal<Object> types) { // treat and lists as or lists - if (types instanceof LiteralList) - ((LiteralList<Object>) types).setAnd(false); + if (types.getAnd() && types instanceof LiteralList) + ((LiteralList<Object>) types).invertAnd(); + if (event instanceof StructureGrowEvent) { Material sapling = ItemUtils.getTreeSapling(((StructureGrowEvent) event).getSpecies()); return types.check(event, type -> { @@ -185,8 +186,9 @@ private static boolean checkFrom(Event event, Literal<Object> types) { private static boolean checkTo(Event event, Literal<Object> types) { // treat and lists as or lists - if (types instanceof LiteralList) - ((LiteralList<Object>) types).setAnd(false); + if (types.getAnd() && types instanceof LiteralList) + ((LiteralList<Object>) types).invertAnd(); + if (event instanceof StructureGrowEvent) { TreeType species = ((StructureGrowEvent) event).getSpecies(); return types.check(event, type -> { @@ -227,4 +229,4 @@ public String toString(@Nullable Event event, boolean debug) { return "grow"; } -} \ No newline at end of file +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprItems.java b/src/main/java/ch/njol/skript/expressions/ExprItems.java index 06d099bc7b4..e008fbc9ce2 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprItems.java +++ b/src/main/java/ch/njol/skript/expressions/ExprItems.java @@ -29,9 +29,7 @@ import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; -import ch.njol.skript.util.Utils; import ch.njol.util.Kleenean; -import ch.njol.util.coll.iterator.IteratorIterable; import com.google.common.collect.Iterables; import org.bukkit.Material; import org.bukkit.event.Event; @@ -140,11 +138,11 @@ public String toString(@Nullable Event event, boolean debug) { } @Override - public boolean isLoopOf(String string) { + public boolean isLoopOf(String input) { if (items) { - return string.equals("item"); + return input.equals("item"); } else { - return string.equals("block"); + return input.equals("block"); } } diff --git a/src/main/java/ch/njol/skript/expressions/base/EventValueExpression.java b/src/main/java/ch/njol/skript/expressions/base/EventValueExpression.java index bd2a602adb8..841eaa2685a 100644 --- a/src/main/java/ch/njol/skript/expressions/base/EventValueExpression.java +++ b/src/main/java/ch/njol/skript/expressions/base/EventValueExpression.java @@ -23,9 +23,6 @@ import java.util.Map; import java.util.Map.Entry; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.SkriptAPIException; import ch.njol.skript.classes.Changer; @@ -45,12 +42,14 @@ import ch.njol.skript.util.Getter; import ch.njol.skript.util.Utils; import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; /** * A useful class for creating default expressions. It simply returns the event value of the given type. * <p> * This class can be used as default expression with <code>new EventValueExpression<T>(T.class)</code> or extended to make it manually placeable in expressions with: - * + * * <pre> * class MyExpression extends EventValueExpression<SomeClass> { * public MyExpression() { @@ -59,17 +58,18 @@ * // ... * } * </pre> - * + * * @see Classes#registerClass(ClassInfo) * @see ClassInfo#defaultExpression(DefaultExpression) * @see DefaultExpression */ public class EventValueExpression<T> extends SimpleExpression<T> implements DefaultExpression<T> { + /** * Registers an expression as {@link ExpressionType#EVENT} with the provided pattern. * This also adds '[the]' to the start of the pattern. - * + * * @param expression The class that represents this EventValueExpression. * @param type The return type of the expression. * @param pattern The pattern for this syntax. @@ -81,43 +81,43 @@ public static <T> void register(Class<? extends EventValueExpression<T>> express private final Map<Class<? extends Event>, Getter<? extends T, ?>> getters = new HashMap<>(); private final Class<?> componentType; - private final Class<? extends T> c; + private final Class<? extends T> type; @Nullable private Changer<? super T> changer; private final boolean single; private final boolean exact; - public EventValueExpression(Class<? extends T> c) { - this(c, null); + public EventValueExpression(Class<? extends T> type) { + this(type, null); } /** * Construct an event value expression. - * - * @param c The class that this event value represents. + * + * @param type The class that this event value represents. * @param exact If false, the event value can be a subclass or a converted event value. */ - public EventValueExpression(Class<? extends T> c, boolean exact) { - this(c, null, exact); + public EventValueExpression(Class<? extends T> type, boolean exact) { + this(type, null, exact); } - public EventValueExpression(Class<? extends T> c, @Nullable Changer<? super T> changer) { - this(c, changer, false); + public EventValueExpression(Class<? extends T> type, @Nullable Changer<? super T> changer) { + this(type, changer, false); } - public EventValueExpression(Class<? extends T> c, @Nullable Changer<? super T> changer, boolean exact) { - assert c != null; - this.c = c; + public EventValueExpression(Class<? extends T> type, @Nullable Changer<? super T> changer, boolean exact) { + assert type != null; + this.type = type; this.exact = exact; this.changer = changer; - single = !c.isArray(); - componentType = single ? c : c.getComponentType(); + single = !type.isArray(); + componentType = single ? type : type.getComponentType(); } @Override - public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) { - if (exprs.length != 0) + public boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parser) { + if (expressions.length != 0) throw new SkriptAPIException(this.getClass().getName() + " has expressions in its pattern but does not override init(...)"); return init(); } @@ -137,7 +137,7 @@ public boolean init() { hasValue = getters.get(event) != null; continue; } - if (EventValues.hasMultipleGetters(event, c, getTime()) == Kleenean.TRUE) { + if (EventValues.hasMultipleGetters(event, type, getTime()) == Kleenean.TRUE) { Noun typeName = Classes.getExactClassInfo(componentType).getName(); log.printError("There are multiple " + typeName.toString(true) + " in " + Utils.a(getParser().getCurrentEventName()) + " event. " + "You must define which " + typeName + " to use."); @@ -145,9 +145,9 @@ public boolean init() { } Getter<? extends T, ?> getter; if (exact) { - getter = EventValues.getExactEventValueGetter(event, c, getTime()); + getter = EventValues.getExactEventValueGetter(event, type, getTime()); } else { - getter = EventValues.getEventValueGetter(event, c, getTime()); + getter = EventValues.getEventValueGetter(event, type, getTime()); } if (getter != null) { getters.put(event, getter); @@ -173,7 +173,7 @@ protected T[] get(Event event) { if (value == null) return (T[]) Array.newInstance(componentType, 0); if (single) { - T[] one = (T[]) Array.newInstance(c, 1); + T[] one = (T[]) Array.newInstance(type, 1); one[0] = value; return one; } @@ -190,16 +190,16 @@ private <E extends Event> T getValue(E event) { final Getter<? extends T, ? super E> g = (Getter<? extends T, ? super E>) getters.get(event.getClass()); return g == null ? null : g.get(event); } - + for (final Entry<Class<? extends Event>, Getter<? extends T, ?>> p : getters.entrySet()) { if (p.getKey().isAssignableFrom(event.getClass())) { getters.put(event.getClass(), p.getValue()); return p.getValue() == null ? null : ((Getter<? extends T, ? super E>) p.getValue()).get(event); } } - + getters.put(event.getClass(), null); - + return null; } @@ -230,13 +230,13 @@ public boolean setTime(int time) { assert event != null; boolean has; if (exact) { - has = EventValues.doesExactEventValueHaveTimeStates(event, c); + has = EventValues.doesExactEventValueHaveTimeStates(event, type); } else { - has = EventValues.doesEventValueHaveTimeStates(event, c); + has = EventValues.doesEventValueHaveTimeStates(event, type); } if (has) { super.setTime(time); - // Since the time was changed, we now need to re-initalize the getters we already got. START + // Since the time was changed, we now need to re-initialize the getters we already got. START getters.clear(); init(); // END diff --git a/src/main/java/ch/njol/skript/expressions/base/PropertyExpression.java b/src/main/java/ch/njol/skript/expressions/base/PropertyExpression.java index 1bc06c9e7ce..3db23041069 100644 --- a/src/main/java/ch/njol/skript/expressions/base/PropertyExpression.java +++ b/src/main/java/ch/njol/skript/expressions/base/PropertyExpression.java @@ -45,26 +45,26 @@ public abstract class PropertyExpression<F, T> extends SimpleExpression<T> { /** * Registers an expression as {@link ExpressionType#PROPERTY} with the two default property patterns "property of %types%" and "%types%'[s] property" * - * @param c the PropertyExpression class being registered. + * @param expressionClass the PropertyExpression class being registered. * @param type the main expression type the property is based off of. * @param property the name of the property. * @param fromType should be plural to support multiple objects but doesn't have to be. */ - public static <T> void register(Class<? extends Expression<T>> c, Class<T> type, String property, String fromType) { - Skript.registerExpression(c, type, ExpressionType.PROPERTY, "[the] " + property + " of %" + fromType + "%", "%" + fromType + "%'[s] " + property); + public static <T> void register(Class<? extends Expression<T>> expressionClass, Class<T> type, String property, String fromType) { + Skript.registerExpression(expressionClass, type, ExpressionType.PROPERTY, "[the] " + property + " of %" + fromType + "%", "%" + fromType + "%'[s] " + property); } /** * Registers an expression as {@link ExpressionType#PROPERTY} with the two default property patterns "property [of %types%]" and "%types%'[s] property" * This method also makes the expression type optional to force a default expression on the property expression. * - * @param c the PropertyExpression class being registered. + * @param expressionClass the PropertyExpression class being registered. * @param type the main expression type the property is based off of. * @param property the name of the property. * @param fromType should be plural to support multiple objects but doesn't have to be. */ - public static <T> void registerDefault(Class<? extends Expression<T>> c, Class<T> type, String property, String fromType) { - Skript.registerExpression(c, type, ExpressionType.PROPERTY, "[the] " + property + " [of %" + fromType + "%]", "%" + fromType + "%'[s] " + property); + public static <T> void registerDefault(Class<? extends Expression<T>> expressionClass, Class<T> type, String property, String fromType) { + Skript.registerExpression(expressionClass, type, ExpressionType.PROPERTY, "[the] " + property + " [of %" + fromType + "%]", "%" + fromType + "%'[s] " + property); } @Nullable diff --git a/src/main/java/ch/njol/skript/expressions/base/SimplePropertyExpression.java b/src/main/java/ch/njol/skript/expressions/base/SimplePropertyExpression.java index 04918958a92..413ac1dca85 100644 --- a/src/main/java/ch/njol/skript/expressions/base/SimplePropertyExpression.java +++ b/src/main/java/ch/njol/skript/expressions/base/SimplePropertyExpression.java @@ -38,18 +38,18 @@ public abstract class SimplePropertyExpression<F, T> extends PropertyExpression< @Override @SuppressWarnings("unchecked") - public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - if (LiteralUtils.hasUnparsedLiteral(exprs[0])) { - setExpr(LiteralUtils.defendExpression(exprs[0])); + public boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (LiteralUtils.hasUnparsedLiteral(expressions[0])) { + setExpr(LiteralUtils.defendExpression(expressions[0])); return LiteralUtils.canInitSafely(getExpr()); } - setExpr((Expression<? extends F>) exprs[0]); + setExpr((Expression<? extends F>) expressions[0]); return true; } @Override @Nullable - public abstract T convert(F f); + public abstract T convert(F from); @Override protected T[] get(Event event, F[] source) { diff --git a/src/main/java/ch/njol/skript/expressions/base/WrapperExpression.java b/src/main/java/ch/njol/skript/expressions/base/WrapperExpression.java index 652ff72fa1f..fdec45e23b1 100644 --- a/src/main/java/ch/njol/skript/expressions/base/WrapperExpression.java +++ b/src/main/java/ch/njol/skript/expressions/base/WrapperExpression.java @@ -18,20 +18,19 @@ */ package ch.njol.skript.expressions.base; -import java.util.Iterator; - -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.classes.Changer.ChangeMode; -import org.skriptlang.skript.lang.converter.ConverterInfo; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.SyntaxElement; import ch.njol.skript.lang.util.ConvertedExpression; import ch.njol.skript.lang.util.SimpleExpression; -import org.skriptlang.skript.lang.converter.Converters; import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.converter.ConverterInfo; +import org.skriptlang.skript.lang.converter.Converters; + +import java.util.Iterator; /** * Represents an expression which is a wrapper of another one. Remember to set the wrapped expression in the constructor ({@link #WrapperExpression(SimpleExpression)}) @@ -47,7 +46,7 @@ public abstract class WrapperExpression<T> extends SimpleExpression<T> { @SuppressWarnings("null") protected WrapperExpression() {} - public WrapperExpression(final SimpleExpression<? extends T> expr) { + public WrapperExpression(SimpleExpression<? extends T> expr) { this.expr = expr; } @@ -56,7 +55,7 @@ public WrapperExpression(final SimpleExpression<? extends T> expr) { * this expression. * @param expr Wrapped expression. */ - protected void setExpr(final Expression<? extends T> expr) { + protected void setExpr(Expression<? extends T> expr) { this.expr = expr; } @@ -66,19 +65,19 @@ public Expression<?> getExpr() { @Override @Nullable - protected <R> ConvertedExpression<T, ? extends R> getConvertedExpr(final Class<R>... to) { - for (final Class<R> c : to) { - assert c != null; - @SuppressWarnings("unchecked") - final ConverterInfo<? super T, ? extends R> conv = (ConverterInfo<? super T, ? extends R>) Converters.getConverterInfo(getReturnType(), c); + @SuppressWarnings("unchecked") + protected <R> ConvertedExpression<T, ? extends R> getConvertedExpr(Class<R>... to) { + for (Class<R> type : to) { + assert type != null; + ConverterInfo<? super T, ? extends R> conv = (ConverterInfo<? super T, ? extends R>) Converters.getConverterInfo(getReturnType(), type); if (conv == null) continue; - return new ConvertedExpression<T, R>(expr, c, conv) { + return new ConvertedExpression<T, R>(expr, type, conv) { @Override - public String toString(final @Nullable Event e, final boolean debug) { - if (debug && e == null) - return "(" + WrapperExpression.this.toString(e, debug) + ")->" + to.getName(); - return WrapperExpression.this.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + if (debug && event == null) + return "(" + WrapperExpression.this.toString(event, debug) + ")->" + to.getName(); + return WrapperExpression.this.toString(event, debug); } }; } @@ -86,14 +85,14 @@ public String toString(final @Nullable Event e, final boolean debug) { } @Override - protected T[] get(final Event e) { - return expr.getArray(e); + protected T[] get(Event event) { + return expr.getArray(event); } @Override @Nullable - public Iterator<? extends T> iterator(final Event e) { - return expr.iterator(e); + public Iterator<? extends T> iterator(Event event) { + return expr.iterator(event); } @Override @@ -113,17 +112,17 @@ public Class<? extends T> getReturnType() { @Override @Nullable - public Class<?>[] acceptChange(final ChangeMode mode) { + public Class<?>[] acceptChange(ChangeMode mode) { return expr.acceptChange(mode); } @Override - public void change(final Event e, final @Nullable Object[] delta, final ChangeMode mode) { - expr.change(e, delta, mode); + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + expr.change(event, delta, mode); } @Override - public boolean setTime(final int time) { + public boolean setTime(int time) { return expr.setTime(time); } diff --git a/src/main/java/ch/njol/skript/lang/Condition.java b/src/main/java/ch/njol/skript/lang/Condition.java index a66610b1de2..c4d67cc5fa4 100644 --- a/src/main/java/ch/njol/skript/lang/Condition.java +++ b/src/main/java/ch/njol/skript/lang/Condition.java @@ -18,14 +18,13 @@ */ package ch.njol.skript.lang; -import java.util.Iterator; - -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Checker; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.Iterator; /** * A condition which must be fulfilled for the trigger to continue. If the condition is in a section the behaviour depends on the section. @@ -33,48 +32,48 @@ * @see Skript#registerCondition(Class, String...) */ public abstract class Condition extends Statement { - - private boolean negated = false; - + + private boolean negated; + protected Condition() {} - + /** * Checks whether this condition is satisfied with the given event. This should not alter the event or the world in any way, as conditions are only checked until one returns * false. All subsequent conditions of the same trigger will then be omitted.<br/> * <br/> * You might want to use {@link SimpleExpression#check(Event, Checker)} * - * @param e the event to check + * @param event the event to check * @return <code>true</code> if the condition is satisfied, <code>false</code> otherwise or if the condition doesn't apply to this event. */ - public abstract boolean check(Event e); - + public abstract boolean check(Event event); + @Override - public final boolean run(Event e) { - return check(e); + public final boolean run(Event event) { + return check(event); } - + /** * Sets the negation state of this condition. This will change the behaviour of {@link Expression#check(Event, Checker, boolean)}. */ protected final void setNegated(boolean invert) { negated = invert; } - + /** * @return whether this condition is negated or not. */ public final boolean isNegated() { return negated; } - - @SuppressWarnings({"rawtypes", "unchecked", "null"}) + @Nullable - public static Condition parse(String s, @Nullable String defaultError) { - s = s.trim(); - while (s.startsWith("(") && SkriptParser.next(s, 0, ParseContext.DEFAULT) == s.length()) - s = s.substring(1, s.length() - 1); - return (Condition) SkriptParser.parse(s, (Iterator) Skript.getConditions().iterator(), defaultError); + @SuppressWarnings({"rawtypes", "unchecked"}) + public static Condition parse(String input, @Nullable String defaultError) { + input = input.trim(); + while (input.startsWith("(") && SkriptParser.next(input, 0, ParseContext.DEFAULT) == input.length()) + input = input.substring(1, input.length() - 1); + return (Condition) SkriptParser.parse(input, (Iterator) Skript.getConditions().iterator(), defaultError); } - + } diff --git a/src/main/java/ch/njol/skript/lang/Debuggable.java b/src/main/java/ch/njol/skript/lang/Debuggable.java index 1ea761eb214..3de15921825 100644 --- a/src/main/java/ch/njol/skript/lang/Debuggable.java +++ b/src/main/java/ch/njol/skript/lang/Debuggable.java @@ -22,21 +22,21 @@ import org.eclipse.jdt.annotation.Nullable; /** - * @author Peter Güttinger + * Represents an element that can print details involving an event. */ public interface Debuggable { - + /** - * @param e The event to get information to. This is always null if debug == false. + * @param event The event to get information from. This is always null if debug == false. * @param debug If true this should print more information, if false this should print what is shown to the end user * @return String representation of this object */ - public String toString(@Nullable Event e, boolean debug); - + String toString(@Nullable Event event, boolean debug); + /** * Should return <tt>{@link #toString(Event, boolean) toString}(null, false)</tt> */ @Override - public String toString(); - + String toString(); + } diff --git a/src/main/java/ch/njol/skript/lang/DefaultExpression.java b/src/main/java/ch/njol/skript/lang/DefaultExpression.java index 28217a343a8..86140cd57db 100644 --- a/src/main/java/ch/njol/skript/lang/DefaultExpression.java +++ b/src/main/java/ch/njol/skript/lang/DefaultExpression.java @@ -19,18 +19,21 @@ package ch.njol.skript.lang; /** - * Represents an expression that can be used as the default value of a certain type and event. - * - * @author Peter Güttinger + * Represents an expression that can be used as the default value of a certain type or event. */ public interface DefaultExpression<T> extends Expression<T> { - - public boolean init(); - + + /** + * Called when an expression is initialized. + * + * @return Whether the expression is valid in its context. Skript will error if false. + */ + boolean init(); + /** * @return Usually true, though this is not required, as e.g. SimpleLiteral implements DefaultExpression but is usually not the default of an event. */ @Override - public boolean isDefault(); - + boolean isDefault(); + } diff --git a/src/main/java/ch/njol/skript/lang/Effect.java b/src/main/java/ch/njol/skript/lang/Effect.java index 1fa5fd428f7..0b734ad4372 100644 --- a/src/main/java/ch/njol/skript/lang/Effect.java +++ b/src/main/java/ch/njol/skript/lang/Effect.java @@ -18,15 +18,14 @@ */ package ch.njol.skript.lang; -import java.util.Iterator; - -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.lang.function.EffFunctionCall; import ch.njol.skript.log.ParseLogHandler; import ch.njol.skript.log.SkriptLogger; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.Iterator; /** * An effect which is unconditionally executed when reached, and execution will usually continue with the next item of the trigger after this effect is executed (the stop effect @@ -35,45 +34,45 @@ * @see Skript#registerEffect(Class, String...) */ public abstract class Effect extends Statement { - + protected Effect() {} - + /** * Executes this effect. * - * @param e + * @param event The event with which this effect will be executed */ - protected abstract void execute(Event e); - + protected abstract void execute(Event event); + @Override - public final boolean run(final Event e) { - execute(e); + public final boolean run(Event event) { + execute(event); return true; } - - @SuppressWarnings({"rawtypes", "unchecked", "null"}) + @Nullable - public static Effect parse(String s, @Nullable String defaultError) { + @SuppressWarnings({"rawtypes", "unchecked"}) + public static Effect parse(String input, @Nullable String defaultError) { ParseLogHandler log = SkriptLogger.startParseLogHandler(); try { - EffFunctionCall f = EffFunctionCall.parse(s); - if (f != null) { + EffFunctionCall functionCall = EffFunctionCall.parse(input); + if (functionCall != null) { log.printLog(); - return f; + return functionCall; } else if (log.hasError()) { log.printError(); return null; } log.clear(); - EffectSection section = EffectSection.parse(s, null, null, null); + EffectSection section = EffectSection.parse(input, null, null, null); if (section != null) { log.printLog(); return new EffectSectionEffect(section); } log.clear(); - Effect effect = (Effect) SkriptParser.parse(s, (Iterator) Skript.getEffects().iterator(), defaultError); + Effect effect = (Effect) SkriptParser.parse(input, (Iterator) Skript.getEffects().iterator(), defaultError); if (effect != null) { log.printLog(); return effect; @@ -85,5 +84,5 @@ public static Effect parse(String s, @Nullable String defaultError) { log.stop(); } } - + } diff --git a/src/main/java/ch/njol/skript/lang/EffectSection.java b/src/main/java/ch/njol/skript/lang/EffectSection.java index 557ed58115b..4da3a5a5bc5 100644 --- a/src/main/java/ch/njol/skript/lang/EffectSection.java +++ b/src/main/java/ch/njol/skript/lang/EffectSection.java @@ -41,7 +41,7 @@ */ public abstract class EffectSection extends Section { - private boolean hasSection = false; + private boolean hasSection; public boolean hasSection() { return hasSection; @@ -51,16 +51,15 @@ public boolean hasSection() { * This method should not be overridden unless you know what you are doing! */ @Override - public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + public boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { SectionContext sectionContext = getParser().getData(SectionContext.class); //noinspection ConstantConditions - For an EffectSection, it may be null hasSection = sectionContext.sectionNode != null; - - return super.init(exprs, matchedPattern, isDelayed, parseResult); + return super.init(expressions, matchedPattern, isDelayed, parseResult); } @Override - public abstract boolean init(Expression<?>[] exprs, + public abstract boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult, @@ -71,15 +70,15 @@ public abstract boolean init(Expression<?>[] exprs, * Similar to {@link Section#parse(String, String, SectionNode, List)}, but will only attempt to parse from other {@link EffectSection}s. */ @Nullable - @SuppressWarnings({"unchecked", "rawtypes", "ConstantConditions"}) - public static EffectSection parse(String expr, @Nullable String defaultError, @Nullable SectionNode sectionNode, @Nullable List<TriggerItem> triggerItems) { + @SuppressWarnings({"unchecked", "rawtypes"}) + public static EffectSection parse(String input, @Nullable String defaultError, @Nullable SectionNode sectionNode, @Nullable List<TriggerItem> triggerItems) { SectionContext sectionContext = ParserInstance.get().getData(SectionContext.class); return sectionContext.modify(sectionNode, triggerItems, () -> (EffectSection) SkriptParser.parse( - expr, + input, (Iterator) Skript.getSections().stream() - .filter(info -> EffectSection.class.isAssignableFrom(info.c)) + .filter(info -> EffectSection.class.isAssignableFrom(info.getElementClass())) .iterator(), defaultError)); } diff --git a/src/main/java/ch/njol/skript/lang/EffectSectionEffect.java b/src/main/java/ch/njol/skript/lang/EffectSectionEffect.java index 223798635bf..2b93bef0b60 100644 --- a/src/main/java/ch/njol/skript/lang/EffectSectionEffect.java +++ b/src/main/java/ch/njol/skript/lang/EffectSectionEffect.java @@ -23,6 +23,9 @@ import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; +/** + * Represents the Effect aspect of an EffectSection. This allows for the use of EffectSections as effects, rather than just sections. + */ public class EffectSectionEffect extends Effect { private final EffectSection effectSection; @@ -32,16 +35,16 @@ public EffectSectionEffect(EffectSection effectSection) { } @Override - public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - return effectSection.init(exprs, matchedPattern, isDelayed, parseResult); + public boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + return effectSection.init(expressions, matchedPattern, isDelayed, parseResult); } @Override - protected void execute(Event e) { } + protected void execute(Event event) { } @Override - protected @Nullable TriggerItem walk(Event e) { - return effectSection.walk(e); + protected @Nullable TriggerItem walk(Event event) { + return effectSection.walk(event); } @Override @@ -65,8 +68,8 @@ public TriggerItem setNext(@Nullable TriggerItem next) { } @Override - public String toString(@Nullable Event e, boolean debug) { - return effectSection.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return effectSection.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/lang/Expression.java b/src/main/java/ch/njol/skript/lang/Expression.java index 413c9103e10..a44283b1b0f 100644 --- a/src/main/java/ch/njol/skript/lang/Expression.java +++ b/src/main/java/ch/njol/skript/lang/Expression.java @@ -18,22 +18,10 @@ */ package ch.njol.skript.lang; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Spliterators; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import org.bukkit.event.Event; -import org.bukkit.inventory.ItemStack; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.classes.Changer.ChangerUtils; -import org.skriptlang.skript.lang.converter.Converter; import ch.njol.skript.conditions.CondIsSet; import ch.njol.skript.lang.util.ConvertedExpression; import ch.njol.skript.lang.util.SimpleExpression; @@ -45,9 +33,11 @@ import org.bukkit.inventory.ItemStack; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.converter.Converter; -import java.util.Arrays; +import java.util.HashMap; import java.util.Iterator; +import java.util.Map; import java.util.Optional; import java.util.Spliterators; import java.util.stream.Stream; @@ -55,14 +45,13 @@ /** * Represents an expression. Expressions are used within conditions, effects and other expressions. - * - * @author Peter Güttinger + * * @see Skript#registerExpression(Class, Class, ExpressionType, String...) * @see SimpleExpression * @see SyntaxElement */ public interface Expression<T> extends SyntaxElement, Debuggable { - + /** * Get the single value of this expression. * <p> @@ -71,26 +60,26 @@ public interface Expression<T> extends SyntaxElement, Debuggable { * <p> * Do not use this in conditions, use {@link #check(Event, Checker, boolean)} instead. * - * @param e The event + * @param event The event * @return The value or null if this expression doesn't have any value for the event * @throws UnsupportedOperationException (optional) if this was called on a non-single expression */ @Nullable - T getSingle(Event e); + T getSingle(Event event); /** * Get an optional of the single value of this expression. * <p> * Do not use this in conditions, use {@link #check(Event, Checker, boolean)} instead. * - * @param e the event + * @param event the event * @return an {@link Optional} containing the {@link #getSingle(Event) single value} of this expression for this event. * @see #getSingle(Event) */ - default Optional<T> getOptionalSingle(Event e) { - return Optional.ofNullable(getSingle(e)); + default Optional<T> getOptionalSingle(Event event) { + return Optional.ofNullable(getSingle(event)); } - + /** * Get all the values of this expression. The returned array is empty if this expression doesn't have any values for the given event. * <p> @@ -98,39 +87,39 @@ default Optional<T> getOptionalSingle(Event e) { * <p> * Do not use this in conditions, use {@link #check(Event, Checker, boolean)} instead. * - * @param e The event + * @param event The event * @return An array of values of this expression which must neither be null nor contain nulls, and which must not be an internal array. */ - public T[] getArray(final Event e); - + T[] getArray(Event event); + /** * Gets all possible return values of this expression, i.e. it returns the same as {@link #getArray(Event)} if {@link #getAnd()} is true, otherwise all possible values for * {@link #getSingle(Event)}. * - * @param e The event + * @param event The event * @return An array of all possible values of this expression for the given event which must neither be null nor contain nulls, and which must not be an internal array. */ - public T[] getAll(final Event e); - + T[] getAll(Event event); + /** * Gets a non-null stream of this expression's values. * - * @param e The event + * @param event The event * @return A non-null stream of this expression's non-null values */ - default public Stream<@NonNull ? extends T> stream(Event event) { - Iterator<? extends T> iter = iterator(event); - if (iter == null) { + default Stream<@NonNull ? extends T> stream(Event event) { + Iterator<? extends T> iterator = iterator(event); + if (iterator == null) { return Stream.empty(); } - return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iter, 0), false); + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false); } - + /** * @return true if this expression will ever only return one value at most, false if it can return multiple values. */ - public abstract boolean isSingle(); - + boolean isSingle(); + /** * Checks this expression against the given checker. This is the normal version of this method and the one which must be used for simple checks, * or as the innermost check of nested checks. @@ -141,25 +130,25 @@ default Optional<T> getOptionalSingle(Event e) { * return negated ^ {@link #check(Event, Checker)}; * </pre> * - * @param e The event - * @param c A checker + * @param event The event to be used for evaluation + * @param checker The checker that determines whether this expression matches * @param negated The checking condition's negated state. This is used to invert the output of the checker if set to true (i.e. <tt>negated ^ checker.check(...)</tt>) * @return Whether this expression matches or doesn't match the given checker depending on the condition's negated state. * @see SimpleExpression#check(Object[], Checker, boolean, boolean) */ - public boolean check(final Event e, final Checker<? super T> c, final boolean negated); - + boolean check(Event event, Checker<? super T> checker, boolean negated); + /** - * Checks this expression against the given checker. This method must only be used around other checks, use {@link #check(Event, Checker, boolean)} for a simple ckeck or the + * Checks this expression against the given checker. This method must only be used around other checks, use {@link #check(Event, Checker, boolean)} for a simple check or the * innermost check of a nested check. * - * @param e The event - * @param c A checker + * @param event The event to be used for evaluation + * @param checker A checker that determines whether this expression matches * @return Whether this expression matches the given checker * @see SimpleExpression#check(Object[], Checker, boolean, boolean) */ - public boolean check(final Event e, final Checker<? super T> c); - + boolean check(Event event, Checker<? super T> checker); + /** * Tries to convert this expression to the given type. This method can print an error prior to returning null to specify the cause. * <p> @@ -176,15 +165,16 @@ default Optional<T> getOptionalSingle(Event e) { * @see ConvertedExpression */ @Nullable - public <R> Expression<? extends R> getConvertedExpression(final Class<R>... to); - + @SuppressWarnings("unchecked") + <R> Expression<? extends R> getConvertedExpression(Class<R>... to); + /** * Gets the return type of this expression. * * @return A supertype of any objects returned by {@link #getSingle(Event)} and the component type of any arrays returned by {@link #getArray(Event)} */ - public abstract Class<? extends T> getReturnType(); - + Class<? extends T> getReturnType(); + /** * Returns true if this expression returns all possible values, false if it only returns some of them. * <p> @@ -195,8 +185,8 @@ default Optional<T> getOptionalSingle(Event e) { * * @return Whether this expression returns all values at once or only part of them. */ - public boolean getAnd(); - + boolean getAnd(); + /** * Sets the time of this expression, i.e. whether the returned value represents this expression before or after the event. * <p> @@ -213,43 +203,43 @@ default Optional<T> getOptionalSingle(Event e) { * @see SimpleExpression#setTime(int, Expression, Class...) * @see ch.njol.skript.lang.parser.ParserInstance#isCurrentEvent(Class...) */ - public boolean setTime(int time); - + boolean setTime(int time); + /** * @return The value passed to {@link #setTime(int)} or 0 if it was never changed. * @see #setTime(int) */ - public int getTime(); - + int getTime(); + /** * Returns whether this value represents the default value of its type for the event, i.e. it can be replaced with a call to event.getXyz() if one knows the event & value type. * <p> * This method might be removed in the future as it's better to check whether value == event.getXyz() for every value an expression returns. * - * @return Whether is is the return types' default expression + * @return Whether this is the return types' default expression */ - public boolean isDefault(); - + boolean isDefault(); + /** * Returns the same as {@link #getArray(Event)} but as an iterator. This method should be overriden by expressions intended to be looped to increase performance. * - * @param e The event + * @param event The event to be used for evaluation * @return An iterator to iterate over all values of this expression which may be empty and/or null, but must not return null elements. */ @Nullable - public Iterator<? extends T> iterator(Event e); - + Iterator<? extends T> iterator(Event event); + /** * Checks whether the given 'loop-...' expression should match this loop, e.g. loop-block matches any loops that loop through blocks and loop-argument matches an * argument loop. * <p> * You should usually just return false as e.g. loop-block will automatically match the expression if its returnType is Block or a subtype of it. * - * @param s The entered string + * @param input The entered input string (the blank in loop-___) * @return Whether this loop matches the given string */ - public boolean isLoopOf(String s); - + boolean isLoopOf(String input); + /** * Returns the original expression that was parsed, i.e. without any conversions done. * <p> @@ -257,8 +247,8 @@ default Optional<T> getOptionalSingle(Event e) { * * @return The unconverted source expression of this expression or this expression itself if it was never converted. */ - public Expression<?> getSource(); - + Expression<?> getSource(); + /** * Simplifies the expression, e.g. if it only contains literals the expression may be simplified to a literal, and wrapped expressions are unwrapped. * <p> @@ -269,8 +259,8 @@ default Optional<T> getOptionalSingle(Event e) { * @return A reference to a simpler version of this expression. Can change this expression directly and return itself if applicable, i.e. no references to the expression before * this method call should be kept! */ - public Expression<? extends T> simplify(); - + Expression<? extends T> simplify(); + /** * Tests whether this expression supports the given mode, and if yes what type it expects the <code>delta</code> to be. * <p> @@ -282,40 +272,40 @@ default Optional<T> getOptionalSingle(Event e) { * <p> * Unlike {@link Changer#acceptChange(ChangeMode)} this method may print errors. * - * @param mode + * @param mode The mode to check * @return An array of types that {@link #change(Event, Object[], ChangeMode)} accepts as its <code>delta</code> parameter (which can be arrays to denote that multiple of * that type are accepted), or null if the given mode is not supported. For {@link ChangeMode#DELETE} and {@link ChangeMode#RESET} this can return any non-null array to * mark them as supported. */ @Nullable - public Class<?>[] acceptChange(ChangeMode mode); + Class<?>[] acceptChange(ChangeMode mode); /** * Tests all accepted change modes, and if so what type it expects the <code>delta</code> to be. * @return A Map contains ChangeMode as the key and accepted types of that mode as the value */ default Map<ChangeMode, Class<?>[]> getAcceptedChangeModes() { - HashMap<ChangeMode, Class<?>[]> map = new HashMap<>(); - for (ChangeMode cm : ChangeMode.values()) { - Class<?>[] ac = acceptChange(cm); - if (ac != null) - map.put(cm, ac); + Map<ChangeMode, Class<?>[]> map = new HashMap<>(); + for (ChangeMode mode : ChangeMode.values()) { + Class<?>[] validClasses = acceptChange(mode); + if (validClasses != null) + map.put(mode, validClasses); } return map; } - + /** * Changes the expression's value by the given amount. This will only be called on supported modes and with the desired <code>delta</code> type as returned by * {@link #acceptChange(ChangeMode)} * - * @param e - * @param delta An array with one or more instances of one or more of the the classes returned by {@link #acceptChange(ChangeMode)} for the given change mode (null for + * @param event The event + * @param delta An array with one or more instances of one or more of the classes returned by {@link #acceptChange(ChangeMode)} for the given change mode (null for * {@link ChangeMode#DELETE} and {@link ChangeMode#RESET}). <b>This can be a Object[], thus casting is not allowed.</b> - * @param mode + * @param mode The {@link ChangeMode} of the attempted change * @throws UnsupportedOperationException (optional) - If this method was called on an unsupported ChangeMode. */ - public void change(Event e, final @Nullable Object[] delta, final ChangeMode mode); - + void change(Event event, @Nullable Object[] delta, ChangeMode mode); + /** * This method is called before this expression is set to another one. * The return value is what will be used for change. You can use modified @@ -331,7 +321,7 @@ default Map<ChangeMode, Class<?>[]> getAcceptedChangeModes() { default Object[] beforeChange(Expression<?> changed, @Nullable Object[] delta) { if (delta == null || delta.length == 0) // Nothing to nothing return null; - + // Slots must be transformed to item stacks when writing to variables // Also, some types must be cloned Object[] newDelta = null; @@ -344,7 +334,7 @@ default Object[] beforeChange(Expression<?> changed, @Nullable Object[] delta) { if (item != null) { item = item.clone(); // ItemStack in inventory is mutable } - + newDelta[i] = item; } else { newDelta[i] = Classes.clone(delta[i]); @@ -352,9 +342,9 @@ default Object[] beforeChange(Expression<?> changed, @Nullable Object[] delta) { } } // Everything else (inventories, actions, etc.) does not need special handling - + // Return the given delta or an Object[] copy of it, with some values transformed return newDelta == null ? delta : newDelta; } - + } diff --git a/src/main/java/ch/njol/skript/lang/ExpressionInfo.java b/src/main/java/ch/njol/skript/lang/ExpressionInfo.java index b50c00b5fad..0c2c0c2bc62 100644 --- a/src/main/java/ch/njol/skript/lang/ExpressionInfo.java +++ b/src/main/java/ch/njol/skript/lang/ExpressionInfo.java @@ -20,22 +20,25 @@ import org.eclipse.jdt.annotation.Nullable; +/** + * Represents an expression's information, for use when creating new instances of expressions. + */ public class ExpressionInfo<E extends Expression<T>, T> extends SyntaxElementInfo<E> { - - public Class<T> returnType; + @Nullable public ExpressionType expressionType; - - public ExpressionInfo(final String[] patterns, final Class<T> returnType, final Class<E> c, final String originClassPath) throws IllegalArgumentException { - this(patterns, returnType, c, originClassPath, null); + public Class<T> returnType; + + public ExpressionInfo(String[] patterns, Class<T> returnType, Class<E> expressionClass, String originClassPath) throws IllegalArgumentException { + this(patterns, returnType, expressionClass, originClassPath, null); } - - public ExpressionInfo(final String[] patterns, final Class<T> returnType, final Class<E> c, final String originClassPath, @Nullable ExpressionType expressionType) throws IllegalArgumentException { - super(patterns, c, originClassPath); + + public ExpressionInfo(String[] patterns, Class<T> returnType, Class<E> expressionClass, String originClassPath, @Nullable ExpressionType expressionType) throws IllegalArgumentException { + super(patterns, expressionClass, originClassPath); this.returnType = returnType; this.expressionType = expressionType; } - + /** * Get the return type of this expression. * @return The return type of this Expression @@ -43,7 +46,7 @@ public ExpressionInfo(final String[] patterns, final Class<T> returnType, final public Class<T> getReturnType() { return returnType; } - + /** * Get the type of this expression. * @return The type of this Expression @@ -52,4 +55,5 @@ public Class<T> getReturnType() { public ExpressionType getExpressionType() { return expressionType; } + } diff --git a/src/main/java/ch/njol/skript/lang/ExpressionList.java b/src/main/java/ch/njol/skript/lang/ExpressionList.java index 7265477d5a1..1440a84bd2e 100644 --- a/src/main/java/ch/njol/skript/lang/ExpressionList.java +++ b/src/main/java/ch/njol/skript/lang/ExpressionList.java @@ -23,7 +23,6 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleLiteral; import ch.njol.skript.registrations.Classes; -import ch.njol.skript.util.Utils; import ch.njol.util.Checker; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; @@ -34,6 +33,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; +import java.util.List; import java.util.NoSuchElementException; /** @@ -42,9 +42,10 @@ public class ExpressionList<T> implements Expression<T> { protected final Expression<? extends T>[] expressions; + private final Class<T> returnType; protected boolean and; private final boolean single; - private final Class<T> returnType; + @Nullable private final ExpressionList<?> source; @@ -73,43 +74,43 @@ protected ExpressionList(Expression<? extends T>[] expressions, Class<T> returnT } @Override - public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + public boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { throw new UnsupportedOperationException(); } @Override @Nullable - public T getSingle(Event e) { + public T getSingle(Event event) { if (!single) throw new UnsupportedOperationException(); Expression<? extends T> expression = CollectionUtils.getRandom(expressions); - return expression != null ? expression.getSingle(e) : null; + return expression != null ? expression.getSingle(event) : null; } - @SuppressWarnings("unchecked") @Override - public T[] getArray(Event e) { + @SuppressWarnings("unchecked") + public T[] getArray(Event event) { if (and) - return getAll(e); + return getAll(event); Expression<? extends T> expression = CollectionUtils.getRandom(expressions); - return expression != null ? expression.getArray(e) : (T[]) Array.newInstance(returnType, 0); + return expression != null ? expression.getArray(event) : (T[]) Array.newInstance(returnType, 0); } - @SuppressWarnings({"null", "unchecked"}) @Override - public T[] getAll(Event e) { - ArrayList<T> r = new ArrayList<>(); + @SuppressWarnings("unchecked") + public T[] getAll(Event event) { + List<T> values = new ArrayList<>(); for (Expression<? extends T> expr : expressions) - r.addAll(Arrays.asList(expr.getAll(e))); - return r.toArray((T[]) Array.newInstance(returnType, r.size())); + values.addAll(Arrays.asList(expr.getAll(event))); + return values.toArray((T[]) Array.newInstance(returnType, values.size())); } @Override @Nullable - public Iterator<? extends T> iterator(Event e) { + public Iterator<? extends T> iterator(Event event) { if (!and) { Expression<? extends T> expression = CollectionUtils.getRandom(expressions); - return expression != null ? expression.iterator(e) : null; + return expression != null ? expression.iterator(event) : null; } return new Iterator<T>() { private int i = 0; @@ -118,22 +119,22 @@ public Iterator<? extends T> iterator(Event e) { @Override public boolean hasNext() { - Iterator<? extends T> c = current; - while (i < expressions.length && (c == null || !c.hasNext())) - current = c = expressions[i++].iterator(e); - return c != null && c.hasNext(); + Iterator<? extends T> iterator = current; + while (i < expressions.length && (iterator == null || !iterator.hasNext())) + current = iterator = expressions[i++].iterator(event); + return iterator != null && iterator.hasNext(); } @Override public T next() { if (!hasNext()) throw new NoSuchElementException(); - Iterator<? extends T> c = current; - if (c == null) + Iterator<? extends T> iterator = current; + if (iterator == null) throw new NoSuchElementException(); - T t = c.next(); - assert t != null : current; - return t; + T value = iterator.next(); + assert value != null : current; + return value; } @Override @@ -152,6 +153,7 @@ public boolean isSingle() { public boolean check(Event event, Checker<? super T> checker, boolean negated) { for (Expression<? extends T> expr : expressions) { boolean result = expr.check(event, checker) ^ negated; + // exit early if we find a FALSE and we're ANDing, or a TRUE and we're ORing if (and && !result) return false; if (!and && result) @@ -165,9 +167,9 @@ public boolean check(Event event, Checker<? super T> checker) { return check(event, checker, false); } - @SuppressWarnings("unchecked") @Override @Nullable + @SuppressWarnings("unchecked") public <R> Expression<? extends R> getConvertedExpression(Class<R>... to) { Expression<? extends R>[] exprs = new Expression[expressions.length]; Class<?>[] returnTypes = new Class[expressions.length]; @@ -189,17 +191,6 @@ public boolean getAnd() { return and; } - /** - * For use in {@link CondCompare} only. - * - * @return The old 'and' value - */ - public boolean setAnd(boolean and) { - boolean r = and; - this.and = and; - return r; - } - /** * For use in {@link CondCompare} only. */ @@ -227,9 +218,9 @@ public Class<?>[] acceptChange(ChangeMode mode) { } @Override - public void change(Event e, @Nullable Object[] delta, ChangeMode mode) throws UnsupportedOperationException { + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) throws UnsupportedOperationException { for (Expression<?> expr : expressions) { - expr.change(e, delta, mode); + expr.change(event, delta, mode); } } @@ -257,35 +248,35 @@ public boolean isDefault() { } @Override - public boolean isLoopOf(String s) { - for (Expression<?> e : expressions) - if (e.isLoopOf(s)) + public boolean isLoopOf(String input) { + for (Expression<?> expression : expressions) + if (expression.isLoopOf(input)) return true; return false; } @Override public Expression<?> getSource() { - ExpressionList<?> s = source; - return s == null ? this : s; + ExpressionList<?> source = this.source; + return source == null ? this : source; } @Override - public String toString(@Nullable Event e, boolean debug) { - StringBuilder b = new StringBuilder("("); + public String toString(@Nullable Event event, boolean debug) { + StringBuilder result = new StringBuilder("("); for (int i = 0; i < expressions.length; i++) { if (i != 0) { if (i == expressions.length - 1) - b.append(and ? " and " : " or "); + result.append(and ? " and " : " or "); else - b.append(", "); + result.append(", "); } - b.append(expressions[i].toString(e, debug)); + result.append(expressions[i].toString(event, debug)); } - b.append(")"); + result.append(")"); if (debug) - b.append("[").append(returnType).append("]"); - return "" + b; + result.append("[").append(returnType).append("]"); + return result.toString(); } @Override @@ -301,6 +292,7 @@ public Expression<? extends T>[] getExpressions() { } @Override + @SuppressWarnings("unchecked") public Expression<T> simplify() { boolean isLiteralList = true; boolean isSimpleList = true; @@ -310,14 +302,13 @@ public Expression<T> simplify() { isSimpleList &= expressions[i].isSingle(); } if (isLiteralList && isSimpleList) { - @SuppressWarnings("unchecked") T[] values = (T[]) Array.newInstance(returnType, expressions.length); + T[] values = (T[]) Array.newInstance(returnType, expressions.length); for (int i = 0; i < values.length; i++) values[i] = ((Literal<? extends T>) expressions[i]).getSingle(); return new SimpleLiteral<>(values, returnType, and); } if (isLiteralList) { Literal<? extends T>[] ls = Arrays.copyOf(expressions, expressions.length, Literal[].class); - assert ls != null; return new LiteralList<>(ls, returnType, and); } return this; diff --git a/src/main/java/ch/njol/skript/lang/Literal.java b/src/main/java/ch/njol/skript/lang/Literal.java index 889197aa667..890b41b2e12 100644 --- a/src/main/java/ch/njol/skript/lang/Literal.java +++ b/src/main/java/ch/njol/skript/lang/Literal.java @@ -26,15 +26,16 @@ * @author Peter Güttinger */ public interface Literal<T> extends Expression<T> { - - public T[] getArray(); - - public T getSingle(); - + + T[] getArray(); + + T getSingle(); + @Override @Nullable - public <R> Literal<? extends R> getConvertedExpression(Class<R>... to); - - public T[] getAll(); - + @SuppressWarnings("unchecked") + <R> Literal<? extends R> getConvertedExpression(Class<R>... to); + + T[] getAll(); + } diff --git a/src/main/java/ch/njol/skript/lang/LiteralList.java b/src/main/java/ch/njol/skript/lang/LiteralList.java index 118627adf98..4c5cec551dd 100644 --- a/src/main/java/ch/njol/skript/lang/LiteralList.java +++ b/src/main/java/ch/njol/skript/lang/LiteralList.java @@ -18,13 +18,11 @@ */ package ch.njol.skript.lang; -import java.lang.reflect.Array; - import ch.njol.skript.registrations.Classes; +import ch.njol.skript.lang.util.SimpleLiteral; import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.lang.util.SimpleLiteral; -import ch.njol.skript.util.Utils; +import java.lang.reflect.Array; /** * A list of literals. Can contain {@link UnparsedLiteral}s. @@ -32,39 +30,35 @@ * @author Peter Güttinger */ public class LiteralList<T> extends ExpressionList<T> implements Literal<T> { - - public LiteralList(final Literal<? extends T>[] literals, final Class<T> returnType, final boolean and) { + + public LiteralList(Literal<? extends T>[] literals, Class<T> returnType, boolean and) { super(literals, returnType, and); } - - public LiteralList(final Literal<? extends T>[] literals, final Class<T> returnType, final boolean and, final LiteralList<?> source) { + + public LiteralList(Literal<? extends T>[] literals, Class<T> returnType, boolean and, LiteralList<?> source) { super(literals, returnType, and, source); } - - @SuppressWarnings("null") + @Override public T[] getArray() { return getArray(null); } - - @SuppressWarnings("null") + @Override public T getSingle() { return getSingle(null); } - - @SuppressWarnings("null") + @Override public T[] getAll() { return getAll(null); } - - @SuppressWarnings("unchecked") + @Override @Nullable public <R> Literal<? extends R> getConvertedExpression(final Class<R>... to) { - final Literal<? extends R>[] exprs = new Literal[expressions.length]; - final Class<?>[] returnTypes = new Class[expressions.length]; + Literal<? extends R>[] exprs = new Literal[expressions.length]; + Class<?>[] returnTypes = new Class[expressions.length]; for (int i = 0; i < exprs.length; i++) { if ((exprs[i] = (Literal<? extends R>) expressions[i].getConvertedExpression(to)) == null) return null; @@ -72,25 +66,25 @@ public <R> Literal<? extends R> getConvertedExpression(final Class<R>... to) { } return new LiteralList<>(exprs, (Class<R>) Classes.getSuperClassInfo(returnTypes).getC(), and, this); } - + @Override public Literal<? extends T>[] getExpressions() { return (Literal<? extends T>[]) super.getExpressions(); } - + @Override + @SuppressWarnings("unchecked") public Expression<T> simplify() { boolean isSimpleList = true; - for (int i = 0; i < expressions.length; i++) - isSimpleList &= expressions[i].isSingle(); + for (Expression<? extends T> expression : expressions) + isSimpleList &= expression.isSingle(); if (isSimpleList) { - @SuppressWarnings("unchecked") - final T[] values = (T[]) Array.newInstance(getReturnType(), expressions.length); + T[] values = (T[]) Array.newInstance(getReturnType(), expressions.length); for (int i = 0; i < values.length; i++) values[i] = ((Literal<? extends T>) expressions[i]).getSingle(); return new SimpleLiteral<>(values, getReturnType(), and); } return this; } - + } diff --git a/src/main/java/ch/njol/skript/lang/ParseContext.java b/src/main/java/ch/njol/skript/lang/ParseContext.java index 7532d16859e..ecac65279e7 100644 --- a/src/main/java/ch/njol/skript/lang/ParseContext.java +++ b/src/main/java/ch/njol/skript/lang/ParseContext.java @@ -19,23 +19,27 @@ package ch.njol.skript.lang; /** - * @author Peter Güttinger + * Used to provide context as to where an element is being parsed from. */ public enum ParseContext { + /** * Default parse mode */ DEFAULT, + /** * Used for parsing events of triggers. * <p> * TODO? replace with {@link #DUMMY} + {@link SkriptParser#PARSE_LITERALS} */ EVENT, + /** * Only used for parsing arguments of commands */ COMMAND, + /** * Used for parsing text in {@link ch.njol.skript.expressions.ExprParse} */ @@ -44,8 +48,10 @@ public enum ParseContext { * Used for parsing values from a config */ CONFIG, + /** * Used for parsing variables in a script's variables section */ - SCRIPT; + SCRIPT + } diff --git a/src/main/java/ch/njol/skript/lang/Section.java b/src/main/java/ch/njol/skript/lang/Section.java index a151e83ab4c..9c4122c686e 100644 --- a/src/main/java/ch/njol/skript/lang/Section.java +++ b/src/main/java/ch/njol/skript/lang/Section.java @@ -39,14 +39,13 @@ * In most cases though, a section should load its code through one of the following loading methods: * {@link #loadCode(SectionNode)}, {@link #loadCode(SectionNode, String, Class[])}, {@link #loadOptionalCode(SectionNode)} * <br><br> - * Every section must override the {@link TriggerSection#walk(Event)} method. In this method, you can determine whether - * or not the section should run. If you have stored a {@link Trigger} from {@link #loadCode(SectionNode, String, Class[])}, you + * Every section must override the {@link TriggerSection#walk(Event)} method. In this method, you can determine whether * the section should run. If you have stored a {@link Trigger} from {@link #loadCode(SectionNode, String, Class[])}, you * should not run it with this event passed in this walk method. * <br><br> * In the walk method, it is recommended that you return {@link TriggerSection#walk(Event, boolean)}. * This method is very useful, as it will handle most of the things you need to do. - * The boolean parameter for the method determines whether or not the section should run. - * If it is true, Skript will attempt to run the section's code if it has been loaded. If the section's code hasn't be loaded, Skript will behave as if false was passed. + * The boolean parameter for the method determines whether the section should run. + * If it is true, Skript will attempt to run the section's code if it has been loaded. If the section's code hasn't been loaded, Skript will behave as if false was passed. * If it is false, Skript will just move onto the next syntax element after this section. * So, if you are using a normal section and your code should run immediately, you should just return the result of this method with true for the parameter. * However, in cases where you have loaded your code into a trigger using {@link #loadCode(SectionNode, String, Class[])}, it does not matter @@ -64,12 +63,12 @@ public abstract class Section extends TriggerSection implements SyntaxElement { * This method should not be overridden unless you know what you are doing! */ @Override - public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + public boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { SectionContext sectionContext = getParser().getData(SectionContext.class); - return init(exprs, matchedPattern, isDelayed, parseResult, sectionContext.sectionNode, sectionContext.triggerItems); + return init(expressions, matchedPattern, isDelayed, parseResult, sectionContext.sectionNode, sectionContext.triggerItems); } - public abstract boolean init(Expression<?>[] exprs, + public abstract boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult, @@ -96,7 +95,7 @@ protected void loadCode(SectionNode sectionNode) { /** * Loads the code in the given {@link SectionNode}, * appropriately modifying {@link ParserInstance#getCurrentSections()}. - * + * <br> * This method differs from {@link #loadCode(SectionNode)} in that it * is meant for code that will be executed in a different event. * @@ -114,7 +113,7 @@ protected final Trigger loadCode(SectionNode sectionNode, String name, Class<? e /** * Loads the code in the given {@link SectionNode}, * appropriately modifying {@link ParserInstance#getCurrentSections()}. - * + * <br> * This method differs from {@link #loadCode(SectionNode)} in that it * is meant for code that will be executed in a different event. * @@ -171,8 +170,8 @@ protected void loadOptionalCode(SectionNode sectionNode) { getParser().setHasDelayBefore(Kleenean.UNKNOWN); } - @SuppressWarnings({"unchecked", "rawtypes"}) @Nullable + @SuppressWarnings({"unchecked", "rawtypes"}) public static Section parse(String expr, @Nullable String defaultError, SectionNode sectionNode, List<TriggerItem> triggerItems) { SectionContext sectionContext = ParserInstance.get().getData(SectionContext.class); return sectionContext.modify(sectionNode, triggerItems, @@ -196,11 +195,11 @@ public SectionContext(ParserInstance parserInstance) { /** * Modifies this SectionContext temporarily, for the duration of the {@link Supplier#get()} call, * reverting the changes afterwards. - * + * <br> * This must be used instead of manually modifying the fields of this instance, * unless you also revert the changes afterwards. - * - * See https://github.com/SkriptLang/Skript/pull/4353 and https://github.com/SkriptLang/Skript/issues/4473. + * <br> + * See <a href="https://github.com/SkriptLang/Skript/pull/4353">Pull Request #4353</a> and <a href="https://github.com/SkriptLang/Skript/issues/4473">Issue #4473</a>. */ protected <T> T modify(SectionNode sectionNode, List<TriggerItem> triggerItems, Supplier<T> supplier) { SectionNode prevSectionNode = this.sectionNode; diff --git a/src/main/java/ch/njol/skript/lang/SectionSkriptEvent.java b/src/main/java/ch/njol/skript/lang/SectionSkriptEvent.java index 593bae282f8..d6f56f294b6 100644 --- a/src/main/java/ch/njol/skript/lang/SectionSkriptEvent.java +++ b/src/main/java/ch/njol/skript/lang/SectionSkriptEvent.java @@ -61,12 +61,12 @@ public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResu } @Override - public boolean check(Event e) { + public boolean check(Event event) { throw new SkriptAPIException("check should never be called for a SectionSkriptEvent."); } @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { return name; } diff --git a/src/main/java/ch/njol/skript/lang/SkriptEvent.java b/src/main/java/ch/njol/skript/lang/SkriptEvent.java index bbdbd7d170a..3be262a6005 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptEvent.java +++ b/src/main/java/ch/njol/skript/lang/SkriptEvent.java @@ -162,7 +162,7 @@ public Priority getPriority() { } /** - * Checks whether the given Event applies, e.g. the leftclick event is only part of the PlayerInteractEvent, and this checks whether the player leftclicked or not. This method + * Checks whether the given Event applies, e.g. the left-click event is only part of the PlayerInteractEvent, and this checks whether the player left-clicked or not. This method * will only be called for events this SkriptEvent is registered for. * @return true if this is SkriptEvent is represented by the Bukkit Event or false if not */ @@ -216,10 +216,10 @@ public static String fixPattern(String pattern) { boolean inType = false; for (int i = 0; i < chars.length; i++) { - char c = chars[i]; - stringBuilder.append(c); + char character = chars[i]; + stringBuilder.append(character); - if (c == '%') { + if (character == '%') { // toggle inType inType = !inType; @@ -227,7 +227,7 @@ public static String fixPattern(String pattern) { // a type specification can have two prefix characters for modification if (inType && i + 2 < chars.length && chars[i + 1] != '-' && chars[i + 2] != '-') stringBuilder.append('-'); - } else if (c == '\\' && i + 1 < chars.length) { + } else if (character == '\\' && i + 1 < chars.length) { // Make sure we don't toggle inType for escape percentage signs stringBuilder.append(chars[i + 1]); i++; diff --git a/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java b/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java index a437bd6e32b..9805ab4b986 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java +++ b/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java @@ -18,99 +18,83 @@ */ package ch.njol.skript.lang; -import java.util.Locale; - -import org.skriptlang.skript.lang.structure.StructureInfo; +import ch.njol.skript.SkriptAPIException; import org.bukkit.event.Event; import org.bukkit.event.player.PlayerInteractAtEntityEvent; import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.structure.StructureInfo; -import ch.njol.skript.SkriptAPIException; +import java.util.Locale; public final class SkriptEventInfo<E extends SkriptEvent> extends StructureInfo<E> { public Class<? extends Event>[] events; public final String name; - - private final String id; - - @Nullable - private String[] description; - @Nullable - private String[] examples; - @Nullable - private String[] keywords; - @Nullable - private String since; + @Nullable - private String documentationID; + private String[] description, examples, keywords, requiredPlugins; + @Nullable - private String[] requiredPlugins; - + private String since, documentationID; + + private final String id; + /** * @param name Capitalised name of the event without leading "On" which is added automatically (Start the name with an asterisk to prevent this). - * @param patterns - * @param c The SkriptEvent's class + * @param patterns The Skript patterns to use for this event + * @param eventClass The SkriptEvent's class * @param originClassPath The class path for the origin of this event. * @param events The Bukkit-Events this SkriptEvent listens to */ - public SkriptEventInfo(String name, final String[] patterns, final Class<E> c, final String originClassPath, final Class<? extends Event>[] events) { - super(patterns, c, originClassPath); - assert name != null; - assert patterns != null && patterns.length > 0; - assert c != null; - assert originClassPath != null; - assert events != null && events.length > 0; - + public SkriptEventInfo(String name, String[] patterns, Class<E> eventClass, String originClassPath, Class<? extends Event>[] events) { + super(patterns, eventClass, originClassPath); for (int i = 0; i < events.length; i++) { for (int j = i + 1; j < events.length; j++) { if (events[i].isAssignableFrom(events[j]) || events[j].isAssignableFrom(events[i])) { if (events[i].equals(PlayerInteractAtEntityEvent.class) || events[j].equals(PlayerInteractAtEntityEvent.class)) continue; // Spigot seems to have an exception for those two events... - - throw new SkriptAPIException("The event " + name + " (" + c.getName() + ") registers with super/subclasses " + events[i].getName() + " and " + events[j].getName()); + + throw new SkriptAPIException("The event " + name + " (" + eventClass.getName() + ") registers with super/subclasses " + events[i].getName() + " and " + events[j].getName()); } } } - + this.events = events; - + if (name.startsWith("*")) { this.name = name = "" + name.substring(1); } else { this.name = "On " + name; } - + // uses the name without 'on ' or '*' this.id = "" + name.toLowerCase(Locale.ENGLISH).replaceAll("[#'\"<>/&]", "").replaceAll("\\s+", "_"); } - + /** * Use this as {@link #description(String...)} to prevent warnings about missing documentation. */ public final static String[] NO_DOC = new String[0]; - + /** * Only used for Skript's documentation. * - * @param description + * @param description The description of this event * @return This SkriptEventInfo object */ - public SkriptEventInfo<E> description(final String... description) { - assert this.description == null; + public SkriptEventInfo<E> description(String... description) { this.description = description; return this; } - + /** * Only used for Skript's documentation. * - * @param examples + * @param examples The examples for this event * @return This SkriptEventInfo object */ - public SkriptEventInfo<E> examples(final String... examples) { - assert this.examples == null; + public SkriptEventInfo<E> examples(String... examples) { this.examples = examples; return this; } @@ -118,36 +102,35 @@ public SkriptEventInfo<E> examples(final String... examples) { /** * Only used for Skript's documentation. * - * @param keywords + * @param keywords The keywords relating to this event * @return This SkriptEventInfo object */ - public SkriptEventInfo<E> keywords(final String... keywords) { - assert this.keywords == null; + public SkriptEventInfo<E> keywords(String... keywords) { this.keywords = keywords; return this; } - + /** * Only used for Skript's documentation. * - * @param since + * @param since The version this event was added in * @return This SkriptEventInfo object */ - public SkriptEventInfo<E> since(final String since) { + public SkriptEventInfo<E> since(String since) { assert this.since == null; this.since = since; return this; } /** - * A non critical ID remapping for syntax elements register using the same class multiple times. - * + * A non-critical ID remapping for syntax elements register using the same class multiple times. + * <br> * Only used for Skript's documentation. * - * @param id + * @param id The ID to use for this syntax element * @return This SkriptEventInfo object */ - public SkriptEventInfo<E> documentationID(final String id) { + public SkriptEventInfo<E> documentationID(String id) { assert this.documentationID == null; this.documentationID = id; return this; @@ -155,32 +138,31 @@ public SkriptEventInfo<E> documentationID(final String id) { /** * Other plugin dependencies for this SkriptEvent. - * + * <br> * Only used for Skript's documentation. * - * @param pluginNames + * @param pluginNames The names of the plugins this event depends on * @return This SkriptEventInfo object */ - public SkriptEventInfo<E> requiredPlugins(final String... pluginNames) { - assert this.requiredPlugins == null; + public SkriptEventInfo<E> requiredPlugins(String... pluginNames) { this.requiredPlugins = pluginNames; return this; } - + public String getId() { return id; } - + public String getName() { return name; } - + @Nullable public String[] getDescription() { return description; } - + @Nullable public String[] getExamples() { return examples; @@ -190,7 +172,7 @@ public String[] getExamples() { public String[] getKeywords() { return keywords; } - + @Nullable public String getSince() { return since; diff --git a/src/main/java/ch/njol/skript/lang/SkriptParser.java b/src/main/java/ch/njol/skript/lang/SkriptParser.java index 7d153d21aaf..d8d7e01f1e9 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptParser.java +++ b/src/main/java/ch/njol/skript/lang/SkriptParser.java @@ -48,7 +48,6 @@ import ch.njol.util.StringUtils; import ch.njol.util.coll.CollectionUtils; import com.google.common.primitives.Booleans; -import org.bukkit.event.EventPriority; import org.bukkit.plugin.java.JavaPlugin; import org.eclipse.jdt.annotation.Nullable; import org.skriptlang.skript.lang.script.Script; @@ -77,24 +76,24 @@ * @author Peter Güttinger */ public class SkriptParser { - - final String expr; - + + private final String expr; + public final static int PARSE_EXPRESSIONS = 1; public final static int PARSE_LITERALS = 2; public final static int ALL_FLAGS = PARSE_EXPRESSIONS | PARSE_LITERALS; private final int flags; - + public final ParseContext context; - - public SkriptParser(final String expr) { + + public SkriptParser(String expr) { this(expr, ALL_FLAGS); } - - public SkriptParser(final String expr, final int flags) { + + public SkriptParser(String expr, int flags) { this(expr, flags, ParseContext.DEFAULT); } - + /** * Constructs a new SkriptParser object that can be used to parse the given expression. * <p> @@ -104,24 +103,23 @@ public SkriptParser(final String expr, final int flags) { * @param flags Some parse flags ({@link #PARSE_EXPRESSIONS}, {@link #PARSE_LITERALS}) * @param context The parse context */ - public SkriptParser(final String expr, final int flags, final ParseContext context) { + public SkriptParser(String expr, int flags, ParseContext context) { assert expr != null; assert (flags & ALL_FLAGS) != 0; this.expr = "" + expr.trim(); this.flags = flags; this.context = context; } - - public SkriptParser(final SkriptParser other, final String expr) { + + public SkriptParser(SkriptParser other, String expr) { this(expr, other.flags, other.context); } - - public final static String wildcard = "[^\"]*?(?:\"[^\"]*?\"[^\"]*?)*?"; - public final static String stringMatcher = "\"[^\"]*?(?:\"\"[^\"]*)*?\""; - - public final static class ParseResult { - public final Expression<?>[] exprs; - public final List<MatchResult> regexes = new ArrayList<>(1); + + public static final String WILDCARD = "[^\"]*?(?:\"[^\"]*?\"[^\"])*?"; + + public static class ParseResult { + public Expression<?>[] exprs; + public List<MatchResult> regexes = new ArrayList<>(1); public String expr; /** * Defaults to 0. Any marks encountered in the pattern will be XORed with the existing value, in particular if only one mark is encountered this value will be set to that @@ -129,8 +127,8 @@ public final static class ParseResult { */ public int mark = 0; public List<String> tags = new ArrayList<>(); - - public ParseResult(final SkriptParser parser, final String pattern) { + + public ParseResult(SkriptParser parser, String pattern) { expr = parser.expr; exprs = new Expression<?>[countUnescaped(pattern, '%') / 2]; } @@ -150,33 +148,33 @@ public boolean hasTag(String tag) { * <p> * Prints errors. */ - @SuppressWarnings("unchecked") @Nullable - public static <T> Literal<? extends T> parseLiteral(String expr, final Class<T> c, final ParseContext context) { + @SuppressWarnings("unchecked") + public static <T> Literal<? extends T> parseLiteral(String expr, Class<T> expectedClass, ParseContext context) { expr = "" + expr.trim(); if (expr.isEmpty()) return null; - return new UnparsedLiteral(expr).getConvertedExpression(context, c); + return new UnparsedLiteral(expr).getConvertedExpression(context, expectedClass); } - + /** * Parses a string as one of the given syntax elements. * <p> * Can print an error. */ @Nullable - public static <T extends SyntaxElement> T parse(String expr, final Iterator<? extends SyntaxElementInfo<T>> source, final @Nullable String defaultError) { + public static <T extends SyntaxElement> T parse(String expr, Iterator<? extends SyntaxElementInfo<T>> source, @Nullable String defaultError) { expr = "" + expr.trim(); if (expr.isEmpty()) { Skript.error(defaultError); return null; } - final ParseLogHandler log = SkriptLogger.startParseLogHandler(); + ParseLogHandler log = SkriptLogger.startParseLogHandler(); try { - final T e = new SkriptParser(expr).parse(source); - if (e != null) { + T element = new SkriptParser(expr).parse(source); + if (element != null) { log.printLog(); - return e; + return element; } log.printError(defaultError); return null; @@ -184,7 +182,7 @@ public static <T extends SyntaxElement> T parse(String expr, final Iterator<? ex log.stop(); } } - + @Nullable public static <T extends SyntaxElement> T parseStatic(String expr, Iterator<? extends SyntaxElementInfo<? extends T>> source, @Nullable String defaultError) { return parseStatic(expr, source, ParseContext.DEFAULT, defaultError); @@ -199,12 +197,12 @@ public static <T extends SyntaxElement> T parseStatic(String expr, Iterator<? ex } ParseLogHandler log = SkriptLogger.startParseLogHandler(); - T e; + T element; try { - e = new SkriptParser(expr, PARSE_LITERALS, parseContext).parse(source); - if (e != null) { + element = new SkriptParser(expr, PARSE_LITERALS, parseContext).parse(source); + if (element != null) { log.printLog(); - return e; + return element; } log.printError(defaultError); return null; @@ -219,56 +217,56 @@ private <T extends SyntaxElement> T parse(Iterator<? extends SyntaxElementInfo<? try { while (source.hasNext()) { SyntaxElementInfo<? extends T> info = source.next(); - patternsLoop: for (int i = 0; i < info.patterns.length; i++) { + patternsLoop: for (int patternIndex = 0; patternIndex < info.patterns.length; patternIndex++) { log.clear(); try { - String pattern = info.patterns[i]; + String pattern = info.patterns[patternIndex]; assert pattern != null; - ParseResult res; + ParseResult parseResult; try { - res = parse_i(pattern, 0, 0); + parseResult = parse_i(pattern); } catch (MalformedPatternException e) { - String message = "pattern compiling exception, element class: " + info.c.getName(); + String message = "pattern compiling exception, element class: " + info.getElementClass().getName(); try { - JavaPlugin providingPlugin = JavaPlugin.getProvidingPlugin(info.c); + JavaPlugin providingPlugin = JavaPlugin.getProvidingPlugin(info.getElementClass()); message += " (provided by " + providingPlugin.getName() + ")"; } catch (IllegalArgumentException | IllegalStateException ignored) {} throw new RuntimeException(message, e); } - if (res != null) { - int x = -1; - for (int j = 0; (x = nextUnescaped(pattern, '%', x + 1)) != -1; j++) { - int x2 = nextUnescaped(pattern, '%', x + 1); - if (res.exprs[j] == null) { - String name = pattern.substring(x + 1, x2); + if (parseResult != null) { + int startIndex = -1; + for (int i = 0; (startIndex = nextUnescaped(pattern, '%', startIndex + 1)) != -1; i++) { + int endIndex = nextUnescaped(pattern, '%', startIndex + 1); + if (parseResult.exprs[i] == null) { + String name = pattern.substring(startIndex + 1, endIndex); if (!name.startsWith("-")) { - ExprInfo vi = getExprInfo(name); - DefaultExpression<?> expr = vi.classes[0].getDefaultExpression(); + ExprInfo exprInfo = getExprInfo(name); + DefaultExpression<?> expr = exprInfo.classes[0].getDefaultExpression(); if (expr == null) - throw new SkriptAPIException("The class '" + vi.classes[0].getCodeName() + "' does not provide a default expression. Either allow null (with %-" + vi.classes[0].getCodeName() + "%) or make it mandatory [pattern: " + info.patterns[i] + "]"); - if (!(expr instanceof Literal) && (vi.flagMask & PARSE_EXPRESSIONS) == 0) - throw new SkriptAPIException("The default expression of '" + vi.classes[0].getCodeName() + "' is not a literal. Either allow null (with %-*" + vi.classes[0].getCodeName() + "%) or make it mandatory [pattern: " + info.patterns[i] + "]"); - if (expr instanceof Literal && (vi.flagMask & PARSE_LITERALS) == 0) - throw new SkriptAPIException("The default expression of '" + vi.classes[0].getCodeName() + "' is a literal. Either allow null (with %-~" + vi.classes[0].getCodeName() + "%) or make it mandatory [pattern: " + info.patterns[i] + "]"); - if (!vi.isPlural[0] && !expr.isSingle()) - throw new SkriptAPIException("The default expression of '" + vi.classes[0].getCodeName() + "' is not a single-element expression. Change your pattern to allow multiple elements or make the expression mandatory [pattern: " + info.patterns[i] + "]"); - if (vi.time != 0 && !expr.setTime(vi.time)) - throw new SkriptAPIException("The default expression of '" + vi.classes[0].getCodeName() + "' does not have distinct time states. [pattern: " + info.patterns[i] + "]"); + throw new SkriptAPIException("The class '" + exprInfo.classes[0].getCodeName() + "' does not provide a default expression. Either allow null (with %-" + exprInfo.classes[0].getCodeName() + "%) or make it mandatory [pattern: " + info.patterns[patternIndex] + "]"); + if (!(expr instanceof Literal) && (exprInfo.flagMask & PARSE_EXPRESSIONS) == 0) + throw new SkriptAPIException("The default expression of '" + exprInfo.classes[0].getCodeName() + "' is not a literal. Either allow null (with %-*" + exprInfo.classes[0].getCodeName() + "%) or make it mandatory [pattern: " + info.patterns[patternIndex] + "]"); + if (expr instanceof Literal && (exprInfo.flagMask & PARSE_LITERALS) == 0) + throw new SkriptAPIException("The default expression of '" + exprInfo.classes[0].getCodeName() + "' is a literal. Either allow null (with %-~" + exprInfo.classes[0].getCodeName() + "%) or make it mandatory [pattern: " + info.patterns[patternIndex] + "]"); + if (!exprInfo.isPlural[0] && !expr.isSingle()) + throw new SkriptAPIException("The default expression of '" + exprInfo.classes[0].getCodeName() + "' is not a single-element expression. Change your pattern to allow multiple elements or make the expression mandatory [pattern: " + info.patterns[patternIndex] + "]"); + if (exprInfo.time != 0 && !expr.setTime(exprInfo.time)) + throw new SkriptAPIException("The default expression of '" + exprInfo.classes[0].getCodeName() + "' does not have distinct time states. [pattern: " + info.patterns[patternIndex] + "]"); if (!expr.init()) continue patternsLoop; - res.exprs[j] = expr; + parseResult.exprs[i] = expr; } } - x = x2; + startIndex = endIndex; } - T t = info.c.newInstance(); - if (t.init(res.exprs, i, getParser().getHasDelayBefore(), res)) { + T element = info.getElementClass().newInstance(); + if (element.init(parseResult.exprs, patternIndex, getParser().getHasDelayBefore(), parseResult)) { log.printLog(); - return t; + return element; } } - } catch (final InstantiationException | IllegalAccessException e) { + } catch (InstantiationException | IllegalAccessException e) { assert false; } } @@ -279,40 +277,49 @@ private <T extends SyntaxElement> T parse(Iterator<? extends SyntaxElementInfo<? log.stop(); } } - - @SuppressWarnings("null") - private final static Pattern varPattern = Pattern.compile("((the )?var(iable)? )?\\{.+\\}", Pattern.CASE_INSENSITIVE); - + + private static final Pattern VARIABLE_PATTERN = Pattern.compile("((the )?var(iable)? )?\\{.+\\}", Pattern.CASE_INSENSITIVE); + /** * Prints errors */ @Nullable - private static <T> Variable<T> parseVariable(final String expr, final Class<? extends T>[] returnTypes) { - if (varPattern.matcher(expr).matches()) { + private static <T> Variable<T> parseVariable(String expr, Class<? extends T>[] returnTypes) { + if (VARIABLE_PATTERN.matcher(expr).matches()) { String variableName = "" + expr.substring(expr.indexOf('{') + 1, expr.lastIndexOf('}')); boolean inExpression = false; int variableDepth = 0; - for (char c : variableName.toCharArray()) { - if (c == '%' && variableDepth == 0) + for (char character : variableName.toCharArray()) { + if (character == '%' && variableDepth == 0) inExpression = !inExpression; if (inExpression) { - if (c == '{') { + if (character == '{') { variableDepth++; - } else if (c == '}') + } else if (character == '}') variableDepth--; } - if (!inExpression && (c == '{' || c == '}')) + if (!inExpression && (character == '{' || character == '}')) return null; } return Variable.newInstance(variableName, returnTypes); } return null; } - - @SuppressWarnings({"unchecked", "rawtypes"}) + @Nullable - private final <T> Expression<? extends T> parseSingleExpr(final boolean allowUnparsedLiteral, @Nullable final LogEntry error, final Class<? extends T>... types) { + private static Expression<?> parseExpression(Class<?>[] types, String expr) {; + if (expr.startsWith("\"") && expr.length() != 1 && nextQuote(expr, 1) == expr.length() - 1) { + return VariableString.newInstance("" + expr.substring(1, expr.length() - 1)); + } else { + return (Expression<?>) parse(expr, (Iterator) Skript.getExpressions(types), null); + } + } + + + @Nullable + @SuppressWarnings({"unchecked", "rawtypes"}) + private <T> Expression<? extends T> parseSingleExpr(boolean allowUnparsedLiteral, @Nullable LogEntry error, Class<? extends T>... types) { assert types.length > 0; assert types.length == 1 || !CollectionUtils.contains(types, Object.class); if (expr.isEmpty()) @@ -322,26 +329,26 @@ private final <T> Expression<? extends T> parseSingleExpr(final boolean allowUnp expr.startsWith("(") && expr.endsWith(")") && next(expr, 0, context) == expr.length()) return new SkriptParser(this, "" + expr.substring(1, expr.length() - 1)).parseSingleExpr(allowUnparsedLiteral, error, types); - final ParseLogHandler log = SkriptLogger.startParseLogHandler(); + ParseLogHandler log = SkriptLogger.startParseLogHandler(); try { if (context == ParseContext.DEFAULT || context == ParseContext.EVENT) { - final Variable<? extends T> var = parseVariable(expr, types); - if (var != null) { + Variable<? extends T> parsedVariable = parseVariable(expr, types); + if (parsedVariable != null) { if ((flags & PARSE_EXPRESSIONS) == 0) { Skript.error("Variables cannot be used here."); log.printError(); return null; } log.printLog(); - return var; + return parsedVariable; } else if (log.hasError()) { log.printError(); return null; } - final FunctionReference<T> fr = parseFunction(types); - if (fr != null) { + FunctionReference<T> functionReference = parseFunction(types); + if (functionReference != null) { log.printLog(); - return new ExprFunctionCall(fr); + return new ExprFunctionCall(functionReference); } else if (log.hasError()) { log.printError(); return null; @@ -349,30 +356,25 @@ private final <T> Expression<? extends T> parseSingleExpr(final boolean allowUnp } log.clear(); if ((flags & PARSE_EXPRESSIONS) != 0) { - final Expression<?> e; - if (expr.startsWith("\"") && expr.length() != 1 && nextQuote(expr, 1) == expr.length() - 1) { - e = VariableString.newInstance("" + expr.substring(1, expr.length() - 1)); - } else { - e = (Expression<?>) parse(expr, (Iterator) Skript.getExpressions(types), null); - } - if (e != null) { // Expression/VariableString parsing success - for (final Class<? extends T> t : types) { + Expression<?> parsedExpression = parseExpression(types, expr); + if (parsedExpression != null) { // Expression/VariableString parsing success + for (Class<? extends T> type : types) { // Check return type against everything that expression accepts - if (t.isAssignableFrom(e.getReturnType())) { + if (type.isAssignableFrom(parsedExpression.getReturnType())) { log.printLog(); - return (Expression<? extends T>) e; + return (Expression<? extends T>) parsedExpression; } } - + // No directly same type found Class<T>[] objTypes = (Class<T>[]) types; // Java generics... ? - final Expression<? extends T> r = e.getConvertedExpression(objTypes); - if (r != null) { + Expression<? extends T> convertedExpression = parsedExpression.getConvertedExpression(objTypes); + if (convertedExpression != null) { log.printLog(); - return r; + return convertedExpression; } // Print errors, if we couldn't get the correct type - log.printError(e.toString(null, false) + " " + Language.get("is") + " " + notOfType(types), ErrorQuality.NOT_AN_EXPRESSION); + log.printError(parsedExpression.toString(null, false) + " " + Language.get("is") + " " + notOfType(types), ErrorQuality.NOT_AN_EXPRESSION); return null; } log.clear(); @@ -388,16 +390,16 @@ private final <T> Expression<? extends T> parseSingleExpr(final boolean allowUnp return null; } log.clear(); - final LogEntry e = log.getError(); - return (Literal<? extends T>) new UnparsedLiteral(expr, e != null && (error == null || e.quality > error.quality) ? e : error); + LogEntry logError = log.getError(); + return (Literal<? extends T>) new UnparsedLiteral(expr, logError != null && (error == null || logError.quality > error.quality) ? logError : error); } - for (final Class<? extends T> c : types) { + for (Class<? extends T> type : types) { log.clear(); - assert c != null; - final T t = Classes.parse(expr, c, context); - if (t != null) { + assert type != null; + T parsedObject = Classes.parse(expr, type, context); + if (parsedObject != null) { log.printLog(); - return new SimpleLiteral<>(t, false); + return new SimpleLiteral<>(parsedObject, false); } } log.printError(); @@ -406,91 +408,87 @@ private final <T> Expression<? extends T> parseSingleExpr(final boolean allowUnp log.stop(); } } - + @Nullable - private final Expression<?> parseSingleExpr(final boolean allowUnparsedLiteral, @Nullable final LogEntry error, final ExprInfo vi) { + private Expression<?> parseSingleExpr(boolean allowUnparsedLiteral, @Nullable LogEntry error, ExprInfo exprInfo) { if (expr.isEmpty()) // Empty expressions return nothing, obviously return null; - + // Command special parsing if (context != ParseContext.COMMAND && context != ParseContext.PARSE && expr.startsWith("(") && expr.endsWith(")") && next(expr, 0, context) == expr.length()) - return new SkriptParser(this, "" + expr.substring(1, expr.length() - 1)).parseSingleExpr(allowUnparsedLiteral, error, vi); - final ParseLogHandler log = SkriptLogger.startParseLogHandler(); + return new SkriptParser(this, "" + expr.substring(1, expr.length() - 1)).parseSingleExpr(allowUnparsedLiteral, error, exprInfo); + ParseLogHandler log = SkriptLogger.startParseLogHandler(); try { // Construct types array which contains all potential classes - final Class<?>[] types = new Class[vi.classes.length]; // This may contain nulls! + Class<?>[] types = new Class[exprInfo.classes.length]; // This may contain nulls! boolean hasSingular = false; boolean hasPlural = false; - + // Another array for all potential types, but this time without any nulls // (indexes do not align with other data in ExprInfo) - final Class<?>[] nonNullTypes = new Class[vi.classes.length]; - + Class<?>[] nonNullTypes = new Class[exprInfo.classes.length]; + int nonNullIndex = 0; for (int i = 0; i < types.length; i++) { - if ((flags & vi.flagMask) == 0) { // Flag mask invalidates this, skip it + if ((flags & exprInfo.flagMask) == 0) { // Flag mask invalidates this, skip it continue; } - + // Plural/singular checks // TODO move them elsewhere, this method needs to be as fast as possible - if (vi.isPlural[i]) + if (exprInfo.isPlural[i]) hasPlural = true; else hasSingular = true; - + // Actually put class to types[i] - types[i] = vi.classes[i].getC(); - + types[i] = exprInfo.classes[i].getC(); + // Handle nonNullTypes data fill nonNullTypes[nonNullIndex] = types[i]; nonNullIndex++; } - - boolean onlyPlural = false; - boolean onlySingular = false; - if (hasSingular && !hasPlural) - onlySingular = true; - else if (!hasSingular && hasPlural) - onlyPlural = true; - + + boolean onlyPlural = !hasSingular && hasPlural; + boolean onlySingular = hasSingular && !hasPlural; + if (context == ParseContext.DEFAULT || context == ParseContext.EVENT) { // Attempt to parse variable first if (onlySingular || onlyPlural) { // No mixed plurals/singulars possible - final Variable<?> var = parseVariable(expr, nonNullTypes); - if (var != null) { // Parsing succeeded, we have a variable + Variable<?> parsedVariable = parseVariable(expr, nonNullTypes); + if (parsedVariable != null) { // Parsing succeeded, we have a variable // If variables cannot be used here, it is now allowed if ((flags & PARSE_EXPRESSIONS) == 0) { Skript.error("Variables cannot be used here."); log.printError(); return null; } - + // Plural/singular sanity check - if (hasSingular && !var.isSingle()) { + if (hasSingular && !parsedVariable.isSingle()) { Skript.error("'" + expr + "' can only accept a single value of any type, not more", ErrorQuality.SEMANTIC_ERROR); return null; } - + log.printLog(); - return var; + return parsedVariable; } else if (log.hasError()) { log.printError(); return null; } } else { // Mixed plurals/singulars - final Variable<?> var = parseVariable(expr, types); - if (var != null) { // Parsing succeeded, we have a variable + Variable<?> parsedVariable = parseVariable(expr, types); + if (parsedVariable != null) { // Parsing succeeded, we have a variable // If variables cannot be used here, it is now allowed if ((flags & PARSE_EXPRESSIONS) == 0) { Skript.error("Variables cannot be used here."); log.printError(); return null; } - + // Plural/singular sanity check // // It's (currently?) not possible to detect this at parse time when there are multiple @@ -502,27 +500,27 @@ else if (!hasSingular && hasPlural) // before executing the syntax element (perhaps even exceptionally with a console warning, // otherwise users may have some hard time debugging the plurality issues) - currently an // improper use in a script would result in an exception - if (((vi.classes.length == 1 && !vi.isPlural[0]) || Booleans.contains(vi.isPlural, true)) - && !var.isSingle()) { + if (((exprInfo.classes.length == 1 && !exprInfo.isPlural[0]) || Booleans.contains(exprInfo.isPlural, true)) + && !parsedVariable.isSingle()) { Skript.error("'" + expr + "' can only accept a single " - + Classes.toString(Stream.of(vi.classes).map(ci -> ci.getName().toString()).toArray(), false) + + Classes.toString(Stream.of(exprInfo.classes).map(classInfo -> classInfo.getName().toString()).toArray(), false) + ", not more", ErrorQuality.SEMANTIC_ERROR); return null; } - + log.printLog(); - return var; + return parsedVariable; } else if (log.hasError()) { log.printError(); return null; } } - + // If it wasn't variable, do same for function call - final FunctionReference<?> fr = parseFunction(types); - if (fr != null) { + FunctionReference<?> functionReference = parseFunction(types); + if (functionReference != null) { log.printLog(); - return new ExprFunctionCall<>(fr); + return new ExprFunctionCall<>(functionReference); } else if (log.hasError()) { log.printError(); return null; @@ -530,51 +528,44 @@ else if (!hasSingular && hasPlural) } log.clear(); if ((flags & PARSE_EXPRESSIONS) != 0) { - final Expression<?> e; - if (expr.startsWith("\"") && expr.length() != 1 && nextQuote(expr, 1) == expr.length() - 1) { - e = VariableString.newInstance("" + expr.substring(1, expr.length() - 1)); - } else { - e = (Expression<?>) parse(expr, (Iterator) Skript.getExpressions(types), null); - } - if (e != null) { // Expression/VariableString parsing success - Class<?> returnType = e.getReturnType(); // Sometimes getReturnType does non-trivial costly operations - assert returnType != null; + Expression<?> parsedExpression = parseExpression(types, expr); + if (parsedExpression != null) { // Expression/VariableString parsing success + Class<?> returnType = parsedExpression.getReturnType(); // Sometimes getReturnType does non-trivial costly operations for (int i = 0; i < types.length; i++) { - final Class<?> t = types[i]; - if (t == null) // Ignore invalid (null) types + Class<?> type = types[i]; + if (type == null) // Ignore invalid (null) types continue; - + // Check return type against everything that expression accepts - if (t.isAssignableFrom(returnType)) { - if (!vi.isPlural[i] && !e.isSingle()) { // Wrong number of arguments + if (type.isAssignableFrom(returnType)) { + if (!exprInfo.isPlural[i] && !parsedExpression.isSingle()) { // Wrong number of arguments if (context == ParseContext.COMMAND) { - Skript.error(Commands.m_too_many_arguments.toString(vi.classes[i].getName().getIndefiniteArticle(), vi.classes[i].getName().toString()), ErrorQuality.SEMANTIC_ERROR); - return null; + Skript.error(Commands.m_too_many_arguments.toString(exprInfo.classes[i].getName().getIndefiniteArticle(), exprInfo.classes[i].getName().toString()), ErrorQuality.SEMANTIC_ERROR); } else { - Skript.error("'" + expr + "' can only accept a single " + vi.classes[i].getName() + ", not more", ErrorQuality.SEMANTIC_ERROR); - return null; + Skript.error("'" + expr + "' can only accept a single " + exprInfo.classes[i].getName() + ", not more", ErrorQuality.SEMANTIC_ERROR); } + return null; } - + log.printLog(); - return e; + return parsedExpression; } } - - if (onlySingular && !e.isSingle()) { + + if (onlySingular && !parsedExpression.isSingle()) { Skript.error("'" + expr + "' can only accept singular expressions, not plural", ErrorQuality.SEMANTIC_ERROR); return null; } - + // No directly same type found - Expression<?> r = e.getConvertedExpression((Class<Object>[]) types); - if (r != null) { + Expression<?> convertedExpression = parsedExpression.getConvertedExpression((Class<Object>[]) types); + if (convertedExpression != null) { log.printLog(); - return r; + return convertedExpression; } // Print errors, if we couldn't get the correct type - log.printError(e.toString(null, false) + " " + Language.get("is") + " " + notOfType(types), ErrorQuality.NOT_AN_EXPRESSION); + log.printError(parsedExpression.toString(null, false) + " " + Language.get("is") + " " + notOfType(types), ErrorQuality.NOT_AN_EXPRESSION); return null; } log.clear(); @@ -583,23 +574,23 @@ else if (!hasSingular && hasPlural) log.printError(); return null; } - if (vi.classes[0].getC() == Object.class) { + if (exprInfo.classes[0].getC() == Object.class) { // Do check if a literal with this name actually exists before returning an UnparsedLiteral if (!allowUnparsedLiteral || Classes.parseSimple(expr, Object.class, context) == null) { log.printError(); return null; } log.clear(); - final LogEntry e = log.getError(); - return new UnparsedLiteral(expr, e != null && (error == null || e.quality > error.quality) ? e : error); + LogEntry logError = log.getError(); + return new UnparsedLiteral(expr, logError != null && (error == null || logError.quality > error.quality) ? logError : error); } - for (final ClassInfo<?> ci : vi.classes) { + for (ClassInfo<?> classInfo : exprInfo.classes) { log.clear(); - assert ci.getC() != null; - final Object t = Classes.parse(expr, ci.getC(), context); - if (t != null) { + assert classInfo.getC() != null; + Object parsedObject = Classes.parse(expr, classInfo.getC(), context); + if (parsedObject != null) { log.printLog(); - return new SimpleLiteral<>(t, false, new UnparsedLiteral(expr)); + return new SimpleLiteral<>(parsedObject, false, new UnparsedLiteral(expr)); } } log.printError(); @@ -608,59 +599,58 @@ else if (!hasSingular && hasPlural) log.stop(); } } - + /** * Matches ',', 'and', 'or', etc. as well as surrounding whitespace. * <p> * group 1 is null for ',', otherwise it's one of and/or/nor (not necessarily lowercase). */ - @SuppressWarnings("null") public static final Pattern LIST_SPLIT_PATTERN = Pattern.compile("\\s*,?\\s+(and|n?or)\\s+|\\s*,\\s*", Pattern.CASE_INSENSITIVE); public static final Pattern OR_PATTERN = Pattern.compile("\\sor\\s", Pattern.CASE_INSENSITIVE); private final static String MULTIPLE_AND_OR = "List has multiple 'and' or 'or', will default to 'and'. Use brackets if you want to define multiple lists."; private final static String MISSING_AND_OR = "List is missing 'and' or 'or', defaulting to 'and'"; - + private boolean suppressMissingAndOrWarnings = SkriptConfig.disableMissingAndOrWarnings.value(); - + private SkriptParser suppressMissingAndOrWarnings() { suppressMissingAndOrWarnings = true; return this; } - - @SuppressWarnings("unchecked") + @Nullable - public final <T> Expression<? extends T> parseExpression(final Class<? extends T>... types) { + @SuppressWarnings("unchecked") + public <T> Expression<? extends T> parseExpression(Class<? extends T>... types) { if (expr.length() == 0) return null; - + assert types != null && types.length > 0; assert types.length == 1 || !CollectionUtils.contains(types, Object.class); - - final boolean isObject = types.length == 1 && types[0] == Object.class; - final ParseLogHandler log = SkriptLogger.startParseLogHandler(); + + boolean isObject = types.length == 1 && types[0] == Object.class; + ParseLogHandler log = SkriptLogger.startParseLogHandler(); try { - final Expression<? extends T> r = parseSingleExpr(true, null, types); - if (r != null) { + Expression<? extends T> parsedExpression = parseSingleExpr(true, null, types); + if (parsedExpression != null) { log.printLog(); - return r; + return parsedExpression; } log.clear(); - - final List<Expression<? extends T>> ts = new ArrayList<>(); + + List<Expression<? extends T>> parsedExpressions = new ArrayList<>(); Kleenean and = Kleenean.UNKNOWN; boolean isLiteralList = true; - - final List<int[]> pieces = new ArrayList<>(); + + List<int[]> pieces = new ArrayList<>(); { - final Matcher m = LIST_SPLIT_PATTERN.matcher(expr); + Matcher matcher = LIST_SPLIT_PATTERN.matcher(expr); int i = 0, j = 0; for (; i >= 0 && i <= expr.length(); i = next(expr, i, context)) { - if (i == expr.length() || m.region(i, expr.length()).lookingAt()) { + if (i == expr.length() || matcher.region(i, expr.length()).lookingAt()) { pieces.add(new int[] {j, i}); if (i == expr.length()) break; - j = i = m.end(); + j = i = matcher.end(); } } if (i != expr.length()) { @@ -669,7 +659,7 @@ public final <T> Expression<? extends T> parseExpression(final Class<? extends T return null; } } - + if (pieces.size() == 1) { // not a list of expressions, and a single one has failed to parse above if (expr.startsWith("(") && expr.endsWith(")") && next(expr, 0, context) == expr.length()) { log.clear(); @@ -685,26 +675,23 @@ public final <T> Expression<? extends T> parseExpression(final Class<? extends T return null; } - // `b` is the first piece included, `a` is the last - outer: for (int b = 0; b < pieces.size();) { - for (int a = 1; a <= pieces.size() - b; a++) { - if (b == 0 && a == pieces.size()) // i.e. the whole expression - already tried to parse above + outer: for (int first = 0; first < pieces.size();) { + for (int last = 1; last <= pieces.size() - first; last++) { + if (first == 0 && last == pieces.size()) // i.e. the whole expression - already tried to parse above continue; - final int x = pieces.get(b)[0], y = pieces.get(b + a - 1)[1]; - final String subExpr = "" + expr.substring(x, y).trim(); + int start = pieces.get(first)[0], end = pieces.get(first + last - 1)[1]; + String subExpr = "" + expr.substring(start, end).trim(); assert subExpr.length() < expr.length() : subExpr; - - final Expression<? extends T> t; - + if (subExpr.startsWith("(") && subExpr.endsWith(")") && next(subExpr, 0, context) == subExpr.length()) - t = new SkriptParser(this, subExpr).parseExpression(types); // only parse as possible expression list if its surrounded by brackets + parsedExpression = new SkriptParser(this, subExpr).parseExpression(types); // only parse as possible expression list if its surrounded by brackets else - t = new SkriptParser(this, subExpr).parseSingleExpr(a == 1, log.getError(), types); // otherwise parse as a single expression only - if (t != null) { - isLiteralList &= t instanceof Literal; - ts.add(t); - if (b != 0) { - String delimiter = expr.substring(pieces.get(b - 1)[1], x).trim().toLowerCase(Locale.ENGLISH); + parsedExpression = new SkriptParser(this, subExpr).parseSingleExpr(last == 1, log.getError(), types); // otherwise parse as a single expression only + if (parsedExpression != null) { + isLiteralList &= parsedExpression instanceof Literal; + parsedExpressions.add(parsedExpression); + if (first != 0) { + String delimiter = expr.substring(pieces.get(first - 1)[1], start).trim().toLowerCase(Locale.ENGLISH); if (!delimiter.equals(",")) { boolean or = !delimiter.contains("nor") && delimiter.endsWith("or"); if (and.isUnknown()) { @@ -717,7 +704,7 @@ public final <T> Expression<? extends T> parseExpression(final Class<? extends T } } } - b += a; + first += last; continue outer; } } @@ -726,65 +713,63 @@ public final <T> Expression<? extends T> parseExpression(final Class<? extends T } log.printLog(false); - - if (ts.size() == 1) - return ts.get(0); - + + if (parsedExpressions.size() == 1) + return parsedExpressions.get(0); + if (and.isUnknown() && !suppressMissingAndOrWarnings) { ParserInstance parser = getParser(); Script currentScript = parser.isActive() ? parser.getCurrentScript() : null; if (currentScript == null || !currentScript.suppressesWarning(ScriptWarning.MISSING_CONJUNCTION)) Skript.warning(MISSING_AND_OR + ": " + expr); } - - final Class<? extends T>[] exprRetTypes = new Class[ts.size()]; - for (int i = 0; i < ts.size(); i++) - exprRetTypes[i] = ts.get(i).getReturnType(); - + + Class<? extends T>[] exprReturnTypes = new Class[parsedExpressions.size()]; + for (int i = 0; i < parsedExpressions.size(); i++) + exprReturnTypes[i] = parsedExpressions.get(i).getReturnType(); + if (isLiteralList) { - final Literal<T>[] ls = ts.toArray(new Literal[ts.size()]); - assert ls != null; - return new LiteralList<>(ls, (Class<T>) Classes.getSuperClassInfo(exprRetTypes).getC(), !and.isFalse()); + Literal<T>[] literals = parsedExpressions.toArray(new Literal[parsedExpressions.size()]); + return new LiteralList<>(literals, (Class<T>) Classes.getSuperClassInfo(exprReturnTypes).getC(), !and.isFalse()); } else { - final Expression<T>[] es = ts.toArray(new Expression[ts.size()]); - assert es != null; - return new ExpressionList<>(es, (Class<T>) Classes.getSuperClassInfo(exprRetTypes).getC(), !and.isFalse()); + Expression<T>[] expressions = parsedExpressions.toArray(new Expression[parsedExpressions.size()]); + return new ExpressionList<>(expressions, (Class<T>) Classes.getSuperClassInfo(exprReturnTypes).getC(), !and.isFalse()); } } finally { log.stop(); } } - + @Nullable - public final Expression<?> parseExpression(final ExprInfo vi) { + public Expression<?> parseExpression(ExprInfo exprInfo) { if (expr.length() == 0) return null; - - final boolean isObject = vi.classes.length == 1 && vi.classes[0].getC() == Object.class; - final ParseLogHandler log = SkriptLogger.startParseLogHandler(); + + boolean isObject = exprInfo.classes.length == 1 && exprInfo.classes[0].getC() == Object.class; + ParseLogHandler log = SkriptLogger.startParseLogHandler(); try { // Attempt to parse a single expression - final Expression<?> r = parseSingleExpr(true, null, vi); - if (r != null) { + Expression<?> parsedExpression = parseSingleExpr(true, null, exprInfo); + if (parsedExpression != null) { log.printLog(); - return r; + return parsedExpression; } log.clear(); - - final List<Expression<?>> ts = new ArrayList<>(); + + List<Expression<?>> parsedExpressions = new ArrayList<>(); Kleenean and = Kleenean.UNKNOWN; boolean isLiteralList = true; - - final List<int[]> pieces = new ArrayList<>(); + + List<int[]> pieces = new ArrayList<>(); { - final Matcher m = LIST_SPLIT_PATTERN.matcher(expr); + Matcher matcher = LIST_SPLIT_PATTERN.matcher(expr); int i = 0, j = 0; for (; i >= 0 && i <= expr.length(); i = next(expr, i, context)) { - if (i == expr.length() || m.region(i, expr.length()).lookingAt()) { + if (i == expr.length() || matcher.region(i, expr.length()).lookingAt()) { pieces.add(new int[] {j, i}); if (i == expr.length()) break; - j = i = m.end(); + j = i = matcher.end(); } } if (i != expr.length()) { @@ -793,11 +778,11 @@ public final Expression<?> parseExpression(final ExprInfo vi) { return null; } } - + if (pieces.size() == 1) { // not a list of expressions, and a single one has failed to parse above if (expr.startsWith("(") && expr.endsWith(")") && next(expr, 0, context) == expr.length()) { log.clear(); - return new SkriptParser(this, "" + expr.substring(1, expr.length() - 1)).parseExpression(vi); + return new SkriptParser(this, "" + expr.substring(1, expr.length() - 1)).parseExpression(exprInfo); } if (isObject && (flags & PARSE_LITERALS) != 0) { // single expression - can return an UnparsedLiteral now log.clear(); @@ -811,44 +796,40 @@ public final Expression<?> parseExpression(final ExprInfo vi) { // Early check if this can be parsed as a list. // The only case where multiple expressions are allowed, is when it is an 'or' list - if (!vi.isPlural[0] && !OR_PATTERN.matcher(expr).find()) { + if (!exprInfo.isPlural[0] && !OR_PATTERN.matcher(expr).find()) { log.printError(); return null; } - // `b` is the first piece included, `a` is the last - outer: for (int b = 0; b < pieces.size();) { - for (int a = 1; a <= pieces.size() - b; a++) { - if (b == 0 && a == pieces.size()) // i.e. the whole expression - already tried to parse above + outer: for (int first = 0; first < pieces.size();) { + for (int last = 1; last <= pieces.size() - first; last++) { + if (first == 0 && last == pieces.size()) // i.e. the whole expression - already tried to parse above continue; - final int x = pieces.get(b)[0], y = pieces.get(b + a - 1)[1]; - final String subExpr = "" + expr.substring(x, y).trim(); + int start = pieces.get(first)[0], end = pieces.get(first + last - 1)[1]; + String subExpr = "" + expr.substring(start, end).trim(); assert subExpr.length() < expr.length() : subExpr; - - final Expression<?> t; - - if (subExpr.startsWith("(") && subExpr.endsWith(")") && next(subExpr, 0, context) == subExpr.length()) - t = new SkriptParser(this, subExpr).parseExpression(vi); // only parse as possible expression list if its surrounded by brackets - else - t = new SkriptParser(this, subExpr).parseSingleExpr(a == 1, log.getError(), vi); // otherwise parse as a single expression only - if (t != null) { - isLiteralList &= t instanceof Literal; - ts.add(t); - if (b != 0) { - String delimiter = expr.substring(pieces.get(b - 1)[1], x).trim().toLowerCase(Locale.ENGLISH); + + if (subExpr.startsWith("(") && subExpr.endsWith(")") && next(subExpr, 0, context) == subExpr.length()) { + parsedExpression = new SkriptParser(this, subExpr).parseExpression(exprInfo); // only parse as possible expression list if its surrounded by brackets + } else { + parsedExpression = new SkriptParser(this, subExpr).parseSingleExpr(last == 1, log.getError(), exprInfo); // otherwise parse as a single expression only + } + if (parsedExpression != null) { + isLiteralList &= parsedExpression instanceof Literal; + parsedExpressions.add(parsedExpression); + if (first != 0) { + String delimiter = expr.substring(pieces.get(first - 1)[1], start).trim().toLowerCase(Locale.ENGLISH); if (!delimiter.equals(",")) { boolean or = !delimiter.contains("nor") && delimiter.endsWith("or"); if (and.isUnknown()) { and = Kleenean.get(!or); // nor is and - } else { - if (and != Kleenean.get(!or)) { - Skript.warning(MULTIPLE_AND_OR + " List: " + expr); - and = Kleenean.TRUE; - } + } else if (and == Kleenean.get(or)) { + Skript.warning(MULTIPLE_AND_OR + " List: " + expr); + and = Kleenean.TRUE; } } } - b += a; + first += last; continue outer; } } @@ -859,67 +840,65 @@ public final Expression<?> parseExpression(final ExprInfo vi) { // Check if multiple values are accepted // If not, only 'or' lists are allowed // (both 'and' and potentially 'and' lists will not be accepted) - if (!vi.isPlural[0] && !and.isFalse()) { + if (!exprInfo.isPlural[0] && !and.isFalse()) { // List cannot be used in place of a single value here log.printError(); return null; } log.printLog(false); - - if (ts.size() == 1) { - return ts.get(0); + + if (parsedExpressions.size() == 1) { + return parsedExpressions.get(0); } - + if (and.isUnknown() && !suppressMissingAndOrWarnings) { ParserInstance parser = getParser(); Script currentScript = parser.isActive() ? parser.getCurrentScript() : null; if (currentScript == null || !currentScript.suppressesWarning(ScriptWarning.MISSING_CONJUNCTION)) Skript.warning(MISSING_AND_OR + ": " + expr); } - - final Class<?>[] exprRetTypes = new Class[ts.size()]; - for (int i = 0; i < ts.size(); i++) - exprRetTypes[i] = ts.get(i).getReturnType(); - + + Class<?>[] exprReturnTypes = new Class[parsedExpressions.size()]; + for (int i = 0; i < parsedExpressions.size(); i++) + exprReturnTypes[i] = parsedExpressions.get(i).getReturnType(); + if (isLiteralList) { - final Literal<?>[] ls = ts.toArray(new Literal[ts.size()]); - assert ls != null; - return new LiteralList(ls, Classes.getSuperClassInfo(exprRetTypes).getC(), !and.isFalse()); + Literal<?>[] literals = parsedExpressions.toArray(new Literal[parsedExpressions.size()]); + return new LiteralList(literals, Classes.getSuperClassInfo(exprReturnTypes).getC(), !and.isFalse()); } else { - final Expression<?>[] es = ts.toArray(new Expression[ts.size()]); - assert es != null; - return new ExpressionList(es, Classes.getSuperClassInfo(exprRetTypes).getC(), !and.isFalse()); + Expression<?>[] expressions = parsedExpressions.toArray(new Expression[parsedExpressions.size()]); + return new ExpressionList(expressions, Classes.getSuperClassInfo(exprReturnTypes).getC(), !and.isFalse()); + } } finally { log.stop(); } } - - @SuppressWarnings("null") - private final static Pattern functionCallPattern = Pattern.compile("(" + Functions.functionNamePattern + ")\\((.*)\\)"); - + + private final static Pattern FUNCTION_CALL_PATTERN = Pattern.compile("(" + Functions.functionNamePattern + ")\\((.*)\\)"); + /** * @param types The required return type or null if it is not used (e.g. when calling a void function) * @return The parsed function, or null if the given expression is not a function call or is an invalid function call (check for an error to differentiate these two) */ - @SuppressWarnings("unchecked") @Nullable - public final <T> FunctionReference<T> parseFunction(final @Nullable Class<? extends T>... types) { + @SuppressWarnings("unchecked") + public <T> FunctionReference<T> parseFunction(@Nullable Class<? extends T>... types) { if (context != ParseContext.DEFAULT && context != ParseContext.EVENT) return null; - final ParseLogHandler log = SkriptLogger.startParseLogHandler(); + ParseLogHandler log = SkriptLogger.startParseLogHandler(); try { - final Matcher m = functionCallPattern.matcher(expr); - if (!m.matches()) { + Matcher matcher = FUNCTION_CALL_PATTERN.matcher(expr); + if (!matcher.matches()) { log.printLog(); return null; } - String functionName = "" + m.group(1); - String args = m.group(2); + String functionName = "" + matcher.group(1); + String args = matcher.group(2); Expression<?>[] params; - + // Check for incorrect quotes, e.g. "myFunction() + otherFunction()" being parsed as one function // See https://github.com/SkriptLang/Skript/issues/1532 for (int i = 0; i < args.length(); i = next(args, i, context)) { @@ -928,101 +907,83 @@ public final <T> FunctionReference<T> parseFunction(final @Nullable Class<? exte return null; } } - + if ((flags & PARSE_EXPRESSIONS) == 0) { Skript.error("Functions cannot be used here (or there is a problem with your arguments)."); log.printError(); return null; } - + if (args.length() != 0) { - final Expression<?> ps = new SkriptParser(args, flags | PARSE_LITERALS, context).suppressMissingAndOrWarnings().parseExpression(Object.class); - if (ps == null) { + Expression<?> parsedExpression = new SkriptParser(args, flags | PARSE_LITERALS, context).suppressMissingAndOrWarnings().parseExpression(Object.class); + if (parsedExpression == null) { log.printError(); return null; } - if (ps instanceof ExpressionList) { - if (!ps.getAnd()) { + if (parsedExpression instanceof ExpressionList) { + if (!parsedExpression.getAnd()) { Skript.error("Function arguments must be separated by commas and optionally an 'and', but not an 'or'." + " Put the 'or' into a second set of parentheses if you want to make it a single parameter, e.g. 'give(player, (sword or axe))'"); log.printError(); return null; } - params = ((ExpressionList<?>) ps).getExpressions(); + params = ((ExpressionList<?>) parsedExpression).getExpressions(); } else { - params = new Expression[] {ps}; + params = new Expression[] {parsedExpression}; } } else { params = new Expression[0]; } -// final List<Expression<?>> params = new ArrayList<Expression<?>>(); -// if (args.length() != 0) { -// final int p = 0; -// int j = 0; -// for (int i = 0; i != -1 && i <= args.length(); i = next(args, i, context)) { -// if (i == args.length() || args.charAt(i) == ',') { -// final Expression<?> e = new SkriptParser("" + args.substring(j, i).trim(), flags | PARSE_LITERALS, context).parseExpression(function.getParameter(p).getType().getC()); -// if (e == null) { -// log.printError("Can't understand this expression: '" + args.substring(j, i) + "'", ErrorQuality.NOT_AN_EXPRESSION); -// return null; -// } -// params.add(e); -// j = i + 1; -// } -// } -// } -// @SuppressWarnings("null") - ParserInstance parser = getParser(); Script currentScript = parser.isActive() ? parser.getCurrentScript() : null; - final FunctionReference<T> e = new FunctionReference<>(functionName, SkriptLogger.getNode(), + FunctionReference<T> functionReference = new FunctionReference<>(functionName, SkriptLogger.getNode(), currentScript != null ? currentScript.getConfig().getFileName() : null, types, params);//.toArray(new Expression[params.size()])); - if (!e.validateFunction(true)) { + if (!functionReference.validateFunction(true)) { log.printError(); return null; } log.printLog(); - return e; + return functionReference; } finally { log.stop(); } } - + /** * Prints parse errors (i.e. must start a ParseLog before calling this method) */ - public static boolean parseArguments(final String args, final ScriptCommand command, final ScriptCommandEvent event) { - final SkriptParser parser = new SkriptParser(args, PARSE_LITERALS, ParseContext.COMMAND); - final ParseResult res = parser.parse_i(command.getPattern(), 0, 0); - if (res == null) + public static boolean parseArguments(String args, ScriptCommand command, ScriptCommandEvent event) { + SkriptParser parser = new SkriptParser(args, PARSE_LITERALS, ParseContext.COMMAND); + ParseResult parseResult = parser.parse_i(command.getPattern()); + if (parseResult == null) return false; - - final List<Argument<?>> as = command.getArguments(); - assert as.size() == res.exprs.length; - for (int i = 0; i < res.exprs.length; i++) { - if (res.exprs[i] == null) - as.get(i).setToDefault(event); + + List<Argument<?>> arguments = command.getArguments(); + assert arguments.size() == parseResult.exprs.length; + for (int i = 0; i < parseResult.exprs.length; i++) { + if (parseResult.exprs[i] == null) + arguments.get(i).setToDefault(event); else - as.get(i).set(event, res.exprs[i].getArray(event)); + arguments.get(i).set(event, parseResult.exprs[i].getArray(event)); } return true; } - + /** * Parses the text as the given pattern as {@link ParseContext#COMMAND}. * <p> * Prints parse errors (i.e. must start a ParseLog before calling this method) */ @Nullable - public static ParseResult parse(final String text, final String pattern) { - return new SkriptParser(text, PARSE_LITERALS, ParseContext.COMMAND).parse_i(pattern, 0, 0); + public static ParseResult parse(String text, String pattern) { + return new SkriptParser(text, PARSE_LITERALS, ParseContext.COMMAND).parse_i(pattern); } - + /** * Finds the closing bracket of the group at <tt>start</tt> (i.e. <tt>start</tt> has to be <i>in</i> a group). * - * @param pattern + * @param pattern The string to search in * @param closingBracket The bracket to look for, e.g. ')' * @param openingBracket A bracket that opens another group, e.g. '(' * @param start This must not be the index of the opening bracket! @@ -1030,138 +991,149 @@ public static ParseResult parse(final String text, final String pattern) { * @return The index of the next bracket * @throws MalformedPatternException If the group is not closed */ - public static int nextBracket(final String pattern, final char closingBracket, final char openingBracket, final int start, final boolean isGroup) throws MalformedPatternException { - int n = 0; + public static int nextBracket(String pattern, char closingBracket, char openingBracket, int start, boolean isGroup) throws MalformedPatternException { + int index = 0; for (int i = start; i < pattern.length(); i++) { if (pattern.charAt(i) == '\\') { i++; - continue; } else if (pattern.charAt(i) == closingBracket) { - if (n == 0) { + if (index == 0) { if (!isGroup) throw new MalformedPatternException(pattern, "Unexpected closing bracket '" + closingBracket + "'"); return i; } - n--; + index--; } else if (pattern.charAt(i) == openingBracket) { - n++; + index++; } } if (isGroup) throw new MalformedPatternException(pattern, "Missing closing bracket '" + closingBracket + "'"); return -1; } - + /** * Gets the next occurrence of a character in a string that is not escaped with a preceding backslash. * - * @param pattern - * @param c The character to search for + * @param pattern The string to search in + * @param character The character to search for * @param from The index to start searching from * @return The next index where the character occurs unescaped or -1 if it doesn't occur. */ - private static int nextUnescaped(final String pattern, final char c, final int from) { + private static int nextUnescaped(String pattern, char character, int from) { for (int i = from; i < pattern.length(); i++) { if (pattern.charAt(i) == '\\') { i++; - } else if (pattern.charAt(i) == c) { + } else if (pattern.charAt(i) == character) { return i; } } return -1; } - + /** * Counts how often the given character occurs in the given string, ignoring any escaped occurrences of the character. * - * @param pattern - * @param c The character to search for + * @param haystack The string to search in + * @param needle The character to search for * @return The number of unescaped occurrences of the given character */ - static int countUnescaped(final String pattern, final char c) { - return countUnescaped(pattern, c, 0, pattern.length()); + static int countUnescaped(String haystack, char needle) { + return countUnescaped(haystack, needle, 0, haystack.length()); } - - static int countUnescaped(final String pattern, final char c, final int start, final int end) { - assert start >= 0 && start <= end && end <= pattern.length() : start + ", " + end + "; " + pattern.length(); - int r = 0; + + /** + * Counts how often the given character occurs between the given indices in the given string, + * ignoring any escaped occurrences of the character. + * + * @param haystack The string to search in + * @param needle The character to search for + * @param start The index to start searching from (inclusive) + * @param end The index to stop searching at (exclusive) + * @return The number of unescaped occurrences of the given character + */ + static int countUnescaped(String haystack, char needle, int start, int end) { + assert start >= 0 && start <= end && end <= haystack.length() : start + ", " + end + "; " + haystack.length(); + int count = 0; for (int i = start; i < end; i++) { - final char x = pattern.charAt(i); - if (x == '\\') { + char character = haystack.charAt(i); + if (character == '\\') { i++; - } else if (x == c) { - r++; + } else if (character == needle) { + count++; } } - return r; + return count; } - + /** * Find the next unescaped (i.e. single) double quote in the string. * - * @param s - * @param from Index after the starting quote + * @param string The string to search in + * @param start Index after the starting quote * @return Index of the end quote */ - private static int nextQuote(final String s, final int from) { + private static int nextQuote(String string, int start) { boolean inExpression = false; - for (int i = from; i < s.length(); i++) { - char c = s.charAt(i); - if (c == '"' && !inExpression) { - if (i == s.length() - 1 || s.charAt(i + 1) != '"') + for (int i = start; i < string.length(); i++) { + char character = string.charAt(i); + if (character == '"' && !inExpression) { + if (i == string.length() - 1 || string.charAt(i + 1) != '"') return i; i++; - } else if (c == '%') { + } else if (character == '%') { inExpression = !inExpression; } } return -1; } - + /** - * @param cs + * @param types The types to include in the message * @return "not an x" or "neither an x, a y nor a z" */ - public static String notOfType(final Class<?>... cs) { - if (cs.length == 1) { - final Class<?> c = cs[0]; - assert c != null; - return Language.get("not") + " " + Classes.getSuperClassInfo(c).getName().withIndefiniteArticle(); + public static String notOfType(Class<?>... types) { + if (types.length == 1) { + Class<?> type = types[0]; + assert type != null; + return Language.get("not") + " " + Classes.getSuperClassInfo(type).getName().withIndefiniteArticle(); } else { - final StringBuilder b = new StringBuilder(Language.get("neither") + " "); - for (int k = 0; k < cs.length; k++) { - if (k != 0) { - if (k != cs.length - 1) - b.append(", "); - else - b.append(" " + Language.get("nor") + " "); + StringBuilder message = new StringBuilder(Language.get("neither") + " "); + for (int i = 0; i < types.length; i++) { + if (i != 0) { + if (i != types.length - 1) { + message.append(", "); + } else { + message.append(" ").append(Language.get("nor")).append(" "); + } } - final Class<?> c = cs[k]; + Class<?> c = types[i]; assert c != null; - b.append(Classes.getSuperClassInfo(c).getName().withIndefiniteArticle()); + message.append(Classes.getSuperClassInfo(c).getName().withIndefiniteArticle()); } - return "" + b.toString(); + return message.toString(); } } - - public static String notOfType(final ClassInfo<?>... cs) { - if (cs.length == 1) { - return Language.get("not") + " " + cs[0].getName().withIndefiniteArticle(); + + public static String notOfType(ClassInfo<?>... types) { + if (types.length == 1) { + return Language.get("not") + " " + types[0].getName().withIndefiniteArticle(); } else { - final StringBuilder b = new StringBuilder(Language.get("neither") + " "); - for (int k = 0; k < cs.length; k++) { - if (k != 0) { - if (k != cs.length - 1) - b.append(", "); - else - b.append(" " + Language.get("nor") + " "); + StringBuilder message = new StringBuilder(Language.get("neither") + " "); + for (int i = 0; i < types.length; i++) { + if (i != 0) { + if (i != types.length - 1) { + message.append(", "); + } else { + message.append(" ").append(Language.get("nor")).append(" "); + } } - b.append(cs[k].getName().withIndefiniteArticle()); + message.append(types[i].getName().withIndefiniteArticle()); } - return "" + b.toString(); + return message.toString(); } } - + /** * Returns the next character in the expression, skipping strings, * variables and parentheses @@ -1185,18 +1157,18 @@ public static int next(String expr, int startIndex, ParseContext context) { if (context == ParseContext.COMMAND || context == ParseContext.PARSE) return startIndex + 1; - int j; + int index; switch (expr.charAt(startIndex)) { case '"': - j = nextQuote(expr, startIndex + 1); - return j < 0 ? -1 : j + 1; + index = nextQuote(expr, startIndex + 1); + return index < 0 ? -1 : index + 1; case '{': - j = VariableString.nextVariableBracket(expr, startIndex + 1); - return j < 0 ? -1 : j + 1; + index = VariableString.nextVariableBracket(expr, startIndex + 1); + return index < 0 ? -1 : index + 1; case '(': - for (j = startIndex + 1; j >= 0 && j < exprLength; j = next(expr, j, context)) { - if (expr.charAt(j) == ')') - return j + 1; + for (index = startIndex + 1; index >= 0 && index < exprLength; index = next(expr, index, context)) { + if (expr.charAt(index) == ')') + return index + 1; } return -1; default: @@ -1238,14 +1210,14 @@ public static int nextOccurrence(String haystack, String needle, int startIndex, while (startIndex < haystackLength) { - char c = haystack.charAt(startIndex); + char character = haystack.charAt(startIndex); if (startsWithSpecialChar) { // Early check before special character handling if (haystack.startsWith(needle, startIndex)) return startIndex; } - switch (c) { + switch (character) { case '"': startIndex = nextQuote(haystack, startIndex + 1); if (startIndex < 0) @@ -1275,9 +1247,7 @@ public static int nextOccurrence(String haystack, String needle, int startIndex, private static final Map<String, SkriptPattern> patterns = new ConcurrentHashMap<>(); @Nullable - private ParseResult parse_i(String pattern, int i, int j) { - if (i != 0 || j != 0) - throw new IllegalArgumentException(); + private ParseResult parse_i(String pattern) { SkriptPattern skriptPattern = patterns.computeIfAbsent(pattern, PatternCompiler::compile); ch.njol.skript.patterns.MatchResult matchResult = skriptPattern.match(expr, flags, context); if (matchResult == null) @@ -1288,59 +1258,59 @@ private ParseResult parse_i(String pattern, int i, int j) { /** * Validates a user-defined pattern (used in {@link ExprParse}). * - * @param pattern + * @param pattern The pattern string to validate * @return The pattern with %codenames% and a boolean array that contains whether the expressions are plural or not */ @Nullable - public static NonNullPair<String, NonNullPair<ClassInfo<?>, Boolean>[]> validatePattern(final String pattern) { - final List<NonNullPair<ClassInfo<?>, Boolean>> pairs = new ArrayList<>(); + public static NonNullPair<String, NonNullPair<ClassInfo<?>, Boolean>[]> validatePattern(String pattern) { + List<NonNullPair<ClassInfo<?>, Boolean>> pairs = new ArrayList<>(); int groupLevel = 0, optionalLevel = 0; - final Deque<Character> groups = new LinkedList<>(); - final StringBuilder stringBuilder = new StringBuilder(pattern.length()); + Deque<Character> groups = new LinkedList<>(); + StringBuilder stringBuilder = new StringBuilder(pattern.length()); int last = 0; for (int i = 0; i < pattern.length(); i++) { - final char c = pattern.charAt(i); - if (c == '(') { + char character = pattern.charAt(i); + if (character == '(') { groupLevel++; - groups.addLast(c); - } else if (c == '|') { + groups.addLast(character); + } else if (character == '|') { if (groupLevel == 0 || groups.peekLast() != '(' && groups.peekLast() != '|') return error("Cannot use the pipe character '|' outside of groups. Escape it if you want to match a literal pipe: '\\|'"); groups.removeLast(); - groups.addLast(c); - } else if (c == ')') { + groups.addLast(character); + } else if (character == ')') { if (groupLevel == 0 || groups.peekLast() != '(' && groups.peekLast() != '|') return error("Unexpected closing group bracket ')'. Escape it if you want to match a literal bracket: '\\)'"); if (groups.peekLast() == '(') return error("(...|...) groups have to contain at least one pipe character '|' to separate it into parts. Escape the brackets if you want to match literal brackets: \"\\(not a group\\)\""); groupLevel--; groups.removeLast(); - } else if (c == '[') { + } else if (character == '[') { optionalLevel++; - groups.addLast(c); - } else if (c == ']') { + groups.addLast(character); + } else if (character == ']') { if (optionalLevel == 0 || groups.peekLast() != '[') return error("Unexpected closing optional bracket ']'. Escape it if you want to match a literal bracket: '\\]'"); optionalLevel--; groups.removeLast(); - } else if (c == '<') { - final int j = pattern.indexOf('>', i + 1); + } else if (character == '<') { + int j = pattern.indexOf('>', i + 1); if (j == -1) return error("Missing closing regex bracket '>'. Escape the '<' if you want to match a literal bracket: '\\<'"); try { Pattern.compile(pattern.substring(i + 1, j)); - } catch (final PatternSyntaxException e) { + } catch (PatternSyntaxException e) { return error("Invalid Regular Expression '" + pattern.substring(i + 1, j) + "': " + e.getLocalizedMessage()); } i = j; - } else if (c == '>') { + } else if (character == '>') { return error("Unexpected closing regex bracket '>'. Escape it if you want to match a literal bracket: '\\>'"); - } else if (c == '%') { - final int j = pattern.indexOf('%', i + 1); + } else if (character == '%') { + int j = pattern.indexOf('%', i + 1); if (j == -1) return error("Missing end sign '%' of expression. Escape the percent sign to match a literal '%': '\\%'"); - final NonNullPair<String, Boolean> pair = Utils.getEnglishPlural("" + pattern.substring(i + 1, j)); - final ClassInfo<?> classInfo = Classes.getClassInfoFromUserInput(pair.getFirst()); + NonNullPair<String, Boolean> pair = Utils.getEnglishPlural("" + pattern.substring(i + 1, j)); + ClassInfo<?> classInfo = Classes.getClassInfoFromUserInput(pair.getFirst()); if (classInfo == null) return error("The type '" + pair.getFirst() + "' could not be found. Please check your spelling or escape the percent signs if you want to match literal %s: \"\\%not an expression\\%\""); pairs.add(new NonNullPair<>(classInfo, pair.getSecond())); @@ -1348,7 +1318,7 @@ public static NonNullPair<String, NonNullPair<ClassInfo<?>, Boolean>[]> validate stringBuilder.append(Utils.toEnglishPlural(classInfo.getCodeName(), pair.getSecond())); last = j; i = j; - } else if (c == '\\') { + } else if (character == '\\') { if (i == pattern.length() - 1) return error("Pattern must not end in an unescaped backslash. Add another backslash to escape it, or remove it altogether."); i++; @@ -1358,87 +1328,87 @@ public static NonNullPair<String, NonNullPair<ClassInfo<?>, Boolean>[]> validate //noinspection unchecked return new NonNullPair<>(stringBuilder.toString(), pairs.toArray(new NonNullPair[0])); } - + @Nullable private static NonNullPair<String, NonNullPair<ClassInfo<?>, Boolean>[]> error(final String error) { Skript.error("Invalid pattern: " + error); return null; } - - private final static Message m_quotes_error = new Message("skript.quotes error"); - private final static Message m_brackets_error = new Message("skript.brackets error"); - - public static boolean validateLine(final String line) { + + private final static Message M_QUOTES_ERROR = new Message("skript.quotes error"); + private final static Message M_BRACKETS_ERROR = new Message("skript.brackets error"); + + public static boolean validateLine(String line) { if (StringUtils.count(line, '"') % 2 != 0) { - Skript.error(m_quotes_error.toString()); + Skript.error(M_QUOTES_ERROR.toString()); return false; } for (int i = 0; i < line.length(); i = next(line, i, ParseContext.DEFAULT)) { if (i == -1) { - Skript.error(m_brackets_error.toString()); + Skript.error(M_BRACKETS_ERROR.toString()); return false; } } return true; } - + public static class ExprInfo { - public ExprInfo(final int length) { + public ExprInfo(int length) { classes = new ClassInfo[length]; isPlural = new boolean[length]; } - + public final ClassInfo<?>[] classes; public final boolean[] isPlural; public boolean isOptional; public int flagMask = ~0; public int time = 0; } - + private static final Map<String,ExprInfo> exprInfoCache = new HashMap<>(); - - private static ExprInfo getExprInfo(String s) throws IllegalArgumentException, SkriptAPIException { - ExprInfo r = exprInfoCache.get(s); - if (r == null) { - r = createExprInfo(s); - exprInfoCache.put(s, r); + + private static ExprInfo getExprInfo(String string) throws IllegalArgumentException, SkriptAPIException { + ExprInfo exprInfo = exprInfoCache.get(string); + if (exprInfo == null) { + exprInfo = createExprInfo(string); + exprInfoCache.put(string, exprInfo); } - - return r; + + return exprInfo; } - - private static ExprInfo createExprInfo(String s) throws IllegalArgumentException, SkriptAPIException { - final ExprInfo r = new ExprInfo(StringUtils.count(s, '/') + 1); - r.isOptional = s.startsWith("-"); - if (r.isOptional) - s = "" + s.substring(1); - if (s.startsWith("*")) { - s = "" + s.substring(1); - r.flagMask &= ~PARSE_EXPRESSIONS; - } else if (s.startsWith("~")) { - s = "" + s.substring(1); - r.flagMask &= ~PARSE_LITERALS; + + private static ExprInfo createExprInfo(String string) throws IllegalArgumentException, SkriptAPIException { + ExprInfo exprInfo = new ExprInfo(StringUtils.count(string, '/') + 1); + exprInfo.isOptional = string.startsWith("-"); + if (exprInfo.isOptional) + string = string.substring(1); + if (string.startsWith("*")) { + string = string.substring(1); + exprInfo.flagMask &= ~PARSE_EXPRESSIONS; + } else if (string.startsWith("~")) { + string = string.substring(1); + exprInfo.flagMask &= ~PARSE_LITERALS; } - if (!r.isOptional) { - r.isOptional = s.startsWith("-"); - if (r.isOptional) - s = "" + s.substring(1); + if (!exprInfo.isOptional) { + exprInfo.isOptional = string.startsWith("-"); + if (exprInfo.isOptional) + string = "" + string.substring(1); } - final int a = s.indexOf("@"); - if (a != -1) { - r.time = Integer.parseInt(s.substring(a + 1)); - s = "" + s.substring(0, a); + int atSign = string.indexOf("@"); + if (atSign != -1) { + exprInfo.time = Integer.parseInt(string.substring(atSign + 1)); + string = "" + string.substring(0, atSign); } - final String[] classes = s.split("/"); - assert classes.length == r.classes.length; + String[] classes = string.split("/"); + assert classes.length == exprInfo.classes.length; for (int i = 0; i < classes.length; i++) { - final NonNullPair<String, Boolean> p = Utils.getEnglishPlural("" + classes[i]); - r.classes[i] = Classes.getClassInfo(p.getFirst()); - r.isPlural[i] = p.getSecond(); + NonNullPair<String, Boolean> plural = Utils.getEnglishPlural("" + classes[i]); + exprInfo.classes[i] = Classes.getClassInfo(plural.getFirst()); + exprInfo.isPlural[i] = plural.getSecond(); } - return r; + return exprInfo; } - + /** * @see ParserInstance#get() */ @@ -1452,5 +1422,12 @@ private static ParserInstance getParser() { */ @Deprecated public final static Pattern listSplitPattern = LIST_SPLIT_PATTERN; - + + /** + * @deprecated due to bad naming conventions, + * use {@link #WILDCARD} instead. + */ + @Deprecated + public final static String wildcard = WILDCARD; + } diff --git a/src/main/java/ch/njol/skript/lang/Statement.java b/src/main/java/ch/njol/skript/lang/Statement.java index 5dba4c70ed9..48f41e3a795 100644 --- a/src/main/java/ch/njol/skript/lang/Statement.java +++ b/src/main/java/ch/njol/skript/lang/Statement.java @@ -18,14 +18,13 @@ */ package ch.njol.skript.lang; -import java.util.Iterator; - -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.lang.function.EffFunctionCall; import ch.njol.skript.log.ParseLogHandler; import ch.njol.skript.log.SkriptLogger; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.Iterator; /** * Supertype of conditions and effects @@ -35,29 +34,29 @@ */ public abstract class Statement extends TriggerItem implements SyntaxElement { - @SuppressWarnings({"rawtypes", "unchecked", "null"}) @Nullable - public static Statement parse(String s, String defaultError) { + @SuppressWarnings({"rawtypes", "unchecked"}) + public static Statement parse(String input, String defaultError) { ParseLogHandler log = SkriptLogger.startParseLogHandler(); try { - EffFunctionCall f = EffFunctionCall.parse(s); - if (f != null) { + EffFunctionCall functionCall = EffFunctionCall.parse(input); + if (functionCall != null) { log.printLog(); - return f; + return functionCall; } else if (log.hasError()) { log.printError(); return null; } log.clear(); - EffectSection section = EffectSection.parse(s, null, null, null); + EffectSection section = EffectSection.parse(input, null, null, null); if (section != null) { log.printLog(); return new EffectSectionEffect(section); } log.clear(); - Statement statement = (Statement) SkriptParser.parse(s, (Iterator) Skript.getStatements().iterator(), defaultError); + Statement statement = (Statement) SkriptParser.parse(input, (Iterator) Skript.getStatements().iterator(), defaultError); if (statement != null) { log.printLog(); return statement; diff --git a/src/main/java/ch/njol/skript/lang/SyntaxElement.java b/src/main/java/ch/njol/skript/lang/SyntaxElement.java index 40e507323e0..5ab3589733c 100644 --- a/src/main/java/ch/njol/skript/lang/SyntaxElement.java +++ b/src/main/java/ch/njol/skript/lang/SyntaxElement.java @@ -30,15 +30,15 @@ public interface SyntaxElement { /** * Called just after the constructor. * - * @param exprs all %expr%s included in the matching pattern in the order they appear in the pattern. If an optional value was left out it will still be included in this list - * holding the default value of the desired type which usually depends on the event. + * @param expressions all %expr%s included in the matching pattern in the order they appear in the pattern. If an optional value was left out, it will still be included in this list + * holding the default value of the desired type, which usually depends on the event. * @param matchedPattern The index of the pattern which matched * @param isDelayed Whether this expression is used after a delay or not (i.e. if the event has already passed when this expression will be called) * @param parseResult Additional information about the match. * @return Whether this expression was initialised successfully. An error should be printed prior to returning false to specify the cause. * @see ParserInstance#isCurrentEvent(Class...) */ - boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult); + boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult); /** * @see ParserInstance#get() diff --git a/src/main/java/ch/njol/skript/lang/SyntaxElementInfo.java b/src/main/java/ch/njol/skript/lang/SyntaxElementInfo.java index 48920962fc8..fe280fef471 100644 --- a/src/main/java/ch/njol/skript/lang/SyntaxElementInfo.java +++ b/src/main/java/ch/njol/skript/lang/SyntaxElementInfo.java @@ -25,35 +25,34 @@ * @param <E> the syntax element this info is for */ public class SyntaxElementInfo<E extends SyntaxElement> { - - public final Class<E> c; + + // todo: 2.9 make all fields private + public final Class<E> elementClass; public final String[] patterns; public final String originClassPath; - - public SyntaxElementInfo(final String[] patterns, final Class<E> c, final String originClassPath) throws IllegalArgumentException { + + public SyntaxElementInfo(String[] patterns, Class<E> elementClass, String originClassPath) throws IllegalArgumentException { this.patterns = patterns; - this.c = c; + this.elementClass = elementClass; this.originClassPath = originClassPath; try { - c.getConstructor(); -// if (!c.getDeclaredConstructor().isAccessible()) -// throw new IllegalArgumentException("The nullary constructor of class "+c.getName()+" is not public"); + elementClass.getConstructor(); } catch (final NoSuchMethodException e) { // throwing an Exception throws an (empty) ExceptionInInitializerError instead, thus an Error is used - throw new Error(c + " does not have a public nullary constructor", e); + throw new Error(elementClass + " does not have a public nullary constructor", e); } catch (final SecurityException e) { throw new IllegalStateException("Skript cannot run properly because a security manager is blocking it!"); } } - + /** * Get the class that represents this element. * @return The Class of the element */ public Class<E> getElementClass() { - return c; + return elementClass; } - + /** * Get the patterns of this syntax element. * @return Array of Skript patterns for this element @@ -61,7 +60,7 @@ public Class<E> getElementClass() { public String[] getPatterns() { return Arrays.copyOf(patterns, patterns.length); } - + /** * Get the original classpath for this element. * @return The original ClassPath for this element diff --git a/src/main/java/ch/njol/skript/lang/Trigger.java b/src/main/java/ch/njol/skript/lang/Trigger.java index 50099e1386f..b9092703247 100644 --- a/src/main/java/ch/njol/skript/lang/Trigger.java +++ b/src/main/java/ch/njol/skript/lang/Trigger.java @@ -26,15 +26,15 @@ import java.util.List; public class Trigger extends TriggerSection { - + private final String name; private final SkriptEvent event; - + @Nullable private final Script script; private int line = -1; // -1 is default: it means there is no line number available private String debugLabel; - + public Trigger(@Nullable Script script, String name, SkriptEvent event, List<TriggerItem> items) { super(items); this.script = script; @@ -68,25 +68,25 @@ public boolean execute(Event event) { return success; } - + @Override @Nullable - protected TriggerItem walk(final Event e) { - return walk(e, true); + protected TriggerItem walk(Event event) { + return walk(event, true); } - + @Override - public String toString(final @Nullable Event e, final boolean debug) { - return name + " (" + event.toString(e, debug) + ")"; + public String toString(@Nullable Event event, boolean debug) { + return name + " (" + this.event.toString(event, debug) + ")"; } - + /** * @return The name of this trigger. */ public String getName() { return name; } - + public SkriptEvent getEvent() { return event; } @@ -107,20 +107,20 @@ public Script getScript() { public void setLineNumber(int line) { this.line = line; } - + /** * @return The line number where this trigger starts. This should ONLY be used for debugging! */ public int getLineNumber() { return line; } - + public void setDebugLabel(String label) { this.debugLabel = label; } - + public String getDebugLabel() { return debugLabel; } - + } diff --git a/src/main/java/ch/njol/skript/lang/TriggerItem.java b/src/main/java/ch/njol/skript/lang/TriggerItem.java index 6322cb63524..ce4965c64f4 100644 --- a/src/main/java/ch/njol/skript/lang/TriggerItem.java +++ b/src/main/java/ch/njol/skript/lang/TriggerItem.java @@ -18,15 +18,14 @@ */ package ch.njol.skript.lang; -import java.io.File; - -import org.skriptlang.skript.lang.script.Script; +import ch.njol.skript.Skript; import ch.njol.skript.util.SkriptColor; +import ch.njol.util.StringUtils; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.script.Script; -import ch.njol.skript.Skript; -import ch.njol.util.StringUtils; +import java.io.File; /** * Represents a trigger item, i.e. a trigger section, a condition or an effect. @@ -37,64 +36,63 @@ * @see Statement */ public abstract class TriggerItem implements Debuggable { - + @Nullable protected TriggerSection parent = null; @Nullable private TriggerItem next = null; - + protected TriggerItem() {} - - protected TriggerItem(final TriggerSection parent) { + + protected TriggerItem(TriggerSection parent) { this.parent = parent; } - + /** * Executes this item and returns the next item to run. * <p> * Overriding classes must call {@link #debug(Event, boolean)}. If this method is overridden, {@link #run(Event)} is not used anymore and can be ignored. * - * @param e + * @param event The event * @return The next item to run or null to stop execution */ @Nullable - protected TriggerItem walk(final Event e) { - if (run(e)) { - debug(e, true); + protected TriggerItem walk(Event event) { + if (run(event)) { + debug(event, true); return next; } else { - debug(e, false); - final TriggerSection parent = this.parent; + debug(event, false); + TriggerSection parent = this.parent; return parent == null ? null : parent.getNext(); } } - + /** * Executes this item. * - * @param e + * @param event The event to run this item with * @return True if the next item should be run, or false for the item following this item's parent. */ - protected abstract boolean run(Event e); - + protected abstract boolean run(Event event); + /** - * @param start - * @param e + * @param start The item to start at + * @param event The event to run the items with * @return false if an exception occurred */ - public static boolean walk(final TriggerItem start, final Event e) { - assert start != null && e != null; - TriggerItem i = start; + public static boolean walk(TriggerItem start, Event event) { + TriggerItem triggerItem = start; try { - while (i != null) - i = i.walk(e); - + while (triggerItem != null) + triggerItem = triggerItem.walk(event); + return true; - } catch (final StackOverflowError err) { - Trigger t = start.getTrigger(); + } catch (StackOverflowError err) { + Trigger trigger = start.getTrigger(); String scriptName = "<unknown>"; - if (t != null) { - Script script = t.getScript(); + if (trigger != null) { + Script script = trigger.getScript(); if (script != null) { File scriptFile = script.getConfig().getFile(); if (scriptFile != null) @@ -104,9 +102,9 @@ public static boolean walk(final TriggerItem start, final Event e) { Skript.adminBroadcast("<red>The script '<gold>" + scriptName + "<red>' infinitely (or excessively) repeated itself!"); if (Skript.debug()) err.printStackTrace(); - } catch (final Exception ex) { + } catch (Exception ex) { if (ex.getStackTrace().length != 0) // empty exceptions have already been printed - Skript.exception(ex, i); + Skript.exception(ex, triggerItem); } catch (Throwable throwable) { // not all Throwables are Exceptions, but we usually don't want to catch them (without rethrowing) Skript.markErrored(); @@ -114,69 +112,66 @@ public static boolean walk(final TriggerItem start, final Event e) { } return false; } - + /** * how much to indent each level */ - private final static String indent = " "; - + private final static String INDENT = " "; + @Nullable private String indentation = null; - + public String getIndentation() { - String ind = indentation; - if (ind == null) { + if (indentation == null) { int level = 0; - TriggerItem i = this; - while ((i = i.parent) != null) + TriggerItem triggerItem = this; + while ((triggerItem = triggerItem.parent) != null) level++; - indentation = ind = StringUtils.multiply(indent, level); + indentation = StringUtils.multiply(INDENT, level); } - return ind; + return indentation; } - - protected final void debug(final Event e, final boolean run) { + + protected final void debug(Event event, boolean run) { if (!Skript.debug()) return; - Skript.debug(SkriptColor.replaceColorChar(getIndentation() + (run ? "" : "-") + toString(e, true))); + Skript.debug(SkriptColor.replaceColorChar(getIndentation() + (run ? "" : "-") + toString(event, true))); } - + @Override public final String toString() { return toString(null, false); } - - public TriggerItem setParent(final @Nullable TriggerSection parent) { + + public TriggerItem setParent(@Nullable TriggerSection parent) { this.parent = parent; return this; } - + @Nullable public final TriggerSection getParent() { return parent; } - + /** * @return The trigger this item belongs to, or null if this is a stand-alone item (e.g. the effect of an effect command) */ @Nullable public final Trigger getTrigger() { - TriggerItem i = this; - while (i != null && !(i instanceof Trigger)) - i = i.getParent(); -// if (i == null) -// throw new IllegalStateException("TriggerItem without a Trigger detected!"); - return (Trigger) i; + TriggerItem triggerItem = this; + while (triggerItem != null && !(triggerItem instanceof Trigger)) + triggerItem = triggerItem.getParent(); + return (Trigger) triggerItem; } - - public TriggerItem setNext(final @Nullable TriggerItem next) { + + public TriggerItem setNext(@Nullable TriggerItem next) { this.next = next; return this; } - + @Nullable public TriggerItem getNext() { return next; } - + } diff --git a/src/main/java/ch/njol/skript/lang/TriggerSection.java b/src/main/java/ch/njol/skript/lang/TriggerSection.java index fb925f25c3c..d1343d43605 100644 --- a/src/main/java/ch/njol/skript/lang/TriggerSection.java +++ b/src/main/java/ch/njol/skript/lang/TriggerSection.java @@ -18,32 +18,29 @@ */ package ch.njol.skript.lang; -import java.util.List; - -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.ScriptLoader; import ch.njol.skript.config.SectionNode; import ch.njol.skript.lang.parser.ParserInstance; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.List; /** * Represents a section of a trigger, e.g. a conditional or a loop */ public abstract class TriggerSection extends TriggerItem { - - @Nullable - protected TriggerItem first = null; + @Nullable - protected TriggerItem last = null; - + protected TriggerItem first, last; + /** * Reserved for new Trigger(...) */ protected TriggerSection(List<TriggerItem> items) { setTriggerItems(items); } - + protected TriggerSection(SectionNode node) { List<TriggerSection> currentSections = ParserInstance.get().getCurrentSections(); currentSections.add(this); @@ -53,12 +50,12 @@ protected TriggerSection(SectionNode node) { currentSections.remove(currentSections.size() - 1); } } - + /** * Important when using this constructor: set the items with {@link #setTriggerItems(List)}! */ protected TriggerSection() {} - + /** * Remember to add this section to {@link ParserInstance#getCurrentSections()} before parsing child elements! * @@ -79,7 +76,7 @@ protected void setTriggerItems(List<TriggerItem> items) { } } } - + @Override public TriggerSection setNext(@Nullable TriggerItem next) { super.setNext(next); @@ -87,30 +84,30 @@ public TriggerSection setNext(@Nullable TriggerItem next) { last.setNext(next); return this; } - + @Override public TriggerSection setParent(@Nullable TriggerSection parent) { super.setParent(parent); return this; } - + @Override - protected final boolean run(Event e) { + protected final boolean run(Event event) { throw new UnsupportedOperationException(); } - + @Override @Nullable - protected abstract TriggerItem walk(Event e); - + protected abstract TriggerItem walk(Event event); + @Nullable - protected final TriggerItem walk(Event e, boolean run) { - debug(e, run); + protected final TriggerItem walk(Event event, boolean run) { + debug(event, run); if (run && first != null) { return first; } else { return getNext(); } } - + } diff --git a/src/main/java/ch/njol/skript/lang/Unit.java b/src/main/java/ch/njol/skript/lang/Unit.java index 2e1614b3442..c1c237d83a6 100644 --- a/src/main/java/ch/njol/skript/lang/Unit.java +++ b/src/main/java/ch/njol/skript/lang/Unit.java @@ -18,20 +18,17 @@ */ package ch.njol.skript.lang; -/** - * @author Peter Güttinger - */ public interface Unit extends Cloneable { - - public int getAmount(); - - public void setAmount(double amount); - + + int getAmount(); + + void setAmount(double amount); + @Override - public String toString(); - - public String toString(int flags); - - public Unit clone(); - + String toString(); + + String toString(int flags); + + Unit clone(); + } diff --git a/src/main/java/ch/njol/skript/lang/UnparsedLiteral.java b/src/main/java/ch/njol/skript/lang/UnparsedLiteral.java index 3bfbaf26b25..9afe7bf664d 100644 --- a/src/main/java/ch/njol/skript/lang/UnparsedLiteral.java +++ b/src/main/java/ch/njol/skript/lang/UnparsedLiteral.java @@ -18,11 +18,6 @@ */ package ch.njol.skript.lang; -import java.util.logging.Level; - -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.SkriptAPIException; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.lang.SkriptParser.ParseResult; @@ -35,66 +30,69 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; import ch.njol.util.coll.iterator.NonNullIterator; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.logging.Level; /** * A literal which has yet to be parsed. This is returned if %object(s)% is used within patterns and no expression matches. - * - * @author Peter Güttinger + * * @see SimpleLiteral */ public class UnparsedLiteral implements Literal<Object> { - + private final String data; @Nullable private final LogEntry error; - + /** * @param data non-null, non-empty & trimmed string */ - public UnparsedLiteral(final String data) { - assert data != null && data.length() > 0; + public UnparsedLiteral(String data) { + assert data.length() > 0; this.data = data; error = null; } - + /** * @param data non-null, non-empty & trimmed string * @param error Error to log if this literal cannot be parsed */ - public UnparsedLiteral(final String data, final @Nullable LogEntry error) { - assert data != null && data.length() > 0; + public UnparsedLiteral(String data, @Nullable LogEntry error) { + assert data.length() > 0; assert error == null || error.getLevel() == Level.SEVERE; this.data = data; this.error = error; } - + public String getData() { return data; } - + @Override - public Class<? extends Object> getReturnType() { + public Class<?> getReturnType() { return Object.class; } - + @Override @Nullable - public <R> Literal<? extends R> getConvertedExpression(final Class<R>... to) { + public <R> Literal<? extends R> getConvertedExpression(Class<R>... to) { return getConvertedExpression(ParseContext.DEFAULT, to); } - + @Nullable - public <R> Literal<? extends R> getConvertedExpression(final ParseContext context, final Class<? extends R>... to) { - assert to != null && to.length > 0; + public <R> Literal<? extends R> getConvertedExpression(ParseContext context, Class<? extends R>... to) { + assert to.length > 0; assert to.length == 1 || !CollectionUtils.contains(to, Object.class); - final ParseLogHandler log = SkriptLogger.startParseLogHandler(); + ParseLogHandler log = SkriptLogger.startParseLogHandler(); try { - for (final Class<? extends R> t : to) { - assert t != null; - final R r = Classes.parse(data, t, context); - if (r != null) { + for (Class<? extends R> type : to) { + assert type != null; + R parsedObject = Classes.parse(data, type, context); + if (parsedObject != null) { log.printLog(); - return new SimpleLiteral<>(r, false, this); + return new SimpleLiteral<>(parsedObject, false, this); } log.clear(); } @@ -108,248 +106,120 @@ public <R> Literal<? extends R> getConvertedExpression(final ParseContext contex } finally { log.stop(); } - - // V2 -// if (to[0] != Object.class) { -// return (Literal<? extends R>) SkriptParser.parseExpression(data, new Converter<String, Literal<? extends R>>() { -// @Override -// public Literal<? extends R> convert(final String s) { -// for (final Class<? extends R> c : to) { -// final R r = Classes.parse(s, c, context); -// if (r != null) -// return new SimpleLiteral<R>(r, false); -// } -// return null; -// } -// }, "'" + data + "' is " + SkriptParser.notOfType(to)); -// } -// return (Literal<? extends R>) SkriptParser.parseExpression(data, new Converter<String, Literal<Object>>() { -// @Override -// public Literal<Object> convert(final String s) { -// for (final ClassInfo<?> ci : Classes.getClassInfos()) { -// if (ci.getParser() != null && ci.getParser().canParse(context)) { -// final Object o = ci.getParser().parse(s, context); -// if (o != null) -// return new SimpleLiteral<Object>(o, false); -// } -// } -// return null; -// } -// }, null); - - // V1 -// if (to == String.class && context == ParseContext.DEFAULT) { -// return (Literal<? extends R>) VariableStringLiteral.newInstance(this); -// } else if (to == Object.class) { -// final SimpleLog log = SkriptLogger.startSubLog(); -// if (context == ParseContext.DEFAULT) { -// final VariableStringLiteral vsl = VariableStringLiteral.newInstance(this); -// if (vsl != null) -// return (Literal<? extends R>) vsl; -// if (log.hasErrors()) { -// log.printLog(); -// return null; -// } -// } -// for (final ClassInfo<?> ci : Classes.getClassInfos()) { -// if (ci.getParser() != null && ci.getParser().canParse(context)) { -// log.clear(); -// final Literal<?> l = convert(ci.getC(), ci.getParser(), context); -// if (l != null) { -// log.stop(); -// log.printLog(); -// return (Literal<? extends R>) l; -// } -// } -// } -// log.stop(); -// return null; -// } -// final Parser<? extends R> p = Classes.getParser(to); -// if (p == null || !p.canParse(context)) -// return null; -// return convert(to, p, context); } - -// private <T> Literal<T> convert(final Class<T> to, final Parser<?> parser, final ParseContext context) { -// assert parser.canParse(context); -// final SimpleLog log = SkriptLogger.startSubLog(); -// -// String last = data; -// LogEntry lastError = null; -// -// final T r = (T) parser.parse(data, context); -// if (r != null) { -// log.stop(); -// log.printLog(); -// return new SimpleLiteral<T>(r, false); -// } -// lastError = log.getFirstError(); -// log.clear(); -// -// final Deque<T> ts = new LinkedList<T>(); -// final Matcher m = SkriptParser.listSplitPattern.matcher(data); -// int end = data.length(); -// int expectedEnd = -1; -// boolean and = true; -// boolean isAndSet = false; -// while (m.find()) { -// if (expectedEnd == -1) -// expectedEnd = m.start(); -// final T t = (T) parser.parse(last = data.substring(m.end(), end), context); -// lastError = log.getFirstError(); -// if (t != null) { -// if (!m.group().matches("\\s*,\\s*")) { -// if (isAndSet) { -// if (and != m.group().toLowerCase(Locale.ENGLISH).contains("and")) { -// Skript.warning("list has multiple 'and' or 'or', will default to 'and'"); -// and = true; -// } -// } else { -// and = m.group().toLowerCase(Locale.ENGLISH).contains("and"); -// isAndSet = true; -// } -// } -// ts.addFirst(t); -// log.clear(); -// end = m.start(); -// m.region(0, end); -// } else { -// log.clear(); -// } -// } -// if (!isAndSet) -// Skript.warning("List is missing 'and' or 'or', defaulting to 'and'"); -// if (end == expectedEnd) { -// final T t = (T) parser.parse(last = data.substring(0, end), context); -// lastError = log.getFirstError(); -// log.stop(); -// if (t != null) { -// log.printLog(); -// ts.addFirst(t); -// return new SimpleLiteral<T>(ts.toArray((T[]) Array.newInstance(to, ts.size())), to, and, this); -// } -// } -// log.stop(); -// if (lastError != null) -// SkriptLogger.log(lastError); -// else -// Skript.error("'" + last + "' is not " + Utils.a(Classes.getSuperClassInfo(to).getName())); -// return null; -// } - + @Override - public String toString(final @Nullable Event e, final boolean debug) { + public String toString(@Nullable Event event, boolean debug) { return "'" + data + "'"; } - + @Override public String toString() { return toString(null, false); } - + @Override public Expression<?> getSource() { return this; } - + @Override public boolean getAnd() { return true; } - + @Override public boolean isSingle() { return true; } - + @Override - public Expression<? extends Object> simplify() { + public Expression<?> simplify() { return this; } - + private static SkriptAPIException invalidAccessException() { return new SkriptAPIException("UnparsedLiterals must be converted before use"); } - + @Override public Object[] getAll() { throw invalidAccessException(); } - + @Override - public Object[] getAll(final Event e) { + public Object[] getAll(Event event) { throw invalidAccessException(); } - + @Override public Object[] getArray() { throw invalidAccessException(); } - + @Override - public Object[] getArray(final Event e) { + public Object[] getArray(Event event) { throw invalidAccessException(); } - + @Override public Object getSingle() { throw invalidAccessException(); } - + @Override - public Object getSingle(final Event e) { + public Object getSingle(Event event) { throw invalidAccessException(); } - + @Override - public NonNullIterator<Object> iterator(final Event e) { + public NonNullIterator<Object> iterator(Event event) { throw invalidAccessException(); } - + @Override - public void change(final Event e, final @Nullable Object[] delta, final ChangeMode mode) throws UnsupportedOperationException { + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) throws UnsupportedOperationException { throw invalidAccessException(); } - + @Override - public Class<?>[] acceptChange(final ChangeMode mode) { + public Class<?>[] acceptChange(ChangeMode mode) { throw invalidAccessException(); } - + @Override - public boolean check(final Event e, final Checker<? super Object> c) { + public boolean check(Event event, Checker<? super Object> checker) { throw invalidAccessException(); } - + @Override - public boolean check(final Event e, final Checker<? super Object> c, final boolean negated) { + public boolean check(Event event, Checker<? super Object> checker, boolean negated) { throw invalidAccessException(); } - + @Override - public boolean setTime(final int time) { + public boolean setTime(int time) { throw invalidAccessException(); } - + @Override public int getTime() { throw invalidAccessException(); } - + @Override public boolean isDefault() { throw invalidAccessException(); } - + @Override - public boolean isLoopOf(final String s) { + public boolean isLoopOf(String input) { throw invalidAccessException(); } - + @Override - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { + public boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { throw invalidAccessException(); } - + } diff --git a/src/main/java/ch/njol/skript/lang/Variable.java b/src/main/java/ch/njol/skript/lang/Variable.java index 619418a1743..062fe307680 100644 --- a/src/main/java/ch/njol/skript/lang/Variable.java +++ b/src/main/java/ch/njol/skript/lang/Variable.java @@ -28,17 +28,6 @@ import java.util.NoSuchElementException; import java.util.TreeMap; -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.entity.Player; -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; -import org.skriptlang.skript.lang.comparator.Comparators; -import org.skriptlang.skript.lang.comparator.Relation; -import org.skriptlang.skript.lang.converter.Converters; -import org.skriptlang.skript.lang.script.Script; -import org.skriptlang.skript.lang.script.ScriptWarning; - import ch.njol.skript.Skript; import ch.njol.skript.SkriptAPIException; import ch.njol.skript.SkriptConfig; @@ -63,6 +52,16 @@ import ch.njol.util.coll.CollectionUtils; import ch.njol.util.coll.iterator.EmptyIterator; import ch.njol.util.coll.iterator.SingleItemIterator; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.comparator.Comparators; +import org.skriptlang.skript.lang.comparator.Relation; +import org.skriptlang.skript.lang.converter.Converters; +import org.skriptlang.skript.lang.script.Script; +import org.skriptlang.skript.lang.script.ScriptWarning; public class Variable<T> implements Expression<T> { @@ -82,9 +81,9 @@ public class Variable<T> implements Expression<T> { private final VariableString name; private final Class<T> superType; - final Class<? extends T>[] types; + private final Class<? extends T>[] types; - final boolean local; + private final boolean local; private final boolean list; @Nullable @@ -92,8 +91,7 @@ public class Variable<T> implements Expression<T> { @SuppressWarnings("unchecked") private Variable(VariableString name, Class<? extends T>[] types, boolean local, boolean list, @Nullable Variable<?> source) { - assert name != null; - assert types != null && types.length > 0; + assert types.length > 0; assert name.isSimple() || name.getMode() == StringMode.VARIABLE_NAME; @@ -134,30 +132,27 @@ public static boolean isValidVariableName(String name, boolean allowListVariable List<Integer> asterisks = new ArrayList<>(); List<Integer> percents = new ArrayList<>(); for (int i = 0; i < name.length(); i++) { - char c = name.charAt(i); - if (c == '*') + char character = name.charAt(i); + if (character == '*') asterisks.add(i); - else if (c == '%') + else if (character == '%') percents.add(i); } int count = asterisks.size(); int index = 0; for (int i = 0; i < percents.size(); i += 2) { - if (index == asterisks.size() || i+1 == percents.size()) // Out of bounds + if (index == asterisks.size() || i+1 == percents.size()) // Out of bounds break; - int lb = percents.get(i), ub = percents.get(i+1); - // Continually decrement asterisk count by checking if any asterisks in current range - while (index < asterisks.size() && lb < asterisks.get(index) && asterisks.get(index) < ub) { + int lowerBound = percents.get(i), upperBound = percents.get(i+1); + // Continually decrement asterisk count by checking if any asterisks in current range + while (index < asterisks.size() && lowerBound < asterisks.get(index) && asterisks.get(index) < upperBound) { count--; index++; } } if (!(count == 0 || (count == 1 && name.endsWith(SEPARATOR + "*")))) { if (printErrors) { - if (name.indexOf("*") == 0) - Skript.error("[2.0] Local variables now start with an underscore, e.g. {_local variable}. The asterisk is reserved for list variables. (error in variable {" + name + "})"); - else - Skript.error("A variable's name must not contain any asterisks except at the end after '" + SEPARATOR + "' to denote a list variable, e.g. {variable" + SEPARATOR + "*} (error in variable {" + name + "})"); + Skript.error("A variable's name must not contain any asterisks except at the end after '" + SEPARATOR + "' to denote a list variable, e.g. {variable" + SEPARATOR + "*} (error in variable {" + name + "})"); } return false; } @@ -181,9 +176,9 @@ public static <T> Variable<T> newInstance(String name, Class<? extends T>[] type name = "" + name.trim(); if (!isValidVariableName(name, true, true)) return null; - VariableString vs = VariableString.newInstance( + VariableString variableString = VariableString.newInstance( name.startsWith(LOCAL_VARIABLE_TOKEN) ? name.substring(LOCAL_VARIABLE_TOKEN.length()).trim() : name, StringMode.VARIABLE_NAME); - if (vs == null) + if (variableString == null) return null; boolean isLocal = name.startsWith(LOCAL_VARIABLE_TOKEN); @@ -201,15 +196,15 @@ public static <T> Variable<T> newInstance(String name, Class<? extends T>[] type } // Check for local variable type hints - if (isLocal && vs.isSimple()) { // Only variable names we fully know already - Class<?> hint = TypeHints.get(vs.toString()); + if (isLocal && variableString.isSimple()) { // Only variable names we fully know already + Class<?> hint = TypeHints.get(variableString.toString()); if (hint != null && !hint.equals(Object.class)) { // Type hint available // See if we can get correct type without conversion for (Class<? extends T> type : types) { assert type != null; if (type.isAssignableFrom(hint)) { // Hint matches, use variable with exactly correct type - return new Variable<>(vs, CollectionUtils.array(type), true, isPlural, null); + return new Variable<>(variableString, CollectionUtils.array(type), true, isPlural, null); } } @@ -217,16 +212,16 @@ public static <T> Variable<T> newInstance(String name, Class<? extends T>[] type for (Class<? extends T> type : types) { if (Converters.converterExists(hint, type)) { // Hint matches, even though converter is needed - return new Variable<>(vs, CollectionUtils.array(type), true, isPlural, null); + return new Variable<>(variableString, CollectionUtils.array(type), true, isPlural, null); } // Special cases if (type.isAssignableFrom(World.class) && hint.isAssignableFrom(String.class)) { // String->World conversion is weird spaghetti code - return new Variable<>(vs, types, true, isPlural, null); + return new Variable<>(variableString, types, true, isPlural, null); } else if (type.isAssignableFrom(Player.class) && hint.isAssignableFrom(String.class)) { // String->Player conversion is not available at this point - return new Variable<>(vs, types, true, isPlural, null); + return new Variable<>(variableString, types, true, isPlural, null); } } @@ -241,11 +236,11 @@ public static <T> Variable<T> newInstance(String name, Class<? extends T>[] type } } - return new Variable<>(vs, types, isLocal, isPlural, null); + return new Variable<>(variableString, types, isLocal, isPlural, null); } @Override - public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + public boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { throw new UnsupportedOperationException(); } @@ -268,18 +263,18 @@ public Class<? extends T> getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { StringBuilder stringBuilder = new StringBuilder() .append("{"); if (local) stringBuilder.append(LOCAL_VARIABLE_TOKEN); - stringBuilder.append(StringUtils.substring(name.toString(e, debug), 1, -1)) + stringBuilder.append(StringUtils.substring(name.toString(event, debug), 1, -1)) .append("}"); if (debug) { stringBuilder.append(" ("); - if (e != null) { - stringBuilder.append(Classes.toString(get(e))) + if (event != null) { + stringBuilder.append(Classes.toString(get(event))) .append(", "); } stringBuilder.append("as ") @@ -338,25 +333,25 @@ public Object getRaw(Event event) { @Nullable @SuppressWarnings("unchecked") private Object get(Event event) { - Object val = getRaw(event); + Object rawValue = getRaw(event); if (!list) - return val; - if (val == null) + return rawValue; + if (rawValue == null) return Array.newInstance(types[0], 0); - List<Object> l = new ArrayList<>(); + List<Object> convertedValues = new ArrayList<>(); String name = StringUtils.substring(this.name.toString(event), 0, -1); - for (Entry<String, ?> v : ((Map<String, ?>) val).entrySet()) { - if (v.getKey() != null && v.getValue() != null) { - Object o; - if (v.getValue() instanceof Map) - o = ((Map<String, ?>) v.getValue()).get(null); + for (Entry<String, ?> variable : ((Map<String, ?>) rawValue).entrySet()) { + if (variable.getKey() != null && variable.getValue() != null) { + Object value; + if (variable.getValue() instanceof Map) + value = ((Map<String, ?>) variable.getValue()).get(null); else - o = v.getValue(); - if (o != null) - l.add(convertIfOldPlayer(name + v.getKey(), event, o)); + value = variable.getValue(); + if (value != null) + convertedValues.add(convertIfOldPlayer(name + variable.getKey(), event, value)); } } - return l.toArray(); + return convertedValues.toArray(); } /* @@ -366,22 +361,22 @@ private Object get(Event event) { */ @Nullable Object convertIfOldPlayer(String key, Event event, @Nullable Object object) { - if (SkriptConfig.enablePlayerVariableFix.value() && object != null && object instanceof Player) { - Player p = (Player) object; - if (!p.isValid() && p.isOnline()) { - Player player = Bukkit.getPlayer(p.getUniqueId()); - Variables.setVariable(key, player, event, local); - return player; + if (SkriptConfig.enablePlayerVariableFix.value() && object instanceof Player) { + Player oldPlayer = (Player) object; + if (!oldPlayer.isValid() && oldPlayer.isOnline()) { + Player newPlayer = Bukkit.getPlayer(oldPlayer.getUniqueId()); + Variables.setVariable(key, newPlayer, event, local); + return newPlayer; } } return object; } - public Iterator<Pair<String, Object>> variablesIterator(Event e) { + public Iterator<Pair<String, Object>> variablesIterator(Event event) { if (!list) throw new SkriptAPIException("Looping a non-list variable"); - String name = StringUtils.substring(this.name.toString(e), 0, -1); - Object val = Variables.getVariable(name + "*", e, local); + String name = StringUtils.substring(this.name.toString(event), 0, -1); + Object val = Variables.getVariable(name + "*", event, local); if (val == null) return new EmptyIterator<>(); assert val instanceof TreeMap; @@ -401,7 +396,7 @@ public boolean hasNext() { while (keys.hasNext()) { key = keys.next(); if (key != null) { - next = convertIfOldPlayer(name + key, e, Variables.getVariable(name + key, e, local)); + next = convertIfOldPlayer(name + key, event, Variables.getVariable(name + key, event, local)); if (next != null && !(next instanceof TreeMap)) return true; } @@ -428,35 +423,35 @@ public void remove() { @Override @Nullable - public Iterator<T> iterator(Event e) { + @SuppressWarnings("unchecked") + public Iterator<T> iterator(Event event) { if (!list) { - T item = getSingle(e); - return item != null ? new SingleItemIterator<>(item) : null; + T value = getSingle(event); + return value != null ? new SingleItemIterator<>(value) : null; } - String name = StringUtils.substring(this.name.toString(e), 0, -1); - Object val = Variables.getVariable(name + "*", e, local); - if (val == null) + String name = StringUtils.substring(this.name.toString(event), 0, -1); + Object value = Variables.getVariable(name + "*", event, local); + if (value == null) return new EmptyIterator<>(); - assert val instanceof TreeMap; + assert value instanceof TreeMap; // temporary list to prevent CMEs - @SuppressWarnings("unchecked") - Iterator<String> keys = new ArrayList<>(((Map<String, Object>) val).keySet()).iterator(); + Iterator<String> keys = new ArrayList<>(((Map<String, Object>) value).keySet()).iterator(); return new Iterator<T>() { @Nullable private String key; @Nullable private T next = null; - @SuppressWarnings({"unchecked"}) @Override + @SuppressWarnings({"unchecked"}) public boolean hasNext() { if (next != null) return true; while (keys.hasNext()) { key = keys.next(); if (key != null) { - next = Converters.convert(Variables.getVariable(name + key, e, local), types); - next = (T) convertIfOldPlayer(name + key, e, next); + next = Converters.convert(Variables.getVariable(name + key, event, local), types); + next = (T) convertIfOldPlayer(name + key, event, next); if (next != null && !(next instanceof TreeMap)) return true; } @@ -483,25 +478,25 @@ public void remove() { } @Nullable - private T getConverted(Event e) { + private T getConverted(Event event) { assert !list; - return Converters.convert(get(e), types); + return Converters.convert(get(event), types); } - private T[] getConvertedArray(Event e) { + private T[] getConvertedArray(Event event) { assert list; - return Converters.convert((Object[]) get(e), types, superType); + return Converters.convert((Object[]) get(event), types, superType); } - private void set(Event e, @Nullable Object value) { - Variables.setVariable("" + name.toString(e), value, e, local); + private void set(Event event, @Nullable Object value) { + Variables.setVariable("" + name.toString(event), value, event, local); } - private void setIndex(Event e, String index, @Nullable Object value) { + private void setIndex(Event event, String index, @Nullable Object value) { assert list; - String s = name.toString(e); - assert s.endsWith("::*") : s + "; " + name; - Variables.setVariable(s.substring(0, s.length() - 1) + index, value, e, local); + String name = this.name.toString(event); + assert name.endsWith(SEPARATOR + "*") : name + "; " + this.name; + Variables.setVariable(name.substring(0, name.length() - 1) + index, value, event, local); } @Override @@ -511,61 +506,61 @@ public Class<?>[] acceptChange(ChangeMode mode) { return CollectionUtils.array(Object[].class); } - @SuppressWarnings({"unchecked", "rawtypes"}) @Override - public void change(Event e, @Nullable Object[] delta, ChangeMode mode) throws UnsupportedOperationException { + @SuppressWarnings({"unchecked", "rawtypes"}) + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) throws UnsupportedOperationException { switch (mode) { case DELETE: if (list) { - ArrayList<String> rem = new ArrayList<>(); - Map<String, Object> o = (Map<String, Object>) getRaw(e); - if (o == null) + ArrayList<String> toDelete = new ArrayList<>(); + Map<String, Object> map = (Map<String, Object>) getRaw(event); + if (map == null) return; - for (Entry<String, Object> i : o.entrySet()) { - if (i.getKey() != null){ - rem.add(i.getKey()); + for (Entry<String, Object> entry : map.entrySet()) { + if (entry.getKey() != null){ + toDelete.add(entry.getKey()); } } - for (String r : rem) { - assert r != null; - setIndex(e, r, null); + for (String index : toDelete) { + assert index != null; + setIndex(event, index, null); } } - set(e, null); + set(event, null); break; case SET: assert delta != null; if (list) { - set(e, null); + set(event, null); int i = 1; - for (Object d : delta) { - if (d instanceof Object[]) { - for (int j = 0; j < ((Object[]) d).length; j++) { - setIndex(e, "" + i + SEPARATOR + (j + 1), ((Object[]) d)[j]); + for (Object value : delta) { + if (value instanceof Object[]) { + for (int j = 0; j < ((Object[]) value).length; j++) { + setIndex(event, "" + i + SEPARATOR + (j + 1), ((Object[]) value)[j]); } } else { - setIndex(e, "" + i, d); + setIndex(event, "" + i, value); } i++; } } else { - set(e, delta[0]); + set(event, delta[0]); } break; case RESET: - Object x = getRaw(e); - if (x == null) + Object rawValue = getRaw(event); + if (rawValue == null) return; - for (Object o : x instanceof Map ? ((Map<?, ?>) x).values() : Arrays.asList(x)) { - Class<?> c = o.getClass(); - assert c != null; - ClassInfo<?> ci = Classes.getSuperClassInfo(c); - Changer<?> changer = ci.getChanger(); + for (Object values : rawValue instanceof Map ? ((Map<?, ?>) rawValue).values() : Arrays.asList(rawValue)) { + Class<?> type = values.getClass(); + assert type != null; + ClassInfo<?> classInfo = Classes.getSuperClassInfo(type); + Changer<?> changer = classInfo.getChanger(); if (changer != null && changer.acceptChange(ChangeMode.RESET) != null) { - Object[] one = (Object[]) Array.newInstance(o.getClass(), 1); - one[0] = o; - ((Changer) changer).change(one, null, ChangeMode.RESET); + Object[] valueArray = (Object[]) Array.newInstance(values.getClass(), 1); + valueArray[0] = values; + ((Changer) changer).change(valueArray, null, ChangeMode.RESET); } } break; @@ -574,114 +569,114 @@ public void change(Event e, @Nullable Object[] delta, ChangeMode mode) throws Un case REMOVE_ALL: assert delta != null; if (list) { - Map<String, Object> o = (Map<String, Object>) getRaw(e); + Map<String, Object> map = (Map<String, Object>) getRaw(event); if (mode == ChangeMode.REMOVE) { - if (o == null) + if (map == null) return; - ArrayList<String> rem = new ArrayList<>(); // prevents CMEs - for (Object d : delta) { - for (Entry<String, Object> i : o.entrySet()) { - if (Relation.EQUAL.isImpliedBy(Comparators.compare(i.getValue(), d))) { - String key = i.getKey(); + ArrayList<String> toRemove = new ArrayList<>(); // prevents CMEs + for (Object value : delta) { + for (Entry<String, Object> entry : map.entrySet()) { + if (Relation.EQUAL.isImpliedBy(Comparators.compare(entry.getValue(), value))) { + String key = entry.getKey(); if (key == null) continue; // This is NOT a part of list variable // Otherwise, we'll mark that key to be set to null - rem.add(key); + toRemove.add(key); break; } } } - for (String r : rem) { - assert r != null; - setIndex(e, r, null); + for (String index : toRemove) { + assert index != null; + setIndex(event, index, null); } } else if (mode == ChangeMode.REMOVE_ALL) { - if (o == null) + if (map == null) return; - ArrayList<String> rem = new ArrayList<>(); // prevents CMEs - for (Entry<String, Object> i : o.entrySet()) { - for (Object d : delta) { - if (Relation.EQUAL.isImpliedBy(Comparators.compare(i.getValue(), d))) - rem.add(i.getKey()); + ArrayList<String> toRemove = new ArrayList<>(); // prevents CMEs + for (Entry<String, Object> i : map.entrySet()) { + for (Object value : delta) { + if (Relation.EQUAL.isImpliedBy(Comparators.compare(i.getValue(), value))) + toRemove.add(i.getKey()); } } - for (String r : rem) { - assert r != null; - setIndex(e, r, null); + for (String index : toRemove) { + assert index != null; + setIndex(event, index, null); } } else { assert mode == ChangeMode.ADD; int i = 1; - for (Object d : delta) { - if (o != null) - while (o.containsKey("" + i)) + for (Object value : delta) { + if (map != null) + while (map.containsKey("" + i)) i++; - setIndex(e, "" + i, d); + setIndex(event, "" + i, value); i++; } } } else { - Object o = get(e); - ClassInfo<?> ci; - if (o == null) { - ci = null; + Object originalValue = get(event); + ClassInfo<?> classInfo; + if (originalValue == null) { + classInfo = null; } else { - Class<?> c = o.getClass(); - assert c != null; - ci = Classes.getSuperClassInfo(c); + Class<?> oldClass = originalValue.getClass(); + assert oldClass != null; + classInfo = Classes.getSuperClassInfo(oldClass); } - Arithmetic a = null; + Arithmetic arithmetic = null; Changer<?> changer; - Class<?>[] cs; - if (o == null || ci == null || (a = ci.getMath()) != null) { + Class<?>[] classes; + if (originalValue == null || (arithmetic = classInfo.getMath()) != null) { boolean changed = false; - for (Object d : delta) { - if (o == null || ci == null) { - Class<?> c = d.getClass(); - assert c != null; - ci = Classes.getSuperClassInfo(c); - - if ((a = ci.getMath()) != null) - o = d; - if (d instanceof Number) { // Nonexistent variable: add/subtract + for (Object newValue : delta) { + if (originalValue == null) { + Class<?> newClass = newValue.getClass(); + assert newClass != null; + classInfo = Classes.getSuperClassInfo(newClass); + + if ((arithmetic = classInfo.getMath()) != null) + originalValue = newValue; + if (newValue instanceof Number) { // Nonexistent variable: add/subtract if (mode == ChangeMode.REMOVE) // Variable is delta negated - o = -((Number) d).doubleValue(); // Hopefully enough precision + originalValue = -((Number) newValue).doubleValue(); // Hopefully enough precision else // Variable is now what was added to it - o = d; + originalValue = newValue; } changed = true; continue; } - Class<?> r = ci.getMathRelativeType(); - assert a != null && r != null : ci; - Object diff = Converters.convert(d, r); - if (diff != null) { + Class<?> relativeType = classInfo.getMathRelativeType(); + assert arithmetic != null && relativeType != null : classInfo; + Object convertedValue = Converters.convert(newValue, relativeType); + if (convertedValue != null) { if (mode == ChangeMode.ADD) - o = a.add(o, diff); + originalValue = arithmetic.add(originalValue, convertedValue); else - o = a.subtract(o, diff); + originalValue = arithmetic.subtract(originalValue, convertedValue); changed = true; } } if (changed) - set(e, o); - } else if ((changer = ci.getChanger()) != null && (cs = changer.acceptChange(mode)) != null) { - Object[] one = (Object[]) Array.newInstance(o.getClass(), 1); - one[0] = o; - - Class<?>[] cs2 = new Class<?>[cs.length]; - for (int i = 0; i < cs.length; i++) - cs2[i] = cs[i].isArray() ? cs[i].getComponentType() : cs[i]; - - ArrayList<Object> l = new ArrayList<>(); - for (Object d : delta) { - Object d2 = Converters.convert(d, cs2); - if (d2 != null) - l.add(d2); + set(event, originalValue); + } else if ((changer = classInfo.getChanger()) != null && (classes = changer.acceptChange(mode)) != null) { + Object[] originalValueArray = (Object[]) Array.newInstance(originalValue.getClass(), 1); + originalValueArray[0] = originalValue; + + Class<?>[] classes2 = new Class<?>[classes.length]; + for (int i = 0; i < classes.length; i++) + classes2[i] = classes[i].isArray() ? classes[i].getComponentType() : classes[i]; + + ArrayList<Object> convertedDelta = new ArrayList<>(); + for (Object value : delta) { + Object convertedValue = Converters.convert(value, classes2); + if (convertedValue != null) + convertedDelta.add(convertedValue); } - ChangerUtils.change(changer, one, l.toArray(), mode); + ChangerUtils.change(changer, originalValueArray, convertedDelta.toArray(), mode); } } @@ -691,50 +686,48 @@ public void change(Event e, @Nullable Object[] delta, ChangeMode mode) throws Un @Override @Nullable - public T getSingle(Event e) { + public T getSingle(Event event) { if (list) throw new SkriptAPIException("Invalid call to getSingle"); - return getConverted(e); + return getConverted(event); } @Override - public T[] getArray(Event e) { - return getAll(e); + public T[] getArray(Event event) { + return getAll(event); } - @SuppressWarnings("unchecked") @Override - public T[] getAll(Event e) { - if(list) - return getConvertedArray(e); - T o = getConverted(e); - if (o == null) { - T[] r = (T[]) Array.newInstance(superType, 0); - assert r != null; - return r; + @SuppressWarnings("unchecked") + public T[] getAll(Event event) { + if (list) + return getConvertedArray(event); + T value = getConverted(event); + if (value == null) { + return (T[]) Array.newInstance(superType, 0); } - T[] one = (T[]) Array.newInstance(superType, 1); - one[0] = o; - return one; + T[] valueArray = (T[]) Array.newInstance(superType, 1); + valueArray[0] = value; + return valueArray; } @Override - public boolean isLoopOf(String s) { - return s.equalsIgnoreCase("var") || s.equalsIgnoreCase("variable") || s.equalsIgnoreCase("value") || s.equalsIgnoreCase("index"); + public boolean isLoopOf(String input) { + return input.equalsIgnoreCase("var") || input.equalsIgnoreCase("variable") || input.equalsIgnoreCase("value") || input.equalsIgnoreCase("index"); } - public boolean isIndexLoop(String s) { - return s.equalsIgnoreCase("index"); + public boolean isIndexLoop(String input) { + return input.equalsIgnoreCase("index"); } @Override - public boolean check(Event e, Checker<? super T> c, boolean negated) { - return SimpleExpression.check(getAll(e), c, negated, getAnd()); + public boolean check(Event event, Checker<? super T> checker, boolean negated) { + return SimpleExpression.check(getAll(event), checker, negated, getAnd()); } @Override - public boolean check(Event e, Checker<? super T> c) { - return SimpleExpression.check(getAll(e), c, false, getAnd()); + public boolean check(Event event, Checker<? super T> checker) { + return SimpleExpression.check(getAll(event), checker, false, getAnd()); } public VariableString getName() { @@ -763,8 +756,8 @@ public boolean isDefault() { @Override public Expression<?> getSource() { - Variable<?> s = source; - return s == null ? this : s; + Variable<?> source = this.source; + return source == null ? this : source; } @Override diff --git a/src/main/java/ch/njol/skript/lang/VariableString.java b/src/main/java/ch/njol/skript/lang/VariableString.java index ccfc0c14c5d..f4614e24e9f 100644 --- a/src/main/java/ch/njol/skript/lang/VariableString.java +++ b/src/main/java/ch/njol/skript/lang/VariableString.java @@ -65,17 +65,14 @@ public class VariableString implements Expression<String> { private final String orig; @Nullable - private final Object[] string; + private final Object[] strings; @Nullable - private Object[] stringUnformatted; + private Object[] stringsUnformatted; private final boolean isSimple; @Nullable - private final String simple; - - @Nullable - private final String simpleUnformatted; + private final String simple, simpleUnformatted; private final StringMode mode; /** @@ -93,46 +90,46 @@ private VariableString(String input) { this.isSimple = true; this.simpleUnformatted = input.replace("%%", "%"); // This doesn't contain variables, so this wasn't done in newInstance! this.simple = Utils.replaceChatStyles(simpleUnformatted); - + this.orig = simple; - this.string = null; + this.strings = null; this.mode = StringMode.MESSAGE; - + ParserInstance parser = getParser(); this.script = parser.isActive() ? parser.getCurrentScript() : null; - + this.components = new MessageComponent[] {ChatMessages.plainText(simpleUnformatted)}; } /** * Creates a new VariableString which contains variables. * - * @param orig Original string (unparsed). - * @param string Objects, some of them are variables. + * @param original Original string (unparsed). + * @param strings Objects, some of them are variables. * @param mode String mode. */ - private VariableString(String orig, Object[] string, StringMode mode) { - this.orig = orig; - this.string = new Object[string.length]; - this.stringUnformatted = new Object[string.length]; + private VariableString(String original, Object[] strings, StringMode mode) { + this.orig = original; + this.strings = new Object[strings.length]; + this.stringsUnformatted = new Object[strings.length]; ParserInstance parser = getParser(); this.script = parser.isActive() ? parser.getCurrentScript() : null; - + // Construct unformatted string and components - List<MessageComponent> components = new ArrayList<>(string.length); - for (int i = 0; i < string.length; i++) { - Object object = string[i]; + List<MessageComponent> components = new ArrayList<>(strings.length); + for (int i = 0; i < strings.length; i++) { + Object object = strings[i]; if (object instanceof String) { - this.string[i] = Utils.replaceChatStyles((String) object); + this.strings[i] = Utils.replaceChatStyles((String) object); components.addAll(ChatMessages.parse((String) object)); } else { - this.string[i] = object; + this.strings[i] = object; components.add(null); // Not known parse-time } // For unformatted string, don't format stuff - this.stringUnformatted[i] = object; + this.stringsUnformatted[i] = object; } this.components = components.toArray(new MessageComponent[0]); @@ -155,78 +152,76 @@ public static VariableString newInstance(String input) { * Creates an instance of VariableString by parsing given string. * Prints errors and returns null if it is somehow invalid. * - * @param orig Unquoted string to parse. + * @param original Unquoted string to parse. * @return A new VariableString instance. */ @Nullable - public static VariableString newInstance(String orig, StringMode mode) { - if (mode != StringMode.VARIABLE_NAME && !isQuotedCorrectly(orig, false)) + public static VariableString newInstance(String original, StringMode mode) { + if (mode != StringMode.VARIABLE_NAME && !isQuotedCorrectly(original, false)) return null; - int n = StringUtils.count(orig, '%'); - if (n % 2 != 0) { + + int percentCount = StringUtils.count(original, '%'); + if (percentCount % 2 != 0) { Skript.error("The percent sign is used for expressions (e.g. %player%). To insert a '%' type it twice: %%."); return null; } // We must not parse color codes yet, as JSON support would be broken :( - String s; if (mode != StringMode.VARIABLE_NAME) { // Replace every double " character with a single ", except for those in expressions (between %) StringBuilder stringBuilder = new StringBuilder(); - + boolean expression = false; - for (int i = 0; i < orig.length(); i++) { - char c = orig.charAt(i); + for (int i = 0; i < original.length(); i++) { + char c = original.charAt(i); stringBuilder.append(c); - + if (c == '%') expression = !expression; - + if (!expression && c == '"') i++; } - s = stringBuilder.toString(); - } else { - s = orig; + original = stringBuilder.toString(); } - List<Object> string = new ArrayList<>(n / 2 + 2); // List of strings and expressions - int c = s.indexOf('%'); - if (c != -1) { - if (c != 0) - string.add(s.substring(0, c)); - while (c != s.length()) { - int c2 = s.indexOf('%', c + 1); - - int a = c; - int b; - while (c2 != -1 && (b = s.indexOf('{', a + 1)) != -1 && b < c2) { - a = nextVariableBracket(s, b + 1); - if (a == -1) { + List<Object> strings = new ArrayList<>(percentCount / 2 + 2); // List of strings and expressions + int exprStart = original.indexOf('%'); + if (exprStart != -1) { + if (exprStart != 0) + strings.add(original.substring(0, exprStart)); + while (exprStart != original.length()) { + int exprEnd = original.indexOf('%', exprStart + 1); + + int variableEnd = exprStart; + int variableStart; + while (exprEnd != -1 && (variableStart = original.indexOf('{', variableEnd + 1)) != -1 && variableStart < exprEnd) { + variableEnd = nextVariableBracket(original, variableStart + 1); + if (variableEnd == -1) { Skript.error("Missing closing bracket '}' to end variable"); return null; } - c2 = s.indexOf('%', a + 1); + exprEnd = original.indexOf('%', variableEnd + 1); } - if (c2 == -1) { + if (exprEnd == -1) { assert false; return null; } - if (c + 1 == c2) { + if (exprStart + 1 == exprEnd) { // %% escaped -> one % in result string - if (string.size() > 0 && string.get(string.size() - 1) instanceof String) { - string.set(string.size() - 1, (String) string.get(string.size() - 1) + "%"); + if (strings.size() > 0 && strings.get(strings.size() - 1) instanceof String) { + strings.set(strings.size() - 1, strings.get(strings.size() - 1) + "%"); } else { - string.add("%"); + strings.add("%"); } } else { RetainingLogHandler log = SkriptLogger.startRetainingLog(); try { Expression<?> expr = - new SkriptParser(s.substring(c + 1, c2), SkriptParser.PARSE_EXPRESSIONS, ParseContext.DEFAULT) + new SkriptParser(original.substring(exprStart + 1, exprEnd), SkriptParser.PARSE_EXPRESSIONS, ParseContext.DEFAULT) .parseExpression(Object.class); if (expr == null) { - log.printErrors("Can't understand this expression: " + s.substring(c + 1, c2)); + log.printErrors("Can't understand this expression: " + original.substring(exprStart + 1, exprEnd)); return null; } else { if (!SkriptConfig.usePlayerUUIDsInVariableNames.value() && OfflinePlayer.class.isAssignableFrom(expr.getReturnType())) { @@ -235,44 +230,43 @@ public static VariableString newInstance(String orig, StringMode mode) { "For information on how to make sure your scripts won't be impacted by this change, see https://github.com/SkriptLang/Skript/discussions/6270." ); } - string.add(expr); + strings.add(expr); } log.printLog(); } finally { log.stop(); } } - c = s.indexOf('%', c2 + 1); - if (c == -1) - c = s.length(); - String l = s.substring(c2 + 1, c); // Try to get string (non-variable) part - if (!l.isEmpty()) { // This is string part (no variables) - if (string.size() > 0 && string.get(string.size() - 1) instanceof String) { + exprStart = original.indexOf('%', exprEnd + 1); + if (exprStart == -1) + exprStart = original.length(); + String literalString = original.substring(exprEnd + 1, exprStart); // Try to get string (non-variable) part + if (!literalString.isEmpty()) { // This is string part (no variables) + if (strings.size() > 0 && strings.get(strings.size() - 1) instanceof String) { // We can append last string part in the list, so let's do so - string.set(string.size() - 1, (String) string.get(string.size() - 1) + l); + strings.set(strings.size() - 1, strings.get(strings.size() - 1) + literalString); } else { // Can't append, just add new part - string.add(l); + strings.add(literalString); } } } } else { // Only one string, no variable parts - string.add(s); + strings.add(original); } // Check if this isn't actually variable string, and return - if (string.size() == 1 && string.get(0) instanceof String) - return new VariableString(s); + if (strings.size() == 1 && strings.get(0) instanceof String) + return new VariableString(original); - Object[] sa = string.toArray(); - if (string.size() == 1 && string.get(0) instanceof Expression && - ((Expression<?>) string.get(0)).getReturnType() == String.class && - ((Expression<?>) string.get(0)).isSingle() && + if (strings.size() == 1 && strings.get(0) instanceof Expression && + ((Expression<?>) strings.get(0)).getReturnType() == String.class && + ((Expression<?>) strings.get(0)).isSingle() && mode == StringMode.MESSAGE) { - String expr = ((Expression<?>) string.get(0)).toString(null, false); + String expr = ((Expression<?>) strings.get(0)).toString(null, false); Skript.warning(expr + " is already a text, so you should not put it in one (e.g. " + expr + " instead of " + "\"%" + expr.replace("\"", "\"\"") + "%\")"); } - return new VariableString(orig, sa, mode); + return new VariableString(original, strings.toArray(), mode); } /** @@ -284,12 +278,12 @@ public static VariableString newInstance(String orig, StringMode mode) { public static String quote(String string) { StringBuilder fixed = new StringBuilder(); boolean inExpression = false; - for (char c : string.toCharArray()) { - if (c == '%') // If we are entering an expression, quotes should NOT be doubled + for (char character : string.toCharArray()) { + if (character == '%') // If we are entering an expression, quotes should NOT be doubled inExpression = !inExpression; - if (!inExpression && c == '"') + if (!inExpression && character == '"') fixed.append('"'); - fixed.append(c); + fixed.append(character); } return fixed.toString(); } @@ -298,28 +292,28 @@ public static String quote(String string) { * Tests whether a string is correctly quoted, i.e. only has doubled double quotes in it. * Singular double quotes are only allowed between percentage signs. * - * @param s The string - * @param withQuotes Whether s must be surrounded by double quotes or not + * @param string The string to test + * @param withQuotes Whether the string must be surrounded by double quotes or not * @return Whether the string is quoted correctly */ - public static boolean isQuotedCorrectly(String s, boolean withQuotes) { - if (withQuotes && (!s.startsWith("\"") || !s.endsWith("\"") || s.length() < 2)) + public static boolean isQuotedCorrectly(String string, boolean withQuotes) { + if (withQuotes && (!string.startsWith("\"") || !string.endsWith("\"") || string.length() < 2)) return false; boolean quote = false; boolean percentage = false; if (withQuotes) - s = s.substring(1, s.length() - 1); - for (char c : s.toCharArray()) { + string = string.substring(1, string.length() - 1); + for (char character : string.toCharArray()) { if (percentage) { - if (c == '%') + if (character == '%') percentage = false; continue; } - if (quote && c != '"') + if (quote && character != '"') return false; - if (c == '"') { + if (character == '"') { quote = !quote; - } else if (c == '%') { + } else if (character == '%') { percentage = true; } } @@ -329,51 +323,51 @@ public static boolean isQuotedCorrectly(String s, boolean withQuotes) { /** * Removes quoted quotes from a string. * - * @param s The string + * @param string The string to remove quotes from * @param surroundingQuotes Whether the string has quotes at the start & end that should be removed - * @return The string with double quotes replaced with signle ones and optionally with removed surrounding quotes. + * @return The string with double quotes replaced with single ones and optionally with removed surrounding quotes. */ - public static String unquote(String s, boolean surroundingQuotes) { - assert isQuotedCorrectly(s, surroundingQuotes); + public static String unquote(String string, boolean surroundingQuotes) { + assert isQuotedCorrectly(string, surroundingQuotes); if (surroundingQuotes) - return s.substring(1, s.length() - 1).replace("\"\"", "\""); - return s.replace("\"\"", "\""); + return string.substring(1, string.length() - 1).replace("\"\"", "\""); + return string.replace("\"\"", "\""); } /** * Copied from {@code SkriptParser#nextBracket(String, char, char, int, boolean)}, but removed escaping & returns -1 on error. * - * @param s + * @param string The string to search in * @param start Index after the opening bracket * @return The next closing curly bracket */ - public static int nextVariableBracket(String s, int start) { - int n = 0; - for (int i = start; i < s.length(); i++) { - if (s.charAt(i) == '}') { - if (n == 0) - return i; - n--; - } else if (s.charAt(i) == '{') { - n++; + public static int nextVariableBracket(String string, int start) { + int variableDepth = 0; + for (int index = start; index < string.length(); index++) { + if (string.charAt(index) == '}') { + if (variableDepth == 0) + return index; + variableDepth--; + } else if (string.charAt(index) == '{') { + variableDepth++; } } return -1; } - + public static VariableString[] makeStrings(String[] args) { VariableString[] strings = new VariableString[args.length]; int j = 0; for (String arg : args) { - VariableString vs = newInstance(arg); - if (vs != null) - strings[j++] = vs; + VariableString variableString = newInstance(arg); + if (variableString != null) + strings[j++] = variableString; } if (j != args.length) strings = Arrays.copyOf(strings, j); return strings; } - + /** * @param args Quoted strings - This is not checked! * @return a new array containing all newly created VariableStrings, or null if one is invalid @@ -383,53 +377,53 @@ public static VariableString[] makeStringsFromQuoted(List<String> args) { VariableString[] strings = new VariableString[args.size()]; for (int i = 0; i < args.size(); i++) { assert args.get(i).startsWith("\"") && args.get(i).endsWith("\""); - VariableString vs = newInstance(args.get(i).substring(1, args.get(i).length() - 1)); - if (vs == null) + VariableString variableString = newInstance(args.get(i).substring(1, args.get(i).length() - 1)); + if (variableString == null) return null; - strings[i] = vs; + strings[i] = variableString; } return strings; } - + /** * Parses all expressions in the string and returns it. * Does not parse formatting codes! - * @param e Event to pass to the expressions. + * @param event Event to pass to the expressions. * @return The input string with all expressions replaced. */ - public String toUnformattedString(Event e) { + public String toUnformattedString(Event event) { if (isSimple) { assert simpleUnformatted != null; return simpleUnformatted; } - Object[] string = this.stringUnformatted; - assert string != null; - StringBuilder b = new StringBuilder(); - for (Object o : string) { - if (o instanceof Expression<?>) { - b.append(Classes.toString(((Expression<?>) o).getArray(e), true, mode)); + Object[] strings = this.stringsUnformatted; + assert strings != null; + StringBuilder builder = new StringBuilder(); + for (Object string : strings) { + if (string instanceof Expression<?>) { + builder.append(Classes.toString(((Expression<?>) string).getArray(event), true, mode)); } else { - b.append(o); + builder.append(string); } } - return b.toString(); + return builder.toString(); } - + /** * Gets message components from this string. Formatting is parsed only * in simple parts for security reasons. - * @param e Currently running event. + * @param event Currently running event. * @return Message components. */ - public List<MessageComponent> getMessageComponents(Event e) { + public List<MessageComponent> getMessageComponents(Event event) { if (isSimple) { // Trusted, constant string in a script assert simpleUnformatted != null; return ChatMessages.parse(simpleUnformatted); } - - // Parse formating - Object[] string = this.stringUnformatted; - assert string != null; + + // Parse formatting + Object[] strings = this.stringsUnformatted; + assert strings != null; List<MessageComponent> message = new ArrayList<>(components.length); // At least this much space int stringPart = -1; MessageComponent previous = null; @@ -440,21 +434,21 @@ public List<MessageComponent> getMessageComponents(Event e) { if (previous != null) { // Also jump over literal part stringPart++; } - Object o = string[stringPart]; + Object string = strings[stringPart]; previous = null; - + // Convert it to plain text String text = null; - if (o instanceof ExprColoured && ((ExprColoured) o).isUnsafeFormat()) { // Special case: user wants to process formatting - String unformatted = Classes.toString(((ExprColoured) o).getArray(e), true, mode); + if (string instanceof ExprColoured && ((ExprColoured) string).isUnsafeFormat()) { // Special case: user wants to process formatting + String unformatted = Classes.toString(((ExprColoured) string).getArray(event), true, mode); if (unformatted != null) { message.addAll(ChatMessages.parse(unformatted)); } continue; - } else if (o instanceof Expression<?>) { - text = Classes.toString(((Expression<?>) o).getArray(e), true, mode); + } else if (string instanceof Expression<?>) { + text = Classes.toString(((Expression<?>) string).getArray(event), true, mode); } - + assert text != null; List<MessageComponent> components = ChatMessages.fromParsedString(text); if (!message.isEmpty()) { // Copy styles from previous component @@ -476,47 +470,47 @@ public List<MessageComponent> getMessageComponents(Event e) { previous = componentCopy; } } - + return message; } - + /** * Gets message components from this string. Formatting is parsed * everywhere, which is a potential security risk. - * @param e Currently running event. + * @param event Currently running event. * @return Message components. */ - public List<MessageComponent> getMessageComponentsUnsafe(Event e) { + public List<MessageComponent> getMessageComponentsUnsafe(Event event) { if (isSimple) { // Trusted, constant string in a script assert simpleUnformatted != null; return ChatMessages.parse(simpleUnformatted); } - - return ChatMessages.parse(toUnformattedString(e)); + + return ChatMessages.parse(toUnformattedString(event)); } - + /** * Parses all expressions in the string and returns it in chat JSON format. * - * @param e Event to pass to the expressions. + * @param event Event to pass to the expressions. * @return The input string with all expressions replaced. */ - public String toChatString(Event e) { - return ChatMessages.toJson(getMessageComponents(e)); + public String toChatString(Event event) { + return ChatMessages.toJson(getMessageComponents(event)); } - + @Nullable - private static ChatColor getLastColor(CharSequence s) { - for (int i = s.length() - 2; i >= 0; i--) { - if (s.charAt(i) == ChatColor.COLOR_CHAR) { - ChatColor c = ChatColor.getByChar(s.charAt(i + 1)); - if (c != null && (c.isColor() || c == ChatColor.RESET)) - return c; + private static ChatColor getLastColor(CharSequence sequence) { + for (int i = sequence.length() - 2; i >= 0; i--) { + if (sequence.charAt(i) == ChatColor.COLOR_CHAR) { + ChatColor color = ChatColor.getByChar(sequence.charAt(i + 1)); + if (color != null && (color.isColor() || color == ChatColor.RESET)) + return color; } } return null; } - + @Override public String toString() { return toString(null, false); @@ -537,7 +531,7 @@ public String toString(@Nullable Event event) { if (event == null) throw new IllegalArgumentException("Event may not be null in non-simple VariableStrings!"); - Object[] string = this.string; + Object[] string = this.strings; assert string != null; StringBuilder builder = new StringBuilder(); List<Class<?>> types = new ArrayList<>(); @@ -569,7 +563,7 @@ public String toString(@Nullable Event event, boolean debug) { assert simple != null; return '"' + simple + '"'; } - Object[] string = this.string; + Object[] string = this.strings; assert string != null; StringBuilder builder = new StringBuilder("\""); for (Object object : string) { @@ -609,8 +603,8 @@ public List<String> getDefaultVariableNames(String variableName, Event event) { List<StringBuilder> typeHints = Lists.newArrayList(new StringBuilder()); // Represents the index of which expression in a variable string, example name::%entity%::%object% the index of 0 will be entity. int hintIndex = 0; - assert string != null; - for (Object object : string) { + assert strings != null; + for (Object object : strings) { if (!(object instanceof Expression)) { typeHints.forEach(builder -> builder.append(object)); continue; @@ -640,58 +634,54 @@ public StringMode getMode() { public VariableString setMode(StringMode mode) { if (this.mode == mode || isSimple) return this; - @SuppressWarnings("resource") - BlockingLogHandler h = new BlockingLogHandler().start(); - try { - VariableString vs = newInstance(orig, mode); - if (vs == null) { + try (BlockingLogHandler ignored = new BlockingLogHandler().start()) { + VariableString variableString = newInstance(orig, mode); + if (variableString == null) { assert false : this + "; " + mode; return this; } - return vs; - } finally { - h.stop(); + return variableString; } } - + @Override - public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + public boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { throw new UnsupportedOperationException(); } - + @Override - public String getSingle(Event e) { - return toString(e); + public String getSingle(Event event) { + return toString(event); } - + @Override - public String[] getArray(Event e) { - return new String[] {toString(e)}; + public String[] getArray(Event event) { + return new String[] {toString(event)}; } - + @Override - public String[] getAll(Event e) { - return new String[] {toString(e)}; + public String[] getAll(Event event) { + return new String[] {toString(event)}; } - + @Override public boolean isSingle() { return true; } - + @Override - public boolean check(Event e, Checker<? super String> c, boolean negated) { - return SimpleExpression.check(getAll(e), c, negated, false); + public boolean check(Event event, Checker<? super String> checker, boolean negated) { + return SimpleExpression.check(getAll(event), checker, negated, false); } - + @Override - public boolean check(Event e, Checker<? super String> c) { - return SimpleExpression.check(getAll(e), c, false, false); + public boolean check(Event event, Checker<? super String> checker) { + return SimpleExpression.check(getAll(event), checker, false, false); } - @SuppressWarnings("unchecked") @Override @Nullable + @SuppressWarnings("unchecked") public <R> Expression<? extends R> getConvertedExpression(Class<R>... to) { if (CollectionUtils.containsSuperclass(to, String.class)) return (Expression<? extends R>) this; @@ -702,68 +692,68 @@ public <R> Expression<? extends R> getConvertedExpression(Class<R>... to) { public Class<? extends String> getReturnType() { return String.class; } - + @Override @Nullable public Class<?>[] acceptChange(ChangeMode mode) { return null; } - + @Override - public void change(Event e, @Nullable Object[] delta, ChangeMode mode) throws UnsupportedOperationException { + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) throws UnsupportedOperationException { throw new UnsupportedOperationException(); } - + @Override public boolean getAnd() { return true; } - + @Override public boolean setTime(int time) { return false; } - + @Override public int getTime() { return 0; } - + @Override public boolean isDefault() { return false; } - + @Override - public Iterator<? extends String> iterator(Event e) { - return new SingleItemIterator<>(toString(e)); + public Iterator<? extends String> iterator(Event event) { + return new SingleItemIterator<>(toString(event)); } - + @Override - public boolean isLoopOf(String s) { + public boolean isLoopOf(String input) { return false; } - + @Override public Expression<?> getSource() { return this; } - + @SuppressWarnings("unchecked") - public static <T> Expression<T> setStringMode(Expression<T> e, StringMode mode) { - if (e instanceof ExpressionList) { - Expression<?>[] ls = ((ExpressionList<?>) e).getExpressions(); - for (int i = 0; i < ls.length; i++) { - Expression<?> l = ls[i]; - assert l != null; - ls[i] = setStringMode(l, mode); + public static <T> Expression<T> setStringMode(Expression<T> expression, StringMode mode) { + if (expression instanceof ExpressionList) { + Expression<?>[] expressions = ((ExpressionList<?>) expression).getExpressions(); + for (int i = 0; i < expressions.length; i++) { + Expression<?> expr = expressions[i]; + assert expr != null; + expressions[i] = setStringMode(expr, mode); } - } else if (e instanceof VariableString) { - return (Expression<T>) ((VariableString) e).setMode(mode); + } else if (expression instanceof VariableString) { + return (Expression<T>) ((VariableString) expression).setMode(mode); } - return e; + return expression; } - + @Override public Expression<String> simplify() { return this; diff --git a/src/main/java/ch/njol/skript/lang/util/ContainerExpression.java b/src/main/java/ch/njol/skript/lang/util/ContainerExpression.java index ca382b05217..e23c43c476f 100644 --- a/src/main/java/ch/njol/skript/lang/util/ContainerExpression.java +++ b/src/main/java/ch/njol/skript/lang/util/ContainerExpression.java @@ -18,91 +18,90 @@ */ package ch.njol.skript.lang.util; -import java.util.Iterator; -import java.util.NoSuchElementException; - -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.util.Container; import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.Iterator; +import java.util.NoSuchElementException; /** * @author Peter Güttinger */ public class ContainerExpression extends SimpleExpression<Object> { - + final Expression<? extends Container<?>> expr; - private final Class<?> c; - - public ContainerExpression(final Expression<? extends Container<?>> expr, final Class<?> c) { + private final Class<?> type; + + public ContainerExpression(Expression<? extends Container<?>> expr, Class<?> type) { this.expr = expr; - this.c = c; + this.type = type; } - + @Override - protected Object[] get(final Event e) { - throw new UnsupportedOperationException("ContanerExpression must only be used by Loops"); + protected Object[] get(Event e) { + throw new UnsupportedOperationException("ContainerExpression must only be used by Loops"); } - + @Override @Nullable - public Iterator<Object> iterator(final Event e) { - final Iterator<? extends Container<?>> iter = expr.iterator(e); - if (iter == null) + public Iterator<Object> iterator(Event event) { + Iterator<? extends Container<?>> iterator = expr.iterator(event); + if (iterator == null) return null; return new Iterator<Object>() { @Nullable private Iterator<?> current; - + @Override public boolean hasNext() { - Iterator<?> c = current; - while (iter.hasNext() && (c == null || !c.hasNext())) { - current = c = iter.next().containerIterator(); + Iterator<?> current = this.current; + while (iterator.hasNext() && (current == null || !current.hasNext())) { + this.current = current = iterator.next().containerIterator(); } - return c != null && c.hasNext(); + return current != null && current.hasNext(); } - + @Override public Object next() { if (!hasNext()) throw new NoSuchElementException(); - final Iterator<?> c = current; - if (c == null) + Iterator<?> current = this.current; + if (current == null) throw new NoSuchElementException(); - final Object o = c.next(); - assert o != null : current + "; " + expr; - return o; + Object value = current.next(); + assert value != null : this.current + "; " + expr; + return value; } - + @Override public void remove() { throw new UnsupportedOperationException(); } }; } - + @Override public boolean isSingle() { return false; } - + @Override - public Class<? extends Object> getReturnType() { - return c; + public Class<?> getReturnType() { + return type; } - + @Override - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { + public boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { throw new UnsupportedOperationException(); } - + @Override - public String toString(final @Nullable Event e, final boolean debug) { - return expr.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + return expr.toString(event, debug); } - + } diff --git a/src/main/java/ch/njol/skript/lang/util/ConvertedExpression.java b/src/main/java/ch/njol/skript/lang/util/ConvertedExpression.java index 0d66cca4dc6..74733cb9f2b 100644 --- a/src/main/java/ch/njol/skript/lang/util/ConvertedExpression.java +++ b/src/main/java/ch/njol/skript/lang/util/ConvertedExpression.java @@ -18,24 +18,23 @@ */ package ch.njol.skript.lang.util; -import java.util.Iterator; -import java.util.NoSuchElementException; - -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.classes.Changer; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.classes.ClassInfo; -import org.skriptlang.skript.lang.converter.Converter; -import org.skriptlang.skript.lang.converter.ConverterInfo; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.registrations.Classes; -import org.skriptlang.skript.lang.converter.Converters; import ch.njol.util.Checker; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.converter.Converter; +import org.skriptlang.skript.lang.converter.ConverterInfo; +import org.skriptlang.skript.lang.converter.Converters; + +import java.util.Iterator; +import java.util.NoSuchElementException; /** * Represents a expression converted to another type. This, and not Expression, is the required return type of {@link SimpleExpression#getConvertedExpr(Class...)} because this @@ -48,229 +47,222 @@ * @author Peter Güttinger */ public class ConvertedExpression<F, T> implements Expression<T> { - + protected Expression<? extends F> source; protected Class<T> to; - final Converter<? super F, ? extends T> conv; - + final Converter<? super F, ? extends T> converter; + /** * Converter information. */ private final ConverterInfo<? super F, ? extends T> converterInfo; - + public ConvertedExpression(Expression<? extends F> source, Class<T> to, ConverterInfo<? super F, ? extends T> info) { - assert source != null; - assert to != null; - assert info != null; - this.source = source; this.to = to; - this.conv = info.getConverter(); + this.converter = info.getConverter(); this.converterInfo = info; } - + @SafeVarargs @Nullable - public static <F, T> ConvertedExpression<F, T> newInstance(final Expression<F> v, final Class<T>... to) { - assert !CollectionUtils.containsSuperclass(to, v.getReturnType()); - for (final Class<T> c : to) { // REMIND try more converters? -> also change WrapperExpression (and maybe ExprLoopValue) - assert c != null; + public static <F, T> ConvertedExpression<F, T> newInstance(Expression<F> from, Class<T>... to) { + assert !CollectionUtils.containsSuperclass(to, from.getReturnType()); + for (Class<T> type : to) { // REMIND try more converters? -> also change WrapperExpression (and maybe ExprLoopValue) + assert type != null; // casting <? super ? extends F> to <? super F> is wrong, but since the converter is only used for values returned by the expression // (which are instances of "<? extends F>") this won't result in any ClassCastExceptions. @SuppressWarnings("unchecked") - final ConverterInfo<? super F, ? extends T> conv = (ConverterInfo<? super F, ? extends T>) Converters.getConverterInfo(v.getReturnType(), c); + ConverterInfo<? super F, ? extends T> conv = (ConverterInfo<? super F, ? extends T>) Converters.getConverterInfo(from.getReturnType(), type); if (conv == null) continue; - return new ConvertedExpression<>(v, c, conv); + return new ConvertedExpression<>(from, type, conv); } return null; } - + @Override - public final boolean init(final Expression<?>[] vars, final int matchedPattern, final Kleenean isDelayed, final ParseResult matcher) { + public final boolean init(Expression<?>[] vars, int matchedPattern, Kleenean isDelayed, ParseResult matcher) { throw new UnsupportedOperationException(); } - + @Override - public String toString(final @Nullable Event e, final boolean debug) { - if (debug && e == null) - return "(" + source.toString(e, debug) + " >> " + conv + ": " + public String toString(@Nullable Event event, boolean debug) { + if (debug && event == null) + return "(" + source.toString(event, debug) + " >> " + converter + ": " + converterInfo + ")"; - return source.toString(e, debug); + return source.toString(event, debug); } - + @Override public String toString() { return toString(null, false); } - + @Override public Class<T> getReturnType() { return to; } - + @Override public boolean isSingle() { return source.isSingle(); } - - @SuppressWarnings("unchecked") + @Override @Nullable - public <R> Expression<? extends R> getConvertedExpression(final Class<R>... to) { + @SuppressWarnings("unchecked") + public <R> Expression<? extends R> getConvertedExpression(Class<R>... to) { if (CollectionUtils.containsSuperclass(to, this.to)) return (Expression<? extends R>) this; return source.getConvertedExpression(to); } - + @Nullable private ClassInfo<? super T> returnTypeInfo; - + @Override @Nullable - public Class<?>[] acceptChange(final ChangeMode mode) { - final Class<?>[] r = source.acceptChange(mode); - if (r == null) { - ClassInfo<? super T> rti = returnTypeInfo; - returnTypeInfo = rti = Classes.getSuperClassInfo(getReturnType()); - final Changer<?> c = rti.getChanger(); - return c == null ? null : c.acceptChange(mode); + public Class<?>[] acceptChange(ChangeMode mode) { + Class<?>[] validClasses = source.acceptChange(mode); + if (validClasses == null) { + ClassInfo<? super T> returnTypeInfo; + this.returnTypeInfo = returnTypeInfo = Classes.getSuperClassInfo(getReturnType()); + Changer<?> changer = returnTypeInfo.getChanger(); + return changer == null ? null : changer.acceptChange(mode); } - return r; + return validClasses; } - + @Override - public void change(final Event e, final @Nullable Object[] delta, final ChangeMode mode) { - final ClassInfo<? super T> rti = returnTypeInfo; - if (rti != null) { - final Changer<? super T> c = rti.getChanger(); - if (c != null) - c.change(getArray(e), delta, mode); + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + ClassInfo<? super T> returnTypeInfo = this.returnTypeInfo; + if (returnTypeInfo != null) { + Changer<? super T> changer = returnTypeInfo.getChanger(); + if (changer != null) + changer.change(getArray(event), delta, mode); } else { - source.change(e, delta, mode); + source.change(event, delta, mode); } } - + @Override @Nullable - public T getSingle(final Event e) { - final F f = source.getSingle(e); - if (f == null) + public T getSingle(Event event) { + F value = source.getSingle(event); + if (value == null) return null; - return conv.convert(f); + return converter.convert(value); } - + @Override - public T[] getArray(final Event e) { - return Converters.convert(source.getArray(e), to, conv); + public T[] getArray(Event event) { + return Converters.convert(source.getArray(event), to, converter); } - + @Override - public T[] getAll(final Event e) { - return Converters.convert(source.getAll(e), to, conv); + public T[] getAll(Event event) { + return Converters.convert(source.getAll(event), to, converter); } - + @Override - public boolean check(final Event e, final Checker<? super T> c, final boolean negated) { - return negated ^ check(e, c); + public boolean check(Event event, Checker<? super T> checker, boolean negated) { + return negated ^ check(event, checker); } - + @Override - public boolean check(final Event e, final Checker<? super T> c) { - return source.check(e, new Checker<F>() { - @Override - public boolean check(final F f) { - final T t = conv.convert(f); - if (t == null) { - return false; - } - return c.check(t); + public boolean check(Event event, Checker<? super T> checker) { + return source.check(event, (Checker<F>) value -> { + T convertedValue = converter.convert(value); + if (convertedValue == null) { + return false; } + return checker.check(convertedValue); }); } - + @Override public boolean getAnd() { return source.getAnd(); } - + @Override - public boolean setTime(final int time) { + public boolean setTime(int time) { return source.setTime(time); } - + @Override public int getTime() { return source.getTime(); } - + @Override public boolean isDefault() { return source.isDefault(); } - + @Override - public boolean isLoopOf(final String s) { + public boolean isLoopOf(String input) { return false;// A loop does not convert the expression to loop } - + @Override @Nullable - public Iterator<T> iterator(final Event e) { - final Iterator<? extends F> iter = source.iterator(e); - if (iter == null) + public Iterator<T> iterator(Event event) { + Iterator<? extends F> iterator = source.iterator(event); + if (iterator == null) return null; return new Iterator<T>() { @Nullable T next = null; - + @Override public boolean hasNext() { if (next != null) return true; - while (next == null && iter.hasNext()) { - final F f = iter.next(); - next = f == null ? null : conv.convert(f); + while (next == null && iterator.hasNext()) { + F value = iterator.next(); + next = value == null ? null : converter.convert(value); } return next != null; } - + @Override public T next() { if (!hasNext()) throw new NoSuchElementException(); - final T n = next; + T n = next; next = null; assert n != null; return n; } - + @Override public void remove() { throw new UnsupportedOperationException(); } }; } - + @Override public Expression<?> getSource() { return source; } - - @SuppressWarnings("unchecked") + @Override + @SuppressWarnings("unchecked") public Expression<? extends T> simplify() { - final Expression<? extends T> c = source.simplify().getConvertedExpression(to); - if (c != null) - return c; + Expression<? extends T> convertedExpression = source.simplify().getConvertedExpression(to); + if (convertedExpression != null) + return convertedExpression; return this; } - + @Override @Nullable public Object[] beforeChange(Expression<?> changed, @Nullable Object[] delta) { return source.beforeChange(changed, delta); // Forward to source // TODO this is not entirely safe, even though probably works well enough } - + } diff --git a/src/main/java/ch/njol/skript/lang/util/ConvertedLiteral.java b/src/main/java/ch/njol/skript/lang/util/ConvertedLiteral.java index 2765dd42a2e..e404deadcc6 100644 --- a/src/main/java/ch/njol/skript/lang/util/ConvertedLiteral.java +++ b/src/main/java/ch/njol/skript/lang/util/ConvertedLiteral.java @@ -18,97 +18,88 @@ */ package ch.njol.skript.lang.util; -import java.util.Iterator; - -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.SkriptAPIException; -import org.skriptlang.skript.lang.converter.Converter; -import org.skriptlang.skript.lang.converter.ConverterInfo; import ch.njol.skript.lang.Literal; import ch.njol.skript.registrations.Classes; -import org.skriptlang.skript.lang.converter.Converters; import ch.njol.util.Checker; import ch.njol.util.coll.CollectionUtils; import ch.njol.util.coll.iterator.ArrayIterator; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.converter.ConverterInfo; +import org.skriptlang.skript.lang.converter.Converters; + +import java.util.Iterator; /** * @see SimpleLiteral */ public class ConvertedLiteral<F, T> extends ConvertedExpression<F, T> implements Literal<T> { - + protected transient T[] data; - + @SuppressWarnings("unchecked") - public ConvertedLiteral(final Literal<F> source, final T[] data, final Class<T> to) { - super(source, to, new ConverterInfo<>((Class<F>) source.getReturnType(), to, new Converter<F, T>() { - @Override - @Nullable - public T convert(final F f) { - return Converters.convert(f, to); - } - }, 0)); + public ConvertedLiteral(Literal<F> source, T[] data, Class<T> to) { + super(source, to, new ConverterInfo<>((Class<F>) source.getReturnType(), to, from -> Converters.convert(from, to), 0)); this.data = data; assert data.length > 0; } - - @SuppressWarnings("unchecked") + @Override @Nullable - public <R> Literal<? extends R> getConvertedExpression(final Class<R>... to) { + @SuppressWarnings("unchecked") + public <R> Literal<? extends R> getConvertedExpression(Class<R>... to) { if (CollectionUtils.containsSuperclass(to, this.to)) return (Literal<? extends R>) this; return ((Literal<F>) source).getConvertedExpression(to); } - + @Override - public String toString(final @Nullable Event e, final boolean debug) { + public String toString(@Nullable Event event, boolean debug) { return Classes.toString(data, getAnd()); } - + @Override public T[] getArray() { return data; } - + @Override public T[] getAll() { return data; } - + @Override - public T[] getArray(final Event e) { + public T[] getArray(Event event) { return getArray(); } - - @SuppressWarnings("null") + @Override public T getSingle() { if (getAnd() && data.length > 1) throw new SkriptAPIException("Call to getSingle on a non-single expression"); return CollectionUtils.getRandom(data); } - + @Override - public T getSingle(final Event e) { + public T getSingle(Event event) { return getSingle(); } - + @Override @Nullable - public Iterator<T> iterator(final Event e) { + public Iterator<T> iterator(Event event) { return new ArrayIterator<>(data); } - + @Override - public boolean check(final Event e, final Checker<? super T> c) { - return SimpleExpression.check(data, c, false, getAnd()); + public boolean check(Event event, Checker<? super T> checker) { + return SimpleExpression.check(data, checker, false, getAnd()); } - + @Override - public boolean check(final Event e, final Checker<? super T> c, final boolean negated) { - return SimpleExpression.check(data, c, negated, getAnd()); + public boolean check(Event event, Checker<? super T> checker, boolean negated) { + return SimpleExpression.check(data, checker, negated, getAnd()); } - + } diff --git a/src/main/java/ch/njol/skript/lang/util/SimpleEvent.java b/src/main/java/ch/njol/skript/lang/util/SimpleEvent.java index 3a1ecd7ddb4..bfaf485f851 100644 --- a/src/main/java/ch/njol/skript/lang/util/SimpleEvent.java +++ b/src/main/java/ch/njol/skript/lang/util/SimpleEvent.java @@ -32,24 +32,24 @@ * @author Peter Güttinger */ public class SimpleEvent extends SkriptEvent { - + public SimpleEvent() {} - + @Override - public boolean check(final Event e) { + public boolean check(Event event) { return true; } - + @Override - public boolean init(final Literal<?>[] args, final int matchedPattern, final ParseResult parser) { + public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parser) { if (args.length != 0) throw new SkriptAPIException("Invalid use of SimpleEvent"); return true; } - + @Override - public String toString(final @Nullable Event e, final boolean debug) { + public String toString(@Nullable Event event, boolean debug) { return "simple event"; } - + } diff --git a/src/main/java/ch/njol/skript/lang/util/SimpleExpression.java b/src/main/java/ch/njol/skript/lang/util/SimpleExpression.java index 6874b1d2b1a..9e5af263ef4 100644 --- a/src/main/java/ch/njol/skript/lang/util/SimpleExpression.java +++ b/src/main/java/ch/njol/skript/lang/util/SimpleExpression.java @@ -18,20 +18,11 @@ */ package ch.njol.skript.lang.util; -import java.lang.reflect.Array; -import java.util.Arrays; -import java.util.Iterator; - -import org.bukkit.event.Event; -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.classes.Changer; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.classes.ClassInfo; -import org.skriptlang.skript.lang.converter.Converter; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.registrations.Classes; @@ -40,136 +31,135 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; import ch.njol.util.coll.iterator.ArrayIterator; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.converter.Converter; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.Iterator; /** * An implementation of the {@link Expression} interface. You should usually extend this class to make a new expression. * * @see Skript#registerExpression(Class, Class, ExpressionType, String...) - * @author Peter Güttinger */ public abstract class SimpleExpression<T> implements Expression<T> { - + private int time = 0; - + protected SimpleExpression() {} - + @Override @Nullable - public final T getSingle(final Event e) { - final T[] all = getArray(e); - if (all.length == 0) + public final T getSingle(Event event) { + T[] values = getArray(event); + if (values.length == 0) return null; - if (all.length > 1) + if (values.length > 1) throw new SkriptAPIException("Call to getSingle() on a non-single expression"); - return all[0]; + return values[0]; } - - /** - * {@inheritDoc} - * <p> - * Unlike {@link #get(Event)} you have to make sure that the this method's returned array is neither null nor contains null elements. - */ - @SuppressWarnings("unchecked") + @Override - public T[] getAll(final Event e) { - final T[] all = get(e); - if (all == null) { - final T[] r = (T[]) Array.newInstance(getReturnType(), 0); - assert r != null; - return r; + @SuppressWarnings("unchecked") + public T[] getAll(Event event) { + T[] values = get(event); + if (values == null) { + T[] emptyArray = (T[]) Array.newInstance(getReturnType(), 0); + assert emptyArray != null; + return emptyArray; } - if (all.length == 0) - return all; + if (values.length == 0) + return values; int numNonNull = 0; - for (final T t : all) - if (t != null) + for (T value : values) + if (value != null) numNonNull++; - if (numNonNull == all.length) - return Arrays.copyOf(all, all.length); - final T[] r = (T[]) Array.newInstance(getReturnType(), numNonNull); - assert r != null; + if (numNonNull == values.length) + return Arrays.copyOf(values, values.length); + T[] valueArray = (T[]) Array.newInstance(getReturnType(), numNonNull); + assert valueArray != null; int i = 0; - for (final T t : all) - if (t != null) - r[i++] = t; - return r; + for (T value : values) + if (value != null) + valueArray[i++] = value; + return valueArray; } - - @SuppressWarnings("unchecked") + @Override - public final T[] getArray(final Event e) { - final T[] all = get(e); - if (all == null) { - final T[] r = (T[]) Array.newInstance(getReturnType(), 0); - assert r != null; - return r; + @SuppressWarnings("unchecked") + public final T[] getArray(Event event) { + T[] values = get(event); + if (values == null) { + return (T[]) Array.newInstance(getReturnType(), 0); } - if (all.length == 0) - return all; - + if (values.length == 0) + return values; + int numNonNull = 0; - for (final T t : all) - if (t != null) + for (T value : values) + if (value != null) numNonNull++; - + if (!getAnd()) { - if (all.length == 1 && all[0] != null) - return Arrays.copyOf(all, 1); + if (values.length == 1 && values[0] != null) + return Arrays.copyOf(values, 1); int rand = Utils.random(0, numNonNull); - final T[] one = (T[]) Array.newInstance(getReturnType(), 1); - for (final T t : all) { - if (t != null) { + T[] valueArray = (T[]) Array.newInstance(getReturnType(), 1); + for (T value : values) { + if (value != null) { if (rand == 0) { - one[0] = t; - return one; + valueArray[0] = value; + return valueArray; } rand--; } } assert false; } - - if (numNonNull == all.length) - return Arrays.copyOf(all, all.length); - final T[] r = (T[]) Array.newInstance(getReturnType(), numNonNull); - assert r != null; + + if (numNonNull == values.length) + return Arrays.copyOf(values, values.length); + T[] valueArray = (T[]) Array.newInstance(getReturnType(), numNonNull); int i = 0; - for (final T t : all) - if (t != null) - r[i++] = t; - return r; + for (T value : values) + if (value != null) + valueArray[i++] = value; + return valueArray; } - + /** * This is the internal method to get an expression's values.<br> * To get the expression's value from the outside use {@link #getSingle(Event)} or {@link #getArray(Event)}. * - * @param e The event + * @param event The event with which this expression is evaluated. * @return An array of values for this event. May not contain nulls. */ @Nullable - protected abstract T[] get(Event e); - + protected abstract T[] get(Event event); + @Override - public final boolean check(final Event e, final Checker<? super T> c) { - return check(e, c, false); + public final boolean check(Event event, Checker<? super T> checker) { + return check(event, checker, false); } - + @Override - public final boolean check(final Event e, final Checker<? super T> c, final boolean negated) { - return check(get(e), c, negated, getAnd()); + public final boolean check(Event event, Checker<? super T> checker, boolean negated) { + return check(get(event), checker, negated, getAnd()); } - - // TODO return a kleenean (UNKNOWN if 'all' is null or empty) - public static <T> boolean check(final @Nullable T[] all, final Checker<? super T> c, final boolean invert, final boolean and) { - if (all == null) + + // TODO return a kleenean (UNKNOWN if 'values' is null or empty) + public static <T> boolean check(@Nullable T[] values, Checker<? super T> checker, boolean invert, boolean and) { + if (values == null) return invert; boolean hasElement = false; - for (final T t : all) { - if (t == null) + for (T value : values) { + if (value == null) continue; hasElement = true; - final boolean b = c.check(t); + boolean b = checker.check(value); if (and && !b) return invert; if (!and && b) @@ -179,7 +169,7 @@ public static <T> boolean check(final @Nullable T[] all, final Checker<? super T return invert; return invert ^ and; } - + /** * Converts this expression to another type. Unless the expression is special, the default implementation is sufficient. * <p> @@ -192,7 +182,7 @@ public static <T> boolean check(final @Nullable T[] all, final Checker<? super T * @see Converter */ @Nullable - protected <R> ConvertedExpression<T, ? extends R> getConvertedExpr(final Class<R>... to) { + protected <R> ConvertedExpression<T, ? extends R> getConvertedExpr(Class<R>... to) { assert !CollectionUtils.containsSuperclass(to, getReturnType()); return ConvertedExpression.newInstance(this, to); } @@ -207,39 +197,39 @@ public static <T> boolean check(final @Nullable T[] all, final Checker<? super T * @return The converted expression */ @Override - @SuppressWarnings("unchecked") @Nullable - public <R> Expression<? extends R> getConvertedExpression(final Class<R>... to) { + @SuppressWarnings("unchecked") + public <R> Expression<? extends R> getConvertedExpression(Class<R>... to) { if (CollectionUtils.containsSuperclass(to, getReturnType())) return (Expression<? extends R>) this; return this.getConvertedExpr(to); } - + @Nullable private ClassInfo<?> returnTypeInfo; - + @Override @Nullable - public Class<?>[] acceptChange(final ChangeMode mode) { - ClassInfo<?> rti = returnTypeInfo; - if (rti == null) - returnTypeInfo = rti = Classes.getSuperClassInfo(getReturnType()); - final Changer<?> c = rti.getChanger(); - if (c == null) + public Class<?>[] acceptChange(ChangeMode mode) { + ClassInfo<?> returnTypeInfo = this.returnTypeInfo; + if (returnTypeInfo == null) + this.returnTypeInfo = returnTypeInfo = Classes.getSuperClassInfo(getReturnType()); + Changer<?> changer = returnTypeInfo.getChanger(); + if (changer == null) return null; - return c.acceptChange(mode); + return changer.acceptChange(mode); } - - @SuppressWarnings("unchecked") + @Override - public void change(final Event e, final @Nullable Object[] delta, final ChangeMode mode) { - final ClassInfo<?> rti = returnTypeInfo; - if (rti == null) + @SuppressWarnings("unchecked") + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + ClassInfo<?> returnTypeInfo = this.returnTypeInfo; + if (returnTypeInfo == null) throw new UnsupportedOperationException(); - final Changer<?> c = rti.getChanger(); - if (c == null) + Changer<?> changer = returnTypeInfo.getChanger(); + if (changer == null) throw new UnsupportedOperationException(); - ((Changer<T>) c).change(getArray(e), delta, mode); + ((Changer<T>) changer).change(getArray(event), delta, mode); } /** @@ -323,38 +313,38 @@ protected final boolean setTime(int time, Expression<?> mustbeDefaultVar, Class< public int getTime() { return time; } - + @Override public boolean isDefault() { return false; } - + @Override - public boolean isLoopOf(final String s) { + public boolean isLoopOf(String input) { return false; } - + @Override @Nullable - public Iterator<? extends T> iterator(final Event e) { - return new ArrayIterator<>(getArray(e)); + public Iterator<? extends T> iterator(Event event) { + return new ArrayIterator<>(getArray(event)); } - + @Override public String toString() { return toString(null, false); } - + @Override public Expression<?> getSource() { return this; } - + @Override public Expression<? extends T> simplify() { return this; } - + @Override public boolean getAnd() { return true; diff --git a/src/main/java/ch/njol/skript/lang/util/SimpleLiteral.java b/src/main/java/ch/njol/skript/lang/util/SimpleLiteral.java index b15e2943c48..3cab2276046 100644 --- a/src/main/java/ch/njol/skript/lang/util/SimpleLiteral.java +++ b/src/main/java/ch/njol/skript/lang/util/SimpleLiteral.java @@ -18,11 +18,6 @@ */ package ch.njol.skript.lang.util; -import java.lang.reflect.Array; - -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.classes.Changer; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.classes.ClassInfo; @@ -32,195 +27,197 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.UnparsedLiteral; import ch.njol.skript.registrations.Classes; -import org.skriptlang.skript.lang.converter.Converters; import ch.njol.skript.util.StringMode; import ch.njol.skript.util.Utils; import ch.njol.util.Checker; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; import ch.njol.util.coll.iterator.NonNullIterator; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.converter.Converters; + +import java.lang.reflect.Array; /** * Represents a literal, i.e. a static value like a number or a string. - * - * @author Peter Güttinger + * * @see UnparsedLiteral */ public class SimpleLiteral<T> implements Literal<T>, DefaultExpression<T> { - - protected final Class<T> c; - + + protected final Class<T> type; + private final boolean isDefault; private final boolean and; - + @Nullable private UnparsedLiteral source = null; - + protected transient T[] data; - - public SimpleLiteral(final T[] data, final Class<T> c, final boolean and) { + + public SimpleLiteral(T[] data, Class<T> type, boolean and) { assert data != null && data.length != 0; - assert c != null; + assert type != null; this.data = data; - this.c = c; + this.type = type; this.and = data.length == 1 || and; this.isDefault = false; } - + public SimpleLiteral(T data, boolean isDefault) { this(data, isDefault, null); } - - @SuppressWarnings({"unchecked", "null"}) + + @SuppressWarnings("unchecked") public SimpleLiteral(T data, boolean isDefault, @Nullable UnparsedLiteral source) { assert data != null; this.data = (T[]) Array.newInstance(data.getClass(), 1); this.data[0] = data; - c = (Class<T>) data.getClass(); + type = (Class<T>) data.getClass(); and = true; this.isDefault = isDefault; this.source = source; } - - public SimpleLiteral(final T[] data, final Class<T> to, final boolean and, final @Nullable UnparsedLiteral source) { + + public SimpleLiteral(T[] data, Class<T> to, boolean and, @Nullable UnparsedLiteral source) { this(data, to, and); this.source = source; } - + @Override - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { + public boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { throw new UnsupportedOperationException(); } - + @Override public boolean init() { return true; } - + @Override public T[] getArray() { return data; } - + @Override - public T[] getArray(final Event e) { + public T[] getArray(Event event) { return data; } - + @Override public T[] getAll() { return data; } - + @Override - public T[] getAll(final Event e) { + public T[] getAll(Event event) { return data; } - - @SuppressWarnings("null") + @Override public T getSingle() { return CollectionUtils.getRandom(data); } - + @Override - public T getSingle(final Event e) { + public T getSingle(Event event) { return getSingle(); } - + @Override public Class<T> getReturnType() { - return c; + return type; } - - @SuppressWarnings("unchecked") + @Override @Nullable - public <R> Literal<? extends R> getConvertedExpression(final Class<R>... to) { - if (CollectionUtils.containsSuperclass(to, c)) + @SuppressWarnings("unchecked") + public <R> Literal<? extends R> getConvertedExpression(Class<R>... to) { + if (CollectionUtils.containsSuperclass(to, type)) return (Literal<? extends R>) this; - final R[] parsedData = Converters.convert(data, to, (Class<R>) Utils.getSuperType(to)); + R[] parsedData = Converters.convert(data, to, (Class<R>) Utils.getSuperType(to)); if (parsedData.length != data.length) return null; return new ConvertedLiteral<>(this, parsedData, (Class<R>) Utils.getSuperType(to)); } - + @Override - public String toString(final @Nullable Event e, final boolean debug) { + public String toString(@Nullable Event event, boolean debug) { if (debug) return "[" + Classes.toString(data, getAnd(), StringMode.DEBUG) + "]"; return Classes.toString(data, getAnd()); } - + @Override public String toString() { return toString(null, false); } - + @Override public boolean isSingle() { return !getAnd() || data.length == 1; } - + @Override public boolean isDefault() { return isDefault; } - + @Override - public boolean check(final Event e, final Checker<? super T> c, final boolean negated) { - return SimpleExpression.check(data, c, negated, getAnd()); + public boolean check(Event event, Checker<? super T> checker, boolean negated) { + return SimpleExpression.check(data, checker, negated, getAnd()); } - + @Override - public boolean check(final Event e, final Checker<? super T> c) { - return SimpleExpression.check(data, c, false, getAnd()); + public boolean check(Event event, Checker<? super T> checker) { + return SimpleExpression.check(data, checker, false, getAnd()); } - + @Nullable private ClassInfo<? super T> returnTypeInfo; - + @Override @Nullable - public Class<?>[] acceptChange(final ChangeMode mode) { - ClassInfo<? super T> rti = returnTypeInfo; - if (rti == null) - returnTypeInfo = rti = Classes.getSuperClassInfo(getReturnType()); - final Changer<? super T> c = rti.getChanger(); - return c == null ? null : c.acceptChange(mode); - } - - @Override - public void change(final Event e, final @Nullable Object[] delta, final ChangeMode mode) throws UnsupportedOperationException { - final ClassInfo<? super T> rti = returnTypeInfo; - if (rti == null) + public Class<?>[] acceptChange(ChangeMode mode) { + ClassInfo<? super T> returnTypeInfo = this.returnTypeInfo; + if (returnTypeInfo == null) + this.returnTypeInfo = returnTypeInfo = Classes.getSuperClassInfo(getReturnType()); + final Changer<? super T> changer = returnTypeInfo.getChanger(); + return changer == null ? null : changer.acceptChange(mode); + } + + @Override + public void change(final Event event, final @Nullable Object[] delta, final ChangeMode mode) throws UnsupportedOperationException { + final ClassInfo<? super T> returnTypeInfo = this.returnTypeInfo; + if (returnTypeInfo == null) throw new UnsupportedOperationException(); - final Changer<? super T> c = rti.getChanger(); - if (c == null) + final Changer<? super T> changer = returnTypeInfo.getChanger(); + if (changer == null) throw new UnsupportedOperationException(); - c.change(getArray(), delta, mode); + changer.change(getArray(), delta, mode); } - + @Override public boolean getAnd() { return and; } - + @Override public boolean setTime(final int time) { return false; } - + @Override public int getTime() { return 0; } - + @Override - public NonNullIterator<T> iterator(final Event e) { + public NonNullIterator<T> iterator(final Event event) { return new NonNullIterator<T>() { private int i = 0; - + @Override @Nullable protected T getNext() { @@ -230,21 +227,21 @@ protected T getNext() { } }; } - + @Override - public boolean isLoopOf(final String s) { + public boolean isLoopOf(final String input) { return false; } - + @Override public Expression<?> getSource() { - final UnparsedLiteral s = source; - return s == null ? this : s; + final UnparsedLiteral source = this.source; + return source == null ? this : source; } - + @Override public Expression<T> simplify() { return this; } - + } diff --git a/src/main/java/ch/njol/skript/structures/StructCommand.java b/src/main/java/ch/njol/skript/structures/StructCommand.java index 540e653bb49..5947a66ca52 100644 --- a/src/main/java/ch/njol/skript/structures/StructCommand.java +++ b/src/main/java/ch/njol/skript/structures/StructCommand.java @@ -83,7 +83,7 @@ public class StructCommand extends Structure { public static final Priority PRIORITY = new Priority(500); private static final Pattern COMMAND_PATTERN = Pattern.compile("(?i)^command\\s+/?(\\S+)\\s*(\\s+(.+))?$"); - private static final Pattern ARGUMENT_PATTERN = Pattern.compile("<\\s*(?:([^>]+?)\\s*:\\s*)?(.+?)\\s*(?:=\\s*(" + SkriptParser.wildcard + "))?\\s*>"); + private static final Pattern ARGUMENT_PATTERN = Pattern.compile("<\\s*(?:([^>]+?)\\s*:\\s*)?(.+?)\\s*(?:=\\s*(" + SkriptParser.WILDCARD + "))?\\s*>"); private static final Pattern DESCRIPTION_PATTERN = Pattern.compile("(?<!\\\\)%-?(.+?)%"); private static final AtomicBoolean SYNC_COMMANDS = new AtomicBoolean(); @@ -348,7 +348,7 @@ public Priority getPriority() { } @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { return "command"; } diff --git a/src/main/java/org/skriptlang/skript/lang/structure/Structure.java b/src/main/java/org/skriptlang/skript/lang/structure/Structure.java index 4852942aafe..24455ee8137 100644 --- a/src/main/java/org/skriptlang/skript/lang/structure/Structure.java +++ b/src/main/java/org/skriptlang/skript/lang/structure/Structure.java @@ -96,10 +96,10 @@ public final EntryContainer getEntryContainer() { } @Override - public final boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + public final boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { StructureData structureData = getParser().getData(StructureData.class); - Literal<?>[] literals = Arrays.copyOf(exprs, exprs.length, Literal[].class); + Literal<?>[] literals = Arrays.copyOf(expressions, expressions.length, Literal[].class); StructureInfo<? extends Structure> structureInfo = structureData.structureInfo; assert structureInfo != null; From ba38932b9af2489cd155c27347849fdb701e640a Mon Sep 17 00:00:00 2001 From: _tud <98935832+UnderscoreTud@users.noreply.github.com> Date: Mon, 1 Jan 2024 23:02:31 +0300 Subject: [PATCH 564/619] Optimize CommandHelp instantiation to prevent misleading errors (#6249) --- .../ch/njol/skript/command/CommandHelp.java | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/main/java/ch/njol/skript/command/CommandHelp.java b/src/main/java/ch/njol/skript/command/CommandHelp.java index 4732ba2cde5..ca1c82f5e08 100644 --- a/src/main/java/ch/njol/skript/command/CommandHelp.java +++ b/src/main/java/ch/njol/skript/command/CommandHelp.java @@ -44,8 +44,10 @@ public class CommandHelp { private final String actualCommand, actualNode, argsColor; private String command; private String langNode; + + private boolean revalidate = true; @Nullable - private Message description; + private Message description = null; private final Map<String, Object> arguments = new LinkedHashMap<>(); @@ -53,18 +55,17 @@ public class CommandHelp { private ArgumentHolder wildcardArg = null; public CommandHelp(String command, SkriptColor argsColor, String langNode) { - this(command, argsColor.getFormattedChat(), langNode, new Message(langNode + "." + DEFAULTENTRY)); + this(command, argsColor.getFormattedChat(), langNode); } public CommandHelp(String command, SkriptColor argsColor) { - this(command, argsColor.getFormattedChat(), command, null); + this(command, argsColor.getFormattedChat(), command); } - private CommandHelp(String command, String argsColor, String node, @Nullable Message description) { + private CommandHelp(String command, String argsColor, String node) { this.actualCommand = this.command = command; this.actualNode = this.langNode = node; this.argsColor = argsColor; - this.description = description; } public CommandHelp add(String argument) { @@ -85,14 +86,14 @@ public CommandHelp add(CommandHelp help) { protected void onAdd(CommandHelp parent) { langNode = parent.langNode + "." + actualNode; - description = new Message(langNode + "." + DEFAULTENTRY); command = parent.command + " " + parent.argsColor + actualCommand; + revalidate = true; for (Entry<String, Object> entry : arguments.entrySet()) { if (entry.getValue() instanceof CommandHelp) { ((CommandHelp) entry.getValue()).onAdd(this); continue; } - ((ArgumentHolder) entry.getValue()).update(); + ((ArgumentHolder) entry.getValue()).revalidate = true; } } @@ -125,26 +126,32 @@ private void showHelp(CommandSender sender, String pre) { @Override public String toString() { + if (revalidate) { + // We don't want to create a new Message object each time toString is called + description = new Message(langNode + "." + DEFAULTENTRY); + revalidate = false; + } return "" + description; } private class ArgumentHolder { private final String argument; - private Message description; + private boolean revalidate = true; + @Nullable + private Message description = null; private ArgumentHolder(String argument) { this.argument = argument; - this.description = new Message(langNode + "." + argument); - } - - private void update() { - description = new Message(langNode + "." + argument); } @Override public String toString() { - return description.toString(); + if (revalidate) { + description = new Message(langNode + "." + argument); + revalidate = false; + } + return "" + description; } } From 59948b53fe668a25860a375d3c7de8d3f48d526e Mon Sep 17 00:00:00 2001 From: _tud <98935832+UnderscoreTud@users.noreply.github.com> Date: Mon, 1 Jan 2024 23:35:05 +0300 Subject: [PATCH 565/619] Arithmetic Rework (#5279) --- src/main/java/ch/njol/skript/Skript.java | 2 + .../ch/njol/skript/classes/Arithmetic.java | 1 + .../ch/njol/skript/classes/ClassInfo.java | 15 +- .../njol/skript/classes/NumberArithmetic.java | 1 + .../skript/classes/VectorArithmethic.java | 1 + .../classes/data/DefaultOperations.java | 119 ++++++++ .../njol/skript/classes/data/JavaClasses.java | 15 +- .../skript/classes/data/SkriptClasses.java | 67 +---- .../skript/expressions/ExprBurnCookTime.java | 94 +++--- .../skript/expressions/ExprDifference.java | 55 ++-- .../expressions/ExprVectorArithmetic.java | 62 +--- .../arithmetic/ArithmeticChain.java | 145 ++++++++-- .../arithmetic/ArithmeticExpressionInfo.java | 47 +++ .../arithmetic/ArithmeticGettable.java | 15 +- .../arithmetic/ExprArithmetic.java | 253 +++++++++------- .../arithmetic/NumberExpressionInfo.java | 22 +- .../expressions/arithmetic/Operator.java | 1 + .../skript/hooks/economy/classes/Money.java | 53 ++-- .../java/ch/njol/skript/lang/Variable.java | 53 ++-- src/main/java/ch/njol/skript/util/Utils.java | 8 + .../skript/lang/arithmetic/Arithmetics.java | 269 ++++++++++++++++++ .../lang/arithmetic/DifferenceInfo.java | 49 ++++ .../skript/lang/arithmetic/Operation.java | 37 +++ .../skript/lang/arithmetic/OperationInfo.java | 88 ++++++ .../skript/lang/arithmetic/Operator.java | 56 ++++ src/main/resources/lang/default.lang | 8 + .../syntaxes/expressions/ExprArithmetic.sk | 257 +++++++++++++++++ 27 files changed, 1365 insertions(+), 428 deletions(-) create mode 100644 src/main/java/ch/njol/skript/classes/data/DefaultOperations.java create mode 100644 src/main/java/ch/njol/skript/expressions/arithmetic/ArithmeticExpressionInfo.java create mode 100644 src/main/java/org/skriptlang/skript/lang/arithmetic/Arithmetics.java create mode 100644 src/main/java/org/skriptlang/skript/lang/arithmetic/DifferenceInfo.java create mode 100644 src/main/java/org/skriptlang/skript/lang/arithmetic/Operation.java create mode 100644 src/main/java/org/skriptlang/skript/lang/arithmetic/OperationInfo.java create mode 100644 src/main/java/org/skriptlang/skript/lang/arithmetic/Operator.java create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprArithmetic.sk diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 291ebff5ae4..d0a2c2d05ab 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -26,6 +26,7 @@ import ch.njol.skript.classes.data.DefaultComparators; import ch.njol.skript.classes.data.DefaultConverters; import ch.njol.skript.classes.data.DefaultFunctions; +import ch.njol.skript.classes.data.DefaultOperations; import ch.njol.skript.classes.data.JavaClasses; import ch.njol.skript.classes.data.SkriptClasses; import ch.njol.skript.command.Commands; @@ -535,6 +536,7 @@ public void onEnable() { new DefaultComparators(); new DefaultConverters(); new DefaultFunctions(); + new DefaultOperations(); ChatMessages.registerListeners(); diff --git a/src/main/java/ch/njol/skript/classes/Arithmetic.java b/src/main/java/ch/njol/skript/classes/Arithmetic.java index 85461a67044..2365c3a64da 100644 --- a/src/main/java/ch/njol/skript/classes/Arithmetic.java +++ b/src/main/java/ch/njol/skript/classes/Arithmetic.java @@ -24,6 +24,7 @@ * @param <A> the type of the absolute value * @param <R> the type of the relative value */ +@Deprecated public interface Arithmetic<A, R> { public R difference(A first, A second); diff --git a/src/main/java/ch/njol/skript/classes/ClassInfo.java b/src/main/java/ch/njol/skript/classes/ClassInfo.java index 253ca2d3cb2..30211480a05 100644 --- a/src/main/java/ch/njol/skript/classes/ClassInfo.java +++ b/src/main/java/ch/njol/skript/classes/ClassInfo.java @@ -37,6 +37,8 @@ import java.util.function.Supplier; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; +import org.skriptlang.skript.lang.arithmetic.Operator; +import org.skriptlang.skript.lang.arithmetic.Arithmetics; /** * @author Peter Güttinger @@ -222,11 +224,19 @@ public ClassInfo<T> changer(final Changer<? super T> changer) { this.changer = changer; return this; } - + + @Deprecated + @SuppressWarnings("unchecked") public <R> ClassInfo<T> math(final Class<R> relativeType, final Arithmetic<? super T, R> math) { assert this.math == null; this.math = math; mathRelativeType = relativeType; + Arithmetics.registerOperation(Operator.ADDITION, c, relativeType, (left, right) -> (T) math.add(left, right)); + Arithmetics.registerOperation(Operator.SUBTRACTION, c, relativeType, (left, right) -> (T) math.subtract(left, right)); + Arithmetics.registerOperation(Operator.MULTIPLICATION, c, relativeType, (left, right) -> (T) math.multiply(left, right)); + Arithmetics.registerOperation(Operator.DIVISION, c, relativeType, (left, right) -> (T) math.divide(left, right)); + Arithmetics.registerOperation(Operator.EXPONENTIATION, c, relativeType, (left, right) -> (T) math.power(left, right)); + Arithmetics.registerDifference(c, relativeType, math::difference); return this; } @@ -387,16 +397,19 @@ public Class<?> getSerializeAs() { } @Nullable + @Deprecated public Arithmetic<? super T, ?> getMath() { return math; } @Nullable + @Deprecated public <R> Arithmetic<T, R> getRelativeMath() { return (Arithmetic<T, R>) math; } @Nullable + @Deprecated public Class<?> getMathRelativeType() { return mathRelativeType; } diff --git a/src/main/java/ch/njol/skript/classes/NumberArithmetic.java b/src/main/java/ch/njol/skript/classes/NumberArithmetic.java index 377e3feccce..71c035ebd7d 100644 --- a/src/main/java/ch/njol/skript/classes/NumberArithmetic.java +++ b/src/main/java/ch/njol/skript/classes/NumberArithmetic.java @@ -21,6 +21,7 @@ /** * @author Peter Güttinger */ +@Deprecated public class NumberArithmetic implements Arithmetic<Number, Number> { @Override diff --git a/src/main/java/ch/njol/skript/classes/VectorArithmethic.java b/src/main/java/ch/njol/skript/classes/VectorArithmethic.java index d907075357f..5bdac60af9a 100644 --- a/src/main/java/ch/njol/skript/classes/VectorArithmethic.java +++ b/src/main/java/ch/njol/skript/classes/VectorArithmethic.java @@ -23,6 +23,7 @@ /** * @author bi0qaw */ +@Deprecated public class VectorArithmethic implements Arithmetic<Vector, Vector> { @Override diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java b/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java new file mode 100644 index 00000000000..d3f51b29bb9 --- /dev/null +++ b/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java @@ -0,0 +1,119 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.classes.data; + +import ch.njol.skript.util.Date; +import ch.njol.skript.util.Timespan; +import ch.njol.skript.util.Utils; +import org.bukkit.util.Vector; +import org.skriptlang.skript.lang.arithmetic.Arithmetics; +import org.skriptlang.skript.lang.arithmetic.Operator; + +public class DefaultOperations { + + static { + // Number - Number + Arithmetics.registerOperation(Operator.ADDITION, Number.class, (left, right) -> { + if (Utils.isInteger(left, right)) + return left.longValue() + right.longValue(); + return left.doubleValue() + right.doubleValue(); + }); + Arithmetics.registerOperation(Operator.SUBTRACTION, Number.class, (left, right) -> { + if (Utils.isInteger(left, right)) + return left.longValue() - right.longValue(); + return left.doubleValue() - right.doubleValue(); + }); + Arithmetics.registerOperation(Operator.MULTIPLICATION, Number.class, (left, right) -> { + if (Utils.isInteger(left, right)) + return left.longValue() * right.longValue(); + return left.doubleValue() * right.doubleValue(); + }); + Arithmetics.registerOperation(Operator.DIVISION, Number.class, (left, right) -> left.doubleValue() / right.doubleValue()); + Arithmetics.registerOperation(Operator.EXPONENTIATION, Number.class, (left, right) -> { + if (Utils.isInteger(left, right) && right.longValue() >= 0) + return (long) Math.pow(left.longValue(), right.longValue()); + return Math.pow(left.doubleValue(), right.doubleValue()); + }); + Arithmetics.registerDifference(Number.class, (left, right) -> { + if (Utils.isInteger(left, right)) + return Math.abs(left.longValue() - right.longValue()); + return Math.abs(left.doubleValue() - right.doubleValue()); + }); + Arithmetics.registerDefaultValue(Number.class, () -> 0L); + + // Vector - Vector + Arithmetics.registerOperation(Operator.ADDITION, Vector.class, (left, right) -> left.clone().add(right)); + Arithmetics.registerOperation(Operator.SUBTRACTION, Vector.class, (left, right) -> left.clone().subtract(right)); + Arithmetics.registerOperation(Operator.MULTIPLICATION, Vector.class, (left, right) -> left.clone().multiply(right)); + Arithmetics.registerOperation(Operator.DIVISION, Vector.class, (left, right) -> left.clone().divide(right)); + Arithmetics.registerDifference(Vector.class, + (left, right) -> new Vector(Math.abs(left.getX() - right.getX()), Math.abs(left.getY() - right.getY()), Math.abs(left.getZ() - right.getZ()))); + Arithmetics.registerDefaultValue(Vector.class, Vector::new); + + // Vector - Number + // Number - Vector + Arithmetics.registerOperation(Operator.MULTIPLICATION, Vector.class, Number.class, (left, right) -> left.clone().multiply(right.doubleValue()), (left, right) -> { + double number = left.doubleValue(); + Vector leftVector = new Vector(number, number, number); + return leftVector.multiply(right); + }); + Arithmetics.registerOperation(Operator.DIVISION, Vector.class, Number.class, (left, right) -> { + double number = right.doubleValue(); + Vector rightVector = new Vector(number, number, number); + return left.clone().divide(rightVector); + }, (left, right) -> { + double number = left.doubleValue(); + Vector leftVector = new Vector(number, number, number); + return leftVector.divide(right); + }); + + // Timespan - Timespan + Arithmetics.registerOperation(Operator.ADDITION, Timespan.class, (left, right) -> new Timespan(left.getMilliSeconds() + right.getMilliSeconds())); + Arithmetics.registerOperation(Operator.SUBTRACTION, Timespan.class, (left, right) -> new Timespan(Math.max(0, left.getMilliSeconds() - right.getMilliSeconds()))); + Arithmetics.registerDifference(Timespan.class, (left, right) -> new Timespan(Math.abs(left.getMilliSeconds() - right.getMilliSeconds()))); + Arithmetics.registerDefaultValue(Timespan.class, Timespan::new); + + // Timespan - Number + // Number - Timespan + Arithmetics.registerOperation(Operator.MULTIPLICATION, Timespan.class, Number.class, (left, right) -> { + long scalar = right.longValue(); + if (scalar < 0) + return null; + return new Timespan(left.getMilliSeconds() * scalar); + }, (left, right) -> { + long scalar = left.longValue(); + if (scalar < 0) + return null; + return new Timespan(scalar * right.getMilliSeconds()); + }); + Arithmetics.registerOperation(Operator.DIVISION, Timespan.class, Number.class, (left, right) -> { + long scalar = right.longValue(); + if (scalar <= 0) + return null; + return new Timespan(left.getMilliSeconds() / scalar); + }); + + // Date - Timespan + Arithmetics.registerOperation(Operator.ADDITION, Date.class, Timespan.class, Date::plus); + Arithmetics.registerOperation(Operator.SUBTRACTION, Date.class, Timespan.class, Date::minus); + Arithmetics.registerDifference(Date.class, Timespan.class, Date::difference); + + } + +} diff --git a/src/main/java/ch/njol/skript/classes/data/JavaClasses.java b/src/main/java/ch/njol/skript/classes/data/JavaClasses.java index 237b5db732c..8bf9f32756b 100644 --- a/src/main/java/ch/njol/skript/classes/data/JavaClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/JavaClasses.java @@ -20,7 +20,6 @@ import ch.njol.skript.SkriptConfig; import ch.njol.skript.classes.ClassInfo; -import ch.njol.skript.classes.NumberArithmetic; import ch.njol.skript.classes.Parser; import ch.njol.skript.classes.Serializer; import ch.njol.skript.lang.ParseContext; @@ -125,7 +124,7 @@ public Number deserialize(String s) { public boolean mustSyncDeserialization() { return false; } - }).math(Number.class, new NumberArithmetic())); + })); Classes.registerClass(new ClassInfo<>(Long.class, "long") .user("int(eger)?s?") @@ -184,7 +183,7 @@ public Long deserialize(String s) { public boolean mustSyncDeserialization() { return false; } - }).math(Number.class, new NumberArithmetic())); + })); Classes.registerClass(new ClassInfo<>(Integer.class, "integer") .name(ClassInfo.NO_DOC) @@ -241,7 +240,7 @@ public Integer deserialize(String s) { public boolean mustSyncDeserialization() { return false; } - }).math(Number.class, new NumberArithmetic())); + })); Classes.registerClass(new ClassInfo<>(Double.class, "double") .name(ClassInfo.NO_DOC) @@ -303,7 +302,7 @@ public Double deserialize(String s) { public boolean mustSyncDeserialization() { return false; } - }).math(Number.class, new NumberArithmetic())); + })); Classes.registerClass(new ClassInfo<>(Float.class, "float") .name(ClassInfo.NO_DOC) @@ -364,7 +363,7 @@ public Float deserialize(String s) { public boolean mustSyncDeserialization() { return false; } - }).math(Number.class, new NumberArithmetic())); + })); Classes.registerClass(new ClassInfo<>(Boolean.class, "boolean") .user("booleans?") @@ -486,7 +485,7 @@ public Short deserialize(String s) { public boolean mustSyncDeserialization() { return false; } - }).math(Number.class, new NumberArithmetic())); + })); Classes.registerClass(new ClassInfo<>(Byte.class, "byte") .name(ClassInfo.NO_DOC) @@ -543,7 +542,7 @@ public Byte deserialize(String s) { public boolean mustSyncDeserialization() { return false; } - }).math(Number.class, new NumberArithmetic())); + })); Classes.registerClass(new ClassInfo<>(String.class, "string") .user("(text|string)s?") diff --git a/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java b/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java index 55727c673a6..39dc6447fa5 100644 --- a/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java @@ -34,7 +34,6 @@ import ch.njol.skript.aliases.ItemType; import ch.njol.skript.bukkitutil.EnchantmentUtils; import ch.njol.skript.bukkitutil.ItemUtils; -import ch.njol.skript.classes.Arithmetic; import ch.njol.skript.classes.Changer; import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.classes.EnumSerializer; @@ -315,38 +314,7 @@ public String toString(final Timespan t, final int flags) { public String toVariableNameString(final Timespan o) { return "timespan:" + o.getMilliSeconds(); } - }).serializer(new YggdrasilSerializer<>()) - .math(Timespan.class, new Arithmetic<Timespan, Timespan>() { - @Override - public Timespan difference(final Timespan t1, final Timespan t2) { - return new Timespan(Math.abs(t1.getMilliSeconds() - t2.getMilliSeconds())); - } - - @Override - public Timespan add(final Timespan value, final Timespan difference) { - return new Timespan(value.getMilliSeconds() + difference.getMilliSeconds()); - } - - @Override - public Timespan subtract(final Timespan value, final Timespan difference) { - return new Timespan(Math.max(0, value.getMilliSeconds() - difference.getMilliSeconds())); - } - - @Override - public Timespan multiply(Timespan value, Timespan multiplier) { - throw new UnsupportedOperationException(); - } - - @Override - public Timespan divide(Timespan value, Timespan divider) { - throw new UnsupportedOperationException(); - } - - @Override - public Timespan power(Timespan value, Timespan exponent) { - throw new UnsupportedOperationException(); - } - })); + }).serializer(new YggdrasilSerializer<>())); // TODO remove Classes.registerClass(new ClassInfo<>(Timeperiod.class, "timeperiod") @@ -408,38 +376,7 @@ public String toVariableNameString(final Timeperiod o) { "subtract a day from {_yesterday}", "# now {_yesterday} represents the date 24 hours before now") .since("1.4") - .serializer(new YggdrasilSerializer<>()) - .math(Timespan.class, new Arithmetic<Date, Timespan>() { - @Override - public Timespan difference(final Date first, final Date second) { - return first.difference(second); - } - - @Override - public Date add(final Date value, final Timespan difference) { - return new Date(value.getTimestamp() + difference.getMilliSeconds()); - } - - @Override - public Date subtract(final Date value, final Timespan difference) { - return new Date(value.getTimestamp() - difference.getMilliSeconds()); - } - - @Override - public Date multiply(Date value, Timespan multiplier) { - throw new UnsupportedOperationException(); - } - - @Override - public Date divide(Date value, Timespan divider) { - throw new UnsupportedOperationException(); - } - - @Override - public Date power(Date value, Timespan exponent) { - throw new UnsupportedOperationException(); - } - })); + .serializer(new YggdrasilSerializer<>())); Classes.registerClass(new ClassInfo<>(Direction.class, "direction") .user("directions?") diff --git a/src/main/java/ch/njol/skript/expressions/ExprBurnCookTime.java b/src/main/java/ch/njol/skript/expressions/ExprBurnCookTime.java index 4f1468c995a..3763d242dc1 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprBurnCookTime.java +++ b/src/main/java/ch/njol/skript/expressions/ExprBurnCookTime.java @@ -21,6 +21,7 @@ import java.util.Arrays; import java.util.function.Function; +import ch.njol.skript.classes.Changer.ChangeMode; import org.bukkit.block.Block; import org.bukkit.block.Furnace; import org.bukkit.event.Event; @@ -30,9 +31,7 @@ import ch.njol.skript.Skript; import ch.njol.skript.aliases.Aliases; import ch.njol.skript.aliases.ItemType; -import ch.njol.skript.classes.Arithmetic; import ch.njol.skript.classes.Changer; -import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; @@ -41,25 +40,30 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser; -import ch.njol.skript.registrations.DefaultClasses; import ch.njol.skript.util.Timespan; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; +import org.skriptlang.skript.lang.arithmetic.Operator; +import org.skriptlang.skript.lang.arithmetic.Arithmetics; @Name("Burn/Cook Time") -@Description({"The time a furnace takes to burn an item in a <a href='events.html#fuel_burn'>fuel burn</a> event.", - "Can also be used to change the burn/cook time of a placed furnace."}) -@Examples({"on fuel burn:", - " if fuel slot is coal:", - " set burning time to 1 tick"}) +@Description({ + "The time a furnace takes to burn an item in a <a href='events.html#fuel_burn'>fuel burn</a> event.", + "Can also be used to change the burn/cook time of a placed furnace." +}) +@Examples({ + "on fuel burn:", + "\tif fuel slot is coal:", + "\t\tset burning time to 1 tick" +}) @Since("2.3") public class ExprBurnCookTime extends PropertyExpression<Block, Timespan> { static { Skript.registerExpression(ExprBurnCookTime.class, Timespan.class, ExpressionType.PROPERTY, "[the] burn[ing] time", - "[the] (burn|1¦cook)[ing] time of %blocks%", - "%blocks%'[s] (burn|1¦cook)[ing] time"); + "[the] (burn|1:cook)[ing] time of %blocks%", + "%blocks%'[s] (burn|1:cook)[ing] time"); } static final ItemType anyFurnace = Aliases.javaItemType("any furnace"); @@ -81,95 +85,85 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye } @Override - protected Timespan[] get(Event e, Block[] source) { + protected Timespan[] get(Event event, Block[] source) { if (isEvent) { - if (!(e instanceof FurnaceBurnEvent)) - return null; - - return CollectionUtils.array(Timespan.fromTicks(((FurnaceBurnEvent) e).getBurnTime())); + if (!(event instanceof FurnaceBurnEvent)) + return new Timespan[0]; + return CollectionUtils.array(Timespan.fromTicks(((FurnaceBurnEvent) event).getBurnTime())); } else { - Timespan[] result = Arrays.stream(source) - .filter(block -> anyFurnace.isOfType(block)) + return Arrays.stream(source) + .filter(anyFurnace::isOfType) .map(furnace -> { Furnace state = (Furnace) furnace.getState(); return Timespan.fromTicks(cookTime ? state.getCookTime() : state.getBurnTime()); }) .toArray(Timespan[]::new); - assert result != null; - return result; } } - - @Override - public String toString(@Nullable Event e, boolean debug) { - return isEvent ? "the burning time" : "" + String.format("the %sing time of %s", cookTime ? "cook" : "burn", getExpr().toString(e, debug)); - } - - @Override - public Class<? extends Timespan> getReturnType() { - return Timespan.class; - } - @Override @Nullable - public Class<?>[] acceptChange(Changer.ChangeMode mode) { - if (mode == Changer.ChangeMode.ADD - || mode == Changer.ChangeMode.REMOVE - || mode == Changer.ChangeMode.SET) + public Class<?>[] acceptChange(ChangeMode mode) { + if (mode == ChangeMode.ADD || mode == ChangeMode.REMOVE || mode == ChangeMode.SET) return CollectionUtils.array(Timespan.class); return null; } @Override - public void change(Event e, @Nullable Object[] delta, Changer.ChangeMode mode) { + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { if (delta == null) return; Function<Timespan, Timespan> value = null; - ClassInfo<Timespan> ci = DefaultClasses.TIMESPAN; - Arithmetic<Timespan, Timespan> arithmetic = ci.getRelativeMath(); Timespan changed = (Timespan) delta[0]; - assert arithmetic != null; switch (mode) { case ADD: - value = (original) -> arithmetic.add(original, changed); + value = (original) -> Arithmetics.calculate(Operator.ADDITION, original, changed, Timespan.class); break; case REMOVE: - value = (original) -> arithmetic.difference(original, changed); + value = (original) -> Arithmetics.difference(original, changed, Timespan.class); break; case SET: value = (original) -> changed; break; - //$CASES-OMITTED$ default: assert false; break; } - assert value != null; // It isn't going to be null but the compiler complains so - - if (isEvent) { - if (!(e instanceof FurnaceBurnEvent)) + if (isEvent) { + if (!(event instanceof FurnaceBurnEvent)) return; - FurnaceBurnEvent event = (FurnaceBurnEvent) e; - event.setBurnTime((int) value.apply(Timespan.fromTicks(event.getBurnTime())).getTicks()); + FurnaceBurnEvent burnEvent = (FurnaceBurnEvent) event; + burnEvent.setBurnTime((int) value.apply(Timespan.fromTicks(burnEvent.getBurnTime())).getTicks()); return; } - for (Block block : getExpr().getArray(e)) { + for (Block block : getExpr().getArray(event)) { if (!anyFurnace.isOfType(block)) continue; Furnace furnace = (Furnace) block.getState(); long time = value.apply(Timespan.fromTicks(cookTime ? furnace.getCookTime() : furnace.getBurnTime())).getTicks(); - if (cookTime) + if (cookTime) { furnace.setCookTime((short) time); - else + } else { furnace.setBurnTime((short) time); + } furnace.update(); } } + + @Override + public Class<? extends Timespan> getReturnType() { + return Timespan.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return isEvent ? "the burning time" : String.format("the %sing time of %s", cookTime ? "cook" : "burn", getExpr().toString(event, debug)); + } + } diff --git a/src/main/java/ch/njol/skript/expressions/ExprDifference.java b/src/main/java/ch/njol/skript/expressions/ExprDifference.java index cfaf10f07df..ef77023912a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprDifference.java +++ b/src/main/java/ch/njol/skript/expressions/ExprDifference.java @@ -36,6 +36,8 @@ import ch.njol.util.Kleenean; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.arithmetic.Arithmetics; +import org.skriptlang.skript.lang.arithmetic.DifferenceInfo; import java.lang.reflect.Array; @@ -62,9 +64,7 @@ public class ExprDifference extends SimpleExpression<Object> { @Nullable @SuppressWarnings("rawtypes") - private Arithmetic math; - @SuppressWarnings("NotNullFieldNotInitialized") - private Class<?> relativeType; + private DifferenceInfo differenceInfo; @Override @SuppressWarnings("unchecked") @@ -77,11 +77,11 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye Class<?> firstReturnType = first.getReturnType(); Class<?> secondReturnType = second.getReturnType(); - ClassInfo<?> classInfo = Classes.getSuperClassInfo(Utils.getSuperType(firstReturnType, secondReturnType)); + Class<?> superType = Utils.getSuperType(firstReturnType, secondReturnType); boolean fail = false; - if (classInfo.getC() == Object.class && (firstReturnType != Object.class || secondReturnType != Object.class)) { + if (superType == Object.class && (firstReturnType != Object.class || secondReturnType != Object.class)) { // We may not have a way to obtain the difference between these two values. Further checks needed. // These two types are unrelated, meaning conversion is needed @@ -91,8 +91,8 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye fail = true; // Attempt to use first type's math - classInfo = Classes.getSuperClassInfo(firstReturnType); - if (classInfo.getMath() != null) { // Try to convert second to first + differenceInfo = Arithmetics.getDifferenceInfo(firstReturnType); + if (differenceInfo != null) { // Try to convert second to first Expression<?> secondConverted = second.getConvertedExpression(firstReturnType); if (secondConverted != null) { second = secondConverted; @@ -101,8 +101,8 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye } if (fail) { // First type won't work, try second type - classInfo = Classes.getSuperClassInfo(secondReturnType); - if (classInfo.getMath() != null) { // Try to convert first to second + differenceInfo = Arithmetics.getDifferenceInfo(secondReturnType); + if (differenceInfo != null) { // Try to convert first to second Expression<?> firstConverted = first.getConvertedExpression(secondReturnType); if (firstConverted != null) { first = firstConverted; @@ -128,19 +128,14 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye if (converted == null) { // It's unlikely that these two can be compared fail = true; } else { // Attempt to resolve a better class info - classInfo = Classes.getSuperClassInfo(Utils.getSuperType(first.getReturnType(), second.getReturnType())); + superType = Utils.getSuperType(first.getReturnType(), second.getReturnType()); } } } - if (classInfo.getC() == Object.class) { // We will have to determine the type during runtime - relativeType = Object.class; - } else if (classInfo.getMath() == null || classInfo.getMathRelativeType() == null) { + if (superType != Object.class && (differenceInfo = Arithmetics.getDifferenceInfo(superType)) == null) { fail = true; - } else { - math = classInfo.getMath(); - relativeType = classInfo.getMathRelativeType(); } if (fail) { @@ -156,7 +151,7 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable - @SuppressWarnings({"unchecked", "rawtypes"}) + @SuppressWarnings("unchecked") protected Object[] get(Event event) { Object first = this.first.getSingle(event); Object second = this.second.getSingle(event); @@ -164,26 +159,20 @@ protected Object[] get(Event event) { return new Object[0]; } - Arithmetic math = this.math; - Class<?> relativeType = this.relativeType; - - if (relativeType == Object.class) { // Try to determine now that actual types are known - ClassInfo<?> info = Classes.getSuperClassInfo(Utils.getSuperType(first.getClass(), second.getClass())); - math = info.getMath(); - if (math == null) { // User did something stupid, just return <none> for them + DifferenceInfo differenceInfo = this.differenceInfo; + if (differenceInfo == null) { // Try to determine now that actual types are known + Class<?> superType = Utils.getSuperType(first.getClass(), second.getClass()); + differenceInfo = Arithmetics.getDifferenceInfo(superType); + if (differenceInfo == null) { // User did something stupid, just return <none> for them return new Object[0]; } - relativeType = info.getMathRelativeType(); - if (relativeType == null) { // Unlikely to be the case, but math is not null meaning we can calculate the difference - relativeType = Object.class; - } } - Object[] one = (Object[]) Array.newInstance(relativeType, 1); + assert differenceInfo != null; // it cannot be null here + Object[] one = (Object[]) Array.newInstance(differenceInfo.getReturnType(), 1); + + one[0] = differenceInfo.getOperation().calculate(first, second); - assert math != null; // it cannot be null here - one[0] = math.difference(first, second); - return one; } @@ -194,7 +183,7 @@ public boolean isSingle() { @Override public Class<?> getReturnType() { - return relativeType; + return differenceInfo == null ? Object.class : differenceInfo.getReturnType(); } @Override diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java b/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java index 312b1d15b32..93962bb3fee 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java @@ -18,6 +18,9 @@ */ package ch.njol.skript.expressions; +import ch.njol.util.coll.CollectionUtils; +import org.skriptlang.skript.lang.arithmetic.Arithmetics; +import org.skriptlang.skript.lang.arithmetic.Operator; import org.bukkit.event.Event; import org.bukkit.util.Vector; import org.eclipse.jdt.annotation.Nullable; @@ -33,7 +36,7 @@ import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.skript.util.Patterns; import ch.njol.util.Kleenean; -import ch.njol.util.coll.CollectionUtils; +import org.skriptlang.skript.lang.arithmetic.Arithmetics; @Name("Vectors - Arithmetic") @Description("Arithmetic expressions for vectors.") @@ -44,64 +47,23 @@ "set {_v} to {_v} ** {_v}", "set {_v} to {_v} // {_v}" }) -@Since("2.2-dev28") +@Since("2.2-dev28, INSERT VERSION (deprecation)") +@Deprecated public class ExprVectorArithmetic extends SimpleExpression<Vector> { - private enum Operator { - PLUS("++") { - @Override - public Vector calculate(final Vector first, final Vector second) { - return first.clone().add(second); - } - }, - MINUS("--") { - @Override - public Vector calculate(final Vector first, final Vector second) { - return first.clone().subtract(second); - } - }, - MULT("**") { - @Override - public Vector calculate(final Vector first, final Vector second) { - return first.clone().multiply(second); - } - }, - DIV("//") { - @Override - public Vector calculate(final Vector first, final Vector second) { - return first.clone().divide(second); - } - }; - - public final String sign; - - Operator(final String sign) { - this.sign = sign; - } - - public abstract Vector calculate(Vector first, Vector second); - - @Override - public String toString() { - return sign; - } - } - private final static Patterns<Operator> patterns = new Patterns<>(new Object[][] { - {"%vector%[ ]++[ ]%vector%", Operator.PLUS}, - {"%vector%[ ]--[ ]%vector%", Operator.MINUS}, - {"%vector%[ ]**[ ]%vector%", Operator.MULT}, - {"%vector%[ ]//[ ]%vector%", Operator.DIV} + {"%vector%[ ]++[ ]%vector%", Operator.ADDITION}, + {"%vector%[ ]--[ ]%vector%", Operator.SUBTRACTION}, + {"%vector%[ ]**[ ]%vector%", Operator.MULTIPLICATION}, + {"%vector%[ ]//[ ]%vector%", Operator.DIVISION} }); static { Skript.registerExpression(ExprVectorArithmetic.class, Vector.class, ExpressionType.SIMPLE, patterns.getPatterns()); } - @SuppressWarnings("null") private Expression<Vector> first, second; - @SuppressWarnings("null") private Operator operator; @Override @@ -110,6 +72,8 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye first = (Expression<Vector>) exprs[0]; second = (Expression<Vector>) exprs[1]; operator = patterns.getInfo(matchedPattern); + Skript.warning("This expression was deprecated in favor of the arithmetic expression, and will be removed in the future." + + " Please use that instead. E.g. 'vector(2, 4, 1) + vector(5, 2, 3)'"); return true; } @@ -117,7 +81,7 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye protected Vector[] get(Event event) { Vector first = this.first.getOptionalSingle(event).orElse(new Vector()); Vector second = this.second.getOptionalSingle(event).orElse(new Vector()); - return CollectionUtils.array(operator.calculate(first, second)); + return CollectionUtils.array(Arithmetics.calculate(operator, first, second, Vector.class)); } @Override diff --git a/src/main/java/ch/njol/skript/expressions/arithmetic/ArithmeticChain.java b/src/main/java/ch/njol/skript/expressions/arithmetic/ArithmeticChain.java index 8b248c73833..6a68491b403 100644 --- a/src/main/java/ch/njol/skript/expressions/arithmetic/ArithmeticChain.java +++ b/src/main/java/ch/njol/skript/expressions/arithmetic/ArithmeticChain.java @@ -19,59 +19,144 @@ package ch.njol.skript.expressions.arithmetic; import java.util.List; +import java.util.function.Function; import org.bukkit.event.Event; import ch.njol.skript.lang.Expression; import ch.njol.skript.util.Utils; import ch.njol.util.Checker; +import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.arithmetic.Operation; +import org.skriptlang.skript.lang.arithmetic.OperationInfo; +import org.skriptlang.skript.lang.arithmetic.Operator; +import org.skriptlang.skript.lang.arithmetic.Arithmetics; +import org.skriptlang.skript.lang.converter.Converters; + +/** + * Represents a chain of arithmetic operations between two operands. + * + * @param <L> the type of the left operand + * @param <R> the type of the right operand + * @param <T> the return type of the operation + */ +public class ArithmeticChain<L, R, T> implements ArithmeticGettable<T> { -public class ArithmeticChain implements ArithmeticGettable { - @SuppressWarnings("unchecked") - private static final Checker<Object>[] CHECKERS = new Checker[]{ - o -> o.equals(Operator.PLUS) || o.equals(Operator.MINUS), - o -> o.equals(Operator.MULT) || o.equals(Operator.DIV), - o -> o.equals(Operator.EXP) + private static final Checker<Object>[] CHECKERS = new Checker[] { + o -> o.equals(Operator.ADDITION) || o.equals(Operator.SUBTRACTION), + o -> o.equals(Operator.MULTIPLICATION) || o.equals(Operator.DIVISION), + o -> o.equals(Operator.EXPONENTIATION) }; - - private final ArithmeticGettable left; + + private final ArithmeticGettable<L> left; + private final ArithmeticGettable<R> right; private final Operator operator; - private final ArithmeticGettable right; - - public ArithmeticChain(ArithmeticGettable left, Operator operator, ArithmeticGettable right) { + private final Class<? extends T> returnType; + @Nullable + private OperationInfo<? extends L, ? extends R, ? extends T> operationInfo; + + public ArithmeticChain(ArithmeticGettable<L> left, Operator operator, ArithmeticGettable<R> right, @Nullable OperationInfo<L, R, T> operationInfo) { this.left = left; - this.operator = operator; this.right = right; + this.operator = operator; + this.operationInfo = operationInfo; + this.returnType = operationInfo != null ? operationInfo.getReturnType() : (Class<? extends T>) Object.class; } - + @Override - public Number get(Event event, boolean integer) { - return operator.calculate(left.get(event, integer), right.get(event, integer), integer); + @Nullable + @SuppressWarnings("unchecked") + public T get(Event event) { + L left = this.left.get(event); + if (left == null && this.left instanceof ArithmeticChain) + return null; + + R right = this.right.get(event); + if (right == null && this.right instanceof ArithmeticChain) + return null; + + Class<? extends L> leftClass = left != null ? (Class<? extends L>) left.getClass() : this.left.getReturnType(); + Class<? extends R> rightClass = right != null ? (Class<? extends R>) right.getClass() : this.right.getReturnType(); + + if (leftClass == Object.class && rightClass == Object.class) + return null; + + if (left == null && leftClass == Object.class) { + operationInfo = lookupOperationInfo(rightClass, OperationInfo::getRight); + } else if (right == null && rightClass == Object.class) { + operationInfo = lookupOperationInfo(leftClass, OperationInfo::getLeft); + } else if (operationInfo == null) { + operationInfo = Arithmetics.lookupOperationInfo(operator, leftClass, rightClass, returnType); + } + + if (operationInfo == null) + return null; + + left = left != null ? left : Arithmetics.getDefaultValue(operationInfo.getLeft()); + if (left == null) + return null; + right = right != null ? right : Arithmetics.getDefaultValue(operationInfo.getRight()); + if (right == null) + return null; + + return ((Operation<L, R, T>) operationInfo.getOperation()).calculate(left, right); } - + + @Nullable @SuppressWarnings("unchecked") - public static ArithmeticGettable parse(List<Object> chain) { + private OperationInfo<L, R, T> lookupOperationInfo(Class<?> anchor, Function<OperationInfo<?, ?, ?>, Class<?>> anchorFunction) { + OperationInfo<?, ?, ?> operationInfo = Arithmetics.lookupOperationInfo(operator, anchor, anchor); + if (operationInfo != null) + return (OperationInfo<L, R, T>) operationInfo; + + return (OperationInfo<L, R, T>) Arithmetics.getOperations(operator).stream() + .filter(info -> anchorFunction.apply(info).isAssignableFrom(anchor)) + .filter(info -> Converters.converterExists(info.getReturnType(), returnType)) + .reduce((info, info2) -> { + if (anchorFunction.apply(info2) == anchor) + return info2; + return info; + }) + .orElse(null); + } + + @Override + public Class<? extends T> getReturnType() { + return returnType; + } + + @Nullable + @SuppressWarnings("unchecked") + public static <L, R, T> ArithmeticGettable<T> parse(List<Object> chain) { for (Checker<Object> checker : CHECKERS) { int lastIndex = Utils.findLastIndex(chain, checker); - + if (lastIndex != -1) { - List<Object> leftChain = chain.subList(0, lastIndex); - ArithmeticGettable left = parse(leftChain); - + ArithmeticGettable<L> left = parse(chain.subList(0, lastIndex)); + Operator operator = (Operator) chain.get(lastIndex); - - List<Object> rightChain = chain.subList(lastIndex + 1, chain.size()); - ArithmeticGettable right = parse(rightChain); - - return new ArithmeticChain(left, operator, right); + + ArithmeticGettable<R> right = parse(chain.subList(lastIndex + 1, chain.size())); + + if (left == null || right == null) + return null; + + OperationInfo<L, R, T> operationInfo = null; + if (left.getReturnType() != Object.class && right.getReturnType() != Object.class) { + operationInfo = (OperationInfo<L, R, T>) Arithmetics.lookupOperationInfo(operator, left.getReturnType(), right.getReturnType()); + if (operationInfo == null) + return null; + } + + return new ArithmeticChain<>(left, operator, right, operationInfo); } } - + if (chain.size() != 1) throw new IllegalStateException(); - - return new NumberExpressionInfo((Expression<? extends Number>) chain.get(0)); + + return new ArithmeticExpressionInfo<>((Expression<T>) chain.get(0)); } - + } diff --git a/src/main/java/ch/njol/skript/expressions/arithmetic/ArithmeticExpressionInfo.java b/src/main/java/ch/njol/skript/expressions/arithmetic/ArithmeticExpressionInfo.java new file mode 100644 index 00000000000..23ced6a2f1f --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/arithmetic/ArithmeticExpressionInfo.java @@ -0,0 +1,47 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions.arithmetic; + +import org.bukkit.event.Event; + +import ch.njol.skript.lang.Expression; +import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.arithmetic.Arithmetics; + +public class ArithmeticExpressionInfo<T> implements ArithmeticGettable<T> { + + private final Expression<? extends T> expression; + + public ArithmeticExpressionInfo(Expression<? extends T> expression) { + this.expression = expression; + } + + @Override + @Nullable + public T get(Event event) { + T object = expression.getSingle(event); + return object == null ? Arithmetics.getDefaultValue(expression.getReturnType()) : object; + } + + @Override + public Class<? extends T> getReturnType() { + return expression.getReturnType(); + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/arithmetic/ArithmeticGettable.java b/src/main/java/ch/njol/skript/expressions/arithmetic/ArithmeticGettable.java index 57d1d5cd08d..0c995c26f1b 100644 --- a/src/main/java/ch/njol/skript/expressions/arithmetic/ArithmeticGettable.java +++ b/src/main/java/ch/njol/skript/expressions/arithmetic/ArithmeticGettable.java @@ -19,9 +19,16 @@ package ch.njol.skript.expressions.arithmetic; import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +/** + * @param <T> The return type of the gettable + */ +public interface ArithmeticGettable<T> { + + @Nullable + T get(Event event); + + Class<? extends T> getReturnType(); -public interface ArithmeticGettable { - - Number get(Event event, boolean integer); - } diff --git a/src/main/java/ch/njol/skript/expressions/arithmetic/ExprArithmetic.java b/src/main/java/ch/njol/skript/expressions/arithmetic/ExprArithmetic.java index 8f022e8fda0..baa160890c8 100644 --- a/src/main/java/ch/njol/skript/expressions/arithmetic/ExprArithmetic.java +++ b/src/main/java/ch/njol/skript/expressions/arithmetic/ExprArithmetic.java @@ -18,14 +18,8 @@ */ package ch.njol.skript.expressions.arithmetic; -import java.lang.reflect.Array; -import java.util.ArrayList; -import java.util.List; - -import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; +import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; @@ -36,159 +30,204 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.skript.lang.util.SimpleLiteral; +import ch.njol.skript.registrations.Classes; +import ch.njol.skript.util.LiteralUtils; import ch.njol.skript.util.Patterns; import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.arithmetic.Arithmetics; +import org.skriptlang.skript.lang.arithmetic.OperationInfo; +import org.skriptlang.skript.lang.arithmetic.Operator; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.List; -/** - * @author Peter Güttinger - */ @Name("Arithmetic") @Description("Arithmetic expressions, e.g. 1 + 2, (health of player - 2) / 3, etc.") @Examples({"set the player's health to 10 - the player's health", - "loop (argument + 2) / 5 times:", - "\tmessage \"Two useless numbers: %loop-num * 2 - 5%, %2^loop-num - 1%\"", - "message \"You have %health of player * 2% half hearts of HP!\""}) + "loop (argument + 2) / 5 times:", + "\tmessage \"Two useless numbers: %loop-num * 2 - 5%, %2^loop-num - 1%\"", + "message \"You have %health of player * 2% half hearts of HP!\""}) @Since("1.4.2") @SuppressWarnings("null") -public class ExprArithmetic extends SimpleExpression<Number> { - +public class ExprArithmetic<L, R, T> extends SimpleExpression<T> { + private static final Class<?>[] INTEGER_CLASSES = {Long.class, Integer.class, Short.class, Byte.class}; - + private static class PatternInfo { public final Operator operator; public final boolean leftGrouped; public final boolean rightGrouped; - + public PatternInfo(Operator operator, boolean leftGrouped, boolean rightGrouped) { this.operator = operator; this.leftGrouped = leftGrouped; this.rightGrouped = rightGrouped; } } - - private final static Patterns<PatternInfo> patterns = new Patterns<>(new Object[][] { - - {"\\(%number%\\)[ ]+[ ]\\(%number%\\)", new PatternInfo(Operator.PLUS, true, true)}, - {"\\(%number%\\)[ ]+[ ]%number%", new PatternInfo(Operator.PLUS, true, false)}, - {"%number%[ ]+[ ]\\(%number%\\)", new PatternInfo(Operator.PLUS, false, true)}, - {"%number%[ ]+[ ]%number%", new PatternInfo(Operator.PLUS, false, false)}, - - {"\\(%number%\\)[ ]-[ ]\\(%number%\\)", new PatternInfo(Operator.MINUS, true, true)}, - {"\\(%number%\\)[ ]-[ ]%number%", new PatternInfo(Operator.MINUS, true, false)}, - {"%number%[ ]-[ ]\\(%number%\\)", new PatternInfo(Operator.MINUS, false, true)}, - {"%number%[ ]-[ ]%number%", new PatternInfo(Operator.MINUS, false, false)}, - - {"\\(%number%\\)[ ]*[ ]\\(%number%\\)", new PatternInfo(Operator.MULT, true, true)}, - {"\\(%number%\\)[ ]*[ ]%number%", new PatternInfo(Operator.MULT, true, false)}, - {"%number%[ ]*[ ]\\(%number%\\)", new PatternInfo(Operator.MULT, false, true)}, - {"%number%[ ]*[ ]%number%", new PatternInfo(Operator.MULT, false, false)}, - - {"\\(%number%\\)[ ]/[ ]\\(%number%\\)", new PatternInfo(Operator.DIV, true, true)}, - {"\\(%number%\\)[ ]/[ ]%number%", new PatternInfo(Operator.DIV, true, false)}, - {"%number%[ ]/[ ]\\(%number%\\)", new PatternInfo(Operator.DIV, false, true)}, - {"%number%[ ]/[ ]%number%", new PatternInfo(Operator.DIV, false, false)}, - - {"\\(%number%\\)[ ]^[ ]\\(%number%\\)", new PatternInfo(Operator.EXP, true, true)}, - {"\\(%number%\\)[ ]^[ ]%number%", new PatternInfo(Operator.EXP, true, false)}, - {"%number%[ ]^[ ]\\(%number%\\)", new PatternInfo(Operator.EXP, false, true)}, - {"%number%[ ]^[ ]%number%", new PatternInfo(Operator.EXP, false, false)}, - + + private static final Patterns<PatternInfo> patterns = new Patterns<>(new Object[][] { + + {"\\(%object%\\)[ ]+[ ]\\(%object%\\)", new PatternInfo(Operator.ADDITION, true, true)}, + {"\\(%object%\\)[ ]+[ ]%object%", new PatternInfo(Operator.ADDITION, true, false)}, + {"%object%[ ]+[ ]\\(%object%\\)", new PatternInfo(Operator.ADDITION, false, true)}, + {"%object%[ ]+[ ]%object%", new PatternInfo(Operator.ADDITION, false, false)}, + + {"\\(%object%\\)[ ]-[ ]\\(%object%\\)", new PatternInfo(Operator.SUBTRACTION, true, true)}, + {"\\(%object%\\)[ ]-[ ]%object%", new PatternInfo(Operator.SUBTRACTION, true, false)}, + {"%object%[ ]-[ ]\\(%object%\\)", new PatternInfo(Operator.SUBTRACTION, false, true)}, + {"%object%[ ]-[ ]%object%", new PatternInfo(Operator.SUBTRACTION, false, false)}, + + {"\\(%object%\\)[ ]*[ ]\\(%object%\\)", new PatternInfo(Operator.MULTIPLICATION, true, true)}, + {"\\(%object%\\)[ ]*[ ]%object%", new PatternInfo(Operator.MULTIPLICATION, true, false)}, + {"%object%[ ]*[ ]\\(%object%\\)", new PatternInfo(Operator.MULTIPLICATION, false, true)}, + {"%object%[ ]*[ ]%object%", new PatternInfo(Operator.MULTIPLICATION, false, false)}, + + {"\\(%object%\\)[ ]/[ ]\\(%object%\\)", new PatternInfo(Operator.DIVISION, true, true)}, + {"\\(%object%\\)[ ]/[ ]%object%", new PatternInfo(Operator.DIVISION, true, false)}, + {"%object%[ ]/[ ]\\(%object%\\)", new PatternInfo(Operator.DIVISION, false, true)}, + {"%object%[ ]/[ ]%object%", new PatternInfo(Operator.DIVISION, false, false)}, + + {"\\(%object%\\)[ ]^[ ]\\(%object%\\)", new PatternInfo(Operator.EXPONENTIATION, true, true)}, + {"\\(%object%\\)[ ]^[ ]%object%", new PatternInfo(Operator.EXPONENTIATION, true, false)}, + {"%object%[ ]^[ ]\\(%object%\\)", new PatternInfo(Operator.EXPONENTIATION, false, true)}, + {"%object%[ ]^[ ]%object%", new PatternInfo(Operator.EXPONENTIATION, false, false)}, + }); - + static { - Skript.registerExpression(ExprArithmetic.class, Number.class, ExpressionType.PATTERN_MATCHES_EVERYTHING, patterns.getPatterns()); + //noinspection unchecked + Skript.registerExpression(ExprArithmetic.class, Object.class, ExpressionType.PATTERN_MATCHES_EVERYTHING, patterns.getPatterns()); } - - @SuppressWarnings("null") - private Expression<? extends Number> first; - @SuppressWarnings("null") - private Expression<? extends Number> second; - @SuppressWarnings("null") - private Operator op; - - @SuppressWarnings("null") - private Class<? extends Number> returnType; - + + private Expression<L> first; + private Expression<R> second; + private Operator operator; + + private Class<? extends T> returnType; + // A chain of expressions and operators, alternating between the two. Always starts and ends with an expression. private final List<Object> chain = new ArrayList<>(); - + // A parsed chain, like a tree - private ArithmeticGettable arithmeticGettable; - - @SuppressWarnings({"unchecked", "null"}) + private ArithmeticGettable<? extends T> arithmeticGettable; + + private boolean leftGrouped, rightGrouped; + @Override - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { - first = (Expression<? extends Number>) exprs[0]; - second = (Expression<? extends Number>) exprs[1]; - + @SuppressWarnings({"ConstantConditions", "unchecked"}) + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + first = LiteralUtils.defendExpression(exprs[0]); + second = LiteralUtils.defendExpression(exprs[1]); + + if (!LiteralUtils.canInitSafely(first, second)) + return false; + + Class<? extends L> firstClass = first.getReturnType(); + Class<? extends R> secondClass = second.getReturnType(); + PatternInfo patternInfo = patterns.getInfo(matchedPattern); - op = patternInfo.operator; - - if (op == Operator.DIV || op == Operator.EXP) { - returnType = Double.class; + leftGrouped = patternInfo.leftGrouped; + rightGrouped = patternInfo.rightGrouped; + operator = patternInfo.operator; + + if (firstClass != Object.class && secondClass == Object.class && Arithmetics.getOperations(operator, firstClass).isEmpty()) { + // If the first class is known but doesn't have any operations, then we fail + return error(firstClass, secondClass); + } else if (firstClass == Object.class && secondClass != Object.class && Arithmetics.getOperations(operator).stream() + .noneMatch(info -> info.getRight().isAssignableFrom(secondClass))) { + // If the second class is known but doesn't have any operations, then we fail + return error(firstClass, secondClass); + } + + OperationInfo<L, R, T> operationInfo; + if (firstClass == Object.class || secondClass == Object.class) { + // If either of the types is unknown, then we resolve the operation at runtime + operationInfo = null; } else { - Class<?> firstReturnType = first.getReturnType(); - Class<?> secondReturnType = second.getReturnType(); - - boolean firstIsInt = false; - boolean secondIsInt = false; - for (final Class<?> i : INTEGER_CLASSES) { - firstIsInt |= i.isAssignableFrom(firstReturnType); - secondIsInt |= i.isAssignableFrom(secondReturnType); + operationInfo = (OperationInfo<L, R, T>) Arithmetics.lookupOperationInfo(operator, firstClass, secondClass); + if (operationInfo == null) // We error if we couldn't find an operation between the two types + return error(firstClass, secondClass); + } + + returnType = operationInfo == null ? (Class<? extends T>) Object.class : operationInfo.getReturnType(); + + if (Number.class.isAssignableFrom(returnType)) { + if (operator == Operator.DIVISION || operator == Operator.EXPONENTIATION) { + returnType = (Class<? extends T>) Double.class; + } else { + boolean firstIsInt = false; + boolean secondIsInt = false; + for (Class<?> i : INTEGER_CLASSES) { + firstIsInt |= i.isAssignableFrom(first.getReturnType()); + secondIsInt |= i.isAssignableFrom(second.getReturnType()); + } + + returnType = (Class<? extends T>) (firstIsInt && secondIsInt ? Long.class : Double.class); } - - returnType = firstIsInt && secondIsInt ? Long.class : Double.class; } - + // Chaining - if (first instanceof ExprArithmetic && !patternInfo.leftGrouped) { - chain.addAll(((ExprArithmetic) first).chain); + if (first instanceof ExprArithmetic && !leftGrouped) { + chain.addAll(((ExprArithmetic<?, ?, L>) first).chain); } else { chain.add(first); } - chain.add(op); - if (second instanceof ExprArithmetic && !patternInfo.rightGrouped) { - chain.addAll(((ExprArithmetic) second).chain); + chain.add(operator); + if (second instanceof ExprArithmetic && !rightGrouped) { + chain.addAll(((ExprArithmetic<?, ?, R>) second).chain); } else { chain.add(second); } - + arithmeticGettable = ArithmeticChain.parse(chain); - - return true; + return arithmeticGettable != null || error(firstClass, secondClass); } - - @SuppressWarnings("null") + @Override - protected Number[] get(final Event e) { - Number[] one = (Number[]) Array.newInstance(returnType, 1); - - one[0] = arithmeticGettable.get(e, returnType == Long.class); - + @SuppressWarnings("unchecked") + protected T[] get(Event event) { + T result = arithmeticGettable.get(event); + T[] one = (T[]) Array.newInstance(result == null ? returnType : result.getClass(), 1); + one[0] = result; return one; } - + + private boolean error(Class<?> firstClass, Class<?> secondClass) { + ClassInfo<?> first = Classes.getSuperClassInfo(firstClass), second = Classes.getSuperClassInfo(secondClass); + Skript.error(operator.getName() + " can't be performed on " + first.getName().withIndefiniteArticle() + " and " + second.getName().withIndefiniteArticle()); + return false; + } + @Override - public Class<? extends Number> getReturnType() { + public Class<? extends T> getReturnType() { return returnType; } - + @Override public boolean isSingle() { return true; } - + @Override - public String toString(final @Nullable Event e, final boolean debug) { - return first.toString(e, debug) + " " + op + " " + second.toString(e, debug); + public String toString(@Nullable Event event, boolean debug) { + String one = first.toString(event, debug); + String two = second.toString(event, debug); + if (leftGrouped) + one = '(' + one + ')'; + if (rightGrouped) + two = '(' + two + ')'; + return one + ' ' + operator + ' ' + two; } - - @SuppressWarnings("null") + @Override - public Expression<? extends Number> simplify() { + @SuppressWarnings("unchecked") + public Expression<? extends T> simplify() { if (first instanceof Literal && second instanceof Literal) - return new SimpleLiteral<>(getArray(null), Number.class, false); + return new SimpleLiteral<>(getArray(null), (Class<T>) getReturnType(), false); return this; } - + } diff --git a/src/main/java/ch/njol/skript/expressions/arithmetic/NumberExpressionInfo.java b/src/main/java/ch/njol/skript/expressions/arithmetic/NumberExpressionInfo.java index bd27a43d630..6c8ede2fff9 100644 --- a/src/main/java/ch/njol/skript/expressions/arithmetic/NumberExpressionInfo.java +++ b/src/main/java/ch/njol/skript/expressions/arithmetic/NumberExpressionInfo.java @@ -18,22 +18,30 @@ */ package ch.njol.skript.expressions.arithmetic; +import ch.njol.skript.lang.Expression; import org.bukkit.event.Event; -import ch.njol.skript.lang.Expression; +@Deprecated +public class NumberExpressionInfo implements ArithmeticGettable<Number> { -public class NumberExpressionInfo implements ArithmeticGettable { - private final Expression<? extends Number> expression; - + public NumberExpressionInfo(Expression<? extends Number> expression) { this.expression = expression; } - - @Override + public Number get(Event event, boolean integer) { + return get(event); + } + + @Override + public Number get(Event event) { Number number = expression.getSingle(event); return number != null ? number : 0; } - + + @Override + public Class<? extends Number> getReturnType() { + return Number.class; + } } diff --git a/src/main/java/ch/njol/skript/expressions/arithmetic/Operator.java b/src/main/java/ch/njol/skript/expressions/arithmetic/Operator.java index 486065328b4..d3b983358fd 100644 --- a/src/main/java/ch/njol/skript/expressions/arithmetic/Operator.java +++ b/src/main/java/ch/njol/skript/expressions/arithmetic/Operator.java @@ -19,6 +19,7 @@ package ch.njol.skript.expressions.arithmetic; @SuppressWarnings("UnnecessaryBoxing") +@Deprecated public enum Operator { PLUS('+') { diff --git a/src/main/java/ch/njol/skript/hooks/economy/classes/Money.java b/src/main/java/ch/njol/skript/hooks/economy/classes/Money.java index a9c759a32d9..acb45d923b2 100644 --- a/src/main/java/ch/njol/skript/hooks/economy/classes/Money.java +++ b/src/main/java/ch/njol/skript/hooks/economy/classes/Money.java @@ -18,12 +18,13 @@ */ package ch.njol.skript.hooks.economy.classes; +import ch.njol.skript.Skript; import ch.njol.skript.classes.data.JavaClasses; import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.Skript; -import ch.njol.skript.classes.Arithmetic; import ch.njol.skript.classes.ClassInfo; +import org.skriptlang.skript.lang.arithmetic.Arithmetics; +import org.skriptlang.skript.lang.arithmetic.Operator; import org.skriptlang.skript.lang.comparator.Comparator; import org.skriptlang.skript.lang.converter.Converter; import ch.njol.skript.classes.Parser; @@ -68,41 +69,7 @@ public String toString(final Money m, final int flags) { public String toVariableNameString(final Money o) { return "money:" + o.amount; } - }) - .math(Money.class, new Arithmetic<Money, Money>() { - @Override - public Money difference(final Money first, final Money second) { - final double d = Math.abs(first.getAmount() - second.getAmount()); - if (d < Skript.EPSILON) - return new Money(0); - return new Money(d); - } - - @Override - public Money add(final Money value, final Money difference) { - return new Money(value.amount + difference.amount); - } - - @Override - public Money subtract(final Money value, final Money difference) { - return new Money(value.amount - difference.amount); - } - - @Override - public Money multiply(Money value, Money multiplier) { - return new Money(value.getAmount() * multiplier.getAmount()); - } - - @Override - public Money divide(Money value, Money divider) { - return new Money(value.getAmount() / divider.getAmount()); - } - - @Override - public Money power(Money value, Money exponent) { - throw new UnsupportedOperationException(); - } - })); + })); Comparators.registerComparator(Money.class, Money.class, new Comparator<Money, Money>() { @Override @@ -133,6 +100,18 @@ public Double convert(final Money m) { return Double.valueOf(m.getAmount()); } }); + + Arithmetics.registerOperation(Operator.ADDITION, Money.class, (left, right) -> new Money(left.getAmount() + right.getAmount())); + Arithmetics.registerOperation(Operator.SUBTRACTION, Money.class, (left, right) -> new Money(left.getAmount() - right.getAmount())); + Arithmetics.registerOperation(Operator.MULTIPLICATION, Money.class, (left, right) -> new Money(left.getAmount() * right.getAmount())); + Arithmetics.registerOperation(Operator.DIVISION, Money.class, (left, right) -> new Money(left.getAmount() / right.getAmount())); + Arithmetics.registerDifference(Money.class, (left, right) -> { + double result = Math.abs(left.getAmount() - right.getAmount()); + if (result < Skript.EPSILON) + return new Money(0); + return new Money(result); + }); + Arithmetics.registerDefaultValue(Money.class, () -> new Money(0)); } final double amount; diff --git a/src/main/java/ch/njol/skript/lang/Variable.java b/src/main/java/ch/njol/skript/lang/Variable.java index 062fe307680..3d547aa0fc4 100644 --- a/src/main/java/ch/njol/skript/lang/Variable.java +++ b/src/main/java/ch/njol/skript/lang/Variable.java @@ -31,11 +31,13 @@ import ch.njol.skript.Skript; import ch.njol.skript.SkriptAPIException; import ch.njol.skript.SkriptConfig; -import ch.njol.skript.classes.Arithmetic; import ch.njol.skript.classes.Changer; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.classes.Changer.ChangerUtils; import ch.njol.skript.classes.ClassInfo; +import org.skriptlang.skript.lang.arithmetic.Arithmetics; +import org.skriptlang.skript.lang.arithmetic.OperationInfo; +import org.skriptlang.skript.lang.arithmetic.Operator; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.skript.lang.util.SimpleExpression; @@ -618,50 +620,27 @@ public void change(Event event, @Nullable Object[] delta, ChangeMode mode) throw } } else { Object originalValue = get(event); - ClassInfo<?> classInfo; - if (originalValue == null) { - classInfo = null; - } else { - Class<?> oldClass = originalValue.getClass(); - assert oldClass != null; - classInfo = Classes.getSuperClassInfo(oldClass); - } - Arithmetic arithmetic = null; + Class<?> clazz = originalValue == null ? null : originalValue.getClass(); + Operator operator = mode == ChangeMode.ADD ? Operator.ADDITION : Operator.SUBTRACTION; Changer<?> changer; Class<?>[] classes; - if (originalValue == null || (arithmetic = classInfo.getMath()) != null) { + if (clazz == null || !Arithmetics.getOperations(operator, clazz).isEmpty()) { boolean changed = false; for (Object newValue : delta) { - if (originalValue == null) { - Class<?> newClass = newValue.getClass(); - assert newClass != null; - classInfo = Classes.getSuperClassInfo(newClass); - - if ((arithmetic = classInfo.getMath()) != null) - originalValue = newValue; - if (newValue instanceof Number) { // Nonexistent variable: add/subtract - if (mode == ChangeMode.REMOVE) // Variable is delta negated - originalValue = -((Number) newValue).doubleValue(); // Hopefully enough precision - else // Variable is now what was added to it - originalValue = newValue; - } - changed = true; + OperationInfo info = Arithmetics.getOperationInfo(operator, clazz != null ? (Class) clazz : newValue.getClass(), newValue.getClass()); + if (info == null) continue; - } - Class<?> relativeType = classInfo.getMathRelativeType(); - assert arithmetic != null && relativeType != null : classInfo; - Object convertedValue = Converters.convert(newValue, relativeType); - if (convertedValue != null) { - if (mode == ChangeMode.ADD) - originalValue = arithmetic.add(originalValue, convertedValue); - else - originalValue = arithmetic.subtract(originalValue, convertedValue); - changed = true; - } + + Object value = originalValue == null ? Arithmetics.getDefaultValue(info.getLeft()) : originalValue; + if (value == null) + continue; + + originalValue = info.getOperation().calculate(value, newValue); + changed = true; } if (changed) set(event, originalValue); - } else if ((changer = classInfo.getChanger()) != null && (classes = changer.acceptChange(mode)) != null) { + } else if ((changer = Classes.getSuperClassInfo(clazz).getChanger()) != null && (classes = changer.acceptChange(mode)) != null) { Object[] originalValueArray = (Object[]) Array.newInstance(originalValue.getClass(), 1); originalValueArray[0] = originalValue; diff --git a/src/main/java/ch/njol/skript/util/Utils.java b/src/main/java/ch/njol/skript/util/Utils.java index 351574c7361..8897d6ef1ec 100644 --- a/src/main/java/ch/njol/skript/util/Utils.java +++ b/src/main/java/ch/njol/skript/util/Utils.java @@ -775,5 +775,13 @@ public static <T> int findLastIndex(List<T> list, Checker<T> checker) { } return lastIndex; } + + public static boolean isInteger(Number... numbers) { + for (Number number : numbers) { + if (Double.class.isAssignableFrom(number.getClass()) || Float.class.isAssignableFrom(number.getClass())) + return false; + } + return true; + } } diff --git a/src/main/java/org/skriptlang/skript/lang/arithmetic/Arithmetics.java b/src/main/java/org/skriptlang/skript/lang/arithmetic/Arithmetics.java new file mode 100644 index 00000000000..7cc4c8b0144 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/arithmetic/Arithmetics.java @@ -0,0 +1,269 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.arithmetic; + +import ch.njol.skript.Skript; +import ch.njol.skript.SkriptAPIException; +import ch.njol.util.Pair; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnmodifiableView; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public final class Arithmetics { + + private static final Map<Operator, List<OperationInfo<?, ?, ?>>> operations = Collections.synchronizedMap(new HashMap<>()); + private static final Map<Operator, Map<Pair<Class<?>, Class<?>>, OperationInfo<?, ?, ?>>> cachedOperations = Collections.synchronizedMap(new HashMap<>()); + + private static final Map<Class<?>, DifferenceInfo<?, ?>> differences = Collections.synchronizedMap(new HashMap<>()); + private static final Map<Class<?>, DifferenceInfo<?, ?>> cachedDifferences = Collections.synchronizedMap(new HashMap<>()); + + private static final Map<Class<?>, Supplier<?>> defaultValues = Collections.synchronizedMap(new HashMap<>()); + private static final Map<Class<?>, Supplier<?>> cachedDefaultValues = Collections.synchronizedMap(new HashMap<>()); + + public static <T> void registerOperation(Operator operator, Class<T> type, Operation<T, T, T> operation) { + registerOperation(operator, type, type, type, operation); + } + + public static <L, R> void registerOperation(Operator operator, Class<L> leftClass, Class<R> rightClass, Operation<L, R, L> operation) { + registerOperation(operator, leftClass, rightClass, leftClass, operation); + } + + public static <L, R> void registerOperation(Operator operator, Class<L> leftClass, Class<R> rightClass, Operation<L, R, L> operation, Operation<R, L, L> commutativeOperation) { + registerOperation(operator, leftClass, rightClass, leftClass, operation); + registerOperation(operator, rightClass, leftClass, leftClass, commutativeOperation); + } + + public static <L, R, T> void registerOperation(Operator operator, Class<L> leftClass, Class<R> rightClass, Class<T> returnType, Operation<L, R, T> operation, Operation<R, L, T> commutativeOperation) { + registerOperation(operator, leftClass, rightClass, returnType, operation); + registerOperation(operator, rightClass, leftClass, returnType, commutativeOperation); + } + + public static <L, R, T> void registerOperation(Operator operator, Class<L> leftClass, Class<R> rightClass, Class<T> returnType, Operation<L, R, T> operation) { + Skript.checkAcceptRegistrations(); + if (exactOperationExists(operator, leftClass, rightClass)) + throw new SkriptAPIException("There's already a " + operator.getName() + " operation registered for types '" + + leftClass + "' and '" + rightClass + "'"); + getOperations_i(operator).add(new OperationInfo<>(leftClass, rightClass, returnType, operation)); + } + + private static boolean exactOperationExists(Operator operator, Class<?> leftClass, Class<?> rightClass) { + for (OperationInfo<?, ?, ?> info : getOperations_i(operator)) { + if (info.getLeft() == leftClass && info.getRight() == rightClass) + return true; + } + return false; + } + + public static boolean operationExists(Operator operator, Class<?> leftClass, Class<?> rightClass) { + return getOperationInfo(operator, leftClass, rightClass) != null; + } + + private static List<OperationInfo<?, ?, ?>> getOperations_i(Operator operator) { + return operations.computeIfAbsent(operator, o -> Collections.synchronizedList(new ArrayList<>())); + } + + @UnmodifiableView + public static List<OperationInfo<?, ?, ?>> getOperations(Operator operator) { + assertIsOperationsDoneLoading(); + return Collections.unmodifiableList(getOperations_i(operator)); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + public static <T> List<OperationInfo<T, ?, ?>> getOperations(Operator operator, Class<T> type) { + return (List) getOperations(operator).stream() + .filter(info -> info.getLeft().isAssignableFrom(type)) + .collect(Collectors.toList()); + } + + @Nullable + @SuppressWarnings("unchecked") + public static <L, R, T> OperationInfo<L, R, T> getOperationInfo(Operator operator, Class<L> leftClass, Class<R> rightClass, Class<T> returnType) { + OperationInfo<L, R, ?> info = getOperationInfo(operator, leftClass, rightClass); + if (info != null && returnType.isAssignableFrom(info.getReturnType())) + return (OperationInfo<L, R, T>) info; + return null; + } + + private static Map<Pair<Class<?>, Class<?>>, OperationInfo<?, ?, ?>> getCachedOperations(Operator operator) { + return cachedOperations.computeIfAbsent(operator, o -> Collections.synchronizedMap(new HashMap<>())); + } + + @Nullable + @SuppressWarnings("unchecked") + public static <L, R> OperationInfo<L, R, ?> getOperationInfo(Operator operator, Class<L> leftClass, Class<R> rightClass) { + assertIsOperationsDoneLoading(); + return (OperationInfo<L, R, ?>) getCachedOperations(operator).computeIfAbsent(new Pair<>(leftClass, rightClass), pair -> + getOperations(operator).stream() + .filter(info -> info.getLeft().isAssignableFrom(leftClass) && info.getRight().isAssignableFrom(rightClass)) + .reduce((info, info2) -> { + if (info2.getLeft() == leftClass && info2.getRight() == rightClass) + return info2; + return info; + }) + .orElse(null)); + } + + @Nullable + public static <L, R, T> Operation<L, R, T> getOperation(Operator operator, Class<L> leftClass, Class<R> rightClass, Class<T> returnType) { + OperationInfo<L, R, T> info = getOperationInfo(operator, leftClass, rightClass, returnType); + return info == null ? null : info.getOperation(); + } + + @Nullable + public static <L, R> Operation<L, R, ?> getOperation(Operator operator, Class<L> leftClass, Class<R> rightClass) { + OperationInfo<L, R, ?> info = getOperationInfo(operator, leftClass, rightClass); + return info == null ? null : info.getOperation(); + } + + @Nullable + public static <L, R, T> OperationInfo<L, R, T> lookupOperationInfo(Operator operator, Class<L> leftClass, Class<R> rightClass, Class<T> returnType) { + OperationInfo<L, R, ?> info = lookupOperationInfo(operator, leftClass, rightClass); + return info != null ? info.getConverted(leftClass, rightClass, returnType) : null; + } + + @Nullable + @SuppressWarnings("unchecked") + public static <L, R> OperationInfo<L, R, ?> lookupOperationInfo(Operator operator, Class<L> leftClass, Class<R> rightClass) { + OperationInfo<L, R, ?> operationInfo = getOperationInfo(operator, leftClass, rightClass); + if (operationInfo != null) + return operationInfo; + return (OperationInfo<L, R, ?>) getCachedOperations(operator).computeIfAbsent(new Pair<>(leftClass, rightClass), pair -> { + for (OperationInfo<?, ?, ?> info : getOperations(operator)) { + if (!info.getLeft().isAssignableFrom(leftClass) && !info.getRight().isAssignableFrom(rightClass)) + continue; + OperationInfo<L, R, ?> convertedInfo = info.getConverted(leftClass, rightClass, info.getReturnType()); + if (convertedInfo != null) + return convertedInfo; + } + return null; + }); + } + + @SuppressWarnings("unchecked") + public static <L, R, T> T calculate(Operator operator, L left, R right, Class<T> returnType) { + Operation<L, R, T> operation = (Operation<L, R, T>) getOperation(operator, left.getClass(), right.getClass(), returnType); + return operation == null ? null : operation.calculate(left, right); + } + + @SuppressWarnings("unchecked") + public static <L, R, T> T calculateUnsafe(Operator operator, L left, R right) { + Operation<L, R, T> operation = (Operation<L, R, T>) getOperation(operator, left.getClass(), right.getClass()); + return operation == null ? null : operation.calculate(left, right); + } + + public static <T> void registerDifference(Class<T> type, Operation<T, T, T> operation) { + registerDifference(type, type, operation); + } + + public static <T, R> void registerDifference(Class<T> type, Class<R> returnType, Operation<T, T, R> operation) { + Skript.checkAcceptRegistrations(); + if (exactDifferenceExists(type)) + throw new IllegalArgumentException("There's already a difference registered for type '" + type + "'"); + differences.put(type, new DifferenceInfo<>(type, returnType, operation)); + } + + private static boolean exactDifferenceExists(Class<?> type) { + return differences.containsKey(type); + } + + public static boolean differenceExists(Class<?> type) { + return getDifferenceInfo(type) != null; + } + + @SuppressWarnings("unchecked") + public static <T, R> DifferenceInfo<T, R> getDifferenceInfo(Class<T> type, Class<R> returnType) { + DifferenceInfo<T, ?> info = getDifferenceInfo(type); + if (info != null && returnType.isAssignableFrom(info.getReturnType())) + return (DifferenceInfo<T, R>) info; + return null; + } + + @SuppressWarnings("unchecked") + public static <T> DifferenceInfo<T, ?> getDifferenceInfo(Class<T> type) { + if (Skript.isAcceptRegistrations()) + throw new SkriptAPIException("Differences cannot be retrieved until Skript has finished registrations."); + return (DifferenceInfo<T, ?>) cachedDifferences.computeIfAbsent(type, c -> { + if (differences.containsKey(type)) + return differences.get(type); + for (Map.Entry<Class<?>, DifferenceInfo<?, ?>> entry : differences.entrySet()) { + if (entry.getKey().isAssignableFrom(type)) + return entry.getValue(); + } + return null; + }); + } + + public static <T, R> Operation<T, T, R> getDifference(Class<T> type, Class<R> returnType) { + DifferenceInfo<T, R> info = getDifferenceInfo(type, returnType); + return info == null ? null : info.getOperation(); + } + + public static <T> Operation<T, T, ?> getDifference(Class<T> type) { + DifferenceInfo<T, ?> info = getDifferenceInfo(type); + return info == null ? null : info.getOperation(); + } + + @SuppressWarnings("unchecked") + public static <T, R> R difference(T left, T right, Class<R> returnType) { + Operation<T, T, R> operation = (Operation<T, T, R>) getDifference(left.getClass(), returnType); + return operation == null ? null : operation.calculate(left, right); + } + + @SuppressWarnings("unchecked") + public static <T, R> R differenceUnsafe(T left, T right) { + Operation<T, T, R> operation = (Operation<T, T, R>) getDifference(left.getClass()); + return operation == null ? null : operation.calculate(left, right); + } + + public static <T> void registerDefaultValue(Class<T> type, Supplier<T> supplier) { + Skript.checkAcceptRegistrations(); + if (defaultValues.containsKey(type)) + throw new IllegalArgumentException("There's already a default value registered for type '" + type + "'"); + defaultValues.put(type, supplier); + } + + @SuppressWarnings("unchecked") + public static <R, T extends R> R getDefaultValue(Class<T> type) { + if (Skript.isAcceptRegistrations()) + throw new SkriptAPIException("Default values cannot be retrieved until Skript has finished registrations."); + Supplier<R> supplier = (Supplier<R>) cachedDefaultValues.computeIfAbsent(type, c -> { + if (defaultValues.containsKey(type)) + return defaultValues.get(type); + for (Map.Entry<Class<?>, Supplier<?>> entry : defaultValues.entrySet()) { + if (entry.getKey().isAssignableFrom(type)) + return entry.getValue(); + } + return null; + }); + return supplier == null ? null : supplier.get(); + } + + private static void assertIsOperationsDoneLoading() { + if (Skript.isAcceptRegistrations()) + throw new SkriptAPIException("Operations cannot be retrieved until Skript has finished registrations."); + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/arithmetic/DifferenceInfo.java b/src/main/java/org/skriptlang/skript/lang/arithmetic/DifferenceInfo.java new file mode 100644 index 00000000000..eb3a4bd485b --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/arithmetic/DifferenceInfo.java @@ -0,0 +1,49 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.arithmetic; + +/** + * @param <T> The type of the difference + * @param <R> The return type of the difference + */ +public final class DifferenceInfo<T, R> { + + private final Class<T> type; + private final Class<R> returnType; + private final Operation<T, T, R> operation; + + public DifferenceInfo(Class<T> type, Class<R> returnType, Operation<T, T, R> operation) { + this.type = type; + this.returnType = returnType; + this.operation = operation; + } + + public Class<T> getType() { + return type; + } + + public Class<R> getReturnType() { + return returnType; + } + + public Operation<T, T, R> getOperation() { + return operation; + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/arithmetic/Operation.java b/src/main/java/org/skriptlang/skript/lang/arithmetic/Operation.java new file mode 100644 index 00000000000..469be7dd08a --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/arithmetic/Operation.java @@ -0,0 +1,37 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.arithmetic; + +import org.jetbrains.annotations.NotNull; + +/** + * Represents a <a href="https://en.wikipedia.org/wiki/Pure_function">pure</a> binary operation + * that takes two operands of types {@code L} and {@code R}, performs a calculation, + * and returns a result of type {@code T}. + * + * @param <L> The class of the left operand. + * @param <R> The class of the right operand. + * @param <T> The return type of the operation. + */ +@FunctionalInterface +public interface Operation<L, R, T> { + + T calculate(@NotNull L left, @NotNull R right); + +} diff --git a/src/main/java/org/skriptlang/skript/lang/arithmetic/OperationInfo.java b/src/main/java/org/skriptlang/skript/lang/arithmetic/OperationInfo.java new file mode 100644 index 00000000000..6ad628fd040 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/arithmetic/OperationInfo.java @@ -0,0 +1,88 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.arithmetic; + +import com.google.common.base.MoreObjects; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.lang.converter.Converters; + +/** + * @param <L> The class of left operand + * @param <R> The class of the right operand + * @param <T> The return type of the operation + */ +public class OperationInfo<L, R, T> { + + private final Class<L> left; + private final Class<R> right; + private final Class<T> returnType; + private final Operation<L, R, T> operation; + + public OperationInfo(Class<L> left, Class<R> right, Class<T> returnType, Operation<L, R, T> operation) { + this.left = left; + this.right = right; + this.returnType = returnType; + this.operation = operation; + } + + public Class<L> getLeft() { + return left; + } + + public Class<R> getRight() { + return right; + } + + public Class<T> getReturnType() { + return returnType; + } + + public Operation<L, R, T> getOperation() { + return operation; + } + + public <L2, R2> @Nullable OperationInfo<L2, R2, T> getConverted(Class<L2> fromLeft, Class<R2> fromRight) { + return getConverted(fromLeft, fromRight, returnType); + } + + public <L2, R2, T2> @Nullable OperationInfo<L2, R2, T2> getConverted(Class<L2> fromLeft, Class<R2> fromRight, Class<T2> toReturnType) { + if (fromLeft == Object.class || fromRight == Object.class) + return null; + if (!Converters.converterExists(fromLeft, left) || !Converters.converterExists(fromRight, right) || !Converters.converterExists(returnType, toReturnType)) + return null; + return new OperationInfo<>(fromLeft, fromRight, toReturnType, (left, right) -> { + L convertedLeft = Converters.convert(left, this.left); + R convertedRight = Converters.convert(right, this.right); + if (convertedLeft == null || convertedRight == null) + return null; + T result = operation.calculate(convertedLeft, convertedRight); + return Converters.convert(result, toReturnType); + }); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("left", left) + .add("right", right) + .add("returnType", returnType) + .toString(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/arithmetic/Operator.java b/src/main/java/org/skriptlang/skript/lang/arithmetic/Operator.java new file mode 100644 index 00000000000..f1cc0762aa6 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/arithmetic/Operator.java @@ -0,0 +1,56 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.lang.arithmetic; + +import ch.njol.skript.Skript; +import ch.njol.skript.SkriptAPIException; +import ch.njol.skript.localization.Noun; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public enum Operator { + + ADDITION('+', "add"), + SUBTRACTION('-', "subtract"), + MULTIPLICATION('*', "multiply"), + DIVISION('/', "divide"), + EXPONENTIATION('^', "exponentiate"); + + private final char sign; + private final Noun m_name; + + Operator(char sign, String node) { + this.sign = sign; + this.m_name = new Noun("operators." + node); + } + + @Override + public String toString() { + return sign + ""; + } + + public String getName() { + return m_name.toString(); + } + +} diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 07f1a1dc45b..77cc9d12c2a 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -1982,6 +1982,14 @@ boolean: name: false pattern: (false|no|off) +# -- Operators -- +operators: + add: addition + subtract: subtraction + multiply: multiplication + divide: division + exponentiate: exponentiation + # -- Types -- types: # Java diff --git a/src/test/skript/tests/syntaxes/expressions/ExprArithmetic.sk b/src/test/skript/tests/syntaxes/expressions/ExprArithmetic.sk new file mode 100644 index 00000000000..cb51efbfef9 --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprArithmetic.sk @@ -0,0 +1,257 @@ +test "number - number operations": + + # --Addition/Subtraction-- + + assert (1 + 1) is (2) with "1 + 1 is not 2" + assert (2 + 2) is (4) with "2 + 2 is not 4" + assert (3 - 3) is (0) with "3 - 3 is not 0" + assert (4 - 2) is (2) with "4 - 2 is not 2" + assert (5 + 1) is (6) with "5 + 1 is not 6" + assert (6 - -2) is (8) with "6 - -2 is not 8" + assert (4-(40)) is (-36) with "4 - 40 is not -36" + + # --Multiplication-- + + assert (1*5) is (5) with "1 * 5 is not 5" + assert (2*5) is (10) with "2 * 5 is not 10" + assert (3*-5) is (-15) with "3 * -5 is not 15" + assert (4*0) is (0) with "4 * 0 is not 0" + assert (5*infinity value) is (infinity value) with "5 * infinity is not infinity" + + # --Division-- + + assert (1/5) is (0.2) with "1 / 5 is not 0.2" + assert (2/5) is (0.4) with "2 / 5 is not 0.4" + assert (3/-5) is (-0.6) with "3 / -5 is not -0.6" + assert (4/0) is (infinity value) with "4 / 0 is not infinity" + assert (5/0) is (infinity value) with "5 / 0 is not infinity" + assert isNaN(0/0) is true with "0 / 0 is not NaN" + assert (5/2.5) is (2) with "5 / 2.5 is not 2" + + # --Exponents-- + + assert (1^5) is (1) with "1 ^ 5 is not 1" + assert (2^5) is (32) with "2 ^ 5 is not 32" + assert (3^-5) is (0.00411522633) with "3 ^ -5 is not 0.00411522633" + assert (4^0) is (1) with "4 ^ 0 is not 1" + assert (5^0) is (1) with "5 ^ 0 is not 1" + assert (0^0) is (1) with "0 ^ 0 is not 1" + assert (5^2.5) - 55.901 < 0.001 with "5 ^ 2.5 is not 55.9" + + # --Order of Operations-- + + assert (1 + 2 - 1 * 3) is (0) with "1 + 2 - 1 * 3 is not 0" + assert (1 + 2 - 1 / 2 * 3 ) is (1.5) with "1 + 2 - 1 / 2 * 3 is not 1.5" + assert (1 + (2) - 1 / (2) * 3) is (1.5) with "1 + (2) - 1 / (2) * 3 is not 1.5" + assert (1 + (2 - 1 / 2) * 3) is (5.5) with "1 + (2 - 1 / 2) * 3 is not 5.5" + assert (1 + 2 - 1 / 2 * 3 + 4 - 5 * 6 / 2 ) is (-9.5) with "1 + 2 - 1 / 2 * 3 + 4 - 5 * 6 / 2 is not -9.5" + assert (1 + (1 - 2) * 3) is (-2) with "1 + (1 - 2) * 3 is not -2" + + # --Edge Cases-- + + assert (0^99999) is (0) with "0^99999 is not 0" + assert (1 + 2.3 + 3.01) is (6.31) with "1 + 2.3 + 3.01 is not 6.31" + assert (0.1 + 0.1 + 0.1) is (0.30000000000000004) with "0.1 + 0.1 + 0.1 is not 0.30000000000000004" + assert (1/-infinity value) is (-0) with "1/-infinity value is not -0" + + + # --Standard Operations-- + + set {_x} to 1 + set {_y} to 2 + set {_z} to 3 + assert ({_x}) is (1) with "{_x} is not 1" + assert ({_y}) is (2) with "{_y} is not 2" + assert ({_z}) is (3) with "{_z} is not 3" + assert ({_x} + {_y} + {_z}) is (6) with "1 + 2 + 3 is not 6" + assert ({_x} + {_y} - {_z}) is (0) with "1 + 2 - 3 is not 0" + assert ({_x} + {_y} * {_z}) is (7) with "1 + 2 * 3 is not 7" + assert ({_x} + {_y} / {_z}) is (1.6666666666666667) with "1 + 2 / 3 is not 1.6666666666666667" + assert ({_x} + {_y} ^ {_z}) is (9) with "1 + 2 ^ 3 is not 9" + assert ({_x} - {_y} + {_z}) is (2) with "1 - 2 + 3 is not 2" + assert ({_x} - {_y} - {_z}) is (-4) with "1 - 2 - 3 is not -4" + assert ({_x} - {_y} * {_z}) is (-5) with "1 - 2 * 3 is not -5" + assert ({_x} - {_y} / {_z}) is (0.3333333333333333) with "1 - 2 / 3 is not 0.3333333333333333" + assert ({_x} - {_y} ^ {_z}) is (-7) with "1 - 2 ^ 3 is not -7" + assert ({_x} * {_y} + {_z}) is (5) with "1 * 2 + 3 is not 5" + assert ({_x} * {_y} - {_z}) is (-1) with "1 * 2 - 3 is not -1" + assert ({_x} * {_y} * {_z}) is (6) with "1 * 2 * 3 is not 6" + assert ({_x} * {_y} / {_z}) is (0.6666666666666666) with "1 * 2 / 3 is not 0.6666666666666666" + assert ({_x} * {_y} ^ {_z}) is (8) with "1 * 2 ^ 3 is not 8" + assert ({_x} / {_y} + {_z}) is (3.5) with "1 / 2 + 3 is not 3.5" + assert ({_x} / {_y} - {_z}) is (-2.5) with "1 / 2 - 3 is not -2.5" + assert ({_x} / {_y} * {_z}) is (1.5) with "1 / 2 * 3 is not 1.5" + assert ({_x} / {_y} / {_z}) is (0.16666666666666666) with "1 / 2 / 3 is not 0.16666666666666666" + assert ({_x} / {_y} ^ {_z}) is (0.125) with "1 / 2 ^ 3 is not 0.125" + assert ({_x} ^ {_y} + {_z}) is (4) with "1 ^ 2 + 3 is not 4" + assert ({_x} ^ {_y} - {_z}) is (-2) with "1 ^ 2 - 3 is not -2" + assert ({_x} ^ {_y} * {_z}) is (3) with "1 ^ 2 * 3 is not 3" + assert ({_x} ^ {_y} / {_z}) is (0.3333333333333333) with "1 ^ 2 / 3 is not 0.3333333333333333" + assert ({_x} ^ {_y} ^ {_z}) is (1) with "1 ^ 2 ^ 3 is not 1" + + # --Non-Numbers-- + + set {_x} to 1 + set {_y} to {_} + set {_z} to 3 + + assert ({_y} / 3) is (0) with "<none> / 3 is not 0" + assert ({_x}) is (1) with "{_x} is not 1" + assert ({_y} + 1 + 1) is (2) with "<none> + 1 + 1 is not 2" + assert (1 + {_y} + 1) is (2) with "1 + <none> + 1 is not 2" + assert ({_z}) is (3) with "{_z} is not 3" + assert ({_x} + {_y} + {_z}) is (4) with "1 + <none> + 3 is not 4" + assert ({_x} + {_y} - {_z}) is (-2) with "1 + <none> - 3 is not -2" + assert ({_x} + {_y} * {_z}) is (1) with "1 + <none> * 3 is not 1" + assert ({_x} + {_y} / {_z}) is (1) with "1 + <none> / 3 is not 1" + assert ({_x} + {_y} ^ {_z}) is (1) with "1 + <none> ^ 3 is not 1" + assert ({_x} - {_y} + {_z}) is (4) with "1 - <none> + 3 is not 4" + assert ({_x} - {_y} - {_z}) is (-2) with "1 - <none> - 3 is not -2" + assert ({_x} - {_y} * {_z}) is (1) with "1 - <none> * 3 is not 1" + assert ({_x} - {_y} / {_z}) is (1) with "1 - <none> / 3 is not 1" + assert ({_x} - {_y} ^ {_z}) is (1) with "1 - <none> ^ 3 is not 1" + assert ({_x} * {_y} + {_z}) is (3) with "1 * <none> + 3 is not 3" + assert ({_x} * {_y} - {_z}) is (-3) with "1 * <none> - 3 is not -3" + assert ({_x} * {_y} * {_z}) is (0) with "1 * <none> * 3 is not 0" + assert ({_x} * {_y} / {_z}) is (0) with "1 * <none> / 3 is not 0" + assert ({_x} * {_y} ^ {_z}) is (0) with "1 * <none> ^ 3 is not 0" + assert ({_x} / {_y} + {_z}) is (infinity value) with "1 / <none> + 3 is not infinity" + assert ({_x} / {_y} - {_z}) is (infinity value) with "1 / <none> - 3 is not infinity" + assert ({_x} / {_y} * {_z}) is (infinity value) with "1 / <none> * 3 is not infinity" + assert ({_x} / {_y} / {_z}) is (infinity value) with "1 / <none> / 3 is not infinity" + assert ({_x} / {_y} ^ {_z}) is (infinity value) with "1 / <none> ^ 3 is not infinity" + assert ({_x} ^ {_y} + {_z}) is (4) with "1 ^ <none> + 3 is not 4" + assert ({_x} ^ {_y} - {_z}) is (-2) with "1 ^ <none> - 3 is not -2" + assert ({_x} ^ {_y} * {_z}) is (3) with "1 ^ <none> * 3 is not 3" + assert ({_x} ^ {_y} / {_z}) is (0.333333333333) with "1 ^ <none> / 3 is not 0.333333333333" + assert ({_x} ^ {_y} ^ {_z}) is (1) with "1 ^ <none> ^ 3 is not 1" + + # --Difference-- + assert (difference between 1 and 2) is (1) with "difference between 1 and 2 is not 1" + assert (difference between 2 and 1) is (1) with "difference between 2 and 1 is not 1" + assert (difference between 1 and 1) is (0) with "difference between 1 and 1 is not 0" + assert (difference between 1 and 0) is (1) with "difference between 1 and 0 is not 1" + assert (difference between 0 and 1) is (1) with "difference between 0 and 1 is not 1" + assert (difference between 0 and 0) is (0) with "difference between 0 and 0 is not 0" + assert (difference between 1 and infinity value) is (infinity value) with "difference between 1 and infinity is not infinity" + assert (difference between infinity value and 1) is (infinity value) with "difference between infinity and 1 is not infinity" + assert isNaN(difference between infinity value and infinity value) is true with "difference between infinity and infinity is not NaN" + assert isNaN(difference between 1 and NaN value) is true with "difference between 1 and NaN is not NaN" + assert isNaN(difference between NaN value and 1) is true with "difference between NaN and 1 is not NaN" + assert isNaN(difference between NaN value and NaN value) is true with "difference between NaN and NaN is not NaN" + +test "vector - vector operations": + # --Addition/Subtraction-- + set {_v1} to vector(1, 2, 3) + set {_v2} to vector(4, 5, 6) + + assert ({_v1} + {_v2}) is (vector(5, 7, 9)) with "{_v1} + {_v2} is not vector(5, 7, 9)" + assert ({_v1} - {_v2}) is (vector(-3, -3, -3)) with "{_v1} - {_v2} is not vector(-3, -3, -3)" + assert ({_v1} * {_v2}) is (vector(4, 10, 18)) with "{_v1} * {_v2} is not vector(4, 10, 18)" + assert ({_v1} / {_v2}) is (vector(0.25, 0.4, 0.5)) with "{_v1} / {_v2} is not vector(0.25, 0.4, 0.5)" + + assert ({_v1} ++ {_v2}) is (vector(5, 7, 9)) with "{_v1} ++ {_v2} is not vector(5, 7, 9)" + assert ({_v1} -- {_v2}) is (vector(-3, -3, -3)) with "{_v1} -- {_v2} is not vector(-3, -3, -3)" + assert ({_v1} ** {_v2}) is (vector(4, 10, 18)) with "{_v1} ** {_v2} is not vector(4, 10, 18)" + assert ({_v1} // {_v2}) is (vector(0.25, 0.4, 0.5)) with "{_v1} // {_v2} is not vector(0.25, 0.4, 0.5)" + + # --Zero Vectors-- + + set {_v1} to vector(0,0,0) + set {_v2} to vector(1,2,3) + assert ({_v1} + {_v2}) is (vector(1, 2, 3)) with "vector(0,0,0) + vector(1,2,3) is not vector(1, 2, 3)" + assert ({_v1} - {_v2}) is (vector(-1, -2, -3)) with "vector(0,0,0) - vector(1,2,3) is not vector(-1, -2, -3)" + assert ({_v1} * {_v2}) is (vector(0, 0, 0)) with "vector(0,0,0) * vector(1,2,3) is not vector(0, 0, 0)" + assert ({_v1} / {_v2}) is (vector(0, 0, 0)) with "vector(0,0,0) / vector(1,2,3) is not vector(0, 0, 0)" + assert ({_v2} + {_v1}) is (vector(1, 2, 3)) with "vector(1,2,3) + vector(0,0,0) is not vector(1, 2, 3)" + assert ({_v2} - {_v1}) is (vector(1, 2, 3)) with "vector(1,2,3) - vector(0,0,0) is not vector(1, 2, 3)" + assert ({_v2} * {_v1}) is (vector(0, 0, 0)) with "vector(1,2,3) * vector(0,0,0) is not vector(0, 0, 0)" + assert vector_equals(({_v2} / {_v1}), infinity value, infinity value, infinity value) is true with "vector(1,2,3) / vector(0,0,0) is not vector(infinity, infinity, infinity)" + + # --Non-Vectors-- + set {_v1} to vector(1, 2, 3) + set {_v2} to "test" + assert ({_v1} + {_v2}) is not set with "vector plus string is set" + assert ({_v1} - {_v2}) is not set with "vector minus string is set" + assert ({_v1} * {_v2}) is not set with "vector multiplied by string is set" + assert ({_v1} / {_v2}) is not set with "vector divided by string is set" + assert ({_v2} + {_v1}) is not set with "string plus vector is set" + assert ({_v2} - {_v1}) is not set with "string minus vector is set" + assert ({_v2} * {_v1}) is not set with "string multiplied by vector is set" + assert ({_v2} / {_v1}) is not set with "string divided by vector is set" + + # --Edge Cases-- + set {_v1} to vector(1, infinity value, 3) + set {_v2} to vector(4, 5, NaN value) + assert vector_equals(({_v1} + {_v2}), 5, infinity value, NaN value) is true with "vector(1, infinity, 3) + vector(4, 5, NaN) is not vector(5, infinity, NaN)" + assert vector_equals(({_v1} - {_v2}), -3, infinity value, NaN value) is true with "vector(1, infinity, 3) - vector(4, 5, NaN) is not vector(-3, infinity, NaN)" + assert vector_equals(({_v1} * {_v2}), 4, infinity value, NaN value) is true with "vector(1, infinity, 3) * vector(4, 5, NaN) is not vector(4, infinity, NaN)" + assert vector_equals(({_v1} / {_v2}), 0.25, infinity value, NaN value) is true with "vector(1, infinity, 3) / vector(4, 5, NaN) is not vector(0.25, infinity, NaN)" + assert vector_equals(({_v2} + {_v1}), 5, infinity value, NaN value) is true with "vector(4, 5, NaN) + vector(1, infinity, 3) is not vector(5, infinity, NaN)" + assert vector_equals(({_v2} - {_v1}), 3, -infinity value, NaN value) is true with "vector(4, 5, NaN) - vector(1, infinity, 3) is not vector(3, -infinity, NaN)" + assert vector_equals(({_v2} * {_v1}), 4, infinity value, NaN value) is true with "vector(4, 5, NaN) * vector(1, infinity, 3) is not vector(4, infinity, NaN)" + assert vector_equals(({_v2} / {_v1}), 4, 0, NaN value) is true with "vector(4, 5, NaN) / vector(1, infinity, 3) is not vector(4, 0, NaN)" + + # --Difference-- + + set {_v1} to vector(1, 2, 3) + set {_v2} to vector(4, 5, 6) + assert (difference between {_v1} and {_v2}) is (vector(3, 3, 3)) with "difference between vector(1,2,3) and vector(4,5,6) is not vector(3, 3, 3)" + assert (difference between {_v2} and {_v1}) is (vector(3, 3, 3)) with "difference between vector(4,5,6) and vector(1,2,3) is not vector(3, 3, 3)" + assert (difference between {_v1} and {_v1}) is (vector(0, 0, 0)) with "difference between vector(1,2,3) and vector(1,2,3) is not vector(0, 0, 0)" + assert (difference between {_v1} and vector(0,0,0)) is (vector(1, 2, 3)) with "difference between vector(1,2,3) and vector(0,0,0) is not vector(1, 2, 3)" + assert (difference between vector(0,0,0) and {_v1}) is (vector(1, 2, 3)) with "difference between vector(0,0,0) and {_v1} is not vector(1, 2, 3)" + assert vector_equals(difference between {_v1} and vector(infinity value, -infinity value, NaN value), infinity value, infinity value, NaN value) is true with "difference between vector(1,2,3) and vector(infinity, -infinity, NaN) is not vector(infinity, infinity, NaN)" + +test "number - vector operations": + set {_v1} to vector(1, 2, 3) + set {_v2} to 2 + assert ({_v1} + 2) is not set with "vector plus number is set" + assert ({_v1} - 2) is not set with "vector minus number is set" + assert ({_v1} * 2) is (vector(2, 4, 6)) with "vector(1,2,3) * 2 is not vector(2, 4, 6)" + assert ({_v1} / 2) is (vector(0.5, 1, 1.5)) with "vector(1,2,3) / 2 is not vector(0.5, 1, 1.5)" + assert ({_v1} ^ 2) is not set with "vector to the power of a number is set" + assert ({_v1} * 0) is (vector(0, 0, 0)) with "vector(1,2,3) * 0 is not vector(0, 0, 0)" + assert vector_equals(({_v1} / 0), infinity value, infinity value, infinity value) is true with "vector(1,2,3) / 0 is not vector(infinity, infinity, infinity)" + assert vector_equals(({_v1} * -infinity value), -infinity value, -infinity value, -infinity value) is true with "vector(1,2,3) * -infinity is not vector(-infinity, -infinity, -infinity)" + assert vector_equals(({_v1} / -infinity value), -0, -0, -0) is true with "vector(1,2,3) / -infinity value is not vector(-0, -0, -0)" + assert vector_equals(({_v1} * NaN value), NaN value, NaN value, NaN value) is true with "vector(1,2,3) * NaN is not vector(NaN, NaN, NaN)" + assert vector_equals(({_v1} / NaN value), NaN value, NaN value, NaN value) is true with "vector(1,2,3) / NaN is not vector(NaN, NaN, NaN)" + +test "timespan arithmetic": + set {_t1} to 1 second + set {_t2} to 2 seconds + assert ({_t1} + {_t2}) is (3 seconds) with "1 second + 2 seconds is not 3 seconds" + assert ({_t1} - {_t2}) is (0 seconds) with "1 second - 2 seconds is not 0 seconds" + assert ({_t1} * 2) is (2 seconds) with "1 second * 2 is not 2 seconds" + assert ({_t1} / 2) is (0.5 seconds) with "1 second / 2 is not 0.5 seconds" + + assert (2 * {_t1}) is (2 seconds) with "2 * 1 second is not 2 seconds" + assert (2 / {_t1}) is not set with "number divided by timespan is set" + + assert ({_t1} + 2) is not set with "timespan plus number is set" + +test "date arithmetic": + set {_d1} to now + set {_d2} to 1 day ago + assert ({_d1} + {_d2}) is not set with "" + + assert ({_d1} + 1 day) is (1 day from {_d1}) with "now + 1 day is not 1 day from now" + assert ({_d1} - 1 day) is (1 day ago) with "now - 1 day is not 1 day ago" + assert ({_d1} + 1 week) is (1 week from {_d1}) with "now + 1 week is not 1 week from now" + assert ({_d1} - 1 week) is (1 week ago) with "now - 1 week is not 1 week ago" + assert ({_d1} + 1) is not set with "" + +local function vector_equals(vec: vector, x: number, y: number, z: number) :: boolean: + return false if component_equals((x of {_vec}), {_x}) is false + return false if component_equals((y of {_vec}), {_y}) is false + return false if component_equals((z of {_vec}), {_z}) is false + return true + +local function component_equals(a: number, b: number) :: boolean: + if: + isNaN({_a}) is true + isNaN({_b}) is true + then: + return true + return true if {_a} is {_b}, else false From 16e217d23dda99d5e7207dd26e4c12243485e8e7 Mon Sep 17 00:00:00 2001 From: Fusezion <fusezionstream@gmail.com> Date: Mon, 1 Jan 2024 15:56:39 -0500 Subject: [PATCH 566/619] Update to world events (#5114) --- .../ch/njol/skript/effects/EffWorldLoad.java | 100 ++++++++++++++++++ .../ch/njol/skript/effects/EffWorldSave.java | 70 ++++++++++++ .../java/ch/njol/skript/events/EvtWorld.java | 96 +++++++++++++++++ .../ch/njol/skript/events/SimpleEvents.java | 24 ----- 4 files changed, 266 insertions(+), 24 deletions(-) create mode 100644 src/main/java/ch/njol/skript/effects/EffWorldLoad.java create mode 100644 src/main/java/ch/njol/skript/effects/EffWorldSave.java create mode 100644 src/main/java/ch/njol/skript/events/EvtWorld.java diff --git a/src/main/java/ch/njol/skript/effects/EffWorldLoad.java b/src/main/java/ch/njol/skript/effects/EffWorldLoad.java new file mode 100644 index 00000000000..1417789789f --- /dev/null +++ b/src/main/java/ch/njol/skript/effects/EffWorldLoad.java @@ -0,0 +1,100 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.effects; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.World.Environment; +import org.bukkit.WorldCreator; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Load World") +@Description({ + "Load your worlds or unload your worlds", + "The load effect will create a new world if world doesn't already exist.", + "When attempting to load a normal vanilla world you must define it's environment i.e \"world_nether\" must be loaded with nether environment" +}) +@Examples({ + "load world \"world_nether\" with environment nether", + "load the world \"myCustomWorld\"", + "unload \"world_nether\"", + "unload \"world_the_end\" without saving", + "unload all worlds" +}) +@Since("INSERT VERSION") +public class EffWorldLoad extends Effect { + + static { + Skript.registerEffect(EffWorldLoad.class, + "load [[the] world[s]] %strings% [with environment %-environment%]", + "unload [[the] world[s]] %worlds% [:without saving]" + ); + } + + private boolean save, load; + private Expression<?> worlds; + @Nullable + private Expression<Environment> environment; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + worlds = exprs[0]; + load = matchedPattern == 0; + if (load) { + environment = (Expression<Environment>) exprs[1]; + } else { + save = !parseResult.hasTag("without saving"); + } + return true; + } + + @Override + protected void execute(Event event) { + Environment environment = this.environment != null ? this.environment.getSingle(event) : null; + for (Object world : worlds.getArray(event)) { + if (load && world instanceof String) { + WorldCreator worldCreator = new WorldCreator((String) world); + if (environment != null) + worldCreator.environment(environment); + worldCreator.createWorld(); + } else if (!load && world instanceof World) { + Bukkit.unloadWorld((World) world, save); + } + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + if (load) + return "load the world(s) " + worlds.toString(event, debug) + (environment == null ? "" : " with environment " + environment.toString(event, debug)); + return "unload the world(s) " + worlds.toString(event, debug) + " " + (save ? "with saving" : "without saving"); + } + +} diff --git a/src/main/java/ch/njol/skript/effects/EffWorldSave.java b/src/main/java/ch/njol/skript/effects/EffWorldSave.java new file mode 100644 index 00000000000..2281ba44dcc --- /dev/null +++ b/src/main/java/ch/njol/skript/effects/EffWorldSave.java @@ -0,0 +1,70 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.effects; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.World; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Save World") +@Description({ + "Save all worlds or a given world manually.", + "Note: saving many worlds at once may possibly cause the server to freeze." +}) +@Examples({ + "save \"world_nether\"", + "save all worlds" +}) +@Since("INSERT VERSION") +public class EffWorldSave extends Effect { + + static { + Skript.registerEffect(EffWorldSave.class, "save [[the] world[s]] %worlds%"); + } + + private Expression<World> worlds; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + worlds = (Expression<World>) exprs[0]; + return true; + } + + @Override + protected void execute(Event event) { + for (World world : worlds.getArray(event)) + world.save(); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "save the world(s) " + worlds.toString(event, debug); + } + +} diff --git a/src/main/java/ch/njol/skript/events/EvtWorld.java b/src/main/java/ch/njol/skript/events/EvtWorld.java new file mode 100644 index 00000000000..657d3f1a002 --- /dev/null +++ b/src/main/java/ch/njol/skript/events/EvtWorld.java @@ -0,0 +1,96 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.events; + +import ch.njol.skript.Skript; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.LiteralList; +import ch.njol.skript.lang.SkriptEvent; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import org.bukkit.World; +import org.bukkit.event.Event; +import org.bukkit.event.world.WorldInitEvent; +import org.bukkit.event.world.WorldLoadEvent; +import org.bukkit.event.world.WorldSaveEvent; +import org.bukkit.event.world.WorldUnloadEvent; +import org.bukkit.event.world.WorldEvent; +import org.eclipse.jdt.annotation.Nullable; + +public class EvtWorld extends SkriptEvent { + + static { + // World Save Event + Skript.registerEvent("World Save", EvtWorld.class, WorldSaveEvent.class, "world sav(e|ing) [of %-worlds%]") + .description("Called when a world is saved to disk. Usually all worlds are saved simultaneously, but world management plugins could change this.") + .examples( + "on world save of \"world\":", + "\tbroadcast \"The world %event-world% has been saved\"") + .since("1.0, INSERT VERSION (defining worlds)"); + + // World Init Event + Skript.registerEvent("World Init", EvtWorld.class, WorldInitEvent.class, "world init[ialization] [of %-worlds%]") + .description("Called when a world is initialized. As all default worlds are initialized before", + "any scripts are loaded, this event is only called for newly created worlds.", + "World management plugins might change the behaviour of this event though.") + .examples("on world init of \"world_the_end\":") + .since("1.0, INSERT VERSION (defining worlds)"); + + // World Unload Event + Skript.registerEvent("World Unload", EvtWorld.class, WorldUnloadEvent.class, "world unload[ing] [of %-worlds%]") + .description("Called when a world is unloaded. This event will never be called if you don't have a world management plugin.") + .examples( + "on world unload:", + "\tbroadcast \"the %event-world% has been unloaded!\"") + .since("1.0, INSERT VERSION (defining worlds)"); + + // World Load Event + Skript.registerEvent("World Load", EvtWorld.class, WorldLoadEvent.class, "world load[ing] [of %-worlds%]") + .description("Called when a world is loaded. As with the world init event, this event will not be called for the server's default world(s).") + .examples( + "on world load of \"world_nether\":", + "\tbroadcast \"The world %event-world% has been loaded!\"") + .since("1.0, INSERT VERSION (defining worlds)"); + } + + private Literal<World> worlds; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult) { + worlds = (Literal<World>) args[0]; + if (worlds instanceof LiteralList<?> && worlds.getAnd()) { + ((LiteralList<World>) worlds).invertAnd(); + } + return true; + } + + @Override + public boolean check(Event event) { + if (worlds == null) + return true; + World evtWorld = ((WorldEvent) event).getWorld(); + return worlds.check(event, world -> world.equals(evtWorld)); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "world save/init/unload/load" + (worlds == null ? "" : " of " + worlds.toString(event,debug)); + } + +} diff --git a/src/main/java/ch/njol/skript/events/SimpleEvents.java b/src/main/java/ch/njol/skript/events/SimpleEvents.java index f4897b19e30..4d19f3186e2 100644 --- a/src/main/java/ch/njol/skript/events/SimpleEvents.java +++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java @@ -105,10 +105,6 @@ import org.bukkit.event.world.LootGenerateEvent; import org.bukkit.event.world.PortalCreateEvent; import org.bukkit.event.world.SpawnChangeEvent; -import org.bukkit.event.world.WorldInitEvent; -import org.bukkit.event.world.WorldLoadEvent; -import org.bukkit.event.world.WorldSaveEvent; -import org.bukkit.event.world.WorldUnloadEvent; import org.spigotmc.event.entity.EntityDismountEvent; import org.spigotmc.event.entity.EntityMountEvent; @@ -423,26 +419,6 @@ public class SimpleEvents { "\tkill event-entity") .since("2.2-dev13b"); } - Skript.registerEvent("World Init", SimpleEvent.class, WorldInitEvent.class, "world init[ialization]") - .description("Called when a world is initialised. As all default worlds are initialised before any scripts are loaded, this event is only called for newly created worlds.", - "World management plugins might change the behaviour of this event though.") - .examples("on world init:") - .since("1.0"); - Skript.registerEvent("World Load", SimpleEvent.class, WorldLoadEvent.class, "world load[ing]") - .description("Called when a world is loaded. As with the world init event, this event will not be called for the server's default world(s).") - .examples("on world load:", - "\tsend \"World is loading...\" to console") - .since("1.0"); - Skript.registerEvent("World Save", SimpleEvent.class, WorldSaveEvent.class, "world sav(e|ing)") - .description("Called when a world is saved to disk. Usually all worlds are saved simultaneously, but world management plugins could change this.") - .examples("on world saving:", - "\tbroadcast \"World has been saved!\"") - .since("1.0"); - Skript.registerEvent("World Unload", SimpleEvent.class, WorldUnloadEvent.class, "world unload[ing]") - .description("Called when a world is unloaded. This event might never be called if you don't have a world management plugin.") - .examples("on world unload:", - "\tcancel event") - .since("1.0"); if (Skript.classExists("org.bukkit.event.entity.EntityToggleGlideEvent")) { Skript.registerEvent("Gliding State Change", SimpleEvent.class, EntityToggleGlideEvent.class, "(gliding state change|toggl(e|ing) gliding)") .description("Called when an entity toggles glider on or off, or when server toggles gliding state of an entity forcibly.") From c1047c282a9fdbc62b6b49d9e2cb607c404dbf53 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Mon, 1 Jan 2024 16:14:49 -0500 Subject: [PATCH 567/619] Rewrite ExprFurnaceSlot (#4281) --- .../skript/expressions/ExprFurnaceSlot.java | 304 +++++++++++------- .../syntaxes/expressions/ExprFurnaceSlot.sk | 31 ++ 2 files changed, 215 insertions(+), 120 deletions(-) create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprFurnaceSlot.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprFurnaceSlot.java b/src/main/java/ch/njol/skript/expressions/ExprFurnaceSlot.java index 296817f14f1..980f4f5f733 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprFurnaceSlot.java +++ b/src/main/java/ch/njol/skript/expressions/ExprFurnaceSlot.java @@ -18,17 +18,6 @@ */ package ch.njol.skript.expressions; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.block.Furnace; -import org.bukkit.event.Event; -import org.bukkit.event.inventory.FurnaceBurnEvent; -import org.bukkit.event.inventory.FurnaceSmeltEvent; -import org.bukkit.inventory.FurnaceInventory; -import org.bukkit.inventory.ItemStack; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Events; @@ -36,160 +25,235 @@ import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.effects.Delay; -import ch.njol.skript.expressions.base.PropertyExpression; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.registrations.Classes; -import ch.njol.skript.util.Getter; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.skript.registrations.EventValues; import ch.njol.skript.util.slot.InventorySlot; import ch.njol.skript.util.slot.Slot; import ch.njol.util.Kleenean; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.block.Furnace; +import org.bukkit.event.Event; +import org.bukkit.event.inventory.FurnaceBurnEvent; +import org.bukkit.event.inventory.FurnaceSmeltEvent; +import org.bukkit.inventory.FurnaceInventory; +import org.bukkit.inventory.ItemStack; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.ArrayList; +import java.util.List; -/** - * @author Peter Güttinger - */ @Name("Furnace Slot") -@Description({"A slot of a furnace, i.e. either the ore, fuel or result slot.", - "Remember to use '<a href='#ExprBlock'>block</a>' and not 'furnace', as 'furnace' is not an existing expression."}) -@Examples({"set the fuel slot of the clicked block to a lava bucket", - "set the block's ore slot to 64 iron ore", - "give the result of the block to the player", - "clear the result slot of the block"}) -@Since("1.0") +@Description({ + "A slot of a furnace, i.e. either the ore, fuel or result slot.", + "Remember to use '<a href='#ExprBlock'>block</a>' and not <code>furnace</code>, as <code>furnace</code> is not an existing expression.", + "Note that <code>the result</code> and <code>the result slot</code> refer to separate things. <code>the result</code> is the product in a smelt event " + + "and <code>the result slot</code> is the output slot of a furnace (where <code>the result</code> will end up).", + "Note that if the result in a smelt event is changed to an item that differs in type from the items currently in " + + "the result slot, the smelting will fail to complete (the item will attempt to smelt itself again).", + "Note that if values other than <code>the result</code> are changed, event values may not accurately reflect the actual items in a furnace.", + "Thus you may wish to use the event block in this case (e.g. <code>the fuel slot of the event-block</code>) to get accurate values if needed." +}) +@Examples({ + "set the fuel slot of the clicked block to a lava bucket", + "set the block's ore slot to 64 iron ore", + "give the result of the block to the player", + "clear the result slot of the block" +}) @Events({"smelt", "fuel burn"}) -public class ExprFurnaceSlot extends PropertyExpression<Block, Slot> { - private final static int ORE = 0, FUEL = 1, RESULT = 2; - private final static String[] slotNames = {"ore", "fuel", "result"}; +@Since("1.0, INSERT VERSION (syntax rework)") +public class ExprFurnaceSlot extends SimpleExpression<Slot> { + + private static final int ORE = 0, FUEL = 1, RESULT = 2; static { Skript.registerExpression(ExprFurnaceSlot.class, Slot.class, ExpressionType.PROPERTY, - "(" + FUEL + "¦fuel|" + RESULT + "¦result) [slot]", - "(" + ORE + "¦ore|" + FUEL + "¦fuel|" + RESULT + "¦result)[s] [slot[s]] of %blocks%", - "%blocks%'[s] (" + ORE + "¦ore|" + FUEL + "¦fuel|" + RESULT + "¦result)[s] [slot[s]]"); + "[the] (0:ore slot|1:fuel slot|2:result [5:slot])", + "[the] (0:ore|1:fuel|2:result) slot[s] of %blocks%", + "%blocks%'[s] (0:ore|1:fuel|2:result) slot[s]" + ); } - - int slot; - boolean isEvent; - - @SuppressWarnings({"unchecked", "null"}) + + @Nullable + private Expression<Block> blocks; + private boolean isEvent; + private boolean isResultSlot; + private int slot; + @Override - public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { + @SuppressWarnings("unchecked") + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { isEvent = matchedPattern == 0; + if (!isEvent) + blocks = (Expression<Block>) exprs[0]; + slot = parseResult.mark; - if (isEvent && slot == RESULT && !getParser().isCurrentEvent(FurnaceSmeltEvent.class)) { - Skript.error("Cannot use 'result slot' outside a fuel smelt event."); + isResultSlot = slot == 7; + if (isResultSlot) + slot = RESULT; + + if (isEvent && (slot == ORE || slot == RESULT) && !getParser().isCurrentEvent(FurnaceSmeltEvent.class)) { + Skript.error("Cannot use 'result slot' or 'ore slot' outside an ore smelt event."); return false; } else if (isEvent && slot == FUEL && !getParser().isCurrentEvent(FurnaceBurnEvent.class)) { Skript.error("Cannot use 'fuel slot' outside a fuel burn event."); return false; } - if (!isEvent) - setExpr((Expression<Block>) exprs[0]); + return true; } + + @Override + @Nullable + protected Slot[] get(Event event) { + Block[] blocks; + if (isEvent) { + blocks = new Block[1]; + if (event instanceof FurnaceSmeltEvent) { + blocks[0] = ((FurnaceSmeltEvent) event).getBlock(); + } else if (event instanceof FurnaceBurnEvent) { + blocks[0] = ((FurnaceBurnEvent) event).getBlock(); + } else { + return new Slot[0]; + } + } else { + assert this.blocks != null; + blocks = this.blocks.getArray(event); + } + + List<Slot> slots = new ArrayList<>(); + for (Block block : blocks) { + BlockState state = block.getState(); + if (!(state instanceof Furnace)) + continue; + FurnaceInventory furnaceInventory = ((Furnace) state).getInventory(); + if (isEvent && !Delay.isDelayed(event)) { + slots.add(new FurnaceEventSlot(event, furnaceInventory)); + } else { // Normal inventory slot is fine since the time will always be in the present + slots.add(new InventorySlot(furnaceInventory, slot)); + } + } + return slots.toArray(new Slot[0]); + } + + @Override + public boolean isSingle() { + if (isEvent) + return true; + assert blocks != null; + return blocks.isSingle(); + } + + @Override + public Class<? extends Slot> getReturnType() { + return InventorySlot.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + String time = (getTime() == -1) ? "past " : (getTime() == 1) ? "future " : ""; + String slotName = (slot == ORE) ? "ore" : (slot == FUEL) ? "fuel" : "result"; + if (isEvent) { + return "the " + time + slotName + (isResultSlot ? " slot" : ""); + } else { + assert blocks != null; + return "the " + time + slotName + " slot of " + blocks.toString(event, debug); + } + } + + @Override + public boolean setTime(int time) { + if (isEvent) { // getExpr will be null + if (slot == RESULT && !isResultSlot) { // 'the past/future result' - doesn't make sense, don't allow it + return false; + } else if (slot == FUEL) { + return setTime(time, FurnaceBurnEvent.class); + } else { + return setTime(time, FurnaceSmeltEvent.class); + } + } + return false; + } private final class FurnaceEventSlot extends InventorySlot { - private final Event e; + private final Event event; - public FurnaceEventSlot(final Event e, final FurnaceInventory invi) { - super(invi, slot); - this.e = e; + public FurnaceEventSlot(Event event, FurnaceInventory furnaceInventory) { + super(furnaceInventory, slot); + this.event = event; } @Override @Nullable public ItemStack getItem() { switch (slot) { - case RESULT: - if (e instanceof FurnaceSmeltEvent) - return getTime() > -1 ? ((FurnaceSmeltEvent) e).getResult().clone() : super.getItem(); - else - return super.getItem(); - case FUEL: - if (e instanceof FurnaceBurnEvent) - return getTime() > -1 ? ((FurnaceBurnEvent) e).getFuel().clone() : super.getItem(); - else - return pastItem(); case ORE: - if (e instanceof FurnaceSmeltEvent) - return pastItem(); - else - return super.getItem(); - default: - return null; + if (event instanceof FurnaceSmeltEvent) { + ItemStack source = ((FurnaceSmeltEvent) event).getSource().clone(); + if (getTime() != EventValues.TIME_FUTURE) + return source; + source.setAmount(source.getAmount() - 1); + return source; + } + return super.getItem(); + case FUEL: + if (event instanceof FurnaceBurnEvent) { + ItemStack fuel = ((FurnaceBurnEvent) event).getFuel().clone(); + if (getTime() != EventValues.TIME_FUTURE) + return fuel; + // a single lava bucket becomes an empty bucket + // see https://minecraft.wiki/w/Smelting#Fuel + // this is declared here because setting the amount to 0 may cause the ItemStack to become AIR + Material newMaterial = fuel.getType() == Material.LAVA_BUCKET ? Material.BUCKET : Material.AIR; + fuel.setAmount(fuel.getAmount() - 1); + if (fuel.getAmount() == 0) + fuel = new ItemStack(newMaterial); + return fuel; + } + return super.getItem(); + case RESULT: + if (event instanceof FurnaceSmeltEvent) { + ItemStack result = ((FurnaceSmeltEvent) event).getResult().clone(); + if (isResultSlot) { // Special handling for getting the result slot + ItemStack currentResult = ((FurnaceInventory) getInventory()).getResult(); + if (currentResult != null) + currentResult = currentResult.clone(); + if (getTime() != EventValues.TIME_FUTURE) { // 'past result slot' and 'result slot' + return currentResult; + } else if (currentResult != null && currentResult.isSimilar(result)) { // 'future result slot' + currentResult.setAmount(currentResult.getAmount() + result.getAmount()); + return currentResult; + } else { + return result; + } + } + // 'the result' + return result; + } + return super.getItem(); } + return null; } - @SuppressWarnings("synthetic-access") @Override - public void setItem(final @Nullable ItemStack item) { - if (getTime() > -1) { - Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.getInstance(), - () -> FurnaceEventSlot.super.setItem(item)); + public void setItem(@Nullable ItemStack item) { + if (slot == RESULT && !isResultSlot && event instanceof FurnaceSmeltEvent) { + ((FurnaceSmeltEvent) event).setResult(item != null ? item : new ItemStack(Material.AIR)); } else { - if (e instanceof FurnaceSmeltEvent && slot == RESULT) { - if (item != null) - ((FurnaceSmeltEvent) e).setResult(item); - else - ((FurnaceSmeltEvent) e).setResult(new ItemStack(Material.AIR)); + if (getTime() == EventValues.TIME_FUTURE) { // Since this is a future expression, run it AFTER the event + Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.getInstance(), () -> FurnaceEventSlot.super.setItem(item)); } else { super.setItem(item); } } } - - @Nullable - private ItemStack pastItem() { - if (getTime() < 1) { - return super.getItem(); - } else { - ItemStack item = super.getItem(); - if (item == null) - return null; - item.setAmount(item.getAmount() - 1); - return item.getAmount() == 0 ? new ItemStack(Material.AIR, 1) : item; - } - } } - @Override - protected Slot[] get(final Event e, final Block[] source) { - return get(source, new Getter<Slot, Block>() { - @Override - @Nullable - public Slot get(final Block b) { - if (!ExprBurnCookTime.anyFurnace.isOfType(b)) - return null; - if (isEvent && getTime() > -1 && !Delay.isDelayed(e)) { - FurnaceInventory invi = ((Furnace) b.getState()).getInventory(); - return new FurnaceEventSlot(e, invi); - } else { - FurnaceInventory invi = ((Furnace) b.getState()).getInventory(); - return new InventorySlot(invi, slot); - } - } - }); - } - - @Override - public Class<Slot> getReturnType() { - return Slot.class; - } - - @Override - public String toString(final @Nullable Event e, final boolean debug) { - if (e == null) - return "the " + (getTime() == -1 ? "past " : getTime() == 1 ? "future " : "") + slotNames[slot] + " slot of " + getExpr().toString(e, debug); - return Classes.getDebugMessage(getSingle(e)); - } - - @SuppressWarnings("unchecked") - @Override - public boolean setTime(final int time) { - return super.setTime(time, getExpr(), FurnaceSmeltEvent.class, FurnaceBurnEvent.class); - } - } diff --git a/src/test/skript/tests/syntaxes/expressions/ExprFurnaceSlot.sk b/src/test/skript/tests/syntaxes/expressions/ExprFurnaceSlot.sk new file mode 100644 index 00000000000..9413338b374 --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprFurnaceSlot.sk @@ -0,0 +1,31 @@ +test "furnace slot": + # init + set block at location(0, 0, 0, world) to furnace + set {_furnace} to block at location(0, 0, 0, world) + + # ore slot test + set ore slot of {_furnace} to 32 iron ores + assert ore slot of {_furnace} is 32 iron ores with "expected ore slot to have '32 iron ores', but it had '%ore slot of {_furnace}%'" + assert fuel slot of {_furnace} is air with "expected fuel slot to have nothing, but it had '%fuel slot of {_furnace}%'" + assert result slot of {_furnace} is air with "expected result slot to have nothing, but it had '%result slot of {_furnace}%'" + clear ore slot of {_furnace} + assert ore slot of {_furnace} is air with "expected ore slot to have nothing, but it had '%ore slot of {_furnace}%'" + + # fuel slot test + set fuel slot of {_furnace} to lava bucket + assert fuel slot of {_furnace} is lava bucket with "expected fuel slot to have 'lava bucket', but it had '%fuel slot of {_furnace}%'" + assert ore slot of {_furnace} is air with "expected ore slot to have nothing, but it had '%ore slot of {_furnace}%'" + assert result slot of {_furnace} is air with "expected result slot to have nothing, but it had '%result slot of {_furnace}%'" + clear fuel slot of {_furnace} + assert fuel slot of {_furnace} is air with "expected fuel slot to have nothing, but it had '%fuel slot of {_furnace}%'" + + # ore slot test + set result slot of {_furnace} to 32 iron ingots + assert result slot of {_furnace} is 32 iron ingots with "expected result slot to have '32 iron ingots', but it had '%result slot of {_furnace}%'" + assert ore slot of {_furnace} is air with "expected ore slot to have nothing, but it had '%ore slot of {_furnace}%'" + assert fuel slot of {_furnace} is air with "expected fuel slot to have nothing, but it had '%fuel slot of {_furnace}%'" + clear result slot of {_furnace} + assert result slot of {_furnace} is air with "expected result slot to have nothing, but it had '%result slot of {_furnace}%'" + + # cleanup + set block at location(0, 0, 0, world) to air From f14816e7155163705c2a9388191b6da1a6a391ec Mon Sep 17 00:00:00 2001 From: _tud <98935832+UnderscoreTud@users.noreply.github.com> Date: Tue, 2 Jan 2024 00:23:14 +0300 Subject: [PATCH 568/619] Copy Effect (#6101) --- .../java/ch/njol/skript/effects/EffCopy.java | 166 ++++++++++++++++++ .../skript/tests/syntaxes/effects/EffCopy.sk | 30 ++++ 2 files changed, 196 insertions(+) create mode 100644 src/main/java/ch/njol/skript/effects/EffCopy.java create mode 100644 src/test/skript/tests/syntaxes/effects/EffCopy.sk diff --git a/src/main/java/ch/njol/skript/effects/EffCopy.java b/src/main/java/ch/njol/skript/effects/EffCopy.java new file mode 100644 index 00000000000..896fdafa0a3 --- /dev/null +++ b/src/main/java/ch/njol/skript/effects/EffCopy.java @@ -0,0 +1,166 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.effects; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Keywords; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionList; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.Variable; +import ch.njol.skript.registrations.Classes; +import ch.njol.skript.variables.Variables; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.*; + +@Name("Copy Into Variable") +@Description({ + "Copies objects into a variable. When copying a list over to another list, the source list and its sublists are also copied over.", + "<strong>Note: Copying a value into a variable/list will overwrite the existing data.</strong>" +}) +@Examples({ + "set {_foo::bar} to 1", + "set {_foo::sublist::foobar} to \"hey\"", + "copy {_foo::*} to {_copy::*}", + "broadcast indices of {_copy::*} # bar, sublist", + "broadcast {_copy::bar} # 1", + "broadcast {_copy::sublist::foobar} # \"hey!\"" +}) +@Since("INSERT VERSION") +@Keywords({"clone", "variable", "list"}) +public class EffCopy extends Effect { + + static { + Skript.registerEffect(EffCopy.class, "copy %~objects% [in]to %~objects%"); + } + + private Expression<?> source; + private Expression<?> rawDestination; + private List<Variable<?>> destinations; + + @Override + public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + source = exprs[0]; + rawDestination = exprs[1]; + if (exprs[1] instanceof Variable<?>) { + destinations = Collections.singletonList((Variable<?>) exprs[1]); + } else if (exprs[1] instanceof ExpressionList<?>) { + destinations = unwrapExpressionList((ExpressionList<?>) exprs[1]); + } + if (destinations == null) { + Skript.error("You can only copy objects into variables"); + return false; + } + for (Variable<?> destination : destinations) { + if (!source.isSingle() && destination.isSingle()) { + Skript.error("Cannot copy multiple objects into a single variable"); + return false; + } + } + return true; + } + + @Override + @SuppressWarnings("unchecked") + protected void execute(Event event) { + if (!(source instanceof Variable) || source.isSingle()) { + ChangeMode mode = ChangeMode.SET; + Object[] clone = (Object[]) Classes.clone(source.getArray(event)); + if (clone.length == 0) + mode = ChangeMode.DELETE; + for (Variable<?> dest : destinations) + dest.change(event, clone, mode); + return; + } + + Map<String, Object> source = copyMap((Map<String, Object>) ((Variable<?>) this.source).getRaw(event)); + + // If we're copying {_foo::*} we don't want to also copy {_foo} + if (source != null) + source.remove(null); + + for (Variable<?> destination : destinations) { + destination.change(event, null, ChangeMode.DELETE); + if (source == null) + continue; + + String target = destination.getName().getSingle(event); + target = target.substring(0, target.length() - (Variable.SEPARATOR + "*").length()); + set(event, target, source, destination.isLocal()); + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "copy " + source.toString(event, debug) + " into " + rawDestination.toString(event, debug); + } + + @SuppressWarnings("unchecked") + @Nullable + private static Map<String, Object> copyMap(@Nullable Map<String, Object> map) { + if (map == null) + return null; + Map<String, Object> copy = new HashMap<>(map.size()); + map.forEach((key, value) -> { + if (value instanceof Map) { + copy.put(key, copyMap((Map<String, Object>) value)); + return; + } + copy.put(key, Classes.clone(value)); + }); + return copy; + } + + @SuppressWarnings("unchecked") + private static void set(Event event, String targetName, Map<String, Object> source, boolean local) { + source.forEach((key, value) -> { + String node = targetName + (key == null ? "" : Variable.SEPARATOR + key); + if (value instanceof Map) { + set(event, node, (Map<String, Object>) value, local); + return; + } + Variables.setVariable(node, value, event, local); + }); + } + + private static List<Variable<?>> unwrapExpressionList(ExpressionList<?> expressionList) { + Expression<?>[] expressions = expressionList.getExpressions(); + List<Variable<?>> destinations = new ArrayList<>(); + for (Expression<?> expression : expressions) { + if (expression instanceof Variable<?>) { + destinations.add((Variable<?>) expression); + continue; + } + if (!(expression instanceof ExpressionList<?>)) + return null; + destinations.addAll(unwrapExpressionList((ExpressionList<?>) expression)); + } + return destinations; + } + +} diff --git a/src/test/skript/tests/syntaxes/effects/EffCopy.sk b/src/test/skript/tests/syntaxes/effects/EffCopy.sk new file mode 100644 index 00000000000..88c6eec9abb --- /dev/null +++ b/src/test/skript/tests/syntaxes/effects/EffCopy.sk @@ -0,0 +1,30 @@ +test "list copy": + set {_foo} to "should not be copied" + set {_foo::a} to "foo" + set {_foo::a::b} to "bar" + set {_foo::c::*} to "hello" and "world" + + set {_bar::*} to "should", "be" and "removed" + copy {_foo::*} into {_bar::*} + + assert {_bar} is not set with "Copied value of base variable" + assert {_bar::1} is not set with "Didn't override existing data 1" + assert {_bar::a} is "foo" with "Didn't copy {_foo::a}" + assert {_bar::a::b} is "bar" with "Didn't copy {_foo::a::b}" + assert {_bar::c::*} is "hello" or "world" with "Didn't copy {_foo::c::*}" + + set {_var} to 10 + copy {_var} into {_bar::*} + assert {_bar::1} is 10 with "Didn't copy single value" + assert {_bar::a} is not set with "Didn't override existing data 2" + + copy {_none} into {_bar::*} + assert {_bar::*} doesn't exist with "Copying nothing didn't delete existing data" + +test "single copy": + set {_var} to 10 + copy {_var} into {_foo} + assert {_foo} is 10 with "Didn't copy single value" + + copy {_none} into {_foo} + assert {_foo} is not set with "Copying nothing didn't delete variable" From 669ee87515461906fb223fe0673e49cd6334e59b Mon Sep 17 00:00:00 2001 From: _tud <98935832+UnderscoreTud@users.noreply.github.com> Date: Tue, 2 Jan 2024 00:27:53 +0300 Subject: [PATCH 569/619] Improve ExprElement (#5478) --- .../njol/skript/expressions/ExprElement.java | 192 ++++++++++++------ .../ch/njol/util/coll/CollectionUtils.java | 25 ++- .../tests/syntaxes/expressions/ExprElement.sk | 14 ++ 3 files changed, 167 insertions(+), 64 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprElement.java b/src/main/java/ch/njol/skript/expressions/ExprElement.java index 52323f8e5e3..3e5efcdef4e 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprElement.java +++ b/src/main/java/ch/njol/skript/expressions/ExprElement.java @@ -29,89 +29,142 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.skript.util.LiteralUtils; +import ch.njol.skript.util.Patterns; import ch.njol.util.Kleenean; import ch.njol.util.StringUtils; import ch.njol.util.coll.CollectionUtils; import com.google.common.collect.Iterators; +import org.apache.commons.lang.ArrayUtils; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; import java.lang.reflect.Array; import java.util.Iterator; -@Name("Element of") -@Description({"The first, last or a random element of a set, e.g. a list variable.", - "See also: <a href='#ExprRandom'>random</a>"}) -@Examples("give a random element out of {free items::*} to the player") -@Since("2.0, 2.7 (relative to last element)") -public class ExprElement extends SimpleExpression<Object> { +@Name("Elements") +@Description({ + "The first, last, range or a random element of a set, e.g. a list variable.", + "See also: <a href='#ExprRandom'>random expression</a>" +}) +@Examples("broadcast the first 3 elements of {top players::*}") +@Since("2.0, 2.7 (relative to last element), INSERT VERSION (range of elements)") +public class ExprElement<T> extends SimpleExpression<T> { + + private static final Patterns<ElementType[]> PATTERNS = new Patterns<>(new Object[][]{ + {"[the] (first|1:last) element [out] of %objects%", new ElementType[] {ElementType.FIRST_ELEMENT, ElementType.LAST_ELEMENT}}, + {"[the] (first|1:last) %integer% elements [out] of %objects%", new ElementType[] {ElementType.FIRST_X_ELEMENTS, ElementType.LAST_X_ELEMENTS}}, + {"[a] random element [out] of %objects%", new ElementType[] {ElementType.RANDOM}}, + {"[the] %integer%(st|nd|rd|th) [1:[to] last] element [out] of %objects%", new ElementType[] {ElementType.ORDINAL, ElementType.TAIL_END_ORDINAL}}, + {"[the] elements (from|between) %integer% (to|and) %integer% [out] of %objects%", new ElementType[] {ElementType.RANGE}} + }); static { - Skript.registerExpression(ExprElement.class, Object.class, ExpressionType.PROPERTY, "(0:[the] first|1:[the] last|2:[a] random|3:[the] %-number%(st|nd|rd|th)|4:[the] %-number%(st|nd|rd|th) [to] last) element [out] of %objects%"); + //noinspection unchecked + Skript.registerExpression(ExprElement.class, Object.class, ExpressionType.PROPERTY, PATTERNS.getPatterns()); } private enum ElementType { - FIRST, LAST, RANDOM, ORDINAL, TAIL_END_ORDINAL + FIRST_ELEMENT, + LAST_ELEMENT, + FIRST_X_ELEMENTS, + LAST_X_ELEMENTS, + RANDOM, + ORDINAL, + TAIL_END_ORDINAL, + RANGE } + private Expression<? extends T> expr; + private @Nullable Expression<Integer> startIndex, endIndex; private ElementType type; - private Expression<?> expr; - - @Nullable - private Expression<Number> number; - @Override @SuppressWarnings("unchecked") public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - expr = LiteralUtils.defendExpression(exprs[2]); - type = ElementType.values()[parseResult.mark]; - number = (Expression<Number>) (type == ElementType.ORDINAL ? exprs[0]: exprs[1]); + ElementType[] types = PATTERNS.getInfo(matchedPattern); + expr = LiteralUtils.defendExpression(exprs[exprs.length - 1]); + switch (type = types[parseResult.mark]) { + case RANGE: + endIndex = (Expression<Integer>) exprs[1]; + //$FALL-THROUGH$ + case FIRST_X_ELEMENTS: + case LAST_X_ELEMENTS: + case ORDINAL: + case TAIL_END_ORDINAL: + startIndex = (Expression<Integer>) exprs[0]; + break; + default: + startIndex = null; + break; + } return LiteralUtils.canInitSafely(expr); } @Override @Nullable - protected Object[] get(Event event) { - Iterator<?> iter = expr.iterator(event); - if (iter == null || !iter.hasNext()) + @SuppressWarnings("unchecked") + protected T[] get(Event event) { + Iterator<? extends T> iterator = expr.iterator(event); + if (iterator == null || !iterator.hasNext()) return null; - Object element = null; + T element = null; + Class<T> returnType = (Class<T>) getReturnType(); + int startIndex = 0, endIndex = 0; + if (this.startIndex != null) { + Integer integer = this.startIndex.getSingle(event); + if (integer == null) + return null; + startIndex = integer; + if (startIndex <= 0 && type != ElementType.RANGE) + return null; + } + if (this.endIndex != null) { + Integer integer = this.endIndex.getSingle(event); + if (integer == null) + return null; + endIndex = integer; + } + T[] elementArray; switch (type) { - case FIRST: - element = iter.next(); + case FIRST_ELEMENT: + element = iterator.next(); + break; + case LAST_ELEMENT: + element = Iterators.getLast(iterator); break; - case LAST: - element = Iterators.getLast(iter); + case RANDOM: + element = CollectionUtils.getRandom(Iterators.toArray(iterator, returnType)); break; case ORDINAL: - assert this.number != null; - Number number = this.number.getSingle(event); - if (number == null) + Iterators.advance(iterator, startIndex - 1); + if (!iterator.hasNext()) return null; - try { - element = Iterators.get(iter, number.intValue() - 1); - } catch (IndexOutOfBoundsException exception) { - return null; - } - break; - case RANDOM: - Object[] allIterValues = Iterators.toArray(iter, Object.class); - element = CollectionUtils.getRandom(allIterValues); + element = iterator.next(); break; case TAIL_END_ORDINAL: - allIterValues = Iterators.toArray(iter, Object.class); - assert this.number != null; - number = this.number.getSingle(event); - if (number == null) - return null; - int ordinal = number.intValue(); - if (ordinal <= 0 || ordinal > allIterValues.length) + elementArray = Iterators.toArray(iterator, returnType); + if (startIndex > elementArray.length) return null; - element = allIterValues[allIterValues.length - ordinal]; + element = elementArray[elementArray.length - startIndex]; break; + case FIRST_X_ELEMENTS: + return Iterators.toArray(Iterators.limit(iterator, startIndex), returnType); + case LAST_X_ELEMENTS: + elementArray = Iterators.toArray(iterator, returnType); + startIndex = Math.min(startIndex, elementArray.length); + return CollectionUtils.subarray(elementArray, elementArray.length - startIndex, elementArray.length); + case RANGE: + elementArray = Iterators.toArray(iterator, returnType); + boolean reverse = startIndex > endIndex; + int from = Math.min(startIndex, endIndex) - 1; + int to = Math.max(startIndex, endIndex); + T[] elements = CollectionUtils.subarray(elementArray, from, to); + if (reverse) + ArrayUtils.reverse(elements); + return elements; } - Object[] elementArray = (Object[]) Array.newInstance(getReturnType(), 1); + //noinspection unchecked + elementArray = (T[]) Array.newInstance(getReturnType(), 1); elementArray[0] = element; return elementArray; } @@ -124,54 +177,69 @@ public <R> Expression<? extends R> getConvertedExpression(Class<R>... to) { if (convExpr == null) return null; - ExprElement exprElement = new ExprElement(); - exprElement.type = this.type; + ExprElement<R> exprElement = new ExprElement<>(); exprElement.expr = convExpr; - exprElement.number = this.number; - return (Expression<? extends R>) exprElement; + exprElement.startIndex = startIndex; + exprElement.endIndex = endIndex; + exprElement.type = type; + return exprElement; } @Override public boolean isSingle() { - return true; + return type != ElementType.FIRST_X_ELEMENTS && type != ElementType.LAST_X_ELEMENTS && type != ElementType.RANGE; } @Override - public Class<?> getReturnType() { + public Class<? extends T> getReturnType() { return expr.getReturnType(); } @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { String prefix; switch (type) { - case FIRST: + case FIRST_ELEMENT: prefix = "the first"; break; - case LAST: + case LAST_ELEMENT: prefix = "the last"; break; + case FIRST_X_ELEMENTS: + assert startIndex != null; + prefix = "the first " + startIndex.toString(event, debug); + break; + case LAST_X_ELEMENTS: + assert startIndex != null; + prefix = "the last " + startIndex.toString(event, debug); + break; case RANDOM: prefix = "a random"; break; case ORDINAL: - assert number != null; + case TAIL_END_ORDINAL: + assert startIndex != null; prefix = "the "; // Proper ordinal number - if (number instanceof Literal) { - Number number = ((Literal<Number>) this.number).getSingle(); - if (number == null) - prefix += this.number.toString(e, debug) + "th"; + if (startIndex instanceof Literal) { + Integer integer = ((Literal<Integer>) startIndex).getSingle(); + if (integer == null) + prefix += startIndex.toString(event, debug) + "th"; else - prefix += StringUtils.fancyOrderNumber(number.intValue()); + prefix += StringUtils.fancyOrderNumber(integer); } else { - prefix += number.toString(e, debug) + "th"; + prefix += startIndex.toString(event, debug) + "th"; } + if (type == ElementType.TAIL_END_ORDINAL) + prefix += " last"; break; + case RANGE: + assert startIndex != null && endIndex != null; + return "the elements from " + startIndex.toString(event, debug) + " to " + endIndex.toString(event, debug) + " of " + expr.toString(event, debug); default: throw new IllegalStateException(); } - return prefix + " element of " + expr.toString(e, debug); + return prefix + (isSingle() ? " element" : " elements") + " of " + expr.toString(event, debug); } } diff --git a/src/main/java/ch/njol/util/coll/CollectionUtils.java b/src/main/java/ch/njol/util/coll/CollectionUtils.java index 908f245e7ff..d923f609e12 100644 --- a/src/main/java/ch/njol/util/coll/CollectionUtils.java +++ b/src/main/java/ch/njol/util/coll/CollectionUtils.java @@ -367,7 +367,28 @@ public static <T> T[] array(final T... array) { public static <T> Class<T[]> arrayType(Class<T> c) { return (Class<T[]>) Array.newInstance(c, 0).getClass(); } - + + /** + * Creates a new array whose elements are the elements between the start and end indices of the original array. + * @param array the original array + * @param startIndex starting index (inclusive) + * @param endIndex ending index (exclusive) + * @return a new array containing the elements between the start and end indices + * @param <T> type of array + */ + @SuppressWarnings("unchecked") + public static <T> T[] subarray(T[] array, int startIndex, int endIndex) { + Class<T> componentType = (Class<T>) array.getClass().getComponentType(); + startIndex = Math.max(startIndex, 0); + endIndex = Math.min(Math.max(endIndex, 0), array.length); + if (startIndex >= endIndex) + return (T[]) Array.newInstance(componentType, 0); + int size = endIndex - startIndex; + T[] subarray = (T[]) Array.newInstance(componentType, size); + System.arraycopy(array, startIndex, subarray, 0, size); + return subarray; + } + /** * Creates a permutation of all integers in the interval [start, end] * @@ -458,5 +479,5 @@ public static Double[] wrap(double[] primitive) { } return wrapped; } - + } diff --git a/src/test/skript/tests/syntaxes/expressions/ExprElement.sk b/src/test/skript/tests/syntaxes/expressions/ExprElement.sk index 1cfb675595f..ee685c47296 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprElement.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprElement.sk @@ -10,3 +10,17 @@ test "ExprElement": assert 1st last element of {_list::*} is "foobar" with "Incorrect 1st last element" assert 0th last element of {_list::*} is not set with "Incorrect 0th last element" assert -1th last element of {_list::*} is not set with "Incorrect -1th element" + assert first 2 elements of {_list::*} are "foo" or "bar" with "Incorrect first 2 elements" + assert first -1 elements of {_list::*} is not set with "Incorrect first -1 elements" + assert first 5 elements of {_list::*} are "foo", "bar" or "foobar" with "Incorrect first 5 elements" + assert last 2 elements of {_list::*} are "bar" or "foobar" with "Incorrect last 2 elements" + assert last -1 elements of {_list::*} is not set with "Incorrect last -1 elements" + assert last 5 elements of {_list::*} are "foo", "bar" or "foobar" with "Incorrect last 5 elements" + assert elements from 1 to 2 of {_list::*} are "foo" or "bar" with "Incorrect elements from 1 to 2" + assert elements from 2 to 1 of {_list::*} are "foo" or "bar" with "Incorrect elements from 2 to 1" + assert elements from 2 to 3 of {_list::*} are "bar" or "foobar" with "Incorrect elements from 2 to 3" + assert elements from 3 to 3 of {_list::*} is "foobar" with "Incorrect elements from 3 to 3" + assert elements from -1 to 4 of {_list::*} are "foo", "bar" or "foobar" with "Incorrect elements from -1 to 4" + assert elements from 5 to 5 of {_list::*} is not set with "Incorrect elements from 5 to 5" + assert elements from 1 to 1 of {_list::*} is "foo" with "Incorrect elements from 1 to 1" + assert (first element out of (5 and 6)) + 5 is 10 with "Incorrect return type" From 38cc0fe26a4e88782d69da59206e30afeaad398e Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Mon, 1 Jan 2024 17:40:03 -0500 Subject: [PATCH 570/619] Prepare For Release (2.8.0-pre1) (#6282) --- gradle.properties | 2 +- skript-aliases | 2 +- .../ch/njol/skript/classes/data/BukkitClasses.java | 6 +++--- .../ch/njol/skript/classes/data/DefaultFunctions.java | 8 ++++---- .../ch/njol/skript/conditions/CondCanPickUpItems.java | 2 +- .../ch/njol/skript/conditions/CondGlowingText.java | 2 +- .../ch/njol/skript/conditions/CondHasItemCooldown.java | 2 +- .../ch/njol/skript/conditions/CondHasLineOfSight.java | 2 +- .../java/ch/njol/skript/conditions/CondIsClimbing.java | 2 +- .../ch/njol/skript/conditions/CondIsHandRaised.java | 2 +- .../java/ch/njol/skript/conditions/CondIsJumping.java | 2 +- .../ch/njol/skript/conditions/CondIsLeftHanded.java | 2 +- .../java/ch/njol/skript/conditions/CondIsSheared.java | 2 +- .../java/ch/njol/skript/effects/EffApplyBoneMeal.java | 2 +- .../java/ch/njol/skript/effects/EffCancelItemUse.java | 2 +- src/main/java/ch/njol/skript/effects/EffCommand.java | 2 +- src/main/java/ch/njol/skript/effects/EffContinue.java | 2 +- src/main/java/ch/njol/skript/effects/EffCopy.java | 2 +- .../java/ch/njol/skript/effects/EffGlowingText.java | 2 +- .../java/ch/njol/skript/effects/EffHandedness.java | 2 +- src/main/java/ch/njol/skript/effects/EffReturn.java | 2 +- src/main/java/ch/njol/skript/effects/EffShear.java | 2 +- .../njol/skript/effects/EffToggleCanPickUpItems.java | 2 +- src/main/java/ch/njol/skript/effects/EffWorldLoad.java | 2 +- src/main/java/ch/njol/skript/effects/EffWorldSave.java | 2 +- .../ch/njol/skript/events/EvtEntityBlockChange.java | 2 +- .../java/ch/njol/skript/events/EvtEntityTransform.java | 2 +- src/main/java/ch/njol/skript/events/EvtGrow.java | 2 +- src/main/java/ch/njol/skript/events/EvtItem.java | 4 ++-- src/main/java/ch/njol/skript/events/EvtMove.java | 2 +- .../ch/njol/skript/events/EvtPlayerCommandSend.java | 2 +- src/main/java/ch/njol/skript/events/EvtWorld.java | 8 ++++---- src/main/java/ch/njol/skript/events/SimpleEvents.java | 6 +++--- .../ch/njol/skript/expressions/ExprActiveItem.java | 2 +- .../njol/skript/expressions/ExprAnvilRepairCost.java | 2 +- .../java/ch/njol/skript/expressions/ExprArmorSlot.java | 2 +- .../ch/njol/skript/expressions/ExprAttachedBlock.java | 2 +- .../ch/njol/skript/expressions/ExprCharacters.java | 2 +- .../ch/njol/skript/expressions/ExprChestInventory.java | 2 +- .../java/ch/njol/skript/expressions/ExprChunk.java | 2 +- .../java/ch/njol/skript/expressions/ExprDamage.java | 2 +- .../java/ch/njol/skript/expressions/ExprElement.java | 2 +- .../njol/skript/expressions/ExprEntityItemUseTime.java | 2 +- .../ch/njol/skript/expressions/ExprEvtInitiator.java | 2 +- .../ch/njol/skript/expressions/ExprFurnaceSlot.java | 2 +- .../skript/expressions/ExprInventoryCloseReason.java | 2 +- .../ch/njol/skript/expressions/ExprItemCooldown.java | 2 +- .../java/ch/njol/skript/expressions/ExprItemsIn.java | 2 +- .../ch/njol/skript/expressions/ExprLoopIteration.java | 2 +- .../java/ch/njol/skript/expressions/ExprLoopValue.java | 2 +- .../ch/njol/skript/expressions/ExprMaxItemUseTime.java | 2 +- .../java/ch/njol/skript/expressions/ExprMemory.java | 2 +- .../java/ch/njol/skript/expressions/ExprPercent.java | 2 +- .../ch/njol/skript/expressions/ExprPortalCooldown.java | 2 +- .../ch/njol/skript/expressions/ExprQuitReason.java | 2 +- .../njol/skript/expressions/ExprRandomCharacter.java | 2 +- .../ch/njol/skript/expressions/ExprReadiedArrow.java | 2 +- .../java/ch/njol/skript/expressions/ExprRepeat.java | 2 +- .../ch/njol/skript/expressions/ExprSentCommands.java | 2 +- .../java/ch/njol/skript/expressions/ExprSlotIndex.java | 2 +- .../java/ch/njol/skript/expressions/ExprTarget.java | 2 +- .../njol/skript/expressions/ExprTransformReason.java | 2 +- .../njol/skript/expressions/ExprVectorArithmetic.java | 2 +- .../skript/expressions/ExprVectorFromDirection.java | 2 +- .../njol/skript/expressions/ExprVectorProjection.java | 2 +- .../tests/syntaxes/expressions/ExprArithmetic.sk | 10 +++++----- 66 files changed, 81 insertions(+), 81 deletions(-) diff --git a/gradle.properties b/gradle.properties index 4542c647233..0bfaa37069b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ org.gradle.parallel=true groupid=ch.njol name=skript -version=2.8.0-dev +version=2.8.0-pre1 jarName=Skript.jar testEnv=java17/paper-1.20.4 testEnvJavaVersion=17 diff --git a/skript-aliases b/skript-aliases index 0884ede0fdf..703f47aa957 160000 --- a/skript-aliases +++ b/skript-aliases @@ -1 +1 @@ -Subproject commit 0884ede0fdf69e914b944500d9f24f1c000a90a2 +Subproject commit 703f47aa957346dcce8642a14a168e05e5f69f40 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 1588be715ea..1f65e0a7aaf 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -1533,7 +1533,7 @@ public String toVariableNameString(EnchantmentOffer eo) { .name("Quit Reason") .description("Represents a quit reason from a <a href='/events.html#quit'>player quit server event</a>.") .requiredPlugins("Paper 1.16.5+") - .since("INSERT VERSION")); + .since("2.8.0")); if (Skript.classExists("org.bukkit.event.inventory.InventoryCloseEvent$Reason")) Classes.registerClass(new EnumClassInfo<>(InventoryCloseEvent.Reason.class, "inventoryclosereason", "inventory close reasons") @@ -1541,13 +1541,13 @@ public String toVariableNameString(EnchantmentOffer eo) { .name("Inventory Close Reasons") .description("The inventory close reason in an <a href='/events.html#inventory_close'>inventory close event</a>.") .requiredPlugins("Paper") - .since("INSERT VERSION")); + .since("2.8.0")); Classes.registerClass(new EnumClassInfo<>(TransformReason.class, "transformreason", "transform reasons") .user("(entity)? ?transform ?(reason|cause)s?") .name("Transform Reason") .description("Represents a transform reason of an <a href='events.html#entity transform'>entity transform event</a>.") - .since("INSERT VERSION")); + .since("2.8.0")); } } diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java b/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java index e9cb7dc1e97..556f396b80b 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java @@ -336,7 +336,7 @@ public Number[] executeSimple(Object[][] params) { "clamp(5, 7, 10) = 7", "clamp((5, 0, 10, 9, 13), 7, 10) = (7, 7, 10, 9, 10)", "set {_clamped::*} to clamp({_values::*}, 0, 10)") - .since("INSERT VERSION"); + .since("2.8.0"); // misc @@ -522,7 +522,7 @@ public Player[] executeSimple(Object[][] params) { } }).description("Returns an online player from their name or UUID, if player is offline function will return nothing.", "Setting 'getExactPlayer' parameter to true will return the player whose name is exactly equal to the provided name instead of returning a player that their name starts with the provided name.") .examples("set {_p} to player(\"Notch\") # will return an online player whose name is or starts with 'Notch'", "set {_p} to player(\"Notch\", true) # will return the only online player whose name is 'Notch'", "set {_p} to player(\"069a79f4-44e9-4726-a5be-fca90e38aaf5\") # <none> if player is offline") - .since("INSERT VERSION"); + .since("2.8.0"); Functions.registerFunction(new SimpleJavaFunction<OfflinePlayer>("offlineplayer", new Parameter[] { new Parameter<>("nameOrUUID", DefaultClasses.STRING, true, null) @@ -540,7 +540,7 @@ public OfflinePlayer[] executeSimple(Object[][] params) { } }).description("Returns a offline player from their name or UUID. This function will still return the player if they're online.") .examples("set {_p} to offlineplayer(\"Notch\")", "set {_p} to offlineplayer(\"069a79f4-44e9-4726-a5be-fca90e38aaf5\")") - .since("INSERT VERSION"); + .since("2.8.0"); Functions.registerFunction(new SimpleJavaFunction<Boolean>("isNaN", numberParam, DefaultClasses.BOOLEAN, true) { @Override @@ -549,7 +549,7 @@ public Boolean[] executeSimple(Object[][] params) { } }).description("Returns true if the input is NaN (not a number).") .examples("isNaN(0) # false", "isNaN(0/0) # true", "isNaN(sqrt(-1)) # true") - .since("INSERT VERSION"); + .since("2.8.0"); } } diff --git a/src/main/java/ch/njol/skript/conditions/CondCanPickUpItems.java b/src/main/java/ch/njol/skript/conditions/CondCanPickUpItems.java index d5a0aaaa591..07f5735e4f9 100644 --- a/src/main/java/ch/njol/skript/conditions/CondCanPickUpItems.java +++ b/src/main/java/ch/njol/skript/conditions/CondCanPickUpItems.java @@ -35,7 +35,7 @@ "\tif player can't pick up items:", "\t\tsend \"Be careful, you won't be able to pick that up!\" to player" }) -@Since("INSERT VERSION") +@Since("2.8.0") public class CondCanPickUpItems extends PropertyCondition<LivingEntity> { static { diff --git a/src/main/java/ch/njol/skript/conditions/CondGlowingText.java b/src/main/java/ch/njol/skript/conditions/CondGlowingText.java index 7d12e477712..aaf14939305 100644 --- a/src/main/java/ch/njol/skript/conditions/CondGlowingText.java +++ b/src/main/java/ch/njol/skript/conditions/CondGlowingText.java @@ -35,7 +35,7 @@ @Name("Has Glowing Text") @Description("Checks whether a sign (either a block or an item) has glowing text") @Examples("if target block has glowing text") -@Since("INSERT VERSION") +@Since("2.8.0") public class CondGlowingText extends PropertyCondition<Object> { static { diff --git a/src/main/java/ch/njol/skript/conditions/CondHasItemCooldown.java b/src/main/java/ch/njol/skript/conditions/CondHasItemCooldown.java index be79e48220b..77902e88026 100644 --- a/src/main/java/ch/njol/skript/conditions/CondHasItemCooldown.java +++ b/src/main/java/ch/njol/skript/conditions/CondHasItemCooldown.java @@ -40,7 +40,7 @@ "if player has player's tool on cooldown:", "\tsend \"You can't use this item right now. Wait %item cooldown of player's tool for player%\"" }) -@Since("INSERT VERSION") +@Since("2.8.0") public class CondHasItemCooldown extends Condition { static { diff --git a/src/main/java/ch/njol/skript/conditions/CondHasLineOfSight.java b/src/main/java/ch/njol/skript/conditions/CondHasLineOfSight.java index dd4a18f9ab0..74f820b5613 100644 --- a/src/main/java/ch/njol/skript/conditions/CondHasLineOfSight.java +++ b/src/main/java/ch/njol/skript/conditions/CondHasLineOfSight.java @@ -40,7 +40,7 @@ "victim has line of sight to attacker", "player has no line of sight to location 100 blocks in front of player" }) -@Since("INSERT VERSION") +@Since("2.8.0") public class CondHasLineOfSight extends Condition { static { diff --git a/src/main/java/ch/njol/skript/conditions/CondIsClimbing.java b/src/main/java/ch/njol/skript/conditions/CondIsClimbing.java index af56f90379f..48cdfe5eb40 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsClimbing.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsClimbing.java @@ -37,7 +37,7 @@ "\tmessage\"The spider is now climbing!\"" }) @RequiredPlugins("Minecraft 1.17+") -@Since("INSERT VERSION") +@Since("2.8.0") public class CondIsClimbing extends PropertyCondition<LivingEntity> { static { diff --git a/src/main/java/ch/njol/skript/conditions/CondIsHandRaised.java b/src/main/java/ch/njol/skript/conditions/CondIsHandRaised.java index 897b7f6b351..d6030bf2b6a 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsHandRaised.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsHandRaised.java @@ -44,7 +44,7 @@ "\t\tdrop player's tool at player", "\t\tset player's tool to air" }) -@Since("INSERT VERSION") +@Since("2.8.0") @RequiredPlugins("Paper") public class CondIsHandRaised extends Condition { diff --git a/src/main/java/ch/njol/skript/conditions/CondIsJumping.java b/src/main/java/ch/njol/skript/conditions/CondIsJumping.java index 9a8c1124fd3..fb3738a05c6 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsJumping.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsJumping.java @@ -40,7 +40,7 @@ "\t\twait 5 ticks", "\tpush event-entity upwards" }) -@Since("INSERT VERSION") +@Since("2.8.0") @RequiredPlugins("Paper 1.15+") public class CondIsJumping extends PropertyCondition<LivingEntity> { diff --git a/src/main/java/ch/njol/skript/conditions/CondIsLeftHanded.java b/src/main/java/ch/njol/skript/conditions/CondIsLeftHanded.java index ab2f4b2e26b..7101ad18b92 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsLeftHanded.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsLeftHanded.java @@ -43,7 +43,7 @@ "\tif victim is left handed:", "\t\tcancel event" }) -@Since("INSERT VERSION") +@Since("2.8.0") @RequiredPlugins("Paper 1.17.1+ (entities)") public class CondIsLeftHanded extends PropertyCondition<LivingEntity> { diff --git a/src/main/java/ch/njol/skript/conditions/CondIsSheared.java b/src/main/java/ch/njol/skript/conditions/CondIsSheared.java index f667f292899..a000891be95 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsSheared.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsSheared.java @@ -38,7 +38,7 @@ "if targeted entity of player is sheared:", "\tsend \"This entity has nothing left to shear!\" to player" }) -@Since("INSERT VERSION") +@Since("2.8.0") @RequiredPlugins("MC 1.13+ (cows, sheep & snowmen), Paper 1.19.4+ (all shearable entities)") public class CondIsSheared extends PropertyCondition<LivingEntity> { diff --git a/src/main/java/ch/njol/skript/effects/EffApplyBoneMeal.java b/src/main/java/ch/njol/skript/effects/EffApplyBoneMeal.java index 080f1cd3982..2b6c4e6ff63 100644 --- a/src/main/java/ch/njol/skript/effects/EffApplyBoneMeal.java +++ b/src/main/java/ch/njol/skript/effects/EffApplyBoneMeal.java @@ -38,7 +38,7 @@ @Description("Applies bone meal to a crop, sapling, or composter") @Examples("apply 3 bone meal to event-block") @RequiredPlugins("MC 1.16.2+") -@Since("INSERT VERSION") +@Since("2.8.0") public class EffApplyBoneMeal extends Effect { static { diff --git a/src/main/java/ch/njol/skript/effects/EffCancelItemUse.java b/src/main/java/ch/njol/skript/effects/EffCancelItemUse.java index bda21e00683..20251dbb82b 100644 --- a/src/main/java/ch/njol/skript/effects/EffCancelItemUse.java +++ b/src/main/java/ch/njol/skript/effects/EffCancelItemUse.java @@ -42,7 +42,7 @@ "\tif the victim's active tool is a bow:", "\t\tinterrupt the usage of the player's active item" }) -@Since("INSERT VERSION") +@Since("2.8.0") @RequiredPlugins("Paper 1.16+") public class EffCancelItemUse extends Effect { diff --git a/src/main/java/ch/njol/skript/effects/EffCommand.java b/src/main/java/ch/njol/skript/effects/EffCommand.java index 8dca06d4638..5977dbcc61a 100644 --- a/src/main/java/ch/njol/skript/effects/EffCommand.java +++ b/src/main/java/ch/njol/skript/effects/EffCommand.java @@ -48,7 +48,7 @@ "execute console command \"/say Hello everyone!\"", "execute player bungeecord command \"/alert &6Testing Announcement!\"" }) -@Since("1.0, INSERT VERSION (bungeecord command)") +@Since("1.0, 2.8.0 (bungeecord command)") public class EffCommand extends Effect { public static final String MESSAGE_CHANNEL = "Message"; diff --git a/src/main/java/ch/njol/skript/effects/EffContinue.java b/src/main/java/ch/njol/skript/effects/EffContinue.java index 4b4c4b3682c..e882b202986 100644 --- a/src/main/java/ch/njol/skript/effects/EffContinue.java +++ b/src/main/java/ch/njol/skript/effects/EffContinue.java @@ -55,7 +55,7 @@ "\t\tcontinue # only print when counter is 1, 2, 3, 5 or 10", "\tbroadcast \"Game starting in %{_counter}% second(s)\"", }) -@Since("2.2-dev37, 2.7 (while loops), INSERT VERSION (outer loops)") +@Since("2.2-dev37, 2.7 (while loops), 2.8.0 (outer loops)") public class EffContinue extends Effect { static { diff --git a/src/main/java/ch/njol/skript/effects/EffCopy.java b/src/main/java/ch/njol/skript/effects/EffCopy.java index 896fdafa0a3..e949f3bd832 100644 --- a/src/main/java/ch/njol/skript/effects/EffCopy.java +++ b/src/main/java/ch/njol/skript/effects/EffCopy.java @@ -51,7 +51,7 @@ "broadcast {_copy::bar} # 1", "broadcast {_copy::sublist::foobar} # \"hey!\"" }) -@Since("INSERT VERSION") +@Since("2.8.0") @Keywords({"clone", "variable", "list"}) public class EffCopy extends Effect { diff --git a/src/main/java/ch/njol/skript/effects/EffGlowingText.java b/src/main/java/ch/njol/skript/effects/EffGlowingText.java index 576a2d58348..7f5b3161396 100644 --- a/src/main/java/ch/njol/skript/effects/EffGlowingText.java +++ b/src/main/java/ch/njol/skript/effects/EffGlowingText.java @@ -40,7 +40,7 @@ @Name("Make Sign Glow") @Description("Makes a sign (either a block or item) have glowing text or normal text") @Examples("make target block of player have glowing text") -@Since("INSERT VERSION") +@Since("2.8.0") public class EffGlowingText extends Effect { static { diff --git a/src/main/java/ch/njol/skript/effects/EffHandedness.java b/src/main/java/ch/njol/skript/effects/EffHandedness.java index ddb8a8d0787..89d5469e1a1 100644 --- a/src/main/java/ch/njol/skript/effects/EffHandedness.java +++ b/src/main/java/ch/njol/skript/effects/EffHandedness.java @@ -41,7 +41,7 @@ "", "make all zombies in radius 10 of player right handed" }) -@Since("INSERT VERSION") +@Since("2.8.0") @RequiredPlugins("Paper 1.17.1+") public class EffHandedness extends Effect { diff --git a/src/main/java/ch/njol/skript/effects/EffReturn.java b/src/main/java/ch/njol/skript/effects/EffReturn.java index 13b563fefee..e9184ac69d5 100644 --- a/src/main/java/ch/njol/skript/effects/EffReturn.java +++ b/src/main/java/ch/njol/skript/effects/EffReturn.java @@ -48,7 +48,7 @@ "function divide(i: number) returns number:", "\treturn {_i} / 2" }) -@Since("2.2, INSERT VERSION (returns aliases)") +@Since("2.2, 2.8.0 (returns aliases)") public class EffReturn extends Effect { static { diff --git a/src/main/java/ch/njol/skript/effects/EffShear.java b/src/main/java/ch/njol/skript/effects/EffShear.java index 5bb6bf3d991..5cc8f5004c9 100644 --- a/src/main/java/ch/njol/skript/effects/EffShear.java +++ b/src/main/java/ch/njol/skript/effects/EffShear.java @@ -50,7 +50,7 @@ "\tchance of 10%", "\tforce shear the clicked sheep" }) -@Since("2.0 (cows, sheep & snowmen), INSERT VERSION (all shearable entities)") +@Since("2.0 (cows, sheep & snowmen), 2.8.0 (all shearable entities)") @RequiredPlugins("Paper 1.19.4+ (all shearable entities)") public class EffShear extends Effect { diff --git a/src/main/java/ch/njol/skript/effects/EffToggleCanPickUpItems.java b/src/main/java/ch/njol/skript/effects/EffToggleCanPickUpItems.java index 6c3b3926988..9629c0f5b78 100644 --- a/src/main/java/ch/njol/skript/effects/EffToggleCanPickUpItems.java +++ b/src/main/java/ch/njol/skript/effects/EffToggleCanPickUpItems.java @@ -41,7 +41,7 @@ "\tif player can't pick up items:", "\t\tallow player to pick up items" }) -@Since("INSERT VERSION") +@Since("2.8.0") public class EffToggleCanPickUpItems extends Effect { static { diff --git a/src/main/java/ch/njol/skript/effects/EffWorldLoad.java b/src/main/java/ch/njol/skript/effects/EffWorldLoad.java index 1417789789f..697c417d58e 100644 --- a/src/main/java/ch/njol/skript/effects/EffWorldLoad.java +++ b/src/main/java/ch/njol/skript/effects/EffWorldLoad.java @@ -47,7 +47,7 @@ "unload \"world_the_end\" without saving", "unload all worlds" }) -@Since("INSERT VERSION") +@Since("2.8.0") public class EffWorldLoad extends Effect { static { diff --git a/src/main/java/ch/njol/skript/effects/EffWorldSave.java b/src/main/java/ch/njol/skript/effects/EffWorldSave.java index 2281ba44dcc..8cd30672f55 100644 --- a/src/main/java/ch/njol/skript/effects/EffWorldSave.java +++ b/src/main/java/ch/njol/skript/effects/EffWorldSave.java @@ -40,7 +40,7 @@ "save \"world_nether\"", "save all worlds" }) -@Since("INSERT VERSION") +@Since("2.8.0") public class EffWorldSave extends Effect { static { diff --git a/src/main/java/ch/njol/skript/events/EvtEntityBlockChange.java b/src/main/java/ch/njol/skript/events/EvtEntityBlockChange.java index 358a0a5086a..9854057db03 100644 --- a/src/main/java/ch/njol/skript/events/EvtEntityBlockChange.java +++ b/src/main/java/ch/njol/skript/events/EvtEntityBlockChange.java @@ -54,7 +54,7 @@ public class EvtEntityBlockChange extends SkriptEvent { "\tevent-entity is a falling dirt", "\tcancel event" ) - .since("<i>unknown</i>, 2.5.2 (falling block), INSERT VERSION (any entity support)"); + .since("<i>unknown</i>, 2.5.2 (falling block), 2.8.0 (any entity support)"); } private enum ChangeEvent { diff --git a/src/main/java/ch/njol/skript/events/EvtEntityTransform.java b/src/main/java/ch/njol/skript/events/EvtEntityTransform.java index 3d3a8a26325..deeb6c69164 100644 --- a/src/main/java/ch/njol/skript/events/EvtEntityTransform.java +++ b/src/main/java/ch/njol/skript/events/EvtEntityTransform.java @@ -40,7 +40,7 @@ public class EvtEntityTransform extends SkriptEvent { "a mooshroom that when sheared, spawns a new cow.") .examples("on a zombie transforming due to curing:", "on mooshroom transforming:", "on zombie, skeleton or slime transform:") .keywords("entity transform") - .since("INSERT VERSION"); + .since("2.8.0"); } @Nullable diff --git a/src/main/java/ch/njol/skript/events/EvtGrow.java b/src/main/java/ch/njol/skript/events/EvtGrow.java index 1d10c5d09ce..98f5f281728 100644 --- a/src/main/java/ch/njol/skript/events/EvtGrow.java +++ b/src/main/java/ch/njol/skript/events/EvtGrow.java @@ -70,7 +70,7 @@ public class EvtGrow extends SkriptEvent { "on grow of wheat, carrots, or potatoes:", "on grow into tree, giant mushroom, cactus:", "on grow from wheat[age=0] to wheat[age=1] or wheat[age=2]:") - .since("1.0, 2.2-dev20 (plants), INSERT VERSION (from, into, blockdata)"); + .since("1.0, 2.2-dev20 (plants), 2.8.0 (from, into, blockdata)"); } @Nullable diff --git a/src/main/java/ch/njol/skript/events/EvtItem.java b/src/main/java/ch/njol/skript/events/EvtItem.java index f00ccc58d36..98a23e29482 100644 --- a/src/main/java/ch/njol/skript/events/EvtItem.java +++ b/src/main/java/ch/njol/skript/events/EvtItem.java @@ -140,7 +140,7 @@ public class EvtItem extends SkriptEvent { "on inventory item move:", "\tbroadcast \"%holder of past event-inventory% is transporting %event-item% to %holder of event-inventory%!\"" ) - .since("INSERT VERSION"); + .since("2.8.0"); if (HAS_PLAYER_STONECUTTER_RECIPE_SELECT_EVENT) { Skript.registerEvent("Stonecutter Recipe Select", EvtItem.class, PlayerStonecutterRecipeSelectEvent.class, "stonecutting [[of] %-itemtypes%]") .description("Called when a player selects a recipe in a stonecutter.") @@ -151,7 +151,7 @@ public class EvtItem extends SkriptEvent { "on stonecutting:", "\tbroadcast \"%player% is using stonecutter to craft %event-item%!\"" ) - .since("INSERT VERSION") + .since("2.8.0") .requiredPlugins("Paper 1.16+"); } } diff --git a/src/main/java/ch/njol/skript/events/EvtMove.java b/src/main/java/ch/njol/skript/events/EvtMove.java index 4283a606d89..cd32790c471 100644 --- a/src/main/java/ch/njol/skript/events/EvtMove.java +++ b/src/main/java/ch/njol/skript/events/EvtMove.java @@ -59,7 +59,7 @@ public class EvtMove extends SkriptEvent { "on player turning around:", "send action bar \"You are currently turning your head around!\" to player") .requiredPlugins("Paper 1.16.5+ (entity move)") - .since("2.6, INSERT VERSION (turn around)"); + .since("2.6, 2.8.0 (turn around)"); } private EntityData<?> entityData; diff --git a/src/main/java/ch/njol/skript/events/EvtPlayerCommandSend.java b/src/main/java/ch/njol/skript/events/EvtPlayerCommandSend.java index 9ef380ada7a..27dd4771ac3 100644 --- a/src/main/java/ch/njol/skript/events/EvtPlayerCommandSend.java +++ b/src/main/java/ch/njol/skript/events/EvtPlayerCommandSend.java @@ -46,7 +46,7 @@ public class EvtPlayerCommandSend extends SkriptEvent { "\tset command list to command list where [input does not contain \":\"]", "\tremove \"help\" from command list" ) - .since("INSERT VERSION"); + .since("2.8.0"); } private final Collection<String> originalCommands = new ArrayList<>(); diff --git a/src/main/java/ch/njol/skript/events/EvtWorld.java b/src/main/java/ch/njol/skript/events/EvtWorld.java index 657d3f1a002..d39dfb71e1c 100644 --- a/src/main/java/ch/njol/skript/events/EvtWorld.java +++ b/src/main/java/ch/njol/skript/events/EvtWorld.java @@ -41,7 +41,7 @@ public class EvtWorld extends SkriptEvent { .examples( "on world save of \"world\":", "\tbroadcast \"The world %event-world% has been saved\"") - .since("1.0, INSERT VERSION (defining worlds)"); + .since("1.0, 2.8.0 (defining worlds)"); // World Init Event Skript.registerEvent("World Init", EvtWorld.class, WorldInitEvent.class, "world init[ialization] [of %-worlds%]") @@ -49,7 +49,7 @@ public class EvtWorld extends SkriptEvent { "any scripts are loaded, this event is only called for newly created worlds.", "World management plugins might change the behaviour of this event though.") .examples("on world init of \"world_the_end\":") - .since("1.0, INSERT VERSION (defining worlds)"); + .since("1.0, 2.8.0 (defining worlds)"); // World Unload Event Skript.registerEvent("World Unload", EvtWorld.class, WorldUnloadEvent.class, "world unload[ing] [of %-worlds%]") @@ -57,7 +57,7 @@ public class EvtWorld extends SkriptEvent { .examples( "on world unload:", "\tbroadcast \"the %event-world% has been unloaded!\"") - .since("1.0, INSERT VERSION (defining worlds)"); + .since("1.0, 2.8.0 (defining worlds)"); // World Load Event Skript.registerEvent("World Load", EvtWorld.class, WorldLoadEvent.class, "world load[ing] [of %-worlds%]") @@ -65,7 +65,7 @@ public class EvtWorld extends SkriptEvent { .examples( "on world load of \"world_nether\":", "\tbroadcast \"The world %event-world% has been loaded!\"") - .since("1.0, INSERT VERSION (defining worlds)"); + .since("1.0, 2.8.0 (defining worlds)"); } private Literal<World> worlds; diff --git a/src/main/java/ch/njol/skript/events/SimpleEvents.java b/src/main/java/ch/njol/skript/events/SimpleEvents.java index 4d19f3186e2..ae20f51dc0d 100644 --- a/src/main/java/ch/njol/skript/events/SimpleEvents.java +++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java @@ -643,7 +643,7 @@ public class SimpleEvents { .examples( "on player stop using item:", "\tbroadcast \"%player% used %event-item% for %event-timespan%.\"") - .since("INSERT VERSION"); + .since("2.8.0"); } if (Skript.classExists("com.destroystokyo.paper.event.player.PlayerReadyArrowEvent")) { @@ -658,7 +658,7 @@ public class SimpleEvents { "\tif selected arrow is not a spectral arrow:", "\t\tcancel event" ) - .since("INSERT VERSION"); + .since("2.8.0"); } if (Skript.classExists("io.papermc.paper.event.player.PlayerInventorySlotChangeEvent")) { @@ -726,7 +726,7 @@ public class SimpleEvents { "\tcancel the event", "\tteleport event-projectile to block 5 above event-projectile" ) - .since("INSERT VERSION") + .since("2.8.0") .requiredPlugins("Minecraft 1.14+ (event-projectile)"); Skript.registerEvent("Inventory Drag", SimpleEvent.class, InventoryDragEvent.class, "inventory drag[ging]") diff --git a/src/main/java/ch/njol/skript/expressions/ExprActiveItem.java b/src/main/java/ch/njol/skript/expressions/ExprActiveItem.java index c568e5f9b95..c3458c7f9b1 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprActiveItem.java +++ b/src/main/java/ch/njol/skript/expressions/ExprActiveItem.java @@ -41,7 +41,7 @@ "\tif victim's active tool is a bow:", "\t\tinterrupt player's active item use" }) -@Since("INSERT VERSION") +@Since("2.8.0") @RequiredPlugins("Paper") public class ExprActiveItem extends SimplePropertyExpression<LivingEntity, ItemStack> { diff --git a/src/main/java/ch/njol/skript/expressions/ExprAnvilRepairCost.java b/src/main/java/ch/njol/skript/expressions/ExprAnvilRepairCost.java index dd47b9af14f..0d24607a445 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprAnvilRepairCost.java +++ b/src/main/java/ch/njol/skript/expressions/ExprAnvilRepairCost.java @@ -51,7 +51,7 @@ "\tplayer have permission \"anvil.repair.max.bypass\"", "\tset max repair cost of event-inventory to 99999" }) -@Since("INSERT VERSION") +@Since("2.8.0") public class ExprAnvilRepairCost extends SimplePropertyExpression<Inventory, Integer> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprArmorSlot.java b/src/main/java/ch/njol/skript/expressions/ExprArmorSlot.java index 09f3677eb14..1d881d78fc8 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprArmorSlot.java +++ b/src/main/java/ch/njol/skript/expressions/ExprArmorSlot.java @@ -47,7 +47,7 @@ "helmet of player is neither a helmet nor air # player is wearing a block, e.g. from another plugin" }) @Keywords("armor") -@Since("1.0, INSERT VERSION (Armour)") +@Since("1.0, 2.8.0 (Armour)") public class ExprArmorSlot extends PropertyExpression<LivingEntity, Slot> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprAttachedBlock.java b/src/main/java/ch/njol/skript/expressions/ExprAttachedBlock.java index 3745940fe82..19ae309cca9 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprAttachedBlock.java +++ b/src/main/java/ch/njol/skript/expressions/ExprAttachedBlock.java @@ -33,7 +33,7 @@ @Name("Arrow Attached Block") @Description("Returns the attached block of an arrow.") @Examples("set hit block of last shot arrow to diamond block") -@Since("INSERT VERSION") +@Since("2.8.0") public class ExprAttachedBlock extends SimplePropertyExpression<Projectile, Block> { private static final boolean HAS_ABSTRACT_ARROW = Skript.classExists("org.bukkit.entity.AbstractArrow"); diff --git a/src/main/java/ch/njol/skript/expressions/ExprCharacters.java b/src/main/java/ch/njol/skript/expressions/ExprCharacters.java index 4c97fa79c39..8d0231bd58f 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprCharacters.java +++ b/src/main/java/ch/njol/skript/expressions/ExprCharacters.java @@ -50,7 +50,7 @@ "# 0123456789ABC... ...uvwxyz", "send alphanumeric characters between \"0\" and \"z\"" }) -@Since("INSERT VERSION") +@Since("2.8.0") public class ExprCharacters extends SimpleExpression<String> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprChestInventory.java b/src/main/java/ch/njol/skript/expressions/ExprChestInventory.java index 671a5b86f7d..0ba4548c184 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprChestInventory.java +++ b/src/main/java/ch/njol/skript/expressions/ExprChestInventory.java @@ -55,7 +55,7 @@ "open chest inventory named \"<##00ff00>hex coloured title!\" with 6 rows to player", }) @RequiredPlugins("Paper 1.16+ (chat format)") -@Since("2.2-dev34, INSERT VERSION (chat format)") +@Since("2.2-dev34, 2.8.0 (chat format)") public class ExprChestInventory extends SimpleExpression<Inventory> { @Nullable diff --git a/src/main/java/ch/njol/skript/expressions/ExprChunk.java b/src/main/java/ch/njol/skript/expressions/ExprChunk.java index d047a73c7ad..04950b20e36 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprChunk.java +++ b/src/main/java/ch/njol/skript/expressions/ExprChunk.java @@ -44,7 +44,7 @@ "add the chunk at the player to {protected chunks::*}", "set {_chunks::*} to the loaded chunks of the player's world" }) -@Since("2.0, INSERT VERSION (loaded chunks)") +@Since("2.0, 2.8.0 (loaded chunks)") public class ExprChunk extends SimpleExpression<Chunk> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprDamage.java b/src/main/java/ch/njol/skript/expressions/ExprDamage.java index 20305cb05be..4ac94c23b06 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprDamage.java +++ b/src/main/java/ch/njol/skript/expressions/ExprDamage.java @@ -53,7 +53,7 @@ "on damage:", "\tincrease the damage by 2" }) -@Since("1.3.5, INSERT VERSION (item damage event)") +@Since("1.3.5, 2.8.0 (item damage event)") @Events({"Damage", "Vehicle Damage", "Item Damage"}) public class ExprDamage extends SimpleExpression<Number> { diff --git a/src/main/java/ch/njol/skript/expressions/ExprElement.java b/src/main/java/ch/njol/skript/expressions/ExprElement.java index 3e5efcdef4e..15b6d96afb5 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprElement.java +++ b/src/main/java/ch/njol/skript/expressions/ExprElement.java @@ -47,7 +47,7 @@ "See also: <a href='#ExprRandom'>random expression</a>" }) @Examples("broadcast the first 3 elements of {top players::*}") -@Since("2.0, 2.7 (relative to last element), INSERT VERSION (range of elements)") +@Since("2.0, 2.7 (relative to last element), 2.8.0 (range of elements)") public class ExprElement<T> extends SimpleExpression<T> { private static final Patterns<ElementType[]> PATTERNS = new Patterns<>(new Object[][]{ diff --git a/src/main/java/ch/njol/skript/expressions/ExprEntityItemUseTime.java b/src/main/java/ch/njol/skript/expressions/ExprEntityItemUseTime.java index 393b9475bca..7b7dba018f7 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprEntityItemUseTime.java +++ b/src/main/java/ch/njol/skript/expressions/ExprEntityItemUseTime.java @@ -43,7 +43,7 @@ "\twait 1 second", "\tbroadcast player's item use time" }) -@Since("INSERT VERSION") +@Since("2.8.0") @RequiredPlugins("Paper") public class ExprEntityItemUseTime extends SimplePropertyExpression<LivingEntity, Timespan> { diff --git a/src/main/java/ch/njol/skript/expressions/ExprEvtInitiator.java b/src/main/java/ch/njol/skript/expressions/ExprEvtInitiator.java index 0ab07a427ba..fce9d5c1e2d 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprEvtInitiator.java +++ b/src/main/java/ch/njol/skript/expressions/ExprEvtInitiator.java @@ -40,7 +40,7 @@ "\tbroadcast \"Item transport requested at %location at holder of event-initiator-inventory%...\"" }) @Events("Inventory Item Move") -@Since("INSERT VERSION") +@Since("2.8.0") public class ExprEvtInitiator extends EventValueExpression<Inventory> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprFurnaceSlot.java b/src/main/java/ch/njol/skript/expressions/ExprFurnaceSlot.java index 980f4f5f733..b3f13efefa7 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprFurnaceSlot.java +++ b/src/main/java/ch/njol/skript/expressions/ExprFurnaceSlot.java @@ -66,7 +66,7 @@ "clear the result slot of the block" }) @Events({"smelt", "fuel burn"}) -@Since("1.0, INSERT VERSION (syntax rework)") +@Since("1.0, 2.8.0 (syntax rework)") public class ExprFurnaceSlot extends SimpleExpression<Slot> { private static final int ORE = 0, FUEL = 1, RESULT = 2; diff --git a/src/main/java/ch/njol/skript/expressions/ExprInventoryCloseReason.java b/src/main/java/ch/njol/skript/expressions/ExprInventoryCloseReason.java index 61443bc9293..c0f61a4b70b 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprInventoryCloseReason.java +++ b/src/main/java/ch/njol/skript/expressions/ExprInventoryCloseReason.java @@ -43,7 +43,7 @@ }) @Events("Inventory Close") @RequiredPlugins("Paper") -@Since("INSERT VERSION") +@Since("2.8.0") public class ExprInventoryCloseReason extends EventValueExpression<InventoryCloseEvent.Reason> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprItemCooldown.java b/src/main/java/ch/njol/skript/expressions/ExprItemCooldown.java index 72bcd192d56..af209c8a3b4 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprItemCooldown.java +++ b/src/main/java/ch/njol/skript/expressions/ExprItemCooldown.java @@ -48,7 +48,7 @@ "\tset item cooldown of stone and grass for all players to 20 seconds", "\treset item cooldown of cobblestone and dirt for all players" }) -@Since("INSERT VERSION") +@Since("2.8.0") public class ExprItemCooldown extends SimpleExpression<Timespan> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprItemsIn.java b/src/main/java/ch/njol/skript/expressions/ExprItemsIn.java index 4ba8ebbb6fd..4c5b1c1bf5c 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprItemsIn.java +++ b/src/main/java/ch/njol/skript/expressions/ExprItemsIn.java @@ -54,7 +54,7 @@ "\tremove loop-item from the player", "set {inventory::%uuid of player%::*} to items in the player's inventory" }) -@Since("2.0, INSERT VERSION (specific types of items)") +@Since("2.0, 2.8.0 (specific types of items)") public class ExprItemsIn extends SimpleExpression<Slot> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprLoopIteration.java b/src/main/java/ch/njol/skript/expressions/ExprLoopIteration.java index b95471a2cba..37640b26db7 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprLoopIteration.java +++ b/src/main/java/ch/njol/skript/expressions/ExprLoopIteration.java @@ -47,7 +47,7 @@ "\tif loop-iteration <= 10:", "\t\tbroadcast \"##%loop-iteration% %loop-index% has $%loop-value%\"", }) -@Since("INSERT VERSION") +@Since("2.8.0") public class ExprLoopIteration extends SimpleExpression<Long> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprLoopValue.java b/src/main/java/ch/njol/skript/expressions/ExprLoopValue.java index 5767d6f3352..343cc9461c3 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprLoopValue.java +++ b/src/main/java/ch/njol/skript/expressions/ExprLoopValue.java @@ -64,7 +64,7 @@ "\tloop-iteration <= 10", "\tsend \"##%loop-iteration% %loop-index% has $%loop-value%\"", }) -@Since("1.0, INSERT VERSION (loop-counter)") +@Since("1.0, 2.8.0 (loop-counter)") public class ExprLoopValue extends SimpleExpression<Object> { static { Skript.registerExpression(ExprLoopValue.class, Object.class, ExpressionType.SIMPLE, "[the] loop-<.+>"); diff --git a/src/main/java/ch/njol/skript/expressions/ExprMaxItemUseTime.java b/src/main/java/ch/njol/skript/expressions/ExprMaxItemUseTime.java index 2521fa65760..b24cce9bddc 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprMaxItemUseTime.java +++ b/src/main/java/ch/njol/skript/expressions/ExprMaxItemUseTime.java @@ -39,7 +39,7 @@ "on right click:", "\tbroadcast max usage duration of player's tool" }) -@Since("INSERT VERSION") +@Since("2.8.0") @RequiredPlugins("Paper") public class ExprMaxItemUseTime extends SimplePropertyExpression<ItemStack, Timespan> { diff --git a/src/main/java/ch/njol/skript/expressions/ExprMemory.java b/src/main/java/ch/njol/skript/expressions/ExprMemory.java index c9dd2ba1151..e82fc8fe1ba 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprMemory.java +++ b/src/main/java/ch/njol/skript/expressions/ExprMemory.java @@ -41,7 +41,7 @@ "\tsend action bar \"Memory left: %free memory%/%max memory%MB\" to player", "\twait 5 ticks" }) -@Since("INSERT VERSION") +@Since("2.8.0") public class ExprMemory extends SimpleExpression<Double> { private static final double BYTES_IN_MEGABYTES = 1E-6; diff --git a/src/main/java/ch/njol/skript/expressions/ExprPercent.java b/src/main/java/ch/njol/skript/expressions/ExprPercent.java index be5778fa6df..ce7b81f87a8 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprPercent.java +++ b/src/main/java/ch/njol/skript/expressions/ExprPercent.java @@ -40,7 +40,7 @@ "set {_result::*} to 10% of {_numbers::*}", "set experience to 50% of player's total experience" }) -@Since("INSERT VERSION") +@Since("2.8.0") public class ExprPercent extends SimpleExpression<Number> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprPortalCooldown.java b/src/main/java/ch/njol/skript/expressions/ExprPortalCooldown.java index e4b98afb7ed..18c0a4999c6 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprPortalCooldown.java +++ b/src/main/java/ch/njol/skript/expressions/ExprPortalCooldown.java @@ -43,7 +43,7 @@ "\twait 1 tick", "\tset portal cooldown of event-entity to 5 seconds" }) -@Since("INSERT VERSION") +@Since("2.8.0") public class ExprPortalCooldown extends SimplePropertyExpression<Entity, Timespan> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprQuitReason.java b/src/main/java/ch/njol/skript/expressions/ExprQuitReason.java index f8b02e4e253..7f702758aa0 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprQuitReason.java +++ b/src/main/java/ch/njol/skript/expressions/ExprQuitReason.java @@ -38,7 +38,7 @@ "\tclear {server::player::%uuid of player%::*}" }) @RequiredPlugins("Paper 1.16.5+") -@Since("INSERT VERSION") +@Since("2.8.0") public class ExprQuitReason extends EventValueExpression<QuitReason> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprRandomCharacter.java b/src/main/java/ch/njol/skript/expressions/ExprRandomCharacter.java index 6f82051d2ea..32afa678c42 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprRandomCharacter.java +++ b/src/main/java/ch/njol/skript/expressions/ExprRandomCharacter.java @@ -45,7 +45,7 @@ "set {_captcha} to join (5 random characters between \"a\" and \"z\") with \"\"", "send 3 random alphanumeric characters between \"0\" and \"z\"" }) -@Since("INSERT VERSION") +@Since("2.8.0") public class ExprRandomCharacter extends SimpleExpression<String> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprReadiedArrow.java b/src/main/java/ch/njol/skript/expressions/ExprReadiedArrow.java index 00301cbb9fe..1e12805904b 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprReadiedArrow.java +++ b/src/main/java/ch/njol/skript/expressions/ExprReadiedArrow.java @@ -42,7 +42,7 @@ "\tif selected arrow is not a spectral arrow:", "\t\tcancel event" }) -@Since("INSERT VERSION") +@Since("2.8.0") @Events("ready arrow") public class ExprReadiedArrow extends SimpleExpression<ItemStack> { diff --git a/src/main/java/ch/njol/skript/expressions/ExprRepeat.java b/src/main/java/ch/njol/skript/expressions/ExprRepeat.java index 831c448ead0..83523072550 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprRepeat.java +++ b/src/main/java/ch/njol/skript/expressions/ExprRepeat.java @@ -40,7 +40,7 @@ "if \"aa\" repeated 2 times is \"aaaa\":", "\tbroadcast \"Ahhhh\" repeated 100 times" }) -@Since("INSERT VERSION") +@Since("2.8.0") public class ExprRepeat extends SimpleExpression<String> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprSentCommands.java b/src/main/java/ch/njol/skript/expressions/ExprSentCommands.java index 5c4f17ab16e..3ec4ff9a68a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprSentCommands.java +++ b/src/main/java/ch/njol/skript/expressions/ExprSentCommands.java @@ -52,7 +52,7 @@ "\tset command list to command list where [input does not contain \":\"]", "\tremove \"help\" from command list" }) -@Since("INSERT VERSION") +@Since("2.8.0") @Events("send command list") public class ExprSentCommands extends SimpleExpression<String> { diff --git a/src/main/java/ch/njol/skript/expressions/ExprSlotIndex.java b/src/main/java/ch/njol/skript/expressions/ExprSlotIndex.java index ff37907b9c9..db19f743308 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprSlotIndex.java +++ b/src/main/java/ch/njol/skript/expressions/ExprSlotIndex.java @@ -49,7 +49,7 @@ "\tif raw index of event-slot > 27: # outside custom inventory", "\t\tcancel event", }) -@Since("2.2-dev35, INSERT VERSION (raw index)") +@Since("2.2-dev35, 2.8.0 (raw index)") public class ExprSlotIndex extends SimplePropertyExpression<Slot, Long> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprTarget.java b/src/main/java/ch/njol/skript/expressions/ExprTarget.java index 8fba8caee04..dc4146bbd8e 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTarget.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTarget.java @@ -67,7 +67,7 @@ "delete targeted entity of player # for players it will delete the target", "delete target of last spawned zombie # for entities it will make them target-less" }) -@Since("1.4.2, 2.7 (Reset), INSERT VERSION (ignore blocks)") +@Since("1.4.2, 2.7 (Reset), 2.8.0 (ignore blocks)") public class ExprTarget extends PropertyExpression<LivingEntity, Entity> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprTransformReason.java b/src/main/java/ch/njol/skript/expressions/ExprTransformReason.java index 09d36074b93..a34fe35d343 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTransformReason.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTransformReason.java @@ -36,7 +36,7 @@ "on entity transform:", "\ttransform reason is infection, drowned or frozen" }) -@Since("INSERT VERSION") +@Since("2.8.0") public class ExprTransformReason extends EventValueExpression<TransformReason> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java b/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java index 93962bb3fee..3cb57f8d10c 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorArithmetic.java @@ -47,7 +47,7 @@ "set {_v} to {_v} ** {_v}", "set {_v} to {_v} // {_v}" }) -@Since("2.2-dev28, INSERT VERSION (deprecation)") +@Since("2.2-dev28, 2.8.0 (deprecation)") @Deprecated public class ExprVectorArithmetic extends SimpleExpression<Vector> { diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorFromDirection.java b/src/main/java/ch/njol/skript/expressions/ExprVectorFromDirection.java index 38367f92720..3f4d9f08510 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorFromDirection.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorFromDirection.java @@ -45,7 +45,7 @@ "set {_v} to vector from facing of player", "set {_v::*} to vectors from north, south, east, and west" }) -@Since("INSERT VERSION") +@Since("2.8.0") public class ExprVectorFromDirection extends SimpleExpression<Vector> { static { diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorProjection.java b/src/main/java/ch/njol/skript/expressions/ExprVectorProjection.java index 1cd10d2ddd5..ec3707b142f 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorProjection.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorProjection.java @@ -35,7 +35,7 @@ @Name("Vectors - Vector Projection") @Description("An expression to get the vector projection of two vectors.") @Examples("set {_projection} to vector projection of vector(1, 2, 3) onto vector(4, 4, 4)") -@Since("INSERT VERSION") +@Since("2.8.0") public class ExprVectorProjection extends SimpleExpression<Vector> { static { diff --git a/src/test/skript/tests/syntaxes/expressions/ExprArithmetic.sk b/src/test/skript/tests/syntaxes/expressions/ExprArithmetic.sk index cb51efbfef9..f035aecd41e 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprArithmetic.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprArithmetic.sk @@ -233,13 +233,13 @@ test "timespan arithmetic": test "date arithmetic": set {_d1} to now - set {_d2} to 1 day ago + set {_d2} to 1 day before {_d1} assert ({_d1} + {_d2}) is not set with "" - assert ({_d1} + 1 day) is (1 day from {_d1}) with "now + 1 day is not 1 day from now" - assert ({_d1} - 1 day) is (1 day ago) with "now - 1 day is not 1 day ago" - assert ({_d1} + 1 week) is (1 week from {_d1}) with "now + 1 week is not 1 week from now" - assert ({_d1} - 1 week) is (1 week ago) with "now - 1 week is not 1 week ago" + assert ({_d1} + 1 day) is (1 day after {_d1}) with "now + 1 day is not 1 day from now" + assert ({_d1} - 1 day) is (1 day before {_d1}) with "now - 1 day is not 1 day ago" + assert ({_d1} + 1 week) is (1 week after {_d1}) with "now + 1 week is not 1 week from now" + assert ({_d1} - 1 week) is (1 week before {_d1}) with "now - 1 week is not 1 week ago" assert ({_d1} + 1) is not set with "" local function vector_equals(vec: vector, x: number, y: number, z: number) :: boolean: From f432a945494e0a2ac05ecd67ebe9f35d9ed3b0a6 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Fri, 5 Jan 2024 14:41:07 -0500 Subject: [PATCH 571/619] Fix Command Parsing (#6286) --- src/main/java/ch/njol/skript/lang/SkriptParser.java | 2 +- src/test/skript/tests/syntaxes/structures/StructCommand.sk | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/lang/SkriptParser.java b/src/main/java/ch/njol/skript/lang/SkriptParser.java index d8d7e01f1e9..f03c573e9ab 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptParser.java +++ b/src/main/java/ch/njol/skript/lang/SkriptParser.java @@ -115,7 +115,7 @@ public SkriptParser(SkriptParser other, String expr) { this(expr, other.flags, other.context); } - public static final String WILDCARD = "[^\"]*?(?:\"[^\"]*?\"[^\"])*?"; + public static final String WILDCARD = "[^\"]*?(?:\"[^\"]*?\"[^\"]*?)*?"; public static class ParseResult { public Expression<?>[] exprs; diff --git a/src/test/skript/tests/syntaxes/structures/StructCommand.sk b/src/test/skript/tests/syntaxes/structures/StructCommand.sk index 9df9e139247..3faa74c756b 100644 --- a/src/test/skript/tests/syntaxes/structures/StructCommand.sk +++ b/src/test/skript/tests/syntaxes/structures/StructCommand.sk @@ -19,3 +19,8 @@ command //somecommand [<text>]: set {_arg1} to arg-1 if {_arg1} is set: assert {_arg1} is "burrito is tasty" with "arg-1 is 'burrito is tasty' test failed (got '%{_arg1}%')" + +# see https://github.com/SkriptLang/Skript/pull/6286 +command /commandtest <string="player">: + trigger: + stop From 59caa86a80b668b8ac23b4211d05a4d884467874 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Fri, 5 Jan 2024 14:45:53 -0500 Subject: [PATCH 572/619] Fix Erroneous Player UUID Warnings (#6287) --- src/main/java/ch/njol/skript/lang/VariableString.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/lang/VariableString.java b/src/main/java/ch/njol/skript/lang/VariableString.java index f4614e24e9f..6f3c11e492a 100644 --- a/src/main/java/ch/njol/skript/lang/VariableString.java +++ b/src/main/java/ch/njol/skript/lang/VariableString.java @@ -224,7 +224,11 @@ public static VariableString newInstance(String original, StringMode mode) { log.printErrors("Can't understand this expression: " + original.substring(exprStart + 1, exprEnd)); return null; } else { - if (!SkriptConfig.usePlayerUUIDsInVariableNames.value() && OfflinePlayer.class.isAssignableFrom(expr.getReturnType())) { + if ( + mode == StringMode.VARIABLE_NAME && + !SkriptConfig.usePlayerUUIDsInVariableNames.value() && + OfflinePlayer.class.isAssignableFrom(expr.getReturnType()) + ) { Skript.warning( "In the future, players in variable names will use the player's UUID instead of their name. " + "For information on how to make sure your scripts won't be impacted by this change, see https://github.com/SkriptLang/Skript/discussions/6270." From 1e466a01891f55593aa717fb00f3f6883eee13d0 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Fri, 5 Jan 2024 11:53:22 -0800 Subject: [PATCH 573/619] Fix event data pollution for event priority (#6292) --- .../ch/njol/skript/structures/StructEvent.java | 18 +++++++++++++++++- .../6288-event data priority pollution.sk | 5 +++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 src/test/skript/tests/regressions/6288-event data priority pollution.sk diff --git a/src/main/java/ch/njol/skript/structures/StructEvent.java b/src/main/java/ch/njol/skript/structures/StructEvent.java index ceb6fe53f9f..c09afc8b259 100644 --- a/src/main/java/ch/njol/skript/structures/StructEvent.java +++ b/src/main/java/ch/njol/skript/structures/StructEvent.java @@ -46,9 +46,18 @@ public class StructEvent extends Structure { @SuppressWarnings("ConstantConditions") public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult, EntryContainer entryContainer) { String expr = parseResult.regexes.get(0).group(); + + EventData data = getParser().getData(EventData.class); + // ensure there's no leftover data from previous parses + data.clear(); + if (!parseResult.tags.isEmpty()) - getParser().getData(EventData.class).priority = EventPriority.valueOf(parseResult.tags.get(0).toUpperCase(Locale.ENGLISH)); + data.priority = EventPriority.valueOf(parseResult.tags.get(0).toUpperCase(Locale.ENGLISH)); + event = SkriptEvent.parse(expr, entryContainer.getSource(), null); + + // cleanup after ourselves + data.clear(); return event != null; } @@ -112,6 +121,13 @@ public EventPriority getPriority() { return priority; } + /** + * Clears all event-specific data from this instance. + */ + public void clear() { + priority = null; + } + } } diff --git a/src/test/skript/tests/regressions/6288-event data priority pollution.sk b/src/test/skript/tests/regressions/6288-event data priority pollution.sk new file mode 100644 index 00000000000..3de238ca724 --- /dev/null +++ b/src/test/skript/tests/regressions/6288-event data priority pollution.sk @@ -0,0 +1,5 @@ +on join with priority lowest: + stop + +every 10 seconds: + stop From 5bbf8b9c3a9d19ed432e57f79001e85ee44c28e3 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Fri, 5 Jan 2024 11:59:13 -0800 Subject: [PATCH 574/619] Fixes IllegalArgumentException when trying to get shoes of entity (#6294) --- .../skript/expressions/ExprArmorSlot.java | 19 +++++++++---------- .../6237-equip slot shoes exception.sk | 10 ++++++++++ 2 files changed, 19 insertions(+), 10 deletions(-) create mode 100644 src/test/skript/tests/regressions/6237-equip slot shoes exception.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprArmorSlot.java b/src/main/java/ch/njol/skript/expressions/ExprArmorSlot.java index 1d881d78fc8..e5dd8b8eb09 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprArmorSlot.java +++ b/src/main/java/ch/njol/skript/expressions/ExprArmorSlot.java @@ -18,15 +18,6 @@ */ package ch.njol.skript.expressions; -import java.util.Arrays; -import java.util.Locale; -import java.util.stream.Stream; - -import org.bukkit.entity.LivingEntity; -import org.bukkit.event.Event; -import org.bukkit.inventory.EntityEquipment; -import org.eclipse.jdt.annotation.Nullable; - import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Keywords; @@ -39,6 +30,14 @@ import ch.njol.skript.util.slot.EquipmentSlot.EquipSlot; import ch.njol.skript.util.slot.Slot; import ch.njol.util.Kleenean; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Event; +import org.bukkit.inventory.EntityEquipment; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.Arrays; +import java.util.Locale; +import java.util.stream.Stream; @Name("Armour Slot") @Description("Equipment of living entities, i.e. the boots, leggings, chestplate or helmet.") @@ -51,7 +50,7 @@ public class ExprArmorSlot extends PropertyExpression<LivingEntity, Slot> { static { - register(ExprArmorSlot.class, Slot.class, "((:boots|:shoes|leggings:leg[ging]s|chestplate:chestplate[s]|helmet:helmet[s]) [(item|:slot)]|armour:armo[u]r[s])", "livingentities"); + register(ExprArmorSlot.class, Slot.class, "((boots:(boots|shoes)|leggings:leg[ging]s|chestplate:chestplate[s]|helmet:helmet[s]) [(item|:slot)]|armour:armo[u]r[s])", "livingentities"); } @Nullable diff --git a/src/test/skript/tests/regressions/6237-equip slot shoes exception.sk b/src/test/skript/tests/regressions/6237-equip slot shoes exception.sk new file mode 100644 index 00000000000..341addddc7e --- /dev/null +++ b/src/test/skript/tests/regressions/6237-equip slot shoes exception.sk @@ -0,0 +1,10 @@ +test "equip slot shoes exception": + # this one threw an exception since EquipSlot.SHOES doesn't exist + set {_} to shoes of {_entity} + # just in case, test the rest + set {_} to boots of {_entity} + set {_} to leggings of {_entity} + set {_} to chestplate of {_entity} + set {_} to helmet of {_entity} + set {_armor::*} to armour of {_entity} + From 891bfddeddcca5ebd04a0644e1243f1096158bf2 Mon Sep 17 00:00:00 2001 From: EquipableMC <66171067+EquipableMC@users.noreply.github.com> Date: Tue, 9 Jan 2024 12:30:59 -0500 Subject: [PATCH 575/619] Fixes outdated website link (#6298) --- src/main/resources/plugin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index df4d137c10f..344b536ab87 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -24,7 +24,7 @@ description: Customize Minecraft's mechanics with simple scripts written in plai authors: [Njol, Mirreski, 'SkriptLang Team', Contributors] -website: https://skriptlang.github.io/Skript +website: https://skriptlang.org main: ch.njol.skript.Skript From e418f6cbbeeb136ebec2254222418a2afc319610 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Tue, 9 Jan 2024 09:48:12 -0800 Subject: [PATCH 576/619] Force "to location" in ExprLocationFromVector (#6310) --- .../java/ch/njol/skript/expressions/ExprLocationFromVector.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprLocationFromVector.java b/src/main/java/ch/njol/skript/expressions/ExprLocationFromVector.java index f8dd3fc9c81..c4b86aeea3c 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprLocationFromVector.java +++ b/src/main/java/ch/njol/skript/expressions/ExprLocationFromVector.java @@ -49,7 +49,7 @@ public class ExprLocationFromVector extends SimpleExpression<Location> { static { Skript.registerExpression(ExprLocationFromVector.class, Location.class, ExpressionType.SIMPLE, - "%vector% [to location] in %world%", + "%vector% to location in %world%", "location (from|of) %vector% in %world%", "%vector% [to location] in %world% with yaw %number% and pitch %number%", "location (from|of) %vector% in %world% with yaw %number% and pitch %number%" From de1e34e5e0d7d51c984d2c7fbdc43076c9511622 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Wed, 10 Jan 2024 12:27:21 -0500 Subject: [PATCH 577/619] Fix ReleaseChannel Logic (#6312) --- build.gradle | 14 +++++--------- src/main/java/ch/njol/skript/SkriptConfig.java | 14 ++++++-------- src/main/resources/config.sk | 2 +- 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/build.gradle b/build.gradle index 5ab26a2e631..33f0399632c 100644 --- a/build.gradle +++ b/build.gradle @@ -295,10 +295,8 @@ task githubResources(type: ProcessResources) { include '**' version = project.property('version') def channel = 'stable' - if (version.contains('alpha')) - channel = 'alpha' - else if (version.contains('beta')) - channel = 'beta' + if (version.contains('pre')) + channel = 'prerelease' filter ReplaceTokens, tokens: [ 'version' : version, 'today' : '' + LocalTime.now(), @@ -330,10 +328,8 @@ task spigotResources(type: ProcessResources) { include '**' version = project.property('version') def channel = 'stable' - if (version.contains('alpha')) - channel = 'alpha' - else if (version.contains('beta')) - channel = 'beta' + if (version.contains('pre')) + channel = 'prerelease' filter ReplaceTokens, tokens: [ 'version' : version, 'today' : '' + LocalTime.now(), @@ -369,7 +365,7 @@ task nightlyResources(type: ProcessResources) { 'version' : version, 'today' : '' + LocalTime.now(), 'release-flavor' : 'skriptlang-nightly', // SkriptLang build, automatically done by CI - 'release-channel' : 'alpha', // No update checking, but these are VERY unstable + 'release-channel' : 'prerelease', // No update checking, but these are VERY unstable 'release-updater' : 'ch.njol.skript.update.NoUpdateChecker', // No autoupdates for now 'release-source' : '', 'release-download': 'null' diff --git a/src/main/java/ch/njol/skript/SkriptConfig.java b/src/main/java/ch/njol/skript/SkriptConfig.java index 13ce39f7620..a8cb74a869b 100644 --- a/src/main/java/ch/njol/skript/SkriptConfig.java +++ b/src/main/java/ch/njol/skript/SkriptConfig.java @@ -97,14 +97,15 @@ public class SkriptConfig { .setter(t -> { ReleaseChannel channel; switch (t) { - case "alpha": // Everything goes in alpha channel - channel = new ReleaseChannel((name) -> true, t); - break; + case "alpha": case "beta": - channel = new ReleaseChannel((name) -> !name.contains("alpha"), t); + Skript.warning("'alpha' and 'beta' are no longer valid release channels. Use 'prerelease' instead."); + case "prerelease": // All development builds are valid + channel = new ReleaseChannel((name) -> true, t); break; case "stable": - channel = new ReleaseChannel((name) -> !name.contains("alpha") && !name.contains("beta"), t); + // TODO a better option would be to check that it is not a pre-release through GH API + channel = new ReleaseChannel((name) -> !name.contains("pre"), t); break; case "none": channel = new ReleaseChannel((name) -> false, t); @@ -116,9 +117,6 @@ public class SkriptConfig { } SkriptUpdater updater = Skript.getInstance().getUpdater(); if (updater != null) { - if (updater.getCurrentRelease().flavor.contains("spigot") && !t.equals("stable")) { - Skript.error("Only stable Skript versions are uploaded to Spigot resources."); - } updater.setReleaseChannel(channel); } }); diff --git a/src/main/resources/config.sk b/src/main/resources/config.sk index 81545ae169c..d15284a06eb 100644 --- a/src/main/resources/config.sk +++ b/src/main/resources/config.sk @@ -53,7 +53,7 @@ update check interval: 12 hours release channel: @release-channel@ # If 'check for new version' is enabled, this determines how stable the updates must be. # 'stable' means that only stable releases of Skript will be considered updates. -# 'beta' and 'alpha' mean that also development releases will be checked for. +# 'prerelease' means that development releases will also be checked for. # Initial value of this depends on the Skript version you first installed; it was '@release-channel@'. enable effect commands: false From 271b79910cdd9d1a9bdb0211081151159b9b49bd Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Wed, 10 Jan 2024 17:01:10 -0500 Subject: [PATCH 578/619] Fix "parsed as a player" Not Working (#6317) --- src/main/java/ch/njol/skript/classes/data/BukkitClasses.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1f65e0a7aaf..25000a2390d 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -681,7 +681,7 @@ public String toVariableNameString(final Inventory i) { @Override @Nullable public Player parse(String string, ParseContext context) { - if (context == ParseContext.COMMAND) { + if (context == ParseContext.COMMAND || context == ParseContext.PARSE) { if (string.isEmpty()) return null; if (UUID_PATTERN.matcher(string).matches()) From 67c4e0e22e913923c890fa16e38a29b9539eee39 Mon Sep 17 00:00:00 2001 From: APickledWalrus <apickledwalrus@gmail.com> Date: Wed, 10 Jan 2024 17:41:42 -0500 Subject: [PATCH 579/619] Prepare For Release --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 0bfaa37069b..77cdcaa9616 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ org.gradle.parallel=true groupid=ch.njol name=skript -version=2.8.0-pre1 +version=2.8.0-pre2 jarName=Skript.jar testEnv=java17/paper-1.20.4 testEnvJavaVersion=17 From 0c6d8bb5179a37cb793c943573c0c9aeaf7bd398 Mon Sep 17 00:00:00 2001 From: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> Date: Mon, 15 Jan 2024 13:59:11 -0600 Subject: [PATCH 580/619] Fix conflict between ExprItemsIn and ExprInventory (#6323) --- .../java/ch/njol/skript/expressions/ExprInventory.java | 7 +++---- .../tests/regressions/6290-item-inventory-conflict.sk | 6 ++++++ 2 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 src/test/skript/tests/regressions/6290-item-inventory-conflict.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprInventory.java b/src/main/java/ch/njol/skript/expressions/ExprInventory.java index 34f4618ccba..4d834afa030 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprInventory.java +++ b/src/main/java/ch/njol/skript/expressions/ExprInventory.java @@ -18,7 +18,6 @@ */ package ch.njol.skript.expressions; -import ch.njol.skript.aliases.ItemData; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.config.Node; import ch.njol.skript.expressions.base.PropertyExpression; @@ -33,17 +32,14 @@ import org.bukkit.event.Event; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; -import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.BlockStateMeta; import org.bukkit.inventory.meta.ItemMeta; import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; -import ch.njol.skript.expressions.base.SimplePropertyExpression; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; @@ -73,6 +69,9 @@ public class ExprInventory extends SimpleExpression<Object> { @Override @SuppressWarnings("unchecked") public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { + // prevent conflict with ExprItemsIn (https://github.com/SkriptLang/Skript/issues/6290) + if (exprs[0].getSource() instanceof ExprItemsIn) + return false; // if we're dealing with a loop of just this expression Node n = SkriptLogger.getNode(); inLoop = n != null && ("loop " + parseResult.expr).equals(n.getKey()); diff --git a/src/test/skript/tests/regressions/6290-item-inventory-conflict.sk b/src/test/skript/tests/regressions/6290-item-inventory-conflict.sk new file mode 100644 index 00000000000..38b2d328cb9 --- /dev/null +++ b/src/test/skript/tests/regressions/6290-item-inventory-conflict.sk @@ -0,0 +1,6 @@ +test "item inventory conflict": + parse: + loop all players: + loop all items in the loop-player's inventory: + set {_item} to loop-item + assert parse logs are not set with "failed to parse loop" From bb3cee2b713f0b8d4548b046cfe9a54ba86cba26 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Mon, 15 Jan 2024 17:29:49 -0500 Subject: [PATCH 581/619] Prepare For Release (2.8.0) (#6333) --- gradle.properties | 2 +- skript-aliases | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 77cdcaa9616..4e9a1a994d7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ org.gradle.parallel=true groupid=ch.njol name=skript -version=2.8.0-pre2 +version=2.8.0 jarName=Skript.jar testEnv=java17/paper-1.20.4 testEnvJavaVersion=17 diff --git a/skript-aliases b/skript-aliases index 703f47aa957..c6a515a9e4e 160000 --- a/skript-aliases +++ b/skript-aliases @@ -1 +1 @@ -Subproject commit 703f47aa957346dcce8642a14a168e05e5f69f40 +Subproject commit c6a515a9e4e249c2859f4cc2992bf0da559b63cc From b9f89c14c6cd7859b96a0c6a7d791fdb455c09d6 Mon Sep 17 00:00:00 2001 From: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> Date: Mon, 15 Jan 2024 17:37:20 -0600 Subject: [PATCH 582/619] Try to fix archive docs (#6325) --- .github/workflows/release-docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-docs.yml b/.github/workflows/release-docs.yml index 06cb5569076..f6f7ebc8a27 100644 --- a/.github/workflows/release-docs.yml +++ b/.github/workflows/release-docs.yml @@ -37,7 +37,6 @@ jobs: - name: Push release documentation uses: ./skript/.github/workflows/docs/push-docs with: - docs_output_dir: ${{ steps.configuration.outputs.DOCS_OUTPUT_DIR }} docs_repo_dir: ${{ steps.configuration.outputs.DOCS_REPO_DIR }} git_name: Release Docs Bot git_email: releasedocs@skriptlang.org @@ -69,6 +68,7 @@ jobs: uses: ./skript/.github/workflows/docs/generate-docs with: docs_repo_dir: ${{ steps.configuration.outputs.DOCS_REPO_DIR }} + docs_output_dir: ${{ steps.configuration.outputs.DOCS_OUTPUT_DIR }} skript_repo_dir: ${{ steps.configuration.outputs.SKRIPT_REPO_DIR }} is_release: true - name: Push archive documentation From 253b026e0408235e3cafd58ef65bea2b534d77fa Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Mon, 29 Jan 2024 22:56:19 +0300 Subject: [PATCH 583/619] =?UTF-8?q?=F0=9F=9A=80=20Allow=20`armour`=20in=20?= =?UTF-8?q?armor=20change=20event=20(#6357)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/ch/njol/skript/events/SimpleEvents.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/ch/njol/skript/events/SimpleEvents.java b/src/main/java/ch/njol/skript/events/SimpleEvents.java index ae20f51dc0d..18679fcbbbf 100644 --- a/src/main/java/ch/njol/skript/events/SimpleEvents.java +++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java @@ -536,12 +536,14 @@ public class SimpleEvents { .since("2.5"); } if (Skript.classExists("com.destroystokyo.paper.event.player.PlayerArmorChangeEvent")) { - Skript.registerEvent("Armor Change", SimpleEvent.class, PlayerArmorChangeEvent.class, "[player] armor change[d]") + Skript.registerEvent("Armor Change", SimpleEvent.class, PlayerArmorChangeEvent.class, "[player] armo[u]r change[d]") .description("Called when armor pieces of a player are changed.") .requiredPlugins("Paper") - .examples("on armor change:", - " send \"You equipped %event-item%!\"") - .since("2.5"); + .keywords("armour") + .examples( + "on armor change:", + "\tsend \"You equipped %event-item%!\"" + ).since("2.5"); } if (Skript.classExists("org.bukkit.event.block.SpongeAbsorbEvent")) { Skript.registerEvent("Sponge Absorb", SimpleEvent.class, SpongeAbsorbEvent.class, "sponge absorb") From e691a6bd21db68dfdd2533ac5a4273b69677fc1e Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Wed, 31 Jan 2024 12:15:56 +0300 Subject: [PATCH 584/619] =?UTF-8?q?=E2=9A=92=EF=B8=8F=20Fix=20isSingle=20o?= =?UTF-8?q?f=20ExprArmorSlot=20(#6356)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/ch/njol/skript/expressions/ExprArmorSlot.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprArmorSlot.java b/src/main/java/ch/njol/skript/expressions/ExprArmorSlot.java index e5dd8b8eb09..12f16c4c090 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprArmorSlot.java +++ b/src/main/java/ch/njol/skript/expressions/ExprArmorSlot.java @@ -56,11 +56,13 @@ public class ExprArmorSlot extends PropertyExpression<LivingEntity, Slot> { @Nullable private EquipSlot slot; private boolean explicitSlot; + private boolean isArmor; @Override @SuppressWarnings("unchecked") public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - slot = parseResult.hasTag("armour") ? null : EquipSlot.valueOf(parseResult.tags.get(0).toUpperCase(Locale.ENGLISH)); + isArmor = parseResult.hasTag("armour"); + slot = isArmor ? null : EquipSlot.valueOf(parseResult.tags.get(0).toUpperCase(Locale.ENGLISH)); explicitSlot = parseResult.hasTag("slot"); // User explicitly asked for SLOT, not item setExpr((Expression<? extends LivingEntity>) exprs[0]); return true; @@ -92,6 +94,11 @@ protected Slot[] get(Event event, LivingEntity[] source) { }); } + @Override + public boolean isSingle() { + return !isArmor && super.isSingle(); + } + @Override public Class<Slot> getReturnType() { return Slot.class; From 3c5b45cba14e1cd1334608072e57bd53cbb81ea6 Mon Sep 17 00:00:00 2001 From: Ramon Jales <85131757+RamonJales@users.noreply.github.com> Date: Thu, 1 Feb 2024 11:57:02 -0300 Subject: [PATCH 585/619] Fix invalid docs link (#6378) --- src/main/java/ch/njol/skript/classes/data/JavaClasses.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/classes/data/JavaClasses.java b/src/main/java/ch/njol/skript/classes/data/JavaClasses.java index 8bf9f32756b..ce726218e6f 100644 --- a/src/main/java/ch/njol/skript/classes/data/JavaClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/JavaClasses.java @@ -550,7 +550,7 @@ public boolean mustSyncDeserialization() { .description("Text is simply text, i.e. a sequence of characters, which can optionally contain expressions which will be replaced with a meaningful representation " + "(e.g. %player% will be replaced with the player's name).", "Because scripts are also text, you have to put text into double quotes to tell Skript which part of the line is an effect/expression and which part is the text.", - "Please read the article on <a href='./strings/'>Texts and Variable Names</a> to learn more.") + "Please read the article on <a href='./text.html'>Texts and Variable Names</a> to learn more.") .usage("simple: \"...\"", "quotes: \"...\"\"...\"", "expressions: \"...%expression%...\"", From 758e3f97adc9066bfd53c681c2b86a1c6d01714a Mon Sep 17 00:00:00 2001 From: Shane Bee <shanebolenback@me.com> Date: Thu, 1 Feb 2024 08:22:59 -0800 Subject: [PATCH 586/619] SimpleEntityData - add new MC 1.20.3 entities (#6367) --- src/main/java/ch/njol/skript/entity/SimpleEntityData.java | 7 +++++++ src/main/resources/lang/default.lang | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/main/java/ch/njol/skript/entity/SimpleEntityData.java b/src/main/java/ch/njol/skript/entity/SimpleEntityData.java index 693f7b5773e..b63388d87db 100644 --- a/src/main/java/ch/njol/skript/entity/SimpleEntityData.java +++ b/src/main/java/ch/njol/skript/entity/SimpleEntityData.java @@ -32,6 +32,7 @@ import org.bukkit.entity.Bat; import org.bukkit.entity.Blaze; import org.bukkit.entity.BlockDisplay; +import org.bukkit.entity.Breeze; import org.bukkit.entity.Camel; import org.bukkit.entity.CaveSpider; import org.bukkit.entity.ChestedHorse; @@ -130,6 +131,7 @@ import org.bukkit.entity.WanderingTrader; import org.bukkit.entity.Warden; import org.bukkit.entity.WaterMob; +import org.bukkit.entity.WindCharge; import org.bukkit.entity.Witch; import org.bukkit.entity.Wither; import org.bukkit.entity.WitherSkeleton; @@ -310,6 +312,11 @@ private static void addSuperEntity(String codeName, Class<? extends Entity> enti addSuperEntity("display", Display.class); } + if (Skript.isRunningMinecraft(1, 20, 3)) { + addSimpleEntity("breeze", Breeze.class); + addSimpleEntity("wind charge", WindCharge.class); + } + // Register zombie after Husk and Drowned to make sure both work addSimpleEntity("zombie", Zombie.class); // Register squid after glow squid to make sure both work diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 77cc9d12c2a..efd0651babf 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -1241,6 +1241,13 @@ entities: text display: name: text display¦s pattern: text display(|1¦s) + # 1.20.3 Entities + breeze: + name: breeze¦s + pattern: breeze(|1¦s) + wind charge: + name: wind charge¦s + pattern: wind charge(|1¦s) # -- Heal Reasons -- heal reasons: From bca7469c0574a7bb61d3fc50106da4d6c0fdc32e Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Thu, 1 Feb 2024 20:11:19 +0300 Subject: [PATCH 587/619] =?UTF-8?q?=E2=9A=92=EF=B8=8F=20Fix=20function=20s?= =?UTF-8?q?ignature=20parentheses=20(#6358)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/ch/njol/skript/structures/StructFunction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/structures/StructFunction.java b/src/main/java/ch/njol/skript/structures/StructFunction.java index 003a149b8fc..4ac26e07b3f 100644 --- a/src/main/java/ch/njol/skript/structures/StructFunction.java +++ b/src/main/java/ch/njol/skript/structures/StructFunction.java @@ -61,7 +61,7 @@ public class StructFunction extends Structure { public static final Priority PRIORITY = new Priority(400); private static final Pattern SIGNATURE_PATTERN = - Pattern.compile("(?:local )?function (" + Functions.functionNamePattern + ")\\((.*)\\)(?:\\s*(?:::| returns )\\s*(.+))?"); + Pattern.compile("^(?:local )?function (" + Functions.functionNamePattern + ")\\((.*?)\\)(?:\\s*(?:::| returns )\\s*(.+))?$"); private static final AtomicBoolean VALIDATE_FUNCTIONS = new AtomicBoolean(); static { From 906c5a2dca61f82dce5615c18a831e40f4ae7305 Mon Sep 17 00:00:00 2001 From: C0D3 M4513R <28912031+C0D3-M4513R@users.noreply.github.com> Date: Thu, 1 Feb 2024 18:16:36 +0100 Subject: [PATCH 588/619] Fix Skript MinecraftServer.isRunning reflection on 1.20(.1) (#6352) --- src/main/java/ch/njol/skript/Skript.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index d0a2c2d05ab..8b7c00f6219 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -1157,7 +1157,15 @@ public void onPluginDisable(PluginDisableEvent event) { try { // Spigot removed the mapping for this method in 1.18, so its back to obfuscated method // 1.19 mapping is u and 1.18 is v - String isRunningMethod = Skript.isRunningMinecraft(1, 19) ? "u" : Skript.isRunningMinecraft(1, 18) ? "v" :"isRunning"; + String isRunningMethod = "isRunning"; + + if (Skript.isRunningMinecraft(1, 20)) { + isRunningMethod = "v"; + } else if (Skript.isRunningMinecraft(1, 19)) { + isRunningMethod = "u"; + } else if (Skript.isRunningMinecraft(1, 18)) { + isRunningMethod = "v"; + } IS_RUNNING = MC_SERVER.getClass().getMethod(isRunningMethod); } catch (NoSuchMethodException e) { throw new RuntimeException(e); From f14bcefb21f83239a68b944c36dc0f81cd79f893 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Thu, 1 Feb 2024 20:49:42 +0100 Subject: [PATCH 589/619] Fix "vector from" syntax conflict by making ExprVectorFromDirection parse later. (#6383) --- .../ch/njol/skript/expressions/ExprLocationFromVector.java | 2 +- .../njol/skript/expressions/ExprLocationVectorOffset.java | 2 +- .../ch/njol/skript/expressions/ExprVectorAngleBetween.java | 2 +- .../skript/expressions/ExprVectorBetweenLocations.java | 2 +- .../ch/njol/skript/expressions/ExprVectorCrossProduct.java | 2 +- .../ch/njol/skript/expressions/ExprVectorCylindrical.java | 2 +- .../ch/njol/skript/expressions/ExprVectorDotProduct.java | 2 +- .../njol/skript/expressions/ExprVectorFromDirection.java | 2 +- .../java/ch/njol/skript/expressions/ExprVectorFromXYZ.java | 2 +- .../njol/skript/expressions/ExprVectorFromYawAndPitch.java | 2 +- .../ch/njol/skript/expressions/ExprVectorNormalize.java | 2 +- .../ch/njol/skript/expressions/ExprVectorOfLocation.java | 2 +- .../ch/njol/skript/expressions/ExprVectorSpherical.java | 2 +- .../regressions/6348-vector from expressions conflict.sk | 7 +++++++ 14 files changed, 20 insertions(+), 13 deletions(-) create mode 100644 src/test/skript/tests/regressions/6348-vector from expressions conflict.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprLocationFromVector.java b/src/main/java/ch/njol/skript/expressions/ExprLocationFromVector.java index c4b86aeea3c..92aa68c0b4a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprLocationFromVector.java +++ b/src/main/java/ch/njol/skript/expressions/ExprLocationFromVector.java @@ -48,7 +48,7 @@ public class ExprLocationFromVector extends SimpleExpression<Location> { static { - Skript.registerExpression(ExprLocationFromVector.class, Location.class, ExpressionType.SIMPLE, + Skript.registerExpression(ExprLocationFromVector.class, Location.class, ExpressionType.COMBINED, "%vector% to location in %world%", "location (from|of) %vector% in %world%", "%vector% [to location] in %world% with yaw %number% and pitch %number%", diff --git a/src/main/java/ch/njol/skript/expressions/ExprLocationVectorOffset.java b/src/main/java/ch/njol/skript/expressions/ExprLocationVectorOffset.java index 89d1deccd92..f11da7d9821 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprLocationVectorOffset.java +++ b/src/main/java/ch/njol/skript/expressions/ExprLocationVectorOffset.java @@ -46,7 +46,7 @@ public class ExprLocationVectorOffset extends SimpleExpression<Location> { static { - Skript.registerExpression(ExprLocationVectorOffset.class, Location.class, ExpressionType.SIMPLE, + Skript.registerExpression(ExprLocationVectorOffset.class, Location.class, ExpressionType.COMBINED, "%location% offset by [[the] vectors] %vectors%", "%location%[ ]~[~][ ]%vectors%"); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorAngleBetween.java b/src/main/java/ch/njol/skript/expressions/ExprVectorAngleBetween.java index 1cdf3626c8e..0d0e5a06700 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorAngleBetween.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorAngleBetween.java @@ -42,7 +42,7 @@ public class ExprVectorAngleBetween extends SimpleExpression<Number> { static { - Skript.registerExpression(ExprVectorAngleBetween.class, Number.class, ExpressionType.SIMPLE, + Skript.registerExpression(ExprVectorAngleBetween.class, Number.class, ExpressionType.COMBINED, "[the] angle between [[the] vectors] %vector% and %vector%"); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorBetweenLocations.java b/src/main/java/ch/njol/skript/expressions/ExprVectorBetweenLocations.java index 75412c017b2..a1060d3eec8 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorBetweenLocations.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorBetweenLocations.java @@ -42,7 +42,7 @@ public class ExprVectorBetweenLocations extends SimpleExpression<Vector> { static { - Skript.registerExpression(ExprVectorBetweenLocations.class, Vector.class, ExpressionType.SIMPLE, + Skript.registerExpression(ExprVectorBetweenLocations.class, Vector.class, ExpressionType.COMBINED, "[the] vector (from|between) %location% (to|and) %location%"); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorCrossProduct.java b/src/main/java/ch/njol/skript/expressions/ExprVectorCrossProduct.java index ae8ebe990e1..88b5dba14e8 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorCrossProduct.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorCrossProduct.java @@ -41,7 +41,7 @@ public class ExprVectorCrossProduct extends SimpleExpression<Vector> { static { - Skript.registerExpression(ExprVectorCrossProduct.class, Vector.class, ExpressionType.SIMPLE, "%vector% cross %vector%"); + Skript.registerExpression(ExprVectorCrossProduct.class, Vector.class, ExpressionType.COMBINED, "%vector% cross %vector%"); } @SuppressWarnings("null") diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorCylindrical.java b/src/main/java/ch/njol/skript/expressions/ExprVectorCylindrical.java index 40cd9edd963..1c7125cdfd0 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorCylindrical.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorCylindrical.java @@ -46,7 +46,7 @@ public class ExprVectorCylindrical extends SimpleExpression<Vector> { static { - Skript.registerExpression(ExprVectorCylindrical.class, Vector.class, ExpressionType.SIMPLE, + Skript.registerExpression(ExprVectorCylindrical.class, Vector.class, ExpressionType.COMBINED, "[a] [new] cylindrical vector [(from|with)] [radius] %number%, [yaw] %number%(,| and) [height] %number%"); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorDotProduct.java b/src/main/java/ch/njol/skript/expressions/ExprVectorDotProduct.java index 50ef5e9ea15..0289e6aa584 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorDotProduct.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorDotProduct.java @@ -41,7 +41,7 @@ public class ExprVectorDotProduct extends SimpleExpression<Number> { static { - Skript.registerExpression(ExprVectorDotProduct.class, Number.class, ExpressionType.SIMPLE, "%vector% dot %vector%"); + Skript.registerExpression(ExprVectorDotProduct.class, Number.class, ExpressionType.COMBINED, "%vector% dot %vector%"); } @SuppressWarnings("null") diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorFromDirection.java b/src/main/java/ch/njol/skript/expressions/ExprVectorFromDirection.java index 3f4d9f08510..c20b8ec74a1 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorFromDirection.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorFromDirection.java @@ -49,7 +49,7 @@ public class ExprVectorFromDirection extends SimpleExpression<Vector> { static { - Skript.registerExpression(ExprVectorFromDirection.class, Vector.class, ExpressionType.SIMPLE, + Skript.registerExpression(ExprVectorFromDirection.class, Vector.class, ExpressionType.PROPERTY, "vector[s] [from] %directions%", "%directions% vector[s]"); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorFromXYZ.java b/src/main/java/ch/njol/skript/expressions/ExprVectorFromXYZ.java index 6b553e6f50b..c720ffc296a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorFromXYZ.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorFromXYZ.java @@ -41,7 +41,7 @@ public class ExprVectorFromXYZ extends SimpleExpression<Vector> { static { - Skript.registerExpression(ExprVectorFromXYZ.class, Vector.class, ExpressionType.SIMPLE, + Skript.registerExpression(ExprVectorFromXYZ.class, Vector.class, ExpressionType.COMBINED, "[a] [new] vector [(from|at|to)] %number%,[ ]%number%(,[ ]| and )%number%"); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorFromYawAndPitch.java b/src/main/java/ch/njol/skript/expressions/ExprVectorFromYawAndPitch.java index 8f7179e7d26..9f04dc36591 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorFromYawAndPitch.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorFromYawAndPitch.java @@ -42,7 +42,7 @@ public class ExprVectorFromYawAndPitch extends SimpleExpression<Vector> { static { - Skript.registerExpression(ExprVectorFromYawAndPitch.class, Vector.class, ExpressionType.SIMPLE, + Skript.registerExpression(ExprVectorFromYawAndPitch.class, Vector.class, ExpressionType.COMBINED, "[a] [new] vector (from|with) yaw %number% and pitch %number%"); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorNormalize.java b/src/main/java/ch/njol/skript/expressions/ExprVectorNormalize.java index 00b80f7d109..db136199bbb 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorNormalize.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorNormalize.java @@ -41,7 +41,7 @@ public class ExprVectorNormalize extends SimpleExpression<Vector> { static { - Skript.registerExpression(ExprVectorNormalize.class, Vector.class, ExpressionType.SIMPLE, + Skript.registerExpression(ExprVectorNormalize.class, Vector.class, ExpressionType.COMBINED, "normalize[d] %vector%", "%vector% normalized"); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorOfLocation.java b/src/main/java/ch/njol/skript/expressions/ExprVectorOfLocation.java index e15974b78a7..69d6a835bf8 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorOfLocation.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorOfLocation.java @@ -42,7 +42,7 @@ public class ExprVectorOfLocation extends SimpleExpression<Vector> { static { - Skript.registerExpression(ExprVectorOfLocation.class, Vector.class, ExpressionType.SIMPLE, + Skript.registerExpression(ExprVectorOfLocation.class, Vector.class, ExpressionType.PROPERTY, "[the] vector (of|from|to) %location%", "%location%'s vector"); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorSpherical.java b/src/main/java/ch/njol/skript/expressions/ExprVectorSpherical.java index 3507f8ab25a..4053ff28bc3 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorSpherical.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorSpherical.java @@ -46,7 +46,7 @@ public class ExprVectorSpherical extends SimpleExpression<Vector> { static { - Skript.registerExpression(ExprVectorSpherical.class, Vector.class, ExpressionType.SIMPLE, + Skript.registerExpression(ExprVectorSpherical.class, Vector.class, ExpressionType.COMBINED, "[new] spherical vector [(from|with)] [radius] %number%, [yaw] %number%(,| and) [pitch] %number%"); } diff --git a/src/test/skript/tests/regressions/6348-vector from expressions conflict.sk b/src/test/skript/tests/regressions/6348-vector from expressions conflict.sk new file mode 100644 index 00000000000..85b1bb205b0 --- /dev/null +++ b/src/test/skript/tests/regressions/6348-vector from expressions conflict.sk @@ -0,0 +1,7 @@ +test "vector from expressions conflict": + set {_x} to 1 + set {_y} to 2 + set {_z} to -3 + set {_v} to vector from {_x}, {_y}, {_z} + set {_v2} to vector({_x}, {_y}, {_z}) + assert {_v} is {_v2} with "Vector from not generating correct vector. Expected %{_v2}%, got %{_v}%" From 1348f782489b0a9c78f22bd295d39caf2accdbb3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 15:10:57 -0500 Subject: [PATCH 590/619] Bump gradle/wrapper-validation-action from 1 to 2 (#6372) --- .github/workflows/java-17-builds.yml | 2 +- .github/workflows/java-8-builds.yml | 2 +- .github/workflows/junit-17-builds.yml | 2 +- .github/workflows/junit-8-builds.yml | 2 +- .github/workflows/repo.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/java-17-builds.yml b/.github/workflows/java-17-builds.yml index 248d72002a6..a0c87141c13 100644 --- a/.github/workflows/java-17-builds.yml +++ b/.github/workflows/java-17-builds.yml @@ -16,7 +16,7 @@ jobs: with: submodules: recursive - name: validate gradle wrapper - uses: gradle/wrapper-validation-action@v1 + uses: gradle/wrapper-validation-action@v2 - name: Set up JDK 17 uses: actions/setup-java@v3 with: diff --git a/.github/workflows/java-8-builds.yml b/.github/workflows/java-8-builds.yml index adae34978ec..090e6b3db6a 100644 --- a/.github/workflows/java-8-builds.yml +++ b/.github/workflows/java-8-builds.yml @@ -16,7 +16,7 @@ jobs: with: submodules: recursive - name: validate gradle wrapper - uses: gradle/wrapper-validation-action@v1 + uses: gradle/wrapper-validation-action@v2 - name: Set up JDK 17 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/junit-17-builds.yml b/.github/workflows/junit-17-builds.yml index d737c152b86..ac8de249e1f 100644 --- a/.github/workflows/junit-17-builds.yml +++ b/.github/workflows/junit-17-builds.yml @@ -16,7 +16,7 @@ jobs: with: submodules: recursive - name: validate gradle wrapper - uses: gradle/wrapper-validation-action@v1 + uses: gradle/wrapper-validation-action@v2 - name: Set up JDK 17 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/junit-8-builds.yml b/.github/workflows/junit-8-builds.yml index bc23d73ba72..2a9bf589e67 100644 --- a/.github/workflows/junit-8-builds.yml +++ b/.github/workflows/junit-8-builds.yml @@ -16,7 +16,7 @@ jobs: with: submodules: recursive - name: validate gradle wrapper - uses: gradle/wrapper-validation-action@v1 + uses: gradle/wrapper-validation-action@v2 - name: Set up JDK 17 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/repo.yml b/.github/workflows/repo.yml index d3527c2c458..77b8a55fd4e 100644 --- a/.github/workflows/repo.yml +++ b/.github/workflows/repo.yml @@ -12,7 +12,7 @@ jobs: with: submodules: recursive - name: validate gradle wrapper - uses: gradle/wrapper-validation-action@v1 + uses: gradle/wrapper-validation-action@v2 - name: Set up JDK 17 uses: actions/setup-java@v4 with: From 555c76be56f67c1c2568abee0fc70c54ce061dfd Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Thu, 1 Feb 2024 22:48:23 +0100 Subject: [PATCH 591/619] Fix paths for enabled scripts in ExprScripts. (#6374) --- .../njol/skript/expressions/ExprScripts.java | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprScripts.java b/src/main/java/ch/njol/skript/expressions/ExprScripts.java index 1300216bf9a..331ac108014 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprScripts.java +++ b/src/main/java/ch/njol/skript/expressions/ExprScripts.java @@ -32,9 +32,12 @@ import ch.njol.util.Kleenean; import java.io.File; +import java.nio.file.Path; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.regex.Pattern; +import java.util.stream.Collectors; import org.bukkit.event.Event; @@ -54,12 +57,12 @@ public class ExprScripts extends SimpleExpression<String> { static { Skript.registerExpression(ExprScripts.class, String.class, ExpressionType.SIMPLE, - "[all [of the]] scripts [(1:without ([subdirectory] paths|parents))]", - "[all [of the]] (enabled|loaded) scripts [(1:without ([subdirectory] paths|parents))]", - "[all [of the]] (disabled|unloaded) scripts [(1:without ([subdirectory] paths|parents))]"); + "[all [of the]|the] scripts [1:without ([subdirectory] paths|parents)]", + "[all [of the]|the] (enabled|loaded) scripts [1:without ([subdirectory] paths|parents)]", + "[all [of the]|the] (disabled|unloaded) scripts [1:without ([subdirectory] paths|parents)]"); } - private static final String SCRIPTS_PATH = new File(Skript.getInstance().getDataFolder(), Skript.SCRIPTSFOLDER).getPath() + File.separator; + private static final Path SCRIPTS_PATH = Skript.getInstance().getScriptsFolder().getAbsoluteFile().toPath(); private boolean includeEnabled; private boolean includeDisabled; @@ -75,20 +78,27 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override protected String[] get(Event event) { - List<File> scripts = new ArrayList<>(); + List<Path> scripts = new ArrayList<>(); if (includeEnabled) { for (Script script : ScriptLoader.getLoadedScripts()) - scripts.add(script.getConfig().getFile()); + scripts.add(script.getConfig().getPath()); } if (includeDisabled) - scripts.addAll(ScriptLoader.getDisabledScripts()); - return formatFiles(scripts); + scripts.addAll(ScriptLoader.getDisabledScripts() + .stream() + .map(File::toPath) + .collect(Collectors.toList())); + return formatPaths(scripts); } @SuppressWarnings("null") - private String[] formatFiles(List<File> files) { - return files.stream() - .map(f -> noPaths ? f.getName() : f.getPath().replaceFirst(Pattern.quote(SCRIPTS_PATH), "")) + private String[] formatPaths(List<Path> paths) { + return paths.stream() + .map(path -> { + if (noPaths) + return path.getFileName(); + return SCRIPTS_PATH.relativize(path.toAbsolutePath()).toString(); + }) .toArray(String[]::new); } From 5dd60e52daee25645b7aa4dbd26deb2fdd4e32e9 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Thu, 1 Feb 2024 23:14:26 +0100 Subject: [PATCH 592/619] Avoid NPE by defaulting to enum name when no language node available in EnumUtil's toString. (#6375) --- src/main/java/ch/njol/skript/util/EnumUtils.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/util/EnumUtils.java b/src/main/java/ch/njol/skript/util/EnumUtils.java index 31c64e1b23b..2d97fc924f5 100644 --- a/src/main/java/ch/njol/skript/util/EnumUtils.java +++ b/src/main/java/ch/njol/skript/util/EnumUtils.java @@ -107,7 +107,8 @@ public E parse(String input) { * @return A string representation of the enumerator. */ public String toString(E enumerator, int flags) { - return names[enumerator.ordinal()]; + String s = names[enumerator.ordinal()]; + return s != null ? s : enumerator.name(); } /** From 6be9375ab85ca8a4085cc3ec795745a6c1ff9f10 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Thu, 1 Feb 2024 18:09:32 -0500 Subject: [PATCH 593/619] Improve UnparsedLiteral Handling (#6360) --- .../arithmetic/ExprArithmetic.java | 180 +++++++++++++++--- .../ch/njol/skript/log/ParseLogHandler.java | 26 ++- .../skript/patterns/TypePatternElement.java | 51 ++++- .../tests/misc/invalid unparsed literals.sk | 6 + .../syntaxes/expressions/ExprArithmetic.sk | 7 + 5 files changed, 234 insertions(+), 36 deletions(-) create mode 100644 src/test/skript/tests/misc/invalid unparsed literals.sk diff --git a/src/main/java/ch/njol/skript/expressions/arithmetic/ExprArithmetic.java b/src/main/java/ch/njol/skript/expressions/arithmetic/ExprArithmetic.java index baa160890c8..0f72b2cb87e 100644 --- a/src/main/java/ch/njol/skript/expressions/arithmetic/ExprArithmetic.java +++ b/src/main/java/ch/njol/skript/expressions/arithmetic/ExprArithmetic.java @@ -28,6 +28,7 @@ import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.UnparsedLiteral; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.skript.lang.util.SimpleLiteral; import ch.njol.skript.registrations.Classes; @@ -117,43 +118,151 @@ public PatternInfo(Operator operator, boolean leftGrouped, boolean rightGrouped) private boolean leftGrouped, rightGrouped; @Override - @SuppressWarnings({"ConstantConditions", "unchecked"}) + @SuppressWarnings({"ConstantConditions", "rawtypes", "unchecked"}) public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - first = LiteralUtils.defendExpression(exprs[0]); - second = LiteralUtils.defendExpression(exprs[1]); - - if (!LiteralUtils.canInitSafely(first, second)) - return false; - - Class<? extends L> firstClass = first.getReturnType(); - Class<? extends R> secondClass = second.getReturnType(); + first = (Expression<L>) exprs[0]; + second = (Expression<R>) exprs[1]; PatternInfo patternInfo = patterns.getInfo(matchedPattern); leftGrouped = patternInfo.leftGrouped; rightGrouped = patternInfo.rightGrouped; operator = patternInfo.operator; - if (firstClass != Object.class && secondClass == Object.class && Arithmetics.getOperations(operator, firstClass).isEmpty()) { - // If the first class is known but doesn't have any operations, then we fail - return error(firstClass, secondClass); - } else if (firstClass == Object.class && secondClass != Object.class && Arithmetics.getOperations(operator).stream() - .noneMatch(info -> info.getRight().isAssignableFrom(secondClass))) { - // If the second class is known but doesn't have any operations, then we fail - return error(firstClass, secondClass); + /* + * Step 1: UnparsedLiteral Resolving + * + * Since Arithmetic may be performed on a variety of types, it is possible that 'first' or 'second' + * will represent unparsed literals. That is, the parser could not determine what their literal contents represent. + * Thus, it is now up to this expression to determine what they mean. + * + * If there are no unparsed literals, nothing happens at this step. + * If there are unparsed literals, one of three possible execution flows will occur: + * + * Case 1. 'first' and 'second' are unparsed literals + * In this case, there is not a lot of information to work with. + * 'first' and 'second' are attempted to be converted to fit one of all operations using 'operator'. + * If they cannot be matched with the types of a known operation, init will fail. + * + * Case 2. 'first' is an unparsed literal, 'second' is not + * In this case, 'first' needs to be converted into the "left" type of + * any operation using 'operator' with the type of 'second' as the right type. + * If 'first' cannot be converted, init will fail. + * If no operations are found for converting 'first', init will fail, UNLESS the type of 'second' is object, + * where operations will be searched again later with the context of the type of first. + * TODO When 'first' can represent multiple literals, it might be worth checking which of those can work with 'operator' and 'second' + * + * Case 3. 'second' is an unparsed literal, 'first' is not + * In this case, 'second' needs to be converted into the "right" type of + * any operation using 'operator' with the type of 'first' as the "left" type. + * If 'second' cannot be converted, init will fail. + * If no operations are found for converting 'second', init will fail, UNLESS the type of 'first' is object, + * where operations will be searched again later with the context of the type of second. + * TODO When 'second' can represent multiple literals, it might be worth checking which of those can work with 'first' and 'operator' + */ + + if (first instanceof UnparsedLiteral) { + if (second instanceof UnparsedLiteral) { // first and second need converting + for (OperationInfo<?, ?, ?> operation : Arithmetics.getOperations(operator)) { + // match left type with 'first' + Expression<?> convertedFirst = first.getConvertedExpression(operation.getLeft()); + if (convertedFirst == null) + continue; + // match right type with 'second' + Expression<?> convertedSecond = second.getConvertedExpression(operation.getRight()); + if (convertedSecond == null) + continue; + // success, set the values + first = (Expression<L>) convertedFirst; + second = (Expression<R>) convertedSecond; + returnType = (Class<? extends T>) operation.getReturnType(); + } + } else { // first needs converting + // attempt to convert <first> to types that make valid operations with <second> + Class<?> secondClass = second.getReturnType(); + Class[] leftTypes = Arithmetics.getOperations(operator).stream() + .filter(info -> info.getRight().isAssignableFrom(secondClass)) + .map(OperationInfo::getLeft) + .toArray(Class[]::new); + if (leftTypes.length == 0) { // no known operations with second's type + if (secondClass != Object.class) // there won't be any operations + return error(first.getReturnType(), secondClass); + first = (Expression<L>) first.getConvertedExpression(Object.class); + } else { + first = (Expression<L>) first.getConvertedExpression(leftTypes); + } + } + } else if (second instanceof UnparsedLiteral) { // second needs converting + // attempt to convert <second> to types that make valid operations with <first> + Class<?> firstClass = first.getReturnType(); + List<? extends OperationInfo<?, ?, ?>> operations = Arithmetics.getOperations(operator, firstClass); + if (operations.isEmpty()) { // no known operations with first's type + if (firstClass != Object.class) // there won't be any operations + return error(firstClass, second.getReturnType()); + second = (Expression<R>) second.getConvertedExpression(Object.class); + } else { + second = (Expression<R>) second.getConvertedExpression(operations.stream() + .map(OperationInfo::getRight) + .toArray(Class[]::new) + ); + } } - OperationInfo<L, R, T> operationInfo; + if (!LiteralUtils.canInitSafely(first, second)) // checks if there are still unparsed literals present + return false; + + /* + * Step 2: Return Type Calculation + * + * After the first step, everything that can be known about 'first' and 'second' during parsing is known. + * As a result, it is time to determine the return type of the operation. + * + * If the types of 'first' or 'second' are object, it is possible that multiple operations with different return types + * will be found. If that is the case, the supertype of these operations will be the return type (can be object). + * If the types of both are object (e.g. variables), the return type will be object (have to wait until runtime and hope it works). + * Of course, if no operations are found, init will fail. + * + * After these checks, it is safe to assume returnType has a value, as init should have failed by now if not. + * One final check is performed specifically for numerical types. + * Any numerical operation involving division or exponents have a return type of Double. + * Other operations will also return Double, UNLESS 'first' and 'second' are of integer types, in which case the return type will be Long. + * + * If the types of both are something meaningful, the search for a registered operation commences. + * If no operation can be found, init will fail. + */ + + Class<? extends L> firstClass = first.getReturnType(); + Class<? extends R> secondClass = second.getReturnType(); + if (firstClass == Object.class || secondClass == Object.class) { - // If either of the types is unknown, then we resolve the operation at runtime - operationInfo = null; - } else { - operationInfo = (OperationInfo<L, R, T>) Arithmetics.lookupOperationInfo(operator, firstClass, secondClass); - if (operationInfo == null) // We error if we couldn't find an operation between the two types + // if either of the types is unknown, then we resolve the operation at runtime + Class<?>[] returnTypes = null; + if (!(firstClass == Object.class && secondClass == Object.class)) { // both aren't object + if (firstClass == Object.class) { + returnTypes = Arithmetics.getOperations(operator).stream() + .filter(info -> info.getRight().isAssignableFrom(secondClass)) + .map(OperationInfo::getReturnType) + .toArray(Class[]::new); + } else { // secondClass is Object + returnTypes = Arithmetics.getOperations(operator, firstClass).stream() + .map(OperationInfo::getReturnType) + .toArray(Class[]::new); + } + } + if (returnTypes == null) { // both are object; can't determine anything + returnType = (Class<? extends T>) Object.class; + } else if (returnTypes.length == 0) { // one of the classes is known but doesn't have any operations + return error(firstClass, secondClass); + } else { + returnType = (Class<? extends T>) Classes.getSuperClassInfo(returnTypes).getC(); + } + } else if (returnType == null) { // lookup + OperationInfo<L, R, T> operationInfo = (OperationInfo<L, R, T>) Arithmetics.lookupOperationInfo(operator, firstClass, secondClass); + if (operationInfo == null) // we error if we couldn't find an operation between the two types return error(firstClass, secondClass); + returnType = operationInfo.getReturnType(); } - returnType = operationInfo == null ? (Class<? extends T>) Object.class : operationInfo.getReturnType(); - + // ensure proper return types for numerical operations if (Number.class.isAssignableFrom(returnType)) { if (operator == Operator.DIVISION || operator == Operator.EXPONENTIATION) { returnType = (Class<? extends T>) Double.class; @@ -164,19 +273,31 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye firstIsInt |= i.isAssignableFrom(first.getReturnType()); secondIsInt |= i.isAssignableFrom(second.getReturnType()); } - returnType = (Class<? extends T>) (firstIsInt && secondIsInt ? Long.class : Double.class); } } - // Chaining - if (first instanceof ExprArithmetic && !leftGrouped) { + /* + * Step 3: Chaining and Parsing + * + * This step builds the arithmetic chain that will be parsed into an ordered operation to be executed at runtime. + * With larger operations, it is possible that 'first' or 'second' will be instances of ExprArithmetic. + * As a result, their chains need to be incorporated into this instance's chain. + * This is to ensure that, during parsing, a "gettable" that follows the order of operations is built. + * However, in the case of parentheses, the chains will not be combined as the + * order of operations dictates that the result of that chain be determined first. + * + * The chain (a list of values and operators) will then be parsed into a "gettable" that + * can be evaluated during runtime for a final result. + */ + + if (first instanceof ExprArithmetic && !leftGrouped) { // combine chain of 'first' if we do not have parentheses chain.addAll(((ExprArithmetic<?, ?, L>) first).chain); } else { chain.add(first); } chain.add(operator); - if (second instanceof ExprArithmetic && !rightGrouped) { + if (second instanceof ExprArithmetic && !rightGrouped) { // combine chain of 'second' if we do not have parentheses chain.addAll(((ExprArithmetic<?, ?, R>) second).chain); } else { chain.add(second); @@ -197,7 +318,8 @@ protected T[] get(Event event) { private boolean error(Class<?> firstClass, Class<?> secondClass) { ClassInfo<?> first = Classes.getSuperClassInfo(firstClass), second = Classes.getSuperClassInfo(secondClass); - Skript.error(operator.getName() + " can't be performed on " + first.getName().withIndefiniteArticle() + " and " + second.getName().withIndefiniteArticle()); + if (first.getC() != Object.class && second.getC() != Object.class) // errors with "object" are not very useful and often misleading + Skript.error(operator.getName() + " can't be performed on " + first.getName().withIndefiniteArticle() + " and " + second.getName().withIndefiniteArticle()); return false; } diff --git a/src/main/java/ch/njol/skript/log/ParseLogHandler.java b/src/main/java/ch/njol/skript/log/ParseLogHandler.java index dea3cc3cc8e..716464da53f 100644 --- a/src/main/java/ch/njol/skript/log/ParseLogHandler.java +++ b/src/main/java/ch/njol/skript/log/ParseLogHandler.java @@ -18,8 +18,9 @@ */ package ch.njol.skript.log; -import ch.njol.skript.Skript; import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; import java.util.ArrayList; import java.util.List; @@ -31,6 +32,29 @@ public class ParseLogHandler extends LogHandler { private LogEntry error = null; private final List<LogEntry> log = new ArrayList<>(); + + /** + * Internal method for creating a backup of this log. + * @return A new ParseLogHandler containing the contents of this ParseLogHandler. + */ + @ApiStatus.Internal + @Contract("-> new") + public ParseLogHandler backup() { + ParseLogHandler copy = new ParseLogHandler(); + copy.error = this.error; + copy.log.addAll(this.log); + return copy; + } + + /** + * Internal method for restoring a backup of this log. + */ + @ApiStatus.Internal + public void restore(ParseLogHandler parseLogHandler) { + this.error = parseLogHandler.error; + this.log.clear(); + this.log.addAll(parseLogHandler.log); + } @Override public LogResult log(LogEntry entry) { diff --git a/src/main/java/ch/njol/skript/patterns/TypePatternElement.java b/src/main/java/ch/njol/skript/patterns/TypePatternElement.java index f3cbdb5f666..1ecdfee11cc 100644 --- a/src/main/java/ch/njol/skript/patterns/TypePatternElement.java +++ b/src/main/java/ch/njol/skript/patterns/TypePatternElement.java @@ -24,6 +24,7 @@ import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptParser; import ch.njol.skript.lang.SkriptParser.ExprInfo; +import ch.njol.skript.lang.UnparsedLiteral; import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.skript.log.ErrorQuality; import ch.njol.skript.log.ParseLogHandler; @@ -138,6 +139,10 @@ public MatchResult match(String expr, MatchResult matchResult) { ExprInfo exprInfo = getExprInfo(); + MatchResult matchBackup = null; + ParseLogHandler loopLogHandlerBackup = null; + ParseLogHandler expressionLogHandlerBackup = null; + ParseLogHandler loopLogHandler = SkriptLogger.startParseLogHandler(); try { while (newExprOffset != -1) { @@ -168,11 +173,37 @@ public MatchResult match(String expr, MatchResult matchResult) { } } - expressionLogHandler.printLog(); - loopLogHandler.printLog(); - newMatchResult.expressions[expressionIndex] = expression; - return newMatchResult; + + /* + * the parser will return unparsed literals in cases where it cannot interpret an input and object is the desired return type. + * in those cases, it is up to the expression to interpret the input. + * however, this presents a problem for input that is not intended as being one of these object-accepting expressions. + * these object-accepting expressions will be matched instead but their parsing will fail as they cannot interpret the unparsed literals. + * even though it can't interpret them, this loop will have returned a match and thus parsing has ended (and the correct interpretation never attempted). + * to avoid this issue, while also permitting unparsed literals in cases where they are justified, + * the code below forces the loop to continue in hopes of finding a match without unparsed literals. + * if it is unsuccessful, a backup of the first successful match (with unparsed literals) is saved to be returned. + */ + boolean hasUnparsedLiteral = false; + for (int i = expressionIndex + 1; i < newMatchResult.expressions.length; i++) { + if (newMatchResult.expressions[i] instanceof UnparsedLiteral) { + hasUnparsedLiteral = Classes.parse(((UnparsedLiteral) newMatchResult.expressions[i]).getData(), Object.class, newMatchResult.parseContext) == null; + if (hasUnparsedLiteral) { + break; + } + } + } + + if (!hasUnparsedLiteral) { + expressionLogHandler.printLog(); + loopLogHandler.printLog(); + return newMatchResult; + } else if (matchBackup == null) { // only backup the first occurrence of unparsed literals + matchBackup = newMatchResult; + loopLogHandlerBackup = loopLogHandler.backup(); + expressionLogHandlerBackup = expressionLogHandler.backup(); + } } } finally { expressionLogHandler.printError(); @@ -193,11 +224,19 @@ public MatchResult match(String expr, MatchResult matchResult) { } } } finally { - if (!loopLogHandler.isStopped()) + if (loopLogHandlerBackup != null) { // print backup logs if applicable + loopLogHandler.restore(loopLogHandlerBackup); + assert expressionLogHandlerBackup != null; + expressionLogHandlerBackup.printLog(); + } + if (!loopLogHandler.isStopped()) { loopLogHandler.printError(); + } } - return null; + // if there were unparsed literals, we will return the backup now + // if there were not, this returns null + return matchBackup; } @Override diff --git a/src/test/skript/tests/misc/invalid unparsed literals.sk b/src/test/skript/tests/misc/invalid unparsed literals.sk new file mode 100644 index 00000000000..6d076a7e079 --- /dev/null +++ b/src/test/skript/tests/misc/invalid unparsed literals.sk @@ -0,0 +1,6 @@ +test "invalid unparsed literals": + + parse: + loop 9 times: + set {_i} to loop-value - 1 + assert last parse logs is not set with "skript should be able to understand 'loop-value - 1' but did not" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprArithmetic.sk b/src/test/skript/tests/syntaxes/expressions/ExprArithmetic.sk index f035aecd41e..16a526d5f7e 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprArithmetic.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprArithmetic.sk @@ -255,3 +255,10 @@ local function component_equals(a: number, b: number) :: boolean: then: return true return true if {_a} is {_b}, else false + +test "arithmetic return types": + # the issue below is that <none> is now returned for the function + # skript interprets this as "y-coordinate of ({_location} - 4)" which is valid due to Object.class return types + # however, we can get more specific return types by returning the superclass of the return types of all Object-Number operations + set {_location} to location(0,10,0,"world") + assert (y-coordinate of {_location} - 4) is 6 with "y-coordinate of {_location} - 4 is not 6 (got '%y-coordinate of {_location} - 4%')" From 909a14d2b42d06ee5718c34f056c2d8df9f7cb65 Mon Sep 17 00:00:00 2001 From: APickledWalrus <apickledwalrus@gmail.com> Date: Thu, 1 Feb 2024 18:34:23 -0500 Subject: [PATCH 594/619] Prepare For Release (2.8.1) --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 4e9a1a994d7..b30bbc715e6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ org.gradle.parallel=true groupid=ch.njol name=skript -version=2.8.0 +version=2.8.1 jarName=Skript.jar testEnv=java17/paper-1.20.4 testEnvJavaVersion=17 From 3d1047deb85083c4ea9558462ef235897e4f09a3 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Sat, 3 Feb 2024 12:34:22 -0500 Subject: [PATCH 595/619] Fix loading on Spigot versions older than 1.18 (#6399) --- .../java/ch/njol/skript/aliases/AliasesProvider.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/aliases/AliasesProvider.java b/src/main/java/ch/njol/skript/aliases/AliasesProvider.java index 1c6e73184c4..c4fc7b4204a 100644 --- a/src/main/java/ch/njol/skript/aliases/AliasesProvider.java +++ b/src/main/java/ch/njol/skript/aliases/AliasesProvider.java @@ -21,10 +21,12 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Set; import java.util.List; import java.util.Map; +import ch.njol.skript.Skript; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; @@ -42,6 +44,9 @@ * Provides aliases on Bukkit/Spigot platform. */ public class AliasesProvider { + + // not supported on Spigot versions older than 1.18 + private static final boolean FASTER_SET_SUPPORTED = Skript.classExists("it.unimi.dsi.fastutil.objects.ObjectOpenHashSet"); /** * When an alias is not found, it will requested from this provider. @@ -173,7 +178,12 @@ public AliasesProvider(int expectedCount, @Nullable AliasesProvider parent) { this.aliases = new HashMap<>(expectedCount); this.variations = new HashMap<>(expectedCount / 20); this.aliasesMap = new AliasesMap(); - this.materials = new ObjectOpenHashSet<>(); + + if (FASTER_SET_SUPPORTED) { + this.materials = new ObjectOpenHashSet<>(); + } else { + this.materials = new HashSet<>(); + } this.gson = new Gson(); } From 0bfec0b709cf74bace063d315d0eb501058cac14 Mon Sep 17 00:00:00 2001 From: APickledWalrus <apickledwalrus@gmail.com> Date: Sat, 3 Feb 2024 12:55:13 -0500 Subject: [PATCH 596/619] Prepare For Release (2.8.2) --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index b30bbc715e6..5a5897881f4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ org.gradle.parallel=true groupid=ch.njol name=skript -version=2.8.1 +version=2.8.2 jarName=Skript.jar testEnv=java17/paper-1.20.4 testEnvJavaVersion=17 From 884641c11b6e69c10a276641227b678847993735 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Sun, 4 Feb 2024 22:36:45 +0100 Subject: [PATCH 597/619] Fix ExprPlain always getting the base material from ItemTypes (#6391) * Update ExprPlain.java * Add regression test * Update src/test/java/org/skriptlang/skript/test/tests/regression/ExprPlainAliasTest.java Co-authored-by: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> --------- Co-authored-by: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> --- .../ch/njol/skript/expressions/ExprPlain.java | 2 +- .../tests/regression/ExprPlainAliasTest.java | 62 +++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 src/test/java/org/skriptlang/skript/test/tests/regression/ExprPlainAliasTest.java diff --git a/src/main/java/ch/njol/skript/expressions/ExprPlain.java b/src/main/java/ch/njol/skript/expressions/ExprPlain.java index e0e7408c413..ca73ba22820 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprPlain.java +++ b/src/main/java/ch/njol/skript/expressions/ExprPlain.java @@ -61,7 +61,7 @@ protected ItemType[] get(Event e) { ItemType itemType = item.getSingle(e); if (itemType == null) return new ItemType[0]; - ItemData data = new ItemData(itemType.getMaterial()); + ItemData data = new ItemData(itemType.getRandom().getType()); data.setPlain(true); ItemType plain = new ItemType(data); return new ItemType[]{plain}; diff --git a/src/test/java/org/skriptlang/skript/test/tests/regression/ExprPlainAliasTest.java b/src/test/java/org/skriptlang/skript/test/tests/regression/ExprPlainAliasTest.java new file mode 100644 index 00000000000..bb462ba0817 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/regression/ExprPlainAliasTest.java @@ -0,0 +1,62 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.test.tests.regression; + +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.TriggerItem; +import ch.njol.skript.lang.util.ContextlessEvent; +import ch.njol.skript.test.runner.SkriptJUnitTest; +import ch.njol.skript.variables.Variables; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.easymock.EasyMock; +import org.jetbrains.annotations.Nullable; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +// https://github.com/SkriptLang/Skript/issues/5994 +public class ExprPlainAliasTest extends SkriptJUnitTest { + private ItemType itemType; + @Nullable + private Effect getPlainRandomItemEffect; + + @Before + public void setup() { + itemType = EasyMock.niceMock(ItemType.class); + getPlainRandomItemEffect = Effect.parse("set {_a} to plain {_item}", null); + } + + @Test + public void test() { + if (getPlainRandomItemEffect == null) + Assert.fail("Plain item effect is null"); + + ContextlessEvent event = ContextlessEvent.get(); + Variables.setVariable("item", itemType, event, true); + + EasyMock.expect(itemType.getRandom()).andReturn(new ItemStack(Material.STONE)).atLeastOnce(); + EasyMock.replay(itemType); + TriggerItem.walk(getPlainRandomItemEffect, event); + EasyMock.verify(itemType); + + } + +} From 3fbf72e97bfa764432cdd6db94485c051513910d Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Sun, 4 Feb 2024 23:11:45 +0100 Subject: [PATCH 598/619] Fix AIOOB with ExprVectorXYZ when used with non-vectors (#6387) fix aioob error and add regression test --- src/main/java/ch/njol/skript/expressions/ExprVectorXYZ.java | 5 ++++- .../skript/tests/regressions/6385-x component of block.sk | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 src/test/skript/tests/regressions/6385-x component of block.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorXYZ.java b/src/main/java/ch/njol/skript/expressions/ExprVectorXYZ.java index 2955fef5b97..9777166eb4d 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorXYZ.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorXYZ.java @@ -52,7 +52,8 @@ public class ExprVectorXYZ extends SimplePropertyExpression<Vector, Number> { static { - register(ExprVectorXYZ.class, Number.class, "[vector] (0¦x|1¦y|2¦z) [component[s]]", "vectors"); + // TODO: Combine with ExprCoordinates for 2.9 + register(ExprVectorXYZ.class, Number.class, "[vector] (0:x|1:y|2:z) [component[s]]", "vectors"); } private final static Character[] axes = new Character[] {'x', 'y', 'z'}; @@ -84,6 +85,8 @@ public Class<?>[] acceptChange(ChangeMode mode) { public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { assert delta != null; Vector[] vectors = getExpr().getArray(event); + if (vectors.length == 0) + return; double deltaValue = ((Number) delta[0]).doubleValue(); switch (mode) { case REMOVE: diff --git a/src/test/skript/tests/regressions/6385-x component of block.sk b/src/test/skript/tests/regressions/6385-x component of block.sk new file mode 100644 index 00000000000..b3be4d85c52 --- /dev/null +++ b/src/test/skript/tests/regressions/6385-x component of block.sk @@ -0,0 +1,3 @@ +test "x component of block": + set {_a} to block at location(0, 0, 0) + add 1 to x of {_a} # should not throw exception From 243281ecad5183ae63d1e36b65662cac2e8d30d8 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Wed, 14 Feb 2024 12:49:12 -0500 Subject: [PATCH 599/619] Fix Compilation Issues (Address DamageEvent Constructor Changes) (#6429) --- .../njol/skript/bukkitutil/HealthUtils.java | 33 +++++++++++++++++-- .../syntaxes/expressions/ExprLastDamageCause | 10 ++++++ 2 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprLastDamageCause diff --git a/src/main/java/ch/njol/skript/bukkitutil/HealthUtils.java b/src/main/java/ch/njol/skript/bukkitutil/HealthUtils.java index 9af9012eb7f..20b5120a0f1 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/HealthUtils.java +++ b/src/main/java/ch/njol/skript/bukkitutil/HealthUtils.java @@ -18,13 +18,21 @@ */ package ch.njol.skript.bukkitutil; +import ch.njol.skript.Skript; import ch.njol.util.Math2; import org.bukkit.attribute.Attributable; import org.bukkit.attribute.Attribute; import org.bukkit.attribute.AttributeInstance; +import org.bukkit.damage.DamageSource; +import org.bukkit.damage.DamageType; import org.bukkit.entity.Damageable; import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.entity.EntityDamageEvent.DamageCause; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; public class HealthUtils { @@ -107,9 +115,28 @@ public static double getFinalDamage(EntityDamageEvent e) { public static void setDamage(EntityDamageEvent e, double damage) { e.setDamage(damage * 2); } - + + @Nullable + private static final Constructor<EntityDamageEvent> OLD_DAMAGE_EVENT_CONSTRUCTOR; + + static { + Constructor<EntityDamageEvent> constructor = null; + try { + constructor = EntityDamageEvent.class.getConstructor(Damageable.class, DamageCause.class, double.class); + } catch (NoSuchMethodException ignored) { } + OLD_DAMAGE_EVENT_CONSTRUCTOR = constructor; + } + public static void setDamageCause(Damageable e, DamageCause cause) { - e.setLastDamageCause(new EntityDamageEvent(e, cause, 0)); + if (OLD_DAMAGE_EVENT_CONSTRUCTOR != null) { + try { + e.setLastDamageCause(OLD_DAMAGE_EVENT_CONSTRUCTOR.newInstance(e, cause, 0)); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException ex) { + Skript.exception("Failed to set last damage cause"); + } + } else { + e.setLastDamageCause(new EntityDamageEvent(e, cause, DamageSource.builder(DamageType.GENERIC).build(), 0)); + } } - + } diff --git a/src/test/skript/tests/syntaxes/expressions/ExprLastDamageCause b/src/test/skript/tests/syntaxes/expressions/ExprLastDamageCause new file mode 100644 index 00000000000..5a9ddf2de67 --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprLastDamageCause @@ -0,0 +1,10 @@ +test "last damage cause": + + spawn a pig at spawn of "world" + set {_pig} to last spawned pig + + assert last damage cause of {_pig} is custom with "expected pig to have custom damage cause, but it didn't" + set last damage cause of {_pig} to drowning + assert last damage cause of {_pig} is drowning with "expected pig to have drowning damage cause, but it didn't" + + delete the entity within {_pig} From 817b5a953fd4acea6d1cc344a94f84b38e5f0955 Mon Sep 17 00:00:00 2001 From: Moderocky <admin@moderocky.com> Date: Sat, 17 Feb 2024 21:50:40 +0000 Subject: [PATCH 600/619] Ask for player's groups off the main thread. (#6192) * Update Vault to 1.7.3 This version should support 1.13 to 1.17+ according to the Spigot page. I'm not sure what this actually adds but it's usually a good idea to keep dependencies updated and all 1.7.X versions of Vault are supposed to be compatible. :) * Do group request from another thread. This change is (basically) pointless -- we still block the main thread while we wait since we absolutely need to, but it prevents the errors from overzealous permissions plugins. * Invert parse tag. --------- Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- build.gradle | 2 +- .../permission/expressions/ExprGroup.java | 31 +++++++++++-------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/build.gradle b/build.gradle index 33f0399632c..92f13c47f46 100644 --- a/build.gradle +++ b/build.gradle @@ -33,7 +33,7 @@ dependencies { implementation group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.700' implementation group: 'com.google.code.findbugs', name: 'findbugs', version: '3.0.1' implementation group: 'com.sk89q.worldguard', name: 'worldguard-legacy', version: '7.0.0-SNAPSHOT' - implementation group: 'net.milkbowl.vault', name: 'Vault', version: '1.7.1', { + implementation group: 'net.milkbowl.vault', name: 'Vault', version: '1.7.3', { exclude group: 'org.bstats', module: 'bstats-bukkit' } diff --git a/src/main/java/ch/njol/skript/hooks/permission/expressions/ExprGroup.java b/src/main/java/ch/njol/skript/hooks/permission/expressions/ExprGroup.java index bd043c1c255..b3c12f9f38c 100644 --- a/src/main/java/ch/njol/skript/hooks/permission/expressions/ExprGroup.java +++ b/src/main/java/ch/njol/skript/hooks/permission/expressions/ExprGroup.java @@ -39,6 +39,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.CompletableFuture; @Name("Group") @Description({ @@ -53,7 +54,7 @@ public class ExprGroup extends SimpleExpression<String> { static { - PropertyExpression.register(ExprGroup.class, String.class, "group[(1¦s)]", "offlineplayers"); + PropertyExpression.register(ExprGroup.class, String.class, "group[plural:s]", "offlineplayers"); } private boolean primary; @@ -68,21 +69,25 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye return false; } players = (Expression<OfflinePlayer>) exprs[0]; - primary = parseResult.mark == 0; + primary = !parseResult.hasTag("plural"); return true; } @SuppressWarnings("null") @Override - protected String[] get(Event e) { - List<String> groups = new ArrayList<>(); - for (OfflinePlayer player : players.getArray(e)) { - if (primary) - groups.add(VaultHook.permission.getPrimaryGroup(null, player)); - else - Collections.addAll(groups, VaultHook.permission.getPlayerGroups(null, player)); - } - return groups.toArray(new String[0]); + protected String[] get(Event event) { + OfflinePlayer[] players = this.players.getArray(event); + return CompletableFuture.supplyAsync(() -> { // #5692: LuckPerms errors for vault requests on main thread + List<String> groups = new ArrayList<>(); + for (OfflinePlayer player : players) { + if (primary) { + groups.add(VaultHook.permission.getPrimaryGroup(null, player)); + } else { + Collections.addAll(groups, VaultHook.permission.getPlayerGroups(null, player)); + } + } + return groups.toArray(new String[0]); + }).join(); } @Override @@ -140,8 +145,8 @@ public Class<? extends String> getReturnType() { @SuppressWarnings("null") @Override - public String toString(Event e, boolean debug) { - return "group" + (primary ? "" : "s") + " of " + players.toString(e, debug); + public String toString(Event event, boolean debug) { + return "group" + (primary ? "" : "s") + " of " + players.toString(event, debug); } } From ba638923d311f68d4dfb765ea85296581fe99698 Mon Sep 17 00:00:00 2001 From: _tud <98935832+UnderscoreTud@users.noreply.github.com> Date: Sun, 18 Feb 2024 01:07:38 +0300 Subject: [PATCH 601/619] Fix Timespan Addition Overflow Exception (#6328) * Fix timespan addition * Fix timespan-number multiplication --------- Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- .../classes/data/DefaultOperations.java | 5 ++-- src/main/java/ch/njol/util/Math2.java | 25 +++++++++++++++++++ ...cause exceptions if there's an overflow.sk | 3 +++ 3 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 src/test/skript/tests/regressions/6328-timespan operations can cause exceptions if there's an overflow.sk diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java b/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java index d3f51b29bb9..e6397a1e767 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java @@ -21,6 +21,7 @@ import ch.njol.skript.util.Date; import ch.njol.skript.util.Timespan; import ch.njol.skript.util.Utils; +import ch.njol.util.Math2; import org.bukkit.util.Vector; import org.skriptlang.skript.lang.arithmetic.Arithmetics; import org.skriptlang.skript.lang.arithmetic.Operator; @@ -84,7 +85,7 @@ public class DefaultOperations { }); // Timespan - Timespan - Arithmetics.registerOperation(Operator.ADDITION, Timespan.class, (left, right) -> new Timespan(left.getMilliSeconds() + right.getMilliSeconds())); + Arithmetics.registerOperation(Operator.ADDITION, Timespan.class, (left, right) -> new Timespan(Math2.addClamped(left.getMilliSeconds(), right.getMilliSeconds()))); Arithmetics.registerOperation(Operator.SUBTRACTION, Timespan.class, (left, right) -> new Timespan(Math.max(0, left.getMilliSeconds() - right.getMilliSeconds()))); Arithmetics.registerDifference(Timespan.class, (left, right) -> new Timespan(Math.abs(left.getMilliSeconds() - right.getMilliSeconds()))); Arithmetics.registerDefaultValue(Timespan.class, Timespan::new); @@ -95,7 +96,7 @@ public class DefaultOperations { long scalar = right.longValue(); if (scalar < 0) return null; - return new Timespan(left.getMilliSeconds() * scalar); + return new Timespan(Math2.multiplyClamped(left.getMilliSeconds(), scalar)); }, (left, right) -> { long scalar = left.longValue(); if (scalar < 0) diff --git a/src/main/java/ch/njol/util/Math2.java b/src/main/java/ch/njol/util/Math2.java index c16018d341e..d17ac6910d1 100644 --- a/src/main/java/ch/njol/util/Math2.java +++ b/src/main/java/ch/njol/util/Math2.java @@ -155,6 +155,31 @@ public static long round(double value) { public static float safe(float value) { return Float.isFinite(value) ? value : 0; } + + /** + * @param x the first value + * @param y the second value + * @return the sum of x and y, or {@link Long#MAX_VALUE} in case of an overflow + */ + public static long addClamped(long x, long y) { + long result = x + y; + // Logic extracted from Math#addExact to avoid having to catch an expensive exception + boolean causedOverflow = ((x ^ result) & (y ^ result)) < 0; + if (causedOverflow) + return Long.MAX_VALUE; + return result; + } + + public static long multiplyClamped(long x, long y) { + long result = x * y; + long ax = Math.abs(x); + long ay = Math.abs(y); + // Logic extracted from Math#multiplyExact to avoid having to catch an expensive exception + if (((ax | ay) >>> 31 != 0) && (((y != 0) && (result / y != x)) || (x == Long.MIN_VALUE && y == -1))) + // If either x or y is negative return the min value, otherwise return the max value + return x < 0 == y < 0 ? Long.MAX_VALUE : Long.MIN_VALUE; + return result; + } @Deprecated @ScheduledForRemoval diff --git a/src/test/skript/tests/regressions/6328-timespan operations can cause exceptions if there's an overflow.sk b/src/test/skript/tests/regressions/6328-timespan operations can cause exceptions if there's an overflow.sk new file mode 100644 index 00000000000..1267fc7c081 --- /dev/null +++ b/src/test/skript/tests/regressions/6328-timespan operations can cause exceptions if there's an overflow.sk @@ -0,0 +1,3 @@ +test "timespan overflow exception": + assert 1000000000 years + 10000000 years is set with "timespan addition overflow did not return max value" + assert 1000000000 years * 10000000 is set with "timespan addition overflow did not return max value" \ No newline at end of file From 7f61018b6b9813b9c47b8410d48aab06a539e160 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Sat, 17 Feb 2024 23:13:26 +0100 Subject: [PATCH 602/619] Make function parameters respect the case-insensitive-variables setting. (#6388) * Update Parameter.java * Update Parameter.java --- .../ch/njol/skript/lang/function/Parameter.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/java/ch/njol/skript/lang/function/Parameter.java b/src/main/java/ch/njol/skript/lang/function/Parameter.java index dc61dee6f00..d8682220d77 100644 --- a/src/main/java/ch/njol/skript/lang/function/Parameter.java +++ b/src/main/java/ch/njol/skript/lang/function/Parameter.java @@ -19,6 +19,7 @@ package ch.njol.skript.lang.function; import ch.njol.skript.Skript; +import ch.njol.skript.SkriptConfig; import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ParseContext; @@ -45,8 +46,9 @@ public final class Parameter<T> { /** * Name of this parameter. Will be used as name for the local variable - * that contains value of it inside function. This is always in lower case; - * variable names are case-insensitive. + * that contains value of it inside function. + * If {@link SkriptConfig#caseInsensitiveVariables} is {@code true}, + * then the valid variable names may not necessarily match this string in casing. */ final String name; @@ -69,7 +71,7 @@ public final class Parameter<T> { @SuppressWarnings("null") public Parameter(String name, ClassInfo<T> type, boolean single, @Nullable Expression<? extends T> def) { - this.name = name != null ? name.toLowerCase(Locale.ENGLISH) : null; + this.name = name; this.type = type; this.def = def; this.single = single; @@ -119,6 +121,7 @@ public static <T> Parameter<T> newInstance(String name, ClassInfo<T> type, boole @Nullable public static List<Parameter<?>> parse(String args) { List<Parameter<?>> params = new ArrayList<>(); + boolean caseInsensitive = SkriptConfig.caseInsensitiveVariables.value(); int j = 0; for (int i = 0; i <= args.length(); i = SkriptParser.next(args, i, ParseContext.DEFAULT)) { if (i == -1) { @@ -138,8 +141,12 @@ public static List<Parameter<?>> parse(String args) { return null; } String paramName = "" + n.group(1); + // for comparing without affecting the original name, in case the config option for case insensitivity changes. + String lowerParamName = paramName.toLowerCase(Locale.ENGLISH); for (Parameter<?> p : params) { - if (p.name.toLowerCase(Locale.ENGLISH).equals(paramName.toLowerCase(Locale.ENGLISH))) { + // only force lowercase if we don't care about case in variables + String otherName = caseInsensitive ? p.name.toLowerCase(Locale.ENGLISH) : p.name; + if (otherName.equals(caseInsensitive ? lowerParamName : paramName)) { Skript.error("Each argument's name must be unique, but the name '" + paramName + "' occurs at least twice."); return null; } From a5485389448976146bf8d7709a517c862003b1a6 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Sat, 17 Feb 2024 23:28:47 +0100 Subject: [PATCH 603/619] Make bucket fill events provide the correct event-block. (#6392) * Fix Bucket event-block * Update src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java --- .../classes/data/BukkitEventValues.java | 4 +-- .../java/ch/njol/skript/events/EvtBlock.java | 36 +++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) 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 1998e124aa4..18d19ae8fff 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -712,14 +712,14 @@ public Block get(final PlayerBedLeaveEvent e) { @Override @Nullable public Block get(final PlayerBucketFillEvent e) { - return e.getBlockClicked().getRelative(e.getBlockFace()); + return e.getBlockClicked(); } }, 0); EventValues.registerEventValue(PlayerBucketFillEvent.class, Block.class, new Getter<Block, PlayerBucketFillEvent>() { @Override @Nullable public Block get(final PlayerBucketFillEvent e) { - final BlockState s = e.getBlockClicked().getRelative(e.getBlockFace()).getState(); + final BlockState s = e.getBlockClicked().getState(); s.setType(Material.AIR); s.setRawData((byte) 0); return new BlockStateBlock(s, true); diff --git a/src/main/java/ch/njol/skript/events/EvtBlock.java b/src/main/java/ch/njol/skript/events/EvtBlock.java index 98b94d7ee0f..4a393146600 100644 --- a/src/main/java/ch/njol/skript/events/EvtBlock.java +++ b/src/main/java/ch/njol/skript/events/EvtBlock.java @@ -90,9 +90,9 @@ public boolean init(final Literal<?>[] args, final int matchedPattern, final Par @SuppressWarnings("null") @Override - public boolean check(final Event e) { - if (mine && e instanceof BlockBreakEvent) { - if (((BlockBreakEvent) e).getBlock().getDrops(((BlockBreakEvent) e).getPlayer().getItemInHand()).isEmpty()) + public boolean check(final Event event) { + if (mine && event instanceof BlockBreakEvent) { + if (((BlockBreakEvent) event).getBlock().getDrops(((BlockBreakEvent) event).getPlayer().getItemInHand()).isEmpty()) return false; } if (types == null) @@ -101,27 +101,27 @@ public boolean check(final Event e) { ItemType item; BlockData blockData = null; - if (e instanceof BlockFormEvent) { - BlockFormEvent blockFormEvent = (BlockFormEvent) e; + if (event instanceof BlockFormEvent) { + BlockFormEvent blockFormEvent = (BlockFormEvent) event; BlockState newState = blockFormEvent.getNewState(); item = new ItemType(newState); blockData = newState.getBlockData(); - } else if (e instanceof BlockEvent) { - BlockEvent blockEvent = (BlockEvent) e; + } else if (event instanceof BlockEvent) { + BlockEvent blockEvent = (BlockEvent) event; Block block = blockEvent.getBlock(); item = new ItemType(block); blockData = block.getBlockData(); - } else if (e instanceof PlayerBucketFillEvent) { - PlayerBucketFillEvent playerBucketFillEvent = ((PlayerBucketFillEvent) e); - Block relative = playerBucketFillEvent.getBlockClicked().getRelative(playerBucketFillEvent.getBlockFace()); - item = new ItemType(relative); - blockData = relative.getBlockData(); - } else if (e instanceof PlayerBucketEmptyEvent) { - PlayerBucketEmptyEvent playerBucketEmptyEvent = ((PlayerBucketEmptyEvent) e); + } else if (event instanceof PlayerBucketFillEvent) { + PlayerBucketFillEvent playerBucketFillEvent = ((PlayerBucketFillEvent) event); + Block block = playerBucketFillEvent.getBlockClicked(); + item = new ItemType(block); + blockData = block.getBlockData(); + } else if (event instanceof PlayerBucketEmptyEvent) { + PlayerBucketEmptyEvent playerBucketEmptyEvent = ((PlayerBucketEmptyEvent) event); item = new ItemType(playerBucketEmptyEvent.getItemStack()); - } else if (e instanceof HangingEvent) { - final EntityData<?> d = EntityData.fromEntity(((HangingEvent) e).getEntity()); - return types.check(e, o -> { + } else if (event instanceof HangingEvent) { + final EntityData<?> d = EntityData.fromEntity(((HangingEvent) event).getEntity()); + return types.check(event, o -> { if (o instanceof ItemType) return Relation.EQUAL.isImpliedBy(DefaultComparators.entityItemComparator.compare(d, ((ItemType) o))); return false; @@ -134,7 +134,7 @@ public boolean check(final Event e) { final ItemType itemF = item; BlockData finalBlockData = blockData; - return types.check(e, o -> { + return types.check(event, o -> { if (o instanceof ItemType) return ((ItemType) o).isSupertypeOf(itemF); else if (o instanceof BlockData && finalBlockData != null) From 69eb15fa23922c48ce3d2f8a9ea27a087553bfa8 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Fri, 1 Mar 2024 18:48:52 +0100 Subject: [PATCH 604/619] Fix EvtAtTime not triggering when world time has been changed. (#6463) --- .../java/ch/njol/skript/events/EvtAtTime.java | 87 ++++++++++--------- 1 file changed, 47 insertions(+), 40 deletions(-) diff --git a/src/main/java/ch/njol/skript/events/EvtAtTime.java b/src/main/java/ch/njol/skript/events/EvtAtTime.java index 17d37f00c5c..f003b26195c 100644 --- a/src/main/java/ch/njol/skript/events/EvtAtTime.java +++ b/src/main/java/ch/njol/skript/events/EvtAtTime.java @@ -32,12 +32,10 @@ import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; -import java.util.ArrayList; -import java.util.Collections; import java.util.Iterator; -import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.PriorityQueue; import java.util.concurrent.ConcurrentHashMap; public class EvtAtTime extends SkriptEvent implements Comparable<EvtAtTime> { @@ -54,12 +52,19 @@ public class EvtAtTime extends SkriptEvent implements Comparable<EvtAtTime> { private static final Map<World, EvtAtInfo> TRIGGERS = new ConcurrentHashMap<>(); private static final class EvtAtInfo { - private int lastTick; // as Bukkit's scheduler is inconsistent this saves the exact tick when the events were last checked - private int currentIndex; - private final List<EvtAtTime> instances = new ArrayList<>(); + /** + * Stores the last world time that this object's instances were checked. + */ + private int lastCheckedTime; + + /** + * A list of all {@link EvtAtTime}s in the world this info object is responsible for. + * Sorted by the time they're listening for in increasing order. + */ + private final PriorityQueue<EvtAtTime> instances = new PriorityQueue<>(EvtAtTime::compareTo); } - private int tick; + private int time; @SuppressWarnings("NotNullFieldNotInitialized") private World[] worlds; @@ -67,7 +72,7 @@ private static final class EvtAtInfo { @Override @SuppressWarnings("unchecked") public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult) { - tick = ((Literal<Time>) args[0]).getSingle().getTicks(); + time = ((Literal<Time>) args[0]).getSingle().getTicks(); worlds = args[1] == null ? Bukkit.getWorlds().toArray(new World[0]) : ((Literal<World>) args[1]).getAll(); return true; } @@ -78,10 +83,9 @@ public boolean postLoad() { EvtAtInfo info = TRIGGERS.get(world); if (info == null) { TRIGGERS.put(world, info = new EvtAtInfo()); - info.lastTick = (int) world.getTime() - 1; + info.lastCheckedTime = (int) world.getTime() - 1; } info.instances.add(this); - Collections.sort(info.instances); } registerListener(); return true; @@ -93,13 +97,11 @@ public void unload() { while (iterator.hasNext()) { EvtAtInfo info = iterator.next(); info.instances.remove(this); - if (info.currentIndex >= info.instances.size()) - info.currentIndex--; if (info.instances.isEmpty()) iterator.remove(); } - if (taskID == -1 && TRIGGERS.isEmpty()) { // Unregister Bukkit listener if possible + if (taskID != -1 && TRIGGERS.isEmpty()) { // Unregister Bukkit listener if possible Bukkit.getScheduler().cancelTask(taskID); taskID = -1; } @@ -120,59 +122,64 @@ public boolean isEventPrioritySupported() { private static void registerListener() { if (taskID != -1) return; + // For each world: + // check each instance in order until triggerTime > (worldTime + period) taskID = Bukkit.getScheduler().scheduleSyncRepeatingTask(Skript.getInstance(), () -> { for (Entry<World, EvtAtInfo> entry : TRIGGERS.entrySet()) { EvtAtInfo info = entry.getValue(); - int tick = (int) entry.getKey().getTime(); + int worldTime = (int) entry.getKey().getTime(); // Stupid Bukkit scheduler - if (info.lastTick == tick) + // TODO: is this really necessary? + if (info.lastCheckedTime == worldTime) continue; // Check if time changed, e.g. by a command or plugin - if (info.lastTick + CHECK_PERIOD * 2 < tick || info.lastTick > tick && info.lastTick - 24000 + CHECK_PERIOD * 2 < tick) - info.lastTick = Math2.mod(tick - CHECK_PERIOD, 24000); + // if the info was last checked more than 2 cycles ago + // then reset the last checked time to the period just before now. + if (info.lastCheckedTime + CHECK_PERIOD * 2 < worldTime || (info.lastCheckedTime > worldTime && info.lastCheckedTime - 24000 + CHECK_PERIOD * 2 < worldTime)) + info.lastCheckedTime = Math2.mod(worldTime - CHECK_PERIOD, 24000); - boolean midnight = info.lastTick > tick; // actually 6:00 + // if we rolled over from 23999 to 0, subtract 24000 from last checked + boolean midnight = info.lastCheckedTime > worldTime; // actually 6:00 if (midnight) - info.lastTick -= 24000; + info.lastCheckedTime -= 24000; - int startIndex = info.currentIndex; - while (true) { - EvtAtTime next = info.instances.get(info.currentIndex); - int nextTick = midnight && next.tick > 12000 ? next.tick - 24000 : next.tick; + // loop instances from earliest to latest + for (EvtAtTime event : info.instances) { + // if we just rolled over, the last checked time will be x - 24000, so we need to do the same to the event time + int eventTime = midnight && event.time > 12000 ? event.time - 24000 : event.time; - if (!(info.lastTick < nextTick && nextTick <= tick)) + // if the event time is in the future, we don't need to check any more events. + if (eventTime > worldTime) break; - // Execute our event - ScheduledEvent event = new ScheduledEvent(entry.getKey()); - SkriptEventHandler.logEventStart(event); - SkriptEventHandler.logTriggerEnd(next.trigger); - next.trigger.execute(event); - SkriptEventHandler.logTriggerEnd(next.trigger); + // if we should have already caught this time previously, check the next one + if (eventTime <= info.lastCheckedTime) + continue; + + // anything that makes it here must satisfy lastCheckedTime < eventTime <= worldTime + // and therefore should trigger this event. + ScheduledEvent scheduledEvent = new ScheduledEvent(entry.getKey()); + SkriptEventHandler.logEventStart(scheduledEvent); + SkriptEventHandler.logTriggerEnd(event.trigger); + event.trigger.execute(scheduledEvent); + SkriptEventHandler.logTriggerEnd(event.trigger); SkriptEventHandler.logEventEnd(); - - info.currentIndex++; - if (info.currentIndex == info.instances.size()) - info.currentIndex = 0; - if (info.currentIndex == startIndex) // All events executed at once - break; } - - info.lastTick = tick; + info.lastCheckedTime = worldTime; } }, 0, CHECK_PERIOD); } @Override public String toString(@Nullable Event event, boolean debug) { - return "at " + Time.toString(tick) + " in worlds " + Classes.toString(worlds, true); + return "at " + Time.toString(time) + " in worlds " + Classes.toString(worlds, true); } @Override public int compareTo(@Nullable EvtAtTime event) { - return event == null ? tick : tick - event.tick; + return event == null ? time : time - event.time; } } From 51f0537df35e9ad9cccb66ce81999a60d4eeb1ab Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Fri, 1 Mar 2024 21:13:17 +0100 Subject: [PATCH 605/619] Fix duplicate calls of certain click events (#6309) --- src/main/java/ch/njol/skript/events/EvtClick.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main/java/ch/njol/skript/events/EvtClick.java b/src/main/java/ch/njol/skript/events/EvtClick.java index 9f43dddea42..3ad035b4e16 100644 --- a/src/main/java/ch/njol/skript/events/EvtClick.java +++ b/src/main/java/ch/njol/skript/events/EvtClick.java @@ -57,11 +57,6 @@ public class EvtClick extends SkriptEvent { */ public static final ClickEventTracker interactTracker = new ClickEventTracker(Skript.getInstance()); - /** - * Tracks PlayerInteractEntityEvents to deduplicate them. - */ - private static final ClickEventTracker entityInteractTracker = new ClickEventTracker(Skript.getInstance()); - static { Class<? extends PlayerEvent>[] eventTypes = CollectionUtils.array( PlayerInteractEvent.class, PlayerInteractEntityEvent.class, PlayerInteractAtEntityEvent.class @@ -147,7 +142,7 @@ public boolean check(Event event) { // PlayerInteractAtEntityEvent called only once for armor stands if (!(event instanceof PlayerInteractAtEntityEvent)) { - if (!entityInteractTracker.checkEvent(clickEvent.getPlayer(), clickEvent, clickEvent.getHand())) { + if (!interactTracker.checkEvent(clickEvent.getPlayer(), clickEvent, clickEvent.getHand())) { return false; // Not first event this tick } } From 20b89810629b8c602672968139f5a6390420474a Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Fri, 1 Mar 2024 15:21:37 -0500 Subject: [PATCH 606/619] Fix parsing errors caused by special characters (#6455) --- .../ch/njol/skript/lang/SkriptParser.java | 20 +++++++++---------- .../6246-special character parsing issues.sk | 4 ++++ 2 files changed, 14 insertions(+), 10 deletions(-) create mode 100644 src/test/skript/tests/regressions/6246-special character parsing issues.sk diff --git a/src/main/java/ch/njol/skript/lang/SkriptParser.java b/src/main/java/ch/njol/skript/lang/SkriptParser.java index f03c573e9ab..3d6a16b4eba 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptParser.java +++ b/src/main/java/ch/njol/skript/lang/SkriptParser.java @@ -1075,10 +1075,11 @@ static int countUnescaped(String haystack, char needle, int start, int end) { */ private static int nextQuote(String string, int start) { boolean inExpression = false; - for (int i = start; i < string.length(); i++) { + int length = string.length(); + for (int i = start; i < length; i++) { char character = string.charAt(i); if (character == '"' && !inExpression) { - if (i == string.length() - 1 || string.charAt(i + 1) != '"') + if (i == length - 1 || string.charAt(i + 1) != '"') return i; i++; } else if (character == '%') { @@ -1200,10 +1201,7 @@ public static int nextOccurrence(String haystack, String needle, int startIndex, if (startIndex >= haystackLength) return -1; - if (!caseSensitive) { - haystack = haystack.toLowerCase(Locale.ENGLISH); - needle = needle.toLowerCase(Locale.ENGLISH); - } + int needleLength = needle.length(); char firstChar = needle.charAt(0); boolean startsWithSpecialChar = firstChar == '"' || firstChar == '{' || firstChar == '('; @@ -1212,9 +1210,11 @@ public static int nextOccurrence(String haystack, String needle, int startIndex, char character = haystack.charAt(startIndex); - if (startsWithSpecialChar) { // Early check before special character handling - if (haystack.startsWith(needle, startIndex)) - return startIndex; + if ( // Early check before special character handling + startsWithSpecialChar && + haystack.regionMatches(!caseSensitive, startIndex, needle, 0, needleLength) + ) { + return startIndex; } switch (character) { @@ -1235,7 +1235,7 @@ public static int nextOccurrence(String haystack, String needle, int startIndex, break; } - if (haystack.startsWith(needle, startIndex)) + if (haystack.regionMatches(!caseSensitive, startIndex, needle, 0, needleLength)) return startIndex; startIndex++; diff --git a/src/test/skript/tests/regressions/6246-special character parsing issues.sk b/src/test/skript/tests/regressions/6246-special character parsing issues.sk new file mode 100644 index 00000000000..2e5f4105617 --- /dev/null +++ b/src/test/skript/tests/regressions/6246-special character parsing issues.sk @@ -0,0 +1,4 @@ +test "special character parsing issues": + parse: + send "İİ" to {_} + assert last parse logs is not set with "parsing failed when the code is valid" From 91059e0a70a69681fe88104d9c789a7a5d90f873 Mon Sep 17 00:00:00 2001 From: DelayedGaming <72163224+DelayedGaming@users.noreply.github.com> Date: Sat, 2 Mar 2024 04:26:32 +0800 Subject: [PATCH 607/619] Fix InventoryItemMoveEvent Implementations (#5462) (#6233) --- .../classes/data/BukkitEventValues.java | 34 ++++++++++++++ .../java/ch/njol/skript/events/EvtItem.java | 4 +- .../skript/expressions/ExprEvtInitiator.java | 42 +++++++++++------ .../syntaxes/InventoryMoveItemEventTest.java | 45 +++++++++++++++++++ .../skript/junit/InventoryMoveItemEvent.sk | 15 +++++++ 5 files changed, 126 insertions(+), 14 deletions(-) create mode 100644 src/test/java/org/skriptlang/skript/test/tests/syntaxes/InventoryMoveItemEventTest.java create mode 100644 src/test/skript/junit/InventoryMoveItemEvent.sk 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 18d19ae8fff..0c3175247b1 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -118,6 +118,7 @@ import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.event.inventory.InventoryDragEvent; +import org.bukkit.event.inventory.InventoryMoveItemEvent; import org.bukkit.event.inventory.InventoryOpenEvent; import org.bukkit.event.inventory.InventoryPickupItemEvent; import org.bukkit.event.inventory.PrepareAnvilEvent; @@ -1797,6 +1798,39 @@ public TransformReason get(EntityTransformEvent event) { return event.getTransformReason(); } }, EventValues.TIME_NOW); + + // InventoryMoveItemEvent + EventValues.registerEventValue(InventoryMoveItemEvent.class, Inventory.class, new Getter<Inventory, InventoryMoveItemEvent>() { + @Override + public Inventory get(InventoryMoveItemEvent event) { + return event.getSource(); + } + }, EventValues.TIME_NOW); + EventValues.registerEventValue(InventoryMoveItemEvent.class, Inventory.class, new Getter<Inventory, InventoryMoveItemEvent>() { + @Override + public Inventory get(InventoryMoveItemEvent event) { + return event.getDestination(); + } + }, EventValues.TIME_FUTURE); + EventValues.registerEventValue(InventoryMoveItemEvent.class, Block.class, new Getter<Block, InventoryMoveItemEvent>() { + @Override + public Block get(InventoryMoveItemEvent event) { + return event.getSource().getLocation().getBlock(); + } + }, EventValues.TIME_NOW); + EventValues.registerEventValue(InventoryMoveItemEvent.class, Block.class, new Getter<Block, InventoryMoveItemEvent>() { + @Override + public Block get(InventoryMoveItemEvent event) { + return event.getDestination().getLocation().getBlock(); + } + }, EventValues.TIME_FUTURE); + EventValues.registerEventValue(InventoryMoveItemEvent.class, ItemStack.class, new Getter<ItemStack, InventoryMoveItemEvent>() { + @Override + public ItemStack get(InventoryMoveItemEvent event) { + return event.getItem(); + } + }, EventValues.TIME_NOW); + } } diff --git a/src/main/java/ch/njol/skript/events/EvtItem.java b/src/main/java/ch/njol/skript/events/EvtItem.java index 98a23e29482..9051e175745 100644 --- a/src/main/java/ch/njol/skript/events/EvtItem.java +++ b/src/main/java/ch/njol/skript/events/EvtItem.java @@ -130,7 +130,9 @@ public class EvtItem extends SkriptEvent { .examples("on item merge of gold blocks:", " cancel event") .since("2.2-dev35"); - Skript.registerEvent("Inventory Item Move", SimpleEvent.class, InventoryMoveItemEvent.class, "inventory item (move|transport)") + Skript.registerEvent("Inventory Item Move", SimpleEvent.class, InventoryMoveItemEvent.class, + "inventory item (move|transport)", + "inventory (mov(e|ing)|transport[ing]) [an] item") .description( "Called when an entity or block (e.g. hopper) tries to move items directly from one inventory to another.", "When this event is called, the initiator may have already removed the item from the source inventory and is ready to move it into the destination inventory.", diff --git a/src/main/java/ch/njol/skript/expressions/ExprEvtInitiator.java b/src/main/java/ch/njol/skript/expressions/ExprEvtInitiator.java index fce9d5c1e2d..ca98a0b5a62 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprEvtInitiator.java +++ b/src/main/java/ch/njol/skript/expressions/ExprEvtInitiator.java @@ -18,37 +18,36 @@ */ package ch.njol.skript.expressions; -import org.bukkit.event.inventory.InventoryMoveItemEvent; -import org.bukkit.inventory.Inventory; - import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Events; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; -import ch.njol.skript.expressions.base.EventValueExpression; 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 org.bukkit.event.Event; +import org.bukkit.event.inventory.InventoryMoveItemEvent; +import org.bukkit.inventory.Inventory; +import org.eclipse.jdt.annotation.Nullable; @Name("Initiator Inventory") @Description("Returns the initiator inventory in an on <a href=\"./events.html?search=#inventory_item_move\">inventory item move</a> event.") @Examples({ "on inventory item move:", "\tholder of event-initiator-inventory is a chest", - "\tbroadcast \"Item transport requested at %location at holder of event-initiator-inventory%...\"" + "\tbroadcast \"Item transport happening at %location at holder of event-initiator-inventory%!\"" }) @Events("Inventory Item Move") @Since("2.8.0") -public class ExprEvtInitiator extends EventValueExpression<Inventory> { +public class ExprEvtInitiator extends SimpleExpression<Inventory> { static { - register(ExprEvtInitiator.class, Inventory.class, "[event-]initiator[( |-)inventory]"); - } - - public ExprEvtInitiator() { - super(Inventory.class); + Skript.registerExpression(ExprEvtInitiator.class, Inventory.class, ExpressionType.SIMPLE, "[the] [event-]initiator[( |-)inventory]"); } @Override @@ -57,11 +56,28 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye Skript.error("'event-initiator' can only be used in an 'inventory item move' event."); return false; } - return super.init(exprs, matchedPattern, isDelayed, parseResult); + return true; + } + + @Override + protected Inventory[] get(Event event) { + if (!(event instanceof InventoryMoveItemEvent)) + return new Inventory[0]; + return CollectionUtils.array(((InventoryMoveItemEvent) event).getInitiator()); + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class<? extends Inventory> getReturnType() { + return Inventory.class; } @Override - public String toString() { + public String toString(@Nullable Event event, boolean debug) { return "event-initiator-inventory"; } diff --git a/src/test/java/org/skriptlang/skript/test/tests/syntaxes/InventoryMoveItemEventTest.java b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/InventoryMoveItemEventTest.java new file mode 100644 index 00000000000..fc486687f85 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/InventoryMoveItemEventTest.java @@ -0,0 +1,45 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.test.tests.syntaxes; + +import ch.njol.skript.test.runner.SkriptJUnitTest; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.event.inventory.InventoryMoveItemEvent; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.junit.Test; + +// TODO properly add test after merge of https://github.com/SkriptLang/Skript/pull/6261 +public class InventoryMoveItemEventTest extends SkriptJUnitTest { + + static { + setShutdownDelay(1); + } + + @Test + public void test() { + Inventory chestInventory = Bukkit.createInventory(null, InventoryType.CHEST); + ItemStack itemStack = new ItemStack(Material.STONE); + Inventory hopperInventory = Bukkit.createInventory(null, InventoryType.HOPPER); + Bukkit.getPluginManager().callEvent(new InventoryMoveItemEvent(chestInventory, itemStack, hopperInventory, false)); + } + +} diff --git a/src/test/skript/junit/InventoryMoveItemEvent.sk b/src/test/skript/junit/InventoryMoveItemEvent.sk new file mode 100644 index 00000000000..e2ad9bdde8b --- /dev/null +++ b/src/test/skript/junit/InventoryMoveItemEvent.sk @@ -0,0 +1,15 @@ + +# TODO properly add test after merge of https://github.com/SkriptLang/Skript/pull/6261 +options: + junit: org.skriptlang.skript.test.tests.syntaxes.InventoryMoveItemEventTest + objective: inventory move item event success + +on script load: + ensure junit test "{@junit}" completes "{@objective}" + +on inventory item move: + assert type of event-initiator-inventory is a hopper inventory with "event-initiator-inventory should be a hopper inventory, but got %type of event-initiator-inventory%" + assert type of event-inventory is a chest inventory with "past event-inventory should be a chest inventory, but got %type of past event-inventory%" + assert type of future event-inventory is a hopper inventory with "event-inventory should be a hopper inventory, but got %type of event-inventory%" + assert type of event-item is a stone with "event-item should be a stone, but got %type of event-item%" + complete objective "{@objective}" for junit test "{@junit}" From 11327579a9af912221ba6e0864d937935ead8799 Mon Sep 17 00:00:00 2001 From: Patrick Miller <apickledwalrus@gmail.com> Date: Fri, 1 Mar 2024 16:18:56 -0500 Subject: [PATCH 608/619] Prepare For Release (2.8.3) (#6467) --- gradle.properties | 2 +- skript-aliases | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 5a5897881f4..e31c5a65002 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ org.gradle.parallel=true groupid=ch.njol name=skript -version=2.8.2 +version=2.8.3 jarName=Skript.jar testEnv=java17/paper-1.20.4 testEnvJavaVersion=17 diff --git a/skript-aliases b/skript-aliases index c6a515a9e4e..490bbeadf6e 160000 --- a/skript-aliases +++ b/skript-aliases @@ -1 +1 @@ -Subproject commit c6a515a9e4e249c2859f4cc2992bf0da559b63cc +Subproject commit 490bbeadf6e44e26dd436acfd191dae5b740ebe6 From 0cc9f980737488894657b00fe65a23226cf505ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 9 Mar 2024 15:29:18 -0500 Subject: [PATCH 609/619] Bump org.gradle.toolchains.foojay-resolver-convention from 0.7.0 to 0.8.0 (#6332) --- settings.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle b/settings.gradle index e5432e8214f..78b185aca4c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,6 +1,6 @@ plugins { - id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0' + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0' } rootProject.name = 'Skript' From d864baa7c979e5499c745bd89684de0e99979117 Mon Sep 17 00:00:00 2001 From: Shane Bee <shanebolenback@me.com> Date: Sat, 9 Mar 2024 12:37:53 -0800 Subject: [PATCH 610/619] EntityData - check if entity can spawn (#6484) --- .../ch/njol/skript/entity/EntityData.java | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/njol/skript/entity/EntityData.java b/src/main/java/ch/njol/skript/entity/EntityData.java index 435415b8c91..2b4fbd1947e 100644 --- a/src/main/java/ch/njol/skript/entity/EntityData.java +++ b/src/main/java/ch/njol/skript/entity/EntityData.java @@ -37,6 +37,7 @@ import org.bukkit.RegionAccessor; import org.bukkit.World; import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import org.eclipse.jdt.annotation.Nullable; @@ -94,6 +95,7 @@ public abstract class EntityData<E extends Entity> implements SyntaxElement, Ygg } catch (NoSuchMethodException | SecurityException ignored) { /* We already checked if the method exists */ } } + private static final boolean HAS_ENABLED_BY_FEATURE = Skript.methodExists(EntityType.class, "isEnabledByFeature", World.class); public final static String LANGUAGE_NODE = "entities"; public final static Message m_age_pattern = new Message(LANGUAGE_NODE + ".age pattern"); @@ -463,6 +465,25 @@ private E apply(E entity) { return entity; } + /** + * Check if this entity type can spawn. + * <p>Some entity types may be restricted by experimental datapacks.</p> + * + * @param world World to check if entity can spawn in + * @return True if entity can spawn else false + */ + public boolean canSpawn(@Nullable World world) { + if (world == null) + return false; + if (HAS_ENABLED_BY_FEATURE) { + // Check if the entity can actually be spawned + // Some entity types may be restricted by experimental datapacks + EntityType bukkitEntityType = EntityUtils.toBukkitEntityType(this); + return bukkitEntityType.isEnabledByFeature(world); + } + return true; + } + /** * Spawn this entity data at a location. * @@ -476,7 +497,7 @@ public final E spawn(Location location) { /** * Spawn this entity data at a location. - * The consumer allows for modiciation to the entity before it actually gets spawned. + * The consumer allows for modification to the entity before it actually gets spawned. * <p> * Bukkit's own {@link org.bukkit.util.Consumer} is deprecated. * Use {@link #spawn(Location, Consumer)} @@ -494,7 +515,7 @@ public E spawn(Location location, org.bukkit.util.@Nullable Consumer<E> consumer /** * Spawn this entity data at a location. - * The consumer allows for modiciation to the entity before it actually gets spawned. + * The consumer allows for modification to the entity before it actually gets spawned. * * @param location The {@link Location} to spawn the entity at. * @param consumer A {@link Consumer} to apply the entity changes to. @@ -503,10 +524,13 @@ public E spawn(Location location, org.bukkit.util.@Nullable Consumer<E> consumer @Nullable public E spawn(Location location, @Nullable Consumer<E> consumer) { assert location != null; + World world = location.getWorld(); + if (!canSpawn(world)) + return null; if (consumer != null) { return EntityData.spawn(location, getType(), e -> consumer.accept(this.apply(e))); } else { - return apply(location.getWorld().spawn(location, getType())); + return apply(world.spawn(location, getType())); } } From a44ed2fad68bd2618be040642e8a2273a3514cc5 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Tue, 19 Mar 2024 10:21:24 -0600 Subject: [PATCH 611/619] Fixes entitydata consumer on versions 1.20.1 and below. (#6475) * Fix entitydata consumer on 1.20.1 and older * Update aliases to master --------- Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- src/main/java/ch/njol/skript/entity/EntityData.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/java/ch/njol/skript/entity/EntityData.java b/src/main/java/ch/njol/skript/entity/EntityData.java index 2b4fbd1947e..9cf1f427fd7 100644 --- a/src/main/java/ch/njol/skript/entity/EntityData.java +++ b/src/main/java/ch/njol/skript/entity/EntityData.java @@ -670,15 +670,18 @@ public void deserialize(final Fields fields) throws StreamCorruptedException, No protected boolean deserialize(final String s) { return false; } - + @SuppressWarnings({"unchecked", "deprecation"}) protected static <E extends Entity> @Nullable E spawn(Location location, Class<E> type, Consumer<E> consumer) { + World world = location.getWorld(); + if (world == null) + return null; try { if (WORLD_1_17_CONSUMER) { - return (@Nullable E) WORLD_1_17_CONSUMER_METHOD.invoke(location.getWorld(), location, type, + return (@Nullable E) WORLD_1_17_CONSUMER_METHOD.invoke(world, location, type, (org.bukkit.util.Consumer<E>) consumer::accept); } else if (WORLD_1_13_CONSUMER) { - return (@Nullable E) WORLD_1_13_CONSUMER_METHOD.invoke(location.getWorld(), location, type, + return (@Nullable E) WORLD_1_13_CONSUMER_METHOD.invoke(world, location, type, (org.bukkit.util.Consumer<E>) consumer::accept); } } catch (InvocationTargetException | IllegalAccessException e) { @@ -686,7 +689,7 @@ protected boolean deserialize(final String s) { Skript.exception(e, "Can't spawn " + type.getName()); return null; } - return location.getWorld().spawn(location, type, consumer); + return world.spawn(location, type, consumer); } - + } From 7a79d8733857c6dc66a7c3c3065fdc1622b8cf70 Mon Sep 17 00:00:00 2001 From: Shane Bee <shanebolenback@me.com> Date: Tue, 26 Mar 2024 14:54:37 -0700 Subject: [PATCH 612/619] Examples Update (#6510) * ExprAnvilText - fix docs * DefaultFunctions - better location examples --------- Co-authored-by: Moderocky <admin@moderocky.com> --- .../skript/classes/data/DefaultFunctions.java | 26 ++++++++++++++++--- .../skript/expressions/ExprAnvilText.java | 2 +- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java b/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java index 556f396b80b..e2c5392dd39 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java @@ -376,10 +376,28 @@ public Location[] execute(FunctionEvent<?> e, Object[][] params) { } }.description("Creates a location from a world and 3 coordinates, with an optional yaw and pitch.", "If for whatever reason the world is not found, it will fallback to the server's main world.") - .examples("location(0, 128, 0)", - "location(player's x-coordinate, player's y-coordinate + 5, player's z-coordinate, player's world, 0, 90)", - "location(0, 64, 0, world \"world_nether\")", - "location(100, 110, -145, world(\"my_custom_world\"))") + .examples("# TELEPORTING", + "teleport player to location(1,1,1, world \"world\")", + "teleport player to location(1,1,1, world \"world\", 100, 0)", + "teleport player to location(1,1,1, world \"world\", yaw of player, pitch of player)", + "teleport player to location(1,1,1, world of player)", + "teleport player to location(1,1,1, world(\"world\"))", + "teleport player to location({_x}, {_y}, {_z}, {_w}, {_yaw}, {_pitch})", + "# SETTING BLOCKS", + "set block at location(1,1,1, world \"world\") to stone", + "set block at location(1,1,1, world \"world\", 100, 0) to stone", + "set block at location(1,1,1, world of player) to stone", + "set block at location(1,1,1, world(\"world\")) to stone", + "set block at location({_x}, {_y}, {_z}, {_w}) to stone", + "# USING VARIABLES", + "set {_l1} to location(1,1,1)", + "set {_l2} to location(10,10,10)", + "set blocks within {_l1} and {_l2} to stone", + "if player is within {_l1} and {_l2}:", + "# OTHER", + "kill all entities in radius 50 around location(1,65,1, world \"world\")", + "delete all entities in radius 25 around location(50,50,50, world \"world_nether\")", + "ignite all entities in radius 25 around location(1,1,1, world of player)") .since("2.2")); Functions.registerFunction(new SimpleJavaFunction<Date>("date", new Parameter[] { diff --git a/src/main/java/ch/njol/skript/expressions/ExprAnvilText.java b/src/main/java/ch/njol/skript/expressions/ExprAnvilText.java index acc05ae124c..15490749007 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprAnvilText.java +++ b/src/main/java/ch/njol/skript/expressions/ExprAnvilText.java @@ -35,7 +35,7 @@ @Examples({ "on inventory click:", "\ttype of event-inventory is anvil inventory", - "\tif the anvil input text of the event-inventory is \"FREE OP\":", + "\tif the anvil text input of the event-inventory is \"FREE OP\":", "\t\tban player" }) @Since("2.7") From 86658cf4712edc2af698057a2e32ea2c4677da0a Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Wed, 27 Mar 2024 19:04:28 +0100 Subject: [PATCH 613/619] Handle invalid amounts in ExprRandomCharacter (#6502) * protect against bad amounts * Update ExprCharacters.sk .% * switch to Integer instead of Number and let chaos reign --------- Co-authored-by: Moderocky <admin@moderocky.com> --- .../skript/expressions/ExprRandomCharacter.java | 13 ++++++++----- .../tests/syntaxes/expressions/ExprCharacters.sk | 8 ++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprRandomCharacter.java b/src/main/java/ch/njol/skript/expressions/ExprRandomCharacter.java index 32afa678c42..13660c7ccd8 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprRandomCharacter.java +++ b/src/main/java/ch/njol/skript/expressions/ExprRandomCharacter.java @@ -50,17 +50,17 @@ public class ExprRandomCharacter extends SimpleExpression<String> { static { Skript.registerExpression(ExprRandomCharacter.class, String.class, ExpressionType.COMBINED, - "[a|%-number%] random [:alphanumeric] character[s] (from|between) %string% (to|and) %string%"); + "[a|%-integer%] random [:alphanumeric] character[s] (from|between) %string% (to|and) %string%"); } @Nullable - private Expression<Number> amount; + private Expression<Integer> amount; private Expression<String> from, to; private boolean isAlphanumeric; @Override public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - amount = (Expression<Number>) exprs[0]; + amount = (Expression<Integer>) exprs[0]; from = (Expression<String>) exprs[1]; to = (Expression<String>) exprs[2]; isAlphanumeric = parseResult.hasTag("alphanumeric"); @@ -70,7 +70,10 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable protected String[] get(Event event) { - int amount = this.amount == null ? 1 : this.amount.getOptionalSingle(event).orElse(1).intValue(); + Integer amount = this.amount == null ? Integer.valueOf(1) : this.amount.getSingle(event); + if (amount == null || amount <= 0) + return new String[0]; + String from = this.from.getSingle(event); String to = this.to.getSingle(event); if (from == null || to == null) @@ -117,7 +120,7 @@ protected String[] get(Event event) { @Override public boolean isSingle() { if (amount instanceof Literal) - return ((Literal<Number>) amount).getSingle().intValue() == 1; + return ((Literal<Integer>) amount).getSingle() == 1; return amount == null; } diff --git a/src/test/skript/tests/syntaxes/expressions/ExprCharacters.sk b/src/test/skript/tests/syntaxes/expressions/ExprCharacters.sk index 5c57a474378..0805fb13d85 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprCharacters.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprCharacters.sk @@ -1,5 +1,13 @@ test "characters between": set {_a} to 1 random character between "a" and "z" + assert {_a} is set with "failed to generate random character" + + set {_a::*} to -10 random character between "a" and "z" + assert {_a::*} is not set with "-10 random characters returned non-null value: %{_a::*}%" + + set {_a::*} to {_null} random character between "a" and "z" + assert {_a::*} is not set with "null random characters returned non-null value: %{_a::*}%" + assert join characters between "a" and "d" is "abcd" with "Invalid characters between a and d" assert join characters between "d" and "a" is "dcba" with "Invalid characters between d and a" assert join characters between "Z" and "a" is "Z[\]^_`a" with "Invalid characters between Z and a" From 9b96c8e8dcf34f44c7e3e1e35db1d1edadf02375 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Wed, 27 Mar 2024 21:14:06 +0300 Subject: [PATCH 614/619] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Fix=20ExprRando?= =?UTF-8?q?m=20missing=20canInitSafely=20(#6512)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Moderocky <admin@moderocky.com> --- src/main/java/ch/njol/skript/expressions/ExprRandom.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprRandom.java b/src/main/java/ch/njol/skript/expressions/ExprRandom.java index c3a87f7e957..5ab7e45996a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprRandom.java +++ b/src/main/java/ch/njol/skript/expressions/ExprRandom.java @@ -78,9 +78,7 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye } else { expr = exprs[1].getConvertedExpression((((Literal<ClassInfo<?>>) exprs[0]).getSingle()).getC()); } - if (expr == null) - return false; - return true; + return expr != null && LiteralUtils.canInitSafely(expr); } @Override From 6fe77c8f5118704d26df0791ee124bd3ce59f6bd Mon Sep 17 00:00:00 2001 From: Shane Bee <shanebolenback@me.com> Date: Fri, 29 Mar 2024 02:35:54 -0700 Subject: [PATCH 615/619] Add MC 1.20.5 Attributes (#6413) default.lang - add upcoming 1.20.5 attributes Co-authored-by: Moderocky <admin@moderocky.com> --- src/main/resources/lang/default.lang | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index efd0651babf..2ac67709dc4 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -1934,12 +1934,21 @@ attribute types: generic_attack_speed: generic attack speed, attack speed generic_flying_speed: generic flying speed, flying speed generic_follow_range: generic follow range, follow range + generic_gravity: generic gravity, gravity + generic_jump_strength: generic jump strength, jump strength generic_knockback_resistance: generic knockback resistance, knockback resistance generic_luck: generic luck, luck generic_max_absorption: generic max absorption, max absorption generic_max_health: generic max health, max health generic_movement_speed: generic movement speed, movement speed + generic_safe_fall_distance: generic safe fall distance, safe fall distance + generic_fall_damage_multiplier: generic fall damage multiplier, fall damage multiplier + generic_scale: generic scale, scale + generic_step_height: generic step height, step height 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 zombie_spawn_reinforcements: zombie spawn reinforcements # -- Environments -- From e3d357b8fb302033b9a83c469eb2c4982a22fac6 Mon Sep 17 00:00:00 2001 From: Shane Bee <shanebolenback@me.com> Date: Sat, 30 Mar 2024 10:38:56 -0700 Subject: [PATCH 616/619] ItemData/BlockCompat - strip out BlockState middleman (#6473) * ItemData/BlockCompat - strip out BlockState middleman * ItemData/BlockCompat - add stuff back, but deprecate * ItemData/BlockCompat - Change a few things around * NewBlockCompat - getBlock at entity instead of forcing a chunk to load * Update src/main/java/ch/njol/skript/aliases/ItemType.java Co-authored-by: Patrick Miller <apickledwalrus@gmail.com> --------- Co-authored-by: Patrick Miller <apickledwalrus@gmail.com> Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> Co-authored-by: Moderocky <admin@moderocky.com> --- .../java/ch/njol/skript/aliases/ItemData.java | 19 ++++++++--- .../java/ch/njol/skript/aliases/ItemType.java | 33 ++++++++++++++----- .../ch/njol/skript/bukkitutil/ItemUtils.java | 4 ++- .../skript/bukkitutil/block/BlockCompat.java | 19 +++++++---- .../bukkitutil/block/NewBlockCompat.java | 26 ++++++++++----- .../njol/skript/entity/FallingBlockData.java | 4 +-- .../java/ch/njol/skript/events/EvtBlock.java | 2 +- .../java/ch/njol/skript/events/EvtGrow.java | 4 +-- 8 files changed, 76 insertions(+), 35 deletions(-) diff --git a/src/main/java/ch/njol/skript/aliases/ItemData.java b/src/main/java/ch/njol/skript/aliases/ItemData.java index b6f90b30f42..32323c57e6f 100644 --- a/src/main/java/ch/njol/skript/aliases/ItemData.java +++ b/src/main/java/ch/njol/skript/aliases/ItemData.java @@ -31,6 +31,7 @@ import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.block.BlockState; +import org.bukkit.block.data.BlockData; import org.bukkit.enchantments.Enchantment; import org.bukkit.inventory.ItemFactory; import org.bukkit.inventory.ItemFlag; @@ -188,15 +189,23 @@ public ItemData(ItemStack stack) { this(stack, BlockCompat.INSTANCE.getBlockValues(stack)); this.itemForm = true; } - - public ItemData(BlockState block) { - this.type = ItemUtils.asItem(block.getType()); + + /** + * @deprecated Use {@link ItemData#ItemData(BlockData)} instead + */ + @Deprecated + public ItemData(BlockState blockState) { + this(blockState.getBlockData()); + } + + public ItemData(BlockData blockData) { + this.type = blockData.getMaterial(); this.stack = new ItemStack(type); - this.blockValues = BlockCompat.INSTANCE.getBlockValues(block); + this.blockValues = BlockCompat.INSTANCE.getBlockValues(blockData); } public ItemData(Block block) { - this(block.getState()); + this(block.getBlockData()); } /** diff --git a/src/main/java/ch/njol/skript/aliases/ItemType.java b/src/main/java/ch/njol/skript/aliases/ItemType.java index 6b56e9cee17..7341667fa91 100644 --- a/src/main/java/ch/njol/skript/aliases/ItemType.java +++ b/src/main/java/ch/njol/skript/aliases/ItemType.java @@ -43,6 +43,7 @@ import org.bukkit.block.Block; import org.bukkit.block.BlockState; import org.bukkit.block.Skull; +import org.bukkit.block.data.BlockData; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.Player; import org.bukkit.inventory.Inventory; @@ -184,10 +185,16 @@ public ItemType(ItemStack i) { add_(new ItemData(i)); } - public ItemType(BlockState b) { -// amount = 1; - add_(new ItemData(b)); - // TODO metadata - spawners, skulls, etc. + /** + * @deprecated Use {@link #ItemType(BlockData)} instead + */ + @Deprecated + public ItemType(BlockState blockState) { + this(blockState.getBlockData()); + } + + public ItemType(BlockData blockData) { + add_(new ItemData(blockData)); } /** @@ -211,7 +218,7 @@ public void setTo(ItemType i) { } public ItemType(Block block) { - this(block.getState()); + this(block.getBlockData()); } /** @@ -272,17 +279,25 @@ public boolean isOfType(@Nullable ItemStack item) { return isOfType(new ItemData(item)); } - public boolean isOfType(@Nullable BlockState block) { - if (block == null) + /** + * @deprecated Use {@link #isOfType(BlockData)} instead + */ + @Deprecated + public boolean isOfType(@Nullable BlockState blockState) { + return blockState != null && isOfType(blockState.getBlockData()); + } + + public boolean isOfType(@Nullable BlockData blockData) { + if (blockData == null) return isOfType(Material.AIR, null); - return isOfType(new ItemData(block)); + return isOfType(new ItemData(blockData)); } public boolean isOfType(@Nullable Block block) { if (block == null) return isOfType(Material.AIR, null); - return isOfType(block.getState()); + return isOfType(block.getBlockData()); } public boolean isOfType(ItemData type) { diff --git a/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java b/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java index ca373598eff..c6d5f25870e 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java +++ b/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java @@ -74,13 +74,15 @@ public static Material asBlock(Material type) { return null; } } - + /** * Gets an item material corresponding to given block material, which might * be the given material. * @param type Material. * @return Item version of material or null. + * @deprecated This just returns itself and has no use */ + @Deprecated public static Material asItem(Material type) { // Assume (naively) that all types are valid items return type; diff --git a/src/main/java/ch/njol/skript/bukkitutil/block/BlockCompat.java b/src/main/java/ch/njol/skript/bukkitutil/block/BlockCompat.java index 5ffb1063284..eef0866a01b 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/block/BlockCompat.java +++ b/src/main/java/ch/njol/skript/bukkitutil/block/BlockCompat.java @@ -23,11 +23,11 @@ import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.block.BlockState; +import org.bukkit.block.data.BlockData; import org.bukkit.entity.FallingBlock; import org.bukkit.inventory.ItemStack; import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.Skript; import ch.njol.skript.aliases.ItemFlags; /** @@ -42,13 +42,15 @@ public interface BlockCompat { BlockCompat INSTANCE = new NewBlockCompat(); static final BlockSetter SETTER = INSTANCE.getSetter(); - + /** * Gets block values from a block state. They can be compared to other * values if needed, but cannot be used to retrieve any other data. * @param block Block state to retrieve value from. * @return Block values. + * @deprecated Use {@link #getBlockValues(BlockData)} instead */ + @Deprecated @Nullable BlockValues getBlockValues(BlockState block); @@ -60,8 +62,11 @@ public interface BlockCompat { */ @Nullable default BlockValues getBlockValues(Block block) { - return getBlockValues(block.getState()); + return getBlockValues(block.getBlockData()); } + + @Nullable + BlockValues getBlockValues(BlockData blockData); /** * Gets block values from a item stack. They can be compared to other values @@ -71,17 +76,19 @@ default BlockValues getBlockValues(Block block) { */ @Nullable BlockValues getBlockValues(ItemStack stack); - + /** * Creates a block state from a falling block. * @param entity Falling block entity * @return Block state. + * @deprecated This shouldn't be used */ + @Deprecated BlockState fallingBlockToState(FallingBlock entity); - + @Nullable default BlockValues getBlockValues(FallingBlock entity) { - return getBlockValues(fallingBlockToState(entity)); + return getBlockValues(entity.getBlockData()); } /** diff --git a/src/main/java/ch/njol/skript/bukkitutil/block/NewBlockCompat.java b/src/main/java/ch/njol/skript/bukkitutil/block/NewBlockCompat.java index 0610022d1c9..2a0ecd36da8 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/block/NewBlockCompat.java +++ b/src/main/java/ch/njol/skript/bukkitutil/block/NewBlockCompat.java @@ -325,16 +325,23 @@ public void sendBlockChange(Player player, Location location, Material type, @Nu } private NewBlockSetter setter = new NewBlockSetter(); - + + /** + * @deprecated Use {@link #getBlockValues(BlockData)} instead + */ + @Deprecated @Nullable @Override - public BlockValues getBlockValues(BlockState block) { - // If block doesn't have useful data, data field of type is MaterialData - if (block.getType().isBlock()) - return new NewBlockValues(block.getType(), block.getBlockData(), false); - return null; + public BlockValues getBlockValues(BlockState blockState) { + return getBlockValues(blockState.getBlockData()); } - + + @Nullable + @Override + public BlockValues getBlockValues(BlockData blockData) { + return new NewBlockValues(blockData.getMaterial(), blockData, false); + } + @Override @Nullable public BlockValues getBlockValues(ItemStack stack) { @@ -345,15 +352,16 @@ public BlockValues getBlockValues(ItemStack stack) { } return null; } - + @Override public BlockSetter getSetter() { return setter; } + @Deprecated @Override public BlockState fallingBlockToState(FallingBlock entity) { - BlockState state = entity.getWorld().getBlockAt(0, 0, 0).getState(); + BlockState state = entity.getLocation().getBlock().getState(); state.setBlockData(entity.getBlockData()); return state; } diff --git a/src/main/java/ch/njol/skript/entity/FallingBlockData.java b/src/main/java/ch/njol/skript/entity/FallingBlockData.java index 2f937148fe9..262b2b564d8 100644 --- a/src/main/java/ch/njol/skript/entity/FallingBlockData.java +++ b/src/main/java/ch/njol/skript/entity/FallingBlockData.java @@ -93,7 +93,7 @@ public ItemType convert(ItemType t) { @Override protected boolean init(final @Nullable Class<? extends FallingBlock> c, final @Nullable FallingBlock e) { if (e != null) // TODO material data support - types = new ItemType[] {new ItemType(BlockCompat.INSTANCE.fallingBlockToState(e))}; + types = new ItemType[] {new ItemType(e.getBlockData())}; return true; } @@ -101,7 +101,7 @@ protected boolean init(final @Nullable Class<? extends FallingBlock> c, final @N protected boolean match(final FallingBlock entity) { if (types != null) { for (final ItemType t : types) { - if (t.isOfType(BlockCompat.INSTANCE.fallingBlockToState(entity))) + if (t.isOfType(entity.getBlockData())) return true; } return false; diff --git a/src/main/java/ch/njol/skript/events/EvtBlock.java b/src/main/java/ch/njol/skript/events/EvtBlock.java index 4a393146600..78d5fc1dc59 100644 --- a/src/main/java/ch/njol/skript/events/EvtBlock.java +++ b/src/main/java/ch/njol/skript/events/EvtBlock.java @@ -104,7 +104,7 @@ public boolean check(final Event event) { if (event instanceof BlockFormEvent) { BlockFormEvent blockFormEvent = (BlockFormEvent) event; BlockState newState = blockFormEvent.getNewState(); - item = new ItemType(newState); + item = new ItemType(newState.getBlockData()); blockData = newState.getBlockData(); } else if (event instanceof BlockEvent) { BlockEvent blockEvent = (BlockEvent) event; diff --git a/src/main/java/ch/njol/skript/events/EvtGrow.java b/src/main/java/ch/njol/skript/events/EvtGrow.java index 98f5f281728..ff2deb11a8d 100644 --- a/src/main/java/ch/njol/skript/events/EvtGrow.java +++ b/src/main/java/ch/njol/skript/events/EvtGrow.java @@ -174,7 +174,7 @@ private static boolean checkFrom(Event event, Literal<Object> types) { BlockState oldState = ((BlockGrowEvent) event).getBlock().getState(); return types.check(event, type -> { if (type instanceof ItemType) { - return ((ItemType) type).isOfType(oldState); + return ((ItemType) type).isOfType(oldState.getBlockData()); } else if (type instanceof BlockData) { return ((BlockData) type).matches(oldState.getBlockData()); } @@ -201,7 +201,7 @@ private static boolean checkTo(Event event, Literal<Object> types) { BlockState newState = ((BlockGrowEvent) event).getNewState(); return types.check(event, type -> { if (type instanceof ItemType) { - return ((ItemType) type).isOfType(newState); + return ((ItemType) type).isOfType(newState.getBlockData()); } else if (type instanceof BlockData) { return ((BlockData) type).matches(newState.getBlockData()); } From bfdadaa59813b14c113b1fb58b953fb0660610be Mon Sep 17 00:00:00 2001 From: Shane Bee <shanebolenback@me.com> Date: Sat, 30 Mar 2024 10:43:50 -0700 Subject: [PATCH 617/619] ExprIndices - fix strings not sorting alphabetically (#6495) * ExprIndices - fix strings not sorting alphabetically * ExprIndices - update method in ExprSortedList and use in this class * ExprIndices - forgot to get values * ExprSortedList - add a message to the error for better understanding --------- Co-authored-by: Moderocky <admin@moderocky.com> --- .../ch/njol/skript/expressions/ExprIndices.java | 7 +------ .../ch/njol/skript/expressions/ExprSortedList.java | 7 +++++-- .../tests/syntaxes/expressions/ExprIndices.sk | 14 ++++++++++++++ 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprIndices.java b/src/main/java/ch/njol/skript/expressions/ExprIndices.java index 1f4439273b6..c09bfe81964 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprIndices.java +++ b/src/main/java/ch/njol/skript/expressions/ExprIndices.java @@ -28,7 +28,6 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.Variable; import ch.njol.skript.lang.util.SimpleExpression; -import org.skriptlang.skript.lang.comparator.Comparators; import ch.njol.skript.util.LiteralUtils; import ch.njol.util.Kleenean; import org.bukkit.event.Event; @@ -102,7 +101,7 @@ protected String[] get(Event e) { if (sort) { int direction = descending ? -1 : 1; return variable.entrySet().stream() - .sorted((a, b) -> compare(a, b, direction)) + .sorted((a, b) -> ExprSortedList.compare(a.getValue(), b.getValue()) * direction) .map(Entry::getKey) .toArray(String[]::new); } @@ -130,8 +129,4 @@ public String toString(@Nullable Event e, boolean debug) { return text; } - // Extracted method for better readability - private int compare(Entry<String, Object> a, Entry<String, Object> b, int direction) { - return Comparators.compare(a.getValue(), b.getValue()).getRelation() * direction; - } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprSortedList.java b/src/main/java/ch/njol/skript/expressions/ExprSortedList.java index 3cfed13b0a6..0a4af988476 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprSortedList.java +++ b/src/main/java/ch/njol/skript/expressions/ExprSortedList.java @@ -34,6 +34,7 @@ import org.eclipse.jdt.annotation.Nullable; import org.skriptlang.skript.lang.comparator.Comparator; import org.skriptlang.skript.lang.comparator.Comparators; +import org.skriptlang.skript.lang.comparator.Relation; import java.lang.reflect.Array; @@ -77,12 +78,14 @@ protected Object[] get(Event event) { } @SuppressWarnings("unchecked") - private static <A, B> int compare(A a, B b) throws IllegalArgumentException, ClassCastException { + public static <A, B> int compare(A a, B b) throws IllegalArgumentException, ClassCastException { + if (a instanceof String && b instanceof String) + return Relation.get(((String) a).compareToIgnoreCase((String) b)).getRelation(); Comparator<A, B> comparator = Comparators.getComparator((Class<A>) a.getClass(), (Class<B>) b.getClass()); if (comparator != null && comparator.supportsOrdering()) return comparator.compare(a, b).getRelation(); if (!(a instanceof Comparable)) - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Cannot compare " + a.getClass()); return ((Comparable<B>) a).compareTo(b); } diff --git a/src/test/skript/tests/syntaxes/expressions/ExprIndices.sk b/src/test/skript/tests/syntaxes/expressions/ExprIndices.sk index fccca1d6a6d..855e4129b78 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprIndices.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprIndices.sk @@ -23,3 +23,17 @@ test "sorted indices": set {_a} to {_leader-board::%loop-value%} set {_b} to {_descending-values::%loop-index%} assert {_a} is equal to {_b} with "Sorting indices in descending order is incorrect" + +test "alphabetically sorted indices": + set {_test::a} to "Bob" + set {_test::b} to "Donald" + set {_test::c} to "Zeffer" + set {_test::d} to "Angus" + set {_test::e} to "Kevin" + set {_test::f} to "anderson" + set {_test::g} to "robertson" + + set {_indexes::*} to sorted indices of {_test::*} in ascending order + + assert {_test::%{_indexes::1}%} = "anderson" with "First element of sorted strings should be 'anderson'" + assert {_test::%{_indexes::7}%} = "Zeffer" with "Last element of sorted strings should be 'Zeffer'" From dfaa66fc3e8f5a177e99bb8cc113bcd9ad845467 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Mon, 1 Apr 2024 03:51:28 -0600 Subject: [PATCH 618/619] Fix expression null pointer exception on expressions with null return type (#6497) * Fix null pointer exception and add proper error message * Update src/main/java/ch/njol/skript/lang/SkriptParser.java Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> * change error message --------- Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Co-authored-by: Moderocky <admin@moderocky.com> --- src/main/java/ch/njol/skript/lang/SkriptParser.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/ch/njol/skript/lang/SkriptParser.java b/src/main/java/ch/njol/skript/lang/SkriptParser.java index 3d6a16b4eba..930c8e0639a 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptParser.java +++ b/src/main/java/ch/njol/skript/lang/SkriptParser.java @@ -531,6 +531,9 @@ private Expression<?> parseSingleExpr(boolean allowUnparsedLiteral, @Nullable Lo Expression<?> parsedExpression = parseExpression(types, expr); if (parsedExpression != null) { // Expression/VariableString parsing success Class<?> returnType = parsedExpression.getReturnType(); // Sometimes getReturnType does non-trivial costly operations + if (returnType == null) + throw new SkriptAPIException("Expression '" + expr + "' returned null for method Expression#getReturnType. Null is not a valid return."); + for (int i = 0; i < types.length; i++) { Class<?> type = types[i]; if (type == null) // Ignore invalid (null) types From cabfad7277270c7688a1465a667e2c688f0b4a90 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Mon, 1 Apr 2024 12:57:21 +0200 Subject: [PATCH 619/619] Prepare for Release (2.8.4) (#6516) Bump version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index e31c5a65002..3271da70815 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ org.gradle.parallel=true groupid=ch.njol name=skript -version=2.8.3 +version=2.8.4 jarName=Skript.jar testEnv=java17/paper-1.20.4 testEnvJavaVersion=17