From e05d62861020abbb45db35173b8d073125067831 Mon Sep 17 00:00:00 2001 From: Jake Potrebic Date: Thu, 20 Jun 2024 09:40:53 -0700 Subject: [PATCH] Tag Lifecycle Events --- .../types/LifecycleEventTypeProvider.java | 2 + .../event/types/LifecycleEvents.java | 6 + .../event/types/TagEventTypeProvider.java | 41 +++++++ .../paper/tag/PostFlattenTagRegistrar.java | 104 ++++++++++++++++++ .../paper/tag/PreFlattenTagRegistrar.java | 103 +++++++++++++++++ .../java/io/papermc/paper/tag/TagEntry.java | 90 +++++++++++++++ .../io/papermc/paper/tag/TagEntryImpl.java | 10 ++ 7 files changed, 356 insertions(+) create mode 100644 paper-api/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/TagEventTypeProvider.java create mode 100644 paper-api/src/main/java/io/papermc/paper/tag/PostFlattenTagRegistrar.java create mode 100644 paper-api/src/main/java/io/papermc/paper/tag/PreFlattenTagRegistrar.java create mode 100644 paper-api/src/main/java/io/papermc/paper/tag/TagEntry.java create mode 100644 paper-api/src/main/java/io/papermc/paper/tag/TagEntryImpl.java diff --git a/paper-api/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEventTypeProvider.java b/paper-api/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEventTypeProvider.java index e15e09c2a4..ece6b72c5a 100644 --- a/paper-api/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEventTypeProvider.java +++ b/paper-api/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEventTypeProvider.java @@ -21,4 +21,6 @@ interface LifecycleEventTypeProvider { LifecycleEventType.Monitorable monitor(String name, Class ownerType); LifecycleEventType.Prioritizable prioritized(String name, Class ownerType); + + TagEventTypeProvider tagProvider(); } diff --git a/paper-api/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEvents.java b/paper-api/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEvents.java index ab6b262cf0..b65c5fd544 100644 --- a/paper-api/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEvents.java +++ b/paper-api/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEvents.java @@ -26,6 +26,12 @@ public final class LifecycleEvents { */ public static final LifecycleEventType.Prioritizable> COMMANDS = prioritized("commands", LifecycleEventOwner.class); + /** + * These events are for registering tags to the server's tag system. You can register a handler for these events + * only in {@link io.papermc.paper.plugin.bootstrap.PluginBootstrap#bootstrap(BootstrapContext)}. + */ + public static final TagEventTypeProvider TAGS = LifecycleEventTypeProvider.provider().tagProvider(); + // @ApiStatus.Internal static LifecycleEventType.Monitorable plugin(final String name) { diff --git a/paper-api/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/TagEventTypeProvider.java b/paper-api/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/TagEventTypeProvider.java new file mode 100644 index 0000000000..843ce7848c --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/TagEventTypeProvider.java @@ -0,0 +1,41 @@ +package io.papermc.paper.plugin.lifecycle.event.types; + +import io.papermc.paper.plugin.bootstrap.BootstrapContext; +import io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent; +import io.papermc.paper.registry.RegistryKey; +import io.papermc.paper.tag.PostFlattenTagRegistrar; +import io.papermc.paper.tag.PreFlattenTagRegistrar; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; + +/** + * Provides event types for tag registration. + * + * @see PreFlattenTagRegistrar + * @see PostFlattenTagRegistrar + */ +@ApiStatus.Experimental +@NullMarked +@ApiStatus.NonExtendable +public interface TagEventTypeProvider { + + /** + * Get a prioritizable, reloadable registrar event for tags before they are flattened. + * + * @param registryKey the registry key for the tag type + * @return the registry event type + * @param the type of value in the tag + * @see PreFlattenTagRegistrar + */ + LifecycleEventType.Prioritizable>> preFlatten(RegistryKey registryKey); + + /** + * Get a prioritizable, reloadable registrar event for tags after they are flattened. + * + * @param registryKey the registry key for the tag type + * @return the registry event type + * @param the type of value in the tag + * @see PostFlattenTagRegistrar + */ + LifecycleEventType.Prioritizable>> postFlatten(RegistryKey registryKey); +} diff --git a/paper-api/src/main/java/io/papermc/paper/tag/PostFlattenTagRegistrar.java b/paper-api/src/main/java/io/papermc/paper/tag/PostFlattenTagRegistrar.java new file mode 100644 index 0000000000..22db9446ca --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/tag/PostFlattenTagRegistrar.java @@ -0,0 +1,104 @@ +package io.papermc.paper.tag; + +import io.papermc.paper.plugin.lifecycle.event.registrar.Registrar; +import io.papermc.paper.registry.RegistryKey; +import io.papermc.paper.registry.TypedKey; +import io.papermc.paper.registry.tag.TagKey; +import java.util.Collection; +import java.util.Map; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Unmodifiable; +import org.jspecify.annotations.NullMarked; + +/** + * Registrar for tags after they have been flattened. Flattened + * tags are tags which have any nested tags resolved to the tagged + * values the nested tags point to. This registrar, being a post-flatten + * registrar, allows for modification after that flattening has happened, when + * tags only point to individual entries and not other nested tags. + *

+ * An example of a custom enchant being registered to the vanilla + * {@code #minecraft:in_enchanting_table} tag. + *

{@code
+ * class YourBootstrapClass implements PluginBootstrap {
+ *
+ *     @Override
+ *     public void bootstrap(BootstrapContext context) {
+ *         LifecycleEventManager manager = context.getLifecycleManager();
+ *         manager.registerEventHandler(LifecycleEvents.TAGS.postFlatten(RegistryKey.ENCHANTMENT), event -> {
+ *             final PostFlattenTagRegistrar registrar = event.registrar();
+ *             registrar.addToTag(
+ *                 EnchantmentTagKeys.IN_ENCHANTING_TABLE,
+ *                 Set.of(CUSTOM_ENCHANT)
+ *             );
+ *         });
+ *     }
+ * }
+ * }
+ * + * @param the type of value in the tag + * @see PreFlattenTagRegistrar + */ +@ApiStatus.Experimental +@NullMarked +@ApiStatus.NonExtendable +public interface PostFlattenTagRegistrar extends Registrar { + + /** + * Get the registry key for this tag registrar. + * + * @return the registry key + */ + RegistryKey registryKey(); + + /** + * Get a copy of all tags currently held in this registrar. + * + * @return an immutable map of all tags + */ + @Contract(value = "-> new", pure = true) + @Unmodifiable Map, Collection>> getAllTags(); + + /** + * Checks if this registrar has a tag with the given key. + * + * @param tagKey the key to check for + * @return true if the tag exists, false otherwise + */ + @Contract(pure = true) + boolean hasTag(TagKey tagKey); + + /** + * Get the tag with the given key. Use {@link #hasTag(TagKey)} to check + * if a tag exists first. + * + * @param tagKey the key of the tag to get + * @return an immutable list of tag entries + * @throws java.util.NoSuchElementException if the tag does not exist + * @see #hasTag(TagKey) + */ + @Contract(value = "_ -> new", pure = true) + @Unmodifiable Collection> getTag(TagKey tagKey); + + /** + * Adds values to the given tag. If the tag does not exist, it will be created. + * + * @param tagKey the key of the tag to add to + * @param values the values to add + * @see #setTag(TagKey, Collection) + */ + @Contract(mutates = "this") + void addToTag(TagKey tagKey, Collection> values); + + /** + * Sets the values of the given tag. If the tag does not exist, it will be created. + * If the tag does exist, it will be overwritten. + * + * @param tagKey the key of the tag to set + * @param values the values to set + * @see #addToTag(TagKey, Collection) + */ + @Contract(mutates = "this") + void setTag(TagKey tagKey, Collection> values); +} diff --git a/paper-api/src/main/java/io/papermc/paper/tag/PreFlattenTagRegistrar.java b/paper-api/src/main/java/io/papermc/paper/tag/PreFlattenTagRegistrar.java new file mode 100644 index 0000000000..8d8a00e4d9 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/tag/PreFlattenTagRegistrar.java @@ -0,0 +1,103 @@ +package io.papermc.paper.tag; + +import io.papermc.paper.plugin.lifecycle.event.registrar.Registrar; +import io.papermc.paper.registry.RegistryKey; +import io.papermc.paper.registry.tag.TagKey; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Unmodifiable; +import org.jspecify.annotations.NullMarked; + +/** + * Registrar for tags before they are flattened. Flattened + * tags are tags which have any nested tags resolved to the tagged + * values the nested tags point to. This registrar, being a pre-flatten + * registrar, allows for modification before that flattening has happened, when + * tags both point to individual entries and other nested tags. + *

+ * An example of a tag being created in a pre-flatten registrar: + *

{@code
+ * class YourBootstrapClass implements PluginBootstrap {
+ *
+ *     @Override
+ *     public void bootstrap(BootstrapContext context) {
+ *         LifecycleEventManager manager = context.getLifecycleManager();
+ *         manager.registerEventHandler(LifecycleEvents.TAGS.preFlatten(RegistryKey.ITEM), event -> {
+ *             final PreFlattenTagRegistrar registrar = event.registrar();
+ *             registrar.setTag(AXE_PICKAXE, Set.of(
+ *                 TagEntry.tagEntry(ItemTypeTagKeys.PICKAXES),
+ *                 TagEntry.tagEntry(ItemTypeTagKeys.AXES)
+ *             ));
+ *         });
+ *     }
+ * }
+ * }
+ * + * @param the type of value in the tag + * @see PostFlattenTagRegistrar + */ +@ApiStatus.Experimental +@NullMarked +@ApiStatus.NonExtendable +public interface PreFlattenTagRegistrar extends Registrar { + + /** + * Get the registry key for this tag registrar. + * + * @return the registry key + */ + RegistryKey registryKey(); + + /** + * Get a copy of all tags currently held in this registrar. + * + * @return an immutable map of all tags + */ + @Contract(value = "-> new", pure = true) + @Unmodifiable Map, Collection>> getAllTags(); + + /** + * Checks if this registrar has a tag with the given key. + * + * @param tagKey the key to check for + * @return true if the tag exists, false otherwise + */ + @Contract(pure = true) + boolean hasTag(TagKey tagKey); + + /** + * Get the tag with the given key. Use {@link #hasTag(TagKey)} to check + * if a tag exists first. + * + * @param tagKey the key of the tag to get + * @return an immutable list of tag entries + * @throws java.util.NoSuchElementException if the tag does not exist + * @see #hasTag(TagKey) + */ + @Contract(value = "_ -> new", pure = true) + @Unmodifiable List> getTag(TagKey tagKey); + + /** + * Adds entries to the given tag. If the tag does not exist, it will be created. + * + * @param tagKey the key of the tag to add to + * @param entries the entries to add + * @see #setTag(TagKey, Collection) + */ + @Contract(mutates = "this") + void addToTag(TagKey tagKey, Collection> entries); + + /** + * Sets the entries of the given tag. If the tag does not exist, it will be created. + * If the tag does exist, it will be overwritten. + * + * @param tagKey the key of the tag to set + * @param entries the entries to set + * @see #addToTag(TagKey, Collection) + */ + @Contract(mutates = "this") + void setTag(TagKey tagKey, Collection> entries); +} diff --git a/paper-api/src/main/java/io/papermc/paper/tag/TagEntry.java b/paper-api/src/main/java/io/papermc/paper/tag/TagEntry.java new file mode 100644 index 0000000000..efa52b387e --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/tag/TagEntry.java @@ -0,0 +1,90 @@ +package io.papermc.paper.tag; + +import io.papermc.paper.registry.TypedKey; +import io.papermc.paper.registry.tag.TagKey; +import net.kyori.adventure.key.Keyed; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; +import org.jspecify.annotations.NullMarked; + +/** + * An entry is a pre-flattened tag. Represents + * either an individual registry entry or a whole tag. + * + * @param the type of value in the tag + * @see PreFlattenTagRegistrar + */ +@ApiStatus.Experimental +@NullMarked +@ApiStatus.NonExtendable +public interface TagEntry extends Keyed { + + /** + * Create required tag entry for a single value. + * + * @param entryKey the key of the entry + * @return a new tag entry for a value + * @param the type of value + */ + @Contract(value = "_ -> new", pure = true) + static TagEntry valueEntry(final TypedKey entryKey) { + return valueEntry(entryKey, true); + } + + /** + * Create tag entry for a single value. + * + * @param entryKey the key of the entry + * @param isRequired if this entry is required (see {@link #isRequired()}) + * @return a new tag entry for a value + * @param the type of value + */ + @Contract(value = "_, _ -> new", pure = true) + static TagEntry valueEntry(final TypedKey entryKey, final boolean isRequired) { + return new TagEntryImpl<>(entryKey.key(), false, isRequired); + } + + /** + * Create a required tag entry for a nested tag. + * + * @param tagKey they key for the tag + * @return a new tag entry for a tag + * @param the type of value + */ + @Contract(value = "_ -> new", pure = true) + static TagEntry tagEntry(final TagKey tagKey) { + return tagEntry(tagKey, true); + } + + /** + * Create a tag entry for a nested tag. + * + * @param tagKey they key for the tag + * @param isRequired if this entry is required (see {@link #isRequired()}) + * @return a new tag entry for a tag + * @param the type of value + */ + @Contract(value = "_, _ -> new", pure = true) + static TagEntry tagEntry(final TagKey tagKey, final boolean isRequired) { + return new TagEntryImpl<>(tagKey.key(), true, isRequired); + } + + /** + * Returns if this entry represents a tag. + * + * @return true if this entry is a tag, false if it is an individual entry + */ + @Contract(pure = true) + boolean isTag(); + + /** + * Returns if this entry is required. If an entry is required, + * the value or tag must exist on the server in order for the tag + * to load correctly. A missing value will prevent the tag holding + * that missing value from being created. + * + * @return true if this entry is required, false if it is optional + */ + @Contract(pure = true) + boolean isRequired(); +} diff --git a/paper-api/src/main/java/io/papermc/paper/tag/TagEntryImpl.java b/paper-api/src/main/java/io/papermc/paper/tag/TagEntryImpl.java new file mode 100644 index 0000000000..8d46a330ca --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/tag/TagEntryImpl.java @@ -0,0 +1,10 @@ +package io.papermc.paper.tag; + +import net.kyori.adventure.key.Key; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; + +@NullMarked +@ApiStatus.Internal +record TagEntryImpl(Key key, boolean isTag, boolean isRequired) implements TagEntry { +}