Compare commits

..
8 Commits
45 changed files with 342 additions and 275 deletions

No files matched your search

+1 -1
View File
@@ -4,7 +4,7 @@ plugins {
allprojects {
group = "me.neznamy"
version = "5.0.4"
version = "5.0.5"
description = "An all-in-one solution that works"
ext.set("id", "tab")
@@ -2,7 +2,7 @@ package me.neznamy.tab.platforms.paper;
import io.netty.channel.Channel;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.util.FunctionWithException;
import me.neznamy.tab.shared.util.function.FunctionWithException;
import org.bukkit.craftbukkit.entity.CraftPlayer;
/**
@@ -7,7 +7,7 @@ import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.FunctionWithException;
import me.neznamy.tab.shared.util.function.FunctionWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
@@ -6,7 +6,7 @@ import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.util.BiFunctionWithException;
import me.neznamy.tab.shared.util.function.BiFunctionWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
@@ -3,7 +3,7 @@ package me.neznamy.tab.platforms.bukkit.nms;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.util.FunctionWithException;
import me.neznamy.tab.shared.util.function.FunctionWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
@@ -2,7 +2,7 @@ package me.neznamy.tab.platforms.bukkit.nms;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.util.BiConsumerWithException;
import me.neznamy.tab.shared.util.function.BiConsumerWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
@@ -3,7 +3,7 @@ package me.neznamy.tab.platforms.bukkit.nms;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
import me.neznamy.tab.shared.util.ToIntFunction;
import me.neznamy.tab.shared.util.function.ToIntFunction;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
@@ -3,7 +3,7 @@ package me.neznamy.tab.platforms.bukkit.nms.converter;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.chat.*;
import me.neznamy.tab.shared.util.FunctionWithException;
import me.neznamy.tab.shared.util.function.FunctionWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component;
@@ -11,7 +11,7 @@ import me.neznamy.tab.platforms.bukkit.scoreboard.packet.PacketScoreboard;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.impl.DummyScoreboard;
import me.neznamy.tab.shared.util.FunctionWithException;
import me.neznamy.tab.shared.util.function.FunctionWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
@@ -5,7 +5,7 @@ import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.FunctionWithException;
import me.neznamy.tab.shared.util.function.FunctionWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import java.lang.reflect.Constructor;
@@ -4,8 +4,8 @@ import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.util.BiFunctionWithException;
import me.neznamy.tab.shared.util.QuintFunction;
import me.neznamy.tab.shared.util.function.BiFunctionWithException;
import me.neznamy.tab.shared.util.function.QuintFunction;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.Nullable;
@@ -12,7 +12,7 @@ import me.neznamy.tab.shared.platform.decorators.SafeScoreboard.Team;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.Scoreboard.TeamAction;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.BiConsumerWithException;
import me.neznamy.tab.shared.util.function.BiConsumerWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
@@ -8,7 +8,7 @@ import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
import me.neznamy.tab.shared.Limitations;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.util.ReflectionUtils;
import me.neznamy.tab.shared.util.TriFunctionWithException;
import me.neznamy.tab.shared.util.function.TriFunctionWithException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -10,7 +10,7 @@ import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import me.neznamy.tab.shared.util.FunctionWithException;
import me.neznamy.tab.shared.util.function.FunctionWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -143,9 +143,6 @@ public class Property {
}
}
// Apply gradients that do not include placeholders to avoid applying them on every refresh
rawFormattedValue0 = RGBUtils.getInstance().applyCleanGradients(rawFormattedValue0);
// Make \n work even if used in '', which snakeyaml does not convert to newline
if (rawFormattedValue0.contains("\\n")) {
rawFormattedValue0 = rawFormattedValue0.replace("\\n", "\n");
@@ -52,8 +52,22 @@ public class StructuredComponent extends TabComponent {
*/
public StructuredComponent(@NotNull String text, @NotNull List<StructuredComponent> components) {
this.text = text;
if (components.isEmpty()) throw new IllegalArgumentException("Unexpected empty array of components"); //exception taken from minecraft
extra = components;
if (!components.isEmpty()) {
extra = components;
}
}
/**
* Constructs new instance with given text and color.
*
* @param text
* Component text
* @param color
* Text color
*/
public StructuredComponent(@NotNull String text, @Nullable TextColor color) {
this.text = text;
modifier.setColor(color);
}
/**
@@ -61,7 +75,8 @@ public class StructuredComponent extends TabComponent {
*
* @return list of extra components
*/
public @NotNull List<StructuredComponent> getExtra() {
@NotNull
public List<StructuredComponent> getExtra() {
if (extra == null) return Collections.emptyList();
return extra;
}
@@ -5,7 +5,8 @@ import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
import me.neznamy.tab.shared.hook.AdventureHook;
import me.neznamy.tab.shared.util.FunctionWithException;
import me.neznamy.tab.shared.util.function.FunctionWithException;
import me.neznamy.tab.shared.util.function.TriFunction;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -21,6 +22,35 @@ import java.util.regex.Pattern;
*/
public abstract class TabComponent {
/** Formatter to convert gradient into TAB's #RRGGBB spam */
private static final TriFunction<TextColor, String, TextColor, String> TABGradientFormatter = (start, text, end) -> {
//lazy support for magic codes in gradients
String magicCodes = EnumChatFormat.getLastColors(text);
String deColorized = text.substring(magicCodes.length());
StringBuilder sb = new StringBuilder();
int length = deColorized.length();
if (length == 1) {
sb.append("#");
sb.append(start.getHexCode());
sb.append(magicCodes);
sb.append(deColorized);
return sb.toString();
}
for (int i=0; i<length; i++) {
int red = (int) (start.getRed() + (float)(end.getRed() - start.getRed())/(length-1)*i);
int green = (int) (start.getGreen() + (float)(end.getGreen() - start.getGreen())/(length-1)*i);
int blue = (int) (start.getBlue() + (float)(end.getBlue() - start.getBlue())/(length-1)*i);
sb.append("#");
sb.append(new TextColor(red, green, blue).getHexCode());
sb.append(magicCodes);
sb.append(deColorized.charAt(i));
}
return sb.toString();
};
/** Formatter to convert RGB code to use TAB's #RRGGBB */
private static final Function<TextColor, String> TABRGBFormatter = color -> "#" + color.getHexCode();
/** Pattern for detecting fonts */
private static final Pattern fontPattern = Pattern.compile("<font:(.*?)>(.*?)</font>");
@@ -182,7 +212,7 @@ public abstract class TabComponent {
* @return organized component from colored text
*/
@NotNull
public static TabComponent fromColoredText(@NotNull String originalText) {
public static StructuredComponent fromColoredText(@NotNull String originalText) {
String remainingText = originalText;
List<StructuredComponent> components = new ArrayList<>();
while (!remainingText.isEmpty()) {
@@ -205,16 +235,12 @@ public abstract class TabComponent {
break;
}
}
if (components.isEmpty()) {
return new SimpleComponent("");
} else {
return new StructuredComponent("", components);
}
return new StructuredComponent("", components);
}
@NotNull
private static List<StructuredComponent> toComponentArray(@NotNull String originalText, @Nullable String font) {
String text = RGBUtils.getInstance().applyFormats(EnumChatFormat.color(originalText));
String text = RGBUtils.getInstance().applyFormats(EnumChatFormat.color(originalText), TABGradientFormatter, TABRGBFormatter);
List<StructuredComponent> components = new ArrayList<>();
StringBuilder builder = new StringBuilder();
StructuredComponent component = new StructuredComponent();
@@ -1,13 +1,18 @@
package me.neznamy.tab.shared.chat.rgb;
import lombok.Getter;
import me.neznamy.tab.shared.chat.rgb.format.*;
import me.neznamy.tab.shared.chat.TextColor;
import me.neznamy.tab.shared.chat.rgb.format.BukkitFormat;
import me.neznamy.tab.shared.chat.rgb.format.CommonFormatter;
import me.neznamy.tab.shared.chat.rgb.format.RGBFormatter;
import me.neznamy.tab.shared.chat.rgb.gradient.CMIGradient;
import me.neznamy.tab.shared.chat.rgb.gradient.CommonGradient;
import me.neznamy.tab.shared.chat.rgb.gradient.GradientPattern;
import me.neznamy.tab.shared.chat.rgb.gradient.NexEngineGradient;
import me.neznamy.tab.shared.util.function.TriFunction;
import org.jetbrains.annotations.NotNull;
import java.util.function.Function;
import java.util.regex.Pattern;
/**
@@ -19,52 +24,41 @@ public class RGBUtils {
@Getter private static final RGBUtils instance = new RGBUtils();
/** Registered RGB formatters */
private final RGBFormatter[] formats = {
new BukkitFormat(),
new CMIFormat(),
new UnnamedFormat1(),
new HtmlFormat(),
new KyoriFormat()
private final RGBFormatter[] formats = {
new BukkitFormat(), // &x&r&r&g&g&b&b
new CommonFormatter(Pattern.compile("\\{#[0-9a-fA-F]{6}}"), "{#"), // CMI's {#RRGGBB}
new CommonFormatter(Pattern.compile("§#[0-9a-fA-F]{6}"), "§#"), // &#RRGGBB
new CommonFormatter(Pattern.compile("#<[0-9a-fA-F]{6}>"), "#<") // #<RRGGBB> // "HTML"
};
/** Registered gradient patterns */
private final GradientPattern[] gradients = {
new CMIGradient(), //{#RRGGBB>}Text{#RRGGBB<}
new CommonGradient(Pattern.compile("<#[0-9a-fA-F]{6}>.*?</#[0-9a-fA-F]{6}>"), "<#", 2, 9, 7), //<#RRGGBB>Text</#RRGGBB>
new CommonGradient(Pattern.compile("<\\$#[0-9a-fA-F]{6}>.*?<\\$#[0-9a-fA-F]{6}>"), "<$", 3, 10, 7), //<$#RRGGBB>Text<$#RRGGBB>
new CommonGradient(Pattern.compile("<#[0-9a-fA-F]{6}>.*?</#[0-9a-fA-F]{6}>"), "<#", 2, 9, 7), //<#RRGGBB>Text</#RRGGBB> // "HTML"
new CommonGradient(Pattern.compile("<\\$#[0-9a-fA-F]{6}>.*?<\\$#[0-9a-fA-F]{6}>"), "<$", 3, 10, 7), //<$#RRGGBB>Text<$#RRGGBB> // ?
new NexEngineGradient() // <gradient:#RRGGBB>Text</gradient:#RRGGBB>
};
/**
* Applies all RGB formats and gradients to text and returns it.
* Applies all gradient formats to text and returns it.
*
* @param text
* original text
* @return text where everything is converted to #RRGGBB
* @param gradientFunction
* Function for reformatting gradient to new text
* @param rgbFunction
* Function for converting RGB codes to desired format
* @return text where all gradients are converted to desired format
*/
public @NotNull String applyFormats(@NotNull String text) {
@NotNull
public String applyFormats(@NotNull String text, @NotNull TriFunction<TextColor, String, TextColor, String> gradientFunction,
@NotNull Function<TextColor, String> rgbFunction) {
String replaced = text;
for (GradientPattern pattern : gradients) {
replaced = pattern.applyPattern(replaced, false);
replaced = pattern.applyPattern(replaced, gradientFunction);
}
for (RGBFormatter formatter : formats) {
replaced = formatter.reformat(replaced);
}
return replaced;
}
/**
* Applies all gradient formats to text and returns it. This only affects
* usage where no placeholder is used inside.
*
* @param text
* original text
* @return text where all gradients with static text are converted to #RRGGBB
*/
public @NotNull String applyCleanGradients(@NotNull String text) {
String replaced = text;
for (GradientPattern pattern : gradients) {
replaced = pattern.applyPattern(replaced, true);
replaced = formatter.reformat(replaced, rgbFunction);
}
return replaced;
}
@@ -1,7 +1,9 @@
package me.neznamy.tab.shared.chat.rgb.format;
import me.neznamy.tab.shared.chat.TextColor;
import org.jetbrains.annotations.NotNull;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -10,17 +12,18 @@ import java.util.regex.Pattern;
*/
public class BukkitFormat implements RGBFormatter {
private final Pattern pattern = Pattern.compile("[§&]x[§&\\p{XDigit}]{12}");
private final Pattern pattern = Pattern.compile("§x[§\\p{XDigit}]{12}");
@Override
public @NotNull String reformat(@NotNull String text) {
if (!text.contains("&x") && !text.contains("§x")) return text;
@NotNull
public String reformat(@NotNull String text, @NotNull Function<TextColor, String> rgbFunction) {
if (!text.contains("§x")) return text;
String replaced = text;
Matcher m = pattern.matcher(replaced);
while (m.find()) {
String hexCode = m.group();
String fixed = new String(new char[] {'#', hexCode.charAt(3), hexCode.charAt(5), hexCode.charAt(7), hexCode.charAt(9), hexCode.charAt(11), hexCode.charAt(13)});
replaced = replaced.replace(hexCode, fixed);
String group = m.group();
String hexCode = new String(new char[] {group.charAt(3), group.charAt(5), group.charAt(7), group.charAt(9), group.charAt(11), group.charAt(13)});
replaced = replaced.replace(group, rgbFunction.apply(new TextColor(hexCode)));
}
return replaced;
}
@@ -1,27 +0,0 @@
package me.neznamy.tab.shared.chat.rgb.format;
import org.jetbrains.annotations.NotNull;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Formatter for {#RRGGBB}
*/
public class CMIFormat implements RGBFormatter {
private final Pattern pattern = Pattern.compile("\\{#[0-9a-fA-F]{6}}");
@Override
public @NotNull String reformat(@NotNull String text) {
if (!text.contains("{#")) return text;
String replaced = text;
Matcher m = pattern.matcher(replaced);
while (m.find()) {
String hexCode = m.group();
String fixed = hexCode.substring(2, 8);
replaced = replaced.replace(hexCode, "#" + fixed);
}
return replaced;
}
}
@@ -0,0 +1,36 @@
package me.neznamy.tab.shared.chat.rgb.format;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.shared.chat.TextColor;
import org.jetbrains.annotations.NotNull;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Common class for various RGB formatters.
*/
@RequiredArgsConstructor
public class CommonFormatter implements RGBFormatter {
/** Pattern for finding the RGB code */
private final Pattern pattern;
/** String to check if the text contains to possibly skip pattern matching for better performance */
private final String stringCheck;
@Override
@NotNull
public String reformat(@NotNull String text, @NotNull Function<TextColor, String> rgbFunction) {
if (!text.contains(stringCheck)) return text;
String replaced = text;
Matcher m = pattern.matcher(replaced);
while (m.find()) {
String group = m.group();
String hexCode = group.substring(2, 8);
replaced = replaced.replace(group, rgbFunction.apply(new TextColor(hexCode)));
}
return replaced;
}
}
@@ -1,27 +0,0 @@
package me.neznamy.tab.shared.chat.rgb.format;
import org.jetbrains.annotations.NotNull;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Formatter for #&lt;RRGGBB&gt;
*/
public class HtmlFormat implements RGBFormatter {
private final Pattern pattern = Pattern.compile("#<[0-9a-fA-F]{6}>");
@Override
public @NotNull String reformat(@NotNull String text) {
if (!text.contains("#<")) return text;
Matcher m = pattern.matcher(text);
String replaced = text;
while (m.find()) {
String hexCode = m.group();
String fixed = hexCode.substring(2, 8);
replaced = replaced.replace(hexCode, "#" + fixed);
}
return replaced;
}
}
@@ -1,27 +0,0 @@
package me.neznamy.tab.shared.chat.rgb.format;
import org.jetbrains.annotations.NotNull;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Formatter for &lt;#RRGGBB&gt;
*/
public class KyoriFormat implements RGBFormatter {
private final Pattern pattern = Pattern.compile("<#[0-9a-fA-F]{6}>");
@Override
public @NotNull String reformat(@NotNull String text) {
if (!text.contains("<#")) return text;
String replaced = text;
Matcher m = pattern.matcher(replaced);
while (m.find()) {
String hexCode = m.group();
String fixed = hexCode.substring(2, 8);
replaced = replaced.replace(hexCode, "#" + fixed);
}
return replaced;
}
}
@@ -1,18 +1,24 @@
package me.neznamy.tab.shared.chat.rgb.format;
import me.neznamy.tab.shared.chat.TextColor;
import org.jetbrains.annotations.NotNull;
import java.util.function.Function;
/**
* Abstract class for different RGB patterns
*/
public interface RGBFormatter {
/**
* Reformats RGB codes in provided text into #RRGGBB format
* Re-formats RGB codes in the provided text using given formatter.
*
* @param text
* text to format
* @param rgbFunction
* Function for converting RGB codes to desired format
* @return reformatted text
*/
@NotNull String reformat(@NotNull String text);
@NotNull
String reformat(@NotNull String text, @NotNull Function<TextColor, String> rgbFunction);
}
@@ -1,14 +0,0 @@
package me.neznamy.tab.shared.chat.rgb.format;
import org.jetbrains.annotations.NotNull;
/**
* Formatter for {@code &#RRGGBB}.
*/
public class UnnamedFormat1 implements RGBFormatter {
@Override
public @NotNull String reformat(@NotNull String text) {
return text.contains("§#") ? text.replace("§#", "#") : text;
}
}
@@ -1,5 +1,7 @@
package me.neznamy.tab.shared.chat.rgb.gradient;
import me.neznamy.tab.shared.chat.TextColor;
import me.neznamy.tab.shared.util.function.TriFunction;
import org.jetbrains.annotations.NotNull;
import java.util.regex.Matcher;
@@ -21,7 +23,8 @@ public class CMIGradient extends CommonGradient {
}
@Override
public String applyPattern(@NotNull String text, boolean ignorePlaceholders) {
@NotNull
public String applyPattern(@NotNull String text, @NotNull TriFunction<TextColor, String, TextColor, String> gradientFunction) {
String replaced = text;
if (replaced.contains("<>}")) {
Matcher m = shortcutPattern.matcher(replaced);
@@ -31,6 +34,6 @@ public class CMIGradient extends CommonGradient {
replaced = replaced.replace(format, "{#" + code + "<}{#" + code + ">}");
}
}
return super.applyPattern(replaced, ignorePlaceholders);
return super.applyPattern(replaced, gradientFunction);
}
}
@@ -2,6 +2,7 @@ package me.neznamy.tab.shared.chat.rgb.gradient;
import lombok.AllArgsConstructor;
import me.neznamy.tab.shared.chat.TextColor;
import me.neznamy.tab.shared.util.function.TriFunction;
import org.jetbrains.annotations.NotNull;
import java.util.regex.Matcher;
@@ -20,18 +21,17 @@ public class CommonGradient implements GradientPattern {
private final int endColorStartSub;
@Override
public String applyPattern(@NotNull String text, boolean ignorePlaceholders) {
@NotNull
public String applyPattern(@NotNull String text, @NotNull TriFunction<TextColor, String, TextColor, String> gradientFunction) {
if (!text.contains(containCheck)) return text;
String replaced = text;
Matcher m = pattern.matcher(replaced);
while (m.find()) {
String format = m.group();
if (ignorePlaceholders && format.contains("%")) continue;
TextColor start = new TextColor(format.substring(startColorStart, startColorStart+6));
String message = format.substring(messageStart, format.length()-10);
TextColor end = new TextColor(format.substring(format.length()-endColorStartSub, format.length()-endColorStartSub+6));
String applied = asGradient(start, message, end);
replaced = replaced.replace(format, applied);
replaced = replaced.replace(format, gradientFunction.apply(start, message, end));
}
return replaced;
}
@@ -1,7 +1,7 @@
package me.neznamy.tab.shared.chat.rgb.gradient;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.TextColor;
import me.neznamy.tab.shared.util.function.TriFunction;
import org.jetbrains.annotations.NotNull;
/**
@@ -14,45 +14,10 @@ public interface GradientPattern {
*
* @param text
* text to be reformatted
* @param ignorePlaceholders
* Whether text containing "%" should be ignored or not
* @param gradientFunction
* Function for reformatting gradient to new text
* @return reformatted text
*/
String applyPattern(@NotNull String text, boolean ignorePlaceholders);
/**
* Returns gradient text based on start color, text and end color
*
* @param start
* start color
* @param text
* text to be reformatted
* @param end
* end color
* @return reformatted text
*/
default String asGradient(@NotNull TextColor start, @NotNull String text, @NotNull TextColor end) {
//lazy support for magic codes in gradients
String magicCodes = EnumChatFormat.getLastColors(text);
String deColorized = text.substring(magicCodes.length());
StringBuilder sb = new StringBuilder();
int length = deColorized.length();
if (length == 1) {
sb.append("#");
sb.append(start.getHexCode());
sb.append(magicCodes);
sb.append(deColorized);
return sb.toString();
}
for (int i=0; i<length; i++) {
int red = (int) (start.getRed() + (float)(end.getRed() - start.getRed())/(length-1)*i);
int green = (int) (start.getGreen() + (float)(end.getGreen() - start.getGreen())/(length-1)*i);
int blue = (int) (start.getBlue() + (float)(end.getBlue() - start.getBlue())/(length-1)*i);
sb.append("#");
sb.append(new TextColor(red, green, blue).getHexCode());
sb.append(magicCodes);
sb.append(deColorized.charAt(i));
}
return sb.toString();
}
@NotNull
String applyPattern(@NotNull String text, @NotNull TriFunction<TextColor, String, TextColor, String> gradientFunction);
}
@@ -1,6 +1,7 @@
package me.neznamy.tab.shared.chat.rgb.gradient;
import me.neznamy.tab.shared.chat.TextColor;
import me.neznamy.tab.shared.util.function.TriFunction;
import org.jetbrains.annotations.NotNull;
import java.util.regex.Matcher;
@@ -12,18 +13,17 @@ public class NexEngineGradient implements GradientPattern {
private final Pattern pattern = Pattern.compile("<gradient:#([A-Fa-f0-9]{6})>(.*?)</gradient:#([A-Fa-f0-9]{6})>");
@Override
public String applyPattern(@NotNull String text, boolean ignorePlaceholders) {
@NotNull
public String applyPattern(@NotNull String text, @NotNull TriFunction<TextColor, String, TextColor, String> gradientFunction) {
if (!text.contains("<grad")) return text;
String replaced = text;
Matcher matcher = pattern.matcher(replaced);
while (matcher.find()) {
String format = matcher.group();
if ((ignorePlaceholders && format.contains("%"))) continue;
TextColor start = new TextColor(matcher.group(1));
String content = matcher.group(2);
TextColor end = new TextColor(matcher.group(3));
String applied = asGradient(start, content, end);
replaced = replaced.replace(format, applied);
replaced = replaced.replace(format, gradientFunction.apply(start, content, end));
}
return replaced;
}
@@ -4,6 +4,8 @@ import me.neznamy.tab.shared.Property;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.StructuredComponent;
import me.neznamy.tab.shared.chat.TextColor;
import me.neznamy.tab.shared.config.file.ConfigurationFile;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.platform.TabPlayer;
@@ -12,6 +14,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
@@ -190,9 +193,14 @@ public class DebugCommand extends SubCommand {
if (disabled) {
sendMessage(sender, "&a" + property.getName() + ": &cDisabled for player with condition");
} else {
String rawValue = property.getCurrentRawValue().replace('§', '&');
String value = String.format((EnumChatFormat.color("&a%s: &e\"&r%s&r&e\" &7(Source: %s)")), property.getName(), rawValue, property.getSource());
sendRawMessage(sender, value);
// Do it this way to avoid sending the "§" symbol to the console to try to color the text (does not work on Velocity)
sendMessage(sender, new StructuredComponent("", Arrays.asList(
new StructuredComponent(property.getName() + ": ", TextColor.legacy(EnumChatFormat.GREEN)),
new StructuredComponent("\"", TextColor.legacy(EnumChatFormat.YELLOW)),
new StructuredComponent(property.getCurrentRawValue().replace('§', '&'), TextColor.legacy(EnumChatFormat.WHITE)),
new StructuredComponent("\" ", TextColor.legacy(EnumChatFormat.YELLOW)),
new StructuredComponent("(Source: " + property.getSource() + ")", TextColor.legacy(EnumChatFormat.GRAY))
)));
}
}
@@ -1,14 +1,17 @@
package me.neznamy.tab.shared.command;
import java.util.*;
import java.util.stream.Collectors;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Handler for "/tab group" subcommand
*/
@@ -60,20 +63,20 @@ public class GroupCommand extends PropertyCommand {
private void sendGroupInfo(@Nullable TabPlayer sender, @NotNull String group) {
sendMessage(sender, "&f=== Group &9" + group + "&f ===");
for (Map.Entry<String, Object> entry : TAB.getInstance().getConfiguration().getGroups().getGlobalSettings(group).entrySet()) {
sendRawMessage(sender, " " + entry.getKey() + ": " + entry.getValue());
sendMessage(sender, " " + entry.getKey() + ": " + entry.getValue());
}
for (Map.Entry<String, Map<String, Object>> entry : TAB.getInstance().getConfiguration().getGroups().getPerWorldSettings(group).entrySet()) {
if (entry.getValue() == null) continue;
sendMessage(sender, "&6World " + entry.getKey() + ":&e");
for (Map.Entry<String, Object> properties : entry.getValue().entrySet()) {
sendRawMessage(sender, " " + properties.getKey() + ": " + properties.getValue());
sendMessage(sender, " " + properties.getKey() + ": " + properties.getValue());
}
}
for (Map.Entry<String, Map<String, Object>> entry : TAB.getInstance().getConfiguration().getGroups().getPerServerSettings(group).entrySet()) {
if (entry.getValue() == null) continue;
sendMessage(sender, "&3Server " + entry.getKey() + ":&b");
for (Map.Entry<String, Object> properties : entry.getValue().entrySet()) {
sendRawMessage(sender, " " + properties.getKey() + ": " + properties.getValue());
sendMessage(sender, " " + properties.getKey() + ": " + properties.getValue());
}
}
}
@@ -90,8 +90,8 @@ public class MySQLCommand extends SubCommand {
}
TAB.getInstance().getCPUManager().getMysqlThread().execute(() -> {
try {
YamlPropertyConfigurationFile groupFile = new YamlPropertyConfigurationFile(Configs.class.getClassLoader().getResourceAsStream("config/groups.yml"), new File(TAB.getInstance().getDataFolder(), "config/groups.yml"));
YamlPropertyConfigurationFile userFile = new YamlPropertyConfigurationFile(Configs.class.getClassLoader().getResourceAsStream("config/users.yml"), new File(TAB.getInstance().getDataFolder(), "config/users.yml"));
YamlPropertyConfigurationFile groupFile = new YamlPropertyConfigurationFile(Configs.class.getClassLoader().getResourceAsStream("config/groups.yml"), new File(TAB.getInstance().getDataFolder(), "groups.yml"));
YamlPropertyConfigurationFile userFile = new YamlPropertyConfigurationFile(Configs.class.getClassLoader().getResourceAsStream("config/users.yml"), new File(TAB.getInstance().getDataFolder(), "users.yml"));
upload(groupFile, TAB.getInstance().getConfiguration().getGroups());
upload(userFile, TAB.getInstance().getConfiguration().getUsers());
sendMessage(sender, getMessages().getMySQLUploadSuccess());
@@ -5,7 +5,9 @@ import me.neznamy.tab.shared.Property;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.StructuredComponent;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.chat.TextColor;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -50,28 +52,37 @@ public class ParseCommand extends SubCommand {
return;
}
}
String replaced = String.join(" ", Arrays.copyOfRange(args, 1, args.length));
if (!replaced.contains("%")) {
sendMessage(sender, "&cThe provided input (" + replaced + ") does not contain any placeholders, therefore there's nothing to test.");
String textToParse = String.join(" ", Arrays.copyOfRange(args, 1, args.length));
if (!textToParse.contains("%")) {
sendMessage(sender, "&cThe provided input (" + textToParse + ") does not contain any placeholders, therefore there's nothing to test.");
return;
}
String message = EnumChatFormat.color("&6Replacing placeholder &e%placeholder% &6for player &e" + target.getName()).replace("%placeholder%", replaced);
sendRawMessage(sender, message);
// Do it this way to avoid sending the "§" symbol to the console to try to color the text (does not work on Velocity)
sendMessage(sender, new StructuredComponent("", Arrays.asList(
new StructuredComponent("Replacing placeholder ", TextColor.legacy(EnumChatFormat.GOLD)),
new StructuredComponent(textToParse, TextColor.legacy(EnumChatFormat.YELLOW)),
new StructuredComponent(" for player ", TextColor.legacy(EnumChatFormat.GOLD)),
new StructuredComponent(target.getName(), TextColor.legacy(EnumChatFormat.YELLOW))
)));
try {
replaced = new Property(null, null, target, replaced, null).get();
String replaced = new Property(null, null, target, textToParse, null).get();
TabComponent colored = TabComponent.fromColoredText("&3Colored output: &e\"&r" + replaced + "&e\"");
if (sender != null) {
sender.sendMessage(colored);
} else {
TAB.getInstance().getPlatform().logInfo(colored);
}
sendMessage(sender, new StructuredComponent("", Arrays.asList(
new StructuredComponent("Raw colors: ", TextColor.legacy(EnumChatFormat.DARK_AQUA)),
new StructuredComponent("\"", TextColor.legacy(EnumChatFormat.YELLOW)),
new StructuredComponent(replaced.replace('§', '&'), TextColor.legacy(EnumChatFormat.WHITE)),
new StructuredComponent("\"", TextColor.legacy(EnumChatFormat.YELLOW))
)));
sendMessage(sender, "&3Output length: &e" + replaced.length() + " &3characters");
} catch (Exception e) {
sendMessage(sender, "&cThe placeholder threw an exception when parsing. Check console for more info.");
TAB.getInstance().getErrorManager().parseCommandError(replaced, target, e);
return;
TAB.getInstance().getErrorManager().parseCommandError(textToParse, target, e);
}
TabComponent colored = TabComponent.fromColoredText("&3Colored output: &e\"&r" + replaced + "&e\"");
if (sender != null) {
sender.sendMessage(colored);
} else {
TAB.getInstance().getPlatform().logInfo(colored);
}
sendRawMessage(sender, EnumChatFormat.color("&3Raw colors: &e\"&r") + replaced.replace('§', '&') + EnumChatFormat.color("&e\""));
sendMessage(sender, "&3Output length: &e" + replaced.length() + " &3characters");
}
@Override
@@ -1,19 +1,18 @@
package me.neznamy.tab.shared.command;
import java.util.*;
import java.util.stream.Collectors;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.shared.chat.SimpleComponent;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.config.MessageFile;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.stream.Collectors;
/**
* Abstract class representing a subcommand of "/tab" command
*/
@@ -87,7 +86,7 @@ public abstract class SubCommand {
* @param sender
* player or console to send the message to
* @param message
* the message to sent
* the message to send
*/
public void sendMessage(@Nullable TabPlayer sender, @NotNull String message) {
if (message.isEmpty()) return;
@@ -99,19 +98,19 @@ public abstract class SubCommand {
}
/**
* Sends message to the command sender without colors translated
* Sends the message component to the player.
* If player is {@code null}, the message is sent to the console instead.
*
* @param sender
* player or console to send the message to
* @param message
* the message to sent
* the message to send
*/
public void sendRawMessage(@Nullable TabPlayer sender, @NotNull String message) {
if (message.isEmpty()) return;
public void sendMessage(@Nullable TabPlayer sender, @NotNull TabComponent message) {
if (sender != null) {
sender.sendMessage(message, false);
sender.sendMessage(message);
} else {
TAB.getInstance().getPlatform().logInfo(new SimpleComponent(message));
TAB.getInstance().getPlatform().logInfo(message);
}
}
@@ -1,5 +1,6 @@
package me.neznamy.tab.shared.hook;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.AdventureComponent;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.util.ReflectionUtils;
@@ -14,9 +15,26 @@ public class MiniMessageHook {
/** Minimessage deserializer with disabled component post-processing */
@Nullable
private static final MiniMessage mm = ReflectionUtils.classExists("net.kyori.adventure.text.minimessage.MiniMessage") &&
ReflectionUtils.classExists("net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer") ?
MiniMessage.builder().postProcessor(c->c).build() : null;
private static final MiniMessage mm = createMiniMessage();
@Nullable
private static MiniMessage createMiniMessage() {
try {
if (ReflectionUtils.classExists("net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer")) {
return MiniMessage.builder().postProcessor(c->c).build();
}
} catch (Throwable ignored) {}
return null;
}
/**
* Returns {@code true} if MiniMessage is available on the server, {@code false} if not.
*
* @return {@code true} if MiniMessage is available on the server, {@code false} if not
*/
public static boolean isAvailable() {
return mm != null;
}
/**
* Attempts to parse the text into an adventure component using MiniMessage syntax. If MiniMessage is
@@ -29,11 +47,14 @@ public class MiniMessageHook {
@Nullable
public static TabComponent parseText(@NotNull String text) {
if (mm == null) return null;
if (!text.contains("<")) return null; // User did not even attempt to use MiniMessage
if (text.contains("§")) return null;
if (text.contains("§")) {
TAB.getInstance().getErrorManager().printError("Cannot convert \"" + text + "\" into a MiniMessage component, because it contains legacy colors", null);
return null;
}
try {
return new AdventureComponent(mm.deserialize(text));
} catch (Throwable ignored) {
} catch (Throwable t) {
TAB.getInstance().getErrorManager().printError("Failed to convert \"" + text + "\" into a MiniMessage component", t);
return null;
}
}
@@ -5,7 +5,6 @@ import lombok.NonNull;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
import me.neznamy.tab.shared.placeholders.animation.AnimationConfiguration.AnimationDefinition;
import org.jetbrains.annotations.NotNull;
@@ -57,7 +56,6 @@ public class Animation {
int refresh = interval;
List<String> nestedPlaceholders = new ArrayList<>();
for (int i=0; i<messages.length; i++) {
messages[i] = RGBUtils.getInstance().applyCleanGradients(messages[i]);
messages[i] = EnumChatFormat.color(messages[i]);
nestedPlaceholders.addAll(PlaceholderManagerImpl.detectPlaceholders(messages[i]));
}
@@ -1,14 +1,33 @@
package me.neznamy.tab.shared.util.cache;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.SimpleComponent;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.chat.TextColor;
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
import me.neznamy.tab.shared.hook.MiniMessageHook;
import me.neznamy.tab.shared.util.function.TriFunction;
import org.jetbrains.annotations.NotNull;
import java.util.Locale;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Cache for String -> TabComponent conversion.
* Cache for String → TabComponent conversion.
*/
public class StringToComponentCache extends Cache<String, TabComponent> {
/** Formatter to use Kyori's &lt;gradient:#RRGGBB:#RRGGBB>Text&lt;/gradient> */
private static final TriFunction<TextColor, String, TextColor, String> kyoriGradientFormatter =
(start, text, end) -> String.format("<gradient:#%s:#%s>%s</gradient>", start.getHexCode(), end.getHexCode(), text);
/** Formatter to convert RGB code to use Kyori's &lt;color:#RRGGBB>*/
private static final Function<TextColor, String> kyoriRGBFormatter = color -> String.format("<color:#%s>", color.getHexCode());
private static final Pattern tabToKyori = Pattern.compile("(?<!:)(#([0-9A-Fa-f]{6}))(?![:>])");
/**
* Constructs new instance with given parameters.
*
@@ -19,11 +38,40 @@ public class StringToComponentCache extends Cache<String, TabComponent> {
*/
public StringToComponentCache(String name, int cacheSize) {
super(name, cacheSize, text -> {
TabComponent component = MiniMessageHook.parseText(text);
if (component != null) return component;
return text.contains("#") || text.contains("&x") || text.contains("§x") || text.contains("<") ?
if (MiniMessageHook.isAvailable() && text.indexOf('<') != -1) { // User may have wanted to use MiniMessage
// Reformat gradients and RGB to kyori format
String mmFormatted = RGBUtils.getInstance().applyFormats(text, kyoriGradientFormatter, kyoriRGBFormatter);
// Convert legacy codes into kyori format
for (EnumChatFormat format : EnumChatFormat.VALUES) {
if (mmFormatted.contains(format.getFormat())) {
String colorName = format == EnumChatFormat.UNDERLINE ? "underlined" : format.name().toLowerCase(Locale.US);
mmFormatted = mmFormatted.replace(format.getFormat(), "<" + colorName + ">");
}
}
// Convert TAB's #RRGGBB to Kyori <color:#RRGGBB>
mmFormatted = tabToKyori(mmFormatted);
TabComponent component = MiniMessageHook.parseText(mmFormatted);
if (component != null) return component;
}
return text.contains("#") || text.contains("§x") || text.contains("<") ?
TabComponent.fromColoredText(text) : //contains RGB colors or font
new SimpleComponent(text); //no RGB
});
}
@NotNull
private static String tabToKyori(@NotNull String text) {
Matcher matcher = tabToKyori.matcher(text);
StringBuffer result = new StringBuffer();
while (matcher.find()) {
// Replace standalone #RRGGBB with <color:#RRGGBB>
matcher.appendReplacement(result, "<color:" + matcher.group(1) + ">");
}
matcher.appendTail(result);
return result.toString();
}
}
@@ -1,4 +1,4 @@
package me.neznamy.tab.shared.util;
package me.neznamy.tab.shared.util.function;
/**
* An interface for BiConsumer which can throw an exception.
@@ -1,4 +1,4 @@
package me.neznamy.tab.shared.util;
package me.neznamy.tab.shared.util.function;
/**
* An interface for BiFunction which can throw an exception.
@@ -1,4 +1,4 @@
package me.neznamy.tab.shared.util;
package me.neznamy.tab.shared.util.function;
/**
* An interface for Function which can throw an exception.
@@ -1,4 +1,4 @@
package me.neznamy.tab.shared.util;
package me.neznamy.tab.shared.util.function;
import java.lang.reflect.InvocationTargetException;
@@ -1,4 +1,4 @@
package me.neznamy.tab.shared.util;
package me.neznamy.tab.shared.util.function;
/**
* Represents a function that accepts an int-valued argument and produces a
@@ -0,0 +1,29 @@
package me.neznamy.tab.shared.util.function;
/**
* An interface for a function with 3 input arguments.
* @param <A>
* First function argument
* @param <B>
* Second function argument
* @param <C>
* Third function argument
* @param <D>
* Function return type
*/
@FunctionalInterface
public interface TriFunction<A, B, C, D> {
/**
* Runs the function and returns the result
*
* @param a
* First argument
* @param b
* Second argument
* @param c
* Third argument
* @return Output from function
*/
D apply(A a, B b, C c);
}
@@ -1,7 +1,7 @@
package me.neznamy.tab.shared.util;
package me.neznamy.tab.shared.util.function;
/**
* An interface for BiFunction which can throw an exception.
* An interface for TriFunction which can throw an exception.
* @param <A>
* First function argument
* @param <B>
@@ -1,10 +1,10 @@
package me.neznamy.tab.platforms.sponge8;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.util.BiFunctionWithException;
import me.neznamy.tab.shared.util.FunctionWithException;
import me.neznamy.tab.shared.util.function.BiFunctionWithException;
import me.neznamy.tab.shared.util.function.FunctionWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import me.neznamy.tab.shared.util.ToIntFunction;
import me.neznamy.tab.shared.util.function.ToIntFunction;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import org.spongepowered.api.entity.living.player.server.ServerPlayer;