From ceb2dd821de04b735eb59726edff37af77ce9208 Mon Sep 17 00:00:00 2001 From: Olyno Date: Mon, 30 Sep 2019 07:59:33 +0200 Subject: [PATCH 01/16] Added scripts tests support --- src/main/java/ch/njol/skript/Skript.java | 3317 ++++++++--------- src/main/resources/scripts/tests/api/api.sk | 183 + .../scripts/tests/api/skript.tests.sk | 6 + .../scripts/tests/helloSkript.tests.sk | 4 + 4 files changed, 1851 insertions(+), 1659 deletions(-) create mode 100644 src/main/resources/scripts/tests/api/api.sk create mode 100644 src/main/resources/scripts/tests/api/skript.tests.sk create mode 100644 src/main/resources/scripts/tests/helloSkript.tests.sk diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index eb9f1bfc779..fcaa0655227 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -1,1659 +1,1658 @@ -/** - * This file is part of Skript. - * - * Skript is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Skript is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Skript. If not, see . - * - * - * Copyright 2011-2017 Peter Güttinger and contributors - */ -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.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.net.MalformedURLException; -import java.net.URL; -import java.nio.channels.Channels; -import java.nio.channels.FileChannel; -import java.nio.channels.ReadableByteChannel; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; -import java.nio.file.StandardOpenOption; -import java.util.ArrayList; -import java.util.Arrays; -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.Scanner; -import java.util.Set; -import java.util.concurrent.Callable; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.logging.Filter; -import java.util.logging.Level; -import java.util.logging.LogRecord; -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 ch.njol.skript.lang.Trigger; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.Material; -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.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.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; -import ch.njol.skript.classes.data.BukkitClasses; -import ch.njol.skript.classes.data.BukkitEventValues; -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.JavaClasses; -import ch.njol.skript.classes.data.SkriptClasses; -import ch.njol.skript.command.Commands; -import ch.njol.skript.doc.Documentation; -import ch.njol.skript.events.EvtSkript; -import ch.njol.skript.hooks.Hook; -import ch.njol.skript.lang.Condition; -import ch.njol.skript.lang.Effect; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.ExpressionInfo; -import ch.njol.skript.lang.ExpressionType; -import ch.njol.skript.lang.SkriptEvent; -import ch.njol.skript.lang.SkriptEventInfo; -import ch.njol.skript.lang.Statement; -import ch.njol.skript.lang.SyntaxElementInfo; -import ch.njol.skript.lang.TriggerItem; -import ch.njol.skript.lang.VariableString; -import ch.njol.skript.lang.function.Functions; -import ch.njol.skript.lang.util.SimpleExpression; -import ch.njol.skript.localization.Language; -import ch.njol.skript.localization.Message; -import ch.njol.skript.log.BukkitLoggerFilter; -import ch.njol.skript.log.CountingLogHandler; -import ch.njol.skript.log.ErrorDescLogHandler; -import ch.njol.skript.log.ErrorQuality; -import ch.njol.skript.log.LogEntry; -import ch.njol.skript.log.LogHandler; -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 ch.njol.skript.registrations.Converters; -import ch.njol.skript.registrations.EventValues; -import ch.njol.skript.timings.SkriptTimings; -import ch.njol.skript.update.ReleaseManifest; -import ch.njol.skript.update.ReleaseStatus; -import ch.njol.skript.update.UpdateManifest; -import ch.njol.skript.update.UpdaterState; -import ch.njol.skript.util.EmptyStacktraceException; -import ch.njol.skript.util.ExceptionUtils; -import ch.njol.skript.util.FileUtils; -import ch.njol.skript.util.Getter; -import ch.njol.skript.util.Task; -import ch.njol.skript.util.Utils; -import ch.njol.skript.util.Version; -import ch.njol.skript.util.chat.BungeeConverter; -import ch.njol.skript.util.chat.ChatMessages; -import ch.njol.skript.variables.Variables; -import ch.njol.util.Closeable; -import ch.njol.util.Kleenean; -import ch.njol.util.NullableChecker; -import ch.njol.util.Pair; -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; - -// TODO meaningful error if someone uses an %expression with percent signs% outside of text or a variable - -/** - * Skript - A Bukkit plugin to modify how Minecraft behaves without having to write a single line of code (You'll likely be writing some code though if you're reading this - * =P) - *

- * Use this class to extend this plugin's functionality by adding more {@link Condition conditions}, {@link Effect effects}, {@link SimpleExpression expressions}, etc. - *

- * If your plugin.yml contains 'depend: [Skript]' then your plugin will not start at all if Skript is not present. Add 'softdepend: [Skript]' to your plugin.yml - * if you want your plugin to work even if Skript isn't present, but want to make sure that Skript gets loaded before your plugin. - *

- * If you use 'softdepend' you can test whether Skript is loaded with 'Bukkit.getPluginManager().getPlugin("Skript") != null' - *

