From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Jake Potrebic Date: Sun, 12 May 2024 17:30:54 -0700 Subject: [PATCH] Add datapack registration lifecycle event diff --git a/src/main/java/io/papermc/paper/datapack/Datapack.java b/src/main/java/io/papermc/paper/datapack/Datapack.java index 233a31afa9673c9cb8d9eb52551425ff15f79661..436606dd81ff666d8378c41f45bbb6a674a477dd 100644 --- a/src/main/java/io/papermc/paper/datapack/Datapack.java +++ b/src/main/java/io/papermc/paper/datapack/Datapack.java @@ -95,4 +95,11 @@ public interface Datapack { TOO_NEW, COMPATIBLE, } + + /** + * Position of the pack in the load order. + */ + enum Position { + TOP, BOTTOM + } } diff --git a/src/main/java/io/papermc/paper/datapack/DatapackRegistrar.java b/src/main/java/io/papermc/paper/datapack/DatapackRegistrar.java new file mode 100644 index 0000000000000000000000000000000000000000..456c26c9295a19743fdb8ea6d42ff672836a9e7b --- /dev/null +++ b/src/main/java/io/papermc/paper/datapack/DatapackRegistrar.java @@ -0,0 +1,204 @@ +package io.papermc.paper.datapack; + +import io.papermc.paper.plugin.configuration.PluginMeta; +import io.papermc.paper.plugin.lifecycle.event.registrar.Registrar; +import java.io.IOException; +import java.net.URI; +import java.nio.file.Path; +import java.util.Map; +import java.util.function.Consumer; +import net.kyori.adventure.text.Component; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Unmodifiable; + +/** + * The registrar for datapacks. The event for this registrar + * is called anytime the game tries to discover datapacks at any of the + * configured locations. This means that if a datapack should stay available to the server, + * it must always be discovered whenever this event fires. + *

An example of a plugin loading a datapack from within it's own jar is below

+ *
{@code
+ * public class YourPluginBootstrap implements PluginBootstrap {
+ *     @Override
+ *     public void bootstrap(BoostrapContext context) {
+ *         final LifecycleEventManager manager = context.getLifecycleManager();
+ *         manager.registerEventHandler(LifecycleEvents.DATAPACK_DISCOVERY, event -> {
+ *             DatapackRegistrar registrar = event.registrar();
+ *             try {
+ *                 final URI uri = Objects.requireNonNull(
+ *                     YourPluginBootstrap.class.getResource("/pack")
+ *                 ).toURI();
+ *                 registrar.discoverPack(uri, "packId");
+ *             } catch (final URISyntaxException | IOException e) {
+ *                 throw new RuntimeException(e);
+ *             }
+ *         });
+ *     }
+ * }
+ * }
+ * @see io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents#DATAPACK_DISCOVERY + */ +@ApiStatus.NonExtendable +@ApiStatus.Experimental +public interface DatapackRegistrar extends Registrar { + + /** + * Checks if a datapack with the specified name has been discovered. + * + * @param name the name of the pack + * @return true if the pack has been discovered + * @see Datapack#getName() + */ + @Contract(pure = true) + boolean hasPackDiscovered(@NonNull String name); + + /** + * Gets a discovered datapack by its name. + * + * @param name the name of the pack + * @return the datapack + * @throws java.util.NoSuchElementException if the pack is not discovered + * @see Datapack#getName() + */ + @Contract(pure = true) + @NonNull DiscoveredDatapack getDiscoveredPack(@NonNull String name); + + /** + * Removes a discovered datapack by its name. + * + * @param name the name of the pack + * @return true if the pack was removed + * @see Datapack#getName() + */ + @Contract(mutates = "this") + boolean removeDiscoveredPack(@NonNull String name); + + /** + * Gets all discovered datapacks. + * + * @return an unmodifiable map of discovered packs + */ + @Contract(pure = true) + @Unmodifiable @NonNull Map getDiscoveredPacks(); + + /** + * Discovers a datapack at the specified {@link URI} with the id. + *

Symlinks obey the {@code allowed_symlinks.txt} in the server root directory.

+ * + * @param uri the location of the pack + * @param id a unique id (will be combined with plugin for the datapacks name) + * @return the discovered datapack (or null if it failed) + * @throws IOException if any IO error occurs + */ + default @Nullable DiscoveredDatapack discoverPack(final @NonNull URI uri, final @NonNull String id) throws IOException { + return this.discoverPack(uri, id, c -> {}); + } + + /** + * Discovers a datapack at the specified {@link URI} with the id. + *

Symlinks obey the {@code allowed_symlinks.txt} in the server root directory.

+ * + * @param uri the location of the pack + * @param id a unique id (will be combined with plugin for the datapacks name) + * @param configurer a configurer for extra options + * @return the discovered datapack (or null if it failed) + * @throws IOException if any IO error occurs + */ + @Nullable DiscoveredDatapack discoverPack(@NonNull URI uri, @NonNull String id, @NonNull Consumer configurer) throws IOException; + + /** + * Discovers a datapack at the specified {@link Path} with the id. + *

Symlinks obey the {@code allowed_symlinks.txt} in the server root directory.

+ * + * @param path the location of the pack + * @param id a unique id (will be combined with plugin for the datapacks name) + * @return the discovered datapack (or null if it failed) + * @throws IOException if any IO error occurs + */ + default @Nullable DiscoveredDatapack discoverPack(final @NonNull Path path, final @NonNull String id) throws IOException { + return this.discoverPack(path, id, c -> {}); + } + + /** + * Discovers a datapack at the specified {@link Path} with the id. + *

Symlinks obey the {@code allowed_symlinks.txt} in the server root directory.

+ * + * @param path the location of the pack + * @param id a unique id (will be combined with plugin for the datapacks name) + * @param configurer a configurer for extra options + * @return the discovered datapack (or null if it failed) + * @throws IOException if any IO error occurs + */ + @Nullable DiscoveredDatapack discoverPack(@NonNull Path path, @NonNull String id, @NonNull Consumer configurer) throws IOException; + + /** + * Discovers a datapack at the specified {@link URI} with the id. + *

Symlinks obey the {@code allowed_symlinks.txt} in the server root directory.

+ * + * @param pluginMeta the plugin which will be the "owner" of this datapack + * @param uri the location of the pack + * @param id a unique id (will be combined with plugin for the datapacks name) + * @param configurer a configurer for extra options + * @return the discovered datapack (or null if it failed) + * @throws IOException if any IO error occurs + */ + @Nullable DiscoveredDatapack discoverPack(@NonNull PluginMeta pluginMeta, @NonNull URI uri, @NonNull String id, @NonNull Consumer configurer) throws IOException; + + /** + * Discovers a datapack at the specified {@link Path} with the id. + *

Symlinks obey the {@code allowed_symlinks.txt} in the server root directory.

+ * + * @param pluginMeta the plugin which will be the "owner" of this datapack + * @param path the location of the pack + * @param id a unique id (will be combined with plugin for the datapacks name) + * @param configurer a configurer for extra options + * @return the discovered datapack (or null if it failed) + * @throws IOException if any IO error occurs + */ + @Nullable DiscoveredDatapack discoverPack(@NonNull PluginMeta pluginMeta, @NonNull Path path, @NonNull String id, @NonNull Consumer configurer) throws IOException; + + /** + * Configures additional, optional, details about a datapack. + */ + @ApiStatus.NonExtendable + @ApiStatus.Experimental + interface Configurer { + + /** + * Changes the title of the datapack from the default which + * is just the "id" in the {@code registerPack} methods. + * + * @param title the new title + * @return the configurer for chaining + */ + @Contract(value = "_ -> this", mutates = "this") + @NonNull Configurer title(@NonNull Component title); + + /** + * Sets if this pack is required. Defaults to false. + * A required pack cannot be disabled once enabled. Marking + * a pack as required does not mean it will immediately be enabled + * upon discovery. It may be enabled if this event was fired + * due to a pending (re)load. + * + * @param required true to require the pack + * @return the configurer for chaining + */ + @Contract(value = "_ -> this", mutates = "this") + @NonNull Configurer required(boolean required); + + /** + * Configures the position in the + * load order of this datapack. + * + * @param fixed won't move around in the load order as packs are added/removed + * @param position try to insert at the top of the order or bottom + * @return the configurer for chaining + */ + @Contract(value = "_, _ -> this", mutates = "this") + @NonNull Configurer position(boolean fixed, Datapack.@NonNull Position position); + } +} diff --git a/src/main/java/io/papermc/paper/datapack/DiscoveredDatapack.java b/src/main/java/io/papermc/paper/datapack/DiscoveredDatapack.java new file mode 100644 index 0000000000000000000000000000000000000000..f0ec90fd08e984995fd3fe48ae3219ce08e2d40a --- /dev/null +++ b/src/main/java/io/papermc/paper/datapack/DiscoveredDatapack.java @@ -0,0 +1,8 @@ +package io.papermc.paper.datapack; + +public interface DiscoveredDatapack { + + String getName(); + + Datapack.Compatibility getCompatibility(); +} diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEvents.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEvents.java index 720fe2546015838708ce794c291ca187cf7bca9c..6dce88760b954bc898bdc05a64081b1dd1737c3c 100644 --- a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEvents.java +++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEvents.java @@ -1,10 +1,12 @@ package io.papermc.paper.plugin.lifecycle.event.types; import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.datapack.DatapackRegistrar; import io.papermc.paper.plugin.bootstrap.BootstrapContext; import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager; import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; +import io.papermc.paper.plugin.lifecycle.event.registrar.RegistrarEvent; import io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent; import org.bukkit.plugin.Plugin; import org.jetbrains.annotations.ApiStatus; @@ -30,6 +32,13 @@ public final class LifecycleEvents { */ public static final TagEventTypeProvider TAGS = LifecycleEventTypeProvider.provider().tagProvider(); + /** + * This event is for informing the server about any available datapacks from other sources such as inside a plugin's jar. You + * can register a handler for this event only in {@link io.papermc.paper.plugin.bootstrap.PluginBootstrap#bootstrap(BootstrapContext)}. + * @see DatapackRegistrar an example of a datapack being discovered + */ + public static final LifecycleEventType.Prioritizable> DATAPACK_DISCOVERY = bootstrapPrioritized("datapack_discovery"); + // @ApiStatus.Internal static LifecycleEventType.Monitorable plugin(final String name) {