Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add datapack registration lifecycle event #11804

Merged
merged 5 commits into from
Dec 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions build-data/paper.at
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ public net.minecraft.server.network.ServerLoginPacketListenerImpl connection
public net.minecraft.server.network.ServerLoginPacketListenerImpl state
public net.minecraft.server.network.ServerLoginPacketListenerImpl$State
public net.minecraft.server.packs.VanillaPackResourcesBuilder safeGetPath(Ljava/net/URI;)Ljava/nio/file/Path;
public net.minecraft.server.packs.repository.FolderRepositorySource$FolderPackDetector
public net.minecraft.server.packs.repository.FolderRepositorySource$FolderPackDetector <init>(Lnet/minecraft/world/level/validation/DirectoryValidator;)V
public net.minecraft.server.packs.repository.Pack resources
public net.minecraft.server.players.PlayerList playerIo
public net.minecraft.server.players.PlayerList players
Expand Down
64 changes: 10 additions & 54 deletions paper-api/src/main/java/io/papermc/paper/datapack/Datapack.java
Original file line number Diff line number Diff line change
@@ -1,61 +1,17 @@
package io.papermc.paper.datapack;

import java.util.Set;
import net.kyori.adventure.text.Component;
import org.bukkit.FeatureFlag;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Unmodifiable;
import org.jspecify.annotations.NullMarked;

/**
* This is a snapshot of a datapack on the server. It
* won't be updated as datapacks are updated.
*/
@NullMarked
public interface Datapack {

/**
* Gets the name/id of this datapack.
*
* @return the name of the pack
*/
@Contract(pure = true)
String getName();

/**
* Gets the title component of this datapack.
*
* @return the title
*/
Component getTitle();

/**
* Gets the description component of this datapack.
*
* @return the description
*/
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
*/
Compatibility getCompatibility();

/**
* Gets the set of required features for this datapack.
*
* @return the set of required features
*/
@Unmodifiable Set<FeatureFlag> getRequiredFeatures();
@ApiStatus.NonExtendable
public interface Datapack extends DiscoveredDatapack {

/**
* Gets the enabled state of this pack.
Expand All @@ -74,13 +30,6 @@ public interface Datapack {
*/
void setEnabled(boolean enabled);

/**
* Gets the source for this datapack.
*
* @return the pack source
*/
DatapackSource getSource();

/**
* Computes the component vanilla Minecraft uses
* to display this datapack. Includes the {@link #getSource()},
Expand All @@ -96,4 +45,11 @@ enum Compatibility {
TOO_NEW,
COMPATIBLE,
}

/**
* Position of the pack in the load order.
*/
enum Position {
TOP, BOTTOM
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
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.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Unmodifiable;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;

/**
* 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.
* <p>An example of a plugin loading a datapack from within it's own jar is below</p>
* <pre>{@code
* public class YourPluginBootstrap implements PluginBootstrap {
* @Override
* public void bootstrap(BoostrapContext context) {
* final LifecycleEventManager<BootstrapContext> 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);
* }
* });
* }
* }
* }</pre>
* @see io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents#DATAPACK_DISCOVERY
*/
@ApiStatus.NonExtendable
@ApiStatus.Experimental
@NullMarked
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(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)
DiscoveredDatapack getDiscoveredPack(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(String name);

/**
* Gets all discovered datapacks.
*
* @return an unmodifiable map of discovered packs
*/
@Contract(pure = true)
@Unmodifiable Map<String, DiscoveredDatapack> getDiscoveredPacks();

/**
* Discovers a datapack at the specified {@link URI} with the id.
* <p>Symlinks obey the {@code allowed_symlinks.txt} in the server root directory.</p>
*
* @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 URI uri, final String id) throws IOException {
return this.discoverPack(uri, id, c -> {});
}

/**
* Discovers a datapack at the specified {@link URI} with the id.
* <p>Symlinks obey the {@code allowed_symlinks.txt} in the server root directory.</p>
*
* @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(URI uri, String id, Consumer<Configurer> configurer) throws IOException;

/**
* Discovers a datapack at the specified {@link Path} with the id.
* <p>Symlinks obey the {@code allowed_symlinks.txt} in the server root directory.</p>
*
* @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 Path path, final String id) throws IOException {
return this.discoverPack(path, id, c -> {});
}

/**
* Discovers a datapack at the specified {@link Path} with the id.
* <p>Symlinks obey the {@code allowed_symlinks.txt} in the server root directory.</p>
*
* @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(Path path, String id, Consumer<Configurer> configurer) throws IOException;

/**
* Discovers a datapack at the specified {@link URI} with the id.
* <p>Symlinks obey the {@code allowed_symlinks.txt} in the server root directory.</p>
*
* @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(PluginMeta pluginMeta, URI uri, String id, Consumer<Configurer> configurer) throws IOException;

/**
* Discovers a datapack at the specified {@link Path} with the id.
* <p>Symlinks obey the {@code allowed_symlinks.txt} in the server root directory.</p>
*
* @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(PluginMeta pluginMeta, Path path, String id, Consumer<Configurer> 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")
Configurer title(Component title);

/**
* Sets whether this pack is going to be automatically enabled on server starts even if previously disabled.
* Defaults to false.
*
* @param autoEnableOnServerStart true to ensure the pack is enabled on server starts.
* @return the configurer for chaining
*/
@Contract(value = "_ -> this", mutates = "this")
Configurer autoEnableOnServerStart(boolean autoEnableOnServerStart);

/**
* 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")
Configurer position(boolean fixed, Datapack.Position position);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public sealed interface DatapackSource permits DatapackSourceImpl {
DatapackSource FEATURE = create("feature");
DatapackSource WORLD = create("world");
DatapackSource SERVER = create("server");
DatapackSource PLUGIN = create("plugin");

private static DatapackSource create(final String name) {
return new DatapackSourceImpl(name);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package io.papermc.paper.datapack;

import java.util.Set;
import net.kyori.adventure.text.Component;
import org.bukkit.FeatureFlag;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Unmodifiable;
import org.jspecify.annotations.NullMarked;

/**
* This is a snapshot of a discovered datapack on the server. It
* won't be updated as datapacks are updated.
*/
@NullMarked
@ApiStatus.NonExtendable
public interface DiscoveredDatapack {

/**
* Gets the name/id of this datapack.
*
* @return the name of the pack
*/
@Contract(pure = true)
String getName();

/**
* Gets the title component of this datapack.
*
* @return the title
*/
Component getTitle();

/**
* Gets the description component of this datapack.
*
* @return the description
*/
Component getDescription();

/**
* Gets if this datapack is required.
* <p>
* A "required" datapack will always be enabled on server startup, even if previously disabled.
*
* @return true if the pack is required
*/
boolean isRequired();

/**
* Gets the compatibility status of this pack.
*
* @return the compatibility of the pack
*/
Datapack.Compatibility getCompatibility();

/**
* Gets the set of required features for this datapack.
*
* @return the set of required features
*/
@Unmodifiable
Set<FeatureFlag> getRequiredFeatures();

/**
* Gets the source for this datapack.
*
* @return the pack source
*/
DatapackSource getSource();
}
Loading
Loading