Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Discord->Minecraft Chat Formatting. #3

Draft
wants to merge 3 commits into
base: dev/1.18.x
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/main/java/tk/sciwhiz12/concord/ConcordConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ public class ConcordConfig {

public static final ForgeConfigSpec.BooleanValue USE_CUSTOM_FONT;
public static final ForgeConfigSpec.BooleanValue LAZY_TRANSLATIONS;
public static final ForgeConfigSpec.BooleanValue USE_LEGACY_FORMATTING;
public static final ForgeConfigSpec.BooleanValue USE_CUSTOM_FORMATTING;
public static final ForgeConfigSpec.EnumValue<CrownVisibility> HIDE_CROWN;

public static final ForgeConfigSpec.BooleanValue ALLOW_MENTIONS;
Expand Down Expand Up @@ -109,6 +111,19 @@ public static void register() {
"Set to false if you cannot ensure that all clients will have the mod installed.")
.define("lazy_translate", true);

USE_CUSTOM_FORMATTING = builder.comment("Allow Discord users to use Concord Message Formatting Codes in a message.",
"This will cause in-game messages to have color formatting.",
"To use it, send a message with a dollar sign ($) followed by either an English-language color (ie. $red), or a hex code (ie. $#FF0000).",
"Names are delimited by a space which will be consumed, so the string \"this is a $red colored text\" will be shown as \"this is a colored text\".",
"Please note that Custom Formatting will override Legacy Formatting when enabled. This is intentional.")
.define("use_custom_formatting", false);

USE_LEGACY_FORMATTING = builder.comment("Allow Discord users to put legacy-style chat formatting (&5, etc) in a message.",
"This will cause in-game messages to have color, bold, italic, strikethrough and \"obfuscated\" formatting.",
"Note however, that this only works with vanilla formatting codes, and is likely to cause weirdness.")
.define("use_legacy_formatting", false);


HIDE_CROWN = builder.comment("Configures when the Server Owner crown is visible to clients.",
"ALWAYS means the crown is always visible, NEVER means the crown is never visible.",
"WITHOUT_ADMINISTRATORS means it is only visible when there are no hoisted Administrator roles.")
Expand Down
135 changes: 122 additions & 13 deletions src/main/java/tk/sciwhiz12/concord/msg/Messaging.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,27 +32,19 @@
import net.dv8tion.jda.api.entities.TextChannel;
import net.minecraft.ChatFormatting;
import net.minecraft.Util;
import net.minecraft.network.chat.ChatType;
import net.minecraft.network.chat.ClickEvent;
import net.minecraft.network.chat.ComponentUtils;
import net.minecraft.network.chat.HoverEvent;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.chat.TextColor;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.network.chat.*;
import net.minecraft.network.protocol.game.ClientboundChatPacket;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import org.spongepowered.asm.mixin.Mutable;
import tk.sciwhiz12.concord.Concord;
import tk.sciwhiz12.concord.ConcordConfig;
import tk.sciwhiz12.concord.ModPresenceTracker;
import tk.sciwhiz12.concord.util.TranslationUtil;

import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.*;
import java.util.function.Predicate;
import java.util.function.Supplier;

Expand Down Expand Up @@ -132,7 +124,7 @@ public static TranslatableComponent createMessage(boolean useIcons, ConcordConfi

public static MutableComponent createContentComponent(Message message) {
final String content = message.getContentDisplay();
final MutableComponent text = new TextComponent(content).withStyle(WHITE);
final MutableComponent text = processCustomFormatting(content);

boolean skipSpace = content.length() <= 0 || Character.isWhitespace(content.codePointAt(content.length() - 1));
for (Message.Attachment attachment : message.getAttachments()) {
Expand Down Expand Up @@ -248,4 +240,121 @@ public static void sendToChannel(JDA discord, CharSequence text) {
channel.sendMessage(text).allowedMentions(allowedMentions).queue();
}
}

/**
* Search a user-input string for legacy-style ChatFormatting.
* ie. the input "&5Sup?" will be sent to Minecraft as "Sup?" with a purple color.
* It is intentional that this only supports the default vanilla formatting.
*
* @param input the text from Discord
* @return a properly formatted MutableComponent to be echoed into chat.
* @author Curle
*/
private static MutableComponent processLegacyFormatting(String input) {
if(!ConcordConfig.USE_LEGACY_FORMATTING.get()) {
// Default to white if legacy formatting is disabled.
return new TextComponent(input).withStyle(WHITE);
} else {
final String[] parts = input.split("(?=&)");
MutableComponent currentComponent = new TextComponent("");

for (String part : parts) {
// Ensure that we only process non-empty strings
if (part.isEmpty()) continue;

final boolean partHasFormatter = part.charAt(0) == '&';
// Short circuit for strings of only "&" to avoid a temporal paradox
if (partHasFormatter && part.length() == 1) {
currentComponent = currentComponent.append(new TextComponent(part).withStyle(WHITE));
continue;
}

// Parse a formatting character after the & trigger
final ChatFormatting formatting = ChatFormatting.getByCode(part.charAt(1));
// Ensure that we only process if there's a formatting code
if (partHasFormatter && formatting != null) {
currentComponent = currentComponent.append(new TextComponent(part.substring(2)).withStyle(formatting));
} else {
// White by default!
currentComponent = currentComponent.append(new TextComponent(part).withStyle(WHITE));
}
}

return currentComponent;
}
}

/**
* Search a user-input string for a custom chat formatting syntax.
* The custom syntax follows the format of $color or $#hex.
* ie. "$red chat" will print "chat" in red. "$#0000FF sup" will print "sup" in blue.
* There is no custom formatting for italic, strikethrough, bold, etc.
*
* @param input the text from Discord
* @return a properly formatted MutableComponent to be echoed into chat.
* @author Curle
*/
private static MutableComponent processCustomFormatting(String input) {
if(!ConcordConfig.USE_CUSTOM_FORMATTING.get()) {
// Default to white if custom formatting is disabled.
return processLegacyFormatting(input);
} else {
MutableComponent currentComponent = new TextComponent("");
// Regexplanation:
// (?= \$ #? [\w \d] + )
// ^ ^ ^^ ^ ^ ^ ^ ^
// | | || | | | | |
// | | || | | | | - End group
// | | || | | | - Match at least one
// | | || | | - Match any digit
// | | || | - Match any word
// | | || - Look for any of the following
// | | |- Match 0 or 1 of the preceding
// | | - Look for a # character
// | - Look for a $ character
// - Include the result in the split strings

final String[] parts = input.split("(?=\\$#?[\\w\\d]+)");


for (String part : parts) {
// Ensure that we only process non-empty strings
if (part.isEmpty()) continue;

final boolean partHasFormatter = part.charAt(0) == '$';
final int firstSpacePosition = part.indexOf(' ');

// Short circuit for strings of only "$" to avoid a temporal paradox
if (partHasFormatter && part.length() == 1 && firstSpacePosition == -1) {
currentComponent = currentComponent.append(new TextComponent(part).withStyle(WHITE));
continue;
}

// Make sure that formatting at the end of messages, or lone formatting, is dealt with.
final String formatString = firstSpacePosition == -1 ? part.substring(1) : part.substring(1, firstSpacePosition);
// Use TextColor's built-in parsing to do the heavy lifting.
final TextColor color = TextColor.parseColor(formatString);
// Assign the TextColor into a Style instance so that we can use it with a TextComponent.
final Style formatting = Style.EMPTY.withColor(color == null ? TextColor.fromLegacyFormat(WHITE) : color);

if (partHasFormatter && color != null) {

currentComponent = currentComponent.append(
// Cut the string on either the space (if there is one) or the end of the string.
new TextComponent(part.substring(firstSpacePosition != -1 ? firstSpacePosition + 1 : part.length()))
.withStyle(formatting)
);
} else {
// White by default!
currentComponent = currentComponent.append(
new TextComponent(part).withStyle(WHITE)
);
}

}


return currentComponent;
}
}
}