aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJake Potrebic <[email protected]>2023-12-07 11:14:29 -0800
committerJake Potrebic <[email protected]>2023-12-07 11:14:29 -0800
commitd0476837a5785cb78ace920a18c62b066b1500e6 (patch)
tree448e82ac675fff1bf0ec442eb10273848dc01ad8
parent9223f05a400f63be1cb5700261816960f9cf8391 (diff)
downloadPaper-d0476837a5785cb78ace920a18c62b066b1500e6.tar.gz
Paper-d0476837a5785cb78ace920a18c62b066b1500e6.zip
more adventure codec stuff
and fix tests
-rw-r--r--patches/server/0420-initial-work-on-native-Adventure-codecs.patch294
-rw-r--r--patches/server/0658-Add-more-advancement-API.patch14
2 files changed, 198 insertions, 110 deletions
diff --git a/patches/server/0420-initial-work-on-native-Adventure-codecs.patch b/patches/server/0420-initial-work-on-native-Adventure-codecs.patch
index c0f5b8e3a9..c211c454d5 100644
--- a/patches/server/0420-initial-work-on-native-Adventure-codecs.patch
+++ b/patches/server/0420-initial-work-on-native-Adventure-codecs.patch
@@ -11,27 +11,35 @@ public net.minecraft.network.chat.contents.TranslatableContents filterAllowedArg
diff --git a/src/main/java/io/papermc/paper/adventure/AdventureCodecs.java b/src/main/java/io/papermc/paper/adventure/AdventureCodecs.java
new file mode 100644
-index 0000000000000000000000000000000000000000..db13894e2710b933f69f3adaa7e728461e3cf049
+index 0000000000000000000000000000000000000000..1a13bf3b79b2d11e4ce6cc46eff65fe81a05a1ad
--- /dev/null
+++ b/src/main/java/io/papermc/paper/adventure/AdventureCodecs.java
-@@ -0,0 +1,277 @@
+@@ -0,0 +1,365 @@
+package io.papermc.paper.adventure;
+
++import com.google.common.base.Suppliers;
+import com.mojang.datafixers.util.Either;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.DataResult;
++import com.mojang.serialization.DynamicOps;
+import com.mojang.serialization.MapCodec;
++import com.mojang.serialization.MapLike;
++import com.mojang.serialization.RecordBuilder;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
++import java.util.Locale;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Function;
++import java.util.function.Supplier;
++import java.util.stream.Stream;
+import net.kyori.adventure.key.Key;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.KeybindComponent;
+import net.kyori.adventure.text.ScoreComponent;
++import net.kyori.adventure.text.SelectorComponent;
+import net.kyori.adventure.text.TextComponent;
+import net.kyori.adventure.text.TranslatableComponent;
+import net.kyori.adventure.text.event.ClickEvent;
@@ -40,6 +48,7 @@ index 0000000000000000000000000000000000000000..db13894e2710b933f69f3adaa7e72846
+import net.kyori.adventure.text.format.Style;
+import net.kyori.adventure.text.format.TextColor;
+import net.kyori.adventure.text.format.TextDecoration;
++import net.kyori.adventure.translation.GlobalTranslator;
+import net.minecraft.core.UUIDUtil;
+import net.minecraft.core.registries.BuiltInRegistries;
+import net.minecraft.nbt.CompoundTag;
@@ -56,14 +65,16 @@ index 0000000000000000000000000000000000000000..db13894e2710b933f69f3adaa7e72846
+import org.checkerframework.framework.qual.DefaultQualifier;
+import org.intellij.lang.annotations.Subst;
+
-+import static java.util.Objects.requireNonNull;
+import static net.kyori.adventure.text.Component.text;
+import static net.minecraft.util.ExtraCodecs.strictOptionalField;
+
+@DefaultQualifier(NonNull.class)
+public final class AdventureCodecs {
+
-+ public static final Codec<Component> COMPONENT_CODEC = ExtraCodecs.recursive("adventure Component", AdventureCodecs::createCodec);
++ private static final MapCodec<Component> COMPONENT_MAP_CODEC = new RecursiveMapCodec<>("adventure Component", c -> createCodec(c, false));
++ public static final Codec<Component> COMPONENT_CODEC = indirectCodec(COMPONENT_MAP_CODEC.codec());
++ private static final MapCodec<Component> RENDERING_COMPONENT_MAP_CODEC = new RecursiveMapCodec<>("rendering adventure Component", c -> createCodec(c, true));
++ public static final Codec<Component> RENDERING_COMPONENT_CODEC = indirectCodec(RENDERING_COMPONENT_MAP_CODEC.codec());
+
+ private static final Codec<TextColor> TEXT_COLOR_CODEC = Codec.STRING.comapFlatMap(s -> {
+ if (s.startsWith("#")) {
@@ -96,40 +107,44 @@ index 0000000000000000000000000000000000000000..db13894e2710b933f69f3adaa7e72846
+ ).apply(instance, ClickEvent::clickEvent);
+ });
+
-+ private static final Codec<HoverEvent.ShowEntity> SHOW_ENTITY_CODEC = RecordCodecBuilder.create((instance) -> {
-+ return instance.group(
-+ KEY_CODEC.fieldOf("type").forGetter(HoverEvent.ShowEntity::type),
-+ UUIDUtil.LENIENT_CODEC.fieldOf("id").forGetter(HoverEvent.ShowEntity::id),
-+ strictOptionalField(COMPONENT_CODEC, "name").forGetter(he -> Optional.ofNullable(he.name()))
-+ ).apply(instance, (key, uuid, component) -> {
-+ return HoverEvent.ShowEntity.showEntity(key, uuid, component.orElse(null));
++ private static Codec<HoverEvent.ShowEntity> showEntityCodec(final Codec<Component> componentCodec) {
++ return RecordCodecBuilder.create((instance) -> {
++ return instance.group(
++ KEY_CODEC.fieldOf("type").forGetter(HoverEvent.ShowEntity::type),
++ UUIDUtil.LENIENT_CODEC.fieldOf("id").forGetter(HoverEvent.ShowEntity::id),
++ strictOptionalField(componentCodec, "name").forGetter(he -> Optional.ofNullable(he.name()))
++ ).apply(instance, (key, uuid, component) -> {
++ return HoverEvent.ShowEntity.showEntity(key, uuid, component.orElse(null));
++ });
+ });
-+ });
++ }
+
-+ private static final Codec<HoverEvent.ShowItem> SHOW_ITEM_CODEC = net.minecraft.network.chat.HoverEvent.ItemStackInfo.CODEC.xmap(isi -> {
-+ @Subst("key") final String typeKey = BuiltInRegistries.ITEM.getKey(isi.item).toString();
-+ return HoverEvent.ShowItem.showItem(Key.key(typeKey), isi.count, PaperAdventure.asBinaryTagHolder(isi.tag.orElse(null)));
-+ }, si -> {
-+ final Item itemType = BuiltInRegistries.ITEM.get(PaperAdventure.asVanilla(si.item()));
-+ final ItemStack stack;
-+ try {
-+ final @Nullable CompoundTag tag = si.nbt() != null ? si.nbt().get(PaperAdventure.NBT_CODEC) : null;
-+ stack = new ItemStack(BuiltInRegistries.ITEM.wrapAsHolder(itemType), si.count(), Optional.ofNullable(tag));
-+ } catch (IOException e) {
-+ throw new RuntimeException(e);
-+ }
-+ return new net.minecraft.network.chat.HoverEvent.ItemStackInfo(stack);
-+ });
++ private static Codec<HoverEvent.ShowItem> showItemCodec(final Codec<Component> componentCodec) {
++ return net.minecraft.network.chat.HoverEvent.ItemStackInfo.CODEC.xmap(isi -> {
++ @Subst("key") final String typeKey = BuiltInRegistries.ITEM.getKey(isi.item).toString();
++ return HoverEvent.ShowItem.showItem(Key.key(typeKey), isi.count, PaperAdventure.asBinaryTagHolder(isi.tag.orElse(null)));
++ }, si -> {
++ final Item itemType = BuiltInRegistries.ITEM.get(PaperAdventure.asVanilla(si.item()));
++ final ItemStack stack;
++ try {
++ final @Nullable CompoundTag tag = si.nbt() != null ? si.nbt().get(PaperAdventure.NBT_CODEC) : null;
++ stack = new ItemStack(BuiltInRegistries.ITEM.wrapAsHolder(itemType), si.count(), Optional.ofNullable(tag));
++ } catch (IOException e) {
++ throw new RuntimeException(e);
++ }
++ return new net.minecraft.network.chat.HoverEvent.ItemStackInfo(stack);
++ });
++ }
+
+ // TODO legacies
-+ private static final HoverEventType<HoverEvent.ShowEntity> SHOW_ENTITY_HOVER_EVENT_TYPE = new HoverEventType<>(SHOW_ENTITY_CODEC, HoverEvent.Action.SHOW_ENTITY, "show_entity");
-+ private static final HoverEventType<HoverEvent.ShowItem> SHOW_ITEM_HOVER_EVENT_TYPE = new HoverEventType<>(SHOW_ITEM_CODEC, HoverEvent.Action.SHOW_ITEM, "show_item");
-+ private static final HoverEventType<Component> SHOW_TEXT_HOVER_EVENT_TYPE = new HoverEventType<>(COMPONENT_CODEC, HoverEvent.Action.SHOW_TEXT, "show_text");
++ private static final HoverEventType<HoverEvent.ShowEntity> SHOW_ENTITY_HOVER_EVENT_TYPE = new HoverEventType<>(AdventureCodecs::showEntityCodec, HoverEvent.Action.SHOW_ENTITY, "show_entity");
++ private static final HoverEventType<HoverEvent.ShowItem> SHOW_ITEM_HOVER_EVENT_TYPE = new HoverEventType<>(AdventureCodecs::showItemCodec, HoverEvent.Action.SHOW_ITEM, "show_item");
++ private static final HoverEventType<Component> SHOW_TEXT_HOVER_EVENT_TYPE = new HoverEventType<>(Function.identity(), HoverEvent.Action.SHOW_TEXT, "show_text");
+ private static final Codec<HoverEventType<?>> HOVER_EVENT_TYPE_CODEC = StringRepresentable.fromValues(() -> new HoverEventType<?>[]{ SHOW_ENTITY_HOVER_EVENT_TYPE, SHOW_ITEM_HOVER_EVENT_TYPE, SHOW_TEXT_HOVER_EVENT_TYPE });
+
-+ private record HoverEventType<V>(Codec<HoverEvent<V>> codec, String id) implements StringRepresentable {
-+ private HoverEventType(final Codec<V> contentCodec, final HoverEvent.Action<V> action, final String id) {
-+ this(contentCodec.xmap(v -> {
++ private record HoverEventType<V>(Function<Codec<Component>, Codec<HoverEvent<V>>> codec, String id) implements StringRepresentable {
++ private HoverEventType(final Function<Codec<Component>, Codec<V>> contentCodec, final HoverEvent.Action<V> action, final String id) {
++ this(cc -> contentCodec.apply(cc).xmap(v -> {
+ return HoverEvent.hoverEvent(action, v);
+ }, HoverEvent::value), id);
+ }
@@ -139,30 +154,33 @@ index 0000000000000000000000000000000000000000..db13894e2710b933f69f3adaa7e72846
+ }
+ }
+
-+ private static final MapCodec<HoverEvent<?>> HOVER_EVENT_MAP_CODEC = HOVER_EVENT_TYPE_CODEC.dispatchMap("action", he -> {
-+ if (he.action() == HoverEvent.Action.SHOW_ENTITY) {
-+ return SHOW_ENTITY_HOVER_EVENT_TYPE;
-+ } else if (he.action() == HoverEvent.Action.SHOW_ITEM) {
-+ return SHOW_ITEM_HOVER_EVENT_TYPE;
-+ } else if (he.action() == HoverEvent.Action.SHOW_TEXT) {
-+ return SHOW_TEXT_HOVER_EVENT_TYPE;
-+ } else {
-+ throw new IllegalStateException();
-+ }
-+ }, HoverEventType::codec);
++ private static MapCodec<HoverEvent<?>> hoverEventMapCodec(final Codec<Component> componentCodec) {
++ return HOVER_EVENT_TYPE_CODEC.dispatchMap("action", he -> {
++ if (he.action() == HoverEvent.Action.SHOW_ENTITY) {
++ return SHOW_ENTITY_HOVER_EVENT_TYPE;
++ } else if (he.action() == HoverEvent.Action.SHOW_ITEM) {
++ return SHOW_ITEM_HOVER_EVENT_TYPE;
++ } else if (he.action() == HoverEvent.Action.SHOW_TEXT) {
++ return SHOW_TEXT_HOVER_EVENT_TYPE;
++ } else {
++ throw new IllegalStateException();
++ }
++ }, het -> het.codec.apply(componentCodec));
++ }
+
-+ public static final MapCodec<Style> STYLE_CODEC = RecordCodecBuilder.mapCodec((instance) -> {
-+ return instance.group(
-+ strictOptionalField(TEXT_COLOR_CODEC, "color").forGetter(styleGetter(Style::color)),
-+ strictOptionalField(Codec.BOOL, "bold").forGetter(decorationGetter(TextDecoration.BOLD)),
-+ strictOptionalField(Codec.BOOL, "italic").forGetter(decorationGetter(TextDecoration.ITALIC)),
-+ strictOptionalField(Codec.BOOL, "underlined").forGetter(decorationGetter(TextDecoration.UNDERLINED)),
-+ strictOptionalField(Codec.BOOL, "strikethrough").forGetter(decorationGetter(TextDecoration.STRIKETHROUGH)),
-+ strictOptionalField(Codec.BOOL, "obfuscated").forGetter(decorationGetter(TextDecoration.OBFUSCATED)),
-+ strictOptionalField(CLICK_EVENT_CODEC, "clickEvent").forGetter(styleGetter(Style::clickEvent)),
-+ strictOptionalField(HOVER_EVENT_MAP_CODEC.codec(), "hoverEvent").forGetter(styleGetter(Style::hoverEvent)),
-+ strictOptionalField(Codec.STRING, "insertion").forGetter(styleGetter(Style::insertion)),
-+ strictOptionalField(KEY_CODEC, "font").forGetter(styleGetter(Style::font))
++ public static MapCodec<Style> styleCodec(final Codec<Component> componentCodec) {
++ return RecordCodecBuilder.mapCodec((instance) -> {
++ return instance.group(
++ strictOptionalField(TEXT_COLOR_CODEC, "color").forGetter(nullableGetter(Style::color)),
++ strictOptionalField(Codec.BOOL, "bold").forGetter(decorationGetter(TextDecoration.BOLD)),
++ strictOptionalField(Codec.BOOL, "italic").forGetter(decorationGetter(TextDecoration.ITALIC)),
++ strictOptionalField(Codec.BOOL, "underlined").forGetter(decorationGetter(TextDecoration.UNDERLINED)),
++ strictOptionalField(Codec.BOOL, "strikethrough").forGetter(decorationGetter(TextDecoration.STRIKETHROUGH)),
++ strictOptionalField(Codec.BOOL, "obfuscated").forGetter(decorationGetter(TextDecoration.OBFUSCATED)),
++ strictOptionalField(CLICK_EVENT_CODEC, "clickEvent").forGetter(nullableGetter(Style::clickEvent)),
++ strictOptionalField(hoverEventMapCodec(componentCodec).codec(), "hoverEvent").forGetter(nullableGetter(Style::hoverEvent)),
++ strictOptionalField(Codec.STRING, "insertion").forGetter(nullableGetter(Style::insertion)),
++ strictOptionalField(KEY_CODEC, "font").forGetter(nullableGetter(Style::font))
+ ).apply(instance, (textColor, bold, italic, underlined, strikethrough, obfuscated, clickEvent, hoverEvent, insertion, font) -> {
+ return Style.style(builder -> {
+ textColor.ifPresent(builder::color);
@@ -176,8 +194,9 @@ index 0000000000000000000000000000000000000000..db13894e2710b933f69f3adaa7e72846
+ insertion.ifPresent(builder::insertion);
+ font.ifPresent(builder::font);
+ });
++ });
+ });
-+ });
++ }
+ private static Consumer<Boolean> styleBooleanConsumer(final Style.Builder builder, final TextDecoration decoration) {
+ return b -> builder.decoration(decoration, b);
+ }
@@ -186,7 +205,7 @@ index 0000000000000000000000000000000000000000..db13894e2710b933f69f3adaa7e72846
+ return style -> Optional.ofNullable(style.decoration(decoration) == TextDecoration.State.NOT_SET ? null : style.decoration(decoration) == TextDecoration.State.TRUE);
+ }
+
-+ private static <T> Function<Style, Optional<T>> styleGetter(final Function<Style, @Nullable T> getter) {
++ private static <R, T> Function<R, Optional<T>> nullableGetter(final Function<R, @Nullable T> getter) {
+ return style -> Optional.ofNullable(getter.apply(style));
+ }
+
@@ -194,78 +213,116 @@ index 0000000000000000000000000000000000000000..db13894e2710b933f69f3adaa7e72846
+ return instance.group(Codec.STRING.fieldOf("text").forGetter(TextComponent::content)).apply(instance, Component::text);
+ });
+ private static final Codec<Object> PRIMITIVE_ARG_CODEC = ExtraCodecs.validate(ExtraCodecs.JAVA, TranslatableContents::filterAllowedArguments);
-+ private static final Codec<Component> ARG_CODEC = Codec.either(PRIMITIVE_ARG_CODEC, COMPONENT_CODEC).xmap((either) -> {
-+ return either.map((object) -> {
-+ if (object instanceof Integer integer) {
-+ return text(integer);
-+ } else if (object instanceof Long l) {
-+ return text(l);
-+ } else if (object instanceof String s) {
-+ return text(s);
-+ } else if (object instanceof Boolean bool) {
-+ return text(bool);
-+ } else if (object instanceof Float f) {
-+ return text(f);
-+ } else if (object instanceof Double d) {
-+ return text(d);
-+ } else if (object instanceof Short s) {
-+ return text(s);
-+ } else {
-+ throw new IllegalStateException();
-+ }
-+ }, (text) -> text);
-+ }, Either::right);
-+ private static final MapCodec<TranslatableComponent> TRANSLATABLE_COMPONENT_CODEC = RecordCodecBuilder.mapCodec((instance) -> {
-+ return instance.group(
-+ Codec.STRING.fieldOf("translate").forGetter(TranslatableComponent::key),
-+ Codec.STRING.fieldOf("fallback").forGetter(TranslatableComponent::fallback),
-+ strictOptionalField(ARG_CODEC.listOf(), "with").forGetter(c -> c.args().isEmpty() ? Optional.empty() : Optional.of(c.args()))
-+ ).apply(instance, (key, fallback, components) -> {
-+ return Component.translatable(key, components.orElse(Collections.emptyList())).fallback(fallback);
++ private static Codec<Component> argCodec(final Codec<Component> componentCodec) {
++ return Codec.either(PRIMITIVE_ARG_CODEC, componentCodec).xmap((either) -> {
++ return either.map((object) -> {
++ if (object instanceof final Integer integer) {
++ return text(integer);
++ } else if (object instanceof final Long l) {
++ return text(l);
++ } else if (object instanceof final String s) {
++ return text(s);
++ } else if (object instanceof final Boolean bool) {
++ return text(bool);
++ } else if (object instanceof final Float f) {
++ return text(f);
++ } else if (object instanceof final Double d) {
++ return text(d);
++ } else if (object instanceof final Short s) {
++ return text(s);
++ } else {
++ throw new IllegalStateException();
++ }
++ }, (text) -> text);
++ }, Either::right);
++ }
++ private static MapCodec<TranslatableComponent> translatableComponentCodec(final Codec<Component> componentCodec) {
++ return RecordCodecBuilder.mapCodec((instance) -> {
++ return instance.group(
++ Codec.STRING.fieldOf("translate").forGetter(TranslatableComponent::key),
++ Codec.STRING.optionalFieldOf("fallback").forGetter(nullableGetter(TranslatableComponent::fallback)),
++ strictOptionalField(argCodec(componentCodec).listOf(), "with").forGetter(c -> c.args().isEmpty() ? Optional.empty() : Optional.of(c.args()))
++ ).apply(instance, (key, fallback, components) -> {
++ return Component.translatable(key, components.orElse(Collections.emptyList())).fallback(fallback.orElse(null));
++ });
+ });
-+ });
++ }
++ private static MapCodec<TranslatableComponent> renderingTranslatableComponentCodec(final Codec<Component> componentCodec) {
++ return new MapCodec<>() {
++ @Override
++ public <T> Stream<T> keys(final DynamicOps<T> ops) {
++ return COMPONENT_MAP_CODEC.keys(ops);
++ }
++
++ @Override
++ public <T> DataResult<TranslatableComponent> decode(final DynamicOps<T> ops, final MapLike<T> input) {
++ return DataResult.error(() -> "Cannot decode using the rendering translatable component codec");
++ }
++
++ @Override
++ public <T> RecordBuilder<T> encode(final TranslatableComponent input, final DynamicOps<T> ops, final RecordBuilder<T> prefix) {
++ final Component rendered = GlobalTranslator.render(input, Locale.US); // TODO get player's locale somehow
++ return COMPONENT_MAP_CODEC.encode(rendered, ops, prefix); // all render-able translatables should be gone, safe to use the non-rendering codec
++ }
++ };
++ }
+ private static final MapCodec<KeybindComponent> KEYBIND_COMPONENT_CODEC = KeybindContents.CODEC.xmap(k -> Component.keybind(k.getName()), k -> new KeybindContents(k.keybind()));
+ private static final MapCodec<ScoreComponent> SCORE_COMPONENT_CODEC = ScoreContents.INNER_CODEC.xmap(s -> Component.score(s.getName(), s.getObjective()), s -> new ScoreContents(s.name(), s.objective()));
++ private static MapCodec<SelectorComponent> selectorComponentCodec(final Codec<Component> componentCodec) {
++ return RecordCodecBuilder.mapCodec((instance) -> {
++ return instance.group(
++ Codec.STRING.fieldOf("selector").forGetter(SelectorComponent::pattern),
++ strictOptionalField(componentCodec, "separator").forGetter(nullableGetter(SelectorComponent::separator))
++ ).apply(instance, (selector, component) -> Component.selector(selector, component.orElse(null)));
++ });
++ }
+
-+ private record ComponentType<C extends Component>(MapCodec<C> codec, String id) implements StringRepresentable {
++ private record ComponentType<C extends Component>(Function<Codec<Component>, MapCodec<C>> codec, String id) implements StringRepresentable {
+ @Override
+ public String getSerializedName() {
+ return this.id;
+ }
+ }
+
-+ private static final ComponentType<TextComponent> PLAIN = new ComponentType<>(TEXT_COMPONENT_CODEC, "text");
-+ private static final ComponentType<TranslatableComponent> TRANSLATABLE = new ComponentType<>(TRANSLATABLE_COMPONENT_CODEC, "translatable");
-+ private static final ComponentType<ScoreComponent> SCORE = new ComponentType<>(SCORE_COMPONENT_CODEC, "score");
-+ private static final ComponentType<KeybindComponent> KEYBIND = new ComponentType<>(KEYBIND_COMPONENT_CODEC, "keybind");
++ private static final ComponentType<TextComponent> PLAIN = new ComponentType<>($ -> TEXT_COMPONENT_CODEC, "text");
++ private static final ComponentType<TranslatableComponent> TRANSLATABLE = new ComponentType<>(AdventureCodecs::translatableComponentCodec, "translatable");
++ private static final ComponentType<TranslatableComponent> RENDERING_TRANSLATABLE = new ComponentType<>(AdventureCodecs::renderingTranslatableComponentCodec, "translatable");
++ private static final ComponentType<KeybindComponent> KEYBIND = new ComponentType<>($ -> KEYBIND_COMPONENT_CODEC, "keybind");
++ private static final ComponentType<ScoreComponent> SCORE = new ComponentType<>($ -> SCORE_COMPONENT_CODEC, "score");
++ private static final ComponentType<SelectorComponent> SELECTOR = new ComponentType<>(AdventureCodecs::selectorComponentCodec, "selector");
+
-+ private static Codec<Component> createCodec(final Codec<Component> selfCodec) {
-+ final ComponentType<?>[] types = new ComponentType<?>[]{PLAIN, TRANSLATABLE, SCORE};
-+ final MapCodec<Component> legacyCodec = ComponentSerialization.createLegacyComponentMatcher(types, ComponentType::codec, component -> {
++ private static MapCodec<Component> createCodec(final Codec<Component> selfCodec, final boolean renderTranslatables) {
++ final ComponentType<?>[] types = new ComponentType<?>[]{PLAIN, TRANSLATABLE, KEYBIND, SCORE};
++ final MapCodec<Component> legacyCodec = ComponentSerialization.createLegacyComponentMatcher(types, ct -> ct.codec().apply(selfCodec), component -> {
+ if (component instanceof TextComponent) {
+ return PLAIN;
+ } else if (component instanceof TranslatableComponent) {
-+ return TRANSLATABLE;
++ return renderTranslatables ? RENDERING_TRANSLATABLE : TRANSLATABLE;
+ } else if (component instanceof KeybindComponent) {
+ return KEYBIND;
+ } else if (component instanceof ScoreComponent) {
+ return SCORE;
++ } else if (component instanceof SelectorComponent) {
++ return SELECTOR;
+ } else {
+ throw new IllegalStateException();
+ }
+ }, "type");
+
-+ final Codec<Component> codec = RecordCodecBuilder.create((instance) -> {
-+ return instance.group(legacyCodec.forGetter(Function.identity()), ExtraCodecs.strictOptionalField(ExtraCodecs.nonEmptyList(selfCodec.listOf()), "extra", List.of()).forGetter(Component::children), STYLE_CODEC.forGetter(Component::style)).apply(instance, (component, children, style) -> {
++ return RecordCodecBuilder.mapCodec((instance) -> {
++ return instance.group(
++ legacyCodec.forGetter(Function.identity()),
++ strictOptionalField(ExtraCodecs.nonEmptyList(selfCodec.listOf()), "extra", List.of()).forGetter(Component::children),
++ styleCodec(selfCodec).forGetter(Component::style)
++ ).apply(instance, (component, children, style) -> {
+ return component.style(style).children(children);
+ });
+ });
-+ return Codec.either(Codec.either(Codec.STRING, ExtraCodecs.nonEmptyList(selfCodec.listOf())), codec).xmap((either) -> {
-+ return either.map((either2) -> {
-+ return either2.map(Component::text, AdventureCodecs::createFromList);
-+ }, (text) -> {
-+ return text;
-+ });
++ }
++
++ private static Codec<Component> indirectCodec(final Codec<Component> selfCodec) {
++ return Codec.either(Codec.either(Codec.STRING, ExtraCodecs.nonEmptyList(selfCodec.listOf())), selfCodec).xmap((stringOrListOrComponent) -> {
++ return stringOrListOrComponent.map((stringOrList) -> stringOrList.map(Component::text, AdventureCodecs::createFromList), Function.identity());
+ }, (text) -> {
+ final @Nullable String string = tryCollapseToString(text);
+ return string != null ? Either.left(Either.left(string)) : Either.right(text);
@@ -289,6 +346,37 @@ index 0000000000000000000000000000000000000000..db13894e2710b933f69f3adaa7e72846
+ return component;
+ }
+
++ static class RecursiveMapCodec<T> extends MapCodec<T> {
++
++ private final String name;
++ private final Supplier<MapCodec<T>> wrapped;
++
++ RecursiveMapCodec(final String name, final Function<Codec<T>, MapCodec<T>> factory) {
++ this.name = name;
++ this.wrapped = Suppliers.memoize(() -> factory.apply(this.codec()));
++ }
++
++ @Override
++ public <T1> Stream<T1> keys(final DynamicOps<T1> ops) {
++ return this.wrapped.get().keys(ops);
++ }
++
++ @Override
++ public <T1> DataResult<T> decode(final DynamicOps<T1> ops, final MapLike<T1> input) {
++ return this.wrapped.get().decode(ops, input);
++ }
++
++ @Override
++ public <T1> RecordBuilder<T1> encode(final T input, final DynamicOps<T1> ops, final RecordBuilder<T1> prefix) {
++ return this.wrapped.get().encode(input, ops, prefix);
++ }
++
++ @Override
++ public String toString() {
++ return "RecursiveMapCodec[" + this.name + "]";
++ }
++ }
++
+ private AdventureCodecs() {
+ }
+}
diff --git a/patches/server/0658-Add-more-advancement-API.patch b/patches/server/0658-Add-more-advancement-API.patch
index e16a50a71a..3258dd170e 100644
--- a/patches/server/0658-Add-more-advancement-API.patch
+++ b/patches/server/0658-Add-more-advancement-API.patch
@@ -189,7 +189,7 @@ index 812819e814cfbdb542051a7dbfe123d3c59e66bd..61d00421b295103a6964b22fe0dfaf09
{
diff --git a/src/test/java/io/papermc/paper/advancement/AdvancementFrameTest.java b/src/test/java/io/papermc/paper/advancement/AdvancementFrameTest.java
new file mode 100644
-index 0000000000000000000000000000000000000000..1a4b930ecf10dac8336dd2eceb4ee2bf9ec780d5
+index 0000000000000000000000000000000000000000..d483fea98005426c91a3ed43f2f3ce72e140c3bc
--- /dev/null
+++ b/src/test/java/io/papermc/paper/advancement/AdvancementFrameTest.java
@@ -0,0 +1,24 @@
@@ -197,7 +197,7 @@ index 0000000000000000000000000000000000000000..1a4b930ecf10dac8336dd2eceb4ee2bf
+
+import io.papermc.paper.adventure.PaperAdventure;
+import net.kyori.adventure.text.format.TextColor;
-+import net.minecraft.advancements.FrameType;
++import net.minecraft.advancements.AdvancementType;
+import net.minecraft.network.chat.contents.TranslatableContents;
+import org.junit.jupiter.api.Test;
+
@@ -207,13 +207,13 @@ index 0000000000000000000000000000000000000000..1a4b930ecf10dac8336dd2eceb4ee2bf
+
+ @Test
+ public void test() {
-+ for (FrameType nmsFrameType : FrameType.values()) {
-+ final TextColor expectedColor = PaperAdventure.asAdventure(nmsFrameType.getChatColor());
-+ final String expectedTranslationKey = ((TranslatableContents) nmsFrameType.getDisplayName().getContents()).getKey();
-+ final var frame = PaperAdvancementDisplay.asPaperFrame(nmsFrameType);
++ for (final AdvancementType advancementType : AdvancementType.values()) {
++ final TextColor expectedColor = PaperAdventure.asAdventure(advancementType.getChatColor());
++ final String expectedTranslationKey = ((TranslatableContents) advancementType.getDisplayName().getContents()).getKey();
++ final var frame = PaperAdvancementDisplay.asPaperFrame(advancementType);
+ assertEquals(expectedTranslationKey, frame.translationKey(), "The translation keys should be the same");
+ assertEquals(expectedColor, frame.color(), "The frame colors should be the same");
-+ assertEquals(nmsFrameType.getName(), AdvancementDisplay.Frame.NAMES.key(frame));
++ assertEquals(advancementType.name(), AdvancementDisplay.Frame.NAMES.key(frame));
+ }
+ }
+}