- * Once you made sure that Skript is loaded you can use Skript.getInstance() whenever you need a reference to the plugin, but you likely won't need it since all API - * methods are static. - * - * @author Peter Güttinger - * @see #registerAddon(JavaPlugin) - * @see #registerCondition(Class, String...) - * @see #registerEffect(Class, String...) - * @see #registerExpression(Class, Class, ExpressionType, String...) - * @see #registerEvent(String, Class, Class, String...) - * @see EventValues#registerEventValue(Class, Class, Getter, int) - * @see Classes#registerClass(ClassInfo) - * @see Comparators#registerComparator(Class, Class, Comparator) - * @see Converters#registerConverter(Class, Class, Converter) - */ -public final class Skript extends JavaPlugin implements Listener { - - // ================ PLUGIN ================ - - @Nullable - private static Skript instance = null; - - private static boolean disabled = false; - - public static Skript getInstance() { - final Skript i = instance; - if (i == null) - throw new IllegalStateException(); - return i; - } - - /** - * Current updater instance used by Skript. - */ - @Nullable - private SkriptUpdater updater; - - public Skript() throws IllegalStateException { - if (instance != null) - throw new IllegalStateException("Cannot create multiple instances of Skript!"); - instance = this; - } - - @Nullable - private static Version version = null; - - public static Version getVersion() { - final Version v = version; - if (v == null) - throw new IllegalStateException(); - return v; - } - - public final static Message m_invalid_reload = new Message("skript.invalid reload"), - m_finished_loading = new Message("skript.finished loading"); - - public static ServerPlatform getServerPlatform() { - if (classExists("net.glowstone.GlowServer")) { - return ServerPlatform.BUKKIT_GLOWSTONE; // Glowstone has timings too, so must check for it first - } else if (classExists("co.aikar.timings.Timings")) { - return ServerPlatform.BUKKIT_PAPER; // Could be Sponge, but it doesn't work at all at the moment - } else if (classExists("org.spigotmc.SpigotConfig")) { - return ServerPlatform.BUKKIT_SPIGOT; - } else if (classExists("org.bukkit.craftbukkit.CraftServer") || classExists("org.bukkit.craftbukkit.Main")) { - // At some point, CraftServer got removed or moved - return ServerPlatform.BUKKIT_CRAFTBUKKIT; - } else { // Probably some ancient Bukkit implementation - return ServerPlatform.BUKKIT_UNKNOWN; - } - } - - /** - * Checks if server software and Minecraft version are supported. - * Prints errors or warnings to console if something is wrong. - * @return Whether Skript can continue loading at all. - */ - private static boolean checkServerPlatform() { - String bukkitV = Bukkit.getBukkitVersion(); - Matcher m = Pattern.compile("\\d+\\.\\d+(\\.\\d+)?").matcher(bukkitV); - if (!m.find()) { - Skript.error("The Bukkit version '" + bukkitV + "' does not contain a version number which is required for Skript to enable or disable certain features. " + - "Skript will still work, but you might get random errors if you use features that are not available in your version of Bukkit."); - minecraftVersion = new Version(666, 0, 0); - } else { - minecraftVersion = new Version("" + m.group()); - } - Skript.debug("Loading for Minecraft " + minecraftVersion); - - // Check that MC version is supported - if (!isRunningMinecraft(1, 9)) { - if (isRunningMinecraft(1, 8)) { // 1.8 probably works, but let's spit a warning - Skript.warning("Using this version of Skript on 1.8 is highly discouraged."); - Skript.warning("Some features have been disabled; use older Skript to restore them."); - Skript.warning("Also, there are probably bugs. And since 1.8 is not supported, they will not be fixed"); - } else { // Older versions definitely do not work - Skript.error("This version of Skript does not work with Minecraft " + minecraftVersion); - Skript.error("You probably want Skript 2.2 or 2.1 (Google to find where to get them)"); - Skript.error("Note that those versions are, of course, completely unsupported!"); - return false; - } - } - - // Check that current server platform is somewhat supported - serverPlatform = getServerPlatform(); - Skript.debug("Server platform: " + serverPlatform); - if (!serverPlatform.works) { - Skript.error("It seems that this server platform (" + serverPlatform.name + ") does not work with Skript."); - if (SkriptConfig.allowUnsafePlatforms.value()) { - Skript.error("However, you have chosen to ignore this. Skript will probably still not work."); - } else { - Skript.error("To prevent potentially unsafe behaviour, Skript has been disabled."); - Skript.error("You may re-enable it by adding a configuration option 'allow unsafe platforms: true'"); - Skript.error("Note that it is unlikely that Skript works correctly even if you do so."); - Skript.error("A better idea would be to install Paper or Spigot in place of your current server."); - return false; - } - } else if (!serverPlatform.supported) { - Skript.warning("This server platform (" + serverPlatform.name + ") is not supported by Skript."); - Skript.warning("It will still probably work, but if it does not, you are on your own."); - Skript.warning("Skript officially supports Paper and Spigot."); - } - // If nothing got triggered, everything is probably ok - return true; - } - - @Override - public void onEnable() { - if (disabled) { - Skript.error(m_invalid_reload.toString()); - setEnabled(false); - return; - } - - handleJvmArguments(); // JVM arguments - - version = new Version("" + getDescription().getVersion()); // Skript version - - Language.loadDefault(getAddonInstance()); - - Workarounds.init(); - - // Start the updater - // Note: if config prohibits update checks, it will NOT do network connections - try { - this.updater = new SkriptUpdater(); - } catch (Exception e) { - Skript.exception(e, "Update checker could not be initialized."); - } - - if (!getDataFolder().isDirectory()) - getDataFolder().mkdirs(); - - final File scripts = new File(getDataFolder(), SCRIPTSFOLDER); - final File config = new File(getDataFolder(), "config.sk"); - final File features = new File(getDataFolder(), "features.sk"); - if (!scripts.isDirectory() || !config.exists() || !features.exists()) { - ZipFile f = null; - try { - boolean populateExamples = false; - if (!scripts.isDirectory()) { - if (!scripts.mkdirs()) - throw new IOException("Could not create the directory " + scripts); - populateExamples = true; - } - f = new ZipFile(getFile()); - for (final ZipEntry e : new EnumerationIterable(f.entries())) { - if (e.isDirectory()) - continue; - File saveTo = null; - if (e.getName().startsWith(SCRIPTSFOLDER + "/") && populateExamples) { - final String fileName = e.getName().substring(e.getName().lastIndexOf('/') + 1); - saveTo = new File(scripts, (fileName.startsWith("-") ? "" : "-") + fileName); - } else if (e.getName().equals("config.sk")) { - if (!config.exists()) - saveTo = config; -// } else if (e.getName().startsWith("aliases-") && e.getName().endsWith(".sk") && !e.getName().contains("/")) { -// final File af = new File(getDataFolder(), e.getName()); -// if (!af.exists()) -// saveTo = af; - } else if (e.getName().startsWith("features.sk")) { - if (!features.exists()) - saveTo = features; - } - if (saveTo != null) { - final InputStream in = f.getInputStream(e); - try { - assert in != null; - FileUtils.save(in, saveTo); - } finally { - in.close(); - } - } - } - info("Successfully generated the config and the example scripts."); - } catch (final ZipException e) {} catch (final IOException e) { - error("Error generating the default files: " + ExceptionUtils.toString(e)); - } finally { - if (f != null) { - try { - f.close(); - } catch (final IOException e) {} - } - } - } - - // Load classes which are always safe to use - new JavaClasses(); // These may be needed in configuration - - // And then not-so-safe classes - Throwable classLoadError = null; - try { - new SkriptClasses(); - } catch (Throwable e) { - classLoadError = e; - } - - // 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(); - - // Use the updater, now that it has been configured to (not) do stuff - if (updater != null) { - CommandSender console = Bukkit.getConsoleSender(); - assert console != null; - assert updater != null; - updater.updateCheck(console); - } - - // 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; - } - - BukkitUnsafe.initialize(); // Needed for aliases - Aliases.load(); // Loaded before anything that might use them - - // If loading can continue (platform ok), check for potentially thrown error - if (classLoadError != null) { - exception(classLoadError); - setEnabled(false); - return; - } - - PluginCommand skriptCommand = getCommand("skript"); - assert skriptCommand != null; // It is defined, unless build is corrupted or something like that - skriptCommand.setExecutor(new SkriptCommand()); - - // Load Bukkit stuff. It is done after platform check, because something might be missing! - new BukkitClasses(); - new BukkitEventValues(); - - new DefaultComparators(); - new DefaultConverters(); - new DefaultFunctions(); - - ChatMessages.registerListeners(); - - try { - getAddonInstance().loadClasses("ch.njol.skript", "conditions", "effects", "events", "expressions", "entity"); - } catch (final Exception e) { - exception(e, "Could not load required .class files: " + e.getLocalizedMessage()); - setEnabled(false); - return; - } - - Language.setUseLocal(true); - - Commands.registerListeners(); - - if (logNormal()) - info(" " + Language.get("skript.copyright")); - - final long tick = testing() ? Bukkit.getWorlds().get(0).getFullTime() : 0; - Bukkit.getScheduler().scheduleSyncDelayedTask(this, new Runnable() { - @SuppressWarnings("synthetic-access") - @Override - public void run() { - assert Bukkit.getWorlds().get(0).getFullTime() == tick; - - // Load hooks from Skript jar - try { - try (JarFile jar = new JarFile(getFile())) { - for (final 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) { - hook.getDeclaredConstructor().setAccessible(true); - hook.getDeclaredConstructor().newInstance(); - } - } catch (final ClassNotFoundException ex) { - Skript.exception(ex, "Cannot load class " + c); - } catch (final ExceptionInInitializerError err) { - Skript.exception(err.getCause(), "Class " + c + " generated an exception while loading"); - } - continue; - } - } - } - } catch (final Exception e) { - error("Error while loading plugin hooks" + (e.getLocalizedMessage() == null ? "" : ": " + e.getLocalizedMessage())); - Skript.exception(e); - } - - Language.setUseLocal(false); - - stopAcceptingRegistrations(); - - - Documentation.generate(); // TODO move to test classes? - - if (logNormal()) - info("Loading variables..."); - final long vls = System.currentTimeMillis(); - - final LogHandler h = SkriptLogger.startLogHandler(new ErrorDescLogHandler() { -// private final List log = new ArrayList(); - - @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 { -// log.add(entry); -// return LogResult.CACHED; - 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 onStop() { - super.onStop(); -// SkriptLogger.logAll(log); - } - }); - final CountingLogHandler c = SkriptLogger.startLogHandler(new CountingLogHandler(SkriptLogger.SEVERE)); - try { - if (!Variables.load()) - if (c.getCount() == 0) - error("(no information available)"); - } finally { - c.stop(); - h.stop(); - } - - final long vld = System.currentTimeMillis() - vls; - if (logNormal()) - info("Loaded " + Variables.numVariables() + " variables in " + ((vld / 100) / 10.) + " seconds"); - - ScriptLoader.loadScripts(); - - Skript.info(m_finished_loading.toString()); - - EvtSkript.onSkriptStart(); - - final 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(); - } - }); - - Skript.metrics = metrics; - - // suppresses the "can't keep up" warning after loading all scripts - final Filter f = new Filter() { - @Override - public boolean isLoggable(final @Nullable LogRecord record) { - if (record == null) - return false; - if (record.getMessage() != null && record.getMessage().toLowerCase(Locale.ENGLISH).startsWith("can't keep up!")) - return false; - return true; - } - }; - BukkitLoggerFilter.addFilter(f); - Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.this, new Runnable() { - @Override - public void run() { - BukkitLoggerFilter.removeFilter(f); - } - }, 1); - } - }); - - Bukkit.getPluginManager().registerEvents(new Listener() { - @EventHandler - public void onJoin(final PlayerJoinEvent e) { - if (e.getPlayer().hasPermission("skript.admin")) { - new Task(Skript.this, 0) { - @Override - public void run() { - Player p = e.getPlayer(); - SkriptUpdater updater = getUpdater(); - if (p == null || updater == null) - return; - - // Don't actually check for updates to avoid breaking Github rate limit - if (updater.getReleaseStatus() == ReleaseStatus.OUTDATED) { - // Last check indicated that an update is available - UpdateManifest update = updater.getUpdateManifest(); - assert update != null; // Because we just checked that one is available - Skript.info(p, "" + SkriptUpdater.m_update_available.toString(update.id, Skript.getVersion())); - p.spigot().sendMessage(BungeeConverter.convert(ChatMessages.parseToArray( - "Download it at: " + update.downloadUrl))); - } - } - }; - } - } - }, this); - - // Tell Timings that we are here! - SkriptTimings.setSkript(this); - } - - /** - * Handles -Dskript.stuff command line arguments. - */ - private void handleJvmArguments() { - Path folder = getDataFolder().toPath(); - - /* - * Burger is a Python application that extracts data from Minecraft. - * Datasets for most common versions are available for download. - * Skript uses them to provide minecraft:material to Bukkit - * Material mappings on Minecraft 1.12 and older. - */ - String burgerEnabled = System.getProperty("skript.burger.enable"); - if (burgerEnabled != null) { - String version = System.getProperty("skript.burger.version"); - String burgerInput; - if (version == null) { // User should have provided JSON file path - String inputFile = System.getProperty("skript.burger.file"); - if (inputFile == null) { - Skript.exception("burger enabled but skript.burger.file not provided"); - return; - } - try { - burgerInput = new String(Files.readAllBytes(Paths.get(inputFile)), StandardCharsets.UTF_8); - } catch (IOException e) { - Skript.exception(e); - return; - } - } else { // Try to download Burger dataset for this version - try { - Path data = folder.resolve("burger-" + version + ".json"); - if (!Files.exists(data)) { - URL url = new URL("https://pokechu22.github.io/Burger/" + version + ".json"); - try (InputStream is = url.openStream()) { - Files.copy(is, data); - } - } - burgerInput = new String(Files.readAllBytes(data), StandardCharsets.UTF_8); - } catch (IOException e) { - Skript.exception(e); - return; - } - } - - // Use BurgerHelper to create some mappings, then dump them as JSON - try { - BurgerHelper burger = new BurgerHelper(burgerInput); - Map materials = burger.mapMaterials(); - Map ids = BurgerHelper.mapIds(); - - Gson gson = new Gson(); - Files.write(folder.resolve("materials_mappings.json"), gson.toJson(materials) - .getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE); - Files.write(folder.resolve("id_mappings.json"), gson.toJson(ids) - .getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE); - } catch (IOException e) { - Skript.exception(e); - } - } - } - - private static Version minecraftVersion = new Version(666); - private static ServerPlatform serverPlatform = ServerPlatform.BUKKIT_UNKNOWN; // Start with unknown... onLoad changes this - - public static Version getMinecraftVersion() { - return minecraftVersion; - } - - /** - * @return Whether this server is running CraftBukkit - */ - public static boolean isRunningCraftBukkit() { - return serverPlatform == ServerPlatform.BUKKIT_CRAFTBUKKIT; - } - - /** - * @return Whether this server is running Minecraft major.minor or higher - */ - public static boolean isRunningMinecraft(final int major, final int minor) { - return minecraftVersion.compareTo(major, minor) >= 0; - } - - public static boolean isRunningMinecraft(final int major, final int minor, final int revision) { - return minecraftVersion.compareTo(major, minor, revision) >= 0; - } - - public static boolean isRunningMinecraft(final Version v) { - return minecraftVersion.compareTo(v) >= 0; - } - - /** - * Used to test whether certain Bukkit features are supported. - * - * @param className - * @return Whether the given class exists. - * @deprecated use {@link #classExists(String)} - */ - @Deprecated - public static boolean supports(final String className) { - return classExists(className); - } - - /** - * Tests whether a given class exists in the classpath. - * - * @param className The {@link Class#getCanonicalName() canonical name} of the class - * @return Whether the given class exists. - */ - public static boolean classExists(final String className) { - try { - Class.forName(className); - return true; - } catch (final ClassNotFoundException e) { - return false; - } - } - - /** - * Tests whether a method exists in the given class. - * - * @param c The class - * @param methodName The name of the method - * @param parameterTypes The parameter types of the method - * @return Whether the given method exists. - */ - public static boolean methodExists(final Class c, final String methodName, final Class... parameterTypes) { - try { - c.getDeclaredMethod(methodName, parameterTypes); - return true; - } catch (final NoSuchMethodException e) { - return false; - } catch (final SecurityException e) { - return false; - } - } - - /** - * Tests whether a method exists in the given class, and whether the return type matches the expected one. - *

- * Note that this method doesn't work properly if multiple methods with the same name and parameters exist but have different return types. - * - * @param c The class - * @param methodName The name of the method - * @param parameterTypes The parameter types of the method - * @param returnType The expected return type - * @return Whether the given method exists. - */ - public static boolean methodExists(final Class c, final String methodName, final Class[] parameterTypes, final Class returnType) { - try { - final Method m = c.getDeclaredMethod(methodName, parameterTypes); - return m.getReturnType() == returnType; - } catch (final NoSuchMethodException e) { - return false; - } catch (final SecurityException e) { - return false; - } - } - - /** - * Tests whether a field exists in the given class. - * - * @param c The class - * @param fieldName The name of the field - * @return Whether the given field exists. - */ - public static boolean fieldExists(final Class c, final String fieldName) { - try { - c.getDeclaredField(fieldName); - return true; - } catch (final NoSuchFieldException e) { - return false; - } catch (final SecurityException e) { - return false; - } - } - - @Nullable - static Metrics metrics; - - @Nullable - public static Metrics getMetrics() { - return metrics; - } - - /** - * Clears triggers, commands, functions and variable names - */ - static void disableScripts() { - VariableString.variableNames.clear(); - SkriptEventHandler.removeAllTriggers(); - Commands.clearCommands(); - Functions.clearFunctions(); - } - - /** - * Prints errors from reloading the config & scripts - */ - static void reload() { - if (!ScriptLoader.loadAsync) - disableScripts(); - reloadMainConfig(); - reloadAliases(); - ScriptLoader.loadScripts(); - } - - /** - * Prints errors - */ - static void reloadScripts() { - if (!ScriptLoader.loadAsync) - disableScripts(); - ScriptLoader.loadScripts(); - } - - /** - * Prints errors - */ - static void reloadMainConfig() { - SkriptConfig.load(); - } - - /** - * Prints errors - */ - static void reloadAliases() { - Aliases.clear(); - Aliases.load(); - } - - @SuppressWarnings("null") - private final static Collection closeOnDisable = Collections.synchronizedCollection(new ArrayList()); - - /** - * Registers a Closeable that should be closed when this plugin is disabled. - *

- * All registered Closeables will be closed after all scripts have been stopped. - * - * @param closeable - */ - public static void closeOnDisable(final Closeable closeable) { - closeOnDisable.add(closeable); - } - - @Override - public void onDisable() { - if (disabled) - return; - disabled = true; - - EvtSkript.onSkriptStop(); // TODO [code style] warn user about delays in Skript stop events - - disableScripts(); - - Bukkit.getScheduler().cancelTasks(this); - - for (final Closeable c : closeOnDisable) { - try { - c.close(); - } catch (final Exception e) { - Skript.exception(e, "An error occurred while shutting down.", "This might or might not cause any issues."); - } - } - - // unset static fields to prevent memory leaks as Bukkit reloads the classes with a different classloader on reload - // async to not slow down server reload, delayed to not slow down server shutdown - final Thread t = newThread(new Runnable() { - @SuppressWarnings("synthetic-access") - @Override - public void run() { - try { - Thread.sleep(10000); - } catch (final InterruptedException e) {} - try { - final Field modifiers = Field.class.getDeclaredField("modifiers"); - modifiers.setAccessible(true); - final JarFile jar = new JarFile(getFile()); - try { - for (final JarEntry e : new EnumerationIterable<>(jar.entries())) { - if (e.getName().endsWith(".class")) { - try { - final Class c = Class.forName(e.getName().replace('/', '.').substring(0, e.getName().length() - ".class".length()), false, getClassLoader()); - for (final Field f : c.getDeclaredFields()) { - if (Modifier.isStatic(f.getModifiers()) && !f.getType().isPrimitive()) { - if (Modifier.isFinal(f.getModifiers())) { - modifiers.setInt(f, f.getModifiers() & ~Modifier.FINAL); - } - f.setAccessible(true); - f.set(null, null); - } - } - } catch (final Throwable ex) { - if (testing()) - ex.printStackTrace(); - } - } - } - } finally { - jar.close(); - } - } catch (final Throwable ex) { - if (testing()) - ex.printStackTrace(); - } - } - }, "Skript cleanup thread"); - t.setPriority(Thread.MIN_PRIORITY); - t.setDaemon(true); - t.start(); - } - - // ================ CONSTANTS, OPTIONS & OTHER ================ - - public final static String SCRIPTSFOLDER = "scripts"; - - public static void outdatedError() { - error("Skript v" + getInstance().getDescription().getVersion() + " is not fully compatible with Bukkit " + Bukkit.getVersion() + ". Some feature(s) will be broken until you update Skript."); - } - - public static void outdatedError(final Exception e) { - outdatedError(); - if (testing()) - e.printStackTrace(); - } - - /** - * A small value, useful for comparing doubles or floats. - *

- * E.g. to test whether two floating-point numbers are equal: - * - *

-	 * Math.abs(a - b) < Skript.EPSILON
-	 * 
- * - * or whether a location is within a specific radius of another location: - * - *
-	 * location.distanceSquared(center) - radius * radius < Skript.EPSILON
-	 * 
- * - * @see #EPSILON_MULT - */ - public final static double EPSILON = 1e-10; - /** - * A value a bit larger than 1 - * - * @see #EPSILON - */ - public final static double EPSILON_MULT = 1.00001; - - /** - * The maximum ID a block can have in Minecraft. - */ - public final static int MAXBLOCKID = 255; - /** - * The maximum data value of Minecraft, i.e. Short.MAX_VALUE - Short.MIN_VALUE. - */ - public final static int MAXDATAVALUE = Short.MAX_VALUE - Short.MIN_VALUE; - - // TODO localise Infinity, -Infinity, NaN (and decimal point?) - public static String toString(final double n) { - return StringUtils.toString(n, SkriptConfig.numberAccuracy.value()); - } - - public final static UncaughtExceptionHandler UEH = new UncaughtExceptionHandler() { - @Override - public void uncaughtException(final @Nullable Thread t, final @Nullable Throwable e) { - Skript.exception(e, "Exception in thread " + (t == null ? null : t.getName())); - } - }; - - /** - * Creates a new Thread and sets its UncaughtExceptionHandler. The Thread is not started automatically. - */ - public static Thread newThread(final Runnable r, final String name) { - final Thread t = new Thread(r, name); - t.setUncaughtExceptionHandler(UEH); - return t; - } - - // ================ REGISTRATIONS ================ - - private static boolean acceptRegistrations = true; - - public static boolean isAcceptRegistrations() { - return acceptRegistrations; - } - - public static void checkAcceptRegistrations() { - if (!acceptRegistrations) - throw new SkriptAPIException("Registering is disabled after initialisation!"); - } - - private static void stopAcceptingRegistrations() { - acceptRegistrations = false; - - Converters.createMissingConverters(); - - Classes.onRegistrationsStop(); - } - - // ================ ADDONS ================ - - private final static HashMap addons = new HashMap<>(); - - /** - * Registers an addon to Skript. This is currently not required for addons to work, but the returned {@link SkriptAddon} provides useful methods for registering syntax elements - * and adding new strings to Skript's localization system (e.g. the required "types.[type]" strings for registered classes). - * - * @param p The plugin - */ - public static SkriptAddon registerAddon(final JavaPlugin p) { - checkAcceptRegistrations(); - if (addons.containsKey(p.getName())) - throw new IllegalArgumentException("The plugin " + p.getName() + " is already registered"); - final SkriptAddon addon = new SkriptAddon(p); - addons.put(p.getName(), addon); - return addon; - } - - @Nullable - public static SkriptAddon getAddon(final JavaPlugin p) { - return addons.get(p.getName()); - } - - @Nullable - public static SkriptAddon getAddon(final String name) { - return addons.get(name); - } - - @SuppressWarnings("null") - public static Collection getAddons() { - return Collections.unmodifiableCollection(addons.values()); - } - - @Nullable - private static SkriptAddon addon; - - /** - * @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; - } - - // ================ CONDITIONS & EFFECTS ================ - - private final static Collection> conditions = new ArrayList<>(50); - private final static Collection> effects = new ArrayList<>(50); - private final static Collection> statements = new ArrayList<>(100); - - /** - * registers a {@link Condition}. - * - * @param condition The condition's class - * @param patterns Skript patterns to match this condition - */ - public static void registerCondition(final Class condition, final String... patterns) throws IllegalArgumentException { - checkAcceptRegistrations(); - String originClassPath = Thread.currentThread().getStackTrace()[2].getClassName(); - final SyntaxElementInfo info = new SyntaxElementInfo<>(patterns, condition, originClassPath); - conditions.add(info); - statements.add(info); - } - - /** - * Registers an {@link Effect}. - * - * @param effect The effect's class - * @param patterns Skript patterns to match this effect - */ - public static void registerEffect(final Class effect, final String... patterns) throws IllegalArgumentException { - checkAcceptRegistrations(); - String originClassPath = Thread.currentThread().getStackTrace()[2].getClassName(); - final SyntaxElementInfo info = new SyntaxElementInfo<>(patterns, effect, originClassPath); - effects.add(info); - statements.add(info); - } - - public static Collection> getStatements() { - return statements; - } - - public static Collection> getConditions() { - return conditions; - } - - public static Collection> getEffects() { - return effects; - } - - // ================ EXPRESSIONS ================ - - private final static List> expressions = new ArrayList<>(100); - - private final static int[] expressionTypesStartIndices = new int[ExpressionType.values().length]; - - /** - * Registers an expression. - * - * @param c The expression's class - * @param returnType The superclass of all values returned by the expression - * @param type The expression's {@link ExpressionType type}. This is used to determine in which order to try to parse expressions. - * @param patterns Skript patterns that match this expression - * @throws IllegalArgumentException if returnType is not a normal class - */ - public static , T> void registerExpression(final Class c, final Class returnType, final ExpressionType type, final String... patterns) throws IllegalArgumentException { - checkAcceptRegistrations(); - if (returnType.isAnnotation() || returnType.isArray() || returnType.isPrimitive()) - throw new IllegalArgumentException("returnType must be a normal type"); - String originClassPath = Thread.currentThread().getStackTrace()[2].getClassName(); - final ExpressionInfo info = new ExpressionInfo<>(patterns, returnType, c, originClassPath); - for (int i = type.ordinal() + 1; i < ExpressionType.values().length; i++) { - expressionTypesStartIndices[i]++; - } - expressions.add(expressionTypesStartIndices[type.ordinal()], info); - } - - @SuppressWarnings("null") - public static Iterator> getExpressions() { - return expressions.iterator(); - } - - public static Iterator> getExpressions(final Class... returnTypes) { - return new CheckedIterator<>(getExpressions(), new NullableChecker>() { - @Override - public boolean check(final @Nullable ExpressionInfo i) { - if (i == null || i.returnType == Object.class) - return true; - for (final Class returnType : returnTypes) { - assert returnType != null; - if (Converters.converterExists(i.returnType, returnType)) - return true; - } - return false; - } - }); - } - - // ================ EVENTS ================ - - private final static Collection> events = new ArrayList<>(50); - - /** - * Registers an event. - * - * @param name Capitalised name of the event without leading "On" which is added automatically (Start the name with an asterisk to prevent this). Used for error messages and - * the documentation. - * @param c The event's class - * @param event The Bukkit event this event applies to - * @param patterns Skript patterns to match this event - * @return A SkriptEventInfo representing the registered event. Used to generate Skript's documentation. - */ - public static SkriptEventInfo registerEvent(final String name, final Class c, final Class event, final String... patterns) { - checkAcceptRegistrations(); - String originClassPath = Thread.currentThread().getStackTrace()[2].getClassName(); - assert originClassPath != null; - final SkriptEventInfo r = new SkriptEventInfo<>(name, patterns, c, originClassPath, CollectionUtils.array(event)); - events.add(r); - return r; - } - - /** - * Registers an event. - * - * @param name The name of the event, used for error messages - * @param c The event's class - * @param events The Bukkit events this event applies to - * @param patterns Skript patterns to match this event - * @return A SkriptEventInfo representing the registered event. Used to generate Skript's documentation. - */ - public static SkriptEventInfo registerEvent(final String name, final Class c, final Class[] events, final String... patterns) { - checkAcceptRegistrations(); - String originClassPath = Thread.currentThread().getStackTrace()[2].getClassName(); - assert originClassPath != null; - final SkriptEventInfo r = new SkriptEventInfo<>(name, patterns, c, originClassPath, events); - Skript.events.add(r); - return r; - } - - public static Collection> getEvents() { - return events; - } - - // ================ COMMANDS ================ - - /** - * Dispatches a command with calling command events - * - * @param sender - * @param command - * @return Whether the command was run - */ - public static boolean dispatchCommand(final CommandSender sender, final String command) { - try { - if (sender instanceof Player) { - final PlayerCommandPreprocessEvent e = new PlayerCommandPreprocessEvent((Player) sender, "/" + command); - Bukkit.getPluginManager().callEvent(e); - if (e.isCancelled() || e.getMessage() == null || !e.getMessage().startsWith("/")) - return false; - return Bukkit.dispatchCommand(e.getPlayer(), e.getMessage().substring(1)); - } else { - final ServerCommandEvent e = new ServerCommandEvent(sender, command); - Bukkit.getPluginManager().callEvent(e); - if (e.getCommand() == null || e.getCommand().isEmpty()) - return false; - return Bukkit.dispatchCommand(e.getSender(), e.getCommand()); - } - } catch (final Exception ex) { - ex.printStackTrace(); // just like Bukkit - return false; - } - } - - // ================ LOGGING ================ - - public static boolean logNormal() { - return SkriptLogger.log(Verbosity.NORMAL); - } - - public static boolean logHigh() { - return SkriptLogger.log(Verbosity.HIGH); - } - - public static boolean logVeryHigh() { - return SkriptLogger.log(Verbosity.VERY_HIGH); - } - - public static boolean debug() { - return SkriptLogger.debug(); - } - - public static boolean testing() { - return debug() || Skript.class.desiredAssertionStatus(); - } - - public static boolean log(final Verbosity minVerb) { - return SkriptLogger.log(minVerb); - } - - public static void debug(final String info) { - if (!debug()) - return; - SkriptLogger.log(SkriptLogger.DEBUG, info); - } - - /** - * @see SkriptLogger#log(Level, String) - */ - @SuppressWarnings("null") - public static void info(final String info) { - SkriptLogger.log(Level.INFO, info); - } - - /** - * @see SkriptLogger#log(Level, String) - */ - @SuppressWarnings("null") - public static void warning(final String warning) { - SkriptLogger.log(Level.WARNING, warning); - } - - /** - * @see SkriptLogger#log(Level, String) - */ - @SuppressWarnings("null") - public static void error(final @Nullable String error) { - if (error != null) - SkriptLogger.log(Level.SEVERE, error); - } - - /** - * Use this in {@link Expression#init(Expression[], int, Kleenean, ch.njol.skript.lang.SkriptParser.ParseResult)} (and other methods that are called during the parsing) to log - * errors with a specific {@link ErrorQuality}. - * - * @param error - * @param quality - */ - public static void error(final String error, final ErrorQuality quality) { - SkriptLogger.log(new LogEntry(SkriptLogger.SEVERE, quality, error)); - } - - private final static String EXCEPTION_PREFIX = "#!#! "; - - /** - * Used if something happens that shouldn't happen - * - * @param info Description of the error and additional information - * @return an EmptyStacktraceException to throw if code execution should terminate. - */ - public static EmptyStacktraceException exception(final String... info) { - return exception(null, info); - } - - public static EmptyStacktraceException exception(final @Nullable Throwable cause, final String... info) { - return exception(cause, null, null, info); - } - - public static EmptyStacktraceException exception(final @Nullable Throwable cause, final @Nullable Thread thread, final String... info) { - return exception(cause, thread, null, info); - } - - public static EmptyStacktraceException exception(final @Nullable Throwable cause, final @Nullable TriggerItem item, final String... info) { - return exception(cause, null, item, info); - } - - /** - * Maps Java packages of plugins to descriptions of said plugins. - * This is only done for plugins that depend or soft-depend on Skript. - */ - private static Map pluginPackages = new HashMap<>(); - private static boolean checkedPlugins = false; - - /** - * Used if something happens that shouldn't happen - * - * @param cause exception that shouldn't occur - * @param info Description of the error and additional information - * @return an EmptyStacktraceException to throw if code execution should terminate. - */ - public static EmptyStacktraceException exception(@Nullable Throwable cause, final @Nullable Thread thread, final @Nullable TriggerItem item, final String... info) { - // First error: gather plugin package information - if (!checkedPlugins) { - for (Plugin plugin : Bukkit.getPluginManager().getPlugins()) { - if (plugin.getName().equals("Skript")) // Don't track myself! - continue; - - PluginDescriptionFile desc = plugin.getDescription(); - if (desc.getDepend().contains("Skript") || desc.getSoftDepend().contains("Skript")) { - // Take actual main class out from the qualified name - String[] parts = desc.getMain().split("\\."); // . is special in regexes... - StringBuilder name = new StringBuilder(desc.getMain().length()); - for (int i = 0; i < parts.length - 1; i++) { - name.append(parts[i]).append('.'); - } - - // Put this to map - pluginPackages.put(name.toString(), desc); - if (Skript.debug()) - Skript.info("Identified potential addon: " + desc.getFullName() + " (" + name.toString() + ")"); - } - } - - checkedPlugins = true; // No need to do this next time - } - - String issuesUrl = "https://github.com/SkriptLang/Skript/issues"; - - logEx(); - logEx("[Skript] Severe Error:"); - logEx(info); - logEx(); - logEx("Something went horribly wrong with Skript."); - logEx("This issue is NOT your fault! You probably can't fix it yourself, either."); - - // Parse something useful out of the stack trace - StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); - Set stackPlugins = new HashSet<>(); - for (StackTraceElement s : stackTrace) { // Look through stack trace - for (Entry e : pluginPackages.entrySet()) { // Look through plugins - if (s.getClassName().contains(e.getKey())) // Hey, is this plugin in that stack trace? - stackPlugins.add(e.getValue()); // Yes? Add it to list - } - } - - SkriptUpdater updater = Skript.getInstance().getUpdater(); - - // Check if server platform is supported - if (!isRunningMinecraft(1, 9)) { - logEx("You are running an outdated Minecraft version not supported by Skript."); - logEx("Please update to Minecraft 1.9.4 or later or fix this yourself and send us a pull request."); - logEx("Alternatively, use an older Skript version; do note that those are also unsupported by us."); - logEx(""); - logEx("Again, we do not support Minecraft versions this old."); - } else if (!serverPlatform.supported){ - logEx("Your server platform appears to be unsupported by Skript. It might not work reliably."); - logEx("You can report this at " + issuesUrl + ". However, we may be unable to fix the issue."); - logEx("It is recommended that you switch to Paper or Spigot, should you encounter problems."); - } else if (updater != null && updater.getReleaseStatus() == ReleaseStatus.OUTDATED) { - logEx("You're running outdated version of Skript! Please try updating it NOW; it might fix this."); - logEx("Run /sk update check to get a download link to latest Skript!"); - logEx("You will be given instructions how to report this error if it persists after update."); - } else { - if (pluginPackages.isEmpty()) { - logEx("You should report it at " + issuesUrl + ". Please copy paste this report there (or use paste service)."); - logEx("This ensures that your issue is noticed and will be fixed as soon as possible."); - } else { - logEx("It looks like you are using some plugin(s) that alter how Skript works (addons)."); - if (stackPlugins.isEmpty()) { - logEx("Here is full list of them:"); - StringBuilder pluginsMessage = new StringBuilder(); - for (PluginDescriptionFile desc : pluginPackages.values()) { - pluginsMessage.append(desc.getName()); - String website = desc.getWebsite(); - if (website != null && !website.isEmpty()) // Add website if found - pluginsMessage.append(" (").append(desc.getWebsite()).append(")"); - - pluginsMessage.append(" "); - } - logEx(pluginsMessage.toString()); - logEx("We could not identify which of those are specially related, so this might also be Skript issue."); - } else { - logEx("Following plugins are probably related to this error in some way:"); - StringBuilder pluginsMessage = new StringBuilder(); - for (PluginDescriptionFile desc : stackPlugins) { - pluginsMessage.append(desc.getName()); - String website = desc.getWebsite(); - if (website != null && !website.isEmpty()) // Add website if found - pluginsMessage.append(" (").append(desc.getWebsite()).append(")"); - - pluginsMessage.append(" "); - } - logEx(pluginsMessage.toString()); - } - - logEx("You should try disabling those plugins one by one, trying to find which one causes it."); - logEx("If the error doesn't disappear even after disabling all listed plugins, it is probably Skript issue."); - logEx("In that case, you will be given instruction on how should you report it."); - logEx("On the other hand, if the error disappears when disabling some plugin, report it to author of that plugin."); - logEx("Only if the author tells you to do so, report it to Skript's issue tracker."); - } - } - - logEx(); - logEx("Stack trace:"); - if (cause == null || cause.getStackTrace().length == 0) { - logEx(" warning: no/empty exception given, dumping current stack trace instead"); - cause = new Exception(cause); - } - boolean first = true; - while (cause != null) { - logEx((first ? "" : "Caused by: ") + cause.toString()); - for (final StackTraceElement e : cause.getStackTrace()) - logEx(" at " + e.toString()); - cause = cause.getCause(); - first = false; - } - - logEx(); - logEx("Version Information:"); - if (updater != null) { - ReleaseStatus status = updater.getReleaseStatus(); - logEx(" Skript: " + getVersion() + (status == ReleaseStatus.LATEST ? " (latest)" - : status == ReleaseStatus.OUTDATED ? " (OUTDATED)" - : status == ReleaseStatus.CUSTOM ? " (custom version)" : "")); - ReleaseManifest current = updater.getCurrentRelease(); - logEx(" Flavor: " + current.flavor); - logEx(" Date: " + current.date); - } else { - logEx(" Skript: " + getVersion() + " (unknown; likely custom)"); - } - logEx(" Bukkit: " + Bukkit.getBukkitVersion()); - logEx(" Minecraft: " + getMinecraftVersion()); - logEx(" Java: " + System.getProperty("java.version") + " (" + System.getProperty("java.vm.name") + " " + System.getProperty("java.vm.version") + ")"); - logEx(" OS: " + System.getProperty("os.name") + " " + System.getProperty("os.arch") + " " + System.getProperty("os.version")); - logEx(); - logEx("Server platform: " + serverPlatform.name + (serverPlatform.supported ? "" : " (unsupported)")); - logEx(); - logEx("Current node: " + SkriptLogger.getNode()); - 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() + ")"); - } - logEx(); - logEx("Thread: " + (thread == null ? Thread.currentThread() : thread).getName()); - logEx(); - logEx("Language: " + Language.getName()); - logEx("Link parse mode: " + ChatMessages.linkParseMode); - logEx(); - logEx("End of Error."); - logEx(); - - return new EmptyStacktraceException(); - } - - static void logEx() { - SkriptLogger.LOGGER.severe(EXCEPTION_PREFIX); - } - - 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 + " "; -// } -// }); -// } - - public static void info(final CommandSender sender, final String info) { - sender.sendMessage(SKRIPT_PREFIX + Utils.replaceEnglishChatStyles(info)); - } - - /** - * @param message - * @param permission - * @see #adminBroadcast(String) - */ - public static void broadcast(final String message, final String permission) { - Bukkit.broadcast(SKRIPT_PREFIX + Utils.replaceEnglishChatStyles(message), permission); - } - - public static void adminBroadcast(final String message) { - Bukkit.broadcast(SKRIPT_PREFIX + Utils.replaceEnglishChatStyles(message), "skript.admin"); - } - - /** - * Similar to {@link #info(CommandSender, String)} but no [Skript] prefix is added. - * - * @param sender - * @param info - */ - public static void message(final CommandSender sender, final String info) { - sender.sendMessage(Utils.replaceEnglishChatStyles(info)); - } - - public static void error(final CommandSender sender, final String error) { - sender.sendMessage(SKRIPT_PREFIX + ChatColor.DARK_RED + Utils.replaceEnglishChatStyles(error)); - } - - /** - * Gets the updater instance currently used by Skript. - * @return SkriptUpdater instance. - */ - @Nullable - public SkriptUpdater getUpdater() { - return updater; - } - -} +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * + * Copyright 2011-2017 Peter Güttinger and contributors + */ +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.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.ReadableByteChannel; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Arrays; +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.Scanner; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.logging.Filter; +import java.util.logging.Level; +import java.util.logging.LogRecord; +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 ch.njol.skript.lang.Trigger; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +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.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.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; +import ch.njol.skript.classes.data.BukkitClasses; +import ch.njol.skript.classes.data.BukkitEventValues; +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.JavaClasses; +import ch.njol.skript.classes.data.SkriptClasses; +import ch.njol.skript.command.Commands; +import ch.njol.skript.doc.Documentation; +import ch.njol.skript.events.EvtSkript; +import ch.njol.skript.hooks.Hook; +import ch.njol.skript.lang.Condition; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionInfo; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.SkriptEvent; +import ch.njol.skript.lang.SkriptEventInfo; +import ch.njol.skript.lang.Statement; +import ch.njol.skript.lang.SyntaxElementInfo; +import ch.njol.skript.lang.TriggerItem; +import ch.njol.skript.lang.VariableString; +import ch.njol.skript.lang.function.Functions; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.skript.localization.Language; +import ch.njol.skript.localization.Message; +import ch.njol.skript.log.BukkitLoggerFilter; +import ch.njol.skript.log.CountingLogHandler; +import ch.njol.skript.log.ErrorDescLogHandler; +import ch.njol.skript.log.ErrorQuality; +import ch.njol.skript.log.LogEntry; +import ch.njol.skript.log.LogHandler; +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 ch.njol.skript.registrations.Converters; +import ch.njol.skript.registrations.EventValues; +import ch.njol.skript.timings.SkriptTimings; +import ch.njol.skript.update.ReleaseManifest; +import ch.njol.skript.update.ReleaseStatus; +import ch.njol.skript.update.UpdateManifest; +import ch.njol.skript.update.UpdaterState; +import ch.njol.skript.util.EmptyStacktraceException; +import ch.njol.skript.util.ExceptionUtils; +import ch.njol.skript.util.FileUtils; +import ch.njol.skript.util.Getter; +import ch.njol.skript.util.Task; +import ch.njol.skript.util.Utils; +import ch.njol.skript.util.Version; +import ch.njol.skript.util.chat.BungeeConverter; +import ch.njol.skript.util.chat.ChatMessages; +import ch.njol.skript.variables.Variables; +import ch.njol.util.Closeable; +import ch.njol.util.Kleenean; +import ch.njol.util.NullableChecker; +import ch.njol.util.Pair; +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; + +// TODO meaningful error if someone uses an %expression with percent signs% outside of text or a variable + +/** + * Skript - A Bukkit plugin to modify how Minecraft behaves without having to write a single line of code (You'll likely be writing some code though if you're reading this + * =P) + *

+ * Use this class to extend this plugin's functionality by adding more {@link Condition conditions}, {@link Effect effects}, {@link SimpleExpression expressions}, etc. + *

+ * If your plugin.yml contains 'depend: [Skript]' then your plugin will not start at all if Skript is not present. Add 'softdepend: [Skript]' to your plugin.yml + * if you want your plugin to work even if Skript isn't present, but want to make sure that Skript gets loaded before your plugin. + *

+ * If you use 'softdepend' you can test whether Skript is loaded with 'Bukkit.getPluginManager().getPlugin("Skript") != null' + *

+ * Once you made sure that Skript is loaded you can use Skript.getInstance() whenever you need a reference to the plugin, but you likely won't need it since all API + * methods are static. + * + * @author Peter Güttinger + * @see #registerAddon(JavaPlugin) + * @see #registerCondition(Class, String...) + * @see #registerEffect(Class, String...) + * @see #registerExpression(Class, Class, ExpressionType, String...) + * @see #registerEvent(String, Class, Class, String...) + * @see EventValues#registerEventValue(Class, Class, Getter, int) + * @see Classes#registerClass(ClassInfo) + * @see Comparators#registerComparator(Class, Class, Comparator) + * @see Converters#registerConverter(Class, Class, Converter) + */ +public final class Skript extends JavaPlugin implements Listener { + + // ================ PLUGIN ================ + + @Nullable + private static Skript instance = null; + + private static boolean disabled = false; + + public static Skript getInstance() { + final Skript i = instance; + if (i == null) + throw new IllegalStateException(); + return i; + } + + /** + * Current updater instance used by Skript. + */ + @Nullable + private SkriptUpdater updater; + + public Skript() throws IllegalStateException { + if (instance != null) + throw new IllegalStateException("Cannot create multiple instances of Skript!"); + instance = this; + } + + @Nullable + private static Version version = null; + + public static Version getVersion() { + final Version v = version; + if (v == null) + throw new IllegalStateException(); + return v; + } + + public final static Message m_invalid_reload = new Message("skript.invalid reload"), + m_finished_loading = new Message("skript.finished loading"); + + public static ServerPlatform getServerPlatform() { + if (classExists("net.glowstone.GlowServer")) { + return ServerPlatform.BUKKIT_GLOWSTONE; // Glowstone has timings too, so must check for it first + } else if (classExists("co.aikar.timings.Timings")) { + return ServerPlatform.BUKKIT_PAPER; // Could be Sponge, but it doesn't work at all at the moment + } else if (classExists("org.spigotmc.SpigotConfig")) { + return ServerPlatform.BUKKIT_SPIGOT; + } else if (classExists("org.bukkit.craftbukkit.CraftServer") || classExists("org.bukkit.craftbukkit.Main")) { + // At some point, CraftServer got removed or moved + return ServerPlatform.BUKKIT_CRAFTBUKKIT; + } else { // Probably some ancient Bukkit implementation + return ServerPlatform.BUKKIT_UNKNOWN; + } + } + + /** + * Checks if server software and Minecraft version are supported. + * Prints errors or warnings to console if something is wrong. + * @return Whether Skript can continue loading at all. + */ + private static boolean checkServerPlatform() { + String bukkitV = Bukkit.getBukkitVersion(); + Matcher m = Pattern.compile("\\d+\\.\\d+(\\.\\d+)?").matcher(bukkitV); + if (!m.find()) { + Skript.error("The Bukkit version '" + bukkitV + "' does not contain a version number which is required for Skript to enable or disable certain features. " + + "Skript will still work, but you might get random errors if you use features that are not available in your version of Bukkit."); + minecraftVersion = new Version(666, 0, 0); + } else { + minecraftVersion = new Version("" + m.group()); + } + Skript.debug("Loading for Minecraft " + minecraftVersion); + + // Check that MC version is supported + if (!isRunningMinecraft(1, 9)) { + if (isRunningMinecraft(1, 8)) { // 1.8 probably works, but let's spit a warning + Skript.warning("Using this version of Skript on 1.8 is highly discouraged."); + Skript.warning("Some features have been disabled; use older Skript to restore them."); + Skript.warning("Also, there are probably bugs. And since 1.8 is not supported, they will not be fixed"); + } else { // Older versions definitely do not work + Skript.error("This version of Skript does not work with Minecraft " + minecraftVersion); + Skript.error("You probably want Skript 2.2 or 2.1 (Google to find where to get them)"); + Skript.error("Note that those versions are, of course, completely unsupported!"); + return false; + } + } + + // Check that current server platform is somewhat supported + serverPlatform = getServerPlatform(); + Skript.debug("Server platform: " + serverPlatform); + if (!serverPlatform.works) { + Skript.error("It seems that this server platform (" + serverPlatform.name + ") does not work with Skript."); + if (SkriptConfig.allowUnsafePlatforms.value()) { + Skript.error("However, you have chosen to ignore this. Skript will probably still not work."); + } else { + Skript.error("To prevent potentially unsafe behaviour, Skript has been disabled."); + Skript.error("You may re-enable it by adding a configuration option 'allow unsafe platforms: true'"); + Skript.error("Note that it is unlikely that Skript works correctly even if you do so."); + Skript.error("A better idea would be to install Paper or Spigot in place of your current server."); + return false; + } + } else if (!serverPlatform.supported) { + Skript.warning("This server platform (" + serverPlatform.name + ") is not supported by Skript."); + Skript.warning("It will still probably work, but if it does not, you are on your own."); + Skript.warning("Skript officially supports Paper and Spigot."); + } + // If nothing got triggered, everything is probably ok + return true; + } + + @Override + public void onEnable() { + if (disabled) { + Skript.error(m_invalid_reload.toString()); + setEnabled(false); + return; + } + + handleJvmArguments(); // JVM arguments + + version = new Version("" + getDescription().getVersion()); // Skript version + + Language.loadDefault(getAddonInstance()); + + Workarounds.init(); + + // Start the updater + // Note: if config prohibits update checks, it will NOT do network connections + try { + this.updater = new SkriptUpdater(); + } catch (Exception e) { + Skript.exception(e, "Update checker could not be initialized."); + } + + if (!getDataFolder().isDirectory()) + getDataFolder().mkdirs(); + + final File scripts = new File(getDataFolder(), SCRIPTSFOLDER); + final File config = new File(getDataFolder(), "config.sk"); + final File features = new File(getDataFolder(), "features.sk"); + if (!scripts.isDirectory() || !config.exists() || !features.exists()) { + ZipFile f = null; + try { + boolean populateExamples = false; + if (!scripts.isDirectory()) { + if (!scripts.mkdirs()) + throw new IOException("Could not create the directory " + scripts); + populateExamples = true; + } + f = new ZipFile(getFile()); + for (final ZipEntry e : new EnumerationIterable(f.entries())) { + if (e.isDirectory()) + continue; + File saveTo = null; + if (e.getName().startsWith(SCRIPTSFOLDER + "/") && populateExamples) { + saveTo = new File(scripts, e.getName().replaceAll("^scripts\\/", "")); + } else if (e.getName().equals("config.sk")) { + if (!config.exists()) + saveTo = config; +// } else if (e.getName().startsWith("aliases-") && e.getName().endsWith(".sk") && !e.getName().contains("/")) { +// final File af = new File(getDataFolder(), e.getName()); +// if (!af.exists()) +// saveTo = af; + } else if (e.getName().startsWith("features.sk")) { + if (!features.exists()) + saveTo = features; + } + if (saveTo != null) { + final InputStream in = f.getInputStream(e); + try { + assert in != null; + FileUtils.save(in, saveTo); + } finally { + in.close(); + } + } + } + info("Successfully generated the config and the example scripts."); + } catch (final ZipException e) {} catch (final IOException e) { + error("Error generating the default files: " + ExceptionUtils.toString(e)); + } finally { + if (f != null) { + try { + f.close(); + } catch (final IOException e) {} + } + } + } + + // Load classes which are always safe to use + new JavaClasses(); // These may be needed in configuration + + // And then not-so-safe classes + Throwable classLoadError = null; + try { + new SkriptClasses(); + } catch (Throwable e) { + classLoadError = e; + } + + // 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(); + + // Use the updater, now that it has been configured to (not) do stuff + if (updater != null) { + CommandSender console = Bukkit.getConsoleSender(); + assert console != null; + assert updater != null; + updater.updateCheck(console); + } + + // 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; + } + + BukkitUnsafe.initialize(); // Needed for aliases + Aliases.load(); // Loaded before anything that might use them + + // If loading can continue (platform ok), check for potentially thrown error + if (classLoadError != null) { + exception(classLoadError); + setEnabled(false); + return; + } + + PluginCommand skriptCommand = getCommand("skript"); + assert skriptCommand != null; // It is defined, unless build is corrupted or something like that + skriptCommand.setExecutor(new SkriptCommand()); + + // Load Bukkit stuff. It is done after platform check, because something might be missing! + new BukkitClasses(); + new BukkitEventValues(); + + new DefaultComparators(); + new DefaultConverters(); + new DefaultFunctions(); + + ChatMessages.registerListeners(); + + try { + getAddonInstance().loadClasses("ch.njol.skript", "conditions", "effects", "events", "expressions", "entity"); + } catch (final Exception e) { + exception(e, "Could not load required .class files: " + e.getLocalizedMessage()); + setEnabled(false); + return; + } + + Language.setUseLocal(true); + + Commands.registerListeners(); + + if (logNormal()) + info(" " + Language.get("skript.copyright")); + + final long tick = testing() ? Bukkit.getWorlds().get(0).getFullTime() : 0; + Bukkit.getScheduler().scheduleSyncDelayedTask(this, new Runnable() { + @SuppressWarnings("synthetic-access") + @Override + public void run() { + assert Bukkit.getWorlds().get(0).getFullTime() == tick; + + // Load hooks from Skript jar + try { + try (JarFile jar = new JarFile(getFile())) { + for (final 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) { + hook.getDeclaredConstructor().setAccessible(true); + hook.getDeclaredConstructor().newInstance(); + } + } catch (final ClassNotFoundException ex) { + Skript.exception(ex, "Cannot load class " + c); + } catch (final ExceptionInInitializerError err) { + Skript.exception(err.getCause(), "Class " + c + " generated an exception while loading"); + } + continue; + } + } + } + } catch (final Exception e) { + error("Error while loading plugin hooks" + (e.getLocalizedMessage() == null ? "" : ": " + e.getLocalizedMessage())); + Skript.exception(e); + } + + Language.setUseLocal(false); + + stopAcceptingRegistrations(); + + + Documentation.generate(); // TODO move to test classes? + + if (logNormal()) + info("Loading variables..."); + final long vls = System.currentTimeMillis(); + + final LogHandler h = SkriptLogger.startLogHandler(new ErrorDescLogHandler() { +// private final List log = new ArrayList(); + + @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 { +// log.add(entry); +// return LogResult.CACHED; + 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 onStop() { + super.onStop(); +// SkriptLogger.logAll(log); + } + }); + final CountingLogHandler c = SkriptLogger.startLogHandler(new CountingLogHandler(SkriptLogger.SEVERE)); + try { + if (!Variables.load()) + if (c.getCount() == 0) + error("(no information available)"); + } finally { + c.stop(); + h.stop(); + } + + final long vld = System.currentTimeMillis() - vls; + if (logNormal()) + info("Loaded " + Variables.numVariables() + " variables in " + ((vld / 100) / 10.) + " seconds"); + + ScriptLoader.loadScripts(); + + Skript.info(m_finished_loading.toString()); + + EvtSkript.onSkriptStart(); + + final 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(); + } + }); + + Skript.metrics = metrics; + + // suppresses the "can't keep up" warning after loading all scripts + final Filter f = new Filter() { + @Override + public boolean isLoggable(final @Nullable LogRecord record) { + if (record == null) + return false; + if (record.getMessage() != null && record.getMessage().toLowerCase(Locale.ENGLISH).startsWith("can't keep up!")) + return false; + return true; + } + }; + BukkitLoggerFilter.addFilter(f); + Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.this, new Runnable() { + @Override + public void run() { + BukkitLoggerFilter.removeFilter(f); + } + }, 1); + } + }); + + Bukkit.getPluginManager().registerEvents(new Listener() { + @EventHandler + public void onJoin(final PlayerJoinEvent e) { + if (e.getPlayer().hasPermission("skript.admin")) { + new Task(Skript.this, 0) { + @Override + public void run() { + Player p = e.getPlayer(); + SkriptUpdater updater = getUpdater(); + if (p == null || updater == null) + return; + + // Don't actually check for updates to avoid breaking Github rate limit + if (updater.getReleaseStatus() == ReleaseStatus.OUTDATED) { + // Last check indicated that an update is available + UpdateManifest update = updater.getUpdateManifest(); + assert update != null; // Because we just checked that one is available + Skript.info(p, "" + SkriptUpdater.m_update_available.toString(update.id, Skript.getVersion())); + p.spigot().sendMessage(BungeeConverter.convert(ChatMessages.parseToArray( + "Download it at: " + update.downloadUrl))); + } + } + }; + } + } + }, this); + + // Tell Timings that we are here! + SkriptTimings.setSkript(this); + } + + /** + * Handles -Dskript.stuff command line arguments. + */ + private void handleJvmArguments() { + Path folder = getDataFolder().toPath(); + + /* + * Burger is a Python application that extracts data from Minecraft. + * Datasets for most common versions are available for download. + * Skript uses them to provide minecraft:material to Bukkit + * Material mappings on Minecraft 1.12 and older. + */ + String burgerEnabled = System.getProperty("skript.burger.enable"); + if (burgerEnabled != null) { + String version = System.getProperty("skript.burger.version"); + String burgerInput; + if (version == null) { // User should have provided JSON file path + String inputFile = System.getProperty("skript.burger.file"); + if (inputFile == null) { + Skript.exception("burger enabled but skript.burger.file not provided"); + return; + } + try { + burgerInput = new String(Files.readAllBytes(Paths.get(inputFile)), StandardCharsets.UTF_8); + } catch (IOException e) { + Skript.exception(e); + return; + } + } else { // Try to download Burger dataset for this version + try { + Path data = folder.resolve("burger-" + version + ".json"); + if (!Files.exists(data)) { + URL url = new URL("https://pokechu22.github.io/Burger/" + version + ".json"); + try (InputStream is = url.openStream()) { + Files.copy(is, data); + } + } + burgerInput = new String(Files.readAllBytes(data), StandardCharsets.UTF_8); + } catch (IOException e) { + Skript.exception(e); + return; + } + } + + // Use BurgerHelper to create some mappings, then dump them as JSON + try { + BurgerHelper burger = new BurgerHelper(burgerInput); + Map materials = burger.mapMaterials(); + Map ids = BurgerHelper.mapIds(); + + Gson gson = new Gson(); + Files.write(folder.resolve("materials_mappings.json"), gson.toJson(materials) + .getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE); + Files.write(folder.resolve("id_mappings.json"), gson.toJson(ids) + .getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE); + } catch (IOException e) { + Skript.exception(e); + } + } + } + + private static Version minecraftVersion = new Version(666); + private static ServerPlatform serverPlatform = ServerPlatform.BUKKIT_UNKNOWN; // Start with unknown... onLoad changes this + + public static Version getMinecraftVersion() { + return minecraftVersion; + } + + /** + * @return Whether this server is running CraftBukkit + */ + public static boolean isRunningCraftBukkit() { + return serverPlatform == ServerPlatform.BUKKIT_CRAFTBUKKIT; + } + + /** + * @return Whether this server is running Minecraft major.minor or higher + */ + public static boolean isRunningMinecraft(final int major, final int minor) { + return minecraftVersion.compareTo(major, minor) >= 0; + } + + public static boolean isRunningMinecraft(final int major, final int minor, final int revision) { + return minecraftVersion.compareTo(major, minor, revision) >= 0; + } + + public static boolean isRunningMinecraft(final Version v) { + return minecraftVersion.compareTo(v) >= 0; + } + + /** + * Used to test whether certain Bukkit features are supported. + * + * @param className + * @return Whether the given class exists. + * @deprecated use {@link #classExists(String)} + */ + @Deprecated + public static boolean supports(final String className) { + return classExists(className); + } + + /** + * Tests whether a given class exists in the classpath. + * + * @param className The {@link Class#getCanonicalName() canonical name} of the class + * @return Whether the given class exists. + */ + public static boolean classExists(final String className) { + try { + Class.forName(className); + return true; + } catch (final ClassNotFoundException e) { + return false; + } + } + + /** + * Tests whether a method exists in the given class. + * + * @param c The class + * @param methodName The name of the method + * @param parameterTypes The parameter types of the method + * @return Whether the given method exists. + */ + public static boolean methodExists(final Class c, final String methodName, final Class... parameterTypes) { + try { + c.getDeclaredMethod(methodName, parameterTypes); + return true; + } catch (final NoSuchMethodException e) { + return false; + } catch (final SecurityException e) { + return false; + } + } + + /** + * Tests whether a method exists in the given class, and whether the return type matches the expected one. + *

+ * Note that this method doesn't work properly if multiple methods with the same name and parameters exist but have different return types. + * + * @param c The class + * @param methodName The name of the method + * @param parameterTypes The parameter types of the method + * @param returnType The expected return type + * @return Whether the given method exists. + */ + public static boolean methodExists(final Class c, final String methodName, final Class[] parameterTypes, final Class returnType) { + try { + final Method m = c.getDeclaredMethod(methodName, parameterTypes); + return m.getReturnType() == returnType; + } catch (final NoSuchMethodException e) { + return false; + } catch (final SecurityException e) { + return false; + } + } + + /** + * Tests whether a field exists in the given class. + * + * @param c The class + * @param fieldName The name of the field + * @return Whether the given field exists. + */ + public static boolean fieldExists(final Class c, final String fieldName) { + try { + c.getDeclaredField(fieldName); + return true; + } catch (final NoSuchFieldException e) { + return false; + } catch (final SecurityException e) { + return false; + } + } + + @Nullable + static Metrics metrics; + + @Nullable + public static Metrics getMetrics() { + return metrics; + } + + /** + * Clears triggers, commands, functions and variable names + */ + static void disableScripts() { + VariableString.variableNames.clear(); + SkriptEventHandler.removeAllTriggers(); + Commands.clearCommands(); + Functions.clearFunctions(); + } + + /** + * Prints errors from reloading the config & scripts + */ + static void reload() { + if (!ScriptLoader.loadAsync) + disableScripts(); + reloadMainConfig(); + reloadAliases(); + ScriptLoader.loadScripts(); + } + + /** + * Prints errors + */ + static void reloadScripts() { + if (!ScriptLoader.loadAsync) + disableScripts(); + ScriptLoader.loadScripts(); + } + + /** + * Prints errors + */ + static void reloadMainConfig() { + SkriptConfig.load(); + } + + /** + * Prints errors + */ + static void reloadAliases() { + Aliases.clear(); + Aliases.load(); + } + + @SuppressWarnings("null") + private final static Collection closeOnDisable = Collections.synchronizedCollection(new ArrayList()); + + /** + * Registers a Closeable that should be closed when this plugin is disabled. + *

+ * All registered Closeables will be closed after all scripts have been stopped. + * + * @param closeable + */ + public static void closeOnDisable(final Closeable closeable) { + closeOnDisable.add(closeable); + } + + @Override + public void onDisable() { + if (disabled) + return; + disabled = true; + + EvtSkript.onSkriptStop(); // TODO [code style] warn user about delays in Skript stop events + + disableScripts(); + + Bukkit.getScheduler().cancelTasks(this); + + for (final Closeable c : closeOnDisable) { + try { + c.close(); + } catch (final Exception e) { + Skript.exception(e, "An error occurred while shutting down.", "This might or might not cause any issues."); + } + } + + // unset static fields to prevent memory leaks as Bukkit reloads the classes with a different classloader on reload + // async to not slow down server reload, delayed to not slow down server shutdown + final Thread t = newThread(new Runnable() { + @SuppressWarnings("synthetic-access") + @Override + public void run() { + try { + Thread.sleep(10000); + } catch (final InterruptedException e) {} + try { + final Field modifiers = Field.class.getDeclaredField("modifiers"); + modifiers.setAccessible(true); + final JarFile jar = new JarFile(getFile()); + try { + for (final JarEntry e : new EnumerationIterable<>(jar.entries())) { + if (e.getName().endsWith(".class")) { + try { + final Class c = Class.forName(e.getName().replace('/', '.').substring(0, e.getName().length() - ".class".length()), false, getClassLoader()); + for (final Field f : c.getDeclaredFields()) { + if (Modifier.isStatic(f.getModifiers()) && !f.getType().isPrimitive()) { + if (Modifier.isFinal(f.getModifiers())) { + modifiers.setInt(f, f.getModifiers() & ~Modifier.FINAL); + } + f.setAccessible(true); + f.set(null, null); + } + } + } catch (final Throwable ex) { + if (testing()) + ex.printStackTrace(); + } + } + } + } finally { + jar.close(); + } + } catch (final Throwable ex) { + if (testing()) + ex.printStackTrace(); + } + } + }, "Skript cleanup thread"); + t.setPriority(Thread.MIN_PRIORITY); + t.setDaemon(true); + t.start(); + } + + // ================ CONSTANTS, OPTIONS & OTHER ================ + + public final static String SCRIPTSFOLDER = "scripts"; + + public static void outdatedError() { + error("Skript v" + getInstance().getDescription().getVersion() + " is not fully compatible with Bukkit " + Bukkit.getVersion() + ". Some feature(s) will be broken until you update Skript."); + } + + public static void outdatedError(final Exception e) { + outdatedError(); + if (testing()) + e.printStackTrace(); + } + + /** + * A small value, useful for comparing doubles or floats. + *

+ * E.g. to test whether two floating-point numbers are equal: + * + *

+	 * Math.abs(a - b) < Skript.EPSILON
+	 * 
+ * + * or whether a location is within a specific radius of another location: + * + *
+	 * location.distanceSquared(center) - radius * radius < Skript.EPSILON
+	 * 
+ * + * @see #EPSILON_MULT + */ + public final static double EPSILON = 1e-10; + /** + * A value a bit larger than 1 + * + * @see #EPSILON + */ + public final static double EPSILON_MULT = 1.00001; + + /** + * The maximum ID a block can have in Minecraft. + */ + public final static int MAXBLOCKID = 255; + /** + * The maximum data value of Minecraft, i.e. Short.MAX_VALUE - Short.MIN_VALUE. + */ + public final static int MAXDATAVALUE = Short.MAX_VALUE - Short.MIN_VALUE; + + // TODO localise Infinity, -Infinity, NaN (and decimal point?) + public static String toString(final double n) { + return StringUtils.toString(n, SkriptConfig.numberAccuracy.value()); + } + + public final static UncaughtExceptionHandler UEH = new UncaughtExceptionHandler() { + @Override + public void uncaughtException(final @Nullable Thread t, final @Nullable Throwable e) { + Skript.exception(e, "Exception in thread " + (t == null ? null : t.getName())); + } + }; + + /** + * Creates a new Thread and sets its UncaughtExceptionHandler. The Thread is not started automatically. + */ + public static Thread newThread(final Runnable r, final String name) { + final Thread t = new Thread(r, name); + t.setUncaughtExceptionHandler(UEH); + return t; + } + + // ================ REGISTRATIONS ================ + + private static boolean acceptRegistrations = true; + + public static boolean isAcceptRegistrations() { + return acceptRegistrations; + } + + public static void checkAcceptRegistrations() { + if (!acceptRegistrations) + throw new SkriptAPIException("Registering is disabled after initialisation!"); + } + + private static void stopAcceptingRegistrations() { + acceptRegistrations = false; + + Converters.createMissingConverters(); + + Classes.onRegistrationsStop(); + } + + // ================ ADDONS ================ + + private final static HashMap addons = new HashMap<>(); + + /** + * Registers an addon to Skript. This is currently not required for addons to work, but the returned {@link SkriptAddon} provides useful methods for registering syntax elements + * and adding new strings to Skript's localization system (e.g. the required "types.[type]" strings for registered classes). + * + * @param p The plugin + */ + public static SkriptAddon registerAddon(final JavaPlugin p) { + checkAcceptRegistrations(); + if (addons.containsKey(p.getName())) + throw new IllegalArgumentException("The plugin " + p.getName() + " is already registered"); + final SkriptAddon addon = new SkriptAddon(p); + addons.put(p.getName(), addon); + return addon; + } + + @Nullable + public static SkriptAddon getAddon(final JavaPlugin p) { + return addons.get(p.getName()); + } + + @Nullable + public static SkriptAddon getAddon(final String name) { + return addons.get(name); + } + + @SuppressWarnings("null") + public static Collection getAddons() { + return Collections.unmodifiableCollection(addons.values()); + } + + @Nullable + private static SkriptAddon addon; + + /** + * @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; + } + + // ================ CONDITIONS & EFFECTS ================ + + private final static Collection> conditions = new ArrayList<>(50); + private final static Collection> effects = new ArrayList<>(50); + private final static Collection> statements = new ArrayList<>(100); + + /** + * registers a {@link Condition}. + * + * @param condition The condition's class + * @param patterns Skript patterns to match this condition + */ + public static void registerCondition(final Class condition, final String... patterns) throws IllegalArgumentException { + checkAcceptRegistrations(); + String originClassPath = Thread.currentThread().getStackTrace()[2].getClassName(); + final SyntaxElementInfo info = new SyntaxElementInfo<>(patterns, condition, originClassPath); + conditions.add(info); + statements.add(info); + } + + /** + * Registers an {@link Effect}. + * + * @param effect The effect's class + * @param patterns Skript patterns to match this effect + */ + public static void registerEffect(final Class effect, final String... patterns) throws IllegalArgumentException { + checkAcceptRegistrations(); + String originClassPath = Thread.currentThread().getStackTrace()[2].getClassName(); + final SyntaxElementInfo info = new SyntaxElementInfo<>(patterns, effect, originClassPath); + effects.add(info); + statements.add(info); + } + + public static Collection> getStatements() { + return statements; + } + + public static Collection> getConditions() { + return conditions; + } + + public static Collection> getEffects() { + return effects; + } + + // ================ EXPRESSIONS ================ + + private final static List> expressions = new ArrayList<>(100); + + private final static int[] expressionTypesStartIndices = new int[ExpressionType.values().length]; + + /** + * Registers an expression. + * + * @param c The expression's class + * @param returnType The superclass of all values returned by the expression + * @param type The expression's {@link ExpressionType type}. This is used to determine in which order to try to parse expressions. + * @param patterns Skript patterns that match this expression + * @throws IllegalArgumentException if returnType is not a normal class + */ + public static , T> void registerExpression(final Class c, final Class returnType, final ExpressionType type, final String... patterns) throws IllegalArgumentException { + checkAcceptRegistrations(); + if (returnType.isAnnotation() || returnType.isArray() || returnType.isPrimitive()) + throw new IllegalArgumentException("returnType must be a normal type"); + String originClassPath = Thread.currentThread().getStackTrace()[2].getClassName(); + final ExpressionInfo info = new ExpressionInfo<>(patterns, returnType, c, originClassPath); + for (int i = type.ordinal() + 1; i < ExpressionType.values().length; i++) { + expressionTypesStartIndices[i]++; + } + expressions.add(expressionTypesStartIndices[type.ordinal()], info); + } + + @SuppressWarnings("null") + public static Iterator> getExpressions() { + return expressions.iterator(); + } + + public static Iterator> getExpressions(final Class... returnTypes) { + return new CheckedIterator<>(getExpressions(), new NullableChecker>() { + @Override + public boolean check(final @Nullable ExpressionInfo i) { + if (i == null || i.returnType == Object.class) + return true; + for (final Class returnType : returnTypes) { + assert returnType != null; + if (Converters.converterExists(i.returnType, returnType)) + return true; + } + return false; + } + }); + } + + // ================ EVENTS ================ + + private final static Collection> events = new ArrayList<>(50); + + /** + * Registers an event. + * + * @param name Capitalised name of the event without leading "On" which is added automatically (Start the name with an asterisk to prevent this). Used for error messages and + * the documentation. + * @param c The event's class + * @param event The Bukkit event this event applies to + * @param patterns Skript patterns to match this event + * @return A SkriptEventInfo representing the registered event. Used to generate Skript's documentation. + */ + public static SkriptEventInfo registerEvent(final String name, final Class c, final Class event, final String... patterns) { + checkAcceptRegistrations(); + String originClassPath = Thread.currentThread().getStackTrace()[2].getClassName(); + assert originClassPath != null; + final SkriptEventInfo r = new SkriptEventInfo<>(name, patterns, c, originClassPath, CollectionUtils.array(event)); + events.add(r); + return r; + } + + /** + * Registers an event. + * + * @param name The name of the event, used for error messages + * @param c The event's class + * @param events The Bukkit events this event applies to + * @param patterns Skript patterns to match this event + * @return A SkriptEventInfo representing the registered event. Used to generate Skript's documentation. + */ + public static SkriptEventInfo registerEvent(final String name, final Class c, final Class[] events, final String... patterns) { + checkAcceptRegistrations(); + String originClassPath = Thread.currentThread().getStackTrace()[2].getClassName(); + assert originClassPath != null; + final SkriptEventInfo r = new SkriptEventInfo<>(name, patterns, c, originClassPath, events); + Skript.events.add(r); + return r; + } + + public static Collection> getEvents() { + return events; + } + + // ================ COMMANDS ================ + + /** + * Dispatches a command with calling command events + * + * @param sender + * @param command + * @return Whether the command was run + */ + public static boolean dispatchCommand(final CommandSender sender, final String command) { + try { + if (sender instanceof Player) { + final PlayerCommandPreprocessEvent e = new PlayerCommandPreprocessEvent((Player) sender, "/" + command); + Bukkit.getPluginManager().callEvent(e); + if (e.isCancelled() || e.getMessage() == null || !e.getMessage().startsWith("/")) + return false; + return Bukkit.dispatchCommand(e.getPlayer(), e.getMessage().substring(1)); + } else { + final ServerCommandEvent e = new ServerCommandEvent(sender, command); + Bukkit.getPluginManager().callEvent(e); + if (e.getCommand() == null || e.getCommand().isEmpty()) + return false; + return Bukkit.dispatchCommand(e.getSender(), e.getCommand()); + } + } catch (final Exception ex) { + ex.printStackTrace(); // just like Bukkit + return false; + } + } + + // ================ LOGGING ================ + + public static boolean logNormal() { + return SkriptLogger.log(Verbosity.NORMAL); + } + + public static boolean logHigh() { + return SkriptLogger.log(Verbosity.HIGH); + } + + public static boolean logVeryHigh() { + return SkriptLogger.log(Verbosity.VERY_HIGH); + } + + public static boolean debug() { + return SkriptLogger.debug(); + } + + public static boolean testing() { + return debug() || Skript.class.desiredAssertionStatus(); + } + + public static boolean log(final Verbosity minVerb) { + return SkriptLogger.log(minVerb); + } + + public static void debug(final String info) { + if (!debug()) + return; + SkriptLogger.log(SkriptLogger.DEBUG, info); + } + + /** + * @see SkriptLogger#log(Level, String) + */ + @SuppressWarnings("null") + public static void info(final String info) { + SkriptLogger.log(Level.INFO, info); + } + + /** + * @see SkriptLogger#log(Level, String) + */ + @SuppressWarnings("null") + public static void warning(final String warning) { + SkriptLogger.log(Level.WARNING, warning); + } + + /** + * @see SkriptLogger#log(Level, String) + */ + @SuppressWarnings("null") + public static void error(final @Nullable String error) { + if (error != null) + SkriptLogger.log(Level.SEVERE, error); + } + + /** + * Use this in {@link Expression#init(Expression[], int, Kleenean, ch.njol.skript.lang.SkriptParser.ParseResult)} (and other methods that are called during the parsing) to log + * errors with a specific {@link ErrorQuality}. + * + * @param error + * @param quality + */ + public static void error(final String error, final ErrorQuality quality) { + SkriptLogger.log(new LogEntry(SkriptLogger.SEVERE, quality, error)); + } + + private final static String EXCEPTION_PREFIX = "#!#! "; + + /** + * Used if something happens that shouldn't happen + * + * @param info Description of the error and additional information + * @return an EmptyStacktraceException to throw if code execution should terminate. + */ + public static EmptyStacktraceException exception(final String... info) { + return exception(null, info); + } + + public static EmptyStacktraceException exception(final @Nullable Throwable cause, final String... info) { + return exception(cause, null, null, info); + } + + public static EmptyStacktraceException exception(final @Nullable Throwable cause, final @Nullable Thread thread, final String... info) { + return exception(cause, thread, null, info); + } + + public static EmptyStacktraceException exception(final @Nullable Throwable cause, final @Nullable TriggerItem item, final String... info) { + return exception(cause, null, item, info); + } + + /** + * Maps Java packages of plugins to descriptions of said plugins. + * This is only done for plugins that depend or soft-depend on Skript. + */ + private static Map pluginPackages = new HashMap<>(); + private static boolean checkedPlugins = false; + + /** + * Used if something happens that shouldn't happen + * + * @param cause exception that shouldn't occur + * @param info Description of the error and additional information + * @return an EmptyStacktraceException to throw if code execution should terminate. + */ + public static EmptyStacktraceException exception(@Nullable Throwable cause, final @Nullable Thread thread, final @Nullable TriggerItem item, final String... info) { + // First error: gather plugin package information + if (!checkedPlugins) { + for (Plugin plugin : Bukkit.getPluginManager().getPlugins()) { + if (plugin.getName().equals("Skript")) // Don't track myself! + continue; + + PluginDescriptionFile desc = plugin.getDescription(); + if (desc.getDepend().contains("Skript") || desc.getSoftDepend().contains("Skript")) { + // Take actual main class out from the qualified name + String[] parts = desc.getMain().split("\\."); // . is special in regexes... + StringBuilder name = new StringBuilder(desc.getMain().length()); + for (int i = 0; i < parts.length - 1; i++) { + name.append(parts[i]).append('.'); + } + + // Put this to map + pluginPackages.put(name.toString(), desc); + if (Skript.debug()) + Skript.info("Identified potential addon: " + desc.getFullName() + " (" + name.toString() + ")"); + } + } + + checkedPlugins = true; // No need to do this next time + } + + String issuesUrl = "https://github.com/SkriptLang/Skript/issues"; + + logEx(); + logEx("[Skript] Severe Error:"); + logEx(info); + logEx(); + logEx("Something went horribly wrong with Skript."); + logEx("This issue is NOT your fault! You probably can't fix it yourself, either."); + + // Parse something useful out of the stack trace + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + Set stackPlugins = new HashSet<>(); + for (StackTraceElement s : stackTrace) { // Look through stack trace + for (Entry e : pluginPackages.entrySet()) { // Look through plugins + if (s.getClassName().contains(e.getKey())) // Hey, is this plugin in that stack trace? + stackPlugins.add(e.getValue()); // Yes? Add it to list + } + } + + SkriptUpdater updater = Skript.getInstance().getUpdater(); + + // Check if server platform is supported + if (!isRunningMinecraft(1, 9)) { + logEx("You are running an outdated Minecraft version not supported by Skript."); + logEx("Please update to Minecraft 1.9.4 or later or fix this yourself and send us a pull request."); + logEx("Alternatively, use an older Skript version; do note that those are also unsupported by us."); + logEx(""); + logEx("Again, we do not support Minecraft versions this old."); + } else if (!serverPlatform.supported){ + logEx("Your server platform appears to be unsupported by Skript. It might not work reliably."); + logEx("You can report this at " + issuesUrl + ". However, we may be unable to fix the issue."); + logEx("It is recommended that you switch to Paper or Spigot, should you encounter problems."); + } else if (updater != null && updater.getReleaseStatus() == ReleaseStatus.OUTDATED) { + logEx("You're running outdated version of Skript! Please try updating it NOW; it might fix this."); + logEx("Run /sk update check to get a download link to latest Skript!"); + logEx("You will be given instructions how to report this error if it persists after update."); + } else { + if (pluginPackages.isEmpty()) { + logEx("You should report it at " + issuesUrl + ". Please copy paste this report there (or use paste service)."); + logEx("This ensures that your issue is noticed and will be fixed as soon as possible."); + } else { + logEx("It looks like you are using some plugin(s) that alter how Skript works (addons)."); + if (stackPlugins.isEmpty()) { + logEx("Here is full list of them:"); + StringBuilder pluginsMessage = new StringBuilder(); + for (PluginDescriptionFile desc : pluginPackages.values()) { + pluginsMessage.append(desc.getName()); + String website = desc.getWebsite(); + if (website != null && !website.isEmpty()) // Add website if found + pluginsMessage.append(" (").append(desc.getWebsite()).append(")"); + + pluginsMessage.append(" "); + } + logEx(pluginsMessage.toString()); + logEx("We could not identify which of those are specially related, so this might also be Skript issue."); + } else { + logEx("Following plugins are probably related to this error in some way:"); + StringBuilder pluginsMessage = new StringBuilder(); + for (PluginDescriptionFile desc : stackPlugins) { + pluginsMessage.append(desc.getName()); + String website = desc.getWebsite(); + if (website != null && !website.isEmpty()) // Add website if found + pluginsMessage.append(" (").append(desc.getWebsite()).append(")"); + + pluginsMessage.append(" "); + } + logEx(pluginsMessage.toString()); + } + + logEx("You should try disabling those plugins one by one, trying to find which one causes it."); + logEx("If the error doesn't disappear even after disabling all listed plugins, it is probably Skript issue."); + logEx("In that case, you will be given instruction on how should you report it."); + logEx("On the other hand, if the error disappears when disabling some plugin, report it to author of that plugin."); + logEx("Only if the author tells you to do so, report it to Skript's issue tracker."); + } + } + + logEx(); + logEx("Stack trace:"); + if (cause == null || cause.getStackTrace().length == 0) { + logEx(" warning: no/empty exception given, dumping current stack trace instead"); + cause = new Exception(cause); + } + boolean first = true; + while (cause != null) { + logEx((first ? "" : "Caused by: ") + cause.toString()); + for (final StackTraceElement e : cause.getStackTrace()) + logEx(" at " + e.toString()); + cause = cause.getCause(); + first = false; + } + + logEx(); + logEx("Version Information:"); + if (updater != null) { + ReleaseStatus status = updater.getReleaseStatus(); + logEx(" Skript: " + getVersion() + (status == ReleaseStatus.LATEST ? " (latest)" + : status == ReleaseStatus.OUTDATED ? " (OUTDATED)" + : status == ReleaseStatus.CUSTOM ? " (custom version)" : "")); + ReleaseManifest current = updater.getCurrentRelease(); + logEx(" Flavor: " + current.flavor); + logEx(" Date: " + current.date); + } else { + logEx(" Skript: " + getVersion() + " (unknown; likely custom)"); + } + logEx(" Bukkit: " + Bukkit.getBukkitVersion()); + logEx(" Minecraft: " + getMinecraftVersion()); + logEx(" Java: " + System.getProperty("java.version") + " (" + System.getProperty("java.vm.name") + " " + System.getProperty("java.vm.version") + ")"); + logEx(" OS: " + System.getProperty("os.name") + " " + System.getProperty("os.arch") + " " + System.getProperty("os.version")); + logEx(); + logEx("Server platform: " + serverPlatform.name + (serverPlatform.supported ? "" : " (unsupported)")); + logEx(); + logEx("Current node: " + SkriptLogger.getNode()); + 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() + ")"); + } + logEx(); + logEx("Thread: " + (thread == null ? Thread.currentThread() : thread).getName()); + logEx(); + logEx("Language: " + Language.getName()); + logEx("Link parse mode: " + ChatMessages.linkParseMode); + logEx(); + logEx("End of Error."); + logEx(); + + return new EmptyStacktraceException(); + } + + static void logEx() { + SkriptLogger.LOGGER.severe(EXCEPTION_PREFIX); + } + + 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 + " "; +// } +// }); +// } + + public static void info(final CommandSender sender, final String info) { + sender.sendMessage(SKRIPT_PREFIX + Utils.replaceEnglishChatStyles(info)); + } + + /** + * @param message + * @param permission + * @see #adminBroadcast(String) + */ + public static void broadcast(final String message, final String permission) { + Bukkit.broadcast(SKRIPT_PREFIX + Utils.replaceEnglishChatStyles(message), permission); + } + + public static void adminBroadcast(final String message) { + Bukkit.broadcast(SKRIPT_PREFIX + Utils.replaceEnglishChatStyles(message), "skript.admin"); + } + + /** + * Similar to {@link #info(CommandSender, String)} but no [Skript] prefix is added. + * + * @param sender + * @param info + */ + public static void message(final CommandSender sender, final String info) { + sender.sendMessage(Utils.replaceEnglishChatStyles(info)); + } + + public static void error(final CommandSender sender, final String error) { + sender.sendMessage(SKRIPT_PREFIX + ChatColor.DARK_RED + Utils.replaceEnglishChatStyles(error)); + } + + /** + * Gets the updater instance currently used by Skript. + * @return SkriptUpdater instance. + */ + @Nullable + public SkriptUpdater getUpdater() { + return updater; + } + +} diff --git a/src/main/resources/scripts/tests/api/api.sk b/src/main/resources/scripts/tests/api/api.sk new file mode 100644 index 00000000000..eca8bf714e5 --- /dev/null +++ b/src/main/resources/scripts/tests/api/api.sk @@ -0,0 +1,183 @@ +#============================================================================ +#| | +#| API EXPLAINATIONS | +#| | +#============================================================================ + +# Functions: +# +# - loadTest(testName: string, command: string, testerName: string = "*") +# ===> Will load your test and add the command to execute +# Args: +# * testName: is the path to your script. +# * command: is the command that the test will make execute to the user. You can add multiple commands. +# * testerName: is the tester's name of this test. Use "*" to use console instead of player. Default: * +# +# - unloadTest(testName: string) +# ===> Will unload your test. +# Args: +# * testName: is the name to your script. Can be "*" to unload all tests. +# +# - startTest(testName: string) +# ===> Will start your test. Can be "*" to start all tests. +# Args: +# * testName: is the name to your script. +# +# - stopTest(testName: string) +# ===> Will stop your test. +# Args: +# * testName: is the name to your script. +# +# - successTest(testName: string) +# ===> Will make your test a success. +# Args: +# * testName: is the path to your script. +# +# - failureTest(testName: string, error: string) +# ===> Will make your test a failure. +# Args: +# * testName: is the path to your script. +# * error: is the error of your test. Why your test is a failure? +# +# - regularTestName(testName: string) +# ===> Will regular test name. This function is just an utility. +# Args: +# * testName: is the path to your script. +# +# Commands: +# +# - /loadTest [] +# ===> Will load a test. +# Args: +# * arg-1: is the path to your script. +# * arg-2: is the command that you want to add to tests. +# * arg-3: is the name of the tester (player) if your command require a player anywhere. Default: * +# +# - /unloadTest [] +# ===> Will unload a test. +# Args: +# * arg-1: is the path to your script. +# +# - /startTest [] +# ===> Will start a test. Let empty to make all tests. Use "*" as argument to make every available tests (default). +# Args: +# * arg-1: is the path to your script. +# +# - /stopTest +# ===> Will stop a test. Let empty to stop all tests or use "*" as argument. +# Args: +# * arg-1: is the naùe of your script. + +options: + testErrorPrefix: [testScripts] + +function regularTestName(testName: string) :: string: + set {_split::*} to {_testName} split at "\" + set {_index} to amount of {_split::*} + set {_testName} to {_split::%{_index}%} + replace all ".tests.sk" with "" in {_testName} + replace all ".sk" with "" in {_testName} + return "testScripts\%{_testName}%.tests" + +function unloadTest(testName: string): + if {_testName} is "*": + clear {testScripts::*} + else: + set {_testName} to regularTestName({_testName}) + clear {testScripts::%{_testName}%::isFinishedWith} + clear {testScripts::%{_testName}%::testerName} + clear {testScripts::%{_testName}%::commands::*} + remove {_testName} from {testScripts::*} + +function loadTest(testName: string, command: string, testerName: string = "*"): + replace all "\" with "/" in {_testName} + set {_dirs::*} to {_testName} split at "/" + if {_dirs::1} is "testScripts": + replace all "/" with "\" in {_testName} + {testScripts::*} does not contain {_testName} + add {_testName} to {testScripts::*} + if {testScripts::%{_testName}%::commands::*} does not contain {_command}: + add {_command} to {testScripts::%{_testName}%::commands::*} + set {testScripts::%{_testName}%::isFinishedWith} to "didn't start" + set {testScripts::%{_testName}%::testerName} to {_testerName} + else: + broadcast "&c{@testErrorPrefix} Tests must be in the ""testScripts"" directory. (test: ""%{_testName}%"")" + +function startTest(testName: string): + if {_testName} is "*": + loop {testScripts::*}: + loop {testScripts::%loop-value%::commands::*}: + if {testScripts::%loop-value-1%::mustBeInGame} is not "*": + make ({testScripts::%loop-value-1%::testerName} parsed as player) execute command loop-value-2 + else: + make console execute command loop-value-2 + while {testScripts::%loop-value-1%::isFinishedWith} is "didn't start": + wait 1 second + if {testScripts::%loop-value-1%::isFinishedWith} is "success": + broadcast "&aTest ""%loop-value-1%"" ended with success!" + else: + broadcast "&cTest ""%loop-value-1%"" ended with failure: %{testScripts::%loop-value-1%::isFinishedWith::message}%" + clear {testScripts::%loop-value%::isFinishedWith::message} + set {testScripts::%loop-value%::isFinishedWith} to "didn't start" + else: + set {_testName} to regularTestName({_testName}) + if {testScripts::*} contains {_testName}: + loop {testScripts::%{_testName}%::commands::*}: + if {testScripts::%{_testName}%::testerName} is not "*": + broadcast "1" + make ({testScripts::%{_testName}%::testerName} parsed as player) execute command loop-value + else: + make console execute command loop-value + while {testScripts::%{_testName}%::isFinishedWith} is "didn't start": + wait 1 second + if {testScripts::%{_testName}%::isFinishedWith} is "success": + broadcast "&a{@testErrorPrefix} Test ""%{_testName}%"" ended with success!" + else: + broadcast "&c{@testErrorPrefix} Test ""%{_testName}%"" ended with failure: %{testScripts::%{_testName}%::isFinishedWith::message}%" + clear {testScripts::%{_testName}%::isFinishedWith::message} + set {testScripts::%{_testName}%::isFinishedWith} to "didn't start" + else: + broadcast "&c{@testErrorPrefix} Can't test ""%{_testName}%"": not found." + +function stopTest(testName: string = "*"): + if {_testName} is "*": + loop {testScripts::*}: + successTest(loop-value) + else: + successTest({_testName}) + + +function successTest(testName: string): + set {testScripts::%{_testName}%::isFinishedWith} to "success" + +#function warningTest(testName: string, warning: string): +# broadcast "&c%{_warning}% (test: ""%{_testName}%"")" + +function failureTest(testName: string, error: string): + set {testScripts::%{_testName}%::isFinishedWith} to "failure" + set {testScripts::%{_testName}%::isFinishedWith::message} to "&c%{_error}% (test: ""%{_testName}%"")" + +command /loadTest []: + trigger: + loadTest(arg-1, arg-2, arg-3) + +command /unloadTest []: + trigger: + unloadTest(arg-1) + +command /startTest []: + trigger: + set {_testName} to regularTestName(arg-1) + if {testScripts::%{_testName}%::testerName} is not "*": + sender is player + startTest(arg-1) + else: + startTest(arg-1) + +command /stopTest : + trigger: + stopTest(arg-1) + +command test: + trigger: + broadcast "%{testScripts::*}%" \ No newline at end of file diff --git a/src/main/resources/scripts/tests/api/skript.tests.sk b/src/main/resources/scripts/tests/api/skript.tests.sk new file mode 100644 index 00000000000..4c744f33240 --- /dev/null +++ b/src/main/resources/scripts/tests/api/skript.tests.sk @@ -0,0 +1,6 @@ +options: + testerName: YOUR MINECRAFT USERNAME + +on load: + loadTest("testScripts/inventory.tests", "inventory.test", "{@testerName}") + loadTest("testScripts/helloSkript.tests", "helloSkript.test") \ No newline at end of file diff --git a/src/main/resources/scripts/tests/helloSkript.tests.sk b/src/main/resources/scripts/tests/helloSkript.tests.sk new file mode 100644 index 00000000000..29f329b41d8 --- /dev/null +++ b/src/main/resources/scripts/tests/helloSkript.tests.sk @@ -0,0 +1,4 @@ +command /helloSkript.test: + trigger: + broadcast "Hello Skript!" + successTest(script) From 1467fd0e8cb61c4c7d9b0a6db453eb015ed25f24 Mon Sep 17 00:00:00 2001 From: Olyno Date: Mon, 30 Sep 2019 08:53:02 +0200 Subject: [PATCH 02/16] Fix disabled names of examples --- src/main/java/ch/njol/skript/Skript.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index fcaa0655227..068b85cdeb8 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -333,7 +333,10 @@ public void onEnable() { continue; File saveTo = null; if (e.getName().startsWith(SCRIPTSFOLDER + "/") && populateExamples) { - saveTo = new File(scripts, e.getName().replaceAll("^scripts\\/", "")); + String fileName = e.getName().replaceAll("^scripts\\/", ""); + if (!fileName.contains("/")) + fileName = (fileName.startsWith("-") ? "" : "-") + fileName; + saveTo = new File(scripts, fileName); } else if (e.getName().equals("config.sk")) { if (!config.exists()) saveTo = config; From 98d934219b71469622390d1061b9277e178ccdaf Mon Sep 17 00:00:00 2001 From: Olyno Date: Fri, 4 Oct 2019 07:58:20 +0200 Subject: [PATCH 03/16] Resolve issues #1253 #1429 #2425 and #611 --- docs/.gitignore | 6 + docs/README.md | 45 +- docs/classes.html | 5 - docs/conditions.html | 5 - docs/css/styles.css | 224 - docs/docs.json | 33 - docs/effects.html | 5 - docs/events.html | 5 - docs/expressions.html | 5 - docs/functions.html | 10 - docs/index.html | 31 - docs/package.json | 34 + docs/rollup.config.js | 105 + docs/src/client.js | 5 + docs/src/components/Card.svelte | 29 + docs/src/components/Footer.svelte | 5 + docs/src/components/Navbar.svelte | 40 + .../components/pages/DocumentationPage.svelte | 83 + docs/src/node_modules/utils.js | 51 + docs/src/routes/_error.svelte | 40 + docs/src/routes/_layout.svelte | 22 + .../routes/documentation/conditions.svelte | 5 + docs/src/routes/documentation/effects.svelte | 5 + docs/src/routes/documentation/events.svelte | 5 + .../routes/documentation/expressions.svelte | 5 + .../src/routes/documentation/functions.svelte | 5 + docs/src/routes/documentation/types.svelte | 5 + docs/src/routes/index.svelte | 157 + .../routes/tutorials/custom_commands.svelte | 206 + docs/src/routes/tutorials/functions.svelte | 199 + docs/src/routes/tutorials/index.svelte | 25 + docs/src/routes/tutorials/loops.svelte | 155 + docs/src/routes/tutorials/text.svelte | 270 + docs/src/server.js | 18 + docs/src/service-worker.js | 82 + docs/src/template.html | 42 + docs/src/utils/developers.json | 34 + docs/src/utils/docs.json | 10688 ++++++++++++++++ docs/src/utils/tutorials.json | 14 + docs/static/bulma.css | 1 + docs/static/favicon.png | Bin 0 -> 27546 bytes docs/static/fontawesome.js | 2959 +++++ docs/static/global.css | 18 + docs/static/logo-192.png | Bin 0 -> 4760 bytes docs/static/logo-512.png | Bin 0 -> 13928 bytes docs/static/manifest.json | 8 + docs/template.html | 18 - docs/templates/desc_full.html | 51 - docs/templates/desc_full.json | 11 - docs/templates/desc_nav.html | 1 - docs/templates/navbar.html | 10 - docs/templates/pattern_element.html | 1 - docs/templates/pattern_element.json | 1 - docs/text.html | 207 - docs/yarn.lock | 1686 +++ 55 files changed, 17036 insertions(+), 644 deletions(-) create mode 100644 docs/.gitignore delete mode 100644 docs/classes.html delete mode 100644 docs/conditions.html delete mode 100644 docs/css/styles.css 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 create mode 100644 docs/package.json create mode 100644 docs/rollup.config.js create mode 100644 docs/src/client.js create mode 100644 docs/src/components/Card.svelte create mode 100644 docs/src/components/Footer.svelte create mode 100644 docs/src/components/Navbar.svelte create mode 100644 docs/src/components/pages/DocumentationPage.svelte create mode 100644 docs/src/node_modules/utils.js create mode 100644 docs/src/routes/_error.svelte create mode 100644 docs/src/routes/_layout.svelte create mode 100644 docs/src/routes/documentation/conditions.svelte create mode 100644 docs/src/routes/documentation/effects.svelte create mode 100644 docs/src/routes/documentation/events.svelte create mode 100644 docs/src/routes/documentation/expressions.svelte create mode 100644 docs/src/routes/documentation/functions.svelte create mode 100644 docs/src/routes/documentation/types.svelte create mode 100644 docs/src/routes/index.svelte create mode 100644 docs/src/routes/tutorials/custom_commands.svelte create mode 100644 docs/src/routes/tutorials/functions.svelte create mode 100644 docs/src/routes/tutorials/index.svelte create mode 100644 docs/src/routes/tutorials/loops.svelte create mode 100644 docs/src/routes/tutorials/text.svelte create mode 100644 docs/src/server.js create mode 100644 docs/src/service-worker.js create mode 100644 docs/src/template.html create mode 100644 docs/src/utils/developers.json create mode 100644 docs/src/utils/docs.json create mode 100644 docs/src/utils/tutorials.json create mode 100644 docs/static/bulma.css create mode 100644 docs/static/favicon.png create mode 100644 docs/static/fontawesome.js create mode 100644 docs/static/global.css create mode 100644 docs/static/logo-192.png create mode 100644 docs/static/logo-512.png create mode 100644 docs/static/manifest.json 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 create mode 100644 docs/yarn.lock diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 00000000000..5aea041396b --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +/node_modules/ +/src/node_modules/@sapper/ +yarn-error.log +/cypress/screenshots/ +/__sapper__/ diff --git a/docs/README.md b/docs/README.md index 2041757711a..637e85260b4 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,28 +1,31 @@ -# Skript Documentation Templates +# Skript Documentation -Skript's features are documented directly in it's Java code. But we still need +Here is the source code of [Skript website](https://skriptlang.github.io/Skript/). -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 +What the website uses: -When generating final result, each HTML file is surrounded by template.html, -which provides head element, navigation bar and so on. + * Bulma + * FontAwesome + * Svelte + * Sapper -## Template Patterns +## Contribute -Patterns have syntax of ${pattern_here}. For example, ${skript.version} is replaced with -current Skript version. Please see below for more... +### Edit documentation -You can also include other files by using ${include }. Just please make -sure that those included files don't have tags which are not allowed in position -where include is called. + 1. Replace ``/src/utils/docs.json`` file with the new one + 2. Enjoy! -## Pattern Reference -``` -skript.* - Information of Skript -version - Skript's version -include - Load given file and place them here -generate - Generated reference -content - In template.html, marks the point where other file is placed -``` +### Add a contributor + + 1. Go in the ``/src/utils/developers.json`` file + 2. Like others, add yours + 3. Enjoy! + +### Add a tutorial + + 1. Go in the ``/src/utils/tutorials.json`` file + 2. Like others, add yours + 3. Go in the ``/src/routes/tutorials`` directory + 4. Like others, write your tutorial page + 5. Enjoy! \ No newline at end of file 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 @@ -

Types

- -
- ${generate classes desc_full.html} -
\ 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 @@ -

Conditions

- -
- ${generate conditions desc_full.html} -
diff --git a/docs/css/styles.css b/docs/css/styles.css deleted file mode 100644 index c8890202b58..00000000000 --- a/docs/css/styles.css +++ /dev/null @@ -1,224 +0,0 @@ -* { - margin: 0px; - padding: 0px; -} - -body { - font-family: arial; - background-color: oldlace; - display: grid; - grid-template-rows: 100vh 100vh; - grid-template-columns: minmax(15em, 18em) minmax(80%, 100%); - overflow: hidden; -} - -a { - color: darkcyan; -} - -a:visited { - color: mediumorchid; -} - -#global-navigation { - grid-row-start: 1; - grid-row-end: 1; - grid-column-start: 2; - position: sticky; - top: 0em; - - background-color: tan; - padding: 1em; - padding-left: 0em; - height: 1.37em; - z-index: 9999; -} - -#global-navigation > a { - text-decoration: none; - color: #403200; - font-size: 120%; - font-weight: bold; - - padding: 0.8em; -} - -#global-navigation > a:hover { - color: black; - background-color: #c1a97c; -} - -#side-nav { - grid-row-start: 1; - grid-row-end: 1; - grid-column-start: 1; - grid-column-end: 1; - position: sticky; - top: 3.2em; -} - -#nav-title { - grid-row-start: 1; - grid-row-end: 1; - grid-column-start: 1; - grid-column-end: 1; - position: sticky; - top: 0em; - - text-decoration: none; - background-color: tan; - font-size: 130%; - font-weight: bold; - - padding: 0.88em; - padding-top: 0.7em; - padding-left: 1em; - height: 1em; - z-index: 9999; -} - -#nav-contents { - height: calc(100vh - 3.2em); - overflow: scroll; - overflow-x: hidden; - position: sticky; - top: 3.2em; -} - -#nav-contents > a { - display: inline-block; - width: 100%; - font-size: 110%; - background-color: navajowhite; - text-decoration: none; - color: #403200; - - padding: 0.3em; - padding-left: 1em; -} - -#nav-contents > a:hover { - color: black; - background-color: #c1a97c; -} - -#content { - grid-row-start: 1; - grid-row-end: 1; - grid-column-start: 2; - overflow: scroll; - overflow-x: hidden; - - padding: 0.22em; - position: sticky; - top: 3.3em; - height: calc(100vh - 3.6em); -} - -#content > p { - margin-bottom: 0.7em; - max-width: 55em; -} - -table { - width: 100%; - border-collapse: collapse; -} - -.item-title { - font-size: 130%; - padding: 0.2em; - padding-top: 0.5em; - padding-left: 0.4em; - - color: #261908; -} - -.item-title > a { - text-decoration: none; -} - -.item-table-label { - background-color: burlywood; - border-color: burlywood; - border-style: solid; - border-right-style: none; - border-width: 1px; - - width: 10em; - padding: 0.3em; -} - -.item-details { - display: block; - border-collapse: collapse; -} - -.item-details td:nth-child(2) { - border-style: solid; - border-left-style: none; - border-color: burlywood; - border-width: 1px; - width: 80%; - max-width: 20em; - padding-left: 0.2em; -} - -.noleftpad { - padding-left: 0em !important; -} - -td ul { - padding: 0em; - list-style-type: none; -} - -.item-description { - margin-top: 0.2em; - width: 82.5%; - max-width: 55em; - - line-height: 120%; - letter-spacing: 0.04px; - text-rendering: optimizelegibility; -} - -.item-description > p { - margin-top: 0.7em; -} - -.skript-code-block { - padding: 0.52em; - font-family: monospace; - width: 82.5%; - max-width: 69.6em; - background-color: gainsboro; -} - -.skript-code-block > a { - text-decoration: none; -} - -ul > .skript-code-block { - width: 98.4%; -} - -.item-examples { - padding-top: 0.5em; -} - -.item-examples > .skript-code-block { - margin-left: 0.15em; - margin-top: 0.3em; -} - -.box { - border-style: solid; - border-color: burlywood; - border-width: 2px; - border-top-width: 1em; - border-radius: 3px; - padding: 0.8em; - - background-color: wheat; -} 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 @@ -

Effects

- -
- ${generate effects desc_full.html} -
\ 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 @@ -

Events

- -
- ${generate events desc_full.html} -
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 @@ -

Expressions

- -
- ${generate expressions desc_full.html} -
\ No newline at end of file diff --git a/docs/functions.html b/docs/functions.html deleted file mode 100644 index 2f5a02ee9ef..00000000000 --- a/docs/functions.html +++ /dev/null @@ -1,10 +0,0 @@ -

Functions

- -

- 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. -

- -
- ${generate functions desc_full.html} -
diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 3287f317c53..00000000000 --- a/docs/index.html +++ /dev/null @@ -1,31 +0,0 @@ -

Documentation

- -
-

- Skript is (surprise, surprise) a scripting plugin for Bukkit platform. It - is easy to use for simple tasks, but yet you can create really complex things - with it. The syntax of Skript is close to English, but it is still not magic. - While for simple tasks you might succeed with experimentation, for anything - more complex you will need some guidance. -

- -

- This is the Skript documentation. You will find all supported features of Skript - plugin here. You might also find some useful examples, in case you need more - than what was shipped with Skript distribution. We do not currently have - tutorials here, but you can find good ones using whatever search engine you prefer. -

- -

- Found something incorrect in this documentation? Please report - it to the issue tracker. -

-

- We are looking for docs authors! - 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 this issue for - more details and if you're interested in helping out. -

-
diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 00000000000..20398880134 --- /dev/null +++ b/docs/package.json @@ -0,0 +1,34 @@ +{ + "name": "skript-docs", + "description": "Skript's documentation", + "version": "0.0.1", + "scripts": { + "dev": "sapper dev", + "build": "sapper build --legacy", + "export": "sapper export --legacy", + "start": "node __sapper__/build" + }, + "dependencies": { + "compression": "^1.7.1", + "polka": "next", + "sirv": "^0.4.0" + }, + "devDependencies": { + "@babel/core": "^7.0.0", + "@babel/plugin-syntax-dynamic-import": "^7.0.0", + "@babel/plugin-transform-runtime": "^7.0.0", + "@babel/preset-env": "^7.0.0", + "@babel/runtime": "^7.0.0", + "npm-run-all": "^4.1.5", + "rollup": "^1.12.0", + "rollup-plugin-babel": "^4.0.2", + "rollup-plugin-commonjs": "^10.0.0", + "rollup-plugin-json": "^4.0.0", + "rollup-plugin-node-resolve": "^5.2.0", + "rollup-plugin-replace": "^2.0.0", + "rollup-plugin-svelte": "^5.0.1", + "rollup-plugin-terser": "^4.0.4", + "sapper": "^0.27.0", + "svelte": "^3.0.0" + } +} diff --git a/docs/rollup.config.js b/docs/rollup.config.js new file mode 100644 index 00000000000..38dfff424c9 --- /dev/null +++ b/docs/rollup.config.js @@ -0,0 +1,105 @@ +import resolve from 'rollup-plugin-node-resolve'; +import replace from 'rollup-plugin-replace'; +import commonjs from 'rollup-plugin-commonjs'; +import json from 'rollup-plugin-json'; +import svelte from 'rollup-plugin-svelte'; +import babel from 'rollup-plugin-babel'; +import { terser } from 'rollup-plugin-terser'; +import config from 'sapper/config/rollup.js'; +import pkg from './package.json'; + +const mode = process.env.NODE_ENV; +const dev = mode === 'development'; +const legacy = !!process.env.SAPPER_LEGACY_BUILD; + +const onwarn = (warning, onwarn) => (warning.code === 'CIRCULAR_DEPENDENCY' && /[/\\]@sapper[/\\]/.test(warning.message)) || onwarn(warning); +const dedupe = importee => importee === 'svelte' || importee.startsWith('svelte/'); + +export default { + client: { + input: config.client.input(), + output: config.client.output(), + plugins: [ + replace({ + 'process.browser': true, + 'process.env.NODE_ENV': JSON.stringify(mode) + }), + svelte({ + dev, + hydratable: true, + emitCss: true + }), + resolve({ + browser: true, + dedupe + }), + commonjs(), + json(), + + legacy && babel({ + extensions: ['.js', '.mjs', '.html', '.svelte'], + runtimeHelpers: true, + exclude: ['node_modules/@babel/**'], + presets: [ + ['@babel/preset-env', { + targets: '> 0.25%, not dead' + }] + ], + plugins: [ + '@babel/plugin-syntax-dynamic-import', + ['@babel/plugin-transform-runtime', { + useESModules: true + }] + ] + }), + + !dev && terser({ + module: true + }) + ], + + onwarn, + }, + + server: { + input: config.server.input(), + output: config.server.output(), + plugins: [ + replace({ + 'process.browser': false, + 'process.env.NODE_ENV': JSON.stringify(mode) + }), + svelte({ + generate: 'ssr', + dev + }), + resolve({ + dedupe + }), + commonjs(), + json() + ], + external: Object.keys(pkg.dependencies).concat( + require('module').builtinModules || Object.keys(process.binding('natives')) + ), + + onwarn, + }, + + serviceworker: { + input: config.serviceworker.input(), + output: config.serviceworker.output(), + plugins: [ + resolve(), + replace({ + 'process.browser': true, + 'process.env.NODE_ENV': JSON.stringify(mode) + }), + commonjs(), + json(), + !dev && terser() + ], + + onwarn, + } +}; diff --git a/docs/src/client.js b/docs/src/client.js new file mode 100644 index 00000000000..cec91725f00 --- /dev/null +++ b/docs/src/client.js @@ -0,0 +1,5 @@ +import * as sapper from '@sapper/app'; + +sapper.start({ + target: document.querySelector('#sapper') +}); \ No newline at end of file diff --git a/docs/src/components/Card.svelte b/docs/src/components/Card.svelte new file mode 100644 index 00000000000..027d23692d5 --- /dev/null +++ b/docs/src/components/Card.svelte @@ -0,0 +1,29 @@ + + +
+ {#if image !== ""} +
+
+ Placeholder image +
+
+ {/if} +
+ {#if !withoutTitle} +
+ +
+ {/if} + + +
+
+ +
+ +
\ No newline at end of file diff --git a/docs/src/components/Footer.svelte b/docs/src/components/Footer.svelte new file mode 100644 index 00000000000..ba07a8bc67c --- /dev/null +++ b/docs/src/components/Footer.svelte @@ -0,0 +1,5 @@ +
+
+

Made with ❤️ (by Olyno)

+
+
\ No newline at end of file diff --git a/docs/src/components/Navbar.svelte b/docs/src/components/Navbar.svelte new file mode 100644 index 00000000000..05f0b8f822d --- /dev/null +++ b/docs/src/components/Navbar.svelte @@ -0,0 +1,40 @@ + + + + + \ No newline at end of file diff --git a/docs/src/components/pages/DocumentationPage.svelte b/docs/src/components/pages/DocumentationPage.svelte new file mode 100644 index 00000000000..f94916b99d1 --- /dev/null +++ b/docs/src/components/pages/DocumentationPage.svelte @@ -0,0 +1,83 @@ + + +
+

{firstLetterUpperCase(docType)}

+
+ +
+ +
+ +
+ +
+ + {#each Object.keys(documentation[docType]) as element} + +
+ + + +

+ {documentation[docType][element].name} + # +

+ +
+ {#if documentation[docType][element].since} + {documentation[docType][element].since} + {/if} +
+ + {#if documentation[docType][element].description} + +
+

{documentation[docType][element].description}

+
+ {/if} + + {#if documentation[docType][element].patterns} + +
+

{documentation[docType][element].patterns.join('\n')}

+
+ {/if} + + {#if documentation[docType][element].examples} + +
+

{documentation[docType][element].examples.join('\n')}

+
+ {/if} + +
+ +
+ + {/each} + +
+ +
\ No newline at end of file diff --git a/docs/src/node_modules/utils.js b/docs/src/node_modules/utils.js new file mode 100644 index 00000000000..5a26023e882 --- /dev/null +++ b/docs/src/node_modules/utils.js @@ -0,0 +1,51 @@ +export async function setupColors() { + const elements = document.getElementsByClassName('skript-code'); + for (let element of elements) { + element.innerHTML = element.innerHTML.split('\n').map(line => line + .replace(/(#.*)/gmui, '$1') + .replace(/(%(-)?(\w+)%)/gmui, '$1') + .replace(/\b(set|delete|clear|remove|add|to)\b/gmui, '$1') + .replace(/\b(player)\b/gmui, '$1') + .replace(/(with|broadcast|send|message|kick)( .+)/gmui, '$1 $2') + .replace(/\b(on|cancel event|command|function|return)\b/gmui, '$1') + .replace(/\b(if|else|is)\b/gmui, '$1') + .replace(/\b(loop|while|chance)\b/gmui, '$1') + .replace(/(\{_?[\w\.::]+\})/gmui, '$1') + .replace(/(premission|permission message|usage|executable by|aliases|cooldown|description|trigger):/gmui, '$1:') + .replace(/(<\w+>):/gmui, '$1') + .replace(/(%-?\w+%):/gmui, '$1') + ).join('\n') + } +} + +export async function clicked(target) { + const isActive = document.getElementsByClassName('is-active')[0]; + if (isActive) isActive.classList.remove('is-active'); + target.target.classList.add('is-active'); +} + +export async function search(searchValue) { + const cards = document.getElementsByClassName('card'); + for (let card of cards) { + + const title = card.getElementsByClassName('card-header-title')[0].innerText; + const description = card.getElementsByClassName('card-content')[0].getElementsByTagName('p')[0].innerText; + const pattern = card.getElementsByClassName('card-content')[0].getElementsByTagName('p')[1].innerText; + + const filters = [title, pattern]; + + const result = filters.filter(filter => filter.toUpperCase().indexOf(searchValue.toUpperCase()) > -1) + + if (result.length > 0) { + card.parentElement.style.display = ""; + } else { + card.parentElement.style.display = "none"; + } + + } +} + +export function firstLetterUpperCase(text) { + if (typeof text !== 'string') return '' + return text.charAt(0).toUpperCase() + text.slice(1) +} \ No newline at end of file diff --git a/docs/src/routes/_error.svelte b/docs/src/routes/_error.svelte new file mode 100644 index 00000000000..320e5870202 --- /dev/null +++ b/docs/src/routes/_error.svelte @@ -0,0 +1,40 @@ + + + + + + {status} + + +

{status}

+ +

{error.message}

+ +{#if dev && error.stack} +
{error.stack}
+{/if} diff --git a/docs/src/routes/_layout.svelte b/docs/src/routes/_layout.svelte new file mode 100644 index 00000000000..5a6710edd5f --- /dev/null +++ b/docs/src/routes/_layout.svelte @@ -0,0 +1,22 @@ + + + + Skript documentation + + + + +
+
+
+
+ +
+
+
+
+ +