"));
desc = desc
- .replace("${element.desc-safe}", Joiner.on("\\n").join(description)
+ .replace("${element.desc-safe}", Joiner.on("\\n").join(description)
.replace("\\", "\\\\").replace("\"", "\\\"").replace("\t", " "));
// By Addon
@@ -721,20 +684,13 @@ private String generateClass(String descTemp, ClassInfo> info, @Nullable Strin
String[] examples = getDefaultIfNullOrEmpty(info.getExamples(), "Missing examples.");
desc = desc.replace("${element.examples}", Joiner.on("\n ").join(Documentation.escapeHTML(examples)));
desc = desc.replace("${element.examples-safe}", Joiner.on("\\n").join(Documentation.escapeHTML(examples))
- .replace("\\", "\\\\").replace("\"", "\\\"").replace("\t", " "));
+ .replace("\\", "\\\\").replace("\"", "\\\"").replace("\t", " "));
Keywords keywords = c.getAnnotation(Keywords.class);
desc = desc.replace("${element.keywords}", keywords == null ? "" : Joiner.on(", ").join(keywords.value()));
// Documentation ID
- String ID = info.getDocumentationID() != null ? info.getDocumentationID() : info.getCodeName();
- // Fix duplicated IDs
- if (page != null) {
- if (page.contains("href=\"#" + ID + "\"")) {
- ID = ID + "-" + (StringUtils.countMatches(page, "href=\"#" + ID + "\"") + 1);
- }
- }
- desc = desc.replace("${element.id}", ID);
+ desc = desc.replace("${element.id}", DocumentationIdProvider.getId(info));
// Cancellable
desc = handleIf(desc, "${if cancellable}", false);
@@ -777,14 +733,14 @@ private String generateClass(String descTemp, ClassInfo> info, @Nullable Strin
int nextBracket = desc.indexOf("}", generate);
String data = desc.substring(generate + 11, nextBracket);
toGen.add(data);
-
+
generate = desc.indexOf("${generate", nextBracket);
}
-
+
// Assume element.pattern generate
for (String data : toGen) {
String[] split = data.split(" ");
- String pattern = readFile(new File(template + "/templates/" + split[1]));
+ String pattern = readFile(new File(templateDir + "/templates/" + split[1]));
StringBuilder patterns = new StringBuilder();
String[] lines = getDefaultIfNullOrEmpty(info.getUsage(), "Missing patterns.");
if (lines == null)
@@ -795,15 +751,15 @@ private String generateClass(String descTemp, ClassInfo> info, @Nullable Strin
String parsed = pattern.replace("${element.pattern}", line);
patterns.append(parsed);
}
-
+
desc = desc.replace("${generate element.patterns " + split[1] + "}", patterns.toString());
desc = desc.replace("${generate element.patterns-safe " + split[1] + "}", patterns.toString().replace("\\", "\\\\"));
}
-
+
assert desc != null;
return desc;
}
-
+
private String generateFunction(String descTemp, JavaFunction> info) {
String desc = "";
@@ -819,7 +775,7 @@ private String generateFunction(String descTemp, JavaFunction> info) {
String[] description = getDefaultIfNullOrEmpty(info.getDescription(), "Missing description.");
desc = desc.replace("${element.desc}", Joiner.on("\n").join(description));
desc = desc
- .replace("${element.desc-safe}", Joiner.on("\\n").join(description)
+ .replace("${element.desc-safe}", Joiner.on("\\n").join(description)
.replace("\\", "\\\\").replace("\"", "\\\"").replace("\t", " "));
// By Addon
@@ -832,14 +788,14 @@ private String generateFunction(String descTemp, JavaFunction> info) {
String[] examples = getDefaultIfNullOrEmpty(info.getExamples(), "Missing examples.");
desc = desc.replace("${element.examples}", Joiner.on("\n ").join(Documentation.escapeHTML(examples)));
desc = desc
- .replace("${element.examples-safe}", Joiner.on("\\n").join(examples)
+ .replace("${element.examples-safe}", Joiner.on("\\n").join(examples)
.replace("\\", "\\\\").replace("\"", "\\\"").replace("\t", " "));
String[] keywords = info.getKeywords();
desc = desc.replace("${element.keywords}", keywords == null ? "" : Joiner.on(", ").join(keywords));
// Documentation ID
- desc = desc.replace("${element.id}", info.getName());
+ desc = desc.replace("${element.id}", DocumentationIdProvider.getId(info));
// Cancellable
desc = handleIf(desc, "${if cancellable}", false);
@@ -870,31 +826,26 @@ private String generateFunction(String descTemp, JavaFunction> info) {
int nextBracket = desc.indexOf("}", generate);
String data = desc.substring(generate + 11, nextBracket);
toGen.add(data);
-
+
generate = desc.indexOf("${generate", nextBracket);
}
-
+
// Assume element.pattern generate
for (String data : toGen) {
String[] split = data.split(" ");
- String pattern = readFile(new File(template + "/templates/" + split[1]));
+ String pattern = readFile(new File(templateDir + "/templates/" + split[1]));
String patterns = "";
- Parameter>[] params = info.getParameters();
- String[] types = new String[params.length];
- for (int i = 0; i < types.length; i++) {
- types[i] = params[i].toString();
- }
- String line = docName + "(" + Joiner.on(", ").join(types) + ")"; // Better not have nulls
+ String line = info.getSignature().toString(false, false); // Better not have nulls
patterns += pattern.replace("${element.pattern}", line);
-
+
desc = desc.replace("${generate element.patterns " + split[1] + "}", patterns);
desc = desc.replace("${generate element.patterns-safe " + split[1] + "}", patterns.replace("\\", "\\\\"));
}
-
+
assert desc != null;
return desc;
}
-
+
@SuppressWarnings("null")
private static String readFile(File f) {
try {
@@ -904,7 +855,7 @@ private static String readFile(File f) {
return "";
}
}
-
+
private static void writeFile(File f, String data) {
try {
Files.write(data, f, StandardCharsets.UTF_8);
@@ -912,7 +863,7 @@ private static void writeFile(File f, String data) {
e.printStackTrace();
}
}
-
+
private static String cleanPatterns(final String patterns) {
return Documentation.cleanPatterns(patterns);
}
@@ -926,14 +877,14 @@ private static String cleanPatterns(final String patterns, boolean escapeHTML) {
/**
* Checks if a string is empty or null then it will return the message provided
- *
+ *
* @param string the String to check
* @param message the String to return if either condition is true
*/
public String getDefaultIfNullOrEmpty(@Nullable String string, String message) {
return (string == null || string.isEmpty()) ? message : string; // Null check first otherwise NullPointerException is thrown
}
-
+
public String[] getDefaultIfNullOrEmpty(@Nullable String[] string, String message) {
return (string == null || string.length == 0 || string[0].equals("")) ? new String[]{ message } : string; // Null check first otherwise NullPointerException is thrown
}
diff --git a/src/main/java/ch/njol/skript/doc/JSONGenerator.java b/src/main/java/ch/njol/skript/doc/JSONGenerator.java
new file mode 100644
index 00000000000..9e33a90f66c
--- /dev/null
+++ b/src/main/java/ch/njol/skript/doc/JSONGenerator.java
@@ -0,0 +1,251 @@
+package ch.njol.skript.doc;
+
+import ch.njol.skript.Skript;
+import ch.njol.skript.classes.ClassInfo;
+import ch.njol.skript.lang.SkriptEventInfo;
+import ch.njol.skript.lang.SyntaxElement;
+import ch.njol.skript.lang.SyntaxElementInfo;
+import ch.njol.skript.lang.function.Functions;
+import ch.njol.skript.lang.function.JavaFunction;
+import ch.njol.skript.registrations.Classes;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+import org.jetbrains.annotations.Nullable;
+import org.skriptlang.skript.lang.structure.Structure;
+import org.skriptlang.skript.lang.structure.StructureInfo;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Iterator;
+import java.util.Objects;
+import java.util.stream.Stream;
+
+/**
+ * Generates JSON docs
+ */
+public class JSONGenerator extends DocumentationGenerator {
+
+ public JSONGenerator(File templateDir, File outputDir) {
+ super(templateDir, outputDir);
+ }
+
+ /**
+ * Coverts a String array to a JsonArray
+ * @param strings the String array to convert
+ * @return the JsonArray containing the Strings
+ */
+ private static @Nullable JsonArray convertToJsonArray(String @Nullable [] strings) {
+ if (strings == null)
+ return null;
+ JsonArray jsonArray = new JsonArray();
+ for (String string : strings)
+ jsonArray.add(new JsonPrimitive(string));
+ return jsonArray;
+ }
+
+ /**
+ * Generates the documentation JsonObject for an element that is annotated with documentation
+ * annotations (e.g. effects, conditions, etc.)
+ * @param syntaxInfo the syntax info element to generate the documentation object of
+ * @return the JsonObject representing the documentation of the provided syntax element
+ */
+ private @Nullable JsonObject generatedAnnotatedElement(SyntaxElementInfo> syntaxInfo) {
+ Class> syntaxClass = syntaxInfo.getElementClass();
+ Name nameAnnotation = syntaxClass.getAnnotation(Name.class);
+ if (nameAnnotation == null || syntaxClass.getAnnotation(NoDoc.class) != null)
+ return null;
+ JsonObject syntaxJsonObject = new JsonObject();
+ syntaxJsonObject.addProperty("id", DocumentationIdProvider.getId(syntaxInfo));
+ syntaxJsonObject.addProperty("name", nameAnnotation.value());
+
+ Since sinceAnnotation = syntaxClass.getAnnotation(Since.class);
+ syntaxJsonObject.addProperty("since", sinceAnnotation == null ? null : sinceAnnotation.value());
+
+ Description descriptionAnnotation = syntaxClass.getAnnotation(Description.class);
+ if (descriptionAnnotation != null) {
+ syntaxJsonObject.add("description", convertToJsonArray(descriptionAnnotation.value()));
+ } else {
+ syntaxJsonObject.add("description", new JsonArray());
+ }
+
+ Examples examplesAnnotation = syntaxClass.getAnnotation(Examples.class);
+ if (examplesAnnotation != null) {
+ syntaxJsonObject.add("examples", convertToJsonArray(examplesAnnotation.value()));
+ } else {
+ syntaxJsonObject.add("examples", new JsonArray());
+ }
+
+
+ syntaxJsonObject.add("patterns", convertToJsonArray(syntaxInfo.getPatterns()));
+ return syntaxJsonObject;
+ }
+
+ /**
+ * Generates the documentation JsonObject for an event
+ * @param eventInfo the event to generate the documentation object for
+ * @return a documentation JsonObject for the event
+ */
+ private JsonObject generateEventElement(SkriptEventInfo> eventInfo) {
+ JsonObject syntaxJsonObject = new JsonObject();
+ syntaxJsonObject.addProperty("id", DocumentationIdProvider.getId(eventInfo));
+ syntaxJsonObject.addProperty("name", eventInfo.name);
+ syntaxJsonObject.addProperty("since", eventInfo.getSince());
+ syntaxJsonObject.add("description", convertToJsonArray(eventInfo.getDescription()));
+ syntaxJsonObject.add("examples", convertToJsonArray(eventInfo.getExamples()));
+ syntaxJsonObject.add("patterns", convertToJsonArray(eventInfo.patterns));
+ return syntaxJsonObject;
+ }
+
+
+ /**
+ * Generates a JsonArray containing the documentation JsonObjects for each structure in the iterator
+ * @param infos the structures to generate documentation for
+ * @return a JsonArray containing the documentation JsonObjects for each structure
+ */
+ private > JsonArray generateStructureElementArray(Iterator infos) {
+ JsonArray syntaxArray = new JsonArray();
+ infos.forEachRemaining(info -> {
+ if (info instanceof SkriptEventInfo> eventInfo) {
+ syntaxArray.add(generateEventElement(eventInfo));
+ } else {
+ JsonObject structureElementJsonObject = generatedAnnotatedElement(info);
+ if (structureElementJsonObject != null)
+ syntaxArray.add(structureElementJsonObject);
+ }
+ });
+ return syntaxArray;
+ }
+
+ /**
+ * Generates a JsonArray containing the documentation JsonObjects for each syntax element in the iterator
+ * @param infos the syntax elements to generate documentation for
+ * @return a JsonArray containing the documentation JsonObjects for each syntax element
+ */
+ private > JsonArray generateSyntaxElementArray(Iterator infos) {
+ JsonArray syntaxArray = new JsonArray();
+ infos.forEachRemaining(info -> {
+ JsonObject syntaxJsonObject = generatedAnnotatedElement(info);
+ if (syntaxJsonObject != null)
+ syntaxArray.add(syntaxJsonObject);
+ });
+ return syntaxArray;
+ }
+
+ /**
+ * Generates the documentation JsonObject for a classinfo
+ * @param classInfo the ClassInfo to generate the documentation of
+ * @return the documentation Jsonobject of the ClassInfo
+ */
+ private @Nullable JsonObject generateClassInfoElement(ClassInfo> classInfo) {
+ if (!classInfo.hasDocs())
+ return null;
+ JsonObject syntaxJsonObject = new JsonObject();
+ syntaxJsonObject.addProperty("id", DocumentationIdProvider.getId(classInfo));
+ syntaxJsonObject.addProperty("name", getClassInfoName(classInfo));
+ syntaxJsonObject.addProperty("since", classInfo.getSince());
+ syntaxJsonObject.add("description", convertToJsonArray(classInfo.getDescription()));
+ syntaxJsonObject.add("examples", convertToJsonArray(classInfo.getExamples()));
+ syntaxJsonObject.add("patterns", convertToJsonArray(classInfo.getUsage()));
+ return syntaxJsonObject;
+ }
+
+ /**
+ * Generates a JsonArray containing the documentation JsonObjects for each classinfo in the iterator
+ * @param classInfos the classinfos to generate documentation for
+ * @return a JsonArray containing the documentation JsonObjects for each classinfo
+ */
+ private JsonArray generateClassInfoArray(Iterator> classInfos) {
+ JsonArray syntaxArray = new JsonArray();
+ classInfos.forEachRemaining(classInfo -> {
+ JsonObject classInfoElement = generateClassInfoElement(classInfo);
+ if (classInfoElement != null)
+ syntaxArray.add(classInfoElement);
+ });
+ return syntaxArray;
+ }
+
+ /**
+ * Gets either the explicitly declared documentation name or code name of a ClassInfo
+ * @param classInfo the ClassInfo to get the effective name of
+ * @return the effective name of the ClassInfo
+ */
+ private String getClassInfoName(ClassInfo> classInfo) {
+ return Objects.requireNonNullElse(classInfo.getDocName(), classInfo.getCodeName());
+ }
+
+ /**
+ * Generates the documentation JsonObject for a JavaFunction
+ * @param function the JavaFunction to generate the JsonObject of
+ * @return the JsonObject of the JavaFunction
+ */
+ private JsonObject generateFunctionElement(JavaFunction> function) {
+ JsonObject functionJsonObject = new JsonObject();
+ functionJsonObject.addProperty("id", DocumentationIdProvider.getId(function));
+ functionJsonObject.addProperty("name", function.getName());
+ functionJsonObject.addProperty("since", function.getSince());
+ functionJsonObject.add("description", convertToJsonArray(function.getDescription()));
+ functionJsonObject.add("examples", convertToJsonArray(function.getExamples()));
+
+ ClassInfo> returnType = function.getReturnType();
+ if (returnType != null) {
+ functionJsonObject.addProperty("return-type", getClassInfoName(returnType));
+ }
+
+ String functionSignature = function.getSignature().toString(false, false);
+ functionJsonObject.add("patterns", convertToJsonArray(new String[] { functionSignature }));
+ return functionJsonObject;
+ }
+
+ /**
+ * Generates a JsonArray containing the documentation JsonObjects for each function in the iterator
+ * @param functions the functions to generate documentation for
+ * @return a JsonArray containing the documentation JsonObjects for each function
+ */
+ private JsonArray generateFunctionArray(Iterator> functions) {
+ JsonArray syntaxArray = new JsonArray();
+ functions.forEachRemaining(function -> syntaxArray.add(generateFunctionElement(function)));
+ return syntaxArray;
+ }
+
+ /**
+ * Writes the documentation JsonObject to an output path
+ * @param outputPath the path to write the documentation to
+ * @param jsonDocs the documentation JsonObject
+ */
+ private void saveDocs(Path outputPath, JsonObject jsonDocs) {
+ try {
+ Gson jsonGenerator = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create();
+ Files.writeString(outputPath, jsonGenerator.toJson(jsonDocs));
+ } catch (IOException exception) {
+ //noinspection ThrowableNotThrown
+ Skript.exception(exception, "An error occurred while trying to generate JSON documentation");
+ }
+ }
+
+ @Override
+ public void generate() {
+ JsonObject jsonDocs = new JsonObject();
+
+ jsonDocs.add("skriptVersion", new JsonPrimitive(Skript.getVersion().toString()));
+ jsonDocs.add("conditions", generateSyntaxElementArray(Skript.getConditions().iterator()));
+ jsonDocs.add("effects", generateSyntaxElementArray(Skript.getEffects().iterator()));
+ jsonDocs.add("expressions", generateSyntaxElementArray(Skript.getExpressions()));
+ jsonDocs.add("events", generateStructureElementArray(Skript.getEvents().iterator()));
+ jsonDocs.add("classes", generateClassInfoArray(Classes.getClassInfos().iterator()));
+
+ Stream> structuresExcludingEvents = Skript.getStructures().stream()
+ .filter(structureInfo -> !(structureInfo instanceof SkriptEventInfo));
+ jsonDocs.add("structures", generateStructureElementArray(structuresExcludingEvents.iterator()));
+ jsonDocs.add("sections", generateSyntaxElementArray(Skript.getSections().iterator()));
+
+ jsonDocs.add("functions", generateFunctionArray(Functions.getJavaFunctions().iterator()));
+
+ saveDocs(outputDir.toPath().resolve("docs.json"), jsonDocs);
+ }
+
+}
diff --git a/src/main/java/ch/njol/skript/effects/EffCancelEvent.java b/src/main/java/ch/njol/skript/effects/EffCancelEvent.java
index 317cdd3a9ee..3343f032cdf 100644
--- a/src/main/java/ch/njol/skript/effects/EffCancelEvent.java
+++ b/src/main/java/ch/njol/skript/effects/EffCancelEvent.java
@@ -1,34 +1,5 @@
-/**
- * This file is part of Skript.
- *
- * Skript is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Skript is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Skript. If not, see .
- *
- * Copyright Peter Güttinger, SkriptLang team and contributors
- */
package ch.njol.skript.effects;
-import org.bukkit.entity.Player;
-import org.bukkit.event.Cancellable;
-import org.bukkit.event.Event;
-import org.bukkit.event.Event.Result;
-import org.bukkit.event.block.BlockCanBuildEvent;
-import org.bukkit.event.inventory.InventoryInteractEvent;
-import org.bukkit.event.player.PlayerDropItemEvent;
-import org.bukkit.event.player.PlayerInteractEvent;
-import org.bukkit.event.player.PlayerLoginEvent;
-import org.jetbrains.annotations.Nullable;
-
import ch.njol.skript.Skript;
import ch.njol.skript.bukkitutil.PlayerUtils;
import ch.njol.skript.doc.Description;
@@ -42,39 +13,58 @@
import ch.njol.skript.log.ErrorQuality;
import ch.njol.skript.util.Utils;
import ch.njol.util.Kleenean;
+import org.bukkit.entity.Player;
+import org.bukkit.event.Cancellable;
+import org.bukkit.event.Event;
+import org.bukkit.event.block.BlockCanBuildEvent;
+import org.bukkit.event.entity.EntityToggleSwimEvent;
+import org.bukkit.event.inventory.InventoryInteractEvent;
+import org.bukkit.event.player.PlayerDropItemEvent;
+import org.bukkit.event.player.PlayerInteractEvent;
+import org.bukkit.event.player.PlayerLoginEvent;
+import org.jetbrains.annotations.Nullable;
-/**
- * @author Peter Güttinger
- */
@Name("Cancel Event")
@Description("Cancels the event (e.g. prevent blocks from being placed, or damage being taken).")
-@Examples({"on damage:",
- " victim is a player",
- " victim has the permission \"skript.god\"",
- " cancel the event"})
+@Examples({
+ "on damage:",
+ "\tvictim is a player",
+ "\tvictim has the permission \"skript.god\"",
+ "\tcancel the event"
+})
@Since("1.0")
public class EffCancelEvent extends Effect {
+
static {
Skript.registerEffect(EffCancelEvent.class, "cancel [the] event", "uncancel [the] event");
}
private boolean cancel;
-
- @SuppressWarnings("null")
+
@Override
- public boolean init(final Expression>[] vars, final int matchedPattern, final Kleenean isDelayed, final ParseResult parser) {
+ public boolean init(Expression>[] expressions, int matchedPattern,
+ Kleenean isDelayed, ParseResult parseResult) {
if (isDelayed == Kleenean.TRUE) {
- Skript.error("Can't cancel an event anymore after it has already passed", ErrorQuality.SEMANTIC_ERROR);
+ Skript.error("An event cannot be cancelled after it has already passed", ErrorQuality.SEMANTIC_ERROR);
return false;
}
+
cancel = matchedPattern == 0;
- final Class extends Event>[] es = getParser().getCurrentEvents();
- if (es == null)
+ Class extends Event>[] currentEvents = getParser().getCurrentEvents();
+
+ if (currentEvents == null)
+ return false;
+
+ if (cancel && getParser().isCurrentEvent(EntityToggleSwimEvent.class)) {
+ Skript.error("Cancelling a toggle swim event has no effect");
return false;
- for (final Class extends Event> e : es) {
- if (Cancellable.class.isAssignableFrom(e) || BlockCanBuildEvent.class.isAssignableFrom(e))
+ }
+
+ for (Class extends Event> event : currentEvents) {
+ if (Cancellable.class.isAssignableFrom(event) || BlockCanBuildEvent.class.isAssignableFrom(event))
return true; // TODO warning if some event(s) cannot be cancelled even though some can (needs a way to be suppressed)
}
+
if (getParser().isCurrentEvent(PlayerLoginEvent.class))
Skript.error("A connect event cannot be cancelled, but the player may be kicked ('kick player by reason of \"...\"')", ErrorQuality.SEMANTIC_ERROR);
else
@@ -83,24 +73,24 @@ public boolean init(final Expression>[] vars, final int matchedPattern, final
}
@Override
- public void execute(final Event e) {
- if (e instanceof Cancellable)
- ((Cancellable) e).setCancelled(cancel);
- if (e instanceof PlayerInteractEvent) {
- EvtClick.interactTracker.eventModified((Cancellable) e);
- ((PlayerInteractEvent) e).setUseItemInHand(cancel ? Result.DENY : Result.DEFAULT);
- ((PlayerInteractEvent) e).setUseInteractedBlock(cancel ? Result.DENY : Result.DEFAULT);
- } else if (e instanceof BlockCanBuildEvent) {
- ((BlockCanBuildEvent) e).setBuildable(!cancel);
- } else if (e instanceof PlayerDropItemEvent) {
- PlayerUtils.updateInventory(((PlayerDropItemEvent) e).getPlayer());
- } else if (e instanceof InventoryInteractEvent) {
- PlayerUtils.updateInventory(((Player) ((InventoryInteractEvent) e).getWhoClicked()));
+ public void execute(Event event) {
+ if (event instanceof Cancellable)
+ ((Cancellable) event).setCancelled(cancel);
+ if (event instanceof PlayerInteractEvent) {
+ EvtClick.interactTracker.eventModified((Cancellable) event);
+ ((PlayerInteractEvent) event).setUseItemInHand(cancel ? Event.Result.DENY : Event.Result.DEFAULT);
+ ((PlayerInteractEvent) event).setUseInteractedBlock(cancel ? Event.Result.DENY : Event.Result.DEFAULT);
+ } else if (event instanceof BlockCanBuildEvent) {
+ ((BlockCanBuildEvent) event).setBuildable(!cancel);
+ } else if (event instanceof PlayerDropItemEvent) {
+ PlayerUtils.updateInventory(((PlayerDropItemEvent) event).getPlayer());
+ } else if (event instanceof InventoryInteractEvent) {
+ PlayerUtils.updateInventory(((Player) ((InventoryInteractEvent) event).getWhoClicked()));
}
}
@Override
- public String toString(final @Nullable Event e, final boolean debug) {
+ public String toString(@Nullable Event event, boolean debug) {
return (cancel ? "" : "un") + "cancel event";
}
diff --git a/src/main/java/ch/njol/skript/effects/EffSort.java b/src/main/java/ch/njol/skript/effects/EffSort.java
index d387fe7bae0..25f7fc840e0 100644
--- a/src/main/java/ch/njol/skript/effects/EffSort.java
+++ b/src/main/java/ch/njol/skript/effects/EffSort.java
@@ -1,21 +1,3 @@
-/**
- * This file is part of Skript.
- *
- * Skript is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Skript is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Skript. If not, see .
- *
- * Copyright Peter Güttinger, SkriptLang team and contributors
- */
package ch.njol.skript.effects;
import ch.njol.skript.Skript;
@@ -48,10 +30,13 @@
import java.util.Set;
@Name("Sort")
-@Description({
- "Sorts a list variable using either the natural ordering of the contents or the results of the given expression.",
- "Be warned, this will overwrite the indices of the list variable."
-})
+@Description("""
+ Sorts a list variable using either the natural ordering of the contents or the results of the given expression.
+ Be warned, this will overwrite the indices of the list variable.
+
+ When using the full sort %~objects% (by|based on) <expression> pattern,
+ the input expression can be used to refer to the current item being sorted.
+ (See input expression for more information.)""")
@Examples({
"set {_words::*} to \"pineapple\", \"banana\", \"yoghurt\", and \"apple\"",
"sort {_words::*} # alphabetical sort",
diff --git a/src/main/java/ch/njol/skript/events/EvtEntityPotion.java b/src/main/java/ch/njol/skript/events/EvtEntityPotion.java
new file mode 100644
index 00000000000..ab56287ef2b
--- /dev/null
+++ b/src/main/java/ch/njol/skript/events/EvtEntityPotion.java
@@ -0,0 +1,74 @@
+/**
+ * This file is part of Skript.
+ *
+ * Skript is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Skript is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Skript. If not, see .
+ *
+ * Copyright Peter Güttinger, SkriptLang team and contributors
+ */
+package ch.njol.skript.events;
+
+import ch.njol.skript.Skript;
+import ch.njol.skript.lang.Expression;
+import ch.njol.skript.lang.Literal;
+import ch.njol.skript.lang.SkriptEvent;
+import ch.njol.skript.lang.SkriptParser.ParseResult;
+import org.bukkit.event.Event;
+import org.bukkit.event.entity.EntityPotionEffectEvent;
+import org.bukkit.potion.PotionEffectType;
+
+import javax.annotation.Nullable;
+
+public class EvtEntityPotion extends SkriptEvent {
+
+ static {
+ Skript.registerEvent("Entity Potion Effect", EvtEntityPotion.class, EntityPotionEffectEvent.class,
+ "entity potion effect [modif[y|ication]] [[of] %-potioneffecttypes%] [due to %-entitypotioncause%]")
+ .description("Called when an entity's potion effect is modified.", "This modification can include adding, removing or changing their potion effect.")
+ .examples(
+ "on entity potion effect modification:",
+ "\t\tbroadcast \"A potion effect was added to %event-entity%!\" ",
+ "",
+ "on entity potion effect modification of night vision:")
+ .since("INSERT VERSION");
+ }
+
+ @SuppressWarnings("unchecked")
+ private Expression potionEffects;
+ private Expression cause;
+
+ @Override
+ public boolean init(Literal>[] args, int matchedPattern, ParseResult parseResult) {
+ potionEffects = (Expression) args[0];
+ cause = (Expression) args[1];
+ return true;
+ }
+
+ @Override
+ public boolean check(Event event) {
+ EntityPotionEffectEvent potionEvent = (EntityPotionEffectEvent) event;
+ boolean effectMatches = potionEffects == null ||
+ (potionEvent.getOldEffect() != null && potionEffects.check(event, effectType -> effectType.equals(potionEvent.getOldEffect().getType()))) ||
+ (potionEvent.getNewEffect() != null && potionEffects.check(event, effectType -> effectType.equals(potionEvent.getNewEffect().getType())));
+
+ boolean causeMatches = cause == null || cause.check(event, cause -> cause.equals(potionEvent.getCause()));
+
+ return effectMatches && causeMatches;
+ }
+
+
+ @Override
+ public String toString(@Nullable Event event, boolean debug) {
+ return "on entity potion effect modification";
+ }
+}
diff --git a/src/main/java/ch/njol/skript/expressions/ExprHoverList.java b/src/main/java/ch/njol/skript/expressions/ExprHoverList.java
index a3b58b6b9d3..835319f3449 100644
--- a/src/main/java/ch/njol/skript/expressions/ExprHoverList.java
+++ b/src/main/java/ch/njol/skript/expressions/ExprHoverList.java
@@ -104,7 +104,7 @@ public void change(Event event, @Nullable Object[] delta, ChangeMode mode) {
if (HAS_NEW_LISTED_PLAYER_INFO) {
List values = new ArrayList<>();
- if (mode != ChangeMode.DELETE && mode != ChangeMode.RESET) {
+ if (mode != ChangeMode.DELETE && mode != ChangeMode.RESET && mode != ChangeMode.REMOVE) {
for (Object object : delta) {
if (object instanceof Player) {
Player player = (Player) object;
@@ -124,7 +124,9 @@ public void change(Event event, @Nullable Object[] delta, ChangeMode mode) {
sample.addAll(values);
break;
case REMOVE:
- sample.removeAll(values);
+ for (Object value : delta) {
+ sample.removeIf(profile -> profile.name().equals(value));
+ }
break;
case DELETE:
case RESET:
@@ -135,7 +137,7 @@ public void change(Event event, @Nullable Object[] delta, ChangeMode mode) {
}
List values = new ArrayList<>();
- if (mode != ChangeMode.DELETE && mode != ChangeMode.RESET) {
+ if (mode != ChangeMode.DELETE && mode != ChangeMode.RESET && mode != ChangeMode.REMOVE) {
for (Object object : delta) {
if (object instanceof Player) {
Player player = (Player) object;
@@ -150,13 +152,14 @@ public void change(Event event, @Nullable Object[] delta, ChangeMode mode) {
switch (mode) {
case SET:
sample.clear();
- sample.addAll(values);
- break;
+ // $FALL-THROUGH$
case ADD:
sample.addAll(values);
break;
case REMOVE:
- sample.removeAll(values);
+ for (Object value : delta) {
+ sample.removeIf(profile -> profile.getName() != null && profile.getName().equals(value));
+ }
break;
case DELETE:
case RESET:
diff --git a/src/main/java/ch/njol/skript/expressions/ExprItems.java b/src/main/java/ch/njol/skript/expressions/ExprItems.java
index 652cb3f6a72..4fb0c11d66a 100644
--- a/src/main/java/ch/njol/skript/expressions/ExprItems.java
+++ b/src/main/java/ch/njol/skript/expressions/ExprItems.java
@@ -1,21 +1,3 @@
-/**
- * This file is part of Skript.
- *
- * Skript is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Skript is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Skript. If not, see .
- *
- * Copyright Peter Güttinger, SkriptLang team and contributors
- */
package ch.njol.skript.expressions;
import ch.njol.skript.Skript;
@@ -54,7 +36,7 @@
public class ExprItems extends SimpleExpression {
private static final ItemType[] ALL_BLOCKS = Arrays.stream(Material.values())
- .filter(Material::isBlock)
+ .filter(material -> !material.isLegacy() && material.isBlock())
.map(ItemType::new)
.toArray(ItemType[]::new);
diff --git a/src/main/java/ch/njol/skript/expressions/ExprPlayerChatCompletions.java b/src/main/java/ch/njol/skript/expressions/ExprPlayerChatCompletions.java
new file mode 100644
index 00000000000..7bf7d335214
--- /dev/null
+++ b/src/main/java/ch/njol/skript/expressions/ExprPlayerChatCompletions.java
@@ -0,0 +1,93 @@
+package ch.njol.skript.expressions;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.bukkit.entity.Player;
+import org.bukkit.event.Event;
+import org.jetbrains.annotations.Nullable;
+
+import ch.njol.skript.Skript;
+import ch.njol.skript.classes.Changer.ChangeMode;
+import ch.njol.skript.doc.Description;
+import ch.njol.skript.doc.Examples;
+import ch.njol.skript.doc.Name;
+import ch.njol.skript.doc.RequiredPlugins;
+import ch.njol.skript.doc.Since;
+import ch.njol.skript.expressions.base.SimplePropertyExpression;
+import ch.njol.util.coll.CollectionUtils;
+
+@Name("Player Chat Completions")
+@Description({
+ "The custom chat completion suggestions. You can add, set, remove, and clear them. Removing the names of online players with this expression is ineffective.",
+ "This expression will not return anything due to Bukkit limitations."
+})
+@Examples({
+ "add \"Skript\" and \"Njol\" to chat completions of all players",
+ "remove \"text\" from {_p}'s chat completions",
+ "clear player's chat completions"
+})
+@RequiredPlugins("Spigot 1.19+")
+@Since("INSERT VERSION")
+public class ExprPlayerChatCompletions extends SimplePropertyExpression {
+
+ static {
+ if (Skript.methodExists(Player.class, "addCustomChatCompletions", Collection.class))
+ register(ExprPlayerChatCompletions.class, String.class, "[custom] chat completion[s]", "players");
+ }
+
+ @Override
+ public @Nullable String convert(Player player) {
+ return null; // Due to Bukkit limitations
+ }
+
+ @Override
+ public @Nullable Class>[] acceptChange(ChangeMode mode) {
+ return switch (mode) {
+ case ADD, SET, REMOVE, DELETE, RESET -> CollectionUtils.array(String[].class);
+ default -> null;
+ };
+ }
+
+ @Override
+ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) {
+ Player[] players = getExpr().getArray(event);
+ if (players.length == 0)
+ return;
+ List completions = new ArrayList<>();
+ if (delta != null && (mode == ChangeMode.ADD || mode == ChangeMode.REMOVE || mode == ChangeMode.SET)) {
+ completions = Arrays.stream(delta)
+ .filter(String.class::isInstance)
+ .map(String.class::cast)
+ .collect(Collectors.toList());
+ }
+ switch (mode) {
+ case DELETE, RESET, SET -> {
+ for (Player player : players)
+ player.setCustomChatCompletions(completions);
+ }
+ case ADD -> {
+ for (Player player : players)
+ player.addCustomChatCompletions(completions);
+ }
+ case REMOVE -> {
+ for (Player player : players)
+ player.removeCustomChatCompletions(completions);
+ }
+ }
+ }
+
+ @Override
+ public Class extends String> getReturnType() {
+ return String.class;
+ }
+
+ @Override
+ protected String getPropertyName() {
+ return "custom chat completions";
+ }
+
+}
diff --git a/src/main/java/ch/njol/skript/expressions/ExprRandomNumber.java b/src/main/java/ch/njol/skript/expressions/ExprRandomNumber.java
index 9cf189d5643..f9ae82447fd 100644
--- a/src/main/java/ch/njol/skript/expressions/ExprRandomNumber.java
+++ b/src/main/java/ch/njol/skript/expressions/ExprRandomNumber.java
@@ -18,9 +18,11 @@
*/
package ch.njol.skript.expressions;
+import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
+import ch.njol.skript.lang.Literal;
import ch.njol.util.Math2;
import org.bukkit.event.Event;
import org.jetbrains.annotations.Nullable;
@@ -36,62 +38,90 @@
import ch.njol.skript.lang.util.SimpleExpression;
import ch.njol.util.Kleenean;
-@Name("Random Number")
+@Name("Random Numbers")
@Description({
- "A random number or integer between two given numbers. Use 'number' if you want any number with decimal parts, or use use 'integer' if you only want whole numbers.",
- "Please note that the order of the numbers doesn't matter, i.e. random number between 2 and 1 will work as well as random number between 1 and 2."
+ "A given amount of random numbers or integers between two given numbers. Use 'number' if you want any number with decimal parts, or use use 'integer' if you only want whole numbers.",
+ "Please note that the order of the numbers doesn't matter, i.e. random number between 2 and 1 will work as well as random number between 1 and 2."
})
@Examples({
- "set the player's health to a random number between 5 and 10",
- "send \"You rolled a %random integer from 1 to 6%!\" to the player"
+ "set the player's health to a random number between 5 and 10",
+ "send \"You rolled a %random integer from 1 to 6%!\" to the player",
+ "set {_chances::*} to 5 random integers between 5 and 96",
+ "set {_decimals::*} to 3 random numbers between 2.7 and -1.5"
})
-@Since("1.4")
+@Since("1.4, INSERT VERSION (Multiple random numbers)")
public class ExprRandomNumber extends SimpleExpression {
static {
Skript.registerExpression(ExprRandomNumber.class, Number.class, ExpressionType.COMBINED,
- "[a] random (:integer|number) (from|between) %number% (to|and) %number%");
+ "[a|%-integer%] random (:integer|number)[s] (from|between) %number% (to|and) %number%");
}
- private Expression from, to;
+ @Nullable
+ private Expression amount;
+ private Expression lower, upper;
private boolean isInteger;
@Override
@SuppressWarnings("unchecked")
- public boolean init(Expression>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) {
- from = (Expression) exprs[0];
- to = (Expression) exprs[1];
- isInteger = parser.hasTag("integer");
+ public boolean init(Expression>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) {
+ amount = (Expression) exprs[0];
+ lower = (Expression) exprs[1];
+ upper = (Expression) exprs[2];
+ isInteger = parseResult.hasTag("integer");
return true;
}
@Override
@Nullable
protected Number[] get(Event event) {
- Number from = this.from.getSingle(event);
- Number to = this.to.getSingle(event);
+ Number lowerNumber = lower.getSingle(event);
+ Number upperNumber = upper.getSingle(event);
+ if (upperNumber == null || lowerNumber == null || !Double.isFinite(lowerNumber.doubleValue()) || !Double.isFinite(upperNumber.doubleValue()))
+ return new Number[0];
- if (to == null || from == null || !Double.isFinite(from.doubleValue()) || !Double.isFinite(to.doubleValue()))
+ Integer amount = this.amount == null ? Integer.valueOf(1) : this.amount.getSingle(event);
+ if (amount == null || amount <= 0)
return new Number[0];
+ double lower = Math.min(lowerNumber.doubleValue(), upperNumber.doubleValue());
+ double upper = Math.max(lowerNumber.doubleValue(), upperNumber.doubleValue());
Random random = ThreadLocalRandom.current();
- double min = Math.min(from.doubleValue(), to.doubleValue());
- double max = Math.max(from.doubleValue(), to.doubleValue());
-
if (isInteger) {
- long inf = Math2.ceil(min);
- long sup = Math2.floor(max);
- if (max - min < 1 && inf - sup <= 1) {
- if (sup == inf || min == inf)
- return new Long[] {inf};
- if (max == sup)
- return new Long[] {sup};
+ Long[] longs = new Long[amount];
+ long floored_upper = Math2.floor(upper);
+ long ceiled_lower = Math2.ceil(lower);
+
+ // catch issues like `integer between 0.5 and 0.6`
+ if (upper - lower < 1 && ceiled_lower - floored_upper <= 1) {
+ if (floored_upper == ceiled_lower || lower == ceiled_lower) {
+ Arrays.fill(longs, ceiled_lower);
+ return longs;
+ }
+ if (upper == floored_upper) {
+ Arrays.fill(longs, floored_upper);
+ return longs;
+ }
return new Long[0];
}
- return new Long[] {inf + Math2.mod(random.nextLong(), sup - inf + 1)};
+
+ for (int i = 0; i < amount; i++)
+ longs[i] = Math2.ceil(lower) + Math2.mod(random.nextLong(), floored_upper - ceiled_lower + 1);
+ return longs;
+ // non-integers
+ } else {
+ Double[] doubles = new Double[amount];
+ for (int i = 0; i < amount; i++)
+ doubles[i] = Math.min(lower + random.nextDouble() * (upper - lower), upper);
+ return doubles;
}
+ }
- return new Double[] {min + random.nextDouble() * (max - min)};
+ @Override
+ public boolean isSingle() {
+ if (amount instanceof Literal)
+ return ((Literal) amount).getSingle() == 1;
+ return amount == null;
}
@Override
@@ -101,12 +131,8 @@ public Class extends Number> getReturnType() {
@Override
public String toString(@Nullable Event event, boolean debug) {
- return "a random " + (isInteger ? "integer" : "number") + " between " + from.toString(event, debug) + " and " + to.toString(event, debug);
- }
-
- @Override
- public boolean isSingle() {
- return true;
+ return (amount == null ? "a" : amount.toString(event, debug)) + " random " + (isInteger ? "integer" : "number") +
+ (amount == null ? "" : "s") + " between " + lower.toString(event, debug) + " and " + upper.toString(event, debug);
}
}
diff --git a/src/main/java/ch/njol/skript/expressions/ExprSkull.java b/src/main/java/ch/njol/skript/expressions/ExprSkull.java
index bce8422a76d..8b8c0e0e19b 100644
--- a/src/main/java/ch/njol/skript/expressions/ExprSkull.java
+++ b/src/main/java/ch/njol/skript/expressions/ExprSkull.java
@@ -1,86 +1,45 @@
-/**
- * This file is part of Skript.
- *
- * Skript is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Skript is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Skript. If not, see .
- *
- * Copyright Peter Güttinger, SkriptLang team and contributors
- */
package ch.njol.skript.expressions;
+import ch.njol.skript.bukkitutil.ItemUtils;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
-import org.bukkit.inventory.meta.SkullMeta;
import org.jetbrains.annotations.Nullable;
-import ch.njol.skript.Skript;
-import ch.njol.skript.aliases.Aliases;
import ch.njol.skript.aliases.ItemType;
import ch.njol.skript.doc.Description;
import ch.njol.skript.doc.Examples;
import ch.njol.skript.doc.Name;
import ch.njol.skript.doc.Since;
import ch.njol.skript.expressions.base.SimplePropertyExpression;
-import ch.njol.skript.lang.Expression;
-import ch.njol.skript.lang.SkriptParser.ParseResult;
-import ch.njol.util.Kleenean;
-/**
- * @author Peter Güttinger
- */
@Name("Player Skull")
@Description("Gets a skull item representing a player. Skulls for other entities are provided by the aliases.")
-@Examples({"give the victim's skull to the attacker",
- "set the block at the entity to the entity's skull"})
+@Examples({
+ "give the victim's skull to the attacker",
+ "set the block at the entity to the entity's skull"
+})
@Since("2.0")
-public class ExprSkull extends SimplePropertyExpression