From bf66c7c548bfc7bb9511127bd39f26476ec309c7 Mon Sep 17 00:00:00 2001 From: Jake Potrebic Date: Sun, 12 May 2024 17:34:17 -0700 Subject: [PATCH] Add datapack registration lifecycle event --- ...atapack-registration-lifecycle-event.patch | 277 ++++++++++++ ...1-move-methods-to-DiscoveredDatapack.patch | 162 +++++++ ...atapack-registration-lifecycle-event.patch | 394 ++++++++++++++++++ ...e-methods-to-PaperDiscoveredDatapack.patch | 189 +++++++++ .../testplugin/TestPluginBootstrap.java | 17 + .../machine_maker/tags/entity_type/test.json | 6 + .../data/machine_maker/tags/item/test.json | 6 + .../src/main/resources/pack/pack.mcmeta | 6 + 8 files changed, 1057 insertions(+) create mode 100644 patches/api/0490-Add-datapack-registration-lifecycle-event.patch create mode 100644 patches/api/0491-move-methods-to-DiscoveredDatapack.patch create mode 100644 patches/server/1059-Add-datapack-registration-lifecycle-event.patch create mode 100644 patches/server/1060-move-methods-to-PaperDiscoveredDatapack.patch create mode 100644 test-plugin/src/main/resources/pack/data/machine_maker/tags/entity_type/test.json create mode 100644 test-plugin/src/main/resources/pack/data/machine_maker/tags/item/test.json create mode 100644 test-plugin/src/main/resources/pack/pack.mcmeta diff --git a/patches/api/0490-Add-datapack-registration-lifecycle-event.patch b/patches/api/0490-Add-datapack-registration-lifecycle-event.patch new file mode 100644 index 0000000000..390556f6d2 --- /dev/null +++ b/patches/api/0490-Add-datapack-registration-lifecycle-event.patch @@ -0,0 +1,277 @@ +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) { diff --git a/patches/api/0491-move-methods-to-DiscoveredDatapack.patch b/patches/api/0491-move-methods-to-DiscoveredDatapack.patch new file mode 100644 index 0000000000..1b1bacea4d --- /dev/null +++ b/patches/api/0491-move-methods-to-DiscoveredDatapack.patch @@ -0,0 +1,162 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 23 Sep 2024 16:04:31 -0700 +Subject: [PATCH] move methods to DiscoveredDatapack + + +diff --git a/src/main/java/io/papermc/paper/datapack/Datapack.java b/src/main/java/io/papermc/paper/datapack/Datapack.java +index 436606dd81ff666d8378c41f45bbb6a674a477dd..073eaecc77c372c9d73a11a1e374be181d2de353 100644 +--- a/src/main/java/io/papermc/paper/datapack/Datapack.java ++++ b/src/main/java/io/papermc/paper/datapack/Datapack.java +@@ -1,60 +1,16 @@ + package io.papermc.paper.datapack; + +-import java.util.Set; + import net.kyori.adventure.text.Component; +-import org.bukkit.FeatureFlag; + import org.checkerframework.checker.nullness.qual.NonNull; ++import org.jetbrains.annotations.ApiStatus; + import org.jetbrains.annotations.Contract; +-import org.jetbrains.annotations.Unmodifiable; + + /** + * This is a snapshot of a datapack on the server. It + * won't be updated as datapacks are updated. + */ +-public interface Datapack { +- +- /** +- * Gets the name/id of this datapack. +- * +- * @return the name of the pack +- */ +- @Contract(pure = true) +- @NonNull String getName(); +- +- /** +- * Gets the title component of this datapack. +- * +- * @return the title +- */ +- @NonNull Component getTitle(); +- +- /** +- * Gets the description component of this datapack. +- * +- * @return the description +- */ +- @NonNull Component getDescription(); +- +- /** +- * Gets if this datapack is required to be enabled. +- * +- * @return true if the pack is required +- */ +- boolean isRequired(); +- +- /** +- * Gets the compatibility status of this pack. +- * +- * @return the compatibility of the pack +- */ +- @NonNull Compatibility getCompatibility(); +- +- /** +- * Gets the set of required features for this datapack. +- * +- * @return the set of required features +- */ +- @NonNull @Unmodifiable Set getRequiredFeatures(); ++@ApiStatus.NonExtendable ++public interface Datapack extends DiscoveredDatapack { + + /** + * Gets the enabled state of this pack. +@@ -73,13 +29,6 @@ public interface Datapack { + */ + void setEnabled(boolean enabled); + +- /** +- * Gets the source for this datapack. +- * +- * @return the pack source +- */ +- @NonNull DatapackSource getSource(); +- + /** + * Computes the component vanilla Minecraft uses + * to display this datapack. Includes the {@link #getSource()}, +diff --git a/src/main/java/io/papermc/paper/datapack/DiscoveredDatapack.java b/src/main/java/io/papermc/paper/datapack/DiscoveredDatapack.java +index f0ec90fd08e984995fd3fe48ae3219ce08e2d40a..8e1da7c41d7060a089c8b8c90f00e3bb47440bda 100644 +--- a/src/main/java/io/papermc/paper/datapack/DiscoveredDatapack.java ++++ b/src/main/java/io/papermc/paper/datapack/DiscoveredDatapack.java +@@ -1,8 +1,67 @@ + package io.papermc.paper.datapack; + ++import java.util.Set; ++import net.kyori.adventure.text.Component; ++import org.bukkit.FeatureFlag; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.jetbrains.annotations.ApiStatus; ++import org.jetbrains.annotations.Contract; ++import org.jetbrains.annotations.Unmodifiable; ++ ++/** ++ * This is a snapshot of a discovered datapack on the server. It ++ * won't be updated as datapacks are updated. ++ */ ++@ApiStatus.NonExtendable + public interface DiscoveredDatapack { + +- String getName(); ++ /** ++ * Gets the name/id of this datapack. ++ * ++ * @return the name of the pack ++ */ ++ @Contract(pure = true) ++ @NonNull String getName(); ++ ++ /** ++ * Gets the title component of this datapack. ++ * ++ * @return the title ++ */ ++ @NonNull Component getTitle(); ++ ++ /** ++ * Gets the description component of this datapack. ++ * ++ * @return the description ++ */ ++ @NonNull Component getDescription(); ++ ++ /** ++ * Gets if this datapack is required to be enabled. ++ * ++ * @return true if the pack is required ++ */ ++ boolean isRequired(); ++ ++ /** ++ * Gets the compatibility status of this pack. ++ * ++ * @return the compatibility of the pack ++ */ ++ Datapack.@NonNull Compatibility getCompatibility(); ++ ++ /** ++ * Gets the set of required features for this datapack. ++ * ++ * @return the set of required features ++ */ ++ @NonNull @Unmodifiable Set getRequiredFeatures(); + +- Datapack.Compatibility getCompatibility(); ++ /** ++ * Gets the source for this datapack. ++ * ++ * @return the pack source ++ */ ++ @NonNull DatapackSource getSource(); + } diff --git a/patches/server/1059-Add-datapack-registration-lifecycle-event.patch b/patches/server/1059-Add-datapack-registration-lifecycle-event.patch new file mode 100644 index 0000000000..3b5a055934 --- /dev/null +++ b/patches/server/1059-Add-datapack-registration-lifecycle-event.patch @@ -0,0 +1,394 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 12 May 2024 17:30:50 -0700 +Subject: [PATCH] Add datapack registration lifecycle event + +== AT == +public net/minecraft/server/packs/repository/FolderRepositorySource$FolderPackDetector +public net/minecraft/server/packs/repository/FolderRepositorySource$FolderPackDetector (Lnet/minecraft/world/level/validation/DirectoryValidator;)V + +diff --git a/src/main/java/io/papermc/paper/datapack/PaperDatapackRegistrar.java b/src/main/java/io/papermc/paper/datapack/PaperDatapackRegistrar.java +new file mode 100644 +index 0000000000000000000000000000000000000000..aa5c7dfddea67db036c066d5151821248b945550 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/datapack/PaperDatapackRegistrar.java +@@ -0,0 +1,166 @@ ++package io.papermc.paper.datapack; ++ ++import com.google.common.base.Preconditions; ++import com.google.common.collect.ImmutableMap; ++import com.mojang.logging.LogUtils; ++import io.papermc.paper.adventure.PaperAdventure; ++import io.papermc.paper.plugin.bootstrap.BootstrapContext; ++import io.papermc.paper.plugin.configuration.PluginMeta; ++import io.papermc.paper.plugin.lifecycle.event.registrar.PaperRegistrar; ++import java.io.IOException; ++import java.net.URI; ++import java.nio.file.Path; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.Map; ++import java.util.NoSuchElementException; ++import java.util.Optional; ++import java.util.function.Consumer; ++import net.kyori.adventure.text.Component; ++import net.minecraft.server.packs.PackLocationInfo; ++import net.minecraft.server.packs.PackSelectionConfig; ++import net.minecraft.server.packs.PackType; ++import net.minecraft.server.packs.VanillaPackResourcesBuilder; ++import net.minecraft.server.packs.repository.FolderRepositorySource; ++import net.minecraft.server.packs.repository.Pack; ++import net.minecraft.server.packs.repository.PackDetector; ++import net.minecraft.world.level.validation.ContentValidationException; ++import net.minecraft.world.level.validation.DirectoryValidator; ++import net.minecraft.world.level.validation.ForbiddenSymlinkInfo; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++import org.jetbrains.annotations.Unmodifiable; ++import org.slf4j.Logger; ++ ++@DefaultQualifier(NonNull.class) ++public class PaperDatapackRegistrar implements PaperRegistrar, DatapackRegistrar { ++ ++ private static final Logger LOGGER = LogUtils.getClassLogger(); ++ ++ private final PackDetector detector; ++ public final Map discoveredPacks; ++ private @Nullable BootstrapContext owner; ++ ++ public PaperDatapackRegistrar(final DirectoryValidator symlinkValidator, final Map discoveredPacks) { ++ this.detector = new FolderRepositorySource.FolderPackDetector(symlinkValidator); ++ this.discoveredPacks = discoveredPacks; ++ } ++ ++ @Override ++ public void setCurrentContext(final @Nullable BootstrapContext owner) { ++ this.owner = owner; ++ } ++ ++ @Override ++ public boolean hasPackDiscovered(final String name) { ++ return this.discoveredPacks.containsKey(name); ++ } ++ ++ @Override ++ public @NonNull DiscoveredDatapack getDiscoveredPack(final String name) { ++ if (!this.hasPackDiscovered(name)) { ++ throw new NoSuchElementException("No pack with id " + name + " was discovered"); ++ } ++ return new PaperDiscoveredDatapack(this.discoveredPacks.get(name)); ++ } ++ ++ @Override ++ public boolean removeDiscoveredPack(final String name) { ++ return this.discoveredPacks.remove(name) != null; ++ } ++ ++ @Override ++ public @Unmodifiable Map getDiscoveredPacks() { ++ final ImmutableMap.Builder builder = ImmutableMap.builderWithExpectedSize(this.discoveredPacks.size()); ++ for (final Map.Entry entry : this.discoveredPacks.entrySet()) { ++ builder.put(entry.getKey(), new PaperDiscoveredDatapack(entry.getValue())); ++ } ++ return builder.build(); ++ } ++ ++ @Override ++ public @Nullable DiscoveredDatapack discoverPack(final URI uri, final String id, final Consumer configurer) throws IOException { ++ Preconditions.checkState(this.owner != null, "Cannot register a datapack without specifying a PluginMeta yet"); ++ return this.discoverPack(this.owner.getPluginMeta(), uri, id, configurer); ++ } ++ ++ @Override ++ public @Nullable DiscoveredDatapack discoverPack(final Path path, final String id, final Consumer configurer) throws IOException { ++ Preconditions.checkState(this.owner != null, "Cannot register a datapack without specifying a PluginMeta yet"); ++ return this.discoverPack(this.owner.getPluginMeta(), path, id, configurer); ++ } ++ ++ @Override ++ public @Nullable DiscoveredDatapack discoverPack(final PluginMeta pluginMeta, final URI uri, final String id, final Consumer configurer) throws IOException { ++ return this.discoverPack(pluginMeta, VanillaPackResourcesBuilder.safeGetPath(uri), id, configurer); ++ } ++ ++ @Override ++ public @Nullable DiscoveredDatapack discoverPack(final PluginMeta pluginMeta, final Path path, final String id, final Consumer configurer) throws IOException { ++ final List badLinks = new ArrayList<>(); ++ final Pack.@Nullable ResourcesSupplier resourcesSupplier = this.detector.detectPackResources(path, badLinks); ++ if (!badLinks.isEmpty()) { ++ LOGGER.warn("Ignoring potential pack entry: {}", ContentValidationException.getMessage(path, badLinks)); ++ } else if (resourcesSupplier != null) { ++ final String packId = pluginMeta.getName() + "/" + id; ++ final ConfigurerImpl configurerImpl = new ConfigurerImpl(Component.text(packId)); ++ configurer.accept(configurerImpl); ++ final PackLocationInfo locInfo = new PackLocationInfo(packId, ++ PaperAdventure.asVanilla(configurerImpl.title), ++ PluginPackSource.INSTANCE, ++ Optional.empty() ++ ); ++ final @Nullable Pack pack = Pack.readMetaAndCreate(locInfo, ++ resourcesSupplier, ++ PackType.SERVER_DATA, ++ new PackSelectionConfig( ++ configurerImpl.required, ++ configurerImpl.position, ++ configurerImpl.fixedPosition ++ )); ++ if (pack != null) { ++ this.discoveredPacks.put(packId, pack); ++ return new PaperDiscoveredDatapack(pack); ++ } ++ return null; ++ } else { ++ LOGGER.info("Found non-pack entry '{}', ignoring", path); ++ } ++ return null; ++ } ++ ++ static final class ConfigurerImpl implements Configurer { ++ ++ private Component title; ++ private boolean required = false; ++ private boolean fixedPosition = false; ++ private Pack.Position position = Pack.Position.TOP; ++ ++ ConfigurerImpl(final Component title) { ++ this.title = title; ++ } ++ ++ @Override ++ public Configurer title(final Component title) { ++ this.title = title; ++ return this; ++ } ++ ++ @Override ++ public Configurer required(final boolean required) { ++ this.required = required; ++ return this; ++ } ++ ++ @Override ++ public Configurer position(final boolean fixed, final Datapack.Position position) { ++ this.fixedPosition = fixed; ++ this.position = switch (position) { ++ case TOP -> Pack.Position.TOP; ++ case BOTTOM -> Pack.Position.BOTTOM; ++ }; ++ return this; ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/datapack/PaperDiscoveredDatapack.java b/src/main/java/io/papermc/paper/datapack/PaperDiscoveredDatapack.java +new file mode 100644 +index 0000000000000000000000000000000000000000..572a62ceafcd066adfc0c2588cc43a0b61cedb7f +--- /dev/null ++++ b/src/main/java/io/papermc/paper/datapack/PaperDiscoveredDatapack.java +@@ -0,0 +1,46 @@ ++package io.papermc.paper.datapack; ++ ++import java.util.Objects; ++import net.minecraft.server.packs.repository.Pack; ++ ++public class PaperDiscoveredDatapack implements DiscoveredDatapack{ ++ ++ private final String name; ++ private final Datapack.Compatibility compatibility; ++ ++ PaperDiscoveredDatapack(Pack pack) { ++ this.name = pack.getId(); ++ this.compatibility = Datapack.Compatibility.valueOf(pack.getCompatibility().name()); ++ } ++ ++ @Override ++ public String getName() { ++ return this.name; ++ } ++ ++ @Override ++ public Datapack.Compatibility getCompatibility() { ++ return this.compatibility; ++ } ++ ++ @Override ++ public boolean equals(final Object o) { ++ if (this == o) return true; ++ if (o == null || this.getClass() != o.getClass()) return false; ++ final PaperDiscoveredDatapack that = (PaperDiscoveredDatapack) o; ++ return Objects.equals(this.name, that.name) && this.compatibility == that.compatibility; ++ } ++ ++ @Override ++ public int hashCode() { ++ return Objects.hash(this.name, this.compatibility); ++ } ++ ++ @Override ++ public String toString() { ++ return "PaperDiscoveredDatapack{" + ++ "name='" + this.name + '\'' + ++ ", compatibility=" + this.compatibility + ++ '}'; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/datapack/PluginPackSource.java b/src/main/java/io/papermc/paper/datapack/PluginPackSource.java +new file mode 100644 +index 0000000000000000000000000000000000000000..dfea23ddde7b929f4d47c5de9539cf8bb96bcfff +--- /dev/null ++++ b/src/main/java/io/papermc/paper/datapack/PluginPackSource.java +@@ -0,0 +1,26 @@ ++package io.papermc.paper.datapack; ++ ++import net.minecraft.ChatFormatting; ++import net.minecraft.network.chat.Component; ++import net.minecraft.server.packs.repository.PackSource; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++final class PluginPackSource implements PackSource { ++ ++ static final PackSource INSTANCE = new PluginPackSource(); ++ ++ private PluginPackSource() { ++ } ++ ++ @Override ++ public Component decorate(final Component packDisplayName) { ++ return Component.translatable("pack.nameAndSource", packDisplayName, "plugin").withStyle(ChatFormatting.GRAY); ++ } ++ ++ @Override ++ public boolean shouldAddAutomatically() { ++ return true; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java +index cca76f2d1623952017a83fdb027f77a601c79b3e..9770bd30943b81d85e3ccdf1ebdbdf0524bff243 100644 +--- a/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java ++++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java +@@ -27,7 +27,8 @@ public class LifecycleEventRunner { + + private static final Logger LOGGER = LogUtils.getClassLogger(); + private static final Supplier>> BLOCKS_RELOADING = Suppliers.memoize(() -> Set.of( // lazy due to cyclic initialization +- LifecycleEvents.COMMANDS ++ LifecycleEvents.COMMANDS, ++ LifecycleEvents.DATAPACK_DISCOVERY + )); + public static final LifecycleEventRunner INSTANCE = new LifecycleEventRunner(); + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 696d075ca2883f3c37e35f983c4d020e5db89d16..78a4dd6a6b5649fd42a729c98a8e03dd80349290 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -2365,7 +2365,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop discoverNewPacks(PackRepository dataPackManager, WorldData saveProperties, Collection enabledDataPacks) { +- dataPackManager.reload(); ++ dataPackManager.reload(true); // Paper - will perform a full reload + Collection collection1 = Lists.newArrayList(enabledDataPacks); + Collection collection2 = saveProperties.getDataConfiguration().dataPacks().getDisabled(); + Iterator iterator = dataPackManager.getAvailableIds().iterator(); +diff --git a/src/main/java/net/minecraft/server/packs/repository/PackRepository.java b/src/main/java/net/minecraft/server/packs/repository/PackRepository.java +index 7cae8350023fb138bfcc5af28af6d36a3433d063..75d98b8c0850906a51b519746f112cb0e45b2d77 100644 +--- a/src/main/java/net/minecraft/server/packs/repository/PackRepository.java ++++ b/src/main/java/net/minecraft/server/packs/repository/PackRepository.java +@@ -21,8 +21,12 @@ public class PackRepository { + private final Set sources; + private Map available = ImmutableMap.of(); + private List selected = ImmutableList.of(); ++ private final net.minecraft.world.level.validation.DirectoryValidator validator; // Paper - add validator + +- public PackRepository(RepositorySource... providers) { ++ // Paper start - add validator ++ public PackRepository(net.minecraft.world.level.validation.DirectoryValidator validator, RepositorySource... providers) { ++ this.validator = validator; ++ // Paper end - add validator + this.sources = ImmutableSet.copyOf(providers); + } + +@@ -33,9 +37,14 @@ public class PackRepository { + } + + public void reload() { ++ // Paper start - perform a full reload ++ this.reload(false); ++ } ++ public void reload(boolean addRequiredPacks) { ++ // Paper end + List list = this.selected.stream().map(Pack::getId).collect(ImmutableList.toImmutableList()); + this.available = this.discoverAvailable(); +- this.selected = this.rebuildSelected(list); ++ this.selected = this.rebuildSelected(list, addRequiredPacks); // Paper + } + + private Map discoverAvailable() { +@@ -45,11 +54,18 @@ public class PackRepository { + repositorySource.loadPacks(profile -> map.put(profile.getId(), profile)); + } + +- return ImmutableMap.copyOf(map); ++ // Paper start - custom plugin-loaded datapacks ++ final io.papermc.paper.datapack.PaperDatapackRegistrar registrar = new io.papermc.paper.datapack.PaperDatapackRegistrar(this.validator, map); ++ io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.INSTANCE.callStaticRegistrarEvent(io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents.DATAPACK_DISCOVERY, ++ registrar, ++ io.papermc.paper.plugin.bootstrap.BootstrapContext.class ++ ); ++ return ImmutableMap.copyOf(registrar.discoveredPacks); ++ // Paper end - custom plugin-loaded datapacks + } + + public void setSelected(Collection enabled) { +- this.selected = this.rebuildSelected(enabled); ++ this.selected = this.rebuildSelected(enabled, false); // Paper - add willReload boolean + } + + public boolean addPack(String profile) { +@@ -76,11 +92,11 @@ public class PackRepository { + } + } + +- private List rebuildSelected(Collection enabledNames) { ++ private List rebuildSelected(Collection enabledNames, boolean addRequiredPacks) { // Paper - add addRequiredPacks boolean + List list = this.getAvailablePacks(enabledNames).collect(Util.toMutableList()); + + for (Pack pack : this.available.values()) { +- if (pack.isRequired() && !list.contains(pack)) { ++ if (pack.isRequired() && !list.contains(pack) && addRequiredPacks) { // Paper - add addRequiredPacks boolean + pack.getDefaultPosition().insert(list, pack, Pack::selectionConfig, false); + } + } +diff --git a/src/main/java/net/minecraft/server/packs/repository/ServerPacksSource.java b/src/main/java/net/minecraft/server/packs/repository/ServerPacksSource.java +index 396ec10a76bdadbf5be2f0e15e88eed47619004d..ac9256a65fab2896fcb42808ad65105701eae6f4 100644 +--- a/src/main/java/net/minecraft/server/packs/repository/ServerPacksSource.java ++++ b/src/main/java/net/minecraft/server/packs/repository/ServerPacksSource.java +@@ -83,13 +83,13 @@ public class ServerPacksSource extends BuiltInPackSource { + } + + public static PackRepository createPackRepository(Path dataPacksPath, DirectoryValidator symlinkFinder) { +- return new PackRepository( ++ return new PackRepository(symlinkFinder, // Paper - add validator + new ServerPacksSource(symlinkFinder), new FolderRepositorySource(dataPacksPath, PackType.SERVER_DATA, PackSource.WORLD, symlinkFinder) + ); + } + + public static PackRepository createVanillaTrustedRepository() { +- return new PackRepository(new ServerPacksSource(new DirectoryValidator(path -> true))); ++ return new PackRepository(new DirectoryValidator(path -> true), new ServerPacksSource(new DirectoryValidator(path -> true))); // Paper - add validator + } + + public static PackRepository createPackRepository(LevelStorageSource.LevelStorageAccess session) { diff --git a/patches/server/1060-move-methods-to-PaperDiscoveredDatapack.patch b/patches/server/1060-move-methods-to-PaperDiscoveredDatapack.patch new file mode 100644 index 0000000000..640bbc56d9 --- /dev/null +++ b/patches/server/1060-move-methods-to-PaperDiscoveredDatapack.patch @@ -0,0 +1,189 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 23 Sep 2024 16:10:57 -0700 +Subject: [PATCH] move methods to PaperDiscoveredDatapack + + +diff --git a/src/main/java/io/papermc/paper/datapack/PaperDatapack.java b/src/main/java/io/papermc/paper/datapack/PaperDatapack.java +index 8bd8263b51fb2bb364353565b1ba26b3b0d1d55e..5a78ce1bee122951a9346e99f1b05eef772912b5 100644 +--- a/src/main/java/io/papermc/paper/datapack/PaperDatapack.java ++++ b/src/main/java/io/papermc/paper/datapack/PaperDatapack.java +@@ -2,71 +2,27 @@ package io.papermc.paper.datapack; + + import io.papermc.paper.adventure.PaperAdventure; + import io.papermc.paper.event.server.ServerResourcesReloadedEvent; +-import io.papermc.paper.world.flag.PaperFeatureFlagProviderImpl; + import java.util.ArrayList; + import java.util.List; +-import java.util.Map; +-import java.util.Set; +-import java.util.concurrent.ConcurrentHashMap; + import net.kyori.adventure.text.Component; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.packs.repository.Pack; +-import net.minecraft.server.packs.repository.PackSource; +-import org.bukkit.FeatureFlag; + import org.checkerframework.checker.nullness.qual.NonNull; + import org.checkerframework.checker.nullness.qual.Nullable; + import org.checkerframework.framework.qual.DefaultQualifier; + + @DefaultQualifier(NonNull.class) +-public class PaperDatapack implements Datapack { +- +- private static final Map PACK_SOURCES = new ConcurrentHashMap<>(); +- static { +- PACK_SOURCES.put(PackSource.DEFAULT, DatapackSource.DEFAULT); +- PACK_SOURCES.put(PackSource.BUILT_IN, DatapackSource.BUILT_IN); +- PACK_SOURCES.put(PackSource.FEATURE, DatapackSource.FEATURE); +- PACK_SOURCES.put(PackSource.WORLD, DatapackSource.WORLD); +- PACK_SOURCES.put(PackSource.SERVER, DatapackSource.SERVER); +- } ++public class PaperDatapack extends PaperDiscoveredDatapack implements Datapack { + + private final Pack pack; + private final boolean enabled; + + PaperDatapack(final Pack pack, final boolean enabled) { ++ super(pack); + this.pack = pack; + this.enabled = enabled; + } + +- @Override +- public String getName() { +- return this.pack.getId(); +- } +- +- @Override +- public Component getTitle() { +- return PaperAdventure.asAdventure(this.pack.getTitle()); +- } +- +- @Override +- public Component getDescription() { +- return PaperAdventure.asAdventure(this.pack.getDescription()); +- } +- +- @Override +- public boolean isRequired() { +- return this.pack.isRequired(); +- } +- +- @Override +- public Compatibility getCompatibility() { +- return Datapack.Compatibility.valueOf(this.pack.getCompatibility().name()); +- } +- +- @Override +- public Set getRequiredFeatures() { +- return PaperFeatureFlagProviderImpl.fromNms(this.pack.getRequestedFeatures()); +- } +- + @Override + public boolean isEnabled() { + return this.enabled; +@@ -91,11 +47,6 @@ public class PaperDatapack implements Datapack { + server.reloadResources(enabledPacks.stream().map(Pack::getId).toList(), ServerResourcesReloadedEvent.Cause.PLUGIN); + } + +- @Override +- public DatapackSource getSource() { +- return PACK_SOURCES.computeIfAbsent(this.pack.location().source(), source -> new DatapackSourceImpl(source.toString())); +- } +- + @Override + public Component computeDisplayName() { + return PaperAdventure.asAdventure(this.pack.getChatLink(this.enabled)); +diff --git a/src/main/java/io/papermc/paper/datapack/PaperDiscoveredDatapack.java b/src/main/java/io/papermc/paper/datapack/PaperDiscoveredDatapack.java +index 572a62ceafcd066adfc0c2588cc43a0b61cedb7f..711e6a8791a72e759ca777f66426ce12fc2230b0 100644 +--- a/src/main/java/io/papermc/paper/datapack/PaperDiscoveredDatapack.java ++++ b/src/main/java/io/papermc/paper/datapack/PaperDiscoveredDatapack.java +@@ -1,46 +1,67 @@ + package io.papermc.paper.datapack; + +-import java.util.Objects; ++import io.papermc.paper.adventure.PaperAdventure; ++import io.papermc.paper.world.flag.PaperFeatureFlagProviderImpl; ++import java.util.Map; ++import java.util.Set; ++import java.util.concurrent.ConcurrentHashMap; ++import net.kyori.adventure.text.Component; + import net.minecraft.server.packs.repository.Pack; ++import net.minecraft.server.packs.repository.PackSource; ++import org.bukkit.FeatureFlag; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; + +-public class PaperDiscoveredDatapack implements DiscoveredDatapack{ ++@DefaultQualifier(NonNull.class) ++public class PaperDiscoveredDatapack implements DiscoveredDatapack { + +- private final String name; +- private final Datapack.Compatibility compatibility; ++ private static final Map PACK_SOURCES = new ConcurrentHashMap<>(); ++ static { ++ PACK_SOURCES.put(PackSource.DEFAULT, DatapackSource.DEFAULT); ++ PACK_SOURCES.put(PackSource.BUILT_IN, DatapackSource.BUILT_IN); ++ PACK_SOURCES.put(PackSource.FEATURE, DatapackSource.FEATURE); ++ PACK_SOURCES.put(PackSource.WORLD, DatapackSource.WORLD); ++ PACK_SOURCES.put(PackSource.SERVER, DatapackSource.SERVER); ++ } ++ ++ private final Pack pack; + + PaperDiscoveredDatapack(Pack pack) { +- this.name = pack.getId(); +- this.compatibility = Datapack.Compatibility.valueOf(pack.getCompatibility().name()); ++ this.pack = pack; + } + + @Override + public String getName() { +- return this.name; ++ return this.pack.getId(); + } + + @Override +- public Datapack.Compatibility getCompatibility() { +- return this.compatibility; ++ public Component getTitle() { ++ return PaperAdventure.asAdventure(this.pack.getTitle()); ++ } ++ ++ @Override ++ public Component getDescription() { ++ return PaperAdventure.asAdventure(this.pack.getDescription()); + } + + @Override +- public boolean equals(final Object o) { +- if (this == o) return true; +- if (o == null || this.getClass() != o.getClass()) return false; +- final PaperDiscoveredDatapack that = (PaperDiscoveredDatapack) o; +- return Objects.equals(this.name, that.name) && this.compatibility == that.compatibility; ++ public boolean isRequired() { ++ return this.pack.isRequired(); ++ } ++ ++ @Override ++ public Datapack.Compatibility getCompatibility() { ++ return Datapack.Compatibility.valueOf(this.pack.getCompatibility().name()); + } + + @Override +- public int hashCode() { +- return Objects.hash(this.name, this.compatibility); ++ public Set getRequiredFeatures() { ++ return PaperFeatureFlagProviderImpl.fromNms(this.pack.getRequestedFeatures()); + } + + @Override +- public String toString() { +- return "PaperDiscoveredDatapack{" + +- "name='" + this.name + '\'' + +- ", compatibility=" + this.compatibility + +- '}'; ++ public DatapackSource getSource() { ++ return PACK_SOURCES.computeIfAbsent(this.pack.location().source(), source -> new DatapackSourceImpl(source.toString())); + } + } diff --git a/test-plugin/src/main/java/io/papermc/testplugin/TestPluginBootstrap.java b/test-plugin/src/main/java/io/papermc/testplugin/TestPluginBootstrap.java index fe2b287b25..bfb7009bcb 100644 --- a/test-plugin/src/main/java/io/papermc/testplugin/TestPluginBootstrap.java +++ b/test-plugin/src/main/java/io/papermc/testplugin/TestPluginBootstrap.java @@ -1,7 +1,14 @@ package io.papermc.testplugin; +import io.papermc.paper.datapack.DatapackRegistrar; import io.papermc.paper.plugin.bootstrap.BootstrapContext; import io.papermc.paper.plugin.bootstrap.PluginBootstrap; +import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager; +import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Objects; import org.jetbrains.annotations.NotNull; public class TestPluginBootstrap implements PluginBootstrap { @@ -9,6 +16,16 @@ public class TestPluginBootstrap implements PluginBootstrap { @Override public void bootstrap(@NotNull BootstrapContext context) { // io.papermc.testplugin.brigtests.Registration.registerViaBootstrap(context); + final LifecycleEventManager manager = context.getLifecycleManager(); + manager.registerEventHandler(LifecycleEvents.DATAPACK_DISCOVERY, event -> { + final DatapackRegistrar registrar = event.registrar(); + try { + final URI uri = Objects.requireNonNull(TestPluginBootstrap.class.getResource("/pack")).toURI(); + registrar.discoverPack(uri, "test"); + } catch (final URISyntaxException | IOException e) { + throw new RuntimeException(e); + } + }); } } diff --git a/test-plugin/src/main/resources/pack/data/machine_maker/tags/entity_type/test.json b/test-plugin/src/main/resources/pack/data/machine_maker/tags/entity_type/test.json new file mode 100644 index 0000000000..36722a9a39 --- /dev/null +++ b/test-plugin/src/main/resources/pack/data/machine_maker/tags/entity_type/test.json @@ -0,0 +1,6 @@ +{ + "values": [ + "minecraft:zombie", + "minecraft:spider" + ] +} diff --git a/test-plugin/src/main/resources/pack/data/machine_maker/tags/item/test.json b/test-plugin/src/main/resources/pack/data/machine_maker/tags/item/test.json new file mode 100644 index 0000000000..2a659578fa --- /dev/null +++ b/test-plugin/src/main/resources/pack/data/machine_maker/tags/item/test.json @@ -0,0 +1,6 @@ +{ + "values": [ + "minecraft:stone_bricks", + "minecraft:string" + ] +} diff --git a/test-plugin/src/main/resources/pack/pack.mcmeta b/test-plugin/src/main/resources/pack/pack.mcmeta new file mode 100644 index 0000000000..8a117a64fc --- /dev/null +++ b/test-plugin/src/main/resources/pack/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack":{ + "pack_format": 41, + "description": "Test datapack for tags" + } +}