From 4a2a3e97128c279cdea501d28dd9c6a20ca10138 Mon Sep 17 00:00:00 2001 From: OBro1961 Date: Sun, 25 Feb 2024 22:46:14 -0600 Subject: [PATCH] Rewrite modifyMessage method +more --- README.md | 4 +- changelog.md | 7 + gradle.properties | 4 +- .../chatpatches/config/ChatSearchSetting.java | 9 +- .../obro1961/chatpatches/config/Config.java | 17 +- .../mixin/chat/MessageHandlerMixin.java | 14 +- .../chatpatches/mixin/gui/ChatHudMixin.java | 164 +++++++++--------- .../mixin/gui/ChatScreenMixin.java | 34 ++-- .../obro1961/chatpatches/util/ChatUtils.java | 91 ++++++++-- .../assets/chatpatches/lang/en_us.json | 4 +- 10 files changed, 202 insertions(+), 146 deletions(-) diff --git a/README.md b/README.md index f64a5b3..98f3c1d 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ Once you contribute, [join the Discord server](https://discord.gg/3MqBvNEyMz) so | Ignore hide message packet | `true` | Should hide message packets that delete chat messages be ignored? | `text.chatpatches.chatHidePacket` | | Override chat width | `0` | The width of the chat box. This overrides vanilla's default and allows for a much larger width. Set to 0 to use the vanilla setting and not override it. | `text.chatpatches.chatWidth` | | Maximum chat messages | `16384` | The max amount of chat messages allowed to save. Vanilla caps it at 100, this mod can increase it up to 32,767. Keep in mind a higher max equals higher memory usage. | `text.chatpatches.chatMaxMessages` | -| Playername text | `"<$>"` | The text that replaces the playername in chat messages. Vanilla is '<$>', name only is '$'; where '$' is a placeholder for the playername. Only applies to player-sent messages. Game won't format message if this is set to '<$>'. | `text.chatpatches.chatNameFormat` | +| Playername text | `"<$>"` | The text that replaces the playername in chat messages. Vanilla is '<$>', name only is '$'; where '$' is a placeholder for the playername. Only applies to player-sent messages. | `text.chatpatches.chatNameFormat` | | Playername color | `0xffffff` | The color that's filled in where it would otherwise be blank white in the resulting formatted playername. To use this with other formatting modifiers, use '&r' in the decoration text option. | `text.chatpatches.chatNameColor` | | Shift chat | `10` | Shifts the chat interface up to not obstruct the armor bar and/or health. Default is 10, set to 0 for no shift. | `text.chatpatches.shiftChat` | | Chat drafting toggle | `false` | Should any text in the chat field persist after closing and reopening the chat? | `text.chatpatches.messageDrafting` | @@ -134,4 +134,4 @@ This mod is available under the GNU LGPLv3 license. Check out [this](https://cho ## Sponsor me! - Ko-Fi: https://ko-fi.com/obro1961 -[![15% off your first month with code OBRO15!](https://i.imgur.com/9mjs77B.png)](https://billing.kinetichosting.net/aff.php?aff=234) +[![15% off your first month with code OBRO15!](https://i.imgur.com/9mjs77B.png)](https://billing.kinetichosting.net/aff.php?aff=234) \ No newline at end of file diff --git a/changelog.md b/changelog.md index efba93b..7d15fa3 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,9 @@ - Made ChatSearchSettings save when the chat screen is closed then reopened; resets on game restart - Fixed team name colors, prefixes, and suffixes being ignored when `chatNameFormat` is customized ([#115](https://www.github.com/mrbuilder1961/ChatPatches/issues/115)) - Added a new runnable config option to reload the config from disk +- Added a minor optimization to the way messages are modified to largely simplify the process in a few scenarios [prepub impl] +- Switched the text in the search settings screen to use pre-bundled translations (ON/OFF instead of a visual switch) +- Fixed the Copy String > Copy Raw String button in the copy menu removing & formattings - **Dev notes:** - Changed the `CONFIG_PATH` and `CHATLOG_PATH` variables to use the `Path#resolve(String)` method instead of concatenating strings - Removed some (now) redundant file constants and references (in `StringTextUtils` and `Config`) @@ -13,6 +16,10 @@ - Capitalized some static final variables - Changed some stuff about how the config is initialized, read, and written to disk - Refactor StringTextUtils to TextUtils + - Restructured the powerhouse `ChatHudMixin#modifyMessage(Text, boolean)` method to be more modular with message reconstruction + - Moved the bulk of the `modifyMessage` method to ChatUtils to help development and greatly ease future troubleshooting + - Tweaked the `MessageHandlerMixin#cacheGameData` method to use built-in methods instead of rewriting the same thing + - Removed the `VANILLA_MESSAGE` matcher in `ChatUtils` because it was redundant ## Chat Patches `202.6.3` for Minecraft 1.20.2 on Fabric, Quilt - Should be compatible with Quilt again! (requires Loader 0.23.0+) diff --git a/gradle.properties b/gradle.properties index 31a257c..dcf2a3e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,8 +12,8 @@ mrId=MOqt4Z5n # Required dependencies - https://fabricmc.net/develop minecraft=1.20.2 yarn=+build.4 -loader=0.15.6 -api=0.91.5+1.20.2 +loader=0.15.7 +api=0.91.6+1.20.2 # Dependencies - modmenu: https://modrinth.com/mod/modmenu/versions?l=fabric and yacl: https://modrinth.com/mod/yacl/versions?l=fabric modmenu=8.0.1 diff --git a/src/main/java/obro1961/chatpatches/config/ChatSearchSetting.java b/src/main/java/obro1961/chatpatches/config/ChatSearchSetting.java index 665dd0a..acf48ab 100644 --- a/src/main/java/obro1961/chatpatches/config/ChatSearchSetting.java +++ b/src/main/java/obro1961/chatpatches/config/ChatSearchSetting.java @@ -5,15 +5,12 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.tooltip.Tooltip; import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.screen.ScreenTexts; import net.minecraft.text.Text; import obro1961.chatpatches.mixin.gui.ChatScreenMixin; -import java.util.function.Function; - /** Represents a search setting pertaining to the {@link ChatScreenMixin} screen. */ public class ChatSearchSetting { - public static final Function TOGGLE_FORMATTER = on -> on ? Text.of(": §7[§2=§aO§7]") : Text.of(": §7[§cX§4=§7]"); - public static ChatSearchSetting caseSensitive = new ChatSearchSetting("caseSensitive", true), modifiers = new ChatSearchSetting("modifiers", false), regex = new ChatSearchSetting("regex", false); @@ -30,11 +27,11 @@ private ChatSearchSetting(String key, boolean on) { } public void update(int yWithOffset, ButtonWidget.PressAction onPress) { - Text text = name.copy().append( TOGGLE_FORMATTER.apply(on) ); + Text text = ScreenTexts.composeToggleText(name, on); ButtonWidget.PressAction UPDATE_BUTTON = button -> { on = !on; - button.setMessage( name.copy().append(TOGGLE_FORMATTER.apply(on)) ); + button.setMessage(ScreenTexts.composeToggleText(name, on)); onPress.onPress(button); // flags ChatScreenMixin to update the search text color }; diff --git a/src/main/java/obro1961/chatpatches/config/Config.java b/src/main/java/obro1961/chatpatches/config/Config.java index 7e40ec7..577278d 100644 --- a/src/main/java/obro1961/chatpatches/config/Config.java +++ b/src/main/java/obro1961/chatpatches/config/Config.java @@ -1,5 +1,7 @@ package obro1961.chatpatches.config; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import com.google.gson.JsonIOException; import com.google.gson.JsonSyntaxException; import com.mojang.authlib.GameProfile; @@ -10,6 +12,7 @@ import net.minecraft.scoreboard.Team; import net.minecraft.text.*; import obro1961.chatpatches.ChatPatches; +import obro1961.chatpatches.util.ChatUtils; import java.io.FileWriter; import java.io.IOException; @@ -31,9 +34,9 @@ public class Config { public static final Path PATH = FabricLoader.getInstance().getConfigDir().resolve("chatpatches.json"); + public static final Config DEFAULTS = new Config(); - private static final com.google.gson.Gson GSON = new com.google.gson.GsonBuilder().setPrettyPrinting().create(); - private static final Config DEFAULTS = new Config(); + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); // categories: time, hover, counter, counter.compact, boundary, chatlog, chat.hud, chat.screen, copy public boolean time = true; public String timeDate = "HH:mm:ss"; public String timeFormat = "[$]"; public int timeColor = 0xff55ff; @@ -117,11 +120,11 @@ public Style makeHoverStyle(Date when) { * player entity and have both a valid name and UUID. */ public MutableText formatPlayername(GameProfile player) { + // todo add some error handling here PlayerEntity entity = MinecraftClient.getInstance().world.getPlayerByUuid(player.getId()); Team team = entity.getScoreboard().getPlayerTeam(player.getName()); Style style = entity.getDisplayName().getStyle().withColor( entity.getTeamColorValue() != 0xffffff ? entity.getTeamColorValue() : chatNameColor ); - if(team != null) { // note: doesn't set the style on every append, as it's already set in the parent text. might cause issues? // if the player is on a team, add the prefix and suffixes from the config AND team (if they exist) to the formatted name @@ -146,7 +149,8 @@ public MutableText makeDupeCounter(int dupes) { public Text makeBoundaryLine(String levelName) { // constructs w empty texts to not throw errors when comparing for the dupe counter - return Text.empty().append( makeObject(boundaryFormat, levelName, "", "", BLANK_STYLE.withColor(boundaryColor)) ).append(Text.empty()); + MutableText boundary = makeObject(boundaryFormat, levelName, "", "", BLANK_STYLE.withColor(boundaryColor)); + return ChatUtils.buildMessage(null, null, boundary, null); } @@ -157,11 +161,8 @@ public static void read() { LOGGER.info("[Config.read] No config file found; using default values."); } else { try { - // sets each config option to the loaded value String rawData = Files.readString(PATH); config = GSON.fromJson(rawData, config.getClass()); - //Config loaded = GSON.fromJson(rawData, config.getClass()); - //getOptions().forEach(opt -> config.getOption(opt.key).set( loaded.getOption(opt.key).val )); LOGGER.info("[Config.read] Loaded config info from '{}'!", PATH); } catch(JsonIOException | JsonSyntaxException e) { writeCopy(); @@ -190,7 +191,7 @@ public static void write() { * log any changes nor does it write to disk. */ public static void reset() { - getOptions().forEach(opt -> config.getOption(opt.key).set(opt.def)); + getOptions().forEach(opt -> getOption(opt.key).set(opt.def)); } /** diff --git a/src/main/java/obro1961/chatpatches/mixin/chat/MessageHandlerMixin.java b/src/main/java/obro1961/chatpatches/mixin/chat/MessageHandlerMixin.java index a14d8d2..cf50dc4 100644 --- a/src/main/java/obro1961/chatpatches/mixin/chat/MessageHandlerMixin.java +++ b/src/main/java/obro1961/chatpatches/mixin/chat/MessageHandlerMixin.java @@ -3,7 +3,6 @@ import com.mojang.authlib.GameProfile; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; -import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.message.MessageHandler; import net.minecraft.network.message.MessageType; import net.minecraft.network.message.SignedMessage; @@ -14,7 +13,6 @@ import obro1961.chatpatches.mixin.gui.ChatHudMixin; import obro1961.chatpatches.util.ChatUtils; import org.apache.commons.lang3.StringUtils; -import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; @@ -34,8 +32,7 @@ @Environment(EnvType.CLIENT) @Mixin(MessageHandler.class) public abstract class MessageHandlerMixin { - @Shadow @Final private MinecraftClient client; - + @Shadow protected abstract UUID extractSender(Text text); /** * Caches the metadata of the last *player* message received by the client. @@ -57,12 +54,11 @@ private void cacheChatData(SignedMessage message, GameProfile sender, MessageTyp */ @Inject(method = "onGameMessage", at = @At("HEAD")) private void cacheGameData(Text message, boolean overlay, CallbackInfo ci) { - String string = TextVisitFactory.removeFormattingCodes(message); - String name = ChatUtils.VANILLA_MESSAGE.matcher(string).matches() ? StringUtils.substringBetween(string, "<", ">") : null; - UUID uuid = name == null ? Util.NIL_UUID : client.getSocialInteractionsManager().getUuid(name); + String name = StringUtils.substringBetween(TextVisitFactory.removeFormattingCodes(message), "<", ">"); + UUID id = extractSender(message); - ChatPatches.msgData = !uuid.equals(Util.NIL_UUID) - ? new ChatUtils.MessageData(new GameProfile(uuid, name), new Date(), true) + ChatPatches.msgData = !id.equals(Util.NIL_UUID) + ? new ChatUtils.MessageData(new GameProfile(id, name), new Date(), true) : ChatUtils.NIL_MSG_DATA; } } \ No newline at end of file diff --git a/src/main/java/obro1961/chatpatches/mixin/gui/ChatHudMixin.java b/src/main/java/obro1961/chatpatches/mixin/gui/ChatHudMixin.java index c3b25e4..6cb13c2 100644 --- a/src/main/java/obro1961/chatpatches/mixin/gui/ChatHudMixin.java +++ b/src/main/java/obro1961/chatpatches/mixin/gui/ChatHudMixin.java @@ -2,7 +2,7 @@ import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.llamalad7.mixinextras.injector.ModifyReturnValue; -import com.llamalad7.mixinextras.injector.WrapWithCondition; +import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; import com.llamalad7.mixinextras.sugar.Local; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; @@ -11,7 +11,10 @@ import net.minecraft.client.gui.hud.ChatHudLine; import net.minecraft.client.gui.hud.MessageIndicator; import net.minecraft.client.util.CommandHistoryManager; -import net.minecraft.text.*; +import net.minecraft.text.MutableText; +import net.minecraft.text.Style; +import net.minecraft.text.Text; +import net.minecraft.text.TranslatableTextContent; import net.minecraft.util.Util; import net.minecraft.util.math.MathHelper; import obro1961.chatpatches.ChatPatches; @@ -30,13 +33,14 @@ import org.spongepowered.asm.mixin.injection.ModifyVariable; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import java.util.Arrays; +import java.util.ArrayList; import java.util.Date; import java.util.List; import static obro1961.chatpatches.ChatPatches.config; import static obro1961.chatpatches.ChatPatches.msgData; -import static obro1961.chatpatches.util.ChatUtils.MSG_INDEX; +import static obro1961.chatpatches.util.ChatUtils.MESSAGE_INDEX; +import static obro1961.chatpatches.util.ChatUtils.getPart; /** * The main entrypoint mixin for most chat modifications. @@ -150,88 +154,80 @@ private Text modifyMessage(Text m, @Local(argsOnly = true) boolean refreshing) { if( refreshing || Flags.LOADING_CHATLOG.isRaised() ) return addCounter(m, refreshing); // cancels modifications when loading the chatlog or regenerating visibles - final Style style = m.getStyle(); + Style style = m.getStyle(); boolean lastEmpty = msgData.equals(ChatUtils.NIL_MSG_DATA); boolean boundary = Flags.BOUNDARY_LINE.isRaised() && config.boundary && !config.vanillaClearing; Date now = lastEmpty ? new Date() : msgData.timestamp(); - String nowTime = String.valueOf( now.getTime() ); // for copy menu and storing timestamp data! only affects the timestamp - - -//todo: next commit reorganize this into part of the overhaul restructuring (aka modular message construction) - Text modified = - Text.empty().setStyle(style) - .append( - config.time && !boundary - ? config.makeTimestamp(now).setStyle( config.makeHoverStyle(now).withInsertion(nowTime) ) - : Text.empty().setStyle( Style.EMPTY.withInsertion(nowTime) ) - ) - .append( - !lastEmpty && !boundary && Config.getOption("chatNameFormat").changed() && msgData.vanilla() - ? Text.empty().setStyle(style) - .append( config.formatPlayername( msgData.sender() ) ) // add formatted name - .append( // add first part of message (depending on the Style and how it was constructed) - Util.make(() -> { - if(m.getContent() instanceof TranslatableTextContent ttc) { // most vanilla chat messages - - MutableText text = Text.empty().setStyle(style); - List messages = Arrays.stream( ttc.getArgs() ).map( arg -> (Text)arg ).toList(); - - // i think the arg at i=0 is the player name in vanilla messages - for(int i = 1; i < messages.size(); ++i) - text.append( messages.get(i) ); - - return text; - } else if(m.getContent() instanceof LiteralTextContent ltc) { // default-style message with name - // assuming the vanilla format ' message' - String[] splitMessage = ltc.string().split(">"); // for now we will always check for a singular bracket, just in case the space is missing - - if(splitMessage.length > 1) - // removes any preceding whitespace - return Text.literal( splitMessage[1].replaceAll("^\\s+", "") ).setStyle(style); - else - //return Text.empty().setStyle(style); // use this? idk - return m.copyContentOnly().setStyle(style); - } else { - // text w/o siblings - return m.copyContentOnly().setStyle(style); - } - }) - ) - .append( // add any siblings (Texts with different styles) - Util.make(() -> { - MutableText msg = Text.empty().setStyle(style); - List siblings = m.getSiblings(); - int i = -1; // index of the first '>' in the playername - - // if the message uses the vanilla style but the main component doesn't have the full playername, then only add (the actual message) after it, (removes duped names) - if(m.getContent() instanceof LiteralTextContent ltc && !ltc.string().contains(">")) - i = siblings.stream().filter(sib -> sib.getString().contains(">")).mapToInt(siblings::indexOf).findFirst().orElse(i); - - // if the vanilla-style message is formatted weird, then only add the text *after* the first '>' (end of playername) - if(i > -1) { - Text rightTri = siblings.get(i); - String rightTriStr = rightTri.getString(); - String restOfStr = rightTriStr.substring( rightTriStr.indexOf(">") + 1 ).replaceAll("^\\s+", ""); - // updates the sibling text and decrements the index, so it doesn't get skipped - if(!restOfStr.isEmpty()) { - siblings.set(i, Text.literal(restOfStr).setStyle(rightTri.getStyle())); - --i; - } - } - - // if there was a split playername, add everything after the '>' (end of playername) - // (if there wasn't a split playername, add everything [-1 + 1 = 0]) - // (if there was, only add after that part [i + 1 = after name component]) - for(int j = i + 1; j < siblings.size(); ++j) - msg.append( siblings.get(j) ); - - return msg; - }) - ) - : m - ); - - modified = addCounter(modified, false); + String nowStr = String.valueOf( now.getTime() ); // for copy menu and storing timestamp data! only affects the timestamp + +//ChatPatches.LOGGER.warn("received {} message: '{}'", m.getContent().getClass().getSimpleName(), m.getString()); + + MutableText timestamp = (config.time && !boundary) ? config.makeTimestamp(now).setStyle( config.makeHoverStyle(now) ) : Text.empty().styled(s -> s.withInsertion(nowStr)); + MutableText content = Text.empty().setStyle(style); + + // reconstruct the player message if it's in the vanilla format and it should be reformatted + if(!lastEmpty && !boundary && msgData.vanilla()) { + // if the message is translatable, then we know exactly where everything is + if(m.getContent() instanceof TranslatableTextContent ttc && ttc.getKey().matches("chat.type.(text|team.(text|sent))")) { + String key = ttc.getKey(); + + // adds the team name for team messages + if(key.startsWith("chat.type.team.")) { + MutableText teamPart = Text.empty(); + // adds the preceding arrow for sent team messages + if(key.endsWith("sent")) + teamPart.append(Text.literal("-> ").setStyle(style)); + + // adds the team name for team messages + teamPart.append( ((MutableText)ttc.getArg(0)).append(" ") ); + + content.append(teamPart); + } else { + content.append(""); // if there isn't a team message, add an empty string to keep the index constant + } + + // adds the formatted playername and content for all message types + content.append(config.formatPlayername(msgData.sender())); // sender data is already known + content.append((Text) ttc.getArg(ttc.getArgs().length - 1)); // always at the end + } else { // reconstructs the message if it matches the vanilla format '<%s> %s' but isn't translatable + // collect all message parts into one list, including the root TextContent + // (assuming this accounts for all parts, TextContents, and siblings) + List parts = Util.make(new ArrayList<>(m.getSiblings().size() + 1), a -> { + if(!m.equals(Text.EMPTY)) + a.add( m.copyContentOnly().setStyle(style) ); + + a.addAll( m.getSiblings() ); + }); + + MutableText realContent = Text.empty(); + // find the index of the end of a '<%s> %s' message + Text firstPart = parts.stream().filter(p -> p.getString().contains(">")).findFirst() + .orElseThrow(() -> new IllegalStateException("No closing angle bracket found in vanilla message '" + m.getString() + "' !")); + String afterEndBracket = firstPart.getString().split(">")[1]; // just get the part after the closing bracket, we know the start + + // adds the part after the closing bracket but before any remaining siblings, if it exists + if(!afterEndBracket.isEmpty()) + realContent.append( Text.literal(afterEndBracket).setStyle(firstPart.getStyle()) ); + + // we know everything remaining is message content parts, so add everything + for(int i = parts.indexOf(firstPart) + 1; i < parts.size(); i++) + realContent.append(parts.get(i)); + + content.append(config.formatPlayername(msgData.sender())); // sender data is already known + content.append(realContent); // adds the reconstructed message content + } +//ChatPatches.LOGGER.warn("DID!!! reformat, content: '{}' aka '{}'+{}", content.getString(), content.copyContentOnly().getString(), content.getSiblings());//delete:- + } else { + // don't reformat if it isn't vanilla or needed + content = m.copy(); +//ChatPatches.LOGGER.warn("didn't reformat, content: '{}' aka '{}'+{}", content.getString(), content.copyContentOnly().getString(), content.getSiblings());//delete:- + } + + // assembles constructed message and adds a duplicate counter according to the #addCounter method + Text modified = addCounter( ChatUtils.buildMessage(style, timestamp, content, null), false ); +/*ChatPatches.LOGGER.info("------ parts of message ------\n\ttimestamp: '{}'\n\tmessage: ('{}')\n\t\tteam: '{}'\n\t\tsender: '{}'\n\t\tcontent: '{}'\n\tdupe: '{}'\n------", +ChatUtils.getPart(modified, 0), ChatUtils.getPart(modified, 1), ChatUtils.getMsgPart(modified, 0), ChatUtils.getMsgPart(modified, 1), ChatUtils.getMsgPart(modified, 2), ChatUtils.getPart(modified, 2) +);*/ //delete:- ChatLog.addMessage(modified); msgData = ChatUtils.NIL_MSG_DATA; // fixes messages that get around MessageHandlerMixin's data caching, usually thru ChatHud#addMessage (ex. open-to-lan message) return modified; @@ -305,7 +301,7 @@ private Text addCounter(Text incoming, boolean refreshing) { // exclude the first message, already checked above messages.subList(1, attemptDistance) .stream() - .filter( hudLine -> hudLine.content().getSiblings().get(MSG_INDEX).getString().equalsIgnoreCase( incoming.getSiblings().get(MSG_INDEX).getString() ) ) + .filter( hudLine -> getPart(hudLine.content(), MESSAGE_INDEX).getString().equalsIgnoreCase( getPart(incoming, MESSAGE_INDEX).getString() ) ) .findFirst() .ifPresent( hudLine -> ChatUtils.getCondensedMessage(incoming, messages.indexOf(hudLine)) ); } diff --git a/src/main/java/obro1961/chatpatches/mixin/gui/ChatScreenMixin.java b/src/main/java/obro1961/chatpatches/mixin/gui/ChatScreenMixin.java index 5ebc450..d47b30e 100644 --- a/src/main/java/obro1961/chatpatches/mixin/gui/ChatScreenMixin.java +++ b/src/main/java/obro1961/chatpatches/mixin/gui/ChatScreenMixin.java @@ -61,6 +61,7 @@ import static obro1961.chatpatches.config.ChatSearchSetting.*; import static obro1961.chatpatches.gui.MenuButtonWidget.anchor; import static obro1961.chatpatches.gui.MenuButtonWidget.of; +import static obro1961.chatpatches.util.ChatUtils.*; import static obro1961.chatpatches.util.RenderUtils.NIL_HUD_LINE; /** @@ -174,29 +175,24 @@ protected void initSearchStuff(CallbackInfo ci) { // only render all this menu stuff if it hasn't already been initialized if(!showCopyMenu) { // hover menu buttons, column two - hoverButtons.put(COPY_RAW_STRING, of(1, COPY_RAW_STRING, () -> Formatting.strip( selectedLine.content().getString() ).replaceAll(TextUtils.AMPERSAND_REGEX, ""))); + hoverButtons.put(COPY_RAW_STRING, of(1, COPY_RAW_STRING, () -> Formatting.strip( selectedLine.content().getString() ))); hoverButtons.put(COPY_FORMATTED_STRING, of(1, COPY_FORMATTED_STRING, () -> TextUtils.reorder( selectedLine.content().asOrderedText(), true ))); hoverButtons.put(COPY_JSON_STRING, of(1, COPY_JSON_STRING, () -> Text.Serializer.toJson(selectedLine.content()))); hoverButtons.put(COPY_LINK_N.apply(0), of(1, COPY_LINK_N.apply(0), () -> "")); - hoverButtons.put(COPY_TIMESTAMP_TEXT, of(1, COPY_TIMESTAMP_TEXT, () -> selectedLine.content().getSiblings().get(ChatUtils.TIMESTAMP_INDEX).getString())); + hoverButtons.put(COPY_TIMESTAMP_TEXT, of(1, COPY_TIMESTAMP_TEXT, () -> getPart(selectedLine.content(), TIMESTAMP_INDEX).getString())); hoverButtons.put(COPY_TIMESTAMP_HOVER_TEXT, of(1, COPY_TIMESTAMP_HOVER_TEXT, () -> { - HoverEvent hoverEvent = selectedLine.content().getSiblings().get(ChatUtils.TIMESTAMP_INDEX).getStyle().getHoverEvent(); - if(hoverEvent != null) - return hoverEvent.getValue(SHOW_TEXT).getString(); - else - return ""; + HoverEvent hoverEvent = getPart(selectedLine.content(), TIMESTAMP_INDEX).getStyle().getHoverEvent(); + return hoverEvent != null ? hoverEvent.getValue(SHOW_TEXT).getString() : ""; })); hoverButtons.put(COPY_NAME, of(1, COPY_NAME, () -> { - Text message = selectedLine.content().getSiblings().get(ChatUtils.MSG_INDEX); - Text text = message.getSiblings().size() > ChatUtils.MSG_NAME_INDEX ? message.getSiblings().get(ChatUtils.MSG_NAME_INDEX) : Text.empty(); - HoverEvent.EntityContent player = text.getStyle().getHoverEvent() != null ? text.getStyle().getHoverEvent().getValue(SHOW_ENTITY) : null; - return player != null ? player.name.getString() : text.getString(); + HoverEvent senderHoverEvent = getMsgPart(selectedLine.content(), MSG_SENDER_INDEX).getStyle().getHoverEvent(); + HoverEvent.EntityContent player = senderHoverEvent != null ? senderHoverEvent.getValue(SHOW_ENTITY) : null; + return player != null ? player.name.getString() : getMsgPart(selectedLine.content(), MSG_SENDER_INDEX).getString(); })); hoverButtons.put(COPY_UUID, of(1, COPY_UUID, () -> { - Text message = selectedLine.content().getSiblings().get(ChatUtils.MSG_INDEX); - Text text = message.getSiblings().size() > ChatUtils.MSG_NAME_INDEX ? message.getSiblings().get(ChatUtils.MSG_NAME_INDEX) : Text.empty(); - HoverEvent.EntityContent player = text.getStyle().getHoverEvent() != null ? text.getStyle().getHoverEvent().getValue(SHOW_ENTITY) : null; - return player != null ? player.uuid.toString() : text.getString(); + HoverEvent senderHoverEvent = getMsgPart(selectedLine.content(), MSG_SENDER_INDEX).getStyle().getHoverEvent(); + HoverEvent.EntityContent player = senderHoverEvent != null ? senderHoverEvent.getValue(SHOW_ENTITY) : null; + return player != null ? player.uuid.toString() : getMsgPart(selectedLine.content(), MSG_SENDER_INDEX).getString(); })); // main menu buttons, column one @@ -204,8 +200,7 @@ protected void initSearchStuff(CallbackInfo ci) { mainButtons.put(COPY_MENU_LINKS, of(0, COPY_MENU_LINKS, hoverButtons.get(COPY_LINK_N.apply(0)))); mainButtons.put(COPY_MENU_TIMESTAMP, of(0, COPY_MENU_TIMESTAMP, hoverButtons.get(COPY_TIMESTAMP_TEXT), hoverButtons.get(COPY_TIMESTAMP_HOVER_TEXT))); mainButtons.put(COPY_UNIX, of(0, COPY_UNIX, () -> { - List siblings = selectedLine.content().getSiblings(); - String time = siblings.size() > ChatUtils.TIMESTAMP_INDEX ? siblings.get(ChatUtils.TIMESTAMP_INDEX).getStyle().getInsertion() : null; + String time = getPart(selectedLine.content(), TIMESTAMP_INDEX).getStyle().getInsertion(); return time != null && !time.isEmpty() ? time : "?"; })); mainButtons.put(COPY_MENU_SENDER, of(0, COPY_MENU_SENDER, hoverButtons.get(COPY_NAME), hoverButtons.get(COPY_UUID))); @@ -614,13 +609,12 @@ so i switched it to a startsWith() bc the first one never has extra spaces. /!\ } // add timestamp button - if( !selectedLine.content().getSiblings().get(ChatUtils.TIMESTAMP_INDEX).getString().isBlank() ) + if( !getPart(selectedLine.content(), TIMESTAMP_INDEX).getString().isBlank() ) mainButtons.get(COPY_MENU_TIMESTAMP).readyToRender(true); mainButtons.get(COPY_UNIX).readyToRender(true); // add player data and reply buttons - Text originalMessage = selectedLine.content().getSiblings().size() > ChatUtils.MSG_INDEX ? selectedLine.content().getSiblings().get(ChatUtils.MSG_INDEX) : Text.empty(); - Style style = originalMessage.getSiblings().size() > 0 ? originalMessage.getSiblings().get(ChatUtils.MSG_NAME_INDEX).getStyle() : Style.EMPTY; + Style style = getMsgPart(selectedLine.content(), MSG_SENDER_INDEX).getStyle(); if( !style.equals(Style.EMPTY) && style.getHoverEvent() != null && style.getHoverEvent().getAction() == HoverEvent.Action.SHOW_ENTITY ) { PlayerListEntry player = client.getNetworkHandler().getPlayerListEntry( UUID.fromString(hoverButtons.get(COPY_UUID).copySupplier.get()) ); // gets the skin texture from the player, then the profile, and finally the NIL profile if all else fails diff --git a/src/main/java/obro1961/chatpatches/util/ChatUtils.java b/src/main/java/obro1961/chatpatches/util/ChatUtils.java index 7e51a13..3d1865c 100644 --- a/src/main/java/obro1961/chatpatches/util/ChatUtils.java +++ b/src/main/java/obro1961/chatpatches/util/ChatUtils.java @@ -5,7 +5,9 @@ import net.minecraft.client.gui.hud.ChatHud; import net.minecraft.client.gui.hud.ChatHudLine; import net.minecraft.client.util.ChatMessages; +import net.minecraft.text.MutableText; import net.minecraft.text.OrderedText; +import net.minecraft.text.Style; import net.minecraft.text.Text; import net.minecraft.util.math.MathHelper; import obro1961.chatpatches.accessor.ChatHudAccessor; @@ -14,8 +16,8 @@ import java.time.Instant; import java.util.Date; import java.util.List; +import java.util.Objects; import java.util.UUID; -import java.util.regex.Pattern; import static obro1961.chatpatches.ChatPatches.config; import static obro1961.chatpatches.util.TextUtils.copyWithoutContent; @@ -27,14 +29,76 @@ public class ChatUtils { public static final UUID NIL_UUID = new UUID(0, 0); public static final MessageData NIL_MSG_DATA = new MessageData(new GameProfile(ChatUtils.NIL_UUID, ""), Date.from(Instant.EPOCH), false); - public static final int TIMESTAMP_INDEX = 0, MSG_INDEX = 1, DUPE_COUNTER_INDEX = 2; // indices of all main (modified message) components - public static final int MSG_NAME_INDEX = 0, MSG_MSG_INDEX = 1, MSG_FORMATTED_TEXT_INDEX = 2; // indices of all MSG_INDEX components + public static final int TIMESTAMP_INDEX = 0, MESSAGE_INDEX = 1, DUPE_INDEX = 2; // indices of all main (modified message) components + public static final int MSG_TEAM_INDEX = 0, MSG_SENDER_INDEX = 1, MSG_CONTENT_INDEX = 2; // indices of all MESSAGE_INDEX components + + /** + * Returns the message component at the given index; + * returns an empty Text if it doesn't exist. This + * prevents {@code IndexOutOfBoundsException} and + * {@code NullPointerException} errors. + * + * @apiNote Intended to be used with the MAIN + * indices specified in this class. + */ + public static Text getPart(Text message, int index) { + return message.getSiblings().size() > index ? message.getSiblings().get(index) : Text.empty(); + } + /** - * Matches a vanilla message, with captures for the playername and message. - * Considers a message invalid if {@link net.minecraft.SharedConstants#isValidChar(char)} - * would return false. + * Returns the message component at the given index of the + * given message; returns an empty Text if it doesn't exist. + * + * @apiNote Intended to be used with the {@code MSG} + * indices specified in this class. + */ + public static Text getMsgPart(Text message, int index) { + return getPart(getPart(message, MESSAGE_INDEX), index); + } + + /** + * Builds a chat message from the given components. + * If anything is {@code null}, it is replaced with + * an empty Text, aside from {@code rootStyle} which + * is replaced with {@link Style#EMPTY}. + * + * @param rootStyle The style of the root Text component + * @param first The first component of the message, + * either the timestamp or team name + * @param second The second component of the message, + * either the message or the sender + * @param third The third component of the message, + * either the dupe counter or the message + * content */ - public static final Pattern VANILLA_MESSAGE = Pattern.compile("^<(?[a-zA-Z0-9_]{3,16})> (?[^\\u0000-\\u001f\\u007f§]+)$"); + public static MutableText buildMessage(Style rootStyle, Text first, Text second, Text third) { + MutableText root = Text.empty(); + + if(rootStyle != null && !rootStyle.equals(Style.EMPTY)) + root.setStyle(rootStyle); + + first = Objects.requireNonNullElse(first, Text.empty()); + second = Objects.requireNonNullElse(second, Text.empty()); + third = Objects.requireNonNullElse(third, Text.empty()); + + return root.append(first).append(second).append(third); + } + + /** + * todo doc AFTER todo moving impl here + */ + public static Text modifyMessage(Text message, boolean vanilla) { + // early if-return checks + + // assign variables + + // declare message parts + // if TranslatableTextContent and known keys, store pre-formatted parts instantly + // else do typical formatting stuff (except optimize it to make it actually work and not ugly) + + // final cleanup and logging + return message; + } /** * Tries to condense the {@code index} message into the incoming message @@ -78,26 +142,27 @@ public static Text getCondensedMessage(Text incoming, int index) { // IF the comparing and incoming message bodies are case-insensitively equal, // AND (if we need to check the style) if the messages' metadata are equal, continue - Text incMsg = incomingParts.get(MSG_INDEX), compMsg = comparingParts.get(MSG_INDEX); + Text incMsg = incomingParts.get(MESSAGE_INDEX), compMsg = comparingParts.get(MESSAGE_INDEX); boolean equalIgnoreCase = incMsg.getString().equalsIgnoreCase( compMsg.getString() ); if( equalIgnoreCase && (!config.counterCheckStyle || copyWithoutContent(incMsg).equals(copyWithoutContent(compMsg))) ) { // info: according to some limited testing, incoming messages (incomingParts) will never contain a dupe counter, so it's been omitted from this check int dupes = ( - comparingParts.size() > DUPE_COUNTER_INDEX - ? Integer.parseInt( comparingParts.get(DUPE_COUNTER_INDEX).getString() + comparingParts.size() > DUPE_INDEX + ? Integer.parseInt( comparingParts.get(DUPE_INDEX).getString() .replaceAll("(§[0-9a-fk-or])+", "") .replaceAll("\\D", "") + .replaceAll("^$", "1") // if the string is empty, replace it with 1 (to prevent NumberFormatException) ) : 1 ) + 1; // i think when old messages are re-added into the chat, it keeps the dupe counter so we have to use set() instead of add() sometimes - if(incomingParts.size() > DUPE_COUNTER_INDEX) - incomingParts.set(DUPE_COUNTER_INDEX, config.makeDupeCounter(dupes)); + if(incomingParts.size() > DUPE_INDEX) + incomingParts.set(DUPE_INDEX, config.makeDupeCounter(dupes)); else - incomingParts.add(DUPE_COUNTER_INDEX, config.makeDupeCounter(dupes)); + incomingParts.add(DUPE_INDEX, config.makeDupeCounter(dupes)); messages.remove(index); diff --git a/src/main/resources/assets/chatpatches/lang/en_us.json b/src/main/resources/assets/chatpatches/lang/en_us.json index 114a7d1..726a965 100644 --- a/src/main/resources/assets/chatpatches/lang/en_us.json +++ b/src/main/resources/assets/chatpatches/lang/en_us.json @@ -97,8 +97,8 @@ "text.chatpatches.desc.chatHidePacket": "Should hide message packets that delete chat messages be ignored?", "text.chatpatches.desc.chatWidth": "The width of the chat box. This overrides vanilla's default and allows for a much larger width. Set to 0 to use the vanilla setting and not override it.", "text.chatpatches.desc.chatMaxMessages": "The max amount of chat messages allowed to save. Vanilla caps it at 100, this mod can increase it up to 32,767. Keep in mind a higher max equals higher memory usage.", - "text.chatpatches.desc.chatNameFormat": "The text that replaces the playername in chat messages. Vanilla is '<$>', name only is '$'; where '$' is a placeholder for the playername. Only applies to player-sent messages. Game won't format message if this is set to '<$>'. ", - "text.chatpatches.desc.chatNameColor": "The color that's filled in where it would otherwise be blank white in the resulting formatted playername. §6Currently only applies when the §lPlayername text §r§6option is configured. §fTo use this with other formatting modifiers, use '&r' in the decoration text option.", + "text.chatpatches.desc.chatNameFormat": "The text that replaces the playername in chat messages. Vanilla is '<$>', name only is '$'; where '$' is a placeholder for the playername. Only applies to player-sent messages.", + "text.chatpatches.desc.chatNameColor": "The color that's filled in where it would otherwise be blank white in the resulting formatted playername. To use this with other formatting modifiers, use '&r' in the decoration text option.", "text.chatpatches.desc.shiftChat": "Shifts the chat interface up to not obstruct the armor bar and/or health. Default is 10, set to 0 for no shift.", "text.chatpatches.desc.messageDrafting": "Should any text in the chat field persist after closing and reopening the chat?", "text.chatpatches.desc.onlyInvasiveDrafting": "Should the text in the chat field only persist when chat is closed unexpectedly? For example when chat is closed by the server opening a GUI, the player is moved to another dimension, or the player dies.",