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

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