Adventure 4.23.0 (#12690)

This commit is contained in:
Kezz
2025-06-25 20:16:18 +01:00
committed by GitHub
parent 0caf75f839
commit bee287927c
14 changed files with 151 additions and 135 deletions

View File

@@ -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"

View File

@@ -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;
};
}
}

View File

@@ -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 })
/*

View File

@@ -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
}

View File

@@ -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<BinaryTagHolder> 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> COMPONENT_CODEC = recursive("adventure Component", AdventureCodecs::createCodec);
public static final StreamCodec<RegistryFriendlyByteBuf, Component> 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<ClickEvent, String> TEXT_PAYLOAD_EXTRACTOR = a -> ((ClickEvent.Payload.Text) a.payload()).value();
/*
* Click
*/
static final MapCodec<ClickEvent> 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<ClickEvent> 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<ClickEvent> 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<ClickEvent> 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<ClickEvent> 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<ClickEvent> 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<ClickEvent> 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<ClickEventType> 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<ClickEventType> 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<ClickEvent> codec, String id) implements StringRepresentable {
@Override
@@ -126,23 +150,17 @@ public final class AdventureCodecs {
}
}
private static final Function<ClickEvent, ClickEventType> 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<ClickEvent, ClickEventType> 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<ClickEvent> CLICK_EVENT_CODEC = CLICK_EVENT_TYPE_CODEC.dispatch("action", GET_CLICK_EVENT_TYPE, ClickEventType::codec);

View File

@@ -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;
};
}

View File

@@ -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<Audience> 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);
}
});
}
}

View File

@@ -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 <uuid>";
}
@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;
}
}

View File

@@ -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) -> {

View File

@@ -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<World> POINTERS_SUPPLIER = PointersSupplier.<World>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<SpawnCategory> 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
}

View File

@@ -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<ServerCommandSender> POINTERS_SUPPLIER = PointersSupplier.<ServerCommandSender>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);
}
}

View File

@@ -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<org.bukkit.entity.Entity> POINTERS_SUPPLIER = PointersSupplier.<org.bukkit.entity.Entity>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

View File

@@ -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<Player> POINTERS_SUPPLIER = PointersSupplier.<Player>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

View File

@@ -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<Tag> 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()
);