diff --git a/paper-api/build.gradle.kts b/paper-api/build.gradle.kts index 82d3005910..db243429f2 100644 --- a/paper-api/build.gradle.kts +++ b/paper-api/build.gradle.kts @@ -11,7 +11,7 @@ java { val annotationsVersion = "26.0.2" // Keep in sync with paper-server adventure-text-serializer-ansi dep -val adventureVersion = "4.21.0" +val adventureVersion = "4.23.0" val bungeeCordChatVersion = "1.21-R0.2-deprecated+build.21" val slf4jVersion = "2.0.16" val log4jVersion = "2.24.1" diff --git a/paper-api/src/main/java/org/bukkit/SoundCategory.java b/paper-api/src/main/java/org/bukkit/SoundCategory.java index 44fed7472c..f4c6af9d6f 100644 --- a/paper-api/src/main/java/org/bukkit/SoundCategory.java +++ b/paper-api/src/main/java/org/bukkit/SoundCategory.java @@ -37,7 +37,7 @@ public enum SoundCategory implements Sound.Source.Provider { case PLAYERS -> Sound.Source.PLAYER; case AMBIENT -> Sound.Source.AMBIENT; case VOICE -> Sound.Source.VOICE; - case UI -> throw new UnsupportedOperationException("Waiting on adventure release for the UI sound source"); // todo adventure + case UI -> Sound.Source.UI; }; } } diff --git a/paper-server/build.gradle.kts b/paper-server/build.gradle.kts index 123d549ab9..3ea718cdbf 100644 --- a/paper-server/build.gradle.kts +++ b/paper-server/build.gradle.kts @@ -134,7 +134,7 @@ dependencies { implementation("org.jline:jline-terminal-ffm:3.27.1") // use ffm on java 22+ implementation("org.jline:jline-terminal-jni:3.27.1") // fall back to jni on java 21 implementation("net.minecrell:terminalconsoleappender:1.3.0") - implementation("net.kyori:adventure-text-serializer-ansi:4.21.0") // Keep in sync with adventureVersion from Paper-API build file + implementation("net.kyori:adventure-text-serializer-ansi:4.23.0") // Keep in sync with adventureVersion from Paper-API build file runtimeConfiguration(sourceSets.main.map { it.runtimeClasspath }) /* diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch index a1ba9c4a3e..43c85fd93c 100644 --- a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch @@ -2610,7 +2610,7 @@ if (!this.receivedMovementThisTick) { this.player.setKnownMovement(Vec3.ZERO); } -@@ -2078,4 +_,17 @@ +@@ -2078,4 +_,29 @@ interface EntityInteraction { InteractionResult run(ServerPlayer player, Entity entity, InteractionHand hand); } @@ -2627,4 +2627,16 @@ + return event; + } + // Paper end - Add fail move event ++ ++ // Paper start - Implement click callbacks with custom click action ++ @Override ++ public void handleCustomClickAction(final net.minecraft.network.protocol.common.ServerboundCustomClickActionPacket packet) { ++ super.handleCustomClickAction(packet); ++ if (packet.id().equals(io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CLICK_CALLBACK_RESOURCE_LOCATION)) { ++ packet.payload().ifPresent(tag -> ++ io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.tryRunCallback(this.player.getBukkitEntity(), tag) ++ ); ++ } ++ } ++ // Paper end - Implement click callbacks with custom click action } diff --git a/paper-server/src/main/java/io/papermc/paper/adventure/AdventureCodecs.java b/paper-server/src/main/java/io/papermc/paper/adventure/AdventureCodecs.java index 2af7fb86dc..dbc03f9e06 100644 --- a/paper-server/src/main/java/io/papermc/paper/adventure/AdventureCodecs.java +++ b/paper-server/src/main/java/io/papermc/paper/adventure/AdventureCodecs.java @@ -1,5 +1,6 @@ package io.papermc.paper.adventure; +import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.datafixers.util.Either; import com.mojang.serialization.Codec; import com.mojang.serialization.DataResult; @@ -14,6 +15,7 @@ import java.util.function.Function; import java.util.function.Predicate; import com.mojang.serialization.codecs.RecordCodecBuilder; import net.kyori.adventure.key.Key; +import net.kyori.adventure.nbt.api.BinaryTagHolder; import net.kyori.adventure.text.BlockNBTComponent; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.EntityNBTComponent; @@ -37,6 +39,9 @@ import net.kyori.adventure.text.format.TextDecoration; import net.minecraft.commands.arguments.selector.SelectorPattern; import net.minecraft.core.UUIDUtil; import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; +import net.minecraft.nbt.TagParser; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.chat.ComponentSerialization; import net.minecraft.network.chat.contents.KeybindContents; @@ -44,6 +49,7 @@ import net.minecraft.network.chat.contents.ScoreContents; import net.minecraft.network.chat.contents.TranslatableContents; import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; import net.minecraft.util.ExtraCodecs; import net.minecraft.util.StringRepresentable; import net.minecraft.world.item.Item; @@ -63,7 +69,14 @@ import static net.kyori.adventure.text.TranslationArgument.numeric; @DefaultQualifier(NonNull.class) public final class AdventureCodecs { - + public static final Codec BINARY_TAG_HOLDER_CODEC = ExtraCodecs.NBT.flatComapMap(tag -> BinaryTagHolder.encode(tag, PaperAdventure.NBT_CODEC), api -> { + try { + final Tag tag = api.get(PaperAdventure.NBT_CODEC); + return DataResult.success(tag); + } catch (CommandSyntaxException e) { + return DataResult.error(e::getMessage); + } + }); public static final Codec COMPONENT_CODEC = recursive("adventure Component", AdventureCodecs::createCodec); public static final StreamCodec STREAM_COMPONENT_CODEC = ByteBufCodecs.fromCodecWithRegistriesTrusted(COMPONENT_CODEC); @@ -89,27 +102,37 @@ public final class AdventureCodecs { return Key.parseable(s) ? DataResult.success(Key.key(s)) : DataResult.error(() -> "Cannot convert " + s + " to adventure Key"); }, Key::asString); + static final Function TEXT_PAYLOAD_EXTRACTOR = a -> ((ClickEvent.Payload.Text) a.payload()).value(); + /* * Click */ static final MapCodec OPEN_URL_CODEC = mapCodec((instance) -> instance.group( - ExtraCodecs.UNTRUSTED_URI.fieldOf("url").forGetter(a -> URI.create(!a.value().contains("://") ? "https://" + a.value() : a.value())) + ExtraCodecs.UNTRUSTED_URI.fieldOf("url").forGetter(a -> { + final String url = ((ClickEvent.Payload.Text) a.payload()).value(); + return URI.create(!url.contains("://") ? "https://" + url : url); + } + ) ).apply(instance, (url) -> ClickEvent.openUrl(url.toString()))); static final MapCodec OPEN_FILE_CODEC = mapCodec((instance) -> instance.group( - Codec.STRING.fieldOf("path").forGetter(ClickEvent::value) + Codec.STRING.fieldOf("path").forGetter(TEXT_PAYLOAD_EXTRACTOR) ).apply(instance, ClickEvent::openFile)); static final MapCodec RUN_COMMAND_CODEC = mapCodec((instance) -> instance.group( - ExtraCodecs.CHAT_STRING.fieldOf("command").forGetter(ClickEvent::value) + ExtraCodecs.CHAT_STRING.fieldOf("command").forGetter(TEXT_PAYLOAD_EXTRACTOR) ).apply(instance, ClickEvent::runCommand)); static final MapCodec SUGGEST_COMMAND_CODEC = mapCodec((instance) -> instance.group( - ExtraCodecs.CHAT_STRING.fieldOf("command").forGetter(ClickEvent::value) + ExtraCodecs.CHAT_STRING.fieldOf("command").forGetter(TEXT_PAYLOAD_EXTRACTOR) ).apply(instance, ClickEvent::suggestCommand)); static final MapCodec CHANGE_PAGE_CODEC = mapCodec((instance) -> instance.group( - ExtraCodecs.POSITIVE_INT.fieldOf("page").forGetter(a -> Integer.parseInt(a.value())) + ExtraCodecs.POSITIVE_INT.fieldOf("page").forGetter(a -> ((ClickEvent.Payload.Int) a.payload()).integer()) ).apply(instance, ClickEvent::changePage)); static final MapCodec COPY_TO_CLIPBOARD_CODEC = mapCodec((instance) -> instance.group( - Codec.STRING.fieldOf("value").forGetter(ClickEvent::value) + Codec.STRING.fieldOf("value").forGetter(TEXT_PAYLOAD_EXTRACTOR) ).apply(instance, ClickEvent::copyToClipboard)); + static final MapCodec CUSTOM_CODEC = mapCodec((instance) -> instance.group( + KEY_CODEC.fieldOf("id").forGetter(a -> ((ClickEvent.Payload.Custom) a.payload()).key()), + BINARY_TAG_HOLDER_CODEC.fieldOf("payload").forGetter(a -> ((ClickEvent.Payload.Custom) a.payload()).nbt()) + ).apply(instance, ClickEvent::custom)); static final ClickEventType OPEN_URL_CLICK_EVENT_TYPE = new ClickEventType(OPEN_URL_CODEC, "open_url"); static final ClickEventType OPEN_FILE_CLICK_EVENT_TYPE = new ClickEventType(OPEN_FILE_CODEC, "open_file"); @@ -117,7 +140,8 @@ public final class AdventureCodecs { static final ClickEventType SUGGEST_COMMAND_CLICK_EVENT_TYPE = new ClickEventType(SUGGEST_COMMAND_CODEC, "suggest_command"); static final ClickEventType CHANGE_PAGE_CLICK_EVENT_TYPE = new ClickEventType(CHANGE_PAGE_CODEC, "change_page"); static final ClickEventType COPY_TO_CLIPBOARD_CLICK_EVENT_TYPE = new ClickEventType(COPY_TO_CLIPBOARD_CODEC, "copy_to_clipboard"); - static final Codec CLICK_EVENT_TYPE_CODEC = StringRepresentable.fromValues(() -> new ClickEventType[]{OPEN_URL_CLICK_EVENT_TYPE, OPEN_FILE_CLICK_EVENT_TYPE, RUN_COMMAND_CLICK_EVENT_TYPE, SUGGEST_COMMAND_CLICK_EVENT_TYPE, CHANGE_PAGE_CLICK_EVENT_TYPE, COPY_TO_CLIPBOARD_CLICK_EVENT_TYPE}); + static final ClickEventType CUSTOM_CLICK_EVENT_TYPE = new ClickEventType(CUSTOM_CODEC, "custom"); + static final Codec CLICK_EVENT_TYPE_CODEC = StringRepresentable.fromValues(() -> new ClickEventType[]{OPEN_URL_CLICK_EVENT_TYPE, OPEN_FILE_CLICK_EVENT_TYPE, RUN_COMMAND_CLICK_EVENT_TYPE, SUGGEST_COMMAND_CLICK_EVENT_TYPE, CHANGE_PAGE_CLICK_EVENT_TYPE, COPY_TO_CLIPBOARD_CLICK_EVENT_TYPE, CUSTOM_CLICK_EVENT_TYPE}); record ClickEventType(MapCodec codec, String id) implements StringRepresentable { @Override @@ -126,23 +150,17 @@ public final class AdventureCodecs { } } - private static final Function GET_CLICK_EVENT_TYPE = he -> { - if (he.action() == ClickEvent.Action.OPEN_URL) { - return OPEN_URL_CLICK_EVENT_TYPE; - } else if (he.action() == ClickEvent.Action.OPEN_FILE) { - return OPEN_FILE_CLICK_EVENT_TYPE; - } else if (he.action() == ClickEvent.Action.RUN_COMMAND) { - return RUN_COMMAND_CLICK_EVENT_TYPE; - } else if (he.action() == ClickEvent.Action.SUGGEST_COMMAND) { - return SUGGEST_COMMAND_CLICK_EVENT_TYPE; - } else if (he.action() == ClickEvent.Action.CHANGE_PAGE) { - return CHANGE_PAGE_CLICK_EVENT_TYPE; - } else if (he.action() == ClickEvent.Action.COPY_TO_CLIPBOARD) { - return COPY_TO_CLIPBOARD_CLICK_EVENT_TYPE; - } else { - throw new IllegalStateException(); - } - }; + private static final Function GET_CLICK_EVENT_TYPE = + he -> switch (he.action()) { + case OPEN_URL -> OPEN_URL_CLICK_EVENT_TYPE; + case OPEN_FILE -> OPEN_FILE_CLICK_EVENT_TYPE; + case RUN_COMMAND -> RUN_COMMAND_CLICK_EVENT_TYPE; + case SUGGEST_COMMAND -> SUGGEST_COMMAND_CLICK_EVENT_TYPE; + case CHANGE_PAGE -> CHANGE_PAGE_CLICK_EVENT_TYPE; + case COPY_TO_CLIPBOARD -> COPY_TO_CLIPBOARD_CLICK_EVENT_TYPE; + case SHOW_DIALOG -> throw new UnsupportedOperationException(); // todo: dialog codec with dialog "api" + case CUSTOM -> CUSTOM_CLICK_EVENT_TYPE; + }; static final Codec CLICK_EVENT_CODEC = CLICK_EVENT_TYPE_CODEC.dispatch("action", GET_CLICK_EVENT_TYPE, ClickEventType::codec); diff --git a/paper-server/src/main/java/io/papermc/paper/adventure/PaperAdventure.java b/paper-server/src/main/java/io/papermc/paper/adventure/PaperAdventure.java index 32b378681f..75b46a9fd1 100644 --- a/paper-server/src/main/java/io/papermc/paper/adventure/PaperAdventure.java +++ b/paper-server/src/main/java/io/papermc/paper/adventure/PaperAdventure.java @@ -35,8 +35,6 @@ import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.plain.PlainComponentSerializer; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import net.kyori.adventure.translation.GlobalTranslator; -import net.kyori.adventure.translation.TranslationRegistry; -import net.kyori.adventure.translation.Translator; import net.kyori.adventure.util.Codec; import net.minecraft.ChatFormatting; import net.minecraft.commands.CommandSourceStack; @@ -78,16 +76,16 @@ import static java.util.Objects.requireNonNull; public final class PaperAdventure { private static final Pattern LOCALIZATION_PATTERN = Pattern.compile("%(?:(\\d+)\\$)?s"); public static final ComponentFlattener FLATTENER = ComponentFlattener.basic().toBuilder() + .nestingLimit(30) // todo: should this be configurable? a system property or config value? .complexMapper(TranslatableComponent.class, (translatable, consumer) -> { - if (!Language.getInstance().has(translatable.key())) { - for (final Translator source : GlobalTranslator.translator().sources()) { - if (source instanceof TranslationRegistry registry && registry.contains(translatable.key())) { - consumer.accept(GlobalTranslator.render(translatable, Locale.US)); - return; - } + final Language language = Language.getInstance(); + final @Nullable String fallback = translatable.fallback(); + if (!language.has(translatable.key()) && (fallback == null || !language.has(fallback))) { + if (GlobalTranslator.translator().canTranslate(translatable.key(), Locale.US)) { + consumer.accept(GlobalTranslator.render(translatable, Locale.US)); + return; } } - final @Nullable String fallback = translatable.fallback(); final @NotNull String translated = Language.getInstance().getOrDefault(translatable.key(), fallback != null ? fallback : translatable.key()); final Matcher matcher = LOCALIZATION_PATTERN.matcher(translated); @@ -379,6 +377,7 @@ public final class PaperAdventure { case PLAYER -> SoundSource.PLAYERS; case AMBIENT -> SoundSource.AMBIENT; case VOICE -> SoundSource.VOICE; + case UI -> SoundSource.UI; }; } diff --git a/paper-server/src/main/java/io/papermc/paper/adventure/providers/ClickCallbackProviderImpl.java b/paper-server/src/main/java/io/papermc/paper/adventure/providers/ClickCallbackProviderImpl.java index 40a7ab04f8..28a3817d46 100644 --- a/paper-server/src/main/java/io/papermc/paper/adventure/providers/ClickCallbackProviderImpl.java +++ b/paper-server/src/main/java/io/papermc/paper/adventure/providers/ClickCallbackProviderImpl.java @@ -1,24 +1,33 @@ package io.papermc.paper.adventure.providers; +import io.papermc.paper.adventure.PaperAdventure; import java.util.HashMap; import java.util.Map; +import java.util.Queue; import java.util.UUID; +import java.util.concurrent.ConcurrentLinkedQueue; import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.key.Key; import net.kyori.adventure.text.event.ClickCallback; import net.kyori.adventure.text.event.ClickEvent; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; +import net.minecraft.resources.ResourceLocation; import org.jetbrains.annotations.NotNull; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; - @SuppressWarnings("UnstableApiUsage") // permitted provider public class ClickCallbackProviderImpl implements ClickCallback.Provider { + private static final Key CLICK_CALLBACK_KEY = Key.key("paper", "click_callback"); + private static final String ID_KEY = "id"; + public static final ResourceLocation CLICK_CALLBACK_RESOURCE_LOCATION = PaperAdventure.asVanilla(CLICK_CALLBACK_KEY); public static final CallbackManager CALLBACK_MANAGER = new CallbackManager(); @Override public @NotNull ClickEvent create(final @NotNull ClickCallback callback, final ClickCallback.@NotNull Options options) { - return ClickEvent.runCommand("/paper:callback " + CALLBACK_MANAGER.addCallback(callback, options)); + final CompoundTag tag = new CompoundTag(); + tag.putString(ID_KEY, CALLBACK_MANAGER.addCallback(callback, options).toString()); + return ClickEvent.custom(CLICK_CALLBACK_KEY, PaperAdventure.asBinaryTagHolder(tag)); } public static final class CallbackManager { @@ -48,12 +57,21 @@ public class ClickCallbackProviderImpl implements ClickCallback.Provider { } } - public void runCallback(final @NotNull Audience audience, final UUID id) { - final StoredCallback callback = this.callbacks.get(id); - if (callback != null && callback.valid()) { //TODO Message if expired/invalid? - callback.takeUse(); - callback.callback.accept(audience); - } + public void tryRunCallback(final @NotNull Audience audience, final Tag tag) { + tag.asCompound().flatMap(t -> t.getString(ID_KEY)).ifPresent(s -> { + final UUID id; + try { + id = UUID.fromString(s); + } catch (final IllegalArgumentException ignored) { + return; + } + + final StoredCallback callback = this.callbacks.get(id); + if (callback != null && callback.valid()) { + callback.takeUse(); + callback.callback.accept(audience); + } + }); } } diff --git a/paper-server/src/main/java/io/papermc/paper/command/CallbackCommand.java b/paper-server/src/main/java/io/papermc/paper/command/CallbackCommand.java deleted file mode 100644 index fa4202abd1..0000000000 --- a/paper-server/src/main/java/io/papermc/paper/command/CallbackCommand.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.papermc.paper.command; - -import io.papermc.paper.adventure.providers.ClickCallbackProviderImpl; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.framework.qual.DefaultQualifier; -import java.util.UUID; - -@DefaultQualifier(NonNull.class) -public class CallbackCommand extends Command { - - protected CallbackCommand(final String name) { - super(name); - this.description = "ClickEvent callback"; - this.usageMessage = "/callback "; - } - - @Override - public boolean execute(final CommandSender sender, final String commandLabel, final String[] args) { - if (args.length != 1) { - return false; - } - - final UUID id; - try { - id = UUID.fromString(args[0]); - } catch (final IllegalArgumentException ignored) { - return false; - } - - ClickCallbackProviderImpl.CALLBACK_MANAGER.runCallback(sender, id); - return true; - } -} diff --git a/paper-server/src/main/java/io/papermc/paper/command/PaperCommands.java b/paper-server/src/main/java/io/papermc/paper/command/PaperCommands.java index f3f466ee58..eeefb915d4 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/PaperCommands.java +++ b/paper-server/src/main/java/io/papermc/paper/command/PaperCommands.java @@ -23,7 +23,6 @@ public final class PaperCommands { public static void registerCommands(final MinecraftServer server) { COMMANDS.put("paper", new PaperCommand("paper")); - COMMANDS.put("callback", new CallbackCommand("callback")); COMMANDS.put("mspt", new MSPTCommand("mspt")); COMMANDS.forEach((s, command) -> { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index 7990382df4..68351e4760 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -29,6 +29,7 @@ import java.util.UUID; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Collectors; +import net.kyori.adventure.pointer.PointersSupplier; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.HolderSet; @@ -157,6 +158,10 @@ import org.jetbrains.annotations.Nullable; public class CraftWorld extends CraftRegionAccessor implements World { public static final int CUSTOM_DIMENSION_OFFSET = 10; private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry(); + private static final PointersSupplier POINTERS_SUPPLIER = PointersSupplier.builder() + .resolving(net.kyori.adventure.identity.Identity.NAME, World::getName) + .resolving(net.kyori.adventure.identity.Identity.UUID, World::getUID) + .build(); private final ServerLevel world; private WorldBorder worldBorder; @@ -168,7 +173,6 @@ public class CraftWorld extends CraftRegionAccessor implements World { private final BlockMetadataStore blockMetadata = new BlockMetadataStore(this); private final Object2IntOpenHashMap spawnCategoryLimit = new Object2IntOpenHashMap<>(); private final CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(CraftWorld.DATA_TYPE_REGISTRY); - private net.kyori.adventure.pointer.Pointers adventure$pointers; // Paper - implement pointers // Paper start - void damage configuration private boolean voidDamageEnabled; private float voidDamageAmount; @@ -2481,14 +2485,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { // Paper start - implement pointers @Override public net.kyori.adventure.pointer.Pointers pointers() { - if (this.adventure$pointers == null) { - this.adventure$pointers = net.kyori.adventure.pointer.Pointers.builder() - .withDynamic(net.kyori.adventure.identity.Identity.NAME, this::getName) - .withDynamic(net.kyori.adventure.identity.Identity.UUID, this::getUID) - .build(); - } - - return this.adventure$pointers; + return POINTERS_SUPPLIER.view(this); } // Paper end } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java b/paper-server/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java index 36e478b226..b540472c0d 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java @@ -2,6 +2,7 @@ package org.bukkit.craftbukkit.command; import java.util.Set; import java.util.UUID; +import net.kyori.adventure.pointer.PointersSupplier; import org.bukkit.Bukkit; import org.bukkit.Server; import org.bukkit.command.CommandSender; @@ -12,8 +13,12 @@ import org.bukkit.permissions.PermissionAttachmentInfo; import org.bukkit.plugin.Plugin; public abstract class ServerCommandSender implements CommandSender { + private static final PointersSupplier POINTERS_SUPPLIER = PointersSupplier.builder() + .resolving(net.kyori.adventure.identity.Identity.DISPLAY_NAME, ServerCommandSender::name) + .resolving(net.kyori.adventure.permission.PermissionChecker.POINTER, serverCommandSender -> serverCommandSender::permissionValue) + .build(); + public final PermissibleBase perm; - private net.kyori.adventure.pointer.Pointers adventure$pointers; protected ServerCommandSender() { this.perm = new PermissibleBase(this); @@ -126,13 +131,6 @@ public abstract class ServerCommandSender implements CommandSender { @Override public net.kyori.adventure.pointer.Pointers pointers() { - if (this.adventure$pointers == null) { - this.adventure$pointers = net.kyori.adventure.pointer.Pointers.builder() - .withDynamic(net.kyori.adventure.identity.Identity.DISPLAY_NAME, this::name) - .withStatic(net.kyori.adventure.permission.PermissionChecker.POINTER, this::permissionValue) - .build(); - } - - return this.adventure$pointers; + return POINTERS_SUPPLIER.view(this); } } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java index a41f481a1e..b38073628d 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java @@ -14,6 +14,7 @@ import java.util.Set; import java.util.UUID; import io.papermc.paper.entity.LookAnchor; import java.util.concurrent.CompletableFuture; +import net.kyori.adventure.pointer.PointersSupplier; import net.kyori.adventure.util.TriState; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.chat.Component; @@ -85,13 +86,17 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { private static PermissibleBase perm; private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry(); + static final PointersSupplier POINTERS_SUPPLIER = PointersSupplier.builder() + .resolving(net.kyori.adventure.identity.Identity.DISPLAY_NAME, org.bukkit.entity.Entity::name) + .resolving(net.kyori.adventure.identity.Identity.UUID, org.bukkit.entity.Entity::getUniqueId) + .resolving(net.kyori.adventure.permission.PermissionChecker.POINTER, entity1 -> entity1::permissionValue) + .build(); protected final CraftServer server; protected Entity entity; private final EntityType entityType; private EntityDamageEvent lastDamageEvent; private final CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(CraftEntity.DATA_TYPE_REGISTRY); - protected net.kyori.adventure.pointer.Pointers adventure$pointers; // Paper - implement pointers // Paper start - Folia shedulers public final io.papermc.paper.threadedregions.EntityScheduler taskScheduler = new io.papermc.paper.threadedregions.EntityScheduler(this); private final io.papermc.paper.threadedregions.scheduler.FoliaEntityScheduler apiScheduler = new io.papermc.paper.threadedregions.scheduler.FoliaEntityScheduler(this); @@ -670,15 +675,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { @Override public net.kyori.adventure.pointer.Pointers pointers() { - if (this.adventure$pointers == null) { - this.adventure$pointers = net.kyori.adventure.pointer.Pointers.builder() - .withDynamic(net.kyori.adventure.identity.Identity.DISPLAY_NAME, this::name) - .withDynamic(net.kyori.adventure.identity.Identity.UUID, this::getUniqueId) - .withStatic(net.kyori.adventure.permission.PermissionChecker.POINTER, this::permissionValue) - .build(); - } - - return this.adventure$pointers; + return POINTERS_SUPPLIER.view(this); } @Override diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java index 144623d4ef..3e18819ae2 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -44,6 +44,8 @@ import java.util.concurrent.CompletableFuture; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; +import net.kyori.adventure.identity.Identity; +import net.kyori.adventure.pointer.PointersSupplier; import net.kyori.adventure.util.TriState; import net.md_5.bungee.api.chat.BaseComponent; import net.minecraft.advancements.AdvancementProgress; @@ -213,6 +215,12 @@ import org.jspecify.annotations.Nullable; @DelegateDeserialization(CraftOfflinePlayer.class) public class CraftPlayer extends CraftHumanEntity implements Player { + private static final PointersSupplier POINTERS_SUPPLIER = PointersSupplier.builder() + .parent(CraftEntity.POINTERS_SUPPLIER) + .resolving(Identity.NAME, Player::getName) + .resolving(Identity.DISPLAY_NAME, Player::displayName) + .resolving(Identity.LOCALE, Player::locale) + .build(); private long firstPlayed = 0; private long lastPlayed = 0; @@ -3283,17 +3291,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { @Override public net.kyori.adventure.pointer.Pointers pointers() { - if (this.adventure$pointers == null) { - this.adventure$pointers = net.kyori.adventure.pointer.Pointers.builder() - .withDynamic(net.kyori.adventure.identity.Identity.DISPLAY_NAME, this::displayName) - .withDynamic(net.kyori.adventure.identity.Identity.NAME, this::getName) - .withDynamic(net.kyori.adventure.identity.Identity.UUID, this::getUniqueId) - .withStatic(net.kyori.adventure.permission.PermissionChecker.POINTER, this::permissionValue) - .withDynamic(net.kyori.adventure.identity.Identity.LOCALE, this::locale) - .build(); - } - - return this.adventure$pointers; + return POINTERS_SUPPLIER.view(this); } @Override diff --git a/paper-server/src/test/java/io/papermc/paper/adventure/AdventureCodecsTest.java b/paper-server/src/test/java/io/papermc/paper/adventure/AdventureCodecsTest.java index 42e9b09c85..f8c00e1de7 100644 --- a/paper-server/src/test/java/io/papermc/paper/adventure/AdventureCodecsTest.java +++ b/paper-server/src/test/java/io/papermc/paper/adventure/AdventureCodecsTest.java @@ -11,11 +11,14 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.net.URI; import java.util.List; +import java.util.Optional; import java.util.UUID; import java.util.function.Function; import java.util.stream.Stream; import net.kyori.adventure.key.Key; +import net.kyori.adventure.nbt.api.BinaryTagHolder; import net.kyori.adventure.text.BlockNBTComponent; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; @@ -106,23 +109,35 @@ class AdventureCodecsTest { } @ParameterizedTest(name = PARAMETERIZED_NAME) - @EnumSource(value = ClickEvent.Action.class, mode = EnumSource.Mode.EXCLUDE, names = {"OPEN_FILE"}) + @EnumSource(value = ClickEvent.Action.class, mode = EnumSource.Mode.EXCLUDE, names = {"OPEN_FILE", "SHOW_DIALOG", "CUSTOM"}) void testClickEvent(final ClickEvent.Action action) { - final ClickEvent event = ClickEvent.clickEvent(action, action.name().equals("OPEN_URL") ? "https://google.com" : "1337"); - final Tag result = CLICK_EVENT_CODEC.encodeStart(NbtOps.INSTANCE, event).result().orElseThrow(); + final ClickEvent event = switch (action) { + case OPEN_URL -> openUrl("https://google.com"); + case RUN_COMMAND -> ClickEvent.runCommand("/say hello"); + case SUGGEST_COMMAND -> suggestCommand("/suggest hello"); + case CHANGE_PAGE -> ClickEvent.changePage(2); + case COPY_TO_CLIPBOARD -> ClickEvent.copyToClipboard("clipboard content"); + case CUSTOM -> ClickEvent.custom(key("test"), BinaryTagHolder.binaryTagHolder("3")); + case SHOW_DIALOG, OPEN_FILE -> throw new IllegalArgumentException(); + }; + final Tag result = CLICK_EVENT_CODEC.encodeStart(NbtOps.INSTANCE, event).result().orElseThrow(() -> new RuntimeException("Failed to encode ClickEvent: " + event)); final net.minecraft.network.chat.ClickEvent nms = net.minecraft.network.chat.ClickEvent.CODEC.decode(NbtOps.INSTANCE, result).result().orElseThrow().getFirst(); assertEquals(event.action().toString(), nms.action().getSerializedName()); switch (nms) { - case net.minecraft.network.chat.ClickEvent.OpenUrl(java.net.URI uri) -> - assertEquals(event.value(), uri.toString()); + case net.minecraft.network.chat.ClickEvent.OpenUrl(URI uri) -> + assertEquals(((ClickEvent.Payload.Text) event.payload()).value(), uri.toString()); case net.minecraft.network.chat.ClickEvent.SuggestCommand(String command) -> - assertEquals(event.value(), command); + assertEquals(((ClickEvent.Payload.Text) event.payload()).value(), command); case net.minecraft.network.chat.ClickEvent.RunCommand(String command) -> - assertEquals(event.value(), command); + assertEquals(((ClickEvent.Payload.Text) event.payload()).value(), command); case net.minecraft.network.chat.ClickEvent.CopyToClipboard(String value) -> - assertEquals(event.value(), value); + assertEquals(((ClickEvent.Payload.Text) event.payload()).value(), value); case net.minecraft.network.chat.ClickEvent.ChangePage(int page) -> - assertEquals(event.value(), String.valueOf(page)); + assertEquals(((ClickEvent.Payload.Int) event.payload()).integer(), page); + case net.minecraft.network.chat.ClickEvent.Custom(ResourceLocation id, Optional payload) -> { + assertEquals(((ClickEvent.Payload.Custom) event.payload()).key().toString(), id.toString()); + assertEquals(((ClickEvent.Payload.Custom) event.payload()).nbt(), payload.orElseThrow().asString()); + } default -> throw new AssertionError("Unexpected ClickEvent type: " + nms.getClass()); } } @@ -294,10 +309,10 @@ class AdventureCodecsTest { .clickEvent(openUrl("https://github.com")) .build(), style() - .hoverEvent(HoverEvent.showEntity(HoverEvent.ShowEntity.showEntity( - Key.key(Key.MINECRAFT_NAMESPACE, "pig"), + .hoverEvent(showEntity(HoverEvent.ShowEntity.showEntity( + key(Key.MINECRAFT_NAMESPACE, "pig"), UUID.randomUUID(), - Component.text("Dolores", TextColor.color(0x0a1ab9)) + text("Dolores", color(0x0a1ab9)) ))) .build() );