Fixed cast error, NPE, masked links

Fixed config cast error
Fixed mcchat NPE
Implemented masked links
Added a test for it
This commit is contained in:
Norbi Peti 2019-01-19 20:26:39 +01:00
parent 0802de4b6f
commit 3a29010042
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
7 changed files with 459 additions and 447 deletions

View file

@ -46,7 +46,7 @@ public class ChatProcessing {
private static final Pattern ITALIC_PATTERN = Pattern.compile("\\*"); private static final Pattern ITALIC_PATTERN = Pattern.compile("\\*");
private static final Pattern BOLD_PATTERN = Pattern.compile("\\*\\*"); private static final Pattern BOLD_PATTERN = Pattern.compile("\\*\\*");
private static final Pattern CODE_PATTERN = Pattern.compile("`"); private static final Pattern CODE_PATTERN = Pattern.compile("`");
private static final Pattern MASKED_LINK_PATTERN = Pattern.compile("\\[([^\\[\\]])]\\(([^()])\\)"); private static final Pattern MASKED_LINK_PATTERN = Pattern.compile("\\[([^\\[\\]]+)]\\(([^()]+)\\)");
private static final Pattern SOMEONE_PATTERN = Pattern.compile("@someone"); //TODO private static final Pattern SOMEONE_PATTERN = Pattern.compile("@someone"); //TODO
private static final Pattern STRIKETHROUGH_PATTERN = Pattern.compile("~~"); private static final Pattern STRIKETHROUGH_PATTERN = Pattern.compile("~~");
private static final Color[] RainbowPresserColors = new Color[]{Color.Red, Color.Gold, Color.Yellow, Color.Green, private static final Color[] RainbowPresserColors = new Color[]{Color.Red, Color.Gold, Color.Yellow, Color.Green,
@ -63,9 +63,8 @@ public class ChatProcessing {
.build(), .build(),
ChatFormatter.builder().regex(STRIKETHROUGH_PATTERN).strikethrough(true).removeCharCount((short) 2).type(ChatFormatter.Type.Range) ChatFormatter.builder().regex(STRIKETHROUGH_PATTERN).strikethrough(true).removeCharCount((short) 2).type(ChatFormatter.Type.Range)
.build(), .build(),
ESCAPE_FORMATTER, ChatFormatter.builder().regex(URL_PATTERN).underlined(true).openlink("$1").type(ChatFormatter.Type.Excluder).build(), ESCAPE_FORMATTER, ChatFormatter.builder().regex(NULL_MENTION_PATTERN).color(Color.DarkRed).build(), // Properly added a bug as a feature
ChatFormatter.builder().regex(NULL_MENTION_PATTERN).color(Color.DarkRed).build(), // Properly added a bug as a feature ChatFormatter.builder().regex(CONSOLE_PING_PATTERN).color(Color.Aqua).onmatch((match, builder, section) -> {
ChatFormatter.builder().regex(CONSOLE_PING_PATTERN).color(Color.Aqua).onmatch((match, builder) -> {
if (!pingedconsole) { if (!pingedconsole) {
System.out.print("\007"); System.out.print("\007");
pingedconsole = true; // Will set it to false in ProcessChat pingedconsole = true; // Will set it to false in ProcessChat
@ -78,9 +77,14 @@ public class ChatProcessing {
ChatFormatter.builder().regex(CYAN_PATTERN).color(Color.Aqua).build(), // #55 ChatFormatter.builder().regex(CYAN_PATTERN).color(Color.Aqua).build(), // #55
ChatFormatter.builder().regex(CODE_PATTERN).color(Color.DarkGray).removeCharCount((short) 1).type(ChatFormatter.Type.Range) ChatFormatter.builder().regex(CODE_PATTERN).color(Color.DarkGray).removeCharCount((short) 1).type(ChatFormatter.Type.Range)
.build(), .build(),
ChatFormatter.builder().regex(MASKED_LINK_PATTERN).underlined(true).onmatch((match, builder) -> { ChatFormatter.builder().regex(MASKED_LINK_PATTERN).underlined(true).onmatch((match, builder, section) -> {
return match; // TODO! String text, link;
}).build()); if (section.Matches.size() < 2 || (text = section.Matches.get(0)).length() == 0 || (link = section.Matches.get(1)).length() == 0)
return "";
builder.setOpenlink(link);
return text;
}).type(ChatFormatter.Type.Excluder).build(),
ChatFormatter.builder().regex(URL_PATTERN).underlined(true).openlink("$1").type(ChatFormatter.Type.Excluder).build());
private static Gson gson = new GsonBuilder() private static Gson gson = new GsonBuilder()
.registerTypeHierarchyAdapter(TellrawSerializableEnum.class, new TellrawSerializer.TwEnum()) .registerTypeHierarchyAdapter(TellrawSerializableEnum.class, new TellrawSerializer.TwEnum())
.registerTypeHierarchyAdapter(Collection.class, new TellrawSerializer.TwCollection()) .registerTypeHierarchyAdapter(Collection.class, new TellrawSerializer.TwCollection())
@ -124,7 +128,7 @@ public class ChatProcessing {
ArrayList<ChatFormatter> formatters = addFormatters(colormode); ArrayList<ChatFormatter> formatters = addFormatters(colormode);
if (colormode == channel.Color().get() && mp != null && mp.RainbowPresserColorMode) { // Only overwrite channel color if (colormode == channel.Color().get() && mp != null && mp.RainbowPresserColorMode) { // Only overwrite channel color
final AtomicInteger rpc = new AtomicInteger(0); final AtomicInteger rpc = new AtomicInteger(0);
formatters.add(ChatFormatter.builder().color(colormode).onmatch((match, cf) -> { formatters.add(ChatFormatter.builder().color(colormode).onmatch((match, cf, s) -> {
cf.setColor(RainbowPresserColors[rpc.getAndUpdate(i -> ++i < RainbowPresserColors.length ? i : 0)]); cf.setColor(RainbowPresserColors[rpc.getAndUpdate(i -> ++i < RainbowPresserColors.length ? i : 0)]);
return match; return match;
}).build()); }).build());
@ -275,7 +279,7 @@ public class ChatProcessing {
}; };
formatters.add(ChatFormatter.builder().regex(Pattern.compile(namesb.toString())).color(Color.Aqua) formatters.add(ChatFormatter.builder().regex(Pattern.compile(namesb.toString())).color(Color.Aqua)
.onmatch((match, builder) -> { .onmatch((match, builder, section) -> {
Player p = Bukkit.getPlayer(match); Player p = Bukkit.getPlayer(match);
Optional<String> pn = nottest ? Optional.empty() Optional<String> pn = nottest ? Optional.empty()
: Arrays.stream(testPlayers).filter(tp -> tp.equalsIgnoreCase(match)).findAny(); : Arrays.stream(testPlayers).filter(tp -> tp.equalsIgnoreCase(match)).findAny();
@ -297,7 +301,7 @@ public class ChatProcessing {
if (addNickFormatter) if (addNickFormatter)
formatters.add(ChatFormatter.builder().regex((Pattern.compile(nicksb.toString()))).color(Color.Aqua) formatters.add(ChatFormatter.builder().regex((Pattern.compile(nicksb.toString()))).color(Color.Aqua)
.onmatch((match, builder) -> { .onmatch((match, builder, section) -> {
if (PlayerListener.nicknames.containsKey(match.toLowerCase())) { //Made a stream and all that but I can actually store it lowercased if (PlayerListener.nicknames.containsKey(match.toLowerCase())) { //Made a stream and all that but I can actually store it lowercased
Player p = Bukkit.getPlayer(PlayerListener.nicknames.get(match.toLowerCase())); Player p = Bukkit.getPlayer(PlayerListener.nicknames.get(match.toLowerCase()));
if (p == null) { if (p == null) {

View file

@ -42,7 +42,7 @@ public class TownColorComponent extends Component {
public static Map<String, Color> NationColor = new HashMap<>(); public static Map<String, Color> NationColor = new HashMap<>();
public ConfigData<Byte> colorCount() { public ConfigData<Byte> colorCount() {
return getConfig().getData("colorCount", (byte) 1, cc -> (byte) cc, cc -> (int) cc); return getConfig().getData("colorCount", (byte) 1, cc -> ((Integer) cc).byteValue(), Byte::intValue);
} }
public ConfigData<Boolean> useNationColors() { //TODO public ConfigData<Boolean> useNationColors() { //TODO

View file

@ -1,394 +1,398 @@
package buttondevteam.chat.formatting; package buttondevteam.chat.formatting;
import buttondevteam.chat.ChatProcessing; import buttondevteam.chat.ChatProcessing;
import buttondevteam.chat.commands.ucmds.admin.DebugCommand; import buttondevteam.chat.commands.ucmds.admin.DebugCommand;
import buttondevteam.lib.chat.Color; import buttondevteam.lib.chat.Color;
import buttondevteam.lib.chat.Priority; import buttondevteam.lib.chat.Priority;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.val; import lombok.val;
import java.util.*; import java.util.*;
import java.util.function.BiFunction; import java.util.function.Predicate;
import java.util.function.Predicate; import java.util.regex.Matcher;
import java.util.regex.Matcher; import java.util.regex.Pattern;
import java.util.regex.Pattern; import java.util.stream.Collectors;
import java.util.stream.Collectors;
/**
/** * A {@link ChatFormatter} shows what formatting to use based on regular expressions. {@link ChatFormatter#Combine(List, String, TellrawPart)} is used to turn it into a {@link TellrawPart}, combining
* A {@link ChatFormatter} shows what formatting to use based on regular expressions. {@link ChatFormatter#Combine(List, String, TellrawPart)} is used to turn it into a {@link TellrawPart}, combining * intersecting parts found, for example when {@code _abc*def*ghi_} is said in chat, it'll turn it into an underlined part, then an underlined <i>and italics</i> part, finally an underlined part
* intersecting parts found, for example when {@code _abc*def*ghi_} is said in chat, it'll turn it into an underlined part, then an underlined <i>and italics</i> part, finally an underlined part * again.
* again. *
* * @author NorbiPeti
* @author NorbiPeti *
* */
*/ @Data
@Data @Builder
@Builder public final class ChatFormatter {
public final class ChatFormatter { Pattern regex;
Pattern regex; boolean italic;
boolean italic; boolean bold;
boolean bold; boolean underlined;
boolean underlined; boolean strikethrough;
boolean strikethrough; boolean obfuscated;
boolean obfuscated; Color color;
Color color; TriFunc<String, ChatFormatter, FormattedSection, String> onmatch;
BiFunction<String, ChatFormatter, String> onmatch; String openlink;
String openlink; @Builder.Default
@Builder.Default Priority priority = Priority.Normal;
Priority priority = Priority.Normal; @Builder.Default
@Builder.Default short removeCharCount = 0;
short removeCharCount = 0; @Builder.Default
@Builder.Default Type type = Type.Normal;
Type type = Type.Normal;
public enum Type {
public enum Type { Normal,
Normal, /**
/** * Matches a start and an end section which gets converted to one section (for example see italics)
* Matches a start and an end section which gets converted to one section (for example see italics) */
*/ Range,
Range, /**
/** * Exclude matching area from further processing (besides this formatter)
* Exclude matching area from further processing (besides this formatter) */
*/ Excluder
Excluder }
}
@FunctionalInterface
public static void Combine(List<ChatFormatter> formatters, String str, TellrawPart tp) { public interface TriFunc<T1, T2, T3, R> {
/* R apply(T1 x1, T2 x2, T3 x3);
* This method assumes that there is always a global formatter }
*/
header("ChatFormatter.Combine begin"); public static void Combine(List<ChatFormatter> formatters, String str, TellrawPart tp) {
ArrayList<FormattedSection> sections = new ArrayList<FormattedSection>(); /*
* This method assumes that there is always a global formatter
for (ChatFormatter formatter : formatters) { */
if (formatter.type != Type.Excluder) header("ChatFormatter.Combine begin");
continue; ArrayList<FormattedSection> sections = new ArrayList<FormattedSection>();
Matcher matcher = formatter.regex.matcher(str);
while (matcher.find()) { for (ChatFormatter formatter : formatters) {
DebugCommand.SendDebugMessage("Found match from " + matcher.start() + " to " + (matcher.end() - 1)); if (formatter.type != Type.Excluder)
DebugCommand.SendDebugMessage("With excluder formatter:" + formatter); continue;
sendMessageWithPointer(str, matcher.start(), matcher.end() - 1); Matcher matcher = formatter.regex.matcher(str);
if (formatter.regex != ChatProcessing.ENTIRE_MESSAGE_PATTERN && sections.stream().anyMatch(fs -> fs.type == Type.Excluder && (fs.End >= matcher.start() && fs.Start <= matcher.end() - 1))) { while (matcher.find()) {
DebugCommand.SendDebugMessage("Ignoring formatter because of an excluder"); DebugCommand.SendDebugMessage("Found match from " + matcher.start() + " to " + (matcher.end() - 1));
continue; //Exclude areas matched by excluders - Range sections are correctly handled afterwards DebugCommand.SendDebugMessage("With excluder formatter:" + formatter);
} sendMessageWithPointer(str, matcher.start(), matcher.end() - 1);
ArrayList<String> groups = new ArrayList<String>(); if (formatter.regex != ChatProcessing.ENTIRE_MESSAGE_PATTERN && sections.stream().anyMatch(fs -> fs.type == Type.Excluder && (fs.End >= matcher.start() && fs.Start <= matcher.end() - 1))) {
for (int i = 0; i < matcher.groupCount(); i++) DebugCommand.SendDebugMessage("Ignoring formatter because of an excluder");
groups.add(matcher.group(i + 1)); continue; //Exclude areas matched by excluders - Range sections are correctly handled afterwards
if (groups.size() > 0) }
DebugCommand.SendDebugMessage("First group: " + groups.get(0)); ArrayList<String> groups = new ArrayList<String>();
FormattedSection section = new FormattedSection(formatter, matcher.start(), matcher.end() - 1, groups, for (int i = 0; i < matcher.groupCount(); i++)
formatter.type); groups.add(matcher.group(i + 1));
sections.add(section); if (groups.size() > 0)
} DebugCommand.SendDebugMessage("First group: " + groups.get(0));
} FormattedSection section = new FormattedSection(formatter, matcher.start(), matcher.end() - 1, groups,
formatter.type);
header("Section creation (excluders done)"); sections.add(section);
for (ChatFormatter formatter : formatters) { }
if (formatter.type == Type.Excluder) }
continue;
Matcher matcher = formatter.regex.matcher(str); header("Section creation (excluders done)");
while (matcher.find()) { for (ChatFormatter formatter : formatters) {
DebugCommand.SendDebugMessage("Found match from " + matcher.start() + " to " + (matcher.end() - 1)); if (formatter.type == Type.Excluder)
DebugCommand.SendDebugMessage("With formatter:" + formatter); continue;
sendMessageWithPointer(str, matcher.start(), matcher.end() - 1); Matcher matcher = formatter.regex.matcher(str);
if (formatter.regex != ChatProcessing.ENTIRE_MESSAGE_PATTERN && sections.stream().anyMatch(fs -> fs.type == Type.Excluder && (fs.End >= matcher.start() && fs.Start <= matcher.end() - 1))) { while (matcher.find()) {
DebugCommand.SendDebugMessage("Ignoring formatter because of an excluder"); DebugCommand.SendDebugMessage("Found match from " + matcher.start() + " to " + (matcher.end() - 1));
continue; //Exclude areas matched by excluders - Range sections are correctly handled afterwards DebugCommand.SendDebugMessage("With formatter:" + formatter);
} sendMessageWithPointer(str, matcher.start(), matcher.end() - 1);
ArrayList<String> groups = new ArrayList<String>(); if (formatter.regex != ChatProcessing.ENTIRE_MESSAGE_PATTERN && sections.stream().anyMatch(fs -> fs.type == Type.Excluder && (fs.End >= matcher.start() && fs.Start <= matcher.end() - 1))) {
for (int i = 0; i < matcher.groupCount(); i++) DebugCommand.SendDebugMessage("Ignoring formatter because of an excluder");
groups.add(matcher.group(i + 1)); continue; //Exclude areas matched by excluders - Range sections are correctly handled afterwards
if (groups.size() > 0) }
DebugCommand.SendDebugMessage("First group: " + groups.get(0)); ArrayList<String> groups = new ArrayList<String>();
FormattedSection section = new FormattedSection(formatter, matcher.start(), matcher.end() - 1, groups, for (int i = 0; i < matcher.groupCount(); i++)
formatter.type); groups.add(matcher.group(i + 1));
sections.add(section); if (groups.size() > 0)
} DebugCommand.SendDebugMessage("First group: " + groups.get(0));
} FormattedSection section = new FormattedSection(formatter, matcher.start(), matcher.end() - 1, groups,
sections.sort( formatter.type);
(s1, s2) -> s1.Start == s2.Start sections.add(section);
? s1.End == s2.End ? Integer.compare(s2.Formatters.get(0).priority.GetValue(), }
s1.Formatters.get(0).priority.GetValue()) : Integer.compare(s2.End, s1.End) }
: Integer.compare(s1.Start, s2.Start)); sections.sort(
(s1, s2) -> s1.Start == s2.Start
/** ? s1.End == s2.End ? Integer.compare(s2.Formatters.get(0).priority.GetValue(),
* 0: Start - 1: End index s1.Formatters.get(0).priority.GetValue()) : Integer.compare(s2.End, s1.End)
*/ : Integer.compare(s1.Start, s2.Start));
val remchars = new ArrayList<int[]>();
/**
header("Range section conversion"); * 0: Start - 1: End index
ArrayList<FormattedSection> combined = new ArrayList<>(); */
Map<ChatFormatter, FormattedSection> nextSection = new HashMap<>(); val remchars = new ArrayList<int[]>();
boolean escaped = false;
int takenStart = -1, takenEnd = -1; header("Range section conversion");
ChatFormatter takenFormatter = null; ArrayList<FormattedSection> combined = new ArrayList<>();
for (int i = 0; i < sections.size(); i++) { Map<ChatFormatter, FormattedSection> nextSection = new HashMap<>();
// Set ending to -1 until closed with another 1 long "section" - only do this if IsRange is true boolean escaped = false;
final FormattedSection section = sections.get(i); int takenStart = -1, takenEnd = -1;
if (section.type!=Type.Range) { ChatFormatter takenFormatter = null;
escaped = section.Formatters.contains(ChatProcessing.ESCAPE_FORMATTER) && !escaped; // Enable escaping on first \, disable on second for (int i = 0; i < sections.size(); i++) {
if (escaped) {// Don't add the escape character // Set ending to -1 until closed with another 1 long "section" - only do this if IsRange is true
remchars.add(new int[]{section.Start, section.Start}); final FormattedSection section = sections.get(i);
DebugCommand.SendDebugMessage("Found escaper section: " + section); if (section.type!=Type.Range) {
} else { escaped = section.Formatters.contains(ChatProcessing.ESCAPE_FORMATTER) && !escaped; // Enable escaping on first \, disable on second
combined.add(section); // The above will delete the \ if (escaped) {// Don't add the escape character
DebugCommand.SendDebugMessage("Added section: " + section); remchars.add(new int[]{section.Start, section.Start});
} DebugCommand.SendDebugMessage("Found escaper section: " + section);
sendMessageWithPointer(str, section.Start, section.End); } else {
continue; combined.add(section); // The above will delete the \
} DebugCommand.SendDebugMessage("Added section: " + section);
if (!escaped) { }
if (combined.stream().anyMatch(s -> section.type != Type.Range && (s.Start == section.Start sendMessageWithPointer(str, section.Start, section.End);
|| (s.Start < section.Start ? s.End >= section.Start : s.Start <= section.End)))) { continue;
DebugCommand.SendDebugMessage("Range " + section + " overlaps with a combined section, ignoring."); }
sendMessageWithPointer(str, section.Start, section.End); if (!escaped) {
continue; if (combined.stream().anyMatch(s -> section.type != Type.Range && (s.Start == section.Start
} || (s.Start < section.Start ? s.End >= section.Start : s.Start <= section.End)))) {
if (section.Start == takenStart || (section.Start > takenStart && section.Start < takenEnd)) { DebugCommand.SendDebugMessage("Range " + section + " overlaps with a combined section, ignoring.");
/* sendMessageWithPointer(str, section.Start, section.End);
* if (nextSection.containsKey(section.Formatters.get(0)) ? section.RemCharFromStart <= takenEnd - takenStart : section.RemCharFromStart > takenEnd - takenStart) { continue;
*/ }
if (section.Formatters.get(0).removeCharCount < takenEnd - takenStart) { if (section.Start == takenStart || (section.Start > takenStart && section.Start < takenEnd)) {
DebugCommand.SendDebugMessage("Lose: " + section); /*
sendMessageWithPointer(str, section.Start, section.End); * if (nextSection.containsKey(section.Formatters.get(0)) ? section.RemCharFromStart <= takenEnd - takenStart : section.RemCharFromStart > takenEnd - takenStart) {
DebugCommand.SendDebugMessage("And win: " + takenFormatter); */
continue; // The current section loses if (section.Formatters.get(0).removeCharCount < takenEnd - takenStart) {
} DebugCommand.SendDebugMessage("Lose: " + section);
nextSection.remove(takenFormatter); // The current section wins sendMessageWithPointer(str, section.Start, section.End);
DebugCommand.SendDebugMessage("Win: " + section); DebugCommand.SendDebugMessage("And win: " + takenFormatter);
sendMessageWithPointer(str, section.Start, section.End); continue; // The current section loses
DebugCommand.SendDebugMessage("And lose: " + takenFormatter); }
} nextSection.remove(takenFormatter); // The current section wins
takenStart = section.Start; DebugCommand.SendDebugMessage("Win: " + section);
takenEnd = section.Start + section.Formatters.get(0).removeCharCount; sendMessageWithPointer(str, section.Start, section.End);
takenFormatter = section.Formatters.get(0); DebugCommand.SendDebugMessage("And lose: " + takenFormatter);
if (nextSection.containsKey(section.Formatters.get(0))) { }
FormattedSection s = nextSection.remove(section.Formatters.get(0)); takenStart = section.Start;
// section: the ending marker section - s: the to-be full section takenEnd = section.Start + section.Formatters.get(0).removeCharCount;
s.End = takenEnd - 1; //Take the remCharCount into account as well takenFormatter = section.Formatters.get(0);
// s.IsRange = false; // IsRange means it's a 1 long section indicating a start or an end if (nextSection.containsKey(section.Formatters.get(0))) {
combined.add(s); FormattedSection s = nextSection.remove(section.Formatters.get(0));
DebugCommand.SendDebugMessage("Finished section: " + s); // section: the ending marker section - s: the to-be full section
sendMessageWithPointer(str, s.Start, s.End); s.End = takenEnd - 1; //Take the remCharCount into account as well
} else { // s.IsRange = false; // IsRange means it's a 1 long section indicating a start or an end
DebugCommand.SendDebugMessage("Adding next section: " + section); combined.add(s);
sendMessageWithPointer(str, section.Start, section.End); DebugCommand.SendDebugMessage("Finished section: " + s);
nextSection.put(section.Formatters.get(0), section); sendMessageWithPointer(str, s.Start, s.End);
} } else {
DebugCommand DebugCommand.SendDebugMessage("Adding next section: " + section);
.SendDebugMessage("New area taken: (" + takenStart + "-" + takenEnd + ") " + takenFormatter); sendMessageWithPointer(str, section.Start, section.End);
sendMessageWithPointer(str, takenStart, takenEnd); nextSection.put(section.Formatters.get(0), section);
} else { }
DebugCommand.SendDebugMessage("Skipping section: " + section); // This will keep the text (character) DebugCommand
sendMessageWithPointer(str, section.Start, section.End); .SendDebugMessage("New area taken: (" + takenStart + "-" + takenEnd + ") " + takenFormatter);
escaped = false; // Reset escaping if applied, like if we're at the '*' in '\*' sendMessageWithPointer(str, takenStart, takenEnd);
} } else {
} DebugCommand.SendDebugMessage("Skipping section: " + section); // This will keep the text (character)
//Do not finish unfinished sections, ignore them sendMessageWithPointer(str, section.Start, section.End);
sections = combined; escaped = false; // Reset escaping if applied, like if we're at the '*' in '\*'
}
header("Adding remove chars (RC)"); // Important to add after the range section conversion }
sections.stream() //Do not finish unfinished sections, ignore them
.flatMap(fs -> fs.Formatters.stream().filter(cf -> cf.removeCharCount > 0) sections = combined;
.mapToInt(cf -> cf.removeCharCount).mapToObj(rcc -> new int[]{fs.Start, fs.Start + rcc - 1}))
.forEach(rc -> remchars.add(rc)); header("Adding remove chars (RC)"); // Important to add after the range section conversion
sections.stream() sections.stream()
.flatMap(fs -> fs.Formatters.stream().filter(cf -> cf.removeCharCount > 0) .flatMap(fs -> fs.Formatters.stream().filter(cf -> cf.removeCharCount > 0)
.mapToInt(cf -> cf.removeCharCount).mapToObj(rcc -> new int[]{fs.End - rcc + 1, fs.End})) .mapToInt(cf -> cf.removeCharCount).mapToObj(rcc -> new int[]{fs.Start, fs.Start + rcc - 1}))
.forEach(rc -> remchars.add(rc)); .forEach(rc -> remchars.add(rc));
DebugCommand.SendDebugMessage("Added remchars:"); sections.stream()
DebugCommand .flatMap(fs -> fs.Formatters.stream().filter(cf -> cf.removeCharCount > 0)
.SendDebugMessage(remchars.stream().map(rc -> Arrays.toString(rc)).collect(Collectors.joining("; "))); .mapToInt(cf -> cf.removeCharCount).mapToObj(rcc -> new int[]{fs.End - rcc + 1, fs.End}))
.forEach(rc -> remchars.add(rc));
header("Section combining"); DebugCommand.SendDebugMessage("Added remchars:");
boolean cont = true; DebugCommand
boolean found = false; .SendDebugMessage(remchars.stream().map(rc -> Arrays.toString(rc)).collect(Collectors.joining("; ")));
for (int i = 1; cont;) {
int nextindex = i + 1; header("Section combining");
if (sections.size() < 2) boolean cont = true;
break; boolean found = false;
DebugCommand.SendDebugMessage("i: " + i); for (int i = 1; cont;) {
FormattedSection firstSection = sections.get(i - 1); int nextindex = i + 1;
DebugCommand.SendDebugMessage("Combining sections " + firstSection); if (sections.size() < 2)
sendMessageWithPointer(str, firstSection.Start, firstSection.End); break;
DebugCommand.SendDebugMessage(" and " + sections.get(i)); DebugCommand.SendDebugMessage("i: " + i);
sendMessageWithPointer(str, sections.get(i).Start, sections.get(i).End); FormattedSection firstSection = sections.get(i - 1);
if (firstSection.Start == sections.get(i).Start && firstSection.End == sections.get(i).End) { DebugCommand.SendDebugMessage("Combining sections " + firstSection);
firstSection.Formatters.addAll(sections.get(i).Formatters); sendMessageWithPointer(str, firstSection.Start, firstSection.End);
firstSection.Matches.addAll(sections.get(i).Matches); DebugCommand.SendDebugMessage(" and " + sections.get(i));
DebugCommand.SendDebugMessage("To section " + firstSection); sendMessageWithPointer(str, sections.get(i).Start, sections.get(i).End);
sendMessageWithPointer(str, firstSection.Start, firstSection.End); if (firstSection.Start == sections.get(i).Start && firstSection.End == sections.get(i).End) {
sections.remove(i); firstSection.Formatters.addAll(sections.get(i).Formatters);
found = true; firstSection.Matches.addAll(sections.get(i).Matches);
} else if (firstSection.End > sections.get(i).Start && firstSection.Start < sections.get(i).End) { DebugCommand.SendDebugMessage("To section " + firstSection);
int origend = firstSection.End; sendMessageWithPointer(str, firstSection.Start, firstSection.End);
firstSection.End = sections.get(i).Start - 1; sections.remove(i);
int origend2 = sections.get(i).End; found = true;
boolean switchends; } else if (firstSection.End > sections.get(i).Start && firstSection.Start < sections.get(i).End) {
if (switchends = origend2 < origend) { int origend = firstSection.End;
int tmp = origend; firstSection.End = sections.get(i).Start - 1;
origend = origend2; int origend2 = sections.get(i).End;
origend2 = tmp; boolean switchends;
} if (switchends = origend2 < origend) {
FormattedSection section = new FormattedSection(firstSection.Formatters, sections.get(i).Start, origend, int tmp = origend;
firstSection.Matches, Type.Normal); origend = origend2;
section.Formatters.addAll(sections.get(i).Formatters); origend2 = tmp;
section.Matches.addAll(sections.get(i).Matches); // TODO: Clean }
sections.add(i, section); FormattedSection section = new FormattedSection(firstSection.Formatters, sections.get(i).Start, origend,
nextindex++; firstSection.Matches, Type.Normal);
FormattedSection thirdFormattedSection = sections.get(i + 1); section.Formatters.addAll(sections.get(i).Formatters);
if (switchends) { // Use the properties of the first section not the second one section.Matches.addAll(sections.get(i).Matches); // TODO: Clean
thirdFormattedSection.Formatters.clear(); sections.add(i, section);
thirdFormattedSection.Formatters.addAll(firstSection.Formatters); nextindex++;
thirdFormattedSection.Matches.clear(); FormattedSection thirdFormattedSection = sections.get(i + 1);
thirdFormattedSection.Matches.addAll(firstSection.Matches); if (switchends) { // Use the properties of the first section not the second one
} thirdFormattedSection.Formatters.clear();
thirdFormattedSection.Start = origend + 1; thirdFormattedSection.Formatters.addAll(firstSection.Formatters);
thirdFormattedSection.End = origend2; thirdFormattedSection.Matches.clear();
thirdFormattedSection.Matches.addAll(firstSection.Matches);
ArrayList<FormattedSection> sts = sections; }
Predicate<FormattedSection> removeIfNeeded = s -> { thirdFormattedSection.Start = origend + 1;
if (s.Start < 0 || s.End < 0 || s.Start > s.End) { thirdFormattedSection.End = origend2;
DebugCommand.SendDebugMessage("Removing section: " + s);
sendMessageWithPointer(str, s.Start, s.End); ArrayList<FormattedSection> sts = sections;
sts.remove(s); Predicate<FormattedSection> removeIfNeeded = s -> {
return true; if (s.Start < 0 || s.End < 0 || s.Start > s.End) {
} DebugCommand.SendDebugMessage("Removing section: " + s);
return false; sendMessageWithPointer(str, s.Start, s.End);
}; sts.remove(s);
return true;
DebugCommand.SendDebugMessage("To sections"); }
if (!removeIfNeeded.test(firstSection)) { return false;
DebugCommand.SendDebugMessage(" 1:" + firstSection + ""); };
sendMessageWithPointer(str, firstSection.Start, firstSection.End);
} DebugCommand.SendDebugMessage("To sections");
if (!removeIfNeeded.test(section)) { if (!removeIfNeeded.test(firstSection)) {
DebugCommand.SendDebugMessage(" 2:" + section + ""); DebugCommand.SendDebugMessage(" 1:" + firstSection + "");
sendMessageWithPointer(str, section.Start, section.End); sendMessageWithPointer(str, firstSection.Start, firstSection.End);
} }
if (!removeIfNeeded.test(thirdFormattedSection)) { if (!removeIfNeeded.test(section)) {
DebugCommand.SendDebugMessage(" 3:" + thirdFormattedSection); DebugCommand.SendDebugMessage(" 2:" + section + "");
sendMessageWithPointer(str, thirdFormattedSection.Start, thirdFormattedSection.End); sendMessageWithPointer(str, section.Start, section.End);
} }
found = true; if (!removeIfNeeded.test(thirdFormattedSection)) {
} DebugCommand.SendDebugMessage(" 3:" + thirdFormattedSection);
for (int j = i - 1; j <= i + 1; j++) { sendMessageWithPointer(str, thirdFormattedSection.Start, thirdFormattedSection.End);
if (j < sections.size() && sections.get(j).End < sections.get(j).Start) { }
DebugCommand.SendDebugMessage("Removing section: " + sections.get(j)); found = true;
sendMessageWithPointer(str, sections.get(j).Start, sections.get(j).End); }
sections.remove(j); for (int j = i - 1; j <= i + 1; j++) {
j--; if (j < sections.size() && sections.get(j).End < sections.get(j).Start) {
found = true; DebugCommand.SendDebugMessage("Removing section: " + sections.get(j));
} sendMessageWithPointer(str, sections.get(j).Start, sections.get(j).End);
} sections.remove(j);
i = nextindex - 1; j--;
i++; found = true;
if (i >= sections.size()) { }
if (found) { }
i = 1; i = nextindex - 1;
found = false; i++;
sections.sort( if (i >= sections.size()) {
(s1, s2) -> s1.Start == s2.Start if (found) {
? s1.End == s2.End i = 1;
? Integer.compare(s2.Formatters.get(0).priority.GetValue(), found = false;
s1.Formatters.get(0).priority.GetValue()) sections.sort(
: Integer.compare(s2.End, s1.End) (s1, s2) -> s1.Start == s2.Start
: Integer.compare(s1.Start, s2.Start)); ? s1.End == s2.End
} else ? Integer.compare(s2.Formatters.get(0).priority.GetValue(),
cont = false; s1.Formatters.get(0).priority.GetValue())
} : Integer.compare(s2.End, s1.End)
} : Integer.compare(s1.Start, s2.Start));
} else
header("Section applying"); cont = false;
TellrawPart lasttp = null; String lastlink = null; }
for (FormattedSection section : sections) { }
DebugCommand.SendDebugMessage("Applying section: " + section);
String originaltext; header("Section applying");
int start = section.Start, end = section.End; TellrawPart lasttp = null; String lastlink = null;
DebugCommand.SendDebugMessage("Start: " + start + " - End: " + end); for (FormattedSection section : sections) {
sendMessageWithPointer(str, start, end); DebugCommand.SendDebugMessage("Applying section: " + section);
val rcs = remchars.stream().filter(rc -> rc[0] <= start && start <= rc[1]).findAny(); String originaltext;
val rce = remchars.stream().filter(rc -> rc[0] <= end && end <= rc[1]).findAny(); int start = section.Start, end = section.End;
val rci = remchars.stream().filter(rc -> start < rc[0] && rc[1] < end).toArray(int[][]::new); DebugCommand.SendDebugMessage("Start: " + start + " - End: " + end);
int s = start, e = end; sendMessageWithPointer(str, start, end);
if (rcs.isPresent()) val rcs = remchars.stream().filter(rc -> rc[0] <= start && start <= rc[1]).findAny();
s = rcs.get()[1] + 1; val rce = remchars.stream().filter(rc -> rc[0] <= end && end <= rc[1]).findAny();
if (rce.isPresent()) val rci = remchars.stream().filter(rc -> start < rc[0] && rc[1] < end).toArray(int[][]::new);
e = rce.get()[0] - 1; int s = start, e = end;
DebugCommand.SendDebugMessage("After RC - Start: " + s + " - End: " + e); if (rcs.isPresent())
if (e - s < 0) { //e-s==0 means the end char is the same as start char, so one char message s = rcs.get()[1] + 1;
DebugCommand.SendDebugMessage("Skipping section because of remchars (length would be " + (e - s + 1) + ")"); if (rce.isPresent())
continue; e = rce.get()[0] - 1;
} DebugCommand.SendDebugMessage("After RC - Start: " + s + " - End: " + e);
originaltext = str.substring(s, e + 1); if (e - s < 0) { //e-s==0 means the end char is the same as start char, so one char message
val sb = new StringBuilder(originaltext); DebugCommand.SendDebugMessage("Skipping section because of remchars (length would be " + (e - s + 1) + ")");
for (int x = rci.length - 1; x >= 0; x--) continue;
sb.delete(rci[x][0] - start - 1, rci[x][1] - start); //Delete going backwards }
originaltext = sb.toString(); originaltext = str.substring(s, e + 1);
DebugCommand.SendDebugMessage("Section text: " + originaltext); val sb = new StringBuilder(originaltext);
String openlink = null; for (int x = rci.length - 1; x >= 0; x--)
section.Formatters.sort(Comparator.comparing(cf2 -> cf2.priority.GetValue())); //Apply the highest last, to overwrite previous ones sb.delete(rci[x][0] - start - 1, rci[x][1] - start); //Delete going backwards
TellrawPart newtp = new TellrawPart(""); originaltext = sb.toString();
for (ChatFormatter formatter : section.Formatters) { DebugCommand.SendDebugMessage("Section text: " + originaltext);
DebugCommand.SendDebugMessage("Applying formatter: " + formatter); String openlink = null;
if (formatter.onmatch != null) section.Formatters.sort(Comparator.comparing(cf2 -> cf2.priority.GetValue())); //Apply the highest last, to overwrite previous ones
originaltext = formatter.onmatch.apply(originaltext, formatter); TellrawPart newtp = new TellrawPart("");
if (formatter.color != null) for (ChatFormatter formatter : section.Formatters) {
newtp.setColor(formatter.color); DebugCommand.SendDebugMessage("Applying formatter: " + formatter);
if (formatter.bold) if (formatter.onmatch != null)
newtp.setBold(formatter.bold); originaltext = formatter.onmatch.apply(originaltext, formatter, section);
if (formatter.italic) if (formatter.color != null)
newtp.setItalic(formatter.italic); newtp.setColor(formatter.color);
if (formatter.underlined) if (formatter.bold)
newtp.setUnderlined(formatter.underlined); newtp.setBold(formatter.bold);
if (formatter.strikethrough) if (formatter.italic)
newtp.setStrikethrough(formatter.strikethrough); newtp.setItalic(formatter.italic);
if (formatter.obfuscated) if (formatter.underlined)
newtp.setObfuscated(formatter.obfuscated); newtp.setUnderlined(formatter.underlined);
if (formatter.openlink != null) if (formatter.strikethrough)
openlink = formatter.openlink; newtp.setStrikethrough(formatter.strikethrough);
} if (formatter.obfuscated)
if (lasttp != null && newtp.getColor() == lasttp.getColor() newtp.setObfuscated(formatter.obfuscated);
&& newtp.isBold() == lasttp.isBold() if (formatter.openlink != null)
&& newtp.isItalic() == lasttp.isItalic() openlink = formatter.openlink;
&& newtp.isUnderlined() == lasttp.isUnderlined() }
&& newtp.isStrikethrough() == lasttp.isStrikethrough() if (lasttp != null && newtp.getColor() == lasttp.getColor()
&& newtp.isObfuscated() == lasttp.isObfuscated() && newtp.isBold() == lasttp.isBold()
&& (openlink == null ? lastlink == null : openlink.equals(lastlink))) { && newtp.isItalic() == lasttp.isItalic()
DebugCommand.SendDebugMessage("This part has the same properties as the previous one, combining."); && newtp.isUnderlined() == lasttp.isUnderlined()
lasttp.setText(lasttp.getText() + originaltext); && newtp.isStrikethrough() == lasttp.isStrikethrough()
continue; //Combine parts with the same properties && newtp.isObfuscated() == lasttp.isObfuscated()
} && Objects.equals(openlink, lastlink)) {
newtp.setText(originaltext); DebugCommand.SendDebugMessage("This part has the same properties as the previous one, combining.");
if (openlink != null && openlink.length() > 0) { lasttp.setText(lasttp.getText() + originaltext);
newtp.setClickEvent(TellrawEvent.create(TellrawEvent.ClickAction.OPEN_URL, continue; //Combine parts with the same properties
(section.Matches.size() > 0 ? openlink.replace("$1", section.Matches.get(0)) : openlink))) }
.setHoverEvent(TellrawEvent.create(TellrawEvent.HoverAction.SHOW_TEXT, newtp.setText(originaltext);
new TellrawPart("Click to open").setColor(Color.Blue))); if (openlink != null && openlink.length() > 0) {
} newtp.setClickEvent(TellrawEvent.create(TellrawEvent.ClickAction.OPEN_URL,
tp.addExtra(newtp); (section.Matches.size() > 0 ? openlink.replace("$1", section.Matches.get(0)) : openlink)))
lasttp = newtp; .setHoverEvent(TellrawEvent.create(TellrawEvent.HoverAction.SHOW_TEXT,
} new TellrawPart("Click to open").setColor(Color.Blue)));
header("ChatFormatter.Combine done"); }
} tp.addExtra(newtp);
lasttp = newtp;
private static void sendMessageWithPointer(String str, int... pointer) { }
DebugCommand.SendDebugMessage(str); header("ChatFormatter.Combine done");
StringBuilder sb = new StringBuilder(str.length()); }
Arrays.sort(pointer);
for (int i = 0; i < pointer.length; i++) { private static void sendMessageWithPointer(String str, int... pointer) {
for (int j = 0; j < pointer[i] - (i > 0 ? pointer[i - 1] + 1 : 0); j++) DebugCommand.SendDebugMessage(str);
sb.append(' '); StringBuilder sb = new StringBuilder(str.length());
if (pointer[i] == (i > 0 ? pointer[i - 1] : -1)) Arrays.sort(pointer);
continue; for (int i = 0; i < pointer.length; i++) {
sb.append('^'); for (int j = 0; j < pointer[i] - (i > 0 ? pointer[i - 1] + 1 : 0); j++)
} sb.append(' ');
DebugCommand.SendDebugMessage(sb.toString()); if (pointer[i] == (i > 0 ? pointer[i - 1] : -1))
} continue;
sb.append('^');
private static void header(String message) { }
DebugCommand.SendDebugMessage("\n--------\n" + message + "\n--------\n"); DebugCommand.SendDebugMessage(sb.toString());
} }
}
private static void header(String message) {
DebugCommand.SendDebugMessage("\n--------\n" + message + "\n--------\n");
}
}

View file

@ -1,36 +1,36 @@
package buttondevteam.chat.formatting; package buttondevteam.chat.formatting;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
class FormattedSection { public class FormattedSection {
int Start; public int Start;
int End; public int End;
ArrayList<ChatFormatter> Formatters = new ArrayList<ChatFormatter>(); public ArrayList<ChatFormatter> Formatters = new ArrayList<ChatFormatter>();
ArrayList<String> Matches = new ArrayList<String>(); public ArrayList<String> Matches = new ArrayList<String>();
ChatFormatter.Type type; public ChatFormatter.Type type;
FormattedSection(ChatFormatter formatter, int start, int end, ArrayList<String> matches, ChatFormatter.Type type) { FormattedSection(ChatFormatter formatter, int start, int end, ArrayList<String> matches, ChatFormatter.Type type) {
Start = start; Start = start;
End = end; End = end;
Formatters.add(formatter); Formatters.add(formatter);
Matches.addAll(matches); Matches.addAll(matches);
this.type = type; this.type = type;
} }
FormattedSection(Collection<ChatFormatter> formatters, int start, int end, ArrayList<String> matches, FormattedSection(Collection<ChatFormatter> formatters, int start, int end, ArrayList<String> matches,
ChatFormatter.Type type) { ChatFormatter.Type type) {
Start = start; Start = start;
End = end; End = end;
Formatters.addAll(formatters); Formatters.addAll(formatters);
Matches.addAll(matches); Matches.addAll(matches);
this.type = type; this.type = type;
} }
@Override @Override
public String toString() { public String toString() {
return "Section(" + Start + ", " + End + ", formatters: " + return "Section(" + Start + ", " + End + ", formatters: " +
Formatters.toString() + ", matches: " + Matches.toString() + ", " + Formatters.toString() + ", matches: " + Matches.toString() + ", " +
type + ")"; type + ")";
} }
} }

View file

@ -12,7 +12,6 @@ import buttondevteam.core.ComponentManager;
import buttondevteam.lib.player.TBMCPlayerJoinEvent; import buttondevteam.lib.player.TBMCPlayerJoinEvent;
import buttondevteam.lib.player.TBMCPlayerLoadEvent; import buttondevteam.lib.player.TBMCPlayerLoadEvent;
import buttondevteam.lib.player.TBMCPlayerSaveEvent; import buttondevteam.lib.player.TBMCPlayerSaveEvent;
import org.bukkit.Bukkit;
import org.bukkit.GameMode; import org.bukkit.GameMode;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
@ -32,7 +31,7 @@ public class PlayerJoinLeaveListener implements Listener {
@EventHandler @EventHandler
public void onPlayerTBMCJoin(TBMCPlayerJoinEvent e) { public void onPlayerTBMCJoin(TBMCPlayerJoinEvent e) {
ChatPlayer cp = e.GetPlayer().asPluginPlayer(ChatPlayer.class); ChatPlayer cp = e.GetPlayer().asPluginPlayer(ChatPlayer.class);
Player p = Bukkit.getPlayer(cp.getUUID()); Player p = e.getPlayer();
if (ComponentManager.isEnabled(FlairComponent.class)) { if (ComponentManager.isEnabled(FlairComponent.class)) {
if (!cp.FlairState().get().equals(FlairStates.NoComment)) { if (!cp.FlairState().get().equals(FlairStates.NoComment)) {

View file

@ -37,9 +37,9 @@ depend:
- Votifier - Votifier
- Vault - Vault
- ButtonCore - ButtonCore
- Dynmap-Towny
soft-depend: soft-depend:
- Minigames - Minigames
- Dynmap-Towny
permissions: permissions:
tbmc.admin: tbmc.admin:
description: Gives access to /un- commands and /u admin commands description: Gives access to /un- commands and /u admin commands

View file

@ -66,10 +66,15 @@ public class ChatFormatIT {
list.add(new ChatFormatIT(sender, "*test _test_ test*", new TellrawPart("test ").setItalic(true).setColor(Color.White), list.add(new ChatFormatIT(sender, "*test _test_ test*", new TellrawPart("test ").setItalic(true).setColor(Color.White),
new TellrawPart("test").setItalic(true).setUnderlined(true).setColor(Color.White), new TellrawPart(" test").setItalic(true).setColor(Color.White))); new TellrawPart("test").setItalic(true).setUnderlined(true).setColor(Color.White), new TellrawPart(" test").setItalic(true).setColor(Color.White)));
list.add(new ChatFormatIT(sender, "https://norbipeti.github.io/test?test&test#test", new TellrawPart("https://norbipeti.github.io/test?test&test#test") list.add(new ChatFormatIT(sender, "https://norbipeti.github.io/test?test&test#test", new TellrawPart("https://norbipeti.github.io/test?test&test#test")
.setColor(Color.White).setUnderlined(true) .setColor(Color.White).setUnderlined(true)
.setHoverEvent(TellrawEvent.create(HoverAction.SHOW_TEXT, .setHoverEvent(TellrawEvent.create(HoverAction.SHOW_TEXT,
new TellrawPart("Click to open").setColor(Color.Blue))) new TellrawPart("Click to open").setColor(Color.Blue)))
.setClickEvent(TellrawEvent.create(ClickAction.OPEN_URL, "https://norbipeti.github.io/test?test&test#test")))); .setClickEvent(TellrawEvent.create(ClickAction.OPEN_URL, "https://norbipeti.github.io/test?test&test#test"))));
list.add(new ChatFormatIT(sender, "[hmm](https://norbipeti.github.io/test)", new TellrawPart("hmm")
.setColor(Color.White).setUnderlined(true)
.setHoverEvent(TellrawEvent.create(HoverAction.SHOW_TEXT,
new TellrawPart("Click to open").setColor(Color.Blue)))
.setClickEvent(TellrawEvent.create(ClickAction.OPEN_URL, "https://norbipeti.github.io/test"))));
return list; return list;
} }