From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Jake Potrebic Date: Thu, 20 Jun 2024 09:40:53 -0700 Subject: [PATCH] Tag Lifecycle Events diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEventTypeProvider.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEventTypeProvider.java index 3b17f056d162dd42cae0d33cbbb22fece82fe525..a52ab9e09420768d89385c881fb7a4c424e5b8d6 100644 --- a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEventTypeProvider.java +++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEventTypeProvider.java @@ -19,4 +19,6 @@ interface LifecycleEventTypeProvider { LifecycleEventType.Monitorable monitor(String name, Class ownerType); LifecycleEventType.Prioritizable prioritized(String name, Class ownerType); + + TagEventTypeProvider tagProvider(); } 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 dae4935d8e9f46c52f700927a43a9202dac13df6..720fe2546015838708ce794c291ca187cf7bca9c 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 @@ -24,6 +24,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/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/TagEventTypeProvider.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/TagEventTypeProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..61c5f6cc6986193eab596ee55e1a414afcf4fbd1 --- /dev/null +++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/TagEventTypeProvider.java @@ -0,0 +1,40 @@ +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.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.ApiStatus; + +/** + * Provides event types for tag registration. + * + * @see PreFlattenTagRegistrar + * @see PostFlattenTagRegistrar + */ +@ApiStatus.Experimental +@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.@NonNull Prioritizable>> preFlatten(@NonNull 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.@NonNull Prioritizable>> postFlatten(@NonNull RegistryKey registryKey); +} diff --git a/src/main/java/io/papermc/paper/tag/PostFlattenTagRegistrar.java b/src/main/java/io/papermc/paper/tag/PostFlattenTagRegistrar.java new file mode 100644 index 0000000000000000000000000000000000000000..32d79dac18c402c3d95edb642eef1563eddf5e9e --- /dev/null +++ b/src/main/java/io/papermc/paper/tag/PostFlattenTagRegistrar.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.TypedKey; +import io.papermc.paper.registry.tag.TagKey; +import java.util.Collection; +import java.util.Map; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Unmodifiable; + +/** + * 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(@NotNull 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 +@ApiStatus.NonExtendable +public interface PostFlattenTagRegistrar extends Registrar { + + /** + * Get the registry key for this tag registrar. + * + * @return the registry key + */ + @NonNull 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 @NonNull 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(@NonNull 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 @NonNull Collection> getTag(@NonNull 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(@NonNull TagKey tagKey, @NonNull 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(@NonNull TagKey tagKey, @NonNull Collection> values); +} diff --git a/src/main/java/io/papermc/paper/tag/PreFlattenTagRegistrar.java b/src/main/java/io/papermc/paper/tag/PreFlattenTagRegistrar.java new file mode 100644 index 0000000000000000000000000000000000000000..44f7a9d3c2734fb00d11890ece9dc4ce2fafa796 --- /dev/null +++ b/src/main/java/io/papermc/paper/tag/PreFlattenTagRegistrar.java @@ -0,0 +1,102 @@ +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.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Unmodifiable; + +/** + * 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(@NotNull 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 +@ApiStatus.NonExtendable +public interface PreFlattenTagRegistrar extends Registrar { + + /** + * Get the registry key for this tag registrar. + * + * @return the registry key + */ + @NonNull 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 @NonNull 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(@NonNull 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 @NonNull List> getTag(@NonNull 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(@NonNull TagKey tagKey, @NonNull 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(@NonNull TagKey tagKey, @NonNull Collection> entries); +} diff --git a/src/main/java/io/papermc/paper/tag/TagEntry.java b/src/main/java/io/papermc/paper/tag/TagEntry.java new file mode 100644 index 0000000000000000000000000000000000000000..436b701f1231354a78a9e0ab04ffd9097343ecfc --- /dev/null +++ b/src/main/java/io/papermc/paper/tag/TagEntry.java @@ -0,0 +1,89 @@ +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.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; + +/** + * 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 +@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 @NonNull TagEntry valueEntry(final @NonNull 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 @NonNull TagEntry valueEntry(final @NonNull 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 @NonNull TagEntry tagEntry(final @NonNull 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 @NonNull TagEntry tagEntry(final @NonNull 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/src/main/java/io/papermc/paper/tag/TagEntryImpl.java b/src/main/java/io/papermc/paper/tag/TagEntryImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..c0ffd2b9f814d92d0d9b370dfa7e352f1241f614 --- /dev/null +++ b/src/main/java/io/papermc/paper/tag/TagEntryImpl.java @@ -0,0 +1,8 @@ +package io.papermc.paper.tag; + +import net.kyori.adventure.key.Key; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +record TagEntryImpl(Key key, boolean isTag, boolean isRequired) implements TagEntry { +}