package net.minecraft.network.chat; import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; import java.util.Objects; import java.util.Optional; import net.minecraft.ChatFormatting; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.ExtraCodecs; import org.jetbrains.annotations.Nullable; /** * A Style for {@link Component}. * Stores color, text formatting (bold, etc.) as well as possible HoverEvent/ClickEvent. */ public class Style { public static final Style EMPTY = new Style(null, null, null, null, null, null, null, null, null, null, null); public static final ResourceLocation DEFAULT_FONT = ResourceLocation.withDefaultNamespace("default"); @Nullable final TextColor color; @Nullable final Integer shadowColor; @Nullable final Boolean bold; @Nullable final Boolean italic; @Nullable final Boolean underlined; @Nullable final Boolean strikethrough; @Nullable final Boolean obfuscated; @Nullable final ClickEvent clickEvent; @Nullable final HoverEvent hoverEvent; @Nullable final String insertion; @Nullable final ResourceLocation font; private static Style create( Optional color, Optional shadowColor, Optional bold, Optional italic, Optional underlined, Optional strikethrough, Optional obfuscated, Optional clickEvent, Optional hoverEvent, Optional insertion, Optional font ) { Style style = new Style( (TextColor)color.orElse(null), (Integer)shadowColor.orElse(null), (Boolean)bold.orElse(null), (Boolean)italic.orElse(null), (Boolean)underlined.orElse(null), (Boolean)strikethrough.orElse(null), (Boolean)obfuscated.orElse(null), (ClickEvent)clickEvent.orElse(null), (HoverEvent)hoverEvent.orElse(null), (String)insertion.orElse(null), (ResourceLocation)font.orElse(null) ); return style.equals(EMPTY) ? EMPTY : style; } private Style( @Nullable TextColor color, @Nullable Integer shadowColor, @Nullable Boolean bold, @Nullable Boolean italic, @Nullable Boolean underlined, @Nullable Boolean strikethrough, @Nullable Boolean obfuscated, @Nullable ClickEvent clickEvent, @Nullable HoverEvent hoverEvent, @Nullable String insertion, @Nullable ResourceLocation font ) { this.color = color; this.shadowColor = shadowColor; this.bold = bold; this.italic = italic; this.underlined = underlined; this.strikethrough = strikethrough; this.obfuscated = obfuscated; this.clickEvent = clickEvent; this.hoverEvent = hoverEvent; this.insertion = insertion; this.font = font; } @Nullable public TextColor getColor() { return this.color; } @Nullable public Integer getShadowColor() { return this.shadowColor; } /** * Whether text of this ChatStyle should be in bold. */ public boolean isBold() { return this.bold == Boolean.TRUE; } /** * Whether text of this ChatStyle should be italicized. */ public boolean isItalic() { return this.italic == Boolean.TRUE; } /** * Whether to format text of this ChatStyle using strikethrough. */ public boolean isStrikethrough() { return this.strikethrough == Boolean.TRUE; } /** * Whether text of this ChatStyle should be underlined. */ public boolean isUnderlined() { return this.underlined == Boolean.TRUE; } /** * Whether text of this ChatStyle should be obfuscated. */ public boolean isObfuscated() { return this.obfuscated == Boolean.TRUE; } /** * Whether this style is empty (inherits everything from the parent). */ public boolean isEmpty() { return this == EMPTY; } /** * The effective chat click event. */ @Nullable public ClickEvent getClickEvent() { return this.clickEvent; } /** * The effective chat hover event. */ @Nullable public HoverEvent getHoverEvent() { return this.hoverEvent; } /** * Get the text to be inserted into Chat when the component is shift-clicked */ @Nullable public String getInsertion() { return this.insertion; } /** * The font to use for this Style */ public ResourceLocation getFont() { return this.font != null ? this.font : DEFAULT_FONT; } private static Style checkEmptyAfterChange(Style style, @Nullable T oldValue, @Nullable T newValue) { return oldValue != null && newValue == null && style.equals(EMPTY) ? EMPTY : style; } public Style withColor(@Nullable TextColor color) { return Objects.equals(this.color, color) ? this : checkEmptyAfterChange( new Style( color, this.shadowColor, this.bold, this.italic, this.underlined, this.strikethrough, this.obfuscated, this.clickEvent, this.hoverEvent, this.insertion, this.font ), this.color, color ); } public Style withColor(@Nullable ChatFormatting formatting) { return this.withColor(formatting != null ? TextColor.fromLegacyFormat(formatting) : null); } public Style withColor(int color) { return this.withColor(TextColor.fromRgb(color)); } public Style withShadowColor(int color) { return checkEmptyAfterChange( new Style( this.color, color, this.bold, this.italic, this.underlined, this.strikethrough, this.obfuscated, this.clickEvent, this.hoverEvent, this.insertion, this.font ), this.shadowColor, color ); } public Style withBold(@Nullable Boolean bold) { return Objects.equals(this.bold, bold) ? this : checkEmptyAfterChange( new Style( this.color, this.shadowColor, bold, this.italic, this.underlined, this.strikethrough, this.obfuscated, this.clickEvent, this.hoverEvent, this.insertion, this.font ), this.bold, bold ); } public Style withItalic(@Nullable Boolean italic) { return Objects.equals(this.italic, italic) ? this : checkEmptyAfterChange( new Style( this.color, this.shadowColor, this.bold, italic, this.underlined, this.strikethrough, this.obfuscated, this.clickEvent, this.hoverEvent, this.insertion, this.font ), this.italic, italic ); } public Style withUnderlined(@Nullable Boolean underlined) { return Objects.equals(this.underlined, underlined) ? this : checkEmptyAfterChange( new Style( this.color, this.shadowColor, this.bold, this.italic, underlined, this.strikethrough, this.obfuscated, this.clickEvent, this.hoverEvent, this.insertion, this.font ), this.underlined, underlined ); } public Style withStrikethrough(@Nullable Boolean strikethrough) { return Objects.equals(this.strikethrough, strikethrough) ? this : checkEmptyAfterChange( new Style( this.color, this.shadowColor, this.bold, this.italic, this.underlined, strikethrough, this.obfuscated, this.clickEvent, this.hoverEvent, this.insertion, this.font ), this.strikethrough, strikethrough ); } public Style withObfuscated(@Nullable Boolean obfuscated) { return Objects.equals(this.obfuscated, obfuscated) ? this : checkEmptyAfterChange( new Style( this.color, this.shadowColor, this.bold, this.italic, this.underlined, this.strikethrough, obfuscated, this.clickEvent, this.hoverEvent, this.insertion, this.font ), this.obfuscated, obfuscated ); } public Style withClickEvent(@Nullable ClickEvent clickEvent) { return Objects.equals(this.clickEvent, clickEvent) ? this : checkEmptyAfterChange( new Style( this.color, this.shadowColor, this.bold, this.italic, this.underlined, this.strikethrough, this.obfuscated, clickEvent, this.hoverEvent, this.insertion, this.font ), this.clickEvent, clickEvent ); } public Style withHoverEvent(@Nullable HoverEvent hoverEvent) { return Objects.equals(this.hoverEvent, hoverEvent) ? this : checkEmptyAfterChange( new Style( this.color, this.shadowColor, this.bold, this.italic, this.underlined, this.strikethrough, this.obfuscated, this.clickEvent, hoverEvent, this.insertion, this.font ), this.hoverEvent, hoverEvent ); } public Style withInsertion(@Nullable String insertion) { return Objects.equals(this.insertion, insertion) ? this : checkEmptyAfterChange( new Style( this.color, this.shadowColor, this.bold, this.italic, this.underlined, this.strikethrough, this.obfuscated, this.clickEvent, this.hoverEvent, insertion, this.font ), this.insertion, insertion ); } public Style withFont(@Nullable ResourceLocation fontId) { return Objects.equals(this.font, fontId) ? this : checkEmptyAfterChange( new Style( this.color, this.shadowColor, this.bold, this.italic, this.underlined, this.strikethrough, this.obfuscated, this.clickEvent, this.hoverEvent, this.insertion, fontId ), this.font, fontId ); } public Style applyFormat(ChatFormatting formatting) { TextColor textColor = this.color; Boolean boolean_ = this.bold; Boolean boolean2 = this.italic; Boolean boolean3 = this.strikethrough; Boolean boolean4 = this.underlined; Boolean boolean5 = this.obfuscated; switch (formatting) { case OBFUSCATED: boolean5 = true; break; case BOLD: boolean_ = true; break; case STRIKETHROUGH: boolean3 = true; break; case UNDERLINE: boolean4 = true; break; case ITALIC: boolean2 = true; break; case RESET: return EMPTY; default: textColor = TextColor.fromLegacyFormat(formatting); } return new Style(textColor, this.shadowColor, boolean_, boolean2, boolean4, boolean3, boolean5, this.clickEvent, this.hoverEvent, this.insertion, this.font); } public Style applyLegacyFormat(ChatFormatting formatting) { TextColor textColor = this.color; Boolean boolean_ = this.bold; Boolean boolean2 = this.italic; Boolean boolean3 = this.strikethrough; Boolean boolean4 = this.underlined; Boolean boolean5 = this.obfuscated; switch (formatting) { case OBFUSCATED: boolean5 = true; break; case BOLD: boolean_ = true; break; case STRIKETHROUGH: boolean3 = true; break; case UNDERLINE: boolean4 = true; break; case ITALIC: boolean2 = true; break; case RESET: return EMPTY; default: boolean5 = false; boolean_ = false; boolean3 = false; boolean4 = false; boolean2 = false; textColor = TextColor.fromLegacyFormat(formatting); } return new Style(textColor, this.shadowColor, boolean_, boolean2, boolean4, boolean3, boolean5, this.clickEvent, this.hoverEvent, this.insertion, this.font); } public Style applyFormats(ChatFormatting... formats) { TextColor textColor = this.color; Boolean boolean_ = this.bold; Boolean boolean2 = this.italic; Boolean boolean3 = this.strikethrough; Boolean boolean4 = this.underlined; Boolean boolean5 = this.obfuscated; for (ChatFormatting chatFormatting : formats) { switch (chatFormatting) { case OBFUSCATED: boolean5 = true; break; case BOLD: boolean_ = true; break; case STRIKETHROUGH: boolean3 = true; break; case UNDERLINE: boolean4 = true; break; case ITALIC: boolean2 = true; break; case RESET: return EMPTY; default: textColor = TextColor.fromLegacyFormat(chatFormatting); } } return new Style(textColor, this.shadowColor, boolean_, boolean2, boolean4, boolean3, boolean5, this.clickEvent, this.hoverEvent, this.insertion, this.font); } /** * Merges the style with another one. If either style is empty the other will be returned. If a value already exists on the current style it will not be overridden. */ public Style applyTo(Style style) { if (this == EMPTY) { return style; } else { return style == EMPTY ? this : new Style( this.color != null ? this.color : style.color, this.shadowColor != null ? this.shadowColor : style.shadowColor, this.bold != null ? this.bold : style.bold, this.italic != null ? this.italic : style.italic, this.underlined != null ? this.underlined : style.underlined, this.strikethrough != null ? this.strikethrough : style.strikethrough, this.obfuscated != null ? this.obfuscated : style.obfuscated, this.clickEvent != null ? this.clickEvent : style.clickEvent, this.hoverEvent != null ? this.hoverEvent : style.hoverEvent, this.insertion != null ? this.insertion : style.insertion, this.font != null ? this.font : style.font ); } } public String toString() { final StringBuilder stringBuilder = new StringBuilder("{"); class Collector { private boolean isNotFirst; private void prependSeparator() { if (this.isNotFirst) { stringBuilder.append(','); } this.isNotFirst = true; } void addFlagString(String key, @Nullable Boolean value) { if (value != null) { this.prependSeparator(); if (!value) { stringBuilder.append('!'); } stringBuilder.append(key); } } void addValueString(String key, @Nullable Object value) { if (value != null) { this.prependSeparator(); stringBuilder.append(key); stringBuilder.append('='); stringBuilder.append(value); } } } Collector lv = new Collector(); lv.addValueString("color", this.color); lv.addValueString("shadowColor", this.shadowColor); lv.addFlagString("bold", this.bold); lv.addFlagString("italic", this.italic); lv.addFlagString("underlined", this.underlined); lv.addFlagString("strikethrough", this.strikethrough); lv.addFlagString("obfuscated", this.obfuscated); lv.addValueString("clickEvent", this.clickEvent); lv.addValueString("hoverEvent", this.hoverEvent); lv.addValueString("insertion", this.insertion); lv.addValueString("font", this.font); stringBuilder.append("}"); return stringBuilder.toString(); } public boolean equals(Object object) { if (this == object) { return true; } else { return !(object instanceof Style style) ? false : this.bold == style.bold && Objects.equals(this.getColor(), style.getColor()) && Objects.equals(this.getShadowColor(), style.getShadowColor()) && this.italic == style.italic && this.obfuscated == style.obfuscated && this.strikethrough == style.strikethrough && this.underlined == style.underlined && Objects.equals(this.clickEvent, style.clickEvent) && Objects.equals(this.hoverEvent, style.hoverEvent) && Objects.equals(this.insertion, style.insertion) && Objects.equals(this.font, style.font); } } public int hashCode() { return Objects.hash( new Object[]{ this.color, this.shadowColor, this.bold, this.italic, this.underlined, this.strikethrough, this.obfuscated, this.clickEvent, this.hoverEvent, this.insertion } ); } public static class Serializer { public static final MapCodec