aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--patches/api/0477-Introduce-registry-entry-and-builders.patch426
-rw-r--r--patches/server/1022-Add-registry-entry-and-builders.patch459
-rw-r--r--test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java5
-rw-r--r--test-plugin/src/main/java/io/papermc/testplugin/TestPluginBootstrap.java48
4 files changed, 938 insertions, 0 deletions
diff --git a/patches/api/0477-Introduce-registry-entry-and-builders.patch b/patches/api/0477-Introduce-registry-entry-and-builders.patch
new file mode 100644
index 0000000000..8ca07bc9d6
--- /dev/null
+++ b/patches/api/0477-Introduce-registry-entry-and-builders.patch
@@ -0,0 +1,426 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Bjarne Koll <[email protected]>
+Date: Thu, 13 Jun 2024 22:35:05 +0200
+Subject: [PATCH] Introduce registry entry and builders
+
+
+diff --git a/src/main/java/io/papermc/paper/registry/data/EnchantmentRegistryEntry.java b/src/main/java/io/papermc/paper/registry/data/EnchantmentRegistryEntry.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..9d8002cc58fbd9145ea4fb22482408d0d1a25d2a
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/data/EnchantmentRegistryEntry.java
+@@ -0,0 +1,294 @@
++package io.papermc.paper.registry.data;
++
++import io.papermc.paper.registry.RegistryBuilder;
++import io.papermc.paper.registry.set.RegistryKeySet;
++import io.papermc.paper.registry.set.RegistrySet;
++import io.papermc.paper.registry.tag.TagKey;
++import java.util.List;
++import net.kyori.adventure.text.Component;
++import org.bukkit.enchantments.Enchantment;
++import org.bukkit.inventory.EquipmentSlotGroup;
++import org.bukkit.inventory.ItemType;
++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.NotNull;
++import org.jetbrains.annotations.Range;
++import org.jetbrains.annotations.Unmodifiable;
++
++/**
++ * A data-centric version-specific registry entry for the {@link Enchantment} type.
++ */
++public interface EnchantmentRegistryEntry {
++
++ /**
++ * Provides the description of this enchantment entry as displayed to the client, e.g. "Sharpness" for the sharpness
++ * enchantment.
++ *
++ * @return the description component.
++ */
++ @NonNull Component description();
++
++ /**
++ * Provides the tag key representing the items this enchantment is supported on.
++ *
++ * @return the tag key.
++ */
++ @NonNull RegistryKeySet<ItemType> supportedItems();
++
++ /**
++ * Provides the tag key representing the item types this enchantment can naturally be applied to when enchanting in
++ * an enchantment table.
++ *
++ * @return the tag key.
++ */
++ @Nullable RegistryKeySet<ItemType> primaryItems();
++
++ /**
++ * Provides the weight of this enchantment used by the weighted random when selecting enchantments.
++ *
++ * @return the weight value.
++ */
++ @Range(from = 1, to = 1024) int weight();
++
++ /**
++ * Provides the maximum level this enchantment can have when applied.
++ *
++ * @return the maximum level.
++ */
++ @Range(from = 1, to = 255) int maxLevel();
++
++ /**
++ * Provides the minimum cost to enchant an item with this enchantment.
++ *
++ * @return the enchantment cost.
++ */
++ @NonNull EnchantmentCost minimumCost();
++
++ /**
++ * Provides the maximum cost to enchant an item with this enchantment.
++ *
++ * @return the enchantment cost.
++ */
++ @NonNull EnchantmentCost maximumCost();
++
++ /**
++ * Gets cost of applying this enchantment using an anvil.
++ * <p>
++ * Note that this is halved when using an enchantment book, and is multiplied by the level of the enchantment.
++ * See <a href="https://minecraft.wiki/w/Anvil_mechanics">https://minecraft.wiki/w/Anvil_mechanics</a> for more information.
++ * </p>
++ *
++ * @return The anvil cost of this enchantment
++ */
++ @Range(from = 0, to = Integer.MAX_VALUE) int anvilCost();
++
++ /**
++ * Provides a list of slot groups this enchantment may be active in.
++ * If the item enchanted with this enchantment is equipped in a slot not covered by the returned list and its groups,
++ * the enchantments effects, like attribute modiifers, will not activate.
++ *
++ * @return a list of equipment slot groups.
++ * @see Enchantment#getActiveSlots()
++ */
++ @NonNull @Unmodifiable List<EquipmentSlotGroup> activeSlots();
++
++ /**
++ * Provides the registry set of enchantments that this enchantment is exclusive with.
++ * This enchantment may not be applied on items that are already enchanted with enchantments found in the returned
++ * set.
++ *
++ * @return a registry set of enchantments exclusive to this one.
++ */
++ @NonNull RegistryKeySet<Enchantment> exclusiveWith();
++
++ /**
++ * A mutable builder for the {@link GameEventRegistryEntry} plugins may change in applicable registry events.
++ * <p>
++ * The following values are required for each builder:
++ * <ul>
++ * <li>{@link #description(Component)}</li>
++ * <li>{@link #supportedItems(RegistryKeySet)}</li>
++ * <li>{@link #weight(int)}</li>
++ * <li>{@link #maxLevel(int)}</li>
++ * <li>{@link #minimumCost(EnchantmentCost)}</li>
++ * <li>{@link #maximumCost(EnchantmentCost)}</li>
++ * <li>{@link #anvilCost(int)}</li>
++ * <li>{@link #activeSlots(Iterable)}</li>
++ * </ul>
++ */
++ @ApiStatus.Experimental
++ @ApiStatus.NonExtendable
++ interface Builder extends EnchantmentRegistryEntry, RegistryBuilder<Enchantment> {
++
++ /**
++ * Configures the description of the enchantment registry entry that is to be displayed to the client.
++ *
++ * @param description the description component.
++ * @return this builder.
++ */
++ @Contract(value = "_ -> this", mutates = "this")
++ @NonNull Builder description(@NonNull Component description);
++
++ /**
++ * Configures the set of supported items this enchantment can be applied on. This
++ * can be a {@link RegistryKeySet} created via {@link RegistrySet#keySet(io.papermc.paper.registry.RegistryKey, Iterable)} or
++ * a tag obtained via {@link io.papermc.paper.registry.event.RegistryFreezeEvent#getOrCreateTag(TagKey)} with
++ * tag keys found in {@link io.papermc.paper.registry.keys.tags.ItemTypeTagKeys} such as
++ * {@link io.papermc.paper.registry.keys.tags.ItemTypeTagKeys#ENCHANTABLE_ARMOR} and
++ * {@link io.papermc.paper.registry.keys.tags.ItemTypeTagKeys#ENCHANTABLE_SWORD}.
++ *
++ * @param supportedItems the tag key representing the supported items.
++ * @return this builder.
++ */
++ @Contract(value = "_ -> this", mutates = "this")
++ @NonNull Builder supportedItems(@NonNull RegistryKeySet<ItemType> supportedItems);
++
++ /**
++ * Configures a set of item types this enchantment can naturally be applied to, when enchanting in an
++ * enchantment table.This can be a {@link RegistryKeySet} created via
++ * {@link RegistrySet#keySet(io.papermc.paper.registry.RegistryKey, Iterable)} or a tag obtained via
++ * {@link io.papermc.paper.registry.event.RegistryFreezeEvent#getOrCreateTag(TagKey)} with
++ * tag keys found in {@link io.papermc.paper.registry.keys.tags.ItemTypeTagKeys} such as
++ * {@link io.papermc.paper.registry.keys.tags.ItemTypeTagKeys#ENCHANTABLE_ARMOR} and
++ * {@link io.papermc.paper.registry.keys.tags.ItemTypeTagKeys#ENCHANTABLE_SWORD}.
++ * <p>
++ * Defaults to {@code null} which means all {@link #supportedItems()} are considered primary items.
++ *
++ * @param primaryItems the tag key representing the primary items.
++ * @return this builder.
++ */
++ @Contract(value = "_ -> this", mutates = "this")
++ @NonNull Builder primaryItems(@Nullable RegistryKeySet<ItemType> primaryItems);
++
++ /**
++ * Configures the weight of this enchantment used by the weighted random when selecting enchantments.
++ *
++ * @param weight the weight value.
++ * @return this builder.
++ */
++ @Contract(value = "_ -> this", mutates = "this")
++ @NonNull Builder weight(@Range(from = 1, to = 1024) int weight);
++
++ /**
++ * Configures the maximum level this enchantment can have when applied.
++ *
++ * @param maxLevel the maximum level.
++ * @return this builder.
++ */
++ @Contract(value = "_ -> this", mutates = "this")
++ @NonNull Builder maxLevel(@Range(from = 1, to = 255) int maxLevel);
++
++ /**
++ * Configures the minimum cost to enchant an item with this enchantment.
++ *
++ * @param minimumCost the enchantment cost.
++ * @return this builder.
++ */
++ @Contract(value = "_ -> this", mutates = "this")
++ @NonNull Builder minimumCost(@NotNull EnchantmentCost minimumCost);
++
++ /**
++ * Configures the maximum cost to enchant an item with this enchantment.
++ *
++ * @param maximumCost the enchantment cost.
++ * @return this builder.
++ */
++ @Contract(value = "_ -> this", mutates = "this")
++ @NonNull Builder maximumCost(@NotNull EnchantmentCost maximumCost);
++
++ /**
++ * Configures the cost of applying this enchantment using an anvil.
++ * <p>
++ * Note that this is halved when using an enchantment book, and is multiplied by the level of the enchantment.
++ * See <a href="https://minecraft.wiki/w/Anvil_mechanics">https://minecraft.wiki/w/Anvil_mechanics</a> for more information.
++ * </p>
++ *
++ * @param anvilCost The anvil cost of this enchantment
++ * @return this builder.
++ */
++ @Contract(value = "_ -> this", mutates = "this")
++ @NonNull Builder anvilCost(@Range(from = 0, to = Integer.MAX_VALUE) int anvilCost);
++
++ /**
++ * Configures the list of slot groups this enchantment may be active in.
++ * <p>
++ * If the item enchanted with this enchantment is equipped in a slot not covered by the returned list and its groups,
++ * the enchantment's effects, like attribute modifiers, will not activate.
++ *
++ * @param activeSlots a list of equipment slot groups.
++ * @return this builder.
++ * @see Enchantment#getActiveSlotGroups()
++ */
++ @Contract(value = "_ -> this", mutates = "this")
++ default @NonNull Builder activeSlots(final @NonNull EquipmentSlotGroup @NonNull... activeSlots) {
++ return this.activeSlots(List.of(activeSlots));
++ }
++
++ /**
++ * Configures the list of slot groups this enchantment may be active in.
++ * <p>
++ * If the item enchanted with this enchantment is equipped in a slot not covered by the returned list and its groups,
++ * the enchantment's effects, like attribute modifiers, will not activate.
++ *
++ * @param activeSlots a list of equipment slot groups.
++ * @return this builder.
++ * @see Enchantment#getActiveSlots()
++ */
++ @Contract(value = "_ -> this", mutates = "this")
++ @NonNull Builder activeSlots(@NonNull Iterable<@NonNull EquipmentSlotGroup> activeSlots);
++
++ /**
++ * Configures the registry set of enchantments that this enchantment is exclusive with.
++ * <p>
++ * This enchantment may not be applied on items that are already enchanted with enchantments found in the returned
++ * set.
++ * <p>
++ * Defaults to an empty set allowing this enchantment to be applied regardless of other enchantments.
++ *
++ * @param exclusiveWith a registry set of enchantments exclusive to this one.
++ * @return this builder.
++ */
++ @Contract(value = "_ -> this", mutates = "this")
++ @NonNull Builder exclusiveWith(@NonNull RegistryKeySet<Enchantment> exclusiveWith);
++ }
++
++ /**
++ * The enchantment cost interface represents the cost of applying an enchantment, split up into its different components.
++ */
++ interface EnchantmentCost {
++ /**
++ * Returns the base cost of this enchantment cost, no matter what level the enchantment has.
++ *
++ * @return the cost in levels.
++ */
++ int baseCost();
++
++ /**
++ * Returns the additional cost added per level of the enchantment to be applied.
++ * This cost is applied per level above the first.
++ *
++ * @return the cost added to the {@link #baseCost()} for each level above the first.
++ */
++ int additionalPerLevelCost();
++
++ /**
++ * Creates a new enchantment cost instance based on the passed values.
++ *
++ * @param baseCost the base cost of the enchantment cost as returned by {@link #baseCost()}
++ * @param additionalPerLevelCost the additional cost per level, as returned by {@link #additionalPerLevelCost()}
++ * @return the created instance.
++ */
++ @Contract(pure = true)
++ static EnchantmentCost of(final int baseCost, final int additionalPerLevelCost) {
++ record Impl(int baseCost, int additionalPerLevelCost) implements EnchantmentCost {
++ }
++
++ return new Impl(baseCost, additionalPerLevelCost);
++ }
++ }
++
++}
+diff --git a/src/main/java/io/papermc/paper/registry/data/GameEventRegistryEntry.java b/src/main/java/io/papermc/paper/registry/data/GameEventRegistryEntry.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..27b4da08168c7b341b776635011ff4022a12d361
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/data/GameEventRegistryEntry.java
+@@ -0,0 +1,43 @@
++package io.papermc.paper.registry.data;
++
++import io.papermc.paper.registry.RegistryBuilder;
++import org.bukkit.GameEvent;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.jetbrains.annotations.Contract;
++import org.jetbrains.annotations.Range;
++
++/**
++ * A data-centric version-specific registry entry for the {@link GameEvent} type.
++ */
++public interface GameEventRegistryEntry {
++
++ /**
++ * Provides the range in which this game event will notify its listeners.
++ *
++ * @return the range of blocks, represented as an int.
++ * @see GameEvent#getRange()
++ */
++ @Range(from = 0, to = Integer.MAX_VALUE) int range();
++
++ /**
++ * A mutable builder for the {@link GameEventRegistryEntry} plugins may change in applicable registry events.
++ * <p>
++ * The following values are required for each builder:
++ * <ul>
++ * <li>{@link #range(int)}</li>
++ * </ul>
++ */
++ interface Builder extends GameEventRegistryEntry, RegistryBuilder<GameEvent> {
++
++ /**
++ * Sets the range in which this game event should notify its listeners.
++ *
++ * @param range the range of blocks.
++ * @return this builder instance.
++ * @see GameEventRegistryEntry#range()
++ * @see GameEvent#getRange()
++ */
++ @Contract(value = "_ -> this", mutates = "this")
++ @NonNull Builder range(@Range(from = 0, to = Integer.MAX_VALUE) int range);
++ }
++}
+diff --git a/src/main/java/io/papermc/paper/registry/data/package-info.java b/src/main/java/io/papermc/paper/registry/data/package-info.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..efb0429cb25700ff7ad88b6d7de3d154ec235a91
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/data/package-info.java
+@@ -0,0 +1,9 @@
++/**
++ * Collection of registry entry types that may be created or modified via the
++ * {@link io.papermc.paper.registry.event.RegistryEvent}.
++ * <p>
++ * A registry entry represents its runtime API counterpart in a version-specific data-focused type.
++ * Registry entries are not expected to be used during plugin runtime interactions with the API but are mostly
++ * exposed during registry creation/modification.
++ */
++package io.papermc.paper.registry.entry;
+diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryEvents.java b/src/main/java/io/papermc/paper/registry/event/RegistryEvents.java
+index 1f89945be2ed68f52a544f41f7a151b8fdfe113e..b32ae215e976bcfcdd86b03037de61b3d896f57c 100644
+--- a/src/main/java/io/papermc/paper/registry/event/RegistryEvents.java
++++ b/src/main/java/io/papermc/paper/registry/event/RegistryEvents.java
+@@ -1,7 +1,14 @@
+ package io.papermc.paper.registry.event;
+
++import io.papermc.paper.registry.RegistryKey;
++import io.papermc.paper.registry.data.EnchantmentRegistryEntry;
++import io.papermc.paper.registry.data.GameEventRegistryEntry;
++import org.bukkit.GameEvent;
++import org.bukkit.enchantments.Enchantment;
+ import org.jetbrains.annotations.ApiStatus;
+
++import static io.papermc.paper.registry.event.RegistryEventProviderImpl.create;
++
+ /**
+ * Holds providers for {@link RegistryEntryAddEvent} and {@link RegistryFreezeEvent}
+ * handlers for each applicable registry.
+@@ -9,6 +16,9 @@ import org.jetbrains.annotations.ApiStatus;
+ @ApiStatus.Experimental
+ public final class RegistryEvents {
+
++ public static final RegistryEventProvider<GameEvent, GameEventRegistryEntry.Builder> GAME_EVENT = create(RegistryKey.GAME_EVENT);
++ public static final RegistryEventProvider<Enchantment, EnchantmentRegistryEntry.Builder> ENCHANTMENT = create(RegistryKey.ENCHANTMENT);
++
+ private RegistryEvents() {
+ }
+ }
+diff --git a/src/main/java/org/bukkit/GameEvent.java b/src/main/java/org/bukkit/GameEvent.java
+index 6c9689baca1763e2ef79495d38618d587e792434..4583092c2d1ffe95be2831c5d5f0e904283ab762 100644
+--- a/src/main/java/org/bukkit/GameEvent.java
++++ b/src/main/java/org/bukkit/GameEvent.java
+@@ -147,4 +147,22 @@ public abstract class GameEvent implements Keyed {
+
+ return gameEvent;
+ }
++ // Paper start
++ /**
++ * Gets the range of the event which is used to
++ * notify listeners of the event.
++ *
++ * @return the range
++ */
++ public abstract int getRange();
++
++ /**
++ * Gets the vibration level of the game event for vibration listeners.
++ * Not all events have vibration levels, and a level of 0 means
++ * it won't cause any vibrations.
++ *
++ * @return the vibration level
++ */
++ public abstract int getVibrationLevel();
++ // Paper end
+ }
diff --git a/patches/server/1022-Add-registry-entry-and-builders.patch b/patches/server/1022-Add-registry-entry-and-builders.patch
new file mode 100644
index 0000000000..4bcae50b06
--- /dev/null
+++ b/patches/server/1022-Add-registry-entry-and-builders.patch
@@ -0,0 +1,459 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Bjarne Koll <[email protected]>
+Date: Thu, 13 Jun 2024 23:45:32 +0200
+Subject: [PATCH] Add registry entry and builders
+
+
+diff --git a/src/main/java/io/papermc/paper/registry/PaperRegistries.java b/src/main/java/io/papermc/paper/registry/PaperRegistries.java
+index a688af29273ebfbb4f75dd74cd30627fc481c96c..9c0972023bc9be20ba81bbc2e1d6688b615760c0 100644
+--- a/src/main/java/io/papermc/paper/registry/PaperRegistries.java
++++ b/src/main/java/io/papermc/paper/registry/PaperRegistries.java
+@@ -1,6 +1,8 @@
+ package io.papermc.paper.registry;
+
+ import io.papermc.paper.adventure.PaperAdventure;
++import io.papermc.paper.registry.data.PaperEnchantmentRegistryEntry;
++import io.papermc.paper.registry.data.PaperGameEventRegistryEntry;
+ import io.papermc.paper.registry.entry.RegistryEntry;
+ import io.papermc.paper.registry.tag.TagKey;
+ import java.util.Collections;
+@@ -58,7 +60,7 @@ public final class PaperRegistries {
+ static {
+ REGISTRY_ENTRIES = List.of(
+ // built-ins
+- entry(Registries.GAME_EVENT, RegistryKey.GAME_EVENT, GameEvent.class, CraftGameEvent::new),
++ writable(Registries.GAME_EVENT, RegistryKey.GAME_EVENT, GameEvent.class, CraftGameEvent::new, PaperGameEventRegistryEntry.PaperBuilder::new),
+ entry(Registries.INSTRUMENT, RegistryKey.INSTRUMENT, MusicInstrument.class, CraftMusicInstrument::new),
+ entry(Registries.MOB_EFFECT, RegistryKey.MOB_EFFECT, PotionEffectType.class, CraftPotionEffectType::new),
+ entry(Registries.STRUCTURE_TYPE, RegistryKey.STRUCTURE_TYPE, StructureType.class, CraftStructureType::new),
+@@ -71,7 +73,7 @@ public final class PaperRegistries {
+ entry(Registries.TRIM_PATTERN, RegistryKey.TRIM_PATTERN, TrimPattern.class, CraftTrimPattern::new).delayed(),
+ entry(Registries.DAMAGE_TYPE, RegistryKey.DAMAGE_TYPE, DamageType.class, CraftDamageType::new).delayed(),
+ entry(Registries.WOLF_VARIANT, RegistryKey.WOLF_VARIANT, Wolf.Variant.class, CraftWolf.CraftVariant::new).delayed(),
+- entry(Registries.ENCHANTMENT, RegistryKey.ENCHANTMENT, Enchantment.class, CraftEnchantment::new).withSerializationUpdater(FieldRename.ENCHANTMENT_RENAME).delayed(),
++ writable(Registries.ENCHANTMENT, RegistryKey.ENCHANTMENT, Enchantment.class, CraftEnchantment::new, PaperEnchantmentRegistryEntry.PaperBuilder::new).withSerializationUpdater(FieldRename.ENCHANTMENT_RENAME).delayed(),
+ entry(Registries.JUKEBOX_SONG, RegistryKey.JUKEBOX_SONG, JukeboxSong.class, CraftJukeboxSong::new).delayed(),
+
+ // api-only
+diff --git a/src/main/java/io/papermc/paper/registry/data/PaperEnchantmentRegistryEntry.java b/src/main/java/io/papermc/paper/registry/data/PaperEnchantmentRegistryEntry.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..8587d9e6167f70894960dd205ca28fbf9340eb92
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/data/PaperEnchantmentRegistryEntry.java
+@@ -0,0 +1,231 @@
++package io.papermc.paper.registry.data;
++
++import com.google.common.base.Preconditions;
++import com.google.common.collect.Iterables;
++import com.google.common.collect.Lists;
++import io.papermc.paper.registry.PaperRegistryBuilder;
++import io.papermc.paper.registry.RegistryKey;
++import io.papermc.paper.registry.TypedKey;
++import io.papermc.paper.registry.data.util.Conversions;
++import io.papermc.paper.registry.set.PaperRegistrySets;
++import io.papermc.paper.registry.set.RegistryKeySet;
++import java.util.Collections;
++import java.util.List;
++import java.util.Optional;
++import java.util.OptionalInt;
++import net.minecraft.core.HolderSet;
++import net.minecraft.core.component.DataComponentMap;
++import net.minecraft.core.registries.Registries;
++import net.minecraft.network.chat.Component;
++import net.minecraft.world.entity.EquipmentSlotGroup;
++import net.minecraft.world.item.Item;
++import net.minecraft.world.item.enchantment.Enchantment;
++import org.bukkit.craftbukkit.CraftEquipmentSlot;
++import org.bukkit.inventory.ItemType;
++import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.checker.nullness.qual.Nullable;
++import org.checkerframework.framework.qual.DefaultQualifier;
++import org.jetbrains.annotations.Range;
++
++import static io.papermc.paper.registry.data.util.Checks.asConfigured;
++
++@DefaultQualifier(NonNull.class)
++public class PaperEnchantmentRegistryEntry implements EnchantmentRegistryEntry {
++
++ // Top level
++ protected @MonotonicNonNull Component description;
++
++ // Definition
++ protected @MonotonicNonNull HolderSet<Item> supportedItems;
++ protected @Nullable HolderSet<Item> primaryItems;
++ protected OptionalInt weight = OptionalInt.empty();
++ protected OptionalInt maxLevel = OptionalInt.empty();
++ protected Enchantment.@MonotonicNonNull Cost minimumCost;
++ protected Enchantment.@MonotonicNonNull Cost maximumCost;
++ protected OptionalInt anvilCost = OptionalInt.empty();
++ protected @MonotonicNonNull List<EquipmentSlotGroup> activeSlots;
++
++ // Exclusive
++ protected HolderSet<Enchantment> exclusiveWith = HolderSet.empty();
++
++ protected final DataComponentMap effects;
++
++ protected final Conversions conversions;
++
++ public PaperEnchantmentRegistryEntry(
++ final @Nullable Conversions conversions,
++ final TypedKey<org.bukkit.enchantments.Enchantment> ignoredKey,
++ final @Nullable Enchantment internal
++ ) {
++ Preconditions.checkState(conversions != null);
++ this.conversions = conversions;
++ if (internal == null) {
++ this.effects = DataComponentMap.EMPTY;
++ return;
++ }
++
++ // top level
++ this.description = internal.description();
++
++ // definition
++ final Enchantment.EnchantmentDefinition definition = internal.definition();
++ this.supportedItems = definition.supportedItems();
++ this.primaryItems = definition.primaryItems().orElse(null);
++ this.weight = OptionalInt.of(definition.weight());
++ this.maxLevel = OptionalInt.of(definition.maxLevel());
++ this.minimumCost = definition.minCost();
++ this.maximumCost = definition.maxCost();
++ this.anvilCost = OptionalInt.of(definition.anvilCost());
++ this.activeSlots = definition.slots();
++
++ // exclusive
++ this.exclusiveWith = internal.exclusiveSet();
++
++ // effects
++ this.effects = internal.effects();
++ }
++
++ @Override
++ public net.kyori.adventure.text.Component description() {
++ return this.conversions.asAdventure(asConfigured(this.description, "description"));
++ }
++
++ @Override
++ public RegistryKeySet<ItemType> supportedItems() {
++ return PaperRegistrySets.convertToApi(RegistryKey.ITEM, asConfigured(this.supportedItems, "supportedItems"));
++ }
++
++ @Override
++ public @Nullable RegistryKeySet<ItemType> primaryItems() {
++ return this.primaryItems == null ? null : PaperRegistrySets.convertToApi(RegistryKey.ITEM, this.primaryItems);
++ }
++
++ @Override
++ public @Range(from = 1, to = 1024) int weight() {
++ return asConfigured(this.weight, "weight");
++ }
++
++ @Override
++ public @Range(from = 1, to = 255) int maxLevel() {
++ return asConfigured(this.maxLevel, "maxLevel");
++ }
++
++ @Override
++ public EnchantmentCost minimumCost() {
++ final Enchantment.@MonotonicNonNull Cost cost = asConfigured(this.minimumCost, "minimumCost");
++ return EnchantmentRegistryEntry.EnchantmentCost.of(cost.base(), cost.perLevelAboveFirst());
++ }
++
++ @Override
++ public EnchantmentCost maximumCost() {
++ final Enchantment.@MonotonicNonNull Cost cost = asConfigured(this.maximumCost, "maximumCost");
++ return EnchantmentRegistryEntry.EnchantmentCost.of(cost.base(), cost.perLevelAboveFirst());
++ }
++
++ @Override
++ public @Range(from = 0, to = Integer.MAX_VALUE) int anvilCost() {
++ return asConfigured(this.anvilCost, "anvilCost");
++ }
++
++ @Override
++ public List<org.bukkit.inventory.EquipmentSlotGroup> activeSlots() {
++ return Collections.unmodifiableList(Lists.transform(this.activeSlots, CraftEquipmentSlot::getSlot));
++ }
++
++ @Override
++ public RegistryKeySet<org.bukkit.enchantments.Enchantment> exclusiveWith() {
++ return PaperRegistrySets.convertToApi(RegistryKey.ENCHANTMENT, this.exclusiveWith);
++ }
++
++ public static final class PaperBuilder extends PaperEnchantmentRegistryEntry implements EnchantmentRegistryEntry.Builder,
++ PaperRegistryBuilder<Enchantment, org.bukkit.enchantments.Enchantment> {
++
++ public PaperBuilder(final @Nullable Conversions conversions, final TypedKey<org.bukkit.enchantments.Enchantment> key, final @Nullable Enchantment internal) {
++ super(conversions, key, internal);
++ }
++
++ @Override
++ public Builder description(final net.kyori.adventure.text.Component description) {
++ this.description = this.conversions.asVanilla(description);
++ return this;
++ }
++
++ @Override
++ public Builder supportedItems(final RegistryKeySet<ItemType> supportedItems) {
++ this.supportedItems = PaperRegistrySets.convertToNms(Registries.ITEM, this.conversions.lookup(), supportedItems);
++ return this;
++ }
++
++ @Override
++ public Builder primaryItems(final @Nullable RegistryKeySet<ItemType> primaryItems) {
++ this.primaryItems = primaryItems == null ? null : PaperRegistrySets.convertToNms(Registries.ITEM, this.conversions.lookup(), primaryItems);
++ return this;
++ }
++
++ @Override
++ public Builder weight(final @Range(from = 1, to = 1024) int weight) {
++ Preconditions.checkArgument(weight >= 1 && weight <= 1024, "weight must be between 1 and 1024 (inclusive)");
++ this.weight = OptionalInt.of(weight);
++ return this;
++ }
++
++ @Override
++ public Builder maxLevel(final @Range(from = 1, to = 255) int maxLevel) {
++ Preconditions.checkArgument(maxLevel >= 1 && maxLevel <= 255, "maxLevel must be between 1 and 255 (inclusive)");
++ this.maxLevel = OptionalInt.of(maxLevel);
++ return this;
++ }
++
++ @Override
++ public Builder minimumCost(final EnchantmentCost minimumCost) {
++ this.minimumCost = Enchantment.dynamicCost(minimumCost.baseCost(), minimumCost.additionalPerLevelCost());
++ return this;
++ }
++
++ @Override
++ public Builder maximumCost(final EnchantmentCost maximumCost) {
++ this.maximumCost = Enchantment.dynamicCost(maximumCost.baseCost(), maximumCost.additionalPerLevelCost());
++ return this;
++ }
++
++ @Override
++ public Builder anvilCost(final @Range(from = 0, to = Integer.MAX_VALUE) int anvilCost) {
++ Preconditions.checkArgument(anvilCost >= 0, "anvilCost must be non-negative");
++ this.anvilCost = OptionalInt.of(anvilCost);
++ return this;
++ }
++
++ @Override
++ public Builder activeSlots(final Iterable<org.bukkit.inventory.EquipmentSlotGroup> activeSlots) {
++ this.activeSlots = Lists.newArrayList(Iterables.transform(activeSlots, CraftEquipmentSlot::getNMSGroup));
++ return this;
++ }
++
++ @Override
++ public Builder exclusiveWith(final RegistryKeySet<org.bukkit.enchantments.Enchantment> exclusiveWith) {
++ this.exclusiveWith = PaperRegistrySets.convertToNms(Registries.ENCHANTMENT, this.conversions.lookup(), exclusiveWith);
++ return this;
++ }
++
++ @Override
++ public Enchantment build() {
++ final Enchantment.EnchantmentDefinition def = new Enchantment.EnchantmentDefinition(
++ asConfigured(this.supportedItems, "supportedItems"),
++ Optional.ofNullable(this.primaryItems),
++ this.weight(),
++ this.maxLevel(),
++ asConfigured(this.minimumCost, "minimumCost"),
++ asConfigured(this.maximumCost, "maximumCost"),
++ this.anvilCost(),
++ Collections.unmodifiableList(this.activeSlots)
++ );
++ return new Enchantment(
++ asConfigured(this.description, "description"),
++ def,
++ this.exclusiveWith,
++ this.effects
++ );
++ }
++ }
++}
+diff --git a/src/main/java/io/papermc/paper/registry/data/PaperGameEventRegistryEntry.java b/src/main/java/io/papermc/paper/registry/data/PaperGameEventRegistryEntry.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..5311a75dbf9f093a4647f95907eb11adf23af948
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/data/PaperGameEventRegistryEntry.java
+@@ -0,0 +1,57 @@
++package io.papermc.paper.registry.data;
++
++import com.google.common.base.Preconditions;
++import io.papermc.paper.registry.PaperRegistryBuilder;
++import io.papermc.paper.registry.data.util.Conversions;
++import java.util.OptionalInt;
++import net.minecraft.world.level.gameevent.GameEvent;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.checker.nullness.qual.Nullable;
++import org.checkerframework.framework.qual.DefaultQualifier;
++import org.jetbrains.annotations.Range;
++
++import static io.papermc.paper.registry.data.util.Checks.asConfigured;
++
++@DefaultQualifier(NonNull.class)
++public class PaperGameEventRegistryEntry implements GameEventRegistryEntry {
++
++ protected OptionalInt range = OptionalInt.empty();
++
++ public PaperGameEventRegistryEntry(
++ final @Nullable Conversions ignoredConversions,
++ final io.papermc.paper.registry.TypedKey<org.bukkit.GameEvent> ignoredKey,
++ final @Nullable GameEvent nms
++ ) {
++ if (nms == null) return;
++
++ this.range = OptionalInt.of(nms.notificationRadius());
++ }
++
++ @Override
++ public @Range(from = 0, to = Integer.MAX_VALUE) int range() {
++ return asConfigured(this.range, "range");
++ }
++
++ public static final class PaperBuilder extends PaperGameEventRegistryEntry implements GameEventRegistryEntry.Builder,
++ PaperRegistryBuilder<GameEvent, org.bukkit.GameEvent> {
++ public PaperBuilder(
++ final @Nullable Conversions conversions,
++ final io.papermc.paper.registry.TypedKey<org.bukkit.GameEvent> key,
++ final @Nullable GameEvent nms
++ ) {
++ super(conversions, key, nms);
++ }
++
++ @Override
++ public GameEventRegistryEntry.Builder range(final @Range(from = 0, to = Integer.MAX_VALUE) int range) {
++ Preconditions.checkArgument(range >= 0, "range must be non-negative");
++ this.range = OptionalInt.of(range);
++ return this;
++ }
++
++ @Override
++ public GameEvent build() {
++ return new GameEvent(this.range());
++ }
++ }
++}
+diff --git a/src/main/java/io/papermc/paper/registry/data/util/Checks.java b/src/main/java/io/papermc/paper/registry/data/util/Checks.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..f0f6c7779aa9ae38d8d616068b76698eb62f854b
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/data/util/Checks.java
+@@ -0,0 +1,27 @@
++package io.papermc.paper.registry.data.util;
++
++import java.util.OptionalInt;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.checker.nullness.qual.Nullable;
++import org.checkerframework.framework.qual.DefaultQualifier;
++
++@DefaultQualifier(NonNull.class)
++public final class Checks {
++
++ public static <T> T asConfigured(final @Nullable T value, final String field) {
++ if (value == null) {
++ throw new IllegalStateException(field + " has not been configured");
++ }
++ return value;
++ }
++
++ public static int asConfigured(final OptionalInt value, final String field) {
++ if (value.isEmpty()) {
++ throw new IllegalStateException(field + " has not been configured");
++ }
++ return value.getAsInt();
++ }
++
++ private Checks() {
++ }
++}
+diff --git a/src/main/java/net/minecraft/world/level/gameevent/GameEvent.java b/src/main/java/net/minecraft/world/level/gameevent/GameEvent.java
+index cc3c56224e64816b885c0131ce2a800a2efe3113..7acd9c71c5c4b487e792b8c36a8e52e10b691e98 100644
+--- a/src/main/java/net/minecraft/world/level/gameevent/GameEvent.java
++++ b/src/main/java/net/minecraft/world/level/gameevent/GameEvent.java
+@@ -85,7 +85,7 @@ public record GameEvent(int notificationRadius) {
+ }
+
+ private static Holder.Reference<GameEvent> register(String id, int range) {
+- return Registry.registerForHolder(BuiltInRegistries.GAME_EVENT, ResourceLocation.withDefaultNamespace(id), new GameEvent(range));
++ return io.papermc.paper.registry.PaperRegistryListenerManager.INSTANCE.registerForHolderWithListeners(BuiltInRegistries.GAME_EVENT, ResourceLocation.withDefaultNamespace(id), new GameEvent(range)); // Paper - run with listeners
+ }
+
+ public static record Context(@Nullable Entity sourceEntity, @Nullable BlockState affectedState) {
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftGameEvent.java b/src/main/java/org/bukkit/craftbukkit/CraftGameEvent.java
+index ac9b4328cd55a68664a3f71186bc9a7be7cd9658..ea9fe1f8b1a1685ea975eba0ca418a831006065a 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftGameEvent.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftGameEvent.java
+@@ -18,10 +18,12 @@ public class CraftGameEvent extends GameEvent implements Handleable<net.minecraf
+ }
+
+ private final NamespacedKey key;
++ private final net.minecraft.resources.ResourceKey<net.minecraft.world.level.gameevent.GameEvent> handleKey; // Paper
+ private final net.minecraft.world.level.gameevent.GameEvent handle;
+
+ public CraftGameEvent(NamespacedKey key, net.minecraft.world.level.gameevent.GameEvent handle) {
+ this.key = key;
++ this.handleKey = net.minecraft.resources.ResourceKey.create(net.minecraft.core.registries.Registries.GAME_EVENT, org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(key)); // Paper
+ this.handle = handle;
+ }
+
+@@ -30,6 +32,18 @@ public class CraftGameEvent extends GameEvent implements Handleable<net.minecraf
+ return this.handle;
+ }
+
++ // Paper start
++ @Override
++ public int getRange() {
++ return this.handle.notificationRadius();
++ }
++
++ @Override
++ public int getVibrationLevel() {
++ return net.minecraft.world.level.gameevent.vibrations.VibrationSystem.getGameEventFrequency(this.handleKey);
++ }
++ // Paper end
++
+ @NotNull
+ @Override
+ public NamespacedKey getKey() {
+diff --git a/src/test/java/io/papermc/paper/registry/RegistryBuilderTest.java b/src/test/java/io/papermc/paper/registry/RegistryBuilderTest.java
+index f27e5e0037b719b1fc10703f8d298d2326b00432..2b42726c034f6701c86120d400446f0d868d464b 100644
+--- a/src/test/java/io/papermc/paper/registry/RegistryBuilderTest.java
++++ b/src/test/java/io/papermc/paper/registry/RegistryBuilderTest.java
+@@ -1,27 +1,33 @@
+ package io.papermc.paper.registry;
+
++import io.papermc.paper.registry.data.PaperEnchantmentRegistryEntry;
++import io.papermc.paper.registry.data.PaperGameEventRegistryEntry;
+ import io.papermc.paper.registry.data.util.Conversions;
+ import java.util.List;
+ import java.util.Map;
+ import net.minecraft.core.Registry;
++import net.minecraft.core.registries.Registries;
+ import net.minecraft.resources.RegistryOps;
+ import net.minecraft.resources.ResourceKey;
++import net.minecraft.world.item.enchantment.Enchantment;
++import net.minecraft.world.level.gameevent.GameEvent;
+ import org.bukkit.support.AbstractTestingBase;
+-import org.junit.jupiter.api.Disabled;
+ import org.junit.jupiter.params.ParameterizedTest;
+ import org.junit.jupiter.params.provider.Arguments;
+ import org.junit.jupiter.params.provider.MethodSource;
+
+ import static org.junit.jupiter.api.Assertions.assertEquals;
++import static org.junit.jupiter.params.provider.Arguments.arguments;
+
+ class RegistryBuilderTest extends AbstractTestingBase {
+
+ static List<Arguments> registries() {
+ return List.of(
++ arguments(Registries.ENCHANTMENT, (PaperRegistryBuilder.Filler<Enchantment, org.bukkit.enchantments.Enchantment, PaperEnchantmentRegistryEntry.PaperBuilder>) PaperEnchantmentRegistryEntry.PaperBuilder::new),
++ arguments(Registries.GAME_EVENT, (PaperRegistryBuilder.Filler<GameEvent, org.bukkit.GameEvent, PaperGameEventRegistryEntry.PaperBuilder>) PaperGameEventRegistryEntry.PaperBuilder::new)
+ );
+ }
+
+- @Disabled
+ @ParameterizedTest
+ @MethodSource("registries")
+ <M, T> void testEquality(final ResourceKey<? extends Registry<M>> resourceKey, final PaperRegistryBuilder.Filler<M, T, ?> filler) {
diff --git a/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java b/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java
index 671c37fa40..3403534a0f 100644
--- a/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java
+++ b/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java
@@ -1,5 +1,7 @@
package io.papermc.testplugin;
+import org.bukkit.GameEvent;
+import org.bukkit.Registry;
import org.bukkit.event.Listener;
import org.bukkit.plugin.java.JavaPlugin;
@@ -10,6 +12,9 @@ public final class TestPlugin extends JavaPlugin implements Listener {
this.getServer().getPluginManager().registerEvents(this, this);
// io.papermc.testplugin.brigtests.Registration.registerViaOnEnable(this);
+
+ final GameEvent gameEvent = Registry.GAME_EVENT.getOrThrow(TestPluginBootstrap.NEW_GAME_EVENT);
+ System.out.println("custom range: " + gameEvent.getRange());
}
}
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..80507334ba 100644
--- a/test-plugin/src/main/java/io/papermc/testplugin/TestPluginBootstrap.java
+++ b/test-plugin/src/main/java/io/papermc/testplugin/TestPluginBootstrap.java
@@ -2,13 +2,61 @@ package io.papermc.testplugin;
import io.papermc.paper.plugin.bootstrap.BootstrapContext;
import io.papermc.paper.plugin.bootstrap.PluginBootstrap;
+import io.papermc.paper.registry.RegistryKey;
+import io.papermc.paper.registry.TypedKey;
+import io.papermc.paper.registry.data.EnchantmentRegistryEntry;
+import io.papermc.paper.registry.event.RegistryEvents;
+import io.papermc.paper.registry.keys.EnchantmentKeys;
+import io.papermc.paper.registry.keys.GameEventKeys;
+import io.papermc.paper.registry.keys.tags.ItemTypeTagKeys;
+import io.papermc.paper.registry.set.RegistrySet;
+import io.papermc.paper.registry.tag.TagKey;
+import java.util.Collections;
+import java.util.List;
+import net.kyori.adventure.key.Key;
+import org.bukkit.GameEvent;
+import org.bukkit.enchantments.Enchantment;
+import org.bukkit.inventory.EquipmentSlotGroup;
import org.jetbrains.annotations.NotNull;
+import static net.kyori.adventure.text.Component.text;
+
public class TestPluginBootstrap implements PluginBootstrap {
+ static final TypedKey<GameEvent> NEW_GAME_EVENT = TypedKey.create(RegistryKey.GAME_EVENT, Key.key("mm:new_game_event"));
+ static final TypedKey<Enchantment> NEW_ENCHANT = TypedKey.create(RegistryKey.ENCHANTMENT, Key.key("machine:test"));
+
@Override
public void bootstrap(@NotNull BootstrapContext context) {
// io.papermc.testplugin.brigtests.Registration.registerViaBootstrap(context);
+
+ context.getLifecycleManager().registerEventHandler(RegistryEvents.ENCHANTMENT.freeze().newHandler(event -> {
+ event.registry().register(NEW_ENCHANT, builder -> {
+ builder.description(text("Epic Enchant!"))
+ .supportedItems(event.getOrCreateTag(ItemTypeTagKeys.SWORDS))
+ .maxLevel(10)
+ .weight(1024)
+ .activeSlots(List.of(EquipmentSlotGroup.ANY))
+ .anvilCost(1)
+ .minimumCost(EnchantmentRegistryEntry.EnchantmentCost.of(1, 1))
+ .maximumCost(EnchantmentRegistryEntry.EnchantmentCost.of(2, 2));
+ });
+ }));
+
+ context.getLifecycleManager().registerEventHandler(RegistryEvents.ENCHANTMENT.entryAdd().newHandler(event -> {
+ event.builder()
+ .description(text("Custom efficiency!"));
+ }).filter(EnchantmentKeys.EFFICIENCY));
+
+ context.getLifecycleManager().registerEventHandler(RegistryEvents.GAME_EVENT.freeze(), event -> {
+ event.registry().register(NEW_GAME_EVENT, builder -> {
+ builder.range(3);
+ });
+ });
+ context.getLifecycleManager().registerEventHandler(RegistryEvents.GAME_EVENT.entryAdd().newHandler(event -> {
+ event.builder().range(3);
+ event.getOrCreateTag(TagKey.create(RegistryKey.ITEM, Key.key("mm:tag")));
+ }).filter(GameEventKeys.CONTAINER_OPEN));
}
}