quickTest
runs the test suite on newest supported server version.
skriptTestJava21
(1.20.6+) runs the tests on Java 21 supported versions.
-skriptTestJava17
(1.17-1.20.4) runs the tests on Java 17 supported versions.
-skriptTestJava8
(1.13-1.16) runs the tests on Java 8 supported versions.
+skriptTestJava17
(1.19.4-1.20.4) runs the tests on Java 17 supported versions.
skriptTest
runs the tests on all versions.
-That is, it runs skriptTestJava8, skriptTestJava17, and skriptTestJava21.
+That is, it runs skriptTestJava17, and skriptTestJava21.
By running the tests, you agree to Mojang's End User License Agreement.
@@ -164,7 +163,7 @@ dependencies {
}
```
-An example of the version tag would be ```dev37c```.
+An example of the version tag would be ```2.8.5```.
> Note: If Gradle isn't able to resolve Skript's dependencies, just [disable the resolution of transitive dependencies](https://docs.gradle.org/current/userguide/resolution_rules.html#sec:disabling_resolution_transitive_dependencies) for Skript in your project.
diff --git a/build.gradle b/build.gradle
index fcd904d9863..069378c56f3 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,7 +5,6 @@ import java.time.LocalTime
plugins {
id 'com.github.johnrengelman.shadow' version '8.1.1'
- id 'com.github.hierynomus.license' version '0.16.1'
id 'maven-publish'
id 'java'
}
@@ -30,7 +29,7 @@ dependencies {
shadow group: 'org.bstats', name: 'bstats-bukkit', version: '3.0.2'
shadow group: 'net.kyori', name: 'adventure-text-serializer-bungeecord', version: '4.3.2'
- implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.20.6-R0.1-SNAPSHOT'
+ implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.21-R0.1-SNAPSHOT'
implementation group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.700'
implementation group: 'com.google.code.findbugs', name: 'findbugs', version: '3.0.1'
implementation group: 'com.sk89q.worldguard', name: 'worldguard-legacy', version: '7.0.0-SNAPSHOT'
@@ -41,7 +40,7 @@ dependencies {
implementation fileTree(dir: 'lib', include: '*.jar')
testShadow group: 'junit', name: 'junit', version: '4.13.2'
- testShadow group: 'org.easymock', name: 'easymock', version: '5.0.1'
+ testShadow group: 'org.easymock', name: 'easymock', version: '5.4.0'
}
task checkAliases {
@@ -55,7 +54,7 @@ task checkAliases {
}
task testJar(type: ShadowJar) {
- dependsOn(compileTestJava, licenseTest)
+ dependsOn(compileTestJava)
archiveFileName = 'Skript-JUnit.jar'
from sourceSets.test.output, sourceSets.main.output, project.configurations.testShadow
}
@@ -71,7 +70,7 @@ task build(overwrite: true, type: ShadowJar) {
from sourceSets.main.output
}
-// Excludes the tests for the build task. Should be using junit, junitJava17, junitJava8, skriptTest, quickTest.
+// Excludes the tests for the build task. Should be using JUnitQuick, JUnitJava21, JUnitJava17, skriptTest, quickTest.
// We do not want tests to run for building. That's time consuming and annoying. Especially in development.
test {
exclude '**/*'
@@ -141,24 +140,6 @@ publishing {
}
}
-license {
- header file('licenseheader.txt')
- exclude('**/Metrics.java') // Not under GPLv3
- exclude('**/BurgerHelper.java') // Not exclusively GPLv3
- exclude('**/*.sk') // Sample scripts and maybe aliases
- exclude('**/*.lang') // Language files do not have headers (still under GPLv3)
- exclude('**/*.json') // JSON files do not have headers
-}
-
-task releaseJavadoc(type: Javadoc) {
- title = project.name + ' ' + project.property('version')
- source = sourceSets.main.allJava
- classpath = configurations.compileClasspath
- options.encoding = 'UTF-8'
- // currently our javadoc has a lot of errors, so we need to suppress the linter
- options.addStringOption('Xdoclint:none', '-quiet')
-}
-
// Task to check that test scripts are named correctly
tasks.register('testNaming') {
doLast {
@@ -198,13 +179,12 @@ void createTestTask(String name, String desc, String environments, int javaVersi
if (junit) {
artifact += 'Skript-JUnit.jar'
} else if (releaseDocs) {
- artifact += 'Skript-github.jar'
+ artifact += 'Skript-' + version + '.jar'
} else {
artifact += 'Skript-nightly.jar'
}
tasks.register(name, JavaExec) {
description = desc
- dependsOn licenseTest
if (junit) {
dependsOn testJar
} else if (releaseDocs) {
@@ -244,7 +224,7 @@ void createTestTask(String name, String desc, String environments, int javaVersi
if (!gradle.taskGraph.hasTask(":tasks") && !gradle.startParameter.dryRun && modifiers.contains(Modifiers.PROFILE)) {
if (!project.hasProperty('profiler'))
throw new MissingPropertyException('Add parameter -Pprofiler=-ea
is used to enable them.
+## Code Complexity
+
+Dense, highly-complex code should be avoided to preserve readability and to help with future maintenance,
+especially within a single method body.
+
+There are many available metrics for measuring code complexity (for different purposes); [we have our own](https://stable.skriptlang.org/Radical_Complexity.pdf).
+There are no strict limits for code complexity, but you may be encouraged (or required) to reformat or break up methods
+into smaller, more manageable chunks. If in doubt, keep things simple.
## Minecraft Features
diff --git a/gradle.properties b/gradle.properties
index 7753116b0ea..a620b360bd5 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -5,7 +5,7 @@ org.gradle.parallel=true
groupid=ch.njol
name=skript
-version=2.8.5
+version=2.9.2
jarName=Skript.jar
-testEnv=java21/paper-1.20.6
+testEnv=java21/paper-1.21.0
testEnvJavaVersion=21
diff --git a/licenseheader.txt b/licenseheader.txt
deleted file mode 100644
index 15be760be16..00000000000
--- a/licenseheader.txt
+++ /dev/null
@@ -1,16 +0,0 @@
- This file is part of Skript.
-
- Skript is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Skript is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with Skript. If not, see
* Once you made sure that Skript is loaded you can use
* 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
@@ -1076,10 +1029,10 @@ public static boolean methodExists(final Class> c, final String methodName, fi
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.
@@ -1094,23 +1047,23 @@ public static boolean fieldExists(final Class> c, final String fieldName) {
return false;
}
}
-
+
@Nullable
static Metrics metrics;
-
+
@Nullable
public static Metrics getMetrics() {
return metrics;
}
-
+
@SuppressWarnings("null")
private final static Collection
* All registered Closeables will be closed after all scripts have been stopped.
- *
+ *
* @param closeable
*/
public static void closeOnDisable(final Closeable closeable) {
@@ -1201,13 +1154,14 @@ public void onDisable() {
if (disabled)
return;
disabled = true;
+ this.experimentRegistry = null;
if (!partDisabled) {
beforeDisable();
}
-
+
Bukkit.getScheduler().cancelTasks(this);
-
+
for (Closeable c : closeOnDisable) {
try {
c.close();
@@ -1216,46 +1170,46 @@ public void onDisable() {
}
}
}
-
+
// ================ 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:
- *
+ *
* 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...)
@@ -183,34 +170,34 @@
* @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;
private static boolean partDisabled = 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;
}
-
+
private static Version minecraftVersion = new Version(666), UNKNOWN_VERSION = new Version(666);
private static ServerPlatform serverPlatform = ServerPlatform.BUKKIT_UNKNOWN; // Start with unknown... onLoad changes this
@@ -228,24 +215,26 @@ public static void updateMinecraftVersion() {
minecraftVersion = new Version("" + m.group());
}
}
-
+
@Nullable
private static Version version = null;
-
+ @Deprecated(forRemoval = true) // TODO this field will be replaced by a proper registry later
+ private static @UnknownNullability ExperimentRegistry experimentRegistry;
+
public static Version getVersion() {
final Version v = version;
if (v == null)
throw new IllegalStateException();
return v;
}
-
+
public static final Message
m_invalid_reload = new Message("skript.invalid reload"),
m_finished_loading = new Message("skript.finished loading"),
m_no_errors = new Message("skript.no errors"),
m_no_scripts = new Message("skript.no scripts");
private static final PluralizingArgsMessage m_scripts_loaded = new PluralizingArgsMessage("skript.scripts loaded");
-
+
public static ServerPlatform getServerPlatform() {
if (classExists("net.glowstone.GlowServer")) {
return ServerPlatform.BUKKIT_GLOWSTONE; // Glowstone has timings too, so must check for it first
@@ -271,7 +260,7 @@ private static boolean using32BitJava() {
// Property returned should either be "Java HotSpot(TM) 32-Bit Server VM" or "OpenJDK 32-Bit Server VM" if 32-bit and using OracleJDK/OpenJDK
return System.getProperty("java.vm.name").contains("32");
}
-
+
/**
* Checks if server software and Minecraft version are supported.
* Prints errors or warnings to console if something is wrong.
@@ -288,7 +277,7 @@ private static boolean checkServerPlatform() {
minecraftVersion = new Version("" + m.group());
}
Skript.debug("Loading for Minecraft " + minecraftVersion);
-
+
// Check that MC version is supported
if (!isRunningMinecraft(1, 9)) {
// Prevent loading when not running at least Minecraft 1.9
@@ -297,7 +286,7 @@ private static boolean checkServerPlatform() {
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);
@@ -317,7 +306,7 @@ private static boolean checkServerPlatform() {
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;
}
@@ -329,7 +318,7 @@ private static boolean checkServerPlatform() {
* Checks whether a hook has been enabled.
* @param hook The hook to check.
* @return Whether the hook is enabled.
- * @see #disableHookRegistration(Class[])
+ * @see #disableHookRegistration(Class[])
*/
public static boolean isHookEnabled(Class extends Hook>> hook) {
return !disabledHookRegistrations.contains(hook);
@@ -347,7 +336,7 @@ public static boolean isFinishedLoadingHooks() {
* Disables the registration for the given hook classes. If Skript has been enabled, this method
* will throw an API exception. It should be used in something like {@link JavaPlugin#onLoad()}.
* @param hooks The hooks to disable the registration of.
- * @see #isHookEnabled(Class)
+ * @see #isHookEnabled(Class)
*/
@SafeVarargs
public static void disableHookRegistration(Class extends Hook>>... hooks) {
@@ -363,6 +352,13 @@ public static void disableHookRegistration(Class extends Hook>>... hooks) {
*/
private File scriptsFolder;
+ /**
+ * @return The manager for experimental, optional features.
+ */
+ public static ExperimentRegistry experiments() {
+ return experimentRegistry;
+ }
+
/**
* @return The folder containing all Scripts.
*/
@@ -372,7 +368,7 @@ public File getScriptsFolder() {
scriptsFolder.mkdirs();
return scriptsFolder;
}
-
+
@Override
public void onEnable() {
Bukkit.getPluginManager().registerEvents(this, this);
@@ -381,11 +377,11 @@ public void onEnable() {
setEnabled(false);
return;
}
-
+
handleJvmArguments(); // JVM arguments
-
+
version = new Version("" + getDescription().getVersion()); // Skript version
-
+
// Start the updater
// Note: if config prohibits update checks, it will NOT do network connections
try {
@@ -393,7 +389,9 @@ public void onEnable() {
} catch (Exception e) {
Skript.exception(e, "Update checker could not be initialized.");
}
-
+ experimentRegistry = new ExperimentRegistry(this);
+ Feature.registerAll(getAddonInstance(), experimentRegistry);
+
if (!getDataFolder().isDirectory())
getDataFolder().mkdirs();
@@ -469,7 +467,7 @@ public void onEnable() {
// initialize the Skript addon instance
getAddonInstance();
-
+
// Load classes which are always safe to use
new JavaClasses(); // These may be needed in configuration
@@ -488,15 +486,15 @@ public void onEnable() {
} 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();
-
+
// Now override the verbosity if test mode is enabled
if (TestMode.VERBOSITY != null)
SkriptLogger.setVerbosity(Verbosity.valueOf(TestMode.VERBOSITY));
-
+
// Use the updater, now that it has been configured to (not) do stuff
if (updater != null) {
CommandSender console = Bukkit.getConsoleSender();
@@ -518,29 +516,29 @@ public void onEnable() {
throw e; // Uh oh, this shouldn't happen. Re-throw the error.
}
}
-
+
// 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());
skriptCommand.setTabCompleter(new SkriptCommandTabCompleter());
-
+
// Load Bukkit stuff. It is done after platform check, because something might be missing!
new BukkitEventValues();
-
+
new DefaultComparators();
new DefaultConverters();
new DefaultFunctions();
new DefaultOperations();
-
+
ChatMessages.registerListeners();
-
+
try {
getAddonInstance().loadClasses("ch.njol.skript",
"conditions", "effects", "events", "expressions", "entity", "sections", "structures");
@@ -552,17 +550,17 @@ public void onEnable() {
}
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())) {
@@ -590,7 +588,7 @@ public void run() {
Skript.exception(e);
}
finishedLoadingHooks = true;
-
+
if (TestMode.ENABLED) {
info("Preparing Skript for testing...");
tainted = true;
@@ -603,10 +601,10 @@ public void run() {
Bukkit.getServer().shutdown();
}
}
-
+
stopAcceptingRegistrations();
-
-
+
+
Documentation.generate(); // TODO move to test classes?
// Variable loading
@@ -741,6 +739,8 @@ protected void afterErrors() {
TestTracker.JUnitTestFailed(test, message);
Skript.exception(failure.getException(), "JUnit test '" + failure.getTestHeader() + " failed.");
});
+ if (SkriptJUnitTest.class.isAssignableFrom(clazz))
+ ((SkriptJUnitTest) clazz.getConstructor().newInstance()).cleanup();
SkriptJUnitTest.clearJUnitTest();
}
} catch (IOException e) {
@@ -748,10 +748,12 @@ protected void afterErrors() {
} catch (ClassNotFoundException e) {
// Should be the Skript test jar gradle task.
assert false : "Class 'ch.njol.skript.variables.FlatFileStorageTest' was not found.";
+ } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
+ Skript.exception(e, "Failed to initalize test JUnit classes.");
}
if (ignored > 0)
Skript.warning("There were " + ignored + " ignored test cases! This can mean they are not properly setup in order in that class!");
-
+
info("Completed " + tests + " JUnit tests in " + size + " classes with " + fails + " failures in " + milliseconds + " milliseconds.");
}
}
@@ -775,58 +777,9 @@ protected void afterErrors() {
}, 100);
}
- // Enable metrics and register custom charts
- Metrics metrics = new Metrics(Skript.this, 722); // 722 is our bStats plugin ID
- metrics.addCustomChart(new SimplePie("pluginLanguage", Language::getName));
- metrics.addCustomChart(new SimplePie("effectCommands", () ->
- SkriptConfig.enableEffectCommands.value().toString()
- ));
- metrics.addCustomChart(new SimplePie("uuidsWithPlayers", () ->
- SkriptConfig.usePlayerUUIDsInVariableNames.value().toString()
- ));
- metrics.addCustomChart(new SimplePie("playerVariableFix", () ->
- SkriptConfig.enablePlayerVariableFix.value().toString()
- ));
- metrics.addCustomChart(new SimplePie("logVerbosity", () ->
- SkriptConfig.verbosity.value().name().toLowerCase(Locale.ENGLISH).replace('_', ' ')
- ));
- metrics.addCustomChart(new SimplePie("pluginPriority", () ->
- SkriptConfig.defaultEventPriority.value().name().toLowerCase(Locale.ENGLISH).replace('_', ' ')
- ));
- metrics.addCustomChart(new SimplePie("logPlayerCommands", () ->
- String.valueOf((SkriptConfig.logEffectCommands.value() || SkriptConfig.logPlayerCommands.value()))
- ));
- metrics.addCustomChart(new SimplePie("maxTargetDistance", () ->
- SkriptConfig.maxTargetBlockDistance.value().toString()
- ));
- metrics.addCustomChart(new SimplePie("softApiExceptions", () ->
- SkriptConfig.apiSoftExceptions.value().toString()
- ));
- metrics.addCustomChart(new SimplePie("timingsStatus", () -> {
- if (!Skript.classExists("co.aikar.timings.Timings"))
- return "unsupported";
- return SkriptConfig.enableTimings.value().toString();
- }));
- metrics.addCustomChart(new SimplePie("parseLinks", () ->
- ChatMessages.linkParseMode.name().toLowerCase(Locale.ENGLISH)
- ));
- metrics.addCustomChart(new SimplePie("colorResetCodes", () ->
- SkriptConfig.colorResetCodes.value().toString()
- ));
- metrics.addCustomChart(new SimplePie("functionsWithNulls", () ->
- SkriptConfig.executeFunctionsWithMissingParams.value().toString()
- ));
- metrics.addCustomChart(new SimplePie("buildFlavor", () -> {
- if (updater != null)
- return updater.getCurrentRelease().flavor;
- return "unknown";
- }));
- metrics.addCustomChart(new SimplePie("updateCheckerEnabled", () ->
- SkriptConfig.checkForNewVersion.value().toString()
- ));
- metrics.addCustomChart(new SimplePie("releaseChannel", SkriptConfig.releaseChannel::value));
- Skript.metrics = metrics;
-
+ Skript.metrics = new Metrics(Skript.getInstance(), 722); // 722 is our bStats plugin ID
+ SkriptMetrics.setupMetrics(Skript.metrics);
+
/*
* Start loading scripts
*/
@@ -877,10 +830,10 @@ protected void afterErrors() {
throw Skript.exception(e);
}
});
-
+
}
});
-
+
Bukkit.getPluginManager().registerEvents(new Listener() {
@EventHandler
public void onJoin(final PlayerJoinEvent e) {
@@ -892,7 +845,7 @@ public void run() {
SkriptUpdater updater = getUpdater();
if (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
@@ -907,17 +860,17 @@ public void run() {
}
}
}, 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.
@@ -956,13 +909,13 @@ private void handleJvmArguments() {
return;
}
}
-
+
// Use BurgerHelper to create some mappings, then dump them as JSON
try {
BurgerHelper burger = new BurgerHelper(burgerInput);
Map
* 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.
*/
@@ -1264,19 +1218,19 @@ public static void outdatedError(final Exception e) {
* 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.
*/
@@ -1285,38 +1239,38 @@ public static Thread newThread(final Runnable r, final String name) {
t.setUncaughtExceptionHandler(UEH);
return t;
}
-
+
// ================ REGISTRATIONS ================
-
+
private static boolean acceptRegistrations = true;
-
+
public static boolean isAcceptRegistrations() {
if (instance == null)
throw new IllegalStateException("Skript was never loaded");
return acceptRegistrations && instance.isEnabled();
}
-
+
public static void checkAcceptRegistrations() {
if (!isAcceptRegistrations() && !Skript.testing())
throw new SkriptAPIException("Registration can only be done during plugin initialization");
}
-
+
private static void stopAcceptingRegistrations() {
Converters.createChainedConverters();
acceptRegistrations = false;
-
+
Classes.onRegistrationsStop();
}
-
+
// ================ ADDONS ================
-
+
private final static HashMap