Add registry builders for SoundEvent and JukeboxSong

This commit is contained in:
Jake Potrebic 2024-12-23 22:09:31 -08:00
parent d0d0efee02
commit d182c26bcf
No known key found for this signature in database
GPG key ID: 27CC63F7CBC866C7
13 changed files with 408 additions and 1 deletions

View file

@ -0,0 +1,63 @@
package io.papermc.paper.registry.data;
import io.papermc.paper.registry.RegistryBuilder;
import io.papermc.paper.registry.TypedKey;
import io.papermc.paper.util.Either;
import java.util.function.Consumer;
import net.kyori.adventure.text.Component;
import org.bukkit.JukeboxSong;
import org.bukkit.Sound;
import org.checkerframework.checker.index.qual.Positive;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Range;
/**
* A data-centric version-specific registry entry for the {@link JukeboxSong} type.
*/
@ApiStatus.Experimental
@ApiStatus.NonExtendable
public interface JukeboxSongRegistryEntry {
Either<TypedKey<Sound>, SoundEventRegistryEntry> soundEvent();
Component description();
@Positive float lengthInSeconds();
@Range(from = 0, to = 15) int comparatorOutput();
/**
* A mutable builder for the {@link JukeboxSongRegistryEntry} plugins may change in applicable registry events.
* <p>
* The following values are required for each builder:
* <ul>
* <li>
* {@link #soundEvent(TypedKey)} or {@link #soundEvent(Consumer)}
* </li>
* <li>{@link #description(Component)}</li>
* <li>{@link #lengthInSeconds(float)}</li>
* <li>{@link #comparatorOutput(int)}</li>
* </ul>
*/
@ApiStatus.Experimental
@ApiStatus.NonExtendable
interface Builder extends JukeboxSongRegistryEntry, RegistryBuilder<JukeboxSong> {
@Contract(value = "_ -> this", mutates = "this")
Builder soundEvent(TypedKey<Sound> soundEvent);
@Contract(value = "_ -> this", mutates = "this")
Builder soundEvent(Consumer<? super SoundEventRegistryEntry.Builder> soundEvent);
@Contract(value = "_ -> this", mutates = "this")
Builder description(Component description);
@Contract(value = "_ -> this", mutates = "this")
Builder lengthInSeconds(@Positive float lengthInSeconds);
@Contract(value = "_ -> this", mutates = "this")
Builder comparatorOutput(@Range(from = 0, to = 15) int comparatorOutput);
}
}

View file

@ -0,0 +1,41 @@
package io.papermc.paper.registry.data;
import io.papermc.paper.registry.RegistryBuilder;
import net.kyori.adventure.key.Key;
import org.bukkit.Sound;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jspecify.annotations.Nullable;
/**
* A data-centric version-specific registry entry for the {@link Sound} type.
*/
@ApiStatus.Experimental
@ApiStatus.NonExtendable
public interface SoundEventRegistryEntry {
@Contract(pure = true)
Key location();
@Contract(pure = true)
@Nullable Float fixedRange();
/**
* A mutable builder for the {@link SoundEventRegistryEntry} plugins may change in applicable registry events.
* <p>
* The following values are required for each builder:
* <ul>
* <li>{@link #location(Key)}</li>
* </ul>
*/
@ApiStatus.Experimental
@ApiStatus.NonExtendable
interface Builder extends SoundEventRegistryEntry, RegistryBuilder<Sound> {
@Contract(value = "_ -> this", mutates = "this")
Builder location(Key location);
@Contract(value = "_ -> this", mutates = "this")
Builder fixedRange(@Nullable Float fixedRange);
}
}

View file

@ -3,9 +3,11 @@ package io.papermc.paper.registry.event;
import io.papermc.paper.registry.RegistryKey; import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.data.EnchantmentRegistryEntry; import io.papermc.paper.registry.data.EnchantmentRegistryEntry;
import io.papermc.paper.registry.data.GameEventRegistryEntry; import io.papermc.paper.registry.data.GameEventRegistryEntry;
import io.papermc.paper.registry.data.JukeboxSongRegistryEntry;
import io.papermc.paper.registry.data.PaintingVariantRegistryEntry; import io.papermc.paper.registry.data.PaintingVariantRegistryEntry;
import org.bukkit.Art; import org.bukkit.Art;
import org.bukkit.GameEvent; import org.bukkit.GameEvent;
import org.bukkit.JukeboxSong;
import org.bukkit.enchantments.Enchantment; import org.bukkit.enchantments.Enchantment;
import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.NullMarked;
@ -23,6 +25,7 @@ public final class RegistryEvents {
public static final RegistryEventProvider<GameEvent, GameEventRegistryEntry.Builder> GAME_EVENT = create(RegistryKey.GAME_EVENT); public static final RegistryEventProvider<GameEvent, GameEventRegistryEntry.Builder> GAME_EVENT = create(RegistryKey.GAME_EVENT);
public static final RegistryEventProvider<Enchantment, EnchantmentRegistryEntry.Builder> ENCHANTMENT = create(RegistryKey.ENCHANTMENT); public static final RegistryEventProvider<Enchantment, EnchantmentRegistryEntry.Builder> ENCHANTMENT = create(RegistryKey.ENCHANTMENT);
public static final RegistryEventProvider<Art, PaintingVariantRegistryEntry.Builder> PAINTING_VARIANT = create(RegistryKey.PAINTING_VARIANT); public static final RegistryEventProvider<Art, PaintingVariantRegistryEntry.Builder> PAINTING_VARIANT = create(RegistryKey.PAINTING_VARIANT);
public static final RegistryEventProvider<JukeboxSong, JukeboxSongRegistryEntry.Builder> JUKEBOX_SONG = create(RegistryKey.JUKEBOX_SONG);
private RegistryEvents() { private RegistryEvents() {
} }

View file

@ -0,0 +1,51 @@
package io.papermc.paper.util;
import java.util.Optional;
import org.jetbrains.annotations.Contract;
import org.jspecify.annotations.NullMarked;
@NullMarked
public sealed interface Either<L, R> permits Either.Left, Either.Right {
@Contract(value = "_ -> new", pure = true)
static <L, R> Either.Left<L, R> left(final L value) {
return new EitherLeft<>(value);
}
@Contract(value = "_ -> new", pure = true)
static <L, R> Either.Right<L, R> right(final R value) {
return new EitherRight<>(value);
}
Optional<L> left();
Optional<R> right();
sealed interface Left<L, R> extends Either<L, R> permits EitherLeft {
L value();
@Override
default Optional<L> left() {
return Optional.of(this.value());
}
@Override
default Optional<R> right() {
return Optional.empty();
}
}
sealed interface Right<L, R> extends Either<L, R> permits EitherRight {
R value();
@Override
default Optional<L> left() {
return Optional.empty();
}
@Override
default Optional<R> right() {
return Optional.of(this.value());
}
}
}

View file

@ -0,0 +1,9 @@
package io.papermc.paper.util;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
@ApiStatus.Internal
@NullMarked
record EitherLeft<L, R>(L value) implements Either.Left<L, R> {
}

View file

@ -0,0 +1,4 @@
package io.papermc.paper.util;
public record EitherRight<L, R>(R value) implements Either.Right<L, R> {
}

View file

@ -6,6 +6,7 @@ import io.papermc.paper.datacomponent.DataComponentTypes;
import io.papermc.paper.datacomponent.PaperDataComponentType; import io.papermc.paper.datacomponent.PaperDataComponentType;
import io.papermc.paper.registry.data.PaperEnchantmentRegistryEntry; import io.papermc.paper.registry.data.PaperEnchantmentRegistryEntry;
import io.papermc.paper.registry.data.PaperGameEventRegistryEntry; import io.papermc.paper.registry.data.PaperGameEventRegistryEntry;
import io.papermc.paper.registry.data.PaperJukeboxSongRegistryEntry;
import io.papermc.paper.registry.data.PaperPaintingVariantRegistryEntry; import io.papermc.paper.registry.data.PaperPaintingVariantRegistryEntry;
import io.papermc.paper.registry.entry.RegistryEntry; import io.papermc.paper.registry.entry.RegistryEntry;
import io.papermc.paper.registry.tag.TagKey; import io.papermc.paper.registry.tag.TagKey;
@ -105,7 +106,7 @@ public final class PaperRegistries {
start(Registries.DAMAGE_TYPE, RegistryKey.DAMAGE_TYPE).craft(DamageType.class, CraftDamageType::new).build().delayed(), start(Registries.DAMAGE_TYPE, RegistryKey.DAMAGE_TYPE).craft(DamageType.class, CraftDamageType::new).build().delayed(),
start(Registries.WOLF_VARIANT, RegistryKey.WOLF_VARIANT).craft(Wolf.Variant.class, CraftWolf.CraftVariant::new).build().delayed(), start(Registries.WOLF_VARIANT, RegistryKey.WOLF_VARIANT).craft(Wolf.Variant.class, CraftWolf.CraftVariant::new).build().delayed(),
start(Registries.ENCHANTMENT, RegistryKey.ENCHANTMENT).craft(Enchantment.class, CraftEnchantment::new).serializationUpdater(FieldRename.ENCHANTMENT_RENAME).writable(PaperEnchantmentRegistryEntry.PaperBuilder::new).delayed(), start(Registries.ENCHANTMENT, RegistryKey.ENCHANTMENT).craft(Enchantment.class, CraftEnchantment::new).serializationUpdater(FieldRename.ENCHANTMENT_RENAME).writable(PaperEnchantmentRegistryEntry.PaperBuilder::new).delayed(),
start(Registries.JUKEBOX_SONG, RegistryKey.JUKEBOX_SONG).craft(JukeboxSong.class, CraftJukeboxSong::new).build().delayed(), start(Registries.JUKEBOX_SONG, RegistryKey.JUKEBOX_SONG).craft(JukeboxSong.class, CraftJukeboxSong::new).writable(PaperJukeboxSongRegistryEntry.Builder::new).delayed(),
start(Registries.BANNER_PATTERN, RegistryKey.BANNER_PATTERN).craft(PatternType.class, CraftPatternType::new).build().delayed(), start(Registries.BANNER_PATTERN, RegistryKey.BANNER_PATTERN).craft(PatternType.class, CraftPatternType::new).build().delayed(),
start(Registries.PAINTING_VARIANT, RegistryKey.PAINTING_VARIANT).craft(Art.class, CraftArt::new).writable(PaperPaintingVariantRegistryEntry.PaperBuilder::new).delayed(), start(Registries.PAINTING_VARIANT, RegistryKey.PAINTING_VARIANT).craft(Art.class, CraftArt::new).writable(PaperPaintingVariantRegistryEntry.PaperBuilder::new).delayed(),
start(Registries.INSTRUMENT, RegistryKey.INSTRUMENT).craft(MusicInstrument.class, CraftMusicInstrument::new).build().delayed(), start(Registries.INSTRUMENT, RegistryKey.INSTRUMENT).craft(MusicInstrument.class, CraftMusicInstrument::new).build().delayed(),

View file

@ -0,0 +1,116 @@
package io.papermc.paper.registry.data;
import io.papermc.paper.registry.PaperRegistries;
import io.papermc.paper.registry.PaperRegistryBuilder;
import io.papermc.paper.registry.TypedKey;
import io.papermc.paper.registry.data.util.Conversions;
import io.papermc.paper.util.Either;
import java.util.OptionalInt;
import java.util.function.Consumer;
import net.minecraft.core.Holder;
import net.minecraft.network.chat.Component;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.world.item.JukeboxSong;
import org.bukkit.Sound;
import org.checkerframework.checker.index.qual.Positive;
import org.jetbrains.annotations.Range;
import org.jspecify.annotations.Nullable;
import static io.papermc.paper.registry.data.util.Checks.asArgument;
import static io.papermc.paper.registry.data.util.Checks.asArgumentMinExclusive;
import static io.papermc.paper.registry.data.util.Checks.asArgumentRange;
import static io.papermc.paper.registry.data.util.Checks.asConfigured;
public class PaperJukeboxSongRegistryEntry implements JukeboxSongRegistryEntry {
protected final Conversions conversions;
protected @Nullable Holder<SoundEvent> soundEvent;
protected @Nullable Component description;
protected @Nullable Float lengthInSeconds;
protected OptionalInt comparatorOutput = OptionalInt.empty();
public PaperJukeboxSongRegistryEntry(final Conversions conversions, final @Nullable JukeboxSong internal) {
this.conversions = conversions;
if (internal == null) {
return;
}
this.soundEvent = internal.soundEvent();
this.description = internal.description();
this.lengthInSeconds = internal.lengthInSeconds();
this.comparatorOutput = OptionalInt.of(internal.comparatorOutput());
}
@Override
public Either<TypedKey<Sound>, SoundEventRegistryEntry> soundEvent() {
final Holder<SoundEvent> current = asConfigured(this.soundEvent, "soundEvent");
return current.unwrap().map(
l -> Either.left(PaperRegistries.fromNms(l)),
r -> Either.right(new PaperSoundEventRegistryEntry(this.conversions, r))
);
}
@Override
public net.kyori.adventure.text.Component description() {
return this.conversions.asAdventure(asConfigured(this.description, "description"));
}
@Override
public float lengthInSeconds() {
return asConfigured(this.lengthInSeconds, "lengthInSeconds");
}
@Override
public int comparatorOutput() {
return asConfigured(this.comparatorOutput, "comparatorOutput");
}
public static final class Builder extends PaperJukeboxSongRegistryEntry implements JukeboxSongRegistryEntry.Builder, PaperRegistryBuilder<JukeboxSong, org.bukkit.JukeboxSong> {
public Builder(final Conversions conversions, final @Nullable JukeboxSong internal) {
super(conversions, internal);
}
@Override
public JukeboxSongRegistryEntry.Builder soundEvent(final TypedKey<Sound> soundEvent) {
this.soundEvent = this.conversions.getReferenceHolder(PaperRegistries.toNms(asArgument(soundEvent, "soundEvent")));
return this;
}
@Override
public JukeboxSongRegistryEntry.Builder soundEvent(final Consumer<? super SoundEventRegistryEntry.Builder> soundEvent) {
final PaperSoundEventRegistryEntry.Builder builder = new PaperSoundEventRegistryEntry.Builder(this.conversions, null);
asArgument(soundEvent, "soundEvent").accept(builder);
this.soundEvent = Holder.direct(builder.build());
return this;
}
@Override
public JukeboxSongRegistryEntry.Builder description(final net.kyori.adventure.text.Component description) {
this.description = this.conversions.asVanilla(asArgument(description, "description"));
return this;
}
@Override
public JukeboxSongRegistryEntry.Builder lengthInSeconds(final @Positive float lengthInSeconds) {
this.lengthInSeconds = asArgumentMinExclusive(lengthInSeconds, "lengthInSeconds", 0);
return this;
}
@Override
public JukeboxSongRegistryEntry.Builder comparatorOutput(final @Range(from = 0, to = 15) int comparatorOutput) {
this.comparatorOutput = OptionalInt.of(asArgumentRange(comparatorOutput, "comparatorOutput", 0, 15));
return this;
}
@Override
public JukeboxSong build() {
return new JukeboxSong(
asConfigured(this.soundEvent, "soundEvent"),
asConfigured(this.description, "description"),
this.lengthInSeconds(),
this.comparatorOutput()
);
}
}
}

View file

@ -0,0 +1,69 @@
package io.papermc.paper.registry.data;
import io.papermc.paper.adventure.PaperAdventure;
import io.papermc.paper.registry.PaperRegistryBuilder;
import io.papermc.paper.registry.data.util.Conversions;
import java.util.Optional;
import net.kyori.adventure.key.Key;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundEvent;
import org.bukkit.Sound;
import org.jspecify.annotations.Nullable;
import static io.papermc.paper.registry.data.util.Checks.asArgument;
import static io.papermc.paper.registry.data.util.Checks.asConfigured;
/**
* Not actually used for modifying {@link net.minecraft.core.registries.Registries#SOUND_EVENT}
* but for creating direct holders for other registries and direct {@link org.bukkit.craftbukkit.CraftSound}s.
*/
public class PaperSoundEventRegistryEntry implements SoundEventRegistryEntry {
protected final Conversions conversions;
protected @Nullable ResourceLocation location;
protected @Nullable Float fixedRange;
public PaperSoundEventRegistryEntry(final Conversions conversions, final @Nullable SoundEvent soundEvent) {
this.conversions = conversions;
if (soundEvent == null) {
return;
}
this.location = soundEvent.location();
this.fixedRange = soundEvent.fixedRange().orElse(null);
}
@Override
public Key location() {
return PaperAdventure.asAdventure(asConfigured(this.location, "location"));
}
@Override
public @Nullable Float fixedRange() {
return this.fixedRange;
}
public static final class Builder extends PaperSoundEventRegistryEntry implements SoundEventRegistryEntry.Builder, PaperRegistryBuilder<SoundEvent, Sound> {
public Builder(final Conversions conversions, final @Nullable SoundEvent soundEvent) {
super(conversions, soundEvent);
}
@Override
public SoundEventRegistryEntry.Builder location(final Key location) {
this.location = PaperAdventure.asVanilla(asArgument(location, "location"));
return this;
}
@Override
public SoundEventRegistryEntry.Builder fixedRange(final @Nullable Float fixedRange) {
this.fixedRange = fixedRange;
return this;
}
@Override
public SoundEvent build() {
return new SoundEvent(asConfigured(this.location, "location"), Optional.ofNullable(this.fixedRange));
}
}
}

View file

@ -40,6 +40,13 @@ public final class Checks {
return value; return value;
} }
public static float asArgumentMinExclusive(final float value, final String field, final float min) {
if (value <= min) {
throw new IllegalArgumentException("argument " + field + " must be [" + min + ",+inf)");
}
return value;
}
private Checks() { private Checks() {
} }
} }

View file

@ -5,6 +5,7 @@ import com.mojang.serialization.JavaOps;
import io.papermc.paper.adventure.WrapperAwareSerializer; import io.papermc.paper.adventure.WrapperAwareSerializer;
import java.util.Optional; import java.util.Optional;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry; import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess; import net.minecraft.core.RegistryAccess;
import net.minecraft.resources.RegistryOps; import net.minecraft.resources.RegistryOps;
@ -46,6 +47,10 @@ public class Conversions {
return this.lookup; return this.lookup;
} }
public <M> Holder.Reference<M> getReferenceHolder(final ResourceKey<M> key) {
return this.lookup.lookup(key.registryKey()).orElseThrow().getter().getOrThrow(key);
}
@Contract("null -> null; !null -> !null") @Contract("null -> null; !null -> !null")
public net.minecraft.network.chat.@Nullable Component asVanilla(final @Nullable Component adventure) { public net.minecraft.network.chat.@Nullable Component asVanilla(final @Nullable Component adventure) {
if (adventure == null) return null; if (adventure == null) return null;

View file

@ -1,6 +1,16 @@
package io.papermc.testplugin; package io.papermc.testplugin;
import io.papermc.paper.datacomponent.DataComponentTypes;
import io.papermc.paper.datacomponent.item.JukeboxPlayable;
import io.papermc.paper.event.player.ChatEvent;
import io.papermc.paper.registry.RegistryAccess;
import io.papermc.paper.registry.RegistryKey;
import org.bukkit.JukeboxSong;
import org.bukkit.Material;
import org.bukkit.Registry;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
public final class TestPlugin extends JavaPlugin implements Listener { public final class TestPlugin extends JavaPlugin implements Listener {
@ -12,4 +22,13 @@ public final class TestPlugin extends JavaPlugin implements Listener {
// io.papermc.testplugin.brigtests.Registration.registerViaOnEnable(this); // io.papermc.testplugin.brigtests.Registration.registerViaOnEnable(this);
} }
@EventHandler
public void onEvent(ChatEvent event) {
final ItemStack stick = new ItemStack(Material.STICK);
final Registry<JukeboxSong> registry = RegistryAccess.registryAccess().getRegistry(RegistryKey.JUKEBOX_SONG);
final JukeboxSong orThrow = registry.getOrThrow(TestPluginBootstrap.NEW);
stick.setData(DataComponentTypes.JUKEBOX_PLAYABLE, JukeboxPlayable.jukeboxPlayable(orThrow));
event.getPlayer().getInventory().addItem(stick);
}
} }

View file

@ -2,13 +2,32 @@ package io.papermc.testplugin;
import io.papermc.paper.plugin.bootstrap.BootstrapContext; import io.papermc.paper.plugin.bootstrap.BootstrapContext;
import io.papermc.paper.plugin.bootstrap.PluginBootstrap; import io.papermc.paper.plugin.bootstrap.PluginBootstrap;
import io.papermc.paper.registry.TypedKey;
import io.papermc.paper.registry.event.RegistryEvents;
import io.papermc.paper.registry.keys.JukeboxSongKeys;
import io.papermc.paper.registry.keys.SoundEventKeys;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component;
import org.bukkit.JukeboxSong;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
public class TestPluginBootstrap implements PluginBootstrap { public class TestPluginBootstrap implements PluginBootstrap {
static final TypedKey<JukeboxSong> NEW = JukeboxSongKeys.create(Key.key("test:test"));
@Override @Override
public void bootstrap(@NotNull BootstrapContext context) { public void bootstrap(@NotNull BootstrapContext context) {
// io.papermc.testplugin.brigtests.Registration.registerViaBootstrap(context); // io.papermc.testplugin.brigtests.Registration.registerViaBootstrap(context);
context.getLifecycleManager().registerEventHandler(RegistryEvents.JUKEBOX_SONG.freeze(), event -> {
// Do something with the event
event.registry().register(NEW, b -> {
b.comparatorOutput(2)
.description(Component.text("EPIC CUSTOM SOUND SONG"))
.lengthInSeconds(2)
.soundEvent(sb -> sb.location(SoundEventKeys.BLOCK_STONE_BUTTON_CLICK_ON));
});
});
} }
} }