2022-07-31 20:41:56 +02:00
|
|
|
/*
|
2024-04-17 17:23:43 +02:00
|
|
|
* This file is part of FalsePatternLib.
|
|
|
|
|
*
|
2025-05-24 19:29:12 +02:00
|
|
|
* Copyright (C) 2022-2025 FalsePattern
|
2022-07-30 15:21:11 +02:00
|
|
|
* All Rights Reserved
|
|
|
|
|
*
|
2024-04-17 17:23:43 +02:00
|
|
|
* The above copyright notice and this permission notice shall be included
|
|
|
|
|
* in all copies or substantial portions of the Software.
|
2022-07-30 15:21:11 +02:00
|
|
|
*
|
2024-04-17 17:23:43 +02:00
|
|
|
* FalsePatternLib is free software: you can redistribute it and/or modify
|
2022-07-30 15:21:11 +02:00
|
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
2025-05-24 19:29:12 +02:00
|
|
|
* the Free Software Foundation, only version 3 of the License.
|
2022-07-30 15:21:11 +02:00
|
|
|
*
|
2024-04-17 17:23:43 +02:00
|
|
|
* FalsePatternLib is distributed in the hope that it will be useful,
|
2022-07-30 15:21:11 +02:00
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
2024-04-17 17:23:43 +02:00
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
* GNU Lesser General Public License for more details.
|
2022-07-30 15:21:11 +02:00
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU Lesser General Public License
|
2024-04-17 17:23:43 +02:00
|
|
|
* along with FalsePatternLib. If not, see <https://www.gnu.org/licenses/>.
|
2022-07-30 15:21:11 +02:00
|
|
|
*/
|
2022-05-06 12:37:14 +02:00
|
|
|
package com.falsepattern.lib.text;
|
|
|
|
|
|
2022-05-06 23:53:11 +02:00
|
|
|
import com.falsepattern.lib.StableAPI;
|
2022-05-06 12:37:14 +02:00
|
|
|
import lombok.NonNull;
|
|
|
|
|
import lombok.val;
|
2022-05-07 11:46:23 +02:00
|
|
|
import lombok.var;
|
2022-07-31 20:41:56 +02:00
|
|
|
|
2022-05-06 18:02:36 +02:00
|
|
|
import net.minecraft.client.entity.EntityOtherPlayerMP;
|
|
|
|
|
import net.minecraft.client.entity.EntityPlayerSP;
|
2022-05-06 12:37:14 +02:00
|
|
|
import net.minecraft.client.gui.FontRenderer;
|
2022-05-06 18:02:36 +02:00
|
|
|
import net.minecraft.command.ICommandSender;
|
|
|
|
|
import net.minecraft.command.server.CommandBlockLogic;
|
|
|
|
|
import net.minecraft.entity.player.EntityPlayerMP;
|
|
|
|
|
import net.minecraft.network.rcon.RConConsoleSource;
|
|
|
|
|
import net.minecraft.server.MinecraftServer;
|
2022-05-06 12:37:14 +02:00
|
|
|
import net.minecraft.util.ChatComponentText;
|
|
|
|
|
import net.minecraft.util.ChatStyle;
|
|
|
|
|
import net.minecraft.util.EnumChatFormatting;
|
2022-05-07 11:46:23 +02:00
|
|
|
import net.minecraft.util.IChatComponent;
|
2022-07-31 20:41:56 +02:00
|
|
|
import cpw.mods.fml.relauncher.Side;
|
|
|
|
|
import cpw.mods.fml.relauncher.SideOnly;
|
|
|
|
|
|
|
|
|
|
import java.awt.Color;
|
|
|
|
|
import java.lang.reflect.Field;
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
import java.util.HashSet;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
import java.util.Set;
|
|
|
|
|
import java.util.function.Consumer;
|
2022-05-06 12:37:14 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Universal escape sequence-based text rendering and chat messages.
|
|
|
|
|
*/
|
2022-05-06 23:53:11 +02:00
|
|
|
@StableAPI(since = "0.6.0")
|
2022-05-06 12:37:14 +02:00
|
|
|
public final class FormattedText {
|
|
|
|
|
private static final Map<Character, EnumChatFormatting> reverseMap = new HashMap<>();
|
|
|
|
|
private static final Map<EnumChatFormatting, Color> colorMap = new HashMap<>();
|
|
|
|
|
|
|
|
|
|
private static final char ESCAPE = '\u00a7';
|
2022-06-08 16:01:06 +02:00
|
|
|
|
2022-05-06 12:37:14 +02:00
|
|
|
static {
|
2022-06-08 16:01:06 +02:00
|
|
|
for (val value : EnumChatFormatting.values()) {
|
2022-05-06 12:37:14 +02:00
|
|
|
reverseMap.put(value.getFormattingCode(), value);
|
|
|
|
|
if (value.isColor()) {
|
|
|
|
|
final int rgb;
|
|
|
|
|
switch (value.getFormattingCode()) {
|
2022-06-08 16:01:06 +02:00
|
|
|
default:
|
|
|
|
|
rgb = 0x000000;
|
|
|
|
|
break;
|
|
|
|
|
case '1':
|
|
|
|
|
rgb = 0x0000AA;
|
|
|
|
|
break;
|
|
|
|
|
case '2':
|
|
|
|
|
rgb = 0x00AA00;
|
|
|
|
|
break;
|
|
|
|
|
case '3':
|
|
|
|
|
rgb = 0x00AAAA;
|
|
|
|
|
break;
|
|
|
|
|
case '4':
|
|
|
|
|
rgb = 0xAA0000;
|
|
|
|
|
break;
|
|
|
|
|
case '5':
|
|
|
|
|
rgb = 0xAA00AA;
|
|
|
|
|
break;
|
|
|
|
|
case '6':
|
|
|
|
|
rgb = 0xFFAA00;
|
|
|
|
|
break;
|
|
|
|
|
case '7':
|
|
|
|
|
rgb = 0xAAAAAA;
|
|
|
|
|
break;
|
|
|
|
|
case '8':
|
|
|
|
|
rgb = 0x555555;
|
|
|
|
|
break;
|
|
|
|
|
case '9':
|
|
|
|
|
rgb = 0x5555FF;
|
|
|
|
|
break;
|
|
|
|
|
case 'a':
|
|
|
|
|
rgb = 0x55FF55;
|
|
|
|
|
break;
|
|
|
|
|
case 'b':
|
|
|
|
|
rgb = 0x55FFFF;
|
|
|
|
|
break;
|
|
|
|
|
case 'c':
|
|
|
|
|
rgb = 0xFF5555;
|
|
|
|
|
break;
|
|
|
|
|
case 'd':
|
|
|
|
|
rgb = 0xFF55FF;
|
|
|
|
|
break;
|
|
|
|
|
case 'e':
|
|
|
|
|
rgb = 0xFFFF55;
|
|
|
|
|
break;
|
|
|
|
|
case 'f':
|
|
|
|
|
rgb = 0xFFFFFF;
|
|
|
|
|
break;
|
2022-05-06 12:37:14 +02:00
|
|
|
}
|
|
|
|
|
colorMap.put(value, new Color(rgb));
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
Field colorF = Color.class.getDeclaredField(value.getFriendlyName());
|
2022-06-08 16:01:06 +02:00
|
|
|
colorMap.put(value, (Color) colorF.get(null));
|
|
|
|
|
} catch (NoSuchFieldException | IllegalAccessException ignored) {
|
|
|
|
|
}
|
2022-05-06 12:37:14 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private final String text;
|
|
|
|
|
|
|
|
|
|
private final EnumChatFormatting colorStyle;
|
|
|
|
|
private final Set<EnumChatFormatting> fancyStyles = new HashSet<>();
|
|
|
|
|
|
|
|
|
|
private final List<FormattedText> siblings = new ArrayList<>();
|
|
|
|
|
|
|
|
|
|
private final boolean endLine;
|
|
|
|
|
|
2022-07-31 20:41:56 +02:00
|
|
|
private FormattedText(@NonNull String text, @NonNull EnumChatFormatting colorStyle, @NonNull Set<EnumChatFormatting> fancyStyles, boolean endLine) {
|
2022-05-06 12:37:14 +02:00
|
|
|
this.text = text;
|
|
|
|
|
this.fancyStyles.addAll(fancyStyles);
|
|
|
|
|
this.colorStyle = colorStyle;
|
|
|
|
|
this.endLine = endLine;
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-06 13:31:07 +02:00
|
|
|
/**
|
|
|
|
|
* Parse a string with minecraft style escapes (\u00a7X) into a {@link FormattedText} instance, which can then be
|
|
|
|
|
* used to generate chat or render-able text.
|
2022-06-08 16:01:06 +02:00
|
|
|
*
|
2022-05-06 13:31:07 +02:00
|
|
|
* @param text The string to parse
|
2022-06-08 16:01:06 +02:00
|
|
|
*
|
2022-05-06 13:31:07 +02:00
|
|
|
* @return The parsed text structure
|
|
|
|
|
*/
|
2022-08-06 13:12:55 +02:00
|
|
|
@StableAPI.Expose
|
2022-05-06 12:37:14 +02:00
|
|
|
public static FormattedText parse(String text) {
|
|
|
|
|
EnumChatFormatting currentColorStyle = EnumChatFormatting.WHITE;
|
|
|
|
|
val currentFancyStyle = new HashSet<EnumChatFormatting>();
|
|
|
|
|
FormattedText result = null;
|
|
|
|
|
val accumulator = new StringBuilder();
|
|
|
|
|
int length = text.length();
|
|
|
|
|
boolean format = false;
|
|
|
|
|
for (int i = 0; i < length; i++) {
|
|
|
|
|
char c = text.charAt(i);
|
|
|
|
|
if (format) {
|
|
|
|
|
val f = reverseMap.get(c);
|
2022-06-08 16:01:06 +02:00
|
|
|
if (f == null) {
|
2022-05-06 12:37:14 +02:00
|
|
|
continue;
|
2022-06-08 16:01:06 +02:00
|
|
|
}
|
2022-05-06 12:37:14 +02:00
|
|
|
if (f == EnumChatFormatting.RESET) {
|
|
|
|
|
currentColorStyle = EnumChatFormatting.WHITE;
|
|
|
|
|
currentFancyStyle.clear();
|
|
|
|
|
} else if (f.isColor()) {
|
|
|
|
|
currentColorStyle = f;
|
|
|
|
|
} else if (f.isFancyStyling()) {
|
|
|
|
|
currentFancyStyle.add(f);
|
|
|
|
|
}
|
|
|
|
|
format = false;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (c == ESCAPE || c == '\n') {
|
|
|
|
|
format = c == ESCAPE;
|
|
|
|
|
val txt = new FormattedText(accumulator.toString(), currentColorStyle, currentFancyStyle, c == '\n');
|
2022-06-08 16:01:06 +02:00
|
|
|
if (result == null) {
|
2022-05-06 12:37:14 +02:00
|
|
|
result = txt;
|
2022-06-08 16:01:06 +02:00
|
|
|
} else {
|
2022-05-06 12:37:14 +02:00
|
|
|
result.siblings.add(txt);
|
2022-06-08 16:01:06 +02:00
|
|
|
}
|
2022-05-06 12:37:14 +02:00
|
|
|
accumulator.setLength(0);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
accumulator.append(c);
|
|
|
|
|
}
|
|
|
|
|
if (accumulator.length() > 0) {
|
|
|
|
|
val txt = new FormattedText(accumulator.toString(), currentColorStyle, currentFancyStyle, true);
|
2022-06-08 16:01:06 +02:00
|
|
|
if (result == null) {
|
2022-05-06 12:37:14 +02:00
|
|
|
result = txt;
|
2022-06-08 16:01:06 +02:00
|
|
|
} else {
|
2022-05-06 12:37:14 +02:00
|
|
|
result.siblings.add(txt);
|
2022-06-08 16:01:06 +02:00
|
|
|
}
|
2022-05-06 12:37:14 +02:00
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-06 13:12:55 +02:00
|
|
|
@StableAPI.Expose
|
2022-06-08 16:01:06 +02:00
|
|
|
public void addChatMessage(ICommandSender target) {
|
|
|
|
|
addChatMessage(target::addChatMessage);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void addChatMessage(Consumer<IChatComponent> consumer) {
|
|
|
|
|
for (val line : toChatText()) {
|
|
|
|
|
consumer.accept(line);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-06 13:31:07 +02:00
|
|
|
/**
|
2022-06-08 16:01:06 +02:00
|
|
|
* Converts this text structure into chat components that can be sent to the client. This is a list, because chat
|
|
|
|
|
* components can't have newlines.
|
|
|
|
|
*
|
2022-05-06 13:31:07 +02:00
|
|
|
* @return The chat component.
|
|
|
|
|
*/
|
2022-08-06 13:12:55 +02:00
|
|
|
@StableAPI.Expose
|
2022-05-07 11:46:23 +02:00
|
|
|
public List<ChatComponentText> toChatText() {
|
|
|
|
|
var thisComponent = toChatTextSingle();
|
|
|
|
|
val result = new ArrayList<ChatComponentText>();
|
|
|
|
|
result.add(thisComponent);
|
|
|
|
|
FormattedText prevSibling = this;
|
2022-06-08 16:01:06 +02:00
|
|
|
for (val sibling : siblings) {
|
2022-05-07 11:46:23 +02:00
|
|
|
val siblingResult = sibling.toChatTextSingle();
|
|
|
|
|
if (prevSibling.endLine) {
|
|
|
|
|
result.add(thisComponent = siblingResult);
|
|
|
|
|
} else {
|
|
|
|
|
thisComponent.appendSibling(siblingResult);
|
|
|
|
|
}
|
|
|
|
|
prevSibling = sibling;
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private ChatComponentText toChatTextSingle() {
|
|
|
|
|
val thisComponent = new ChatComponentText(text);
|
2022-05-06 12:37:14 +02:00
|
|
|
val style = new ChatStyle();
|
|
|
|
|
if (colorStyle != null) {
|
|
|
|
|
style.setColor(colorStyle);
|
|
|
|
|
}
|
2022-06-08 16:01:06 +02:00
|
|
|
for (val fancyStyle : fancyStyles) {
|
2022-05-06 12:37:14 +02:00
|
|
|
switch (fancyStyle) {
|
|
|
|
|
case OBFUSCATED:
|
|
|
|
|
style.setObfuscated(true);
|
|
|
|
|
break;
|
|
|
|
|
case BOLD:
|
|
|
|
|
style.setBold(true);
|
|
|
|
|
break;
|
|
|
|
|
case STRIKETHROUGH:
|
|
|
|
|
style.setStrikethrough(true);
|
|
|
|
|
break;
|
|
|
|
|
case UNDERLINE:
|
|
|
|
|
style.setUnderlined(true);
|
|
|
|
|
break;
|
|
|
|
|
case ITALIC:
|
|
|
|
|
style.setItalic(true);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-05-07 11:46:23 +02:00
|
|
|
thisComponent.setChatStyle(style);
|
|
|
|
|
return thisComponent;
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-06 18:02:36 +02:00
|
|
|
@SideOnly(Side.CLIENT)
|
2022-08-06 13:12:55 +02:00
|
|
|
@StableAPI.Expose
|
2022-05-06 18:02:36 +02:00
|
|
|
public void addChatMessage(EntityOtherPlayerMP target) {
|
2022-05-07 11:46:23 +02:00
|
|
|
addChatMessage(target::addChatMessage);
|
2022-05-06 18:02:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@SideOnly(Side.CLIENT)
|
2022-08-06 13:12:55 +02:00
|
|
|
@StableAPI.Expose
|
2022-05-06 18:02:36 +02:00
|
|
|
public void addChatMessage(EntityPlayerSP target) {
|
2022-05-07 11:46:23 +02:00
|
|
|
addChatMessage(target::addChatMessage);
|
2022-05-06 18:02:36 +02:00
|
|
|
}
|
|
|
|
|
|
2022-08-06 13:12:55 +02:00
|
|
|
@StableAPI.Expose
|
2022-05-06 18:02:36 +02:00
|
|
|
public void addChatMessage(CommandBlockLogic target) {
|
2022-05-07 11:46:23 +02:00
|
|
|
addChatMessage(target::addChatMessage);
|
2022-05-06 18:02:36 +02:00
|
|
|
}
|
|
|
|
|
|
2022-08-06 13:12:55 +02:00
|
|
|
@StableAPI.Expose
|
2022-05-06 18:02:36 +02:00
|
|
|
public void addChatMessage(EntityPlayerMP target) {
|
2022-05-07 11:46:23 +02:00
|
|
|
addChatMessage(target::addChatMessage);
|
2022-05-06 18:02:36 +02:00
|
|
|
}
|
|
|
|
|
|
2022-08-06 13:12:55 +02:00
|
|
|
@StableAPI.Expose
|
2022-05-06 18:02:36 +02:00
|
|
|
public void addChatMessage(RConConsoleSource target) {
|
2022-05-07 11:46:23 +02:00
|
|
|
addChatMessage(target::addChatMessage);
|
2022-05-06 18:02:36 +02:00
|
|
|
}
|
|
|
|
|
|
2022-08-06 13:12:55 +02:00
|
|
|
@StableAPI.Expose
|
2022-05-06 18:02:36 +02:00
|
|
|
public void addChatMessage(MinecraftServer target) {
|
2022-05-07 11:46:23 +02:00
|
|
|
addChatMessage(target::addChatMessage);
|
2022-05-06 18:02:36 +02:00
|
|
|
}
|
|
|
|
|
|
2022-05-06 13:31:07 +02:00
|
|
|
/**
|
|
|
|
|
* {@link #draw(FontRenderer, int, int, boolean)} without drop shadows.
|
2022-06-08 16:01:06 +02:00
|
|
|
*
|
2022-05-06 13:31:07 +02:00
|
|
|
* @param renderer The font renderer to use
|
2022-06-08 16:01:06 +02:00
|
|
|
* @param x Left side
|
|
|
|
|
* @param y Top side
|
2022-05-06 13:31:07 +02:00
|
|
|
*/
|
2022-05-06 18:02:36 +02:00
|
|
|
@SideOnly(Side.CLIENT)
|
2022-08-06 13:12:55 +02:00
|
|
|
@StableAPI.Expose
|
2022-05-06 12:37:14 +02:00
|
|
|
public void draw(FontRenderer renderer, int x, int y) {
|
|
|
|
|
draw(renderer, x, y, false);
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-06 13:31:07 +02:00
|
|
|
/**
|
|
|
|
|
* Renders this structure as GUI text.
|
2022-06-08 16:01:06 +02:00
|
|
|
*
|
2022-05-06 13:31:07 +02:00
|
|
|
* @param renderer The font renderer to use
|
2022-06-08 16:01:06 +02:00
|
|
|
* @param x Left side
|
|
|
|
|
* @param y Top side
|
|
|
|
|
* @param shadow Whether to have drop shadow under the text
|
2022-05-06 13:31:07 +02:00
|
|
|
*/
|
2022-05-06 18:02:36 +02:00
|
|
|
@SideOnly(Side.CLIENT)
|
2022-08-06 13:12:55 +02:00
|
|
|
@StableAPI.Expose
|
2022-05-06 12:37:14 +02:00
|
|
|
public void draw(FontRenderer renderer, int x, int y, boolean shadow) {
|
2022-05-07 17:38:58 +02:00
|
|
|
int startX = x;
|
|
|
|
|
x = renderer.drawString(text, x, y, colorMap.get(colorStyle).getRGB(), shadow);
|
|
|
|
|
if (endLine) {
|
2022-05-06 12:37:14 +02:00
|
|
|
y += renderer.FONT_HEIGHT;
|
2022-05-07 17:38:58 +02:00
|
|
|
x = startX;
|
|
|
|
|
}
|
2022-06-08 16:01:06 +02:00
|
|
|
for (val sibling : siblings) {
|
2022-05-07 17:38:58 +02:00
|
|
|
x = renderer.drawString(sibling.text, x, y, colorMap.get(sibling.colorStyle).getRGB(), shadow);
|
|
|
|
|
if (sibling.endLine) {
|
|
|
|
|
y += renderer.FONT_HEIGHT;
|
|
|
|
|
x = startX;
|
|
|
|
|
}
|
2022-05-06 12:37:14 +02:00
|
|
|
}
|
|
|
|
|
}
|
2022-06-08 16:01:06 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* {@link #draw(FontRenderer, int, int, boolean)} with drop shadows.
|
|
|
|
|
*
|
|
|
|
|
* @param renderer The font renderer to use
|
|
|
|
|
* @param x Left side
|
|
|
|
|
* @param y Top side
|
|
|
|
|
*/
|
|
|
|
|
@SideOnly(Side.CLIENT)
|
2022-08-06 13:12:55 +02:00
|
|
|
@StableAPI.Expose
|
2022-06-08 16:01:06 +02:00
|
|
|
public void drawWithShadow(FontRenderer renderer, int x, int y) {
|
|
|
|
|
draw(renderer, x, y, true);
|
|
|
|
|
}
|
2022-05-06 12:37:14 +02:00
|
|
|
}
|