506 lines
22 KiB
Java
506 lines
22 KiB
Java
package io.papermc.paper.adventure;
|
|
|
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
|
import com.mojang.serialization.JavaOps;
|
|
import io.netty.util.AttributeKey;
|
|
import java.io.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
import java.util.Optional;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
import java.util.function.BiConsumer;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
import java.util.stream.StreamSupport;
|
|
import net.kyori.adventure.bossbar.BossBar;
|
|
import net.kyori.adventure.inventory.Book;
|
|
import net.kyori.adventure.key.Key;
|
|
import net.kyori.adventure.nbt.api.BinaryTagHolder;
|
|
import net.kyori.adventure.sound.Sound;
|
|
import net.kyori.adventure.text.Component;
|
|
import net.kyori.adventure.text.TranslatableComponent;
|
|
import net.kyori.adventure.text.TranslationArgument;
|
|
import net.kyori.adventure.text.event.DataComponentValue;
|
|
import net.kyori.adventure.text.event.DataComponentValueConverterRegistry;
|
|
import net.kyori.adventure.text.flattener.ComponentFlattener;
|
|
import net.kyori.adventure.text.format.Style;
|
|
import net.kyori.adventure.text.format.TextColor;
|
|
import net.kyori.adventure.text.serializer.ComponentSerializer;
|
|
import net.kyori.adventure.text.serializer.ansi.ANSIComponentSerializer;
|
|
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.util.Codec;
|
|
import net.minecraft.ChatFormatting;
|
|
import net.minecraft.commands.CommandSourceStack;
|
|
import net.minecraft.core.Holder;
|
|
import net.minecraft.core.component.DataComponentPatch;
|
|
import net.minecraft.core.component.DataComponentType;
|
|
import net.minecraft.core.component.DataComponents;
|
|
import net.minecraft.core.registries.BuiltInRegistries;
|
|
import net.minecraft.locale.Language;
|
|
import net.minecraft.nbt.CompoundTag;
|
|
import net.minecraft.nbt.NbtOps;
|
|
import net.minecraft.nbt.Tag;
|
|
import net.minecraft.nbt.TagParser;
|
|
import net.minecraft.network.chat.ComponentUtils;
|
|
import net.minecraft.network.protocol.Packet;
|
|
import net.minecraft.network.protocol.game.ClientboundSoundEntityPacket;
|
|
import net.minecraft.network.protocol.game.ClientboundSoundPacket;
|
|
import net.minecraft.resources.RegistryOps;
|
|
import net.minecraft.resources.ResourceKey;
|
|
import net.minecraft.resources.ResourceLocation;
|
|
import net.minecraft.server.network.Filterable;
|
|
import net.minecraft.sounds.SoundEvent;
|
|
import net.minecraft.sounds.SoundSource;
|
|
import net.minecraft.world.BossEvent;
|
|
import net.minecraft.world.entity.Entity;
|
|
import net.minecraft.world.item.ItemStack;
|
|
import net.minecraft.world.item.component.WrittenBookContent;
|
|
import org.bukkit.command.CommandSender;
|
|
import org.bukkit.craftbukkit.CraftRegistry;
|
|
import org.bukkit.craftbukkit.command.VanillaCommandWrapper;
|
|
import org.bukkit.craftbukkit.entity.CraftEntity;
|
|
import org.intellij.lang.annotations.Subst;
|
|
import org.jetbrains.annotations.Contract;
|
|
import org.jetbrains.annotations.NotNull;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
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) -> {
|
|
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 @NotNull String translated = Language.getInstance().getOrDefault(translatable.key(), fallback != null ? fallback : translatable.key());
|
|
|
|
final Matcher matcher = LOCALIZATION_PATTERN.matcher(translated);
|
|
final List<TranslationArgument> args = translatable.arguments();
|
|
int argPosition = 0;
|
|
int lastIdx = 0;
|
|
while (matcher.find()) {
|
|
// append prior
|
|
if (lastIdx < matcher.start()) {
|
|
consumer.accept(Component.text(translated.substring(lastIdx, matcher.start())));
|
|
}
|
|
lastIdx = matcher.end();
|
|
|
|
final @Nullable String argIdx = matcher.group(1);
|
|
// calculate argument position
|
|
if (argIdx != null) {
|
|
try {
|
|
final int idx = Integer.parseInt(argIdx) - 1;
|
|
if (idx < args.size()) {
|
|
consumer.accept(args.get(idx).asComponent());
|
|
}
|
|
} catch (final NumberFormatException ex) {
|
|
// ignore, drop the format placeholder
|
|
}
|
|
} else {
|
|
final int idx = argPosition++;
|
|
if (idx < args.size()) {
|
|
consumer.accept(args.get(idx).asComponent());
|
|
}
|
|
}
|
|
}
|
|
|
|
// append tail
|
|
if (lastIdx < translated.length()) {
|
|
consumer.accept(Component.text(translated.substring(lastIdx)));
|
|
}
|
|
})
|
|
.build();
|
|
public static final AttributeKey<Locale> LOCALE_ATTRIBUTE = AttributeKey.valueOf("adventure:locale"); // init after FLATTENER because classloading triggered here might create a logger
|
|
@Deprecated
|
|
public static final PlainComponentSerializer PLAIN = PlainComponentSerializer.builder().flattener(FLATTENER).build();
|
|
public static final ANSIComponentSerializer ANSI_SERIALIZER = ANSIComponentSerializer.builder().flattener(FLATTENER).build();
|
|
public static final Codec<Tag, String, CommandSyntaxException, RuntimeException> NBT_CODEC = new Codec<>() {
|
|
@Override
|
|
public @NotNull Tag decode(final @NotNull String encoded) throws CommandSyntaxException {
|
|
return TagParser.parseCompoundFully(encoded);
|
|
}
|
|
|
|
@Override
|
|
public @NotNull String encode(final @NotNull Tag decoded) {
|
|
return decoded.toString();
|
|
}
|
|
};
|
|
public static final ComponentSerializer<Component, Component, net.minecraft.network.chat.Component> WRAPPER_AWARE_SERIALIZER = new WrapperAwareSerializer(() -> CraftRegistry.getMinecraftRegistry().createSerializationContext(JavaOps.INSTANCE));
|
|
|
|
private PaperAdventure() {
|
|
}
|
|
|
|
// Key
|
|
|
|
public static Key asAdventure(final ResourceLocation key) {
|
|
return Key.key(key.getNamespace(), key.getPath());
|
|
}
|
|
|
|
public static ResourceLocation asVanilla(final Key key) {
|
|
return ResourceLocation.fromNamespaceAndPath(key.namespace(), key.value());
|
|
}
|
|
|
|
public static <T> ResourceKey<T> asVanilla(
|
|
final ResourceKey<? extends net.minecraft.core.Registry<T>> registry,
|
|
final Key key
|
|
) {
|
|
return ResourceKey.create(registry, asVanilla(key));
|
|
}
|
|
|
|
public static Key asAdventureKey(final ResourceKey<?> key) {
|
|
return asAdventure(key.location());
|
|
}
|
|
|
|
public static @Nullable ResourceLocation asVanillaNullable(final Key key) {
|
|
if (key == null) {
|
|
return null;
|
|
}
|
|
return asVanilla(key);
|
|
}
|
|
|
|
public static Holder<SoundEvent> resolveSound(final Key key) {
|
|
ResourceLocation id = asVanilla(key);
|
|
Optional<Holder.Reference<SoundEvent>> vanilla = BuiltInRegistries.SOUND_EVENT.get(id);
|
|
if (vanilla.isPresent()) {
|
|
return vanilla.get();
|
|
}
|
|
|
|
// sound is not known so not in the registry but might be used by the client with a resource pack
|
|
return Holder.direct(SoundEvent.createVariableRangeEvent(id));
|
|
}
|
|
|
|
// Component
|
|
|
|
public static @NotNull Component asAdventure(@Nullable final net.minecraft.network.chat.Component component) {
|
|
return component == null ? Component.empty() : WRAPPER_AWARE_SERIALIZER.deserialize(component);
|
|
}
|
|
|
|
public static ArrayList<Component> asAdventure(final List<? extends net.minecraft.network.chat.Component> vanillas) {
|
|
final ArrayList<Component> adventures = new ArrayList<>(vanillas.size());
|
|
for (final net.minecraft.network.chat.Component vanilla : vanillas) {
|
|
adventures.add(asAdventure(vanilla));
|
|
}
|
|
return adventures;
|
|
}
|
|
|
|
public static ArrayList<Component> asAdventureFromJson(final List<String> jsonStrings) {
|
|
final ArrayList<Component> adventures = new ArrayList<>(jsonStrings.size());
|
|
for (final String json : jsonStrings) {
|
|
adventures.add(GsonComponentSerializer.gson().deserialize(json));
|
|
}
|
|
return adventures;
|
|
}
|
|
|
|
public static List<String> asJson(final List<? extends Component> adventures) {
|
|
final List<String> jsons = new ArrayList<>(adventures.size());
|
|
for (final Component component : adventures) {
|
|
jsons.add(GsonComponentSerializer.gson().serialize(component));
|
|
}
|
|
return jsons;
|
|
}
|
|
|
|
public static net.minecraft.network.chat.@NotNull Component asVanillaNullToEmpty(final @Nullable Component component) {
|
|
if (component == null) return net.minecraft.network.chat.CommonComponents.EMPTY;
|
|
return asVanilla(component);
|
|
}
|
|
|
|
@Contract("null -> null; !null -> !null")
|
|
public static net.minecraft.network.chat.Component asVanilla(final @Nullable Component component) {
|
|
if (component == null) return null;
|
|
if (true) return new AdventureComponent(component);
|
|
return WRAPPER_AWARE_SERIALIZER.serialize(component);
|
|
}
|
|
|
|
public static List<net.minecraft.network.chat.Component> asVanilla(final List<? extends Component> adventures) {
|
|
final List<net.minecraft.network.chat.Component> vanillas = new ArrayList<>(adventures.size());
|
|
for (final Component adventure : adventures) {
|
|
vanillas.add(asVanilla(adventure));
|
|
}
|
|
return vanillas;
|
|
}
|
|
|
|
public static String asJsonString(final Component component, final Locale locale) {
|
|
return GsonComponentSerializer.gson().serialize(translated(component, locale));
|
|
}
|
|
|
|
public static boolean hasAnyTranslations() {
|
|
return StreamSupport.stream(GlobalTranslator.translator().sources().spliterator(), false)
|
|
.anyMatch(t -> t.hasAnyTranslations().toBooleanOrElse(true));
|
|
}
|
|
|
|
private static final Map<Locale, com.mojang.serialization.Codec<Component>> LOCALIZED_CODECS = new ConcurrentHashMap<>();
|
|
|
|
public static com.mojang.serialization.Codec<Component> localizedCodec(final @Nullable Locale l) {
|
|
if (l == null) {
|
|
return AdventureCodecs.COMPONENT_CODEC;
|
|
}
|
|
return LOCALIZED_CODECS.computeIfAbsent(l, locale -> AdventureCodecs.COMPONENT_CODEC.xmap(
|
|
component -> component, // decode
|
|
component -> translated(component, locale) // encode
|
|
));
|
|
}
|
|
|
|
public static String asPlain(final Component component, final Locale locale) {
|
|
return PlainTextComponentSerializer.plainText().serialize(translated(component, locale));
|
|
}
|
|
|
|
private static Component translated(final Component component, final Locale locale) {
|
|
//noinspection ConstantValue
|
|
return GlobalTranslator.render(
|
|
component,
|
|
// play it safe
|
|
locale != null
|
|
? locale
|
|
: Locale.US
|
|
);
|
|
}
|
|
|
|
public static Component resolveWithContext(final @NotNull Component component, final @Nullable CommandSender context, final @Nullable org.bukkit.entity.Entity scoreboardSubject, final boolean bypassPermissions) throws IOException {
|
|
final CommandSourceStack css = context != null ? VanillaCommandWrapper.getListener(context) : null;
|
|
Boolean previous = null;
|
|
if (css != null && bypassPermissions) {
|
|
previous = css.bypassSelectorPermissions;
|
|
css.bypassSelectorPermissions = true;
|
|
}
|
|
try {
|
|
return asAdventure(ComponentUtils.updateForEntity(css, asVanilla(component), scoreboardSubject == null ? null : ((CraftEntity) scoreboardSubject).getHandle(), 0));
|
|
} catch (final CommandSyntaxException e) {
|
|
throw new IOException(e);
|
|
} finally {
|
|
if (css != null && previous != null) {
|
|
css.bypassSelectorPermissions = previous;
|
|
}
|
|
}
|
|
}
|
|
|
|
// BossBar
|
|
|
|
public static BossEvent.BossBarColor asVanilla(final BossBar.Color color) {
|
|
return switch (color) {
|
|
case PINK -> BossEvent.BossBarColor.PINK;
|
|
case BLUE -> BossEvent.BossBarColor.BLUE;
|
|
case RED -> BossEvent.BossBarColor.RED;
|
|
case GREEN -> BossEvent.BossBarColor.GREEN;
|
|
case YELLOW -> BossEvent.BossBarColor.YELLOW;
|
|
case PURPLE -> BossEvent.BossBarColor.PURPLE;
|
|
case WHITE -> BossEvent.BossBarColor.WHITE;
|
|
};
|
|
}
|
|
|
|
public static BossBar.Color asAdventure(final BossEvent.BossBarColor color) {
|
|
return switch (color) {
|
|
case PINK -> BossBar.Color.PINK;
|
|
case BLUE -> BossBar.Color.BLUE;
|
|
case RED -> BossBar.Color.RED;
|
|
case GREEN -> BossBar.Color.GREEN;
|
|
case YELLOW -> BossBar.Color.YELLOW;
|
|
case PURPLE -> BossBar.Color.PURPLE;
|
|
case WHITE -> BossBar.Color.WHITE;
|
|
};
|
|
}
|
|
|
|
public static BossEvent.BossBarOverlay asVanilla(final BossBar.Overlay overlay) {
|
|
return switch (overlay) {
|
|
case PROGRESS -> BossEvent.BossBarOverlay.PROGRESS;
|
|
case NOTCHED_6 -> BossEvent.BossBarOverlay.NOTCHED_6;
|
|
case NOTCHED_10 -> BossEvent.BossBarOverlay.NOTCHED_10;
|
|
case NOTCHED_12 -> BossEvent.BossBarOverlay.NOTCHED_12;
|
|
case NOTCHED_20 -> BossEvent.BossBarOverlay.NOTCHED_20;
|
|
};
|
|
}
|
|
|
|
public static BossBar.Overlay asAdventure(final BossEvent.BossBarOverlay overlay) {
|
|
return switch (overlay) {
|
|
case PROGRESS -> BossBar.Overlay.PROGRESS;
|
|
case NOTCHED_6 -> BossBar.Overlay.NOTCHED_6;
|
|
case NOTCHED_10 -> BossBar.Overlay.NOTCHED_10;
|
|
case NOTCHED_12 -> BossBar.Overlay.NOTCHED_12;
|
|
case NOTCHED_20 -> BossBar.Overlay.NOTCHED_20;
|
|
};
|
|
}
|
|
|
|
public static void setFlag(final BossBar bar, final BossBar.Flag flag, final boolean value) {
|
|
if (value) {
|
|
bar.addFlag(flag);
|
|
} else {
|
|
bar.removeFlag(flag);
|
|
}
|
|
}
|
|
|
|
// Book
|
|
|
|
public static ItemStack asItemStack(final Book book, final Locale locale) {
|
|
final ItemStack item = new ItemStack(net.minecraft.world.item.Items.WRITTEN_BOOK, 1);
|
|
item.set(DataComponents.WRITTEN_BOOK_CONTENT, new WrittenBookContent(
|
|
Filterable.passThrough(validateField(asPlain(book.title(), locale), WrittenBookContent.TITLE_MAX_LENGTH, "title")),
|
|
asPlain(book.author(), locale),
|
|
0,
|
|
book.pages().stream().map(c -> Filterable.passThrough(PaperAdventure.asVanilla(c))).toList(), // TODO should we validate legnth?
|
|
false
|
|
));
|
|
return item;
|
|
}
|
|
|
|
private static String validateField(final String content, final int length, final String name) {
|
|
final int actual = content.length();
|
|
if (actual > length) {
|
|
throw new IllegalArgumentException("Field '" + name + "' has a maximum length of " + length + " but was passed '" + content + "', which was " + actual + " characters long.");
|
|
}
|
|
return content;
|
|
}
|
|
|
|
// Sounds
|
|
|
|
public static SoundSource asVanilla(final Sound.Source source) {
|
|
return switch (source) {
|
|
case MASTER -> SoundSource.MASTER;
|
|
case MUSIC -> SoundSource.MUSIC;
|
|
case RECORD -> SoundSource.RECORDS;
|
|
case WEATHER -> SoundSource.WEATHER;
|
|
case BLOCK -> SoundSource.BLOCKS;
|
|
case HOSTILE -> SoundSource.HOSTILE;
|
|
case NEUTRAL -> SoundSource.NEUTRAL;
|
|
case PLAYER -> SoundSource.PLAYERS;
|
|
case AMBIENT -> SoundSource.AMBIENT;
|
|
case VOICE -> SoundSource.VOICE;
|
|
case UI -> SoundSource.UI;
|
|
};
|
|
}
|
|
|
|
public static @Nullable SoundSource asVanillaNullable(final Sound.@Nullable Source source) {
|
|
if (source == null) {
|
|
return null;
|
|
}
|
|
return asVanilla(source);
|
|
}
|
|
|
|
public static Packet<?> asSoundPacket(final Sound sound, final double x, final double y, final double z, final long seed, @Nullable BiConsumer<Packet<?>, Float> packetConsumer) {
|
|
final ResourceLocation name = asVanilla(sound.name());
|
|
final Optional<SoundEvent> soundEvent = BuiltInRegistries.SOUND_EVENT.getOptional(name);
|
|
final SoundSource source = asVanilla(sound.source());
|
|
|
|
final Holder<SoundEvent> soundEventHolder = soundEvent.map(BuiltInRegistries.SOUND_EVENT::wrapAsHolder).orElseGet(() -> Holder.direct(SoundEvent.createVariableRangeEvent(name)));
|
|
final Packet<?> packet = new ClientboundSoundPacket(soundEventHolder, source, x, y, z, sound.volume(), sound.pitch(), seed);
|
|
if (packetConsumer != null) {
|
|
packetConsumer.accept(packet, soundEventHolder.value().getRange(sound.volume()));
|
|
}
|
|
return packet;
|
|
}
|
|
|
|
public static Packet<?> asSoundPacket(final Sound sound, final Entity emitter, final long seed, @Nullable BiConsumer<Packet<?>, Float> packetConsumer) {
|
|
final ResourceLocation name = asVanilla(sound.name());
|
|
final Optional<SoundEvent> soundEvent = BuiltInRegistries.SOUND_EVENT.getOptional(name);
|
|
final SoundSource source = asVanilla(sound.source());
|
|
|
|
final Holder<SoundEvent> soundEventHolder = soundEvent.map(BuiltInRegistries.SOUND_EVENT::wrapAsHolder).orElseGet(() -> Holder.direct(SoundEvent.createVariableRangeEvent(name)));
|
|
final Packet<?> packet = new ClientboundSoundEntityPacket(soundEventHolder, source, emitter, sound.volume(), sound.pitch(), seed);
|
|
if (packetConsumer != null) {
|
|
packetConsumer.accept(packet, soundEventHolder.value().getRange(sound.volume()));
|
|
}
|
|
return packet;
|
|
}
|
|
|
|
// NBT
|
|
|
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
|
public static Map<Key, ? extends DataComponentValue> asAdventure(
|
|
final DataComponentPatch patch
|
|
) {
|
|
if (patch.isEmpty()) {
|
|
return Collections.emptyMap();
|
|
}
|
|
final Map<Key, DataComponentValue> map = new HashMap<>();
|
|
for (final Map.Entry<DataComponentType<?>, Optional<?>> entry : patch.entrySet()) {
|
|
if (entry.getKey().isTransient()) continue;
|
|
@Subst("key:value") final String typeKey = requireNonNull(BuiltInRegistries.DATA_COMPONENT_TYPE.getKey(entry.getKey())).toString();
|
|
if (entry.getValue().isEmpty()) {
|
|
map.put(Key.key(typeKey), DataComponentValue.removed());
|
|
} else {
|
|
map.put(Key.key(typeKey), new DataComponentValueImpl(entry.getKey().codec(), entry.getValue().get()));
|
|
}
|
|
}
|
|
return map;
|
|
}
|
|
|
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
|
public static DataComponentPatch asVanilla(final Map<? extends Key, ? extends DataComponentValue> map) {
|
|
if (map.isEmpty()) {
|
|
return DataComponentPatch.EMPTY;
|
|
}
|
|
final DataComponentPatch.Builder builder = DataComponentPatch.builder();
|
|
map.forEach((key, dataComponentValue) -> {
|
|
final DataComponentType<?> type = requireNonNull(BuiltInRegistries.DATA_COMPONENT_TYPE.getValue(asVanilla(key)));
|
|
if (dataComponentValue instanceof DataComponentValue.Removed) {
|
|
builder.remove(type);
|
|
return;
|
|
}
|
|
final DataComponentValueImpl<?> converted = DataComponentValueConverterRegistry.convert(DataComponentValueImpl.class, key, dataComponentValue);
|
|
builder.set((DataComponentType) type, (Object) converted.value());
|
|
});
|
|
return builder.build();
|
|
}
|
|
|
|
public record DataComponentValueImpl<T>(com.mojang.serialization.Codec<T> codec, T value) implements DataComponentValue.TagSerializable {
|
|
|
|
@Override
|
|
public @NotNull BinaryTagHolder asBinaryTag() {
|
|
return BinaryTagHolder.encode(this.codec.encodeStart(CraftRegistry.getMinecraftRegistry().createSerializationContext(NbtOps.INSTANCE), this.value).getOrThrow(IllegalArgumentException::new), NBT_CODEC);
|
|
}
|
|
}
|
|
|
|
public static @Nullable BinaryTagHolder asBinaryTagHolder(final @Nullable CompoundTag tag) {
|
|
if (tag == null) {
|
|
return null;
|
|
}
|
|
return BinaryTagHolder.encode(tag, NBT_CODEC);
|
|
}
|
|
|
|
// Colors
|
|
|
|
public static @NotNull TextColor asAdventure(final ChatFormatting formatting) {
|
|
final Integer color = formatting.getColor();
|
|
if (color == null) {
|
|
throw new IllegalArgumentException("Not a valid color");
|
|
}
|
|
return TextColor.color(color);
|
|
}
|
|
|
|
public static @Nullable ChatFormatting asVanilla(final TextColor color) {
|
|
return ChatFormatting.getByHexValue(color.value());
|
|
}
|
|
|
|
// Style
|
|
|
|
public static net.minecraft.network.chat.Style asVanilla(final Style style) {
|
|
final RegistryOps<Object> ops = CraftRegistry.getMinecraftRegistry().createSerializationContext(JavaOps.INSTANCE);
|
|
final Object encoded = AdventureCodecs.STYLE_MAP_CODEC.codec()
|
|
.encodeStart(ops, style).getOrThrow(IllegalStateException::new);
|
|
|
|
return net.minecraft.network.chat.Style.Serializer.CODEC
|
|
.parse(ops, encoded).getOrThrow(IllegalStateException::new);
|
|
}
|
|
|
|
public static Style asAdventure(final net.minecraft.network.chat.Style style) {
|
|
final RegistryOps<Object> ops = CraftRegistry.getMinecraftRegistry().createSerializationContext(JavaOps.INSTANCE);
|
|
final Object encoded = net.minecraft.network.chat.Style.Serializer.CODEC
|
|
.encodeStart(ops, style).getOrThrow(IllegalStateException::new);
|
|
|
|
return AdventureCodecs.STYLE_MAP_CODEC.codec()
|
|
.parse(ops, encoded).getOrThrow(IllegalStateException::new);
|
|
}
|
|
}
|