minecraft-src/net/minecraft/network/chat/Style.java
2025-07-04 01:41:11 +03:00

508 lines
15 KiB
Java

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 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);
public static final ResourceLocation DEFAULT_FONT = ResourceLocation.withDefaultNamespace("default");
@Nullable
final TextColor color;
@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<TextColor> color,
Optional<Boolean> bold,
Optional<Boolean> italic,
Optional<Boolean> underlined,
Optional<Boolean> strikethrough,
Optional<Boolean> obfuscated,
Optional<ClickEvent> clickEvent,
Optional<HoverEvent> hoverEvent,
Optional<String> insertion,
Optional<ResourceLocation> font
) {
Style style = new Style(
(TextColor)color.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 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.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;
}
/**
* 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 <T> 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.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 rgb) {
return this.withColor(TextColor.fromRgb(rgb));
}
public Style withBold(@Nullable Boolean bold) {
return Objects.equals(this.bold, bold)
? this
: checkEmptyAfterChange(
new Style(this.color, 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.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.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.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.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.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.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.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.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, 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, 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, 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.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.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())
&& 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.bold, this.italic, this.underlined, this.strikethrough, this.obfuscated, this.clickEvent, this.hoverEvent, this.insertion}
);
}
public static class Serializer {
public static final MapCodec<Style> MAP_CODEC = RecordCodecBuilder.mapCodec(
instance -> instance.group(
TextColor.CODEC.optionalFieldOf("color").forGetter(style -> Optional.ofNullable(style.color)),
Codec.BOOL.optionalFieldOf("bold").forGetter(style -> Optional.ofNullable(style.bold)),
Codec.BOOL.optionalFieldOf("italic").forGetter(style -> Optional.ofNullable(style.italic)),
Codec.BOOL.optionalFieldOf("underlined").forGetter(style -> Optional.ofNullable(style.underlined)),
Codec.BOOL.optionalFieldOf("strikethrough").forGetter(style -> Optional.ofNullable(style.strikethrough)),
Codec.BOOL.optionalFieldOf("obfuscated").forGetter(style -> Optional.ofNullable(style.obfuscated)),
ClickEvent.CODEC.optionalFieldOf("clickEvent").forGetter(style -> Optional.ofNullable(style.clickEvent)),
HoverEvent.CODEC.optionalFieldOf("hoverEvent").forGetter(style -> Optional.ofNullable(style.hoverEvent)),
Codec.STRING.optionalFieldOf("insertion").forGetter(style -> Optional.ofNullable(style.insertion)),
ResourceLocation.CODEC.optionalFieldOf("font").forGetter(style -> Optional.ofNullable(style.font))
)
.apply(instance, Style::create)
);
public static final Codec<Style> CODEC = MAP_CODEC.codec();
public static final StreamCodec<RegistryFriendlyByteBuf, Style> TRUSTED_STREAM_CODEC = ByteBufCodecs.fromCodecWithRegistriesTrusted(CODEC);
}
}