From e336707b1e4b47c05510ac7ac88c1c2f87fa7982 Mon Sep 17 00:00:00 2001 From: Shane Bee Date: Sun, 16 Feb 2025 13:39:22 -0800 Subject: [PATCH 01/18] ExprCompassTarget - update docs (#7584) --- .../java/ch/njol/skript/expressions/ExprCompassTarget.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprCompassTarget.java b/src/main/java/ch/njol/skript/expressions/ExprCompassTarget.java index 9bcc06735d4..9228c9d6ea0 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprCompassTarget.java +++ b/src/main/java/ch/njol/skript/expressions/ExprCompassTarget.java @@ -16,7 +16,9 @@ * @author Peter Güttinger */ @Name("Compass Target") -@Description("The location a player's compass is pointing at.") +@Description({"The location a player's compass is pointing at.", + "As of Minecraft 1.21.4, the compass is controlled by the resource pack and by default will not point to " + + "this compass target when used outside of the overworld dimension."}) @Examples({"# make all player's compasses target a player stored in {compass::target::%player%}", "every 5 seconds:", "\tloop all players:", From f32613d7fa19a18fa65d33403291185f59779a9b Mon Sep 17 00:00:00 2001 From: Eren <67760502+erenkarakal@users.noreply.github.com> Date: Mon, 17 Feb 2025 00:52:39 +0300 Subject: [PATCH 02/18] Fix Text Display Alignment Example (#7603) --- .../skript/bukkit/displays/text/ExprTextDisplayAlignment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/skriptlang/skript/bukkit/displays/text/ExprTextDisplayAlignment.java b/src/main/java/org/skriptlang/skript/bukkit/displays/text/ExprTextDisplayAlignment.java index 64cd0786d07..b05ea938928 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/displays/text/ExprTextDisplayAlignment.java +++ b/src/main/java/org/skriptlang/skript/bukkit/displays/text/ExprTextDisplayAlignment.java @@ -15,7 +15,7 @@ @Name("Text Display Alignment") @Description("Returns or changes the alignment setting of text displays.") -@Examples("set text alignment of the last spawned text display to left") +@Examples("set text alignment of the last spawned text display to left aligned") @Since("2.10") public class ExprTextDisplayAlignment extends SimplePropertyExpression { From c7c5d17f4ac51872f8e34fbdab1ed48faa759d0b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Feb 2025 22:22:24 -0500 Subject: [PATCH 03/18] Bump org.joml:joml from 1.10.5 to 1.10.8 (#7486) Bumps [org.joml:joml](https://github.com/JOML-CI/JOML) from 1.10.5 to 1.10.8. - [Release notes](https://github.com/JOML-CI/JOML/releases) - [Commits](https://github.com/JOML-CI/JOML/compare/1.10.5...1.10.8) --- updated-dependencies: - dependency-name: org.joml:joml dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 9d647455a6b..b0e4c846287 100644 --- a/build.gradle +++ b/build.gradle @@ -34,7 +34,7 @@ dependencies { implementation group: 'com.google.code.findbugs', name: 'findbugs', version: '3.0.1' // bundled with Minecraft 1.19.4+ for display entity transforms - implementation group: 'org.joml', name: 'joml', version: '1.10.5' + implementation group: 'org.joml', name: 'joml', version: '1.10.8' // Plugin hook libraries implementation group: 'com.sk89q.worldguard', name: 'worldguard-legacy', version: '7.0.0-SNAPSHOT', { From 006c29c3c451e658317f2ae989b30f2ce170d99e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Feb 2025 22:30:20 -0500 Subject: [PATCH 04/18] Bump org.easymock:easymock from 5.4.0 to 5.5.0 (#7488) Bumps [org.easymock:easymock](https://github.com/easymock/easymock) from 5.4.0 to 5.5.0. - [Release notes](https://github.com/easymock/easymock/releases) - [Changelog](https://github.com/easymock/easymock/blob/master/ReleaseNotes.md) - [Commits](https://github.com/easymock/easymock/compare/easymock-5.4.0...easymock-5.5.0) --- updated-dependencies: - dependency-name: org.easymock:easymock dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b0e4c846287..e3ad791966e 100644 --- a/build.gradle +++ b/build.gradle @@ -48,7 +48,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.4.0' + testShadow group: 'org.easymock', name: 'easymock', version: '5.5.0' } checkstyle { From 198bddb244786359462e27147071b31c439bda6d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Feb 2025 22:33:55 -0500 Subject: [PATCH 05/18] Bump org.bstats:bstats-bukkit from 3.0.2 to 3.1.0 (#7489) Bumps [org.bstats:bstats-bukkit](https://github.com/Bastian/bStats-Metrics) from 3.0.2 to 3.1.0. - [Release notes](https://github.com/Bastian/bStats-Metrics/releases) - [Commits](https://github.com/Bastian/bStats-Metrics/compare/v3.0.2...v3.1.0) --- updated-dependencies: - dependency-name: org.bstats:bstats-bukkit dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index e3ad791966e..5b3e105c3f0 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ allprojects { dependencies { shadow group: 'io.papermc', name: 'paperlib', version: '1.0.8' - shadow group: 'org.bstats', name: 'bstats-bukkit', version: '3.0.2' + shadow group: 'org.bstats', name: 'bstats-bukkit', version: '3.1.0' shadow group: 'net.kyori', name: 'adventure-text-serializer-bungeecord', version: '4.3.2' implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.21.4-R0.1-SNAPSHOT' From 70b3ce6851787d0382169ead4fd7ab947c8bc126 Mon Sep 17 00:00:00 2001 From: Efnilite <35348263+Efnilite@users.noreply.github.com> Date: Fri, 28 Feb 2025 04:40:08 +0100 Subject: [PATCH 06/18] Fix empty event requirements in docs (#7499) --- .../ch/njol/skript/doc/HTMLGenerator.java | 40 +++++++++++-------- .../ch/njol/skript/lang/SkriptEventInfo.java | 10 ++++- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/src/main/java/ch/njol/skript/doc/HTMLGenerator.java b/src/main/java/ch/njol/skript/doc/HTMLGenerator.java index 63dbb2900a4..a2ef14fce7e 100644 --- a/src/main/java/ch/njol/skript/doc/HTMLGenerator.java +++ b/src/main/java/ch/njol/skript/doc/HTMLGenerator.java @@ -29,12 +29,7 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.Date; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; +import java.util.*; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -387,21 +382,30 @@ private static String minifyHtml(String page) { return sb.toString(); } - private static String handleIf(String desc, String start, boolean value) { + /** + * Handles an optional part in an HTML description. + * @param desc The existing description. + * @param condition The condition string to check. + * @param value Whether + * @return The modified description. + */ + private static String handleIf(String desc, String condition, boolean value) { assert desc != null; - int ifStart = desc.indexOf(start); + int ifStart = desc.indexOf(condition); + while (ifStart != -1) { int ifEnd = desc.indexOf("${end}", ifStart); - String data = desc.substring(ifStart + start.length() + 1, ifEnd); + String data = desc.substring(ifStart + condition.length() + 1, ifEnd); String before = desc.substring(0, ifStart); String after = desc.substring(ifEnd + 6); + if (value) - desc = before + data + after; + desc = before + data + after; // include if condition is met else - desc = before + after; + desc = before + after; // skip if condition is not met - ifStart = desc.indexOf(start, ifEnd); + ifStart = desc.indexOf(condition, ifEnd); } return desc; @@ -605,10 +609,14 @@ private String generateEvent(String descTemp, SkriptEventInfo info, @Nullable } desc = desc.replace("${element.events-safe}", events == null ? "" : Joiner.on(", ").join((events != null ? events.value() : null))); - // Required Plugins - String[] requiredPlugins = info.getRequiredPlugins(); - desc = handleIf(desc, "${if required-plugins}", requiredPlugins != null); - desc = desc.replace("${element.required-plugins}", Joiner.on(", ").join(requiredPlugins == null ? new String[0] : requiredPlugins)); + // RequiredPlugins + String[] plugins = info.getRequiredPlugins(); + desc = handleIf(desc, "${if required-plugins}", plugins != null && plugins.length > 0); + if (plugins == null) { + desc = desc.replace("${element.required-plugins}", ""); + } else { + desc = desc.replace("${element.required-plugins}", Joiner.on(", ").join(plugins)); + } // New Elements desc = handleIf(desc, "${if new-element}", NEW_TAG_PATTERN.matcher(since).find()); diff --git a/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java b/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java index 71bd8e661e9..06713eed888 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java +++ b/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java @@ -27,8 +27,14 @@ public sealed class SkriptEventInfo extends StructureInfo public final String name; private ListeningBehavior listeningBehavior; - private String @Nullable [] description, examples, keywords, requiredPlugins; - private @Nullable String since, documentationID; + + private String @Nullable [] description = null; + private String @Nullable [] examples = null; + private String @Nullable [] keywords = null; + private String @Nullable [] requiredPlugins = null; + + private @Nullable String since = null; + private @Nullable String documentationID = null; private final String id; From 6fb9749376087dd017349a47a78f71c339f9eadf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Feb 2025 22:45:55 -0500 Subject: [PATCH 07/18] Bump net.kyori:adventure-text-serializer-bungeecord from 4.3.2 to 4.3.4 (#7545) Bumps [net.kyori:adventure-text-serializer-bungeecord](https://github.com/KyoriPowered/adventure-platform) from 4.3.2 to 4.3.4. - [Release notes](https://github.com/KyoriPowered/adventure-platform/releases) - [Commits](https://github.com/KyoriPowered/adventure-platform/compare/v4.3.2...v4.3.4) --- updated-dependencies: - dependency-name: net.kyori:adventure-text-serializer-bungeecord dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5b3e105c3f0..3f10d16973f 100644 --- a/build.gradle +++ b/build.gradle @@ -28,7 +28,7 @@ allprojects { dependencies { shadow group: 'io.papermc', name: 'paperlib', version: '1.0.8' shadow group: 'org.bstats', name: 'bstats-bukkit', version: '3.1.0' - shadow group: 'net.kyori', name: 'adventure-text-serializer-bungeecord', version: '4.3.2' + shadow group: 'net.kyori', name: 'adventure-text-serializer-bungeecord', version: '4.3.4' implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.21.4-R0.1-SNAPSHOT' implementation group: 'com.google.code.findbugs', name: 'findbugs', version: '3.0.1' From f0ff9d283d098a028ffcc2a7a88ae72f9e61c020 Mon Sep 17 00:00:00 2001 From: SirSmurfy2 <82696841+TheAbsolutionism@users.noreply.github.com> Date: Thu, 27 Feb 2025 22:50:25 -0500 Subject: [PATCH 08/18] Vehicle Class Info (#7591) * Initial Commit * Fix Thanks to @ShaneBeee --------- --- .../skript/classes/data/BukkitClasses.java | 12 +++++-- src/main/resources/lang/default.lang | 1 + .../syntaxes/events/EvtVehicleEnterTest.java | 36 +++++++++++++++++++ src/test/skript/junit/EvtVehicleEnterTest.sk | 12 +++++++ 4 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 src/test/java/org/skriptlang/skript/test/tests/syntaxes/events/EvtVehicleEnterTest.java create mode 100644 src/test/skript/junit/EvtVehicleEnterTest.sk diff --git a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java index c6b17abb6d2..02652a96394 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -6,8 +6,8 @@ import ch.njol.skript.aliases.ItemType; import ch.njol.skript.bukkitutil.BukkitUtils; import ch.njol.skript.bukkitutil.EntityUtils; -import ch.njol.skript.bukkitutil.SkriptTeleportFlag; import ch.njol.skript.bukkitutil.ItemUtils; +import ch.njol.skript.bukkitutil.SkriptTeleportFlag; import ch.njol.skript.classes.*; import ch.njol.skript.classes.registry.RegistryClassInfo; import ch.njol.skript.entity.EntityData; @@ -21,7 +21,6 @@ import ch.njol.skript.util.BlockUtils; import ch.njol.skript.util.PotionEffectUtils; import ch.njol.skript.util.StringMode; -import ch.njol.util.StringUtils; import ch.njol.yggdrasil.Fields; import io.papermc.paper.world.MoonPhase; import org.bukkit.*; @@ -1536,6 +1535,15 @@ public String toVariableNameString(EntitySnapshot snapshot) { .description("Teleport Flags are settings to retain during a teleport.") .requiredPlugins("Paper 1.19+") .since("2.10")); + + Classes.registerClass(new ClassInfo<>(Vehicle.class, "vehicle") + .user("vehicles?") + .name("Vehicle") + .description("Represents a vehicle.") + .since("INSERT VERSION") + .changer(DefaultChangers.entityChanger) + ); + } } diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index e380aef7bcf..7b8bd655719 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -2653,6 +2653,7 @@ types: lootcontext: loot context¦s @a bannerpatterntype: banner pattern type¦s @a bannerpattern: banner pattern¦s @a + vehicle: vehicle¦s @a # Skript weathertype: weather type¦s @a diff --git a/src/test/java/org/skriptlang/skript/test/tests/syntaxes/events/EvtVehicleEnterTest.java b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/events/EvtVehicleEnterTest.java new file mode 100644 index 00000000000..15b86ffdc31 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/events/EvtVehicleEnterTest.java @@ -0,0 +1,36 @@ +package org.skriptlang.skript.test.tests.syntaxes.events; + +import ch.njol.skript.test.runner.SkriptJUnitTest; +import org.bukkit.Bukkit; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Vehicle; +import org.bukkit.event.vehicle.VehicleEnterEvent; +import org.easymock.EasyMock; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class EvtVehicleEnterTest extends SkriptJUnitTest { + + private Entity pig; + private Player player; + + @Before + public void setup() { + pig = spawnTestPig(); + player = EasyMock.niceMock(Player.class); + } + + @Test + public void test() { + Bukkit.getPluginManager().callEvent(new VehicleEnterEvent((Vehicle) pig, player)); + } + + @After + public void cleanup() { + if (pig != null) + pig.remove(); + } + +} diff --git a/src/test/skript/junit/EvtVehicleEnterTest.sk b/src/test/skript/junit/EvtVehicleEnterTest.sk new file mode 100644 index 00000000000..dd7db422d90 --- /dev/null +++ b/src/test/skript/junit/EvtVehicleEnterTest.sk @@ -0,0 +1,12 @@ +options: + test: "org.skriptlang.skript.test.tests.syntaxes.events.EvtVehicleEnterTest" + +test "EvtVehicleEnterTest" when running JUnit: + ensure {@test} completes "vehicle - pig" and "entity - player" + +on vehicle enter: + junit test is {@test} + if event-entity is a player: + complete "entity - player" for {@test} + if event-vehicle is a pig: + complete "vehicle - pig" for {@test} From 913a7d8c48c6a98805f8d3111a08476b06756d19 Mon Sep 17 00:00:00 2001 From: Efnilite <35348263+Efnilite@users.noreply.github.com> Date: Fri, 28 Feb 2025 04:55:51 +0100 Subject: [PATCH 09/18] Fix DateTest failing occasionally (#7632) * init commit * fix reviews --------- --- .../org/skriptlang/skript/test/tests/utils/DateTest.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/test/java/org/skriptlang/skript/test/tests/utils/DateTest.java b/src/test/java/org/skriptlang/skript/test/tests/utils/DateTest.java index 2f8f8f1d8b7..d449b002b39 100644 --- a/src/test/java/org/skriptlang/skript/test/tests/utils/DateTest.java +++ b/src/test/java/org/skriptlang/skript/test/tests/utils/DateTest.java @@ -4,14 +4,10 @@ import org.junit.Test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; public class DateTest { - @Test - public void testNow() { - assertEquals(System.currentTimeMillis(), Date.now().getTime()); - } - @Test public void testFromJavaDate() { java.util.Date javaDate = new java.util.Date(); From 20b8f2ac934ead40827d7bb2a621563211dfa8ae Mon Sep 17 00:00:00 2001 From: Moderocky Date: Fri, 28 Feb 2025 03:59:54 +0000 Subject: [PATCH 10/18] Documentation annotations improvements (Prototype). (#7470) * Add example annotation. * Support new annotation in documentation generators. * Update src/main/java/ch/njol/skript/doc/Example.java --- .../ch/njol/skript/doc/Documentation.java | 7 ++- src/main/java/ch/njol/skript/doc/Example.java | 50 +++++++++++++++++++ .../ch/njol/skript/doc/HTMLGenerator.java | 29 +++++++++-- .../ch/njol/skript/doc/JSONGenerator.java | 17 +++++-- 4 files changed, 95 insertions(+), 8 deletions(-) create mode 100644 src/main/java/ch/njol/skript/doc/Example.java diff --git a/src/main/java/ch/njol/skript/doc/Documentation.java b/src/main/java/ch/njol/skript/doc/Documentation.java index d1509f84a42..66386594cfc 100644 --- a/src/main/java/ch/njol/skript/doc/Documentation.java +++ b/src/main/java/ch/njol/skript/doc/Documentation.java @@ -286,7 +286,12 @@ private static void insertSyntaxElement(final PrintWriter pw, final SyntaxElemen Class elementClass = info.getElementClass(); if (elementClass.getAnnotation(NoDoc.class) != null) return; - if (elementClass.getAnnotation(Name.class) == null || elementClass.getAnnotation(Description.class) == null || elementClass.getAnnotation(Examples.class) == null || elementClass.getAnnotation(Since.class) == null) { + if (elementClass.getAnnotation(Name.class) == null + || elementClass.getAnnotation(Description.class) == null + || (!elementClass.isAnnotationPresent(Examples.class) + && !elementClass.isAnnotationPresent(Example.class) + && !elementClass.isAnnotationPresent(Example.Examples.class)) + || elementClass.getAnnotation(Since.class) == null) { Skript.warning("" + elementClass.getSimpleName() + " is missing information"); return; } diff --git a/src/main/java/ch/njol/skript/doc/Example.java b/src/main/java/ch/njol/skript/doc/Example.java new file mode 100644 index 00000000000..31934380c25 --- /dev/null +++ b/src/main/java/ch/njol/skript/doc/Example.java @@ -0,0 +1,50 @@ +package ch.njol.skript.doc; + +import java.lang.annotation.*; + +/** + * An example to be used in documentation for the annotated element. + * Multiple example annotations can be stacked on a single syntax element. + *

+ * Each annotation should include a single example. + * This can be used instead of the existing {@link ch.njol.skript.doc.Examples} annotation. + *

+ * Multi-line examples should use multi-line strings. + * Note that whitespace and quotes do not need to be escaped in this mode. + * The indentation will start from the least-indented line (and most IDEs provide a guideline to show this). + *

{@code
+ * @Example("set player's health to 1")
+ * @Example("""
+ * 		if player's health is greater than 10:
+ * 			send "Wow you're really healthy!"
+ * 		""")
+ * @Example("""
+ * 		# sets the player's health to 1
+ * 		set player's health to 1""")
+ * public class MyExpression extends ... {
+ * }
+ * }
+ */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Repeatable(Example.Examples.class) +@Documented +public @interface Example { + + String value(); + + boolean inTrigger() default true; // todo needed? + + /** + * The internal container annotation for multiple examples. + */ + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @interface Examples { + + Example[] value(); + + } + +} diff --git a/src/main/java/ch/njol/skript/doc/HTMLGenerator.java b/src/main/java/ch/njol/skript/doc/HTMLGenerator.java index a2ef14fce7e..15a179a2594 100644 --- a/src/main/java/ch/njol/skript/doc/HTMLGenerator.java +++ b/src/main/java/ch/njol/skript/doc/HTMLGenerator.java @@ -20,6 +20,7 @@ import org.bukkit.event.Cancellable; import org.bukkit.event.Event; import org.bukkit.event.block.BlockCanBuildEvent; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.entry.EntryData; import org.skriptlang.skript.lang.entry.EntryValidator; @@ -442,10 +443,7 @@ private String generateAnnotated(String descTemp, SyntaxElementInfo info, @Nu .replace("\\", "\\\\").replace("\"", "\\\"").replace("\t", " ")); // Examples - Examples examples = c.getAnnotation(Examples.class); - desc = desc.replace("${element.examples}", Joiner.on("
").join(getDefaultIfNullOrEmpty((examples != null ? Documentation.escapeHTML(examples.value()) : null), "Missing examples."))); - desc = desc.replace("${element.examples-safe}", Joiner.on("
").join(getDefaultIfNullOrEmpty((examples != null ? Documentation.escapeHTML(examples.value()) : null), "Missing examples.")) - .replace("\\", "\\\\").replace("\"", "\\\"").replace("\t", " ")); + desc = extractExamples(desc, c); // Documentation ID desc = desc.replace("${element.id}", DocumentationIdProvider.getId(info)); @@ -546,6 +544,29 @@ private String generateAnnotated(String descTemp, SyntaxElementInfo info, @Nu return desc; } + private @NotNull String extractExamples(String desc, Class syntax) { + if (syntax.isAnnotationPresent(Example.class)) { + Example examples = syntax.getAnnotation(Example.class); + return this.addExamples(desc, examples.value()); + } else if (syntax.isAnnotationPresent(Example.Examples.class)) { + Example.Examples examples = syntax.getAnnotation(Example.Examples.class); + return this.addExamples(desc, Arrays.stream(examples.value()) + .map(Example::value).toArray(String[]::new)); + } else if (syntax.isAnnotationPresent(Examples.class)) { + Examples examples = syntax.getAnnotation(Examples.class); + return this.addExamples(desc, examples.value()); + } else { + return this.addExamples(desc, (String[]) null); + } + } + + private @NotNull String addExamples(String desc, String @Nullable ... examples) { + desc = desc.replace("${element.examples}", Joiner.on("
").join(getDefaultIfNullOrEmpty((examples != null ? Documentation.escapeHTML(examples) : null), "Missing examples."))); + desc = desc.replace("${element.examples-safe}", Joiner.on("
").join(getDefaultIfNullOrEmpty((examples != null ? Documentation.escapeHTML(examples) : null), "Missing examples.")) + .replace("\\", "\\\\").replace("\"", "\\\"").replace("\t", " ")); + return desc; + } + private String generateEvent(String descTemp, SkriptEventInfo info, @Nullable String page) { Class c = info.getElementClass(); String desc; diff --git a/src/main/java/ch/njol/skript/doc/JSONGenerator.java b/src/main/java/ch/njol/skript/doc/JSONGenerator.java index f8c9827474e..0ef57d40822 100644 --- a/src/main/java/ch/njol/skript/doc/JSONGenerator.java +++ b/src/main/java/ch/njol/skript/doc/JSONGenerator.java @@ -13,6 +13,7 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.structure.Structure; import org.skriptlang.skript.lang.structure.StructureInfo; @@ -21,6 +22,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Arrays; import java.util.Iterator; import java.util.Objects; import java.util.stream.Stream; @@ -39,7 +41,7 @@ public JSONGenerator(File templateDir, File outputDir) { * @param strings the String array to convert * @return the JsonArray containing the Strings */ - private static @Nullable JsonArray convertToJsonArray(String @Nullable [] strings) { + private static @Nullable JsonArray convertToJsonArray(String @Nullable ... strings) { if (strings == null) return null; JsonArray jsonArray = new JsonArray(); @@ -73,9 +75,18 @@ public JSONGenerator(File templateDir, File outputDir) { syntaxJsonObject.add("description", new JsonArray()); } - Examples examplesAnnotation = syntaxClass.getAnnotation(Examples.class); - if (examplesAnnotation != null) { + if (syntaxClass.isAnnotationPresent(Examples.class)) { + @NotNull Examples examplesAnnotation = syntaxClass.getAnnotation(Examples.class); syntaxJsonObject.add("examples", convertToJsonArray(examplesAnnotation.value())); + } else if (syntaxClass.isAnnotationPresent(Example.Examples.class)) { + // If there are multiple examples, they get containerised + @NotNull Example.Examples examplesAnnotation = syntaxClass.getAnnotation(Example.Examples.class); + syntaxJsonObject.add("examples", convertToJsonArray(Arrays.stream(examplesAnnotation.value()) + .map(Example::value).toArray(String[]::new))); + } else if (syntaxClass.isAnnotationPresent(Example.class)) { + // If the user adds just one example, it isn't containerised + @NotNull Example example = syntaxClass.getAnnotation(Example.class); + syntaxJsonObject.add("examples", convertToJsonArray(example.value())); } else { syntaxJsonObject.add("examples", new JsonArray()); } From c0004837cec8162fac29d7db7d646b5de14a78de Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Thu, 27 Feb 2025 23:05:39 -0500 Subject: [PATCH 11/18] Fix Truncation in Timespan Tabulation (#7646) * switch timespan mult/div to use doubles until creating the timespan * revert to /0 equaling null --- .../classes/data/DefaultOperations.java | 21 +++++++++++-------- .../7645-floating point timespan math.sk | 12 +++++++++++ 2 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 src/test/skript/tests/regressions/7645-floating point timespan math.sk diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java b/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java index 907de84ef89..dd8df4e36c3 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java @@ -99,21 +99,24 @@ public class DefaultOperations { // Timespan - Number // Number - Timespan Arithmetics.registerOperation(Operator.MULTIPLICATION, Timespan.class, Number.class, (left, right) -> { - long scalar = right.longValue(); - if (scalar < 0) + double scalar = right.doubleValue(); + if (scalar < 0 || !Double.isFinite(scalar)) return null; - return new Timespan(Math2.multiplyClamped(left.getAs(TimePeriod.MILLISECOND), scalar)); + double value = left.getAs(TimePeriod.MILLISECOND) * scalar; + return new Timespan((long) Math.min(value, Long.MAX_VALUE)); }, (left, right) -> { - long scalar = left.longValue(); - if (scalar < 0) + double scalar = left.doubleValue(); + if (scalar < 0 || !Double.isFinite(scalar)) return null; - return new Timespan(scalar * right.getAs(TimePeriod.MILLISECOND)); + double value = right.getAs(TimePeriod.MILLISECOND) * scalar; + return new Timespan((long) Math.min(value, Long.MAX_VALUE)); }); Arithmetics.registerOperation(Operator.DIVISION, Timespan.class, Number.class, (left, right) -> { - long scalar = right.longValue(); - if (scalar <= 0) + double scalar = right.doubleValue(); + if (scalar <= 0 || !Double.isFinite(scalar)) return null; - return new Timespan(left.getAs(TimePeriod.MILLISECOND) / scalar); + double value = left.getAs(TimePeriod.MILLISECOND) / scalar; + return new Timespan((long) Math.min(value, Long.MAX_VALUE)); }); // Timespan / Timespan = Number diff --git a/src/test/skript/tests/regressions/7645-floating point timespan math.sk b/src/test/skript/tests/regressions/7645-floating point timespan math.sk new file mode 100644 index 00000000000..36d87912b69 --- /dev/null +++ b/src/test/skript/tests/regressions/7645-floating point timespan math.sk @@ -0,0 +1,12 @@ +options: + max-timespan: 9223372036854775807 milliseconds + +test "floating point timespan math": + assert (0.9 * 5 seconds) is 4.5 seconds with "failed basic floating point multiplication" + assert (1 * 5 seconds) is 5 seconds with "failed basic integer multiplication" + assert (0.0 * 5 seconds) is 0 seconds with "failed multiplication by 0" + assert (5 seconds / 5) is 1 seconds with "failed integer division" + assert (5 seconds / 2.5) is 2 seconds with "failed floating point division" + assert (5 seconds / infinity value) is not set with "Division by infinity unexpectedly returned real value" + + assert (5 seconds * 10 ^ 308) is {@max-timespan} with "failed to clamp to the long max value" From 74e7bdecac89dc3785e7ceca4be8bfbd71dadbed Mon Sep 17 00:00:00 2001 From: Jake Date: Fri, 28 Feb 2025 05:10:12 +0100 Subject: [PATCH 12/18] Fixed NoSuchMethodException when running tests on Spigot (Closes #7553) (#7558) * Added spigot semi-equivalent of the "get chunk at async" method. * Removed listening code as 'getChunkAt' is called synchronously. * Removed unused imports. * Switched to PaperLib method that handles chunk loading. * Removed accidental boolean negation. * Added requested changes Requested from @sovdeeth Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --------- Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> Co-authored-by: Efnilite <35348263+Efnilite@users.noreply.github.com> --- src/main/java/ch/njol/skript/Skript.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index adf004c1eaf..27a75d44e4d 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -44,9 +44,9 @@ import ch.njol.util.StringUtils; import ch.njol.util.coll.iterator.CheckedIterator; import ch.njol.util.coll.iterator.EnumerationIterable; -import com.google.common.collect.Lists; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import io.papermc.lib.PaperLib; import org.bstats.bukkit.Metrics; import org.bukkit.*; import org.bukkit.command.CommandSender; @@ -683,7 +683,7 @@ protected void afterErrors() { if (TestMode.DEV_MODE) { runTests(); // Dev mode doesn't need a delay } else { - Bukkit.getWorlds().get(0).getChunkAtAsync(100, 100).thenRun(() -> runTests()); + PaperLib.getChunkAtAsync(Bukkit.getWorlds().get(0), 100, 100).thenRun(() -> runTests()); } } From 2e504091948f02c71615c9a62fdf98bcdde24cc7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Feb 2025 11:40:36 -0500 Subject: [PATCH 13/18] Bump gradle/wrapper-validation-action from 2 to 3 (#7490) * Bump gradle/wrapper-validation-action from 2 to 3 Bumps [gradle/wrapper-validation-action](https://github.com/gradle/wrapper-validation-action) from 2 to 3. - [Release notes](https://github.com/gradle/wrapper-validation-action/releases) - [Commits](https://github.com/gradle/wrapper-validation-action/compare/v2...v3) --- updated-dependencies: - dependency-name: gradle/wrapper-validation-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Migrate to gradle/actions/wrapper-validation@v4 --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> --- .github/workflows/checkstyle.yml | 2 +- .github/workflows/java-17-builds.yml | 2 +- .github/workflows/java-21-builds.yml | 2 +- .github/workflows/junit-17-builds.yml | 2 +- .github/workflows/junit-21-builds.yml | 2 +- .github/workflows/repo.yml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/checkstyle.yml b/.github/workflows/checkstyle.yml index 409ca6b4fd7..5c54d147ae1 100644 --- a/.github/workflows/checkstyle.yml +++ b/.github/workflows/checkstyle.yml @@ -16,7 +16,7 @@ jobs: with: submodules: recursive - name: validate gradle wrapper - uses: gradle/wrapper-validation-action@v2 + uses: gradle/actions/wrapper-validation@v4 - name: Set up JDK 21 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/java-17-builds.yml b/.github/workflows/java-17-builds.yml index 43d6458c946..c4aa686d276 100644 --- a/.github/workflows/java-17-builds.yml +++ b/.github/workflows/java-17-builds.yml @@ -16,7 +16,7 @@ jobs: with: submodules: recursive - name: validate gradle wrapper - uses: gradle/wrapper-validation-action@v2 + uses: gradle/actions/wrapper-validation@v4 - name: Set up JDK 21 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/java-21-builds.yml b/.github/workflows/java-21-builds.yml index 6c82f22ce78..0fe2c49f1f1 100644 --- a/.github/workflows/java-21-builds.yml +++ b/.github/workflows/java-21-builds.yml @@ -16,7 +16,7 @@ jobs: with: submodules: recursive - name: validate gradle wrapper - uses: gradle/wrapper-validation-action@v2 + uses: gradle/actions/wrapper-validation@v4 - name: Set up JDK 21 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/junit-17-builds.yml b/.github/workflows/junit-17-builds.yml index 7bbe8952eb3..62ec22ddafc 100644 --- a/.github/workflows/junit-17-builds.yml +++ b/.github/workflows/junit-17-builds.yml @@ -16,7 +16,7 @@ jobs: with: submodules: recursive - name: validate gradle wrapper - uses: gradle/wrapper-validation-action@v2 + uses: gradle/actions/wrapper-validation@v4 - name: Set up JDK 21 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/junit-21-builds.yml b/.github/workflows/junit-21-builds.yml index 1eeee5846dc..7afc8b4d7a0 100644 --- a/.github/workflows/junit-21-builds.yml +++ b/.github/workflows/junit-21-builds.yml @@ -16,7 +16,7 @@ jobs: with: submodules: recursive - name: validate gradle wrapper - uses: gradle/wrapper-validation-action@v2 + uses: gradle/actions/wrapper-validation@v4 - name: Set up JDK 21 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/repo.yml b/.github/workflows/repo.yml index a04327ed64c..c482d4c9d97 100644 --- a/.github/workflows/repo.yml +++ b/.github/workflows/repo.yml @@ -12,7 +12,7 @@ jobs: with: submodules: recursive - name: validate gradle wrapper - uses: gradle/wrapper-validation-action@v2 + uses: gradle/actions/wrapper-validation@v4 - name: Set up JDK 21 uses: actions/setup-java@v4 with: From 8bde9539f41e0873e5019902d224a97ca2cd64ab Mon Sep 17 00:00:00 2001 From: Efnilite <35348263+Efnilite@users.noreply.github.com> Date: Fri, 28 Feb 2025 21:05:35 +0100 Subject: [PATCH 14/18] Improve generated json by JSONGenerator (#7633) * init commit * Standardize * add cancellable to events * fix reviews * add event-values * make EventValueInfo public * reviews * oops * forgot to add * fix error on generate * made convertToJsonArray return null if strings is empty, not null * use getClassInfoName * add semver * add keywords, deprecated * add cleaning patterns * escape html * fix links in patterns * fix generating test-only functions * add doc * fix function parameters getting removed * add times to event-values, remove duplicates * remove eventvalues with empty name * remove test elements from docs * switch to class NoDoc check * i forgor :skull: * fix method * Delete .github/workflows/release-docs2.yml * revert to using TestMode.ENABLED (oops) * remove unnecessary testmode checks * Update src/main/java/ch/njol/skript/test/runner/TestMode.java Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --------- Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- .../java/ch/njol/skript/SkriptCommand.java | 18 +- .../ch/njol/skript/doc/Documentation.java | 10 +- .../ch/njol/skript/doc/JSONGenerator.java | 250 ++++++++++++++---- .../ch/njol/skript/lang/SkriptEventInfo.java | 5 +- .../skript/registrations/EventValues.java | 38 ++- .../njol/skript/test/runner/EvtTestCase.java | 2 +- .../skript/test/runner/TestFunctions.java | 4 +- 7 files changed, 259 insertions(+), 68 deletions(-) diff --git a/src/main/java/ch/njol/skript/SkriptCommand.java b/src/main/java/ch/njol/skript/SkriptCommand.java index e59cff1111c..8ea5c0488c8 100644 --- a/src/main/java/ch/njol/skript/SkriptCommand.java +++ b/src/main/java/ch/njol/skript/SkriptCommand.java @@ -382,19 +382,21 @@ public boolean onCommand(CommandSender sender, Command command, String label, St } else if (args[0].equalsIgnoreCase("gen-docs")) { File templateDir = Documentation.getDocsTemplateDirectory(); + File outputDir = Documentation.getDocsOutputDirectory(); + outputDir.mkdirs(); + + Skript.info(sender, "Generating docs..."); + JSONGenerator jsonGenerator = new JSONGenerator(templateDir, outputDir); + jsonGenerator.generate(); + if (!templateDir.exists()) { - Skript.error(sender, "Cannot generate docs! Documentation templates not found at '" + Documentation.getDocsTemplateDirectory().getPath() + "'"); - TestMode.docsFailed = true; + Skript.info(sender, "JSON-only documentation generated!"); return true; } - File outputDir = Documentation.getDocsOutputDirectory(); - outputDir.mkdirs(); + HTMLGenerator htmlGenerator = new HTMLGenerator(templateDir, outputDir); - JSONGenerator jsonGenerator = new JSONGenerator(templateDir, outputDir); - Skript.info(sender, "Generating docs..."); htmlGenerator.generate(); // Try to generate docs... hopefully - jsonGenerator.generate(); - Skript.info(sender, "Documentation generated!"); + Skript.info(sender, "All documentation generated!"); } else if (args[0].equalsIgnoreCase("test") && TestMode.DEV_MODE) { File scriptFile; if (args.length == 1) { diff --git a/src/main/java/ch/njol/skript/doc/Documentation.java b/src/main/java/ch/njol/skript/doc/Documentation.java index 66386594cfc..026981e85a5 100644 --- a/src/main/java/ch/njol/skript/doc/Documentation.java +++ b/src/main/java/ch/njol/skript/doc/Documentation.java @@ -179,7 +179,10 @@ protected static String cleanPatterns(final String patterns) { } protected static String cleanPatterns(String patterns, boolean escapeHTML) { + return cleanPatterns(patterns, escapeHTML, true); + } + protected static String cleanPatterns(String patterns, boolean escapeHTML, boolean useLinks) { String cleanedPatterns = escapeHTML ? escapeHTML(patterns) : patterns; cleanedPatterns = CP_PARSE_MARKS_PATTERN.matcher(cleanedPatterns).replaceAll(""); // Remove marks @@ -268,8 +271,13 @@ protected static String cleanPatterns(String patterns, boolean escapeHTML) { first = false; final NonNullPair p = Utils.getEnglishPlural(c); final ClassInfo ci = Classes.getClassInfoNoError(p.getFirst()); + if (ci != null && ci.hasDocs()) { // equals method throws null error when doc name is null - b.append("").append(ci.getName().toString(p.getSecond())).append(""); + if (useLinks) { + b.append("").append(ci.getName().toString(p.getSecond())).append(""); + } else { + b.append(ci.getName().toString(p.getSecond())); + } } else { b.append(c); if (ci != null && ci.hasDocs()) diff --git a/src/main/java/ch/njol/skript/doc/JSONGenerator.java b/src/main/java/ch/njol/skript/doc/JSONGenerator.java index 0ef57d40822..7d7431e9cde 100644 --- a/src/main/java/ch/njol/skript/doc/JSONGenerator.java +++ b/src/main/java/ch/njol/skript/doc/JSONGenerator.java @@ -8,11 +8,14 @@ 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 ch.njol.skript.registrations.EventValues; +import ch.njol.skript.registrations.EventValues.EventValueInfo; +import ch.njol.skript.util.Version; +import com.google.common.collect.Multimap; +import com.google.gson.*; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.block.BlockCanBuildEvent; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.structure.Structure; @@ -22,9 +25,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Arrays; -import java.util.Iterator; -import java.util.Objects; +import java.util.*; import java.util.stream.Stream; /** @@ -32,17 +33,39 @@ */ public class JSONGenerator extends DocumentationGenerator { + /** + * The current version of the JSON generator + */ + public static final Version JSON_VERSION = new Version(1, 0); + + private static final Gson GSON = new GsonBuilder() + .disableHtmlEscaping() + .setPrettyPrinting() + .serializeNulls() + .create(); + public JSONGenerator(File templateDir, File outputDir) { super(templateDir, outputDir); } + /** + * @return The version of the JSON generator + */ + private static JsonObject getVersion() { + JsonObject version = new JsonObject(); + version.addProperty("major", JSON_VERSION.getMajor()); + version.addProperty("minor", JSON_VERSION.getMinor()); + return version; + } + /** * 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) + private static JsonArray convertToJsonArray(String @Nullable ... strings) { + if (strings == null || strings.length == 0) return null; JsonArray jsonArray = new JsonArray(); for (String string : strings) @@ -53,27 +76,30 @@ public JSONGenerator(File templateDir, File outputDir) { /** * 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) { + private static JsonObject generatedAnnotatedElement(SyntaxElementInfo syntaxInfo) { Class syntaxClass = syntaxInfo.getElementClass(); - Name nameAnnotation = syntaxClass.getAnnotation(Name.class); - if (nameAnnotation == null || syntaxClass.getAnnotation(NoDoc.class) != null) + Name name = syntaxClass.getAnnotation(Name.class); + if (name == null || syntaxClass.getAnnotation(NoDoc.class) != null) return null; + JsonObject syntaxJsonObject = new JsonObject(); + syntaxJsonObject.addProperty("id", DocumentationIdProvider.getId(syntaxInfo)); - syntaxJsonObject.addProperty("name", nameAnnotation.value()); + syntaxJsonObject.addProperty("name", name.value()); + Since since = syntaxClass.getAnnotation(Since.class); + syntaxJsonObject.add("since", since == null ? null : convertToJsonArray(since.value())); - Since sinceAnnotation = syntaxClass.getAnnotation(Since.class); - syntaxJsonObject.add("since", sinceAnnotation == null ? null : convertToJsonArray(sinceAnnotation.value())); + Deprecated deprecated = syntaxClass.getAnnotation(Deprecated.class); + syntaxJsonObject.addProperty("deprecated", deprecated != null); - Description descriptionAnnotation = syntaxClass.getAnnotation(Description.class); - if (descriptionAnnotation != null) { - syntaxJsonObject.add("description", convertToJsonArray(descriptionAnnotation.value())); - } else { - syntaxJsonObject.add("description", new JsonArray()); - } + Description description = syntaxClass.getAnnotation(Description.class); + syntaxJsonObject.add("description", description == null ? null : convertToJsonArray(description.value())); + + syntaxJsonObject.add("patterns", cleanPatterns(syntaxInfo.getPatterns())); if (syntaxClass.isAnnotationPresent(Examples.class)) { @NotNull Examples examplesAnnotation = syntaxClass.getAnnotation(Examples.class); @@ -88,37 +114,124 @@ public JSONGenerator(File templateDir, File outputDir) { @NotNull Example example = syntaxClass.getAnnotation(Example.class); syntaxJsonObject.add("examples", convertToJsonArray(example.value())); } else { - syntaxJsonObject.add("examples", new JsonArray()); + syntaxJsonObject.add("examples", null); } + Events events = syntaxClass.getAnnotation(Events.class); + syntaxJsonObject.add("events", events == null ? null : convertToJsonArray(events.value())); + + RequiredPlugins requirements = syntaxClass.getAnnotation(RequiredPlugins.class); + syntaxJsonObject.add("requirements", requirements == null ? null : convertToJsonArray(requirements.value())); + + Keywords keywords = syntaxClass.getAnnotation(Keywords.class); + syntaxJsonObject.add("keywords", keywords == null ? null : convertToJsonArray(keywords.value())); - 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 + * + * @param info the event to generate the documentation object for * @return a documentation JsonObject for the event */ - private JsonObject generateEventElement(SkriptEventInfo eventInfo) { + private static JsonObject generateEventElement(SkriptEventInfo info) { 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)); + syntaxJsonObject.addProperty("id", DocumentationIdProvider.getId(info)); + syntaxJsonObject.addProperty("name", info.getName()); + syntaxJsonObject.addProperty("since", info.getSince()); + syntaxJsonObject.addProperty("cancellable", isCancellable(info)); + + syntaxJsonObject.add("patterns", cleanPatterns(info.getPatterns())); + syntaxJsonObject.add("description", convertToJsonArray(info.getDescription())); + syntaxJsonObject.add("requirements", convertToJsonArray(info.getRequiredPlugins())); + syntaxJsonObject.add("examples", convertToJsonArray(info.getExamples())); + syntaxJsonObject.add("eventValues", getEventValues(info)); + syntaxJsonObject.add("keywords", convertToJsonArray(info.getKeywords())); + return syntaxJsonObject; } + /** + * Generates the documentation for the event values of an event + * + * @param info the event to generate the event values of + * @return a JsonArray containing the documentation JsonObjects for each event value + */ + private static JsonArray getEventValues(SkriptEventInfo info) { + Set eventValues = new HashSet<>(); + + Multimap, EventValueInfo> allEventValues = EventValues.getPerEventEventValues(); + for (Class supportedEvent : info.events) { + for (Class event : allEventValues.keySet()) { + if (!event.isAssignableFrom(supportedEvent)) { + continue; + } + + Collection> eventValueInfos = allEventValues.get(event); + + for (EventValueInfo eventValueInfo : eventValueInfos) { + Class[] excludes = eventValueInfo.excludes(); + if (excludes != null && Set.of(excludes).contains(event)) { + continue; + } + + ClassInfo exactClassInfo = Classes.getExactClassInfo(eventValueInfo.c()); + if (exactClassInfo == null) { + continue; + } + + String name = getClassInfoName(exactClassInfo).toLowerCase(Locale.ENGLISH); + if (name.isBlank()) { + continue; + } + + if (eventValueInfo.time() == EventValues.TIME_PAST) { + name = "past " + name; + } else if (eventValueInfo.time() == EventValues.TIME_FUTURE) { + name = "future " + name; + } + + JsonObject object = new JsonObject(); + object.addProperty("id", DocumentationIdProvider.getId(exactClassInfo)); + object.addProperty("name", name); + eventValues.add(object); + } + } + } + + JsonArray array = new JsonArray(); + for (JsonObject eventValue : eventValues) { + array.add(eventValue); + } + return array; + } + + /** + * Determines whether an event is cancellable. + * + * @param info the event to check + * @return true if the event is cancellable, false otherwise + */ + private static boolean isCancellable(SkriptEventInfo info) { + boolean cancellable = false; + for (Class event : info.events) { + if (Cancellable.class.isAssignableFrom(event) || BlockCanBuildEvent.class.isAssignableFrom(event)) { + cancellable = true; + break; + } + } + return cancellable; + } + /** * 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) { + private static > JsonArray generateStructureElementArray(Iterator infos) { JsonArray syntaxArray = new JsonArray(); infos.forEachRemaining(info -> { if (info instanceof SkriptEventInfo eventInfo) { @@ -134,10 +247,11 @@ private > JsonArray generateStructu /** * 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) { + private static > JsonArray generateSyntaxElementArray(Iterator infos) { JsonArray syntaxArray = new JsonArray(); infos.forEachRemaining(info -> { JsonObject syntaxJsonObject = generatedAnnotatedElement(info); @@ -149,28 +263,34 @@ private > JsonArray generat /** * 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) { + private static 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("patterns", cleanPatterns(classInfo.getUsage())); syntaxJsonObject.add("description", convertToJsonArray(classInfo.getDescription())); + syntaxJsonObject.add("requirements", convertToJsonArray(classInfo.getRequiredPlugins())); 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) { + private static JsonArray generateClassInfoArray(Iterator> classInfos) { JsonArray syntaxArray = new JsonArray(); classInfos.forEachRemaining(classInfo -> { JsonObject classInfoElement = generateClassInfoElement(classInfo); @@ -182,56 +302,85 @@ private JsonArray generateClassInfoArray(Iterator> classInfos) { /** * 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) { + private static 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) { + private static 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("returnType", getReturnType(function)); + 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 })); + functionJsonObject.add("patterns", convertToJsonArray(functionSignature)); return functionJsonObject; } + /** + * Gets the return type of JavaFunction, with the name and id + * + * @param function the JavaFunction to get the return type of + * @return the JsonObject representing the return type of the JavaFunction + */ + private static JsonObject getReturnType(JavaFunction function) { + JsonObject returnType = new JsonObject(); + returnType.addProperty("name", getClassInfoName(function.getReturnType())); + returnType.addProperty("id", DocumentationIdProvider.getId(function.getReturnType())); + return returnType; + } + /** * 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) { + private static JsonArray generateFunctionArray(Iterator> functions) { JsonArray syntaxArray = new JsonArray(); functions.forEachRemaining(function -> syntaxArray.add(generateFunctionElement(function))); return syntaxArray; } + /** + * Cleans the provided patterns + * + * @param strings the patterns to clean + * @return the cleaned patterns + */ + private static JsonArray cleanPatterns(String... strings) { + if (strings == null || strings.length == 0 || (strings.length == 1 && strings[0].isBlank())) + return null; + + for (int i = 0; i < strings.length; i++) { + strings[i] = Documentation.cleanPatterns(strings[i], false, false); + } + return convertToJsonArray(strings); + } + /** * Writes the documentation JsonObject to an output path + * * @param outputPath the path to write the documentation to - * @param jsonDocs the documentation JsonObject + * @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)); + Files.writeString(outputPath, GSON.toJson(jsonDocs)); } catch (IOException exception) { //noinspection ThrowableNotThrown Skript.exception(exception, "An error occurred while trying to generate JSON documentation"); @@ -242,7 +391,8 @@ private void saveDocs(Path outputPath, JsonObject jsonDocs) { public void generate() { JsonObject jsonDocs = new JsonObject(); - jsonDocs.add("skriptVersion", new JsonPrimitive(Skript.getVersion().toString())); + jsonDocs.addProperty("skriptVersion", Skript.getVersion().toString()); + jsonDocs.add("version", getVersion()); jsonDocs.add("conditions", generateSyntaxElementArray(Skript.getConditions().iterator())); jsonDocs.add("effects", generateSyntaxElementArray(Skript.getEffects().iterator())); jsonDocs.add("expressions", generateSyntaxElementArray(Skript.getExpressions())); diff --git a/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java b/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java index 06713eed888..f277de8d9f8 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java +++ b/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java @@ -27,15 +27,14 @@ public sealed class SkriptEventInfo extends StructureInfo public final String name; private ListeningBehavior listeningBehavior; + private @Nullable String since = null; + private @Nullable String documentationID = null; private String @Nullable [] description = null; private String @Nullable [] examples = null; private String @Nullable [] keywords = null; private String @Nullable [] requiredPlugins = null; - private @Nullable String since = null; - private @Nullable String documentationID = null; - private final String id; /** diff --git a/src/main/java/ch/njol/skript/registrations/EventValues.java b/src/main/java/ch/njol/skript/registrations/EventValues.java index fe8043d7e06..190df76b2be 100644 --- a/src/main/java/ch/njol/skript/registrations/EventValues.java +++ b/src/main/java/ch/njol/skript/registrations/EventValues.java @@ -5,12 +5,15 @@ import ch.njol.skript.util.Getter; import ch.njol.util.Kleenean; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Multimap; +import com.google.common.collect.MultimapBuilder; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.converter.Converter; import org.skriptlang.skript.lang.converter.Converters; import java.util.ArrayList; +import java.util.Collection; import java.util.List; public class EventValues { @@ -108,7 +111,7 @@ public static void registerEventValue( ) { Skript.checkAcceptRegistrations(); List> eventValues = getEventValuesList(time); - EventValueInfo element = new EventValueInfo<>(event, type, converter, excludeErrorMessage, excludes); + EventValueInfo element = new EventValueInfo<>(event, type, converter, excludeErrorMessage, excludes, time); for (int i = 0; i < eventValues.size(); i++) { EventValueInfo info = eventValues.get(i); @@ -454,12 +457,39 @@ public static boolean doesEventValueHaveTimeStates(Class event, return getEventValueConverter(event, c, TIME_PAST, false) != null || getEventValueConverter(event, c, TIME_FUTURE, false) != null; } - private record EventValueInfo( + /** + * All supported time states for an event value. + * @return An array of all the time states. + */ + public static int[] getTimeStates() { + return new int[] {TIME_PAST, TIME_NOW, TIME_FUTURE}; + } + + /** + * @return All the event values for each registered event's class. + */ + public static Multimap, EventValueInfo> getPerEventEventValues() { + Multimap, EventValueInfo> eventValues = MultimapBuilder + .hashKeys() + .hashSetValues() + .build(); + + for (int time : getTimeStates()) { + for (EventValueInfo eventValueInfo : getEventValuesListForTime(time)) { + Collection> existing = eventValues.get(eventValueInfo.event); + existing.add(eventValueInfo); + eventValues.putAll(eventValueInfo.event, existing); + } + } + return eventValues; + } + + public record EventValueInfo( Class event, Class c, Converter converter, @Nullable String excludeErrorMessage, - @Nullable Class[] excludes + @Nullable Class[] excludes, int time ) { - private EventValueInfo { + public EventValueInfo { assert event != null; assert c != null; assert converter != null; diff --git a/src/main/java/ch/njol/skript/test/runner/EvtTestCase.java b/src/main/java/ch/njol/skript/test/runner/EvtTestCase.java index 2b055fb7257..9566288b2e6 100644 --- a/src/main/java/ch/njol/skript/test/runner/EvtTestCase.java +++ b/src/main/java/ch/njol/skript/test/runner/EvtTestCase.java @@ -14,7 +14,7 @@ public class EvtTestCase extends SkriptEvent { static { - if (TestMode.ENABLED) { + if (TestMode.ENABLED && !TestMode.GEN_DOCS) { Skript.registerEvent("Test Case", EvtTestCase.class, SkriptTestEvent.class, "test %string% [when <.+>]") .description("Contents represent one test case.") .examples("") diff --git a/src/main/java/ch/njol/skript/test/runner/TestFunctions.java b/src/main/java/ch/njol/skript/test/runner/TestFunctions.java index faed1e0d409..e67e1d33156 100644 --- a/src/main/java/ch/njol/skript/test/runner/TestFunctions.java +++ b/src/main/java/ch/njol/skript/test/runner/TestFunctions.java @@ -14,7 +14,9 @@ public class TestFunctions { static { - if (TestMode.ENABLED) // Prevent accidental registration if something visits this class + // Prevent accidental registration if something visits this class + // To prevent these functions from showing up in the docs, don't register when generating docs + if (TestMode.ENABLED && !TestMode.GEN_DOCS) registerTestFunctions(); } From 12c9f07045d91799c33a6102a045bd5a678db5f1 Mon Sep 17 00:00:00 2001 From: SirSmurfy2 <82696841+TheAbsolutionism@users.noreply.github.com> Date: Sat, 1 Mar 2025 12:23:25 -0500 Subject: [PATCH 15/18] Sub Entity Data Fix (#7641) * Initial Commit * Re-add private finals Forgot to add them back when removing all finals * fireball wand wither skull * Add back additional finals Lesson learned, never ctrl+f and replacing all again * Update skript-aliases * Update * Fix Java21 * Update src/main/java/ch/njol/skript/entity/EntityData.java Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --------- --- skript-aliases | 2 +- .../ch/njol/skript/entity/EntityData.java | 318 +++++++++--------- .../njol/skript/entity/SimpleEntityData.java | 178 +++++----- .../skript/tests/misc/sub entity datas.sk | 39 +++ .../tests/syntaxes/sections/EffSecSpawn.sk | 6 +- 5 files changed, 294 insertions(+), 249 deletions(-) create mode 100644 src/test/skript/tests/misc/sub entity datas.sk diff --git a/skript-aliases b/skript-aliases index 97212b5b809..2ebfe194ba1 160000 --- a/skript-aliases +++ b/skript-aliases @@ -1 +1 @@ -Subproject commit 97212b5b809147e16eef5c5a80dd55324662d572 +Subproject commit 2ebfe194ba1ac240ff3541ee38458f4be55faa9e diff --git a/src/main/java/ch/njol/skript/entity/EntityData.java b/src/main/java/ch/njol/skript/entity/EntityData.java index f7ea4d45dd3..a7b8fe35b7e 100644 --- a/src/main/java/ch/njol/skript/entity/EntityData.java +++ b/src/main/java/ch/njol/skript/entity/EntityData.java @@ -29,10 +29,7 @@ import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; +import java.util.*; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -44,22 +41,13 @@ public abstract class EntityData implements SyntaxElement, Ygg * From the class header: "API methods which use this consumer will be remapped to Java's consumer at runtime, resulting in an error." * But in 1.13-1.16 the only way to use a consumer was World#spawn(Location, Class, org.bukkit.util.Consumer). */ - @Nullable - protected static Method WORLD_1_13_CONSUMER_METHOD; - protected static final boolean WORLD_1_13_CONSUMER = Skript.methodExists(World.class, "spawn", Location.class, Class.class, org.bukkit.util.Consumer.class); - - @Nullable - protected static Method WORLD_1_17_CONSUMER_METHOD; + protected static @Nullable Method WORLD_1_17_CONSUMER_METHOD; protected static boolean WORLD_1_17_CONSUMER; static { try { - if (WORLD_1_13_CONSUMER) { - WORLD_1_13_CONSUMER_METHOD = World.class.getDeclaredMethod("spawn", Location.class, Class.class, org.bukkit.util.Consumer.class); - } else if (Skript.classExists("org.bukkit.RegionAccessor")) { - if (WORLD_1_17_CONSUMER = Skript.methodExists(RegionAccessor.class, "spawn", Location.class, Class.class, org.bukkit.util.Consumer.class)) - WORLD_1_17_CONSUMER_METHOD = RegionAccessor.class.getDeclaredMethod("spawn", Location.class, Class.class, org.bukkit.util.Consumer.class); - } + if (WORLD_1_17_CONSUMER = Skript.methodExists(RegionAccessor.class, "spawn", Location.class, Class.class, org.bukkit.util.Consumer.class)) + WORLD_1_17_CONSUMER_METHOD = RegionAccessor.class.getDeclaredMethod("spawn", Location.class, Class.class, org.bukkit.util.Consumer.class); } catch (NoSuchMethodException | SecurityException ignored) { /* We already checked if the method exists */ } } @@ -77,10 +65,10 @@ public abstract class EntityData implements SyntaxElement, Ygg public static Serializer serializer = new Serializer() { @Override - public Fields serialize(final EntityData o) throws NotSerializableException { - final Fields f = o.serialize(); - f.putObject("codeName", o.info.codeName); - return f; + public Fields serialize(EntityData entityData) throws NotSerializableException { + Fields fields = entityData.serialize(); + fields.putObject("codeName", entityData.info.codeName); + return fields; } @Override @@ -89,25 +77,23 @@ public boolean canBeInstantiated() { } @Override - public void deserialize(final EntityData o, final Fields f) throws StreamCorruptedException { + public void deserialize(EntityData entityData, Fields fields) throws StreamCorruptedException { assert false; } @Override - protected EntityData deserialize(final Fields fields) throws StreamCorruptedException, NotSerializableException { - final String codeName = fields.getAndRemoveObject("codeName", String.class); + protected EntityData deserialize(Fields fields) throws StreamCorruptedException, NotSerializableException { + String codeName = fields.getAndRemoveObject("codeName", String.class); if (codeName == null) throw new StreamCorruptedException(); - final EntityDataInfo info = getInfo(codeName); + EntityDataInfo info = getInfo(codeName); if (info == null) throw new StreamCorruptedException("Invalid EntityData code name " + codeName); try { - final EntityData d = info.getElementClass().newInstance(); - d.deserialize(fields); - return d; - } catch (final InstantiationException e) { - Skript.exception(e); - } catch (final IllegalAccessException e) { + EntityData entityData = info.getElementClass().newInstance(); + entityData.deserialize(fields); + return entityData; + } catch (InstantiationException | IllegalAccessException e) { Skript.exception(e); } throw new StreamCorruptedException(); @@ -117,24 +103,23 @@ protected EntityData deserialize(final Fields fields) throws StreamCorruptedExce @SuppressWarnings("null") @Override @Deprecated - @Nullable - public EntityData deserialize(final String s) { - final String[] split = s.split(":", 2); + public @Nullable EntityData deserialize(String string) { + String[] split = string.split(":", 2); if (split.length != 2) return null; - final EntityDataInfo i = getInfo(split[0]); - if (i == null) + EntityDataInfo entityDataInfo = getInfo(split[0]); + if (entityDataInfo == null) return null; - EntityData d; + EntityData entityData; try { - d = i.getElementClass().newInstance(); - } catch (final Exception e) { - Skript.exception(e, "Can't create an instance of " + i.getElementClass().getCanonicalName()); + entityData = entityDataInfo.getElementClass().newInstance(); + } catch (Exception e) { + Skript.exception(e, "Can't create an instance of " + entityDataInfo.getElementClass().getCanonicalName()); return null; } - if (!d.deserialize(split[1])) + if (!entityData.deserialize(split[1])) return null; - return d; + return entityData; } @Override @@ -157,21 +142,20 @@ public boolean mustSyncDeserialization() { .supplier(ALL_ENTITY_DATAS::iterator) .parser(new Parser() { @Override - public String toString(final EntityData d, final int flags) { - return d.toString(flags); + public String toString(EntityData entityData, int flags) { + return entityData.toString(flags); } @Override - @Nullable - public EntityData parse(final String s, final ParseContext context) { - return EntityData.parse(s); + public @Nullable EntityData parse(String string, ParseContext context) { + return EntityData.parse(string); } @Override - public String toVariableNameString(final EntityData o) { - return "entitydata:" + o.toString(); + public String toVariableNameString(EntityData entityData) { + return "entitydata:" + entityData.toString(); } - }).serializer(serializer)); + }).serializer(serializer)); } public static void onRegistrationStop() { @@ -196,7 +180,7 @@ private final static class EntityDataInfo> extends Synta final Class entityClass; final Noun[] names; - public EntityDataInfo(final Class dataClass, final String codeName, final String[] codeNames, final int defaultName, final Class entityClass) throws IllegalArgumentException { + public EntityDataInfo(Class dataClass, String codeName, String[] codeNames, int defaultName, Class entityClass) throws IllegalArgumentException { super(new String[codeNames.length], dataClass, dataClass.getName()); assert codeName != null && entityClass != null && codeNames.length > 0; this.codeName = codeName; @@ -220,21 +204,17 @@ public void onLanguageChange() { @Override public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + codeName.hashCode(); - return result; + return Objects.hashCode(codeName); } @Override - public boolean equals(final @Nullable Object obj) { + public boolean equals(@Nullable Object obj) { if (this == obj) return true; if (obj == null) return false; - if (!(obj instanceof EntityDataInfo)) + if (!(obj instanceof EntityDataInfo other)) return false; - final EntityDataInfo other = (EntityDataInfo) obj; if (!codeName.equals(other.codeName)) return false; assert Arrays.equals(codeNames, other.codeNames); @@ -245,13 +225,24 @@ public boolean equals(final @Nullable Object obj) { } - public static > void register(final Class dataClass, final String name, final Class entityClass, final String codeName) throws IllegalArgumentException { + public static > void register( + Class dataClass, + String name, + Class entityClass, + String codeName + ) throws IllegalArgumentException { register(dataClass, name, entityClass, 0, codeName); } @SuppressWarnings("unchecked") - public static > void register(final Class dataClass, final String name, final Class entityClass, final int defaultName, final String... codeNames) throws IllegalArgumentException { - final EntityDataInfo info = new EntityDataInfo<>(dataClass, name, codeNames, defaultName, entityClass); + public static > void register( + Class dataClass, + String name, + Class entityClass, + int defaultName, + String... codeNames + ) throws IllegalArgumentException { + EntityDataInfo info = new EntityDataInfo<>(dataClass, name, codeNames, defaultName, entityClass); for (int i = 0; i < infos.size(); i++) { if (infos.get(i).entityClass.isAssignableFrom(entityClass)) { infos.add(i, (EntityDataInfo>) info); @@ -267,10 +258,10 @@ public static > void register(final Cl private Kleenean baby = Kleenean.UNKNOWN; public EntityData() { - for (final EntityDataInfo i : infos) { - if (getClass() == i.getElementClass()) { - info = i; - matchedPattern = i.defaultName; + for (EntityDataInfo info : infos) { + if (getClass() == info.getElementClass()) { + this.info = info; + matchedPattern = info.defaultName; return; } } @@ -279,25 +270,25 @@ public EntityData() { @SuppressWarnings("null") @Override - public final boolean init(final Expression[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { + public final boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { this.matchedPattern = matchedPattern; // plural bits (0x3): 0 = singular, 1 = plural, 2 = unknown - final int pluralBits = parseResult.mark & 0x3; + int pluralBits = parseResult.mark & 0x3; this.plural = pluralBits == 1 ? Kleenean.TRUE : pluralBits == 0 ? Kleenean.FALSE : Kleenean.UNKNOWN; // age bits (0xC): 0 = unknown, 4 = baby, 8 = adult - final int ageBits = parseResult.mark & 0xC; + int ageBits = parseResult.mark & 0xC; this.baby = ageBits == 4 ? Kleenean.TRUE : ageBits == 8 ? Kleenean.FALSE : Kleenean.UNKNOWN; return init(Arrays.copyOf(exprs, exprs.length, Literal[].class), matchedPattern, parseResult); } - protected abstract boolean init(final Literal[] exprs, final int matchedPattern, final ParseResult parseResult); + protected abstract boolean init(Literal[] exprs, int matchedPattern, ParseResult parseResult); /** - * @param c An entity's class, e.g. Player - * @param e An actual entity, or null to get an entity data for an entity class + * @param entityClass An entity's class, e.g. Player + * @param entity An actual entity, or null to get an entity data for an entity class * @return Whether initialisation was successful */ - protected abstract boolean init(@Nullable Class c, @Nullable E e); + protected abstract boolean init(@Nullable Class entityClass, @Nullable E entity); public abstract void set(E entity); @@ -322,14 +313,13 @@ protected Noun getName() { return info.names[matchedPattern]; } - @Nullable - protected Adjective getAgeAdjective() { + protected @Nullable Adjective getAgeAdjective() { return baby.isTrue() ? m_baby : baby.isFalse() ? m_adult : null; } @SuppressWarnings("null") - public String toString(final int flags) { - final Noun name = info.names[matchedPattern]; + public String toString(int flags) { + Noun name = info.names[matchedPattern]; return baby.isTrue() ? m_baby.toString(name, flags) : baby.isFalse() ? m_adult.toString(name, flags) : name.toString(flags); } @@ -345,7 +335,7 @@ public Kleenean isBaby() { @Override public final int hashCode() { - final int prime = 31; + int prime = 31; int result = 1; result = prime * result + baby.hashCode(); result = prime * result + plural.hashCode(); @@ -358,14 +348,13 @@ public final int hashCode() { protected abstract boolean equals_i(EntityData obj); @Override - public final boolean equals(final @Nullable Object obj) { + public final boolean equals(@Nullable Object obj) { if (this == obj) return true; if (obj == null) return false; - if (!(obj instanceof EntityData)) + if (!(obj instanceof EntityData other)) return false; - final EntityData other = (EntityData) obj; if (baby != other.baby) return false; if (plural != other.plural) @@ -377,19 +366,18 @@ public final boolean equals(final @Nullable Object obj) { return equals_i(other); } - public static EntityDataInfo getInfo(final Class> c) { - for (final EntityDataInfo i : infos) { - if (i.getElementClass() == c) - return i; + public static EntityDataInfo getInfo(Class> entityDataClass) { + for (EntityDataInfo info : infos) { + if (info.getElementClass() == entityDataClass) + return info; } - throw new SkriptAPIException("Unregistered EntityData class " + c.getName()); + throw new SkriptAPIException("Unregistered EntityData class " + entityDataClass.getName()); } - @Nullable - public static EntityDataInfo getInfo(final String codeName) { - for (final EntityDataInfo i : infos) { - if (i.codeName.equals(codeName)) - return i; + public static @Nullable EntityDataInfo getInfo(String codeName) { + for (EntityDataInfo info : infos) { + if (info.codeName.equals(codeName)) + return info; } return null; } @@ -397,26 +385,24 @@ public static EntityDataInfo getInfo(final String codeName) { /** * Prints errors. * - * @param s String with optional indefinite article at the beginning + * @param string String with optional indefinite article at the beginning * @return The parsed entity data */ @SuppressWarnings("null") - @Nullable - public static EntityData parse(String s) { + public static @Nullable EntityData parse(String string) { Iterator>> it = new ArrayList<>(infos).iterator(); - return SkriptParser.parseStatic(Noun.stripIndefiniteArticle(s), it, null); + return SkriptParser.parseStatic(Noun.stripIndefiniteArticle(string), it, null); } /** * Prints errors. * - * @param s + * @param string * @return The parsed entity data */ - @Nullable - public static EntityData parseWithoutIndefiniteArticle(String s) { + public static @Nullable EntityData parseWithoutIndefiniteArticle(String string) { Iterator>> it = new ArrayList<>(infos).iterator(); - return SkriptParser.parseStatic(s, it, null); + return SkriptParser.parseStatic(string, it, null); } private E apply(E entity) { @@ -446,7 +432,7 @@ public boolean canSpawn(@Nullable World world) { if (HAS_ENABLED_BY_FEATURE) { // Check if the entity can actually be spawned // Some entity types may be restricted by experimental datapacks - return bukkitEntityType.isEnabledByFeature(world) && bukkitEntityType.isSpawnable(); + return bukkitEntityType.isEnabledByFeature(world) && bukkitEntityType.isSpawnable(); } return bukkitEntityType.isSpawnable(); } @@ -457,8 +443,7 @@ public boolean canSpawn(@Nullable World world) { * @param location The {@link Location} to spawn the entity at. * @return The Entity object that is spawned. */ - @Nullable - public final E spawn(Location location) { + public final @Nullable E spawn(Location location) { return spawn(location, (Consumer) null); } @@ -473,10 +458,9 @@ public final E spawn(Location location) { * @param consumer A {@link Consumer} to apply the entity changes to. * @return The Entity object that is spawned. */ - @Nullable @Deprecated @SuppressWarnings("deprecation") - public E spawn(Location location, org.bukkit.util.@Nullable Consumer consumer) { + public @Nullable E spawn(Location location, org.bukkit.util.@Nullable Consumer consumer) { return spawn(location, (Consumer) e -> consumer.accept(e)); } @@ -488,8 +472,7 @@ public E spawn(Location location, org.bukkit.util.@Nullable Consumer consumer * @param consumer A {@link Consumer} to apply the entity changes to. * @return The Entity object that is spawned. */ - @Nullable - public E spawn(Location location, @Nullable Consumer consumer) { + public @Nullable E spawn(Location location, @Nullable Consumer consumer) { assert location != null; World world = location.getWorld(); if (!canSpawn(world)) @@ -502,13 +485,14 @@ public E spawn(Location location, @Nullable Consumer consumer) { } @SuppressWarnings("unchecked") - public E[] getAll(final World... worlds) { + public E[] getAll(World... worlds) { assert worlds != null && worlds.length > 0 : Arrays.toString(worlds); - final List list = new ArrayList<>(); - for (final World w : worlds) { - for (final E e : w.getEntitiesByClass(getType())) - if (match(e)) - list.add(e); + List list = new ArrayList<>(); + for (World world : worlds) { + for (E entity : world.getEntitiesByClass(getType())) { + if (match(entity)) + list.add(entity); + } } return list.toArray((E[]) Array.newInstance(getType(), list.size())); } @@ -520,26 +504,26 @@ public E[] getAll(final World... worlds) { * @return All entities of this type in the given worlds */ @SuppressWarnings({"null", "unchecked"}) - public static E[] getAll(final EntityData[] types, final Class type, @Nullable World[] worlds) { + public static E[] getAll(EntityData[] types, Class type, World @Nullable [] worlds) { assert types.length > 0; if (type == Player.class) { if (worlds == null) return (E[]) Bukkit.getOnlinePlayers().toArray(new Player[0]); List list = new ArrayList<>(); - for (Player p : Bukkit.getOnlinePlayers()) { - if (CollectionUtils.contains(worlds, p.getWorld())) - list.add(p); + for (Player player : Bukkit.getOnlinePlayers()) { + if (CollectionUtils.contains(worlds, player.getWorld())) + list.add(player); } return (E[]) list.toArray(new Player[list.size()]); } - final List list = new ArrayList<>(); + List list = new ArrayList<>(); if (worlds == null) worlds = Bukkit.getWorlds().toArray(new World[0]); - for (final World w : worlds) { - for (final E e : w.getEntitiesByClass(type)) { - for (final EntityData t : types) { - if (t.isInstance(e)) { - list.add(e); + for (World world : worlds) { + for (E entity : world.getEntitiesByClass(type)) { + for (EntityData entityData : types) { + if (entityData.isInstance(entity)) { + list.add(entity); break; } } @@ -549,13 +533,13 @@ public static E[] getAll(final EntityData[] types, final C } @SuppressWarnings("unchecked") - public static E[] getAll(final EntityData[] types, final Class type, Chunk[] chunks) { + public static E[] getAll(EntityData[] types, Class type, Chunk[] chunks) { assert types.length > 0; - final List list = new ArrayList<>(); + List list = new ArrayList<>(); for (Chunk chunk : chunks) { for (Entity entity : chunk.getEntities()) { - for (EntityData t : types) { - if (t.isInstance(entity)) { + for (EntityData entityData : types) { + if (entityData.isInstance(entity)) { list.add(((E) entity)); break; } @@ -565,63 +549,70 @@ public static E[] getAll(final EntityData[] types, final C return list.toArray((E[]) Array.newInstance(type, list.size())); } - private static EntityData getData(final @Nullable Class c, final @Nullable E e) { - assert c == null ^ e == null; - assert c == null || c.isInterface(); - for (final EntityDataInfo info : infos) { - if (info.entityClass != Entity.class && (e == null ? info.entityClass.isAssignableFrom(c) : info.entityClass.isInstance(e))) { + private static EntityData getData(@Nullable Class entityClass, @Nullable E entity) { + assert entityClass == null ^ entity == null; + assert entityClass == null || entityClass.isInterface(); + EntityDataInfo closestInfo = null; + EntityData closestData = null; + for (EntityDataInfo info : infos) { + if (info.entityClass == Entity.class) + continue; + if (entity == null ? info.entityClass.isAssignableFrom(entityClass) : info.entityClass.isInstance(entity)) { + EntityData entityData = null; try { - @SuppressWarnings("unchecked") - final EntityData d = (EntityData) info.getElementClass().newInstance(); - if (d.init(c, e)) - return d; - } catch (final Exception ex) { - throw Skript.exception(ex); + //noinspection unchecked + entityData = (EntityData) info.getElementClass().newInstance(); + } catch (Exception ignored) {} + if (entityData != null && entityData.init(entityClass, entity)) { + if (closestInfo == null || closestInfo.entityClass.isAssignableFrom(info.entityClass)) { + closestInfo = info; + closestData = entityData; + } } } } - if (e != null) { - return new SimpleEntityData(e); - } else { - assert c != null; - return new SimpleEntityData(c); + if (closestInfo == null) { + if (entity != null) + return new SimpleEntityData(entity); + return new SimpleEntityData(entityClass); } - } + return closestData; + }; - public static EntityData fromClass(final Class c) { - return getData(c, null); + public static EntityData fromClass(Class entityClass) { + return getData(entityClass, null); } - public static EntityData fromEntity(final E e) { - return getData(null, e); + public static EntityData fromEntity(E entity) { + return getData(null, entity); } - public static String toString(final Entity e) { - return fromEntity(e).getSuperType().toString(); + public static String toString(Entity entity) { + return fromEntity(entity).getSuperType().toString(); } - public static String toString(final Class c) { - return fromClass(c).getSuperType().toString(); + public static String toString(Class entityClass) { + return fromClass(entityClass).getSuperType().toString(); } - public static String toString(final Entity e, final int flags) { - return fromEntity(e).getSuperType().toString(flags); + public static String toString(Entity entity, int flags) { + return fromEntity(entity).getSuperType().toString(flags); } - public static String toString(final Class c, final int flags) { - return fromClass(c).getSuperType().toString(flags); + public static String toString(Class entityClass, int flags) { + return fromClass(entityClass).getSuperType().toString(flags); } @SuppressWarnings("unchecked") - public final boolean isInstance(final @Nullable Entity e) { - if (e == null) + public final boolean isInstance(@Nullable Entity entity) { + if (entity == null) return false; - if (!baby.isUnknown() && EntityUtils.isAgeable(e) && EntityUtils.isAdult(e) != baby.isFalse()) + if (!baby.isUnknown() && EntityUtils.isAgeable(entity) && EntityUtils.isAdult(entity) != baby.isFalse()) return false; - return getType().isInstance(e) && match((E) e); + return getType().isInstance(entity) && match((E) entity); } - public abstract boolean isSupertypeOf(EntityData e); + public abstract boolean isSupertypeOf(EntityData entityData); @Override public Fields serialize() throws NotSerializableException { @@ -629,12 +620,12 @@ public Fields serialize() throws NotSerializableException { } @Override - public void deserialize(final Fields fields) throws StreamCorruptedException, NotSerializableException { + public void deserialize(Fields fields) throws StreamCorruptedException, NotSerializableException { fields.setFields(this); } @Deprecated - protected boolean deserialize(final String s) { + protected boolean deserialize(String string) { return false; } @@ -652,16 +643,13 @@ protected boolean deserialize(final String s) { if (WORLD_1_17_CONSUMER) { return (@Nullable E) WORLD_1_17_CONSUMER_METHOD.invoke(world, location, type, (org.bukkit.util.Consumer) consumer::accept); - } else if (WORLD_1_13_CONSUMER) { - return (@Nullable E) WORLD_1_13_CONSUMER_METHOD.invoke(world, location, type, - (org.bukkit.util.Consumer) consumer::accept); } } catch (InvocationTargetException | IllegalAccessException e) { if (Skript.testing()) Skript.exception(e, "Can't spawn " + type.getName()); return null; - } - return world.spawn(location, type, consumer); + } + return world.spawn(location, type, consumer); } /** diff --git a/src/main/java/ch/njol/skript/entity/SimpleEntityData.java b/src/main/java/ch/njol/skript/entity/SimpleEntityData.java index 3509ab31ff7..cb9fe2348e8 100644 --- a/src/main/java/ch/njol/skript/entity/SimpleEntityData.java +++ b/src/main/java/ch/njol/skript/entity/SimpleEntityData.java @@ -36,14 +36,13 @@ public int hashCode() { } @Override - public boolean equals(final @Nullable Object obj) { + public boolean equals(@Nullable Object obj) { if (this == obj) return true; if (obj == null) return false; - if (!(obj instanceof SimpleEntityDataInfo)) + if (!(obj instanceof SimpleEntityDataInfo other)) return false; - final SimpleEntityDataInfo other = (SimpleEntityDataInfo) obj; if (c != other.c) return false; assert codeName.equals(other.codeName); @@ -66,8 +65,13 @@ private static void addSimpleEntity(String codeName, Class ent } private static void addSuperEntity(String codeName, Class entityClass) { - types.add(new SimpleEntityDataInfo(codeName, entityClass, true, Kleenean.UNKNOWN)); + addSuperEntity(codeName, entityClass, Kleenean.UNKNOWN); } + + private static void addSuperEntity(String codeName, Class entityClass, Kleenean allowSpawning) { + types.add(new SimpleEntityDataInfo(codeName, entityClass, true, allowSpawning)); + } + static { // Simple Entities addSimpleEntity("arrow", Arrow.class); @@ -86,7 +90,7 @@ private static void addSuperEntity(String codeName, Class enti addSimpleEntity("ender eye", EnderSignal.class); addSimpleEntity("small fireball", SmallFireball.class); addSimpleEntity("large fireball", LargeFireball.class); - addSimpleEntity("fireball", Fireball.class); + addSuperEntity("fireball", Fireball.class, Kleenean.TRUE); addSimpleEntity("fish hook", FishHook.class); addSimpleEntity("ghast", Ghast.class); addSimpleEntity("giant", Giant.class); @@ -146,41 +150,36 @@ private static void addSuperEntity(String codeName, Class enti addSimpleEntity("illusioner", Illusioner.class); - if (Skript.isRunningMinecraft(1, 14)) { - addSimpleEntity("pillager", Pillager.class); - addSimpleEntity("ravager", Ravager.class); - addSimpleEntity("wandering trader", WanderingTrader.class); - } + // 1.14 + addSimpleEntity("pillager", Pillager.class); + addSimpleEntity("ravager", Ravager.class); + addSimpleEntity("wandering trader", WanderingTrader.class); - if (Skript.isRunningMinecraft(1, 16)) { - addSimpleEntity("piglin", Piglin.class); - addSimpleEntity("hoglin", Hoglin.class); - addSimpleEntity("zoglin", Zoglin.class); - addSimpleEntity("strider", Strider.class); - } + // 1.16 + addSimpleEntity("piglin", Piglin.class); + addSimpleEntity("hoglin", Hoglin.class); + addSimpleEntity("zoglin", Zoglin.class); + addSimpleEntity("strider", Strider.class); - if (Skript.classExists("org.bukkit.entity.PiglinBrute")) // Added in 1.16.2 - addSimpleEntity("piglin brute", PiglinBrute.class); + // 1.16.2 + addSimpleEntity("piglin brute", PiglinBrute.class); - if (Skript.isRunningMinecraft(1, 17)) { - addSimpleEntity("glow squid", GlowSquid.class); - addSimpleEntity("marker", Marker.class); - addSimpleEntity("glow item frame", GlowItemFrame.class); - } + // 1.17 + addSimpleEntity("glow squid", GlowSquid.class); + addSimpleEntity("marker", Marker.class); + addSimpleEntity("glow item frame", GlowItemFrame.class); - if (Skript.isRunningMinecraft(1, 19)) { - addSimpleEntity("allay", Allay.class); - addSimpleEntity("tadpole", Tadpole.class); - addSimpleEntity("warden", Warden.class); - } + // 1.19 + addSimpleEntity("allay", Allay.class); + addSimpleEntity("tadpole", Tadpole.class); + addSimpleEntity("warden", Warden.class); - if (Skript.isRunningMinecraft(1, 19, 3)) - addSimpleEntity("camel", Camel.class); + // 1.19.3 + addSimpleEntity("camel", Camel.class); - if (Skript.isRunningMinecraft(1, 19, 4)) { - addSimpleEntity("sniffer", Sniffer.class); - addSimpleEntity("interaction", Interaction.class); - } + // 1.19.4 + addSimpleEntity("sniffer", Sniffer.class); + addSimpleEntity("interaction", Interaction.class); if (Skript.isRunningMinecraft(1, 20, 3)) { addSimpleEntity("breeze", Breeze.class); @@ -258,9 +257,9 @@ private static void addSuperEntity(String codeName, Class enti } static { - final String[] codeNames = new String[types.size()]; + String[] codeNames = new String[types.size()]; int i = 0; - for (final SimpleEntityDataInfo info : types) { + for (SimpleEntityDataInfo info : types) { codeNames[i++] = info.codeName; } EntityData.register(SimpleEntityData.class, "simple", Entity.class, 0, codeNames); @@ -272,64 +271,84 @@ public SimpleEntityData() { this(Entity.class); } - private SimpleEntityData(final SimpleEntityDataInfo info) { + private SimpleEntityData(SimpleEntityDataInfo info) { assert info != null; this.info = info; matchedPattern = types.indexOf(info); } - public SimpleEntityData(final Class c) { - assert c != null && c.isInterface() : c; + public SimpleEntityData(Class entityClass) { + assert entityClass != null && entityClass.isInterface() : entityClass; int i = 0; - for (final SimpleEntityDataInfo info : types) { - if (info.c.isAssignableFrom(c)) { - this.info = info; - matchedPattern = i; - return; + SimpleEntityDataInfo closestInfo = null; + int closestPattern = 0; + for (SimpleEntityDataInfo info : types) { + if (info.c.isAssignableFrom(entityClass)) { + if (closestInfo == null || closestInfo.c.isAssignableFrom(info.c)) { + closestInfo = info; + closestPattern = i; + } } i++; } + if (closestInfo != null) { + this.info = closestInfo; + this.matchedPattern = closestPattern; + return; + } throw new IllegalStateException(); } - public SimpleEntityData(final Entity e) { + public SimpleEntityData(Entity entity) { int i = 0; - for (final SimpleEntityDataInfo info : types) { - if (info.c.isInstance(e)) { - this.info = info; - matchedPattern = i; - return; + SimpleEntityDataInfo closestInfo = null; + int closestPattern = 0; + for (SimpleEntityDataInfo info : types) { + if (info.c.isInstance(entity)) { + if (closestInfo == null || closestInfo.c.isAssignableFrom(info.c)) { + closestInfo = info; + closestPattern = i; + } } i++; } + if (closestInfo != null) { + this.info = closestInfo; + this.matchedPattern = closestPattern; + return; + } throw new IllegalStateException(); } - - @SuppressWarnings("null") + @Override - protected boolean init(final Literal[] exprs, final int matchedPattern, final ParseResult parseResult) { + protected boolean init(Literal[] exprs, int matchedPattern, ParseResult parseResult) { info = types.get(matchedPattern); assert info != null : matchedPattern; return true; } @Override - protected boolean init(final @Nullable Class c, final @Nullable Entity e) { + protected boolean init(@Nullable Class entityClass, @Nullable Entity entity) { assert false; return false; } @Override - public void set(final Entity entity) {} + public void set(Entity entity) {} @Override - public boolean match(final Entity e) { + public boolean match(Entity entity) { if (info.isSupertype) - return info.c.isInstance(e); - for (final SimpleEntityDataInfo info : types) { - if (info.c.isInstance(e)) - return this.info.c == info.c; + return info.c.isInstance(entity); + SimpleEntityDataInfo closest = null; + for (SimpleEntityDataInfo info : types) { + if (info.c.isInstance(entity)) { + if (closest == null || closest.c.isAssignableFrom(info.c)) + closest = info; + } } + if (closest != null) + return this.info.c == closest.c; assert false; return false; } @@ -345,10 +364,9 @@ protected int hashCode_i() { } @Override - protected boolean equals_i(final EntityData obj) { - if (!(obj instanceof SimpleEntityData)) + protected boolean equals_i(EntityData obj) { + if (!(obj instanceof SimpleEntityData other)) return false; - final SimpleEntityData other = (SimpleEntityData) obj; return info.equals(other.info); } @@ -363,17 +381,17 @@ public boolean canSpawn(@Nullable World world) { @Override public Fields serialize() throws NotSerializableException { - final Fields f = super.serialize(); - f.putObject("info.codeName", info.codeName); - return f; + Fields fields = super.serialize(); + fields.putObject("info.codeName", info.codeName); + return fields; } @Override - public void deserialize(final Fields fields) throws StreamCorruptedException, NotSerializableException { - final String codeName = fields.getAndRemoveObject("info.codeName", String.class); - for (final SimpleEntityDataInfo i : types) { - if (i.codeName.equals(codeName)) { - info = i; + public void deserialize(Fields fields) throws StreamCorruptedException, NotSerializableException { + String codeName = fields.getAndRemoveObject("info.codeName", String.class); + for (SimpleEntityDataInfo info : types) { + if (info.codeName.equals(codeName)) { + this.info = info; super.deserialize(fields); return; } @@ -381,27 +399,27 @@ public void deserialize(final Fields fields) throws StreamCorruptedException, No throw new StreamCorruptedException("Invalid SimpleEntityDataInfo code name " + codeName); } -// return info.c.getName(); + @Override @Deprecated - protected boolean deserialize(final String s) { + protected boolean deserialize(String string) { try { - final Class c = Class.forName(s); - for (final SimpleEntityDataInfo i : types) { - if (i.c == c) { - info = i; + Class c = Class.forName(string); + for (SimpleEntityDataInfo info : types) { + if (info.c == c) { + this.info = info; return true; } } return false; - } catch (final ClassNotFoundException e) { + } catch (ClassNotFoundException e) { return false; } } @Override - public boolean isSupertypeOf(final EntityData e) { - return info.c == e.getType() || info.isSupertype && info.c.isAssignableFrom(e.getType()); + public boolean isSupertypeOf(EntityData entityData) { + return info.c == entityData.getType() || info.isSupertype && info.c.isAssignableFrom(entityData.getType()); } @Override diff --git a/src/test/skript/tests/misc/sub entity datas.sk b/src/test/skript/tests/misc/sub entity datas.sk new file mode 100644 index 00000000000..0e3584ea4cc --- /dev/null +++ b/src/test/skript/tests/misc/sub entity datas.sk @@ -0,0 +1,39 @@ + +test "fireball": + spawn a fireball at test-location: + set {_entity} to entity + assert {_entity} is a fireball with "Entity should be a fireball" + clear entity within {_entity} + +test "small fireball": + spawn a small fireball at test-location: + set {_entity} to entity + assert {_entity} is a fireball with "Entity should be a fireball" + assert {_entity} is a small fireball with "Entity should be a small fireball" + clear entity within {_entity} + +test "large fireball": + spawn a large fireball at test-location: + set {_entity} to entity + assert {_entity} is a fireball with "Entity should be a fireball" + assert {_entity} is a large fireball with "Entity should be a large fireball" + clear entity within {_entity} + +test "dragon fireball": + spawn a dragon fireball at test-location: + set {_entity} to entity + assert {_entity} is a fireball with "Entity should be a fireball" + assert {_entity} is a dragon fireball with "Entity should be a dragon fireball" + clear entity within {_entity} + +test "wither skull": + spawn a wither skull at test-location: + set {_entity} to entity + assert {_entity} is a wither skull with "Entity should be a wither skull" + clear entity within {_entity} + +test "wind charge" when running minecraft "1.21.0": + spawn a wind charge at test-location: + set {_entity} to entity + assert {_entity} is a wind charge with "Entity should be a wind charge" + clear entity within {_entity} diff --git a/src/test/skript/tests/syntaxes/sections/EffSecSpawn.sk b/src/test/skript/tests/syntaxes/sections/EffSecSpawn.sk index 36cd029d11f..300716e5d22 100644 --- a/src/test/skript/tests/syntaxes/sections/EffSecSpawn.sk +++ b/src/test/skript/tests/syntaxes/sections/EffSecSpawn.sk @@ -85,14 +85,14 @@ test "spawn entities": add "ender dragon" and "wither" to {_entities::*} add "oak boat", "oak chest boat", "armor stand", "primed tnt", "firework", "arrow", "trident" and "egg" to {_entities::*} add "snowball", "end crystal", "eye of ender", "regular minecart", "falling sand" and "falling gravel" to {_entities::*} - add "falling powder snow" to {_entities::*} + add "falling powder snow", "fireball" and "wither skull" to {_entities::*} if running minecraft "1.20.0": add "camel" and "sniffer" to {_entities::*} if running minecraft "1.20.6": add "armadillo" to {_entities::*} - if running minecraft "1.21": - add "bogged" and "breeze" to {_entities::*} + if running minecraft "1.21.0": + add "bogged", "breeze" and "wind charge" to {_entities::*} # Note: Had to remove 'fireball' and 'wind charge' From 5f737e1557654f64d02cbc4e1cebb0d6a183b801 Mon Sep 17 00:00:00 2001 From: Pesek <42549665+Pesekjak@users.noreply.github.com> Date: Sat, 1 Mar 2025 18:31:38 +0100 Subject: [PATCH 16/18] Fix of the BlockLineIterator causing IllegalStateException (#7513) * replaced Bukkit's (broken) block iterator with own implementation * checkstyle and fixed the overflowing max target block distance * fixed block duplicates, mimics the behaviour of previous block line iterator (using grid traversal algorithm instead of Bresenham's) * added documentation, changed some variable names so the code is easier to understand * fixed epsilon calculation, small changes * simplify calculations by removing Planes, add more tests * sidestep weird JOML mistake (now fixed, but not in pre 1.21.4) --------- Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- .../njol/skript/expressions/ExprBlocks.java | 28 +-- .../njol/skript/util/BlockLineIterator.java | 166 +++++++++++------- .../util/coll/iterator/StoppableIterator.java | 3 +- .../skript/util/BlockLineIteratorTest.java | 44 +++++ .../5566-block iterator being 100 always.sk | 3 +- .../7496-block iterator missing end block.sk | 19 ++ 6 files changed, 191 insertions(+), 72 deletions(-) create mode 100644 src/test/java/ch/njol/skript/util/BlockLineIteratorTest.java create mode 100644 src/test/skript/tests/regressions/7496-block iterator missing end block.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprBlocks.java b/src/main/java/ch/njol/skript/expressions/ExprBlocks.java index 46122dccbff..ae96c454b44 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprBlocks.java +++ b/src/main/java/ch/njol/skript/expressions/ExprBlocks.java @@ -28,13 +28,14 @@ import ch.njol.util.coll.iterator.ArrayIterator; @Name("Blocks") -@Description({"Blocks relative to other blocks or between other blocks. Can be used to get blocks relative to other blocks or for looping.", - "Blocks from/to and between will return a straight line whereas blocks within will return a cuboid."}) +@Description({"Blocks relative to other blocks or between other blocks.", + "Can be used to get blocks relative to other blocks or for looping.", + "Blocks from/to and between will return a straight line whereas blocks within will return a cuboid."}) @Examples({"loop blocks above the player:", - "loop blocks between the block below the player and the targeted block:", - "set the blocks below the player, the victim and the targeted block to air", - "set all blocks within {loc1} and {loc2} to stone", - "set all blocks within chunk at player to air"}) + "loop blocks between the block below the player and the targeted block:", + "set the blocks below the player, the victim and the targeted block to air", + "set all blocks within {loc1} and {loc2} to stone", + "set all blocks within chunk at player to air"}) @Since("1.0, 2.5.1 (within/cuboid/chunk)") public class ExprBlocks extends SimpleExpression { @@ -100,7 +101,7 @@ protected Block[] get(Event event) { return from.stream(event) .filter(Location.class::isInstance) .map(Location.class::cast) - .filter(location -> { + .filter(location -> { if (SUPPORTS_WORLD_LOADED) return location.isWorldLoaded(); return location.getChunk().isLoaded(); @@ -129,15 +130,20 @@ public Iterator iterator(Event event) { Object object = from.getSingle(event); if (object == null) return null; - Location location = object instanceof Location ? (Location) object : ((Block) object).getLocation().add(0.5, 0.5, 0.5); + Location location = object instanceof Location + ? (Location) object + : ((Block) object).getLocation().add(0.5, 0.5, 0.5); Direction direction = this.direction.getSingle(event); if (direction == null || location.getWorld() == null) return null; - Vector vector = object != location ? direction.getDirection((Block) object) : direction.getDirection(location); + Vector vector = object != location + ? direction.getDirection((Block) object) + : direction.getDirection(location); // Cannot be zero. if (vector.getX() == 0 && vector.getY() == 0 && vector.getZ() == 0) return null; - int distance = SkriptConfig.maxTargetBlockDistance.value(); + // start block + (max - 1) == max + int distance = SkriptConfig.maxTargetBlockDistance.value() - 1; if (this.direction instanceof ExprDirection) { Expression numberExpression = ((ExprDirection) this.direction).amount; if (numberExpression != null) { @@ -188,7 +194,7 @@ public String toString(@Nullable Event event, boolean debug) { return "blocks from " + from.toString(event, debug) + " to " + end.toString(event, debug); } else { assert direction != null; - return "block" + (isSingle() ? "" : "s") + " " + direction.toString(event, debug) + " " + from.toString(event, debug); + return "blocks " + direction.toString(event, debug) + " " + from.toString(event, debug); } } diff --git a/src/main/java/ch/njol/skript/util/BlockLineIterator.java b/src/main/java/ch/njol/skript/util/BlockLineIterator.java index 201f031cc4d..c09e09d2749 100644 --- a/src/main/java/ch/njol/skript/util/BlockLineIterator.java +++ b/src/main/java/ch/njol/skript/util/BlockLineIterator.java @@ -1,84 +1,132 @@ package ch.njol.skript.util; -import ch.njol.skript.bukkitutil.WorldUtils; -import ch.njol.util.Math2; import org.bukkit.Location; +import org.bukkit.World; import org.bukkit.block.Block; -import org.bukkit.util.BlockIterator; import org.bukkit.util.Vector; -import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.joml.Vector3d; -import ch.njol.util.NullableChecker; -import ch.njol.util.coll.iterator.StoppableIterator; +import java.util.Iterator; +import java.util.NoSuchElementException; -public class BlockLineIterator extends StoppableIterator { +/** + * Iterates through blocks in a straight line from a start to end location (inclusive). + *

+ * Given start and end locations are always cloned but may not be block-centered. + * Iterates through all blocks the line passes through in order from start to end location. + */ +public class BlockLineIterator implements Iterator { + + private final Vector3d current; + private final Vector3d end; + private final Vector3d centeredEnd; + final Vector3d step; // package private for tests + private final World world; + private boolean finished; + + /** + * @param start start location + * @param end end location + */ + public BlockLineIterator(@NotNull Location start, @NotNull Location end) { + this.current = start.toVector().toVector3d(); + this.world = start.getWorld(); + this.end = end.toVector().toVector3d(); + this.centeredEnd = centered(this.end); + this.step = this.end.sub(current, new Vector3d()).normalize(); + } + + /** + * @param start first block + * @param end last block + */ + public BlockLineIterator(@NotNull Block start, @NotNull Block end) { + this(start.getLocation().toCenterLocation(), end.getLocation().toCenterLocation()); + } /** - * @param start - * @param end - * @throws IllegalStateException randomly (Bukkit bug) + * @param start start location + * @param direction direction to travel in + * @param distance maximum distance to travel */ - public BlockLineIterator(Block start, Block end) throws IllegalStateException { - super(new BlockIterator(start.getWorld(), start.getLocation().toVector(), - end.equals(start) ? new Vector(1, 0, 0) : end.getLocation().subtract(start.getLocation()).toVector(), // should prevent an error if start = end - 0, 0 - ), - new NullableChecker() { - private final double overshotSq = Math.pow(start.getLocation().distance(end.getLocation()) + 2, 2); - - @Override - public boolean check(@Nullable Block block) { - assert block != null; - if (block.getLocation().distanceSquared(start.getLocation()) > overshotSq) - throw new IllegalStateException("BlockLineIterator missed the end block!"); - return block.equals(end); - } - }, true); + public BlockLineIterator(Location start, @NotNull Vector direction, double distance) { + this(start, start.clone().add(direction.clone().normalize().multiply(distance))); + } + + /** + * @param start first block + * @param direction direction to travel in + * @param distance maximum distance to travel + */ + public BlockLineIterator(@NotNull Block start, Vector direction, double distance) { + this(start.getLocation().toCenterLocation(), direction, distance); + } + + @Override + public boolean hasNext() { + return !finished; + } + + @Override + public Block next() { + if (!hasNext()) throw new NoSuchElementException("Reached the final block destination"); + // sanity check (is the current->end vector pointing away from step) + if (end.sub(current, new Vector3d()).dot(step) < 0) throw new NoSuchElementException("Overshot the final block!"); + // get block and check end + Vector3d center = centered(current); + Block block = getBlock(center, world); + if (center.equals(centeredEnd)) finished = true; + // calculate next position + double t = stepsToNextFace(current, step, center) + Math.ulp(1); + current.fma(t, step); + return block; } /** - * @param start - * @param direction - * @param distance - * @throws IllegalStateException randomly (Bukkit bug) + * Calculates the number of steps to the next closest block face this ray, defined by start and step, will encounter. + * Block faces are determined by the center vector, which is interpreted as the center of the block. + * @param start the current location of the ray to check. + * @param step the direction of the ray. + * @param center the center location of the block the ray is currently within. + * @return a scalar floating point number representing the number of times step must be added to start in order + * to arrive at the closest block face. */ - public BlockLineIterator(Location start, Vector direction, double distance) throws IllegalStateException { - super(new BlockIterator(start.getWorld(), start.toVector(), direction, 0, 0), new NullableChecker() { - private final double distSq = distance * distance; - - @Override - public boolean check(final @Nullable Block b) { - return b != null && b.getLocation().add(0.5, 0.5, 0.5).distanceSquared(start) >= distSq; - } - }, false); + static double stepsToNextFace(Vector3d start, @NotNull Vector3d step, Vector3d center) { + Vector3d neededSteps = new Vector3d(Math.signum(step.x), Math.signum(step.y), Math.signum(step.z)) + .mulAdd(0.5, center) + .sub(start) + .div(step, new Vector3d()); // need to make new vector due to JOML method signature issue + // get min component, ignoring NaN + if (Double.isNaN(neededSteps.x)) + neededSteps.x = Double.POSITIVE_INFINITY; + if (Double.isNaN(neededSteps.y)) + neededSteps.y = Double.POSITIVE_INFINITY; + if (Double.isNaN(neededSteps.z)) + neededSteps.z = Double.POSITIVE_INFINITY; + return neededSteps.get(neededSteps.minComponent()); } /** - * @param start - * @param direction - * @param distance - * @throws IllegalStateException randomly (Bukkit bug) + * Creates vector at the center of a block at the coordinates provided + * by {@code vector}. + * + * @param vector point + * @return coordinates at the center of a block at given point */ - public BlockLineIterator(Block start, Vector direction, double distance) throws IllegalStateException { - this(start.getLocation().add(0.5, 0.5, 0.5), direction, distance); + @Contract("_ -> new") + private static Vector3d centered(@NotNull Vector3d vector) { + return vector.floor(new Vector3d()).add(0.5, 0.5, 0.5); } /** - * Makes the vector fit within the world parameters. - * - * @param location The original starting location. - * @param direction The direction of the vector that will be based on the location. - * @return The newly modified Vector if needed. + * @param vector the xyz coordinates of the block to get. + * @param world the world which the block should be obtained from + * @return the block at the given xyz coords in the given world. */ - private static Vector fitInWorld(Location location, Vector direction) { - int lowest = WorldUtils.getWorldMinHeight(location.getWorld()); - int highest = location.getWorld().getMaxHeight(); - Vector vector = location.toVector(); - int y = location.getBlockY(); - if (y >= lowest && y <= highest) - return vector; - double newY = Math2.fit(lowest, location.getY(), highest); - return new Vector(location.getX(), newY, location.getZ()); + private static @NotNull Block getBlock(@NotNull Vector3d vector, @NotNull World world) { + return Vector.fromJOML(vector).toLocation(world).getBlock(); } } diff --git a/src/main/java/ch/njol/util/coll/iterator/StoppableIterator.java b/src/main/java/ch/njol/util/coll/iterator/StoppableIterator.java index cf0688572d6..c329361fe64 100644 --- a/src/main/java/ch/njol/util/coll/iterator/StoppableIterator.java +++ b/src/main/java/ch/njol/util/coll/iterator/StoppableIterator.java @@ -8,8 +8,9 @@ import ch.njol.util.NullableChecker; /** - * @author Peter Güttinger + * @deprecated unused */ +@Deprecated public class StoppableIterator implements Iterator { private final Iterator iter; diff --git a/src/test/java/ch/njol/skript/util/BlockLineIteratorTest.java b/src/test/java/ch/njol/skript/util/BlockLineIteratorTest.java new file mode 100644 index 00000000000..0bc493519ad --- /dev/null +++ b/src/test/java/ch/njol/skript/util/BlockLineIteratorTest.java @@ -0,0 +1,44 @@ +package ch.njol.skript.util; + +import org.bukkit.Location; +import org.joml.Vector3d; +import org.junit.Assert; +import org.junit.Test; + +import java.util.NoSuchElementException; + +import static org.junit.Assert.assertEquals; + +public class BlockLineIteratorTest { + + @Test + public void testStepsToNextFace() { + Vector3d start = new Vector3d(0,0,0); + Vector3d center = new Vector3d(0.5,0.5,0.5); + Vector3d step = new Vector3d(0,0,0); + assertEquals(Double.POSITIVE_INFINITY, BlockLineIterator.stepsToNextFace(start, step, center), Math.ulp(1d)); + step.x = 1; + assertEquals(1.0, BlockLineIterator.stepsToNextFace(start, step, center), Math.ulp(1d)); + step.y = 1; + step.normalize(); + assertEquals(Math.sqrt(2), BlockLineIterator.stepsToNextFace(start, step, center), Math.ulp(1d)); + step.x = 1; + step.y = 1; + step.z = 1; + step.normalize(); + assertEquals(Math.sqrt(3), BlockLineIterator.stepsToNextFace(start, step, center), Math.ulp(1d)); + start.x = 0.99; + assertEquals(Math.sqrt(3) / 100, BlockLineIterator.stepsToNextFace(start, step, center), Math.ulp(1d)); + } + + @Test + public void testOvershoot() { + Location start = new Location(null, 0,0,0); + Location end = new Location(null, 0,10,0); + var iterator = new BlockLineIterator(start, end); + iterator.step.mul(-1); + // step is now wrong way, so it should trigger overshoot protection + Assert.assertThrows(NoSuchElementException.class, iterator::next); + } + +} diff --git a/src/test/skript/tests/regressions/5566-block iterator being 100 always.sk b/src/test/skript/tests/regressions/5566-block iterator being 100 always.sk index b9581140abe..4d6eb3b687a 100644 --- a/src/test/skript/tests/regressions/5566-block iterator being 100 always.sk +++ b/src/test/skript/tests/regressions/5566-block iterator being 100 always.sk @@ -4,5 +4,6 @@ test "100 blocks fix": set {_l} to test-location ~ vector(0, -1, 10) set block at {_l} to air - assert blocks 5 below {_l} contains air, grass block, dirt, dirt, bedrock and void air with "Failed to get correct blocks (got '%blocks 5 below test-location%')" + set {_blocks::*} to (block at {_l}), (block at {_l} ~ vector(0, -1, 0)), (block at {_l} ~ vector(0, -2, 0)), (block at {_l} ~ vector(0, -3, 0)), (block at {_l} ~ vector(0, -4, 0)), and (block at {_l} ~ vector(0, -5, 0)) + assert blocks 5 below {_l} is {_blocks::*} with "Failed to get correct blocks" assert size of blocks 3 below location below {_l} is 4 with "Failed to match asserted size" diff --git a/src/test/skript/tests/regressions/7496-block iterator missing end block.sk b/src/test/skript/tests/regressions/7496-block iterator missing end block.sk new file mode 100644 index 00000000000..3283a744e83 --- /dev/null +++ b/src/test/skript/tests/regressions/7496-block iterator missing end block.sk @@ -0,0 +1,19 @@ +test "basic block iterator test": + set {_l1} to location(0, 100, 0, world "world") + set {_l2} to location(10, 100, 10, world "world") + set {_origin} to location(5, 100, 5, world "world") + loop blocks within {_l1} and {_l2}: + set {_loc} to location of loop-value + assert blocks from {_origin} to {_loc} is set with "failed to find blocks between %{_origin}% and %{_loc}%." + +test "ensure non-centered block iterators are not equal": + set {_l1} to location(0.1, 0.1, 0.1, world "world") + set {_l2} to location(0.9, 0.9, 0.9, world "world") + set {_dir} to vector(1, 0, -1) + assert blocks {_dir} {_l1} are not blocks {_dir} {_l2} with "block iterators from offset locations returned same values." + +test "ensure same block iterators are equal": + set {_l1} to location(0.1, 0.1, 0.1, world "world") + set {_l2} to location(0.9, 0.9, 0.9, world "world") + set {_dir} to vector(1, 0, -1) + assert blocks {_dir} (block at {_l1}) are blocks {_dir} (block at {_l2}) with "block iterators from same block returned different values." From 936c1e1628a55f9a093cc56132a27e9a511a6f28 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Sat, 1 Mar 2025 12:36:10 -0500 Subject: [PATCH 17/18] Update release model to 4 feature releases a year. (#7611) Update release model to 4 features per year --- CLOCKWORK_RELEASE_MODEL.md | 40 +++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/CLOCKWORK_RELEASE_MODEL.md b/CLOCKWORK_RELEASE_MODEL.md index e5e086f62eb..871d4364068 100644 --- a/CLOCKWORK_RELEASE_MODEL.md +++ b/CLOCKWORK_RELEASE_MODEL.md @@ -34,6 +34,8 @@ This document does *not* cover the distribution or publication of artifacts buil Plans for a new release model began in March 2023 and several models were discussed, with this being the final version agreed upon by the organisation's administrative group and approved by the core contributors. +An update to the release model was made in February 2025 to accommodate a more frequent Minecraft release schedule and to help limit update sizes further. + ### Motivations The release cycle for the `2.7.0` version was significant in that it took an unusually long time and included an unusually-large number of additions and changes. @@ -59,6 +61,11 @@ Members of the organisation and the wider community identified several problems Of these, the principle complaint is that the `2.7.0` version took a significant amount of time to finish and this had an adverse effect on the community and the wider ecosystem. +As of February 2025, Mojang has committed to 'drops', which means significant upstream changes happen much more frequently. +This corresponds to Skript having a much higher lag time on releasing support for new content due to the mismatch in release dates. +In addition, an uptick in contributions has led to `2.10` being one of the largest updates yet. +The size of the update meant that some of the previous concerns, like short notice additions and an extensive changelog, were still persistent despite the new release schedule. + ### Goals Our release model has been designed to achieve the following goals: @@ -98,6 +105,7 @@ A 'patch' version (labelled `0.0.X`) may contain: - Bug fixes - Non-impactful2 improvements to existing features - Changes to meta content (e.g. documentation) +- Opt-in experiments There may be **very rare** occasions when a breaking change is necessary in a patch release. These may occur if and only if: either a breaking change is required in order to fix an issue, and the issue is significant enough to need fixing in a patch rather than waiting for a major release, or an issue occurred with an inclusion in the version immediately-prior to this, which must be changed or reverted in some way. @@ -138,18 +146,20 @@ A table of (expected) dates is displayed below. | 15th Jan | Feature release | 0.1.0 | | 1st Feb | Patch | 0.1.1 | | 1st Mar | Patch | 0.1.2 | -| 1st Apr | Patch | 0.1.3 | -| 1st May | Patch | 0.1.4 | -| 1st Jun | Patch | 0.1.5 | -| 1st Jul | Pre-release | 0.2.0-pre1 | -| 15th Jul | Feature release | 0.2.0 | -| 1st Aug | Patch | 0.2.1 | -| 1st Sep | Patch | 0.2.2 | -| 1st Oct | Patch | 0.2.3 | -| 1st Nov | Patch | 0.2.4 | -| 1st Dec | Patch | 0.2.5 | - -An estimated 14 releases are expected per year, with 10 patches, 2 pre-releases and 2 feature-releases that immediately follow them. +| 1st Apr | Pre-release | 0.2.0-pre1 | +| 15th Apr | Feature release | 0.2.0 | +| 1st May | Patch | 0.2.1 | +| 1st Jun | Patch | 0.2.2 | +| 1st Jul | Pre-release | 0.3.0-pre1 | +| 15th Jul | Feature release | 0.3.0 | +| 1st Aug | Patch | 0.3.1 | +| 1st Sep | Patch | 0.3.2 | +| 1st Oct | Pre-release | 0.4.0-pre1 | +| 15th Oct | Feature release | 0.4.0 | +| 1st Nov | Patch | 0.4.1 | +| 1st Dec | Patch | 0.4.2 | + +An estimated 16 releases are expected per year, with 8 patches, 4 pre-releases and 4 feature-releases. Please note that the actual number may differ from this in cases such as: - A version requiring multiple pre-releases to correct mistakes (`0.3.0-pre1`, `0.3.0-pre2`) @@ -160,15 +170,17 @@ There is no fixed timetable for the circulation of unpublished builds to the pub ### Major Version Schedule -A [feature version](#feature-releases) will be released on the **15th of January** and the **15th of July**. +A [feature version](#feature-releases) will be released on the **15th of January**, the **15th of April**, the **15th of July**, and the **15th of October**. -This will include all finished content from the previous 6 months that was tested in the pre-release. +This will include all finished content that was tested in the pre-release. Any features, additions or changes that were *not* ready or approved at the time of the pre-release may **not** be included in the feature release [according to goal 3](#goals). \ The feature release must **not** be delayed to accomodate content that was not ready by the deadline [according to goal 5](#goals). If there is no content ready at the scheduled date of a feature release, the release will be skipped and a notice published explaining this. +The April and October releases are intended to include minimal breaking changes and focus more on including new features and opt-in experiments. + ### Pre-Release Schedule A [pre-release](#pre-releases) will be released on the **1st of January** and the **1st of July**, leaving two weeks before the following release for public testing to occur. From 0fc4c8cd185871ad8f51daddefe2e7baeba3bef0 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Sat, 1 Mar 2025 13:11:45 -0500 Subject: [PATCH 18/18] Prepare for 2.10.2 (#7660) bump version --- gradle.properties | 2 +- src/main/java/ch/njol/skript/classes/data/BukkitClasses.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 028c8c3c729..74a4f516f45 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ org.gradle.parallel=true groupid=ch.njol name=skript -version=2.10.1 +version=2.10.2 jarName=Skript.jar testEnv=java21/paper-1.21.4 testEnvJavaVersion=21 diff --git a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java index 02652a96394..8486136f6d2 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -1540,7 +1540,7 @@ public String toVariableNameString(EntitySnapshot snapshot) { .user("vehicles?") .name("Vehicle") .description("Represents a vehicle.") - .since("INSERT VERSION") + .since("2.10.2") .changer(DefaultChangers.entityChanger) );