diff options
Diffstat (limited to 'patches/server/0420-initial-work-on-native-Adventure-codecs.patch')
-rw-r--r-- | patches/server/0420-initial-work-on-native-Adventure-codecs.patch | 294 |
1 files changed, 191 insertions, 103 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() { + } +} |