Skip to content

Commit

Permalink
Fix ClassCastExceptions + finishing touches
Browse files Browse the repository at this point in the history
  • Loading branch information
mrbuilder1961 committed Mar 12, 2024
1 parent 0cc562d commit 0541b30
Show file tree
Hide file tree
Showing 5 changed files with 236 additions and 139 deletions.
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- 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
- Created a new `ChatUtils#getArg(..)` method to avoid the elusive `ClassCastException`s that kept getting thrown
- 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

Expand Down
19 changes: 19 additions & 0 deletions src/main/java/obro1961/chatpatches/ChatPatches.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,25 @@ public void onInitializeClient() {
}


/**
* Logs an error-level message telling the user to report
* the given error. The class and method of the caller is
* provided from a {@link StackWalker}.
* <br><br>
* Outputs the following message:
* <pre>
* [$class.$method] /!\ Please report this error on GitHub or Discord with the full log file attached! /!\
* (error)
* </pre>
*/
public static void logInfoReportMessage(Throwable error) {
StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
String clazz = walker.getCallerClass().getSimpleName();
String method = walker.walk(frames -> frames.skip(1).findFirst().orElseThrow().getMethodName());
method = method.isBlank() ? error.getStackTrace()[0].getMethodName() : method;
LOGGER.error("[%s.%s] /!\\ Please report this error on GitHub or Discord with the full log file attached! /!\\".formatted(clazz, method), error);
}

/**
* Creates a new Identifier using the ChatPatches mod ID.
*/
Expand Down
56 changes: 34 additions & 22 deletions src/main/java/obro1961/chatpatches/config/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -119,28 +119,38 @@ public Style makeHoverStyle(Date when) {
* @implNote {@code player} must reference a valid, existing
* 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
MutableText playername = text(player.getName());
Text configPrefix = text(chatNameFormat.split("\\$")[0]);
Text configSuffix = text(chatNameFormat.split("\\$")[1] + " ");

return Text.empty().setStyle(style)
.append(configPrefix)
.append(team.getPrefix())
.append(playername)
.append(team.getSuffix())
.append(configSuffix);
} else {
return makeObject(chatNameFormat, player.getName(), "", " ", style);
public MutableText formatPlayername(GameProfile profile) {
Style style = BLANK_STYLE.withColor(chatNameColor);
try {
PlayerEntity entity = MinecraftClient.getInstance().world.getPlayerByUuid(profile.getId());
Team team = null;

if(entity != null) {
team = entity.getScoreboard().getPlayerTeam(profile.getName());
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
MutableText playername = text(profile.getName());
String[] configFormat = chatNameFormat.split("\\$");
Text configPrefix = text(configFormat[0]);
Text configSuffix = text(configFormat[1] + " ");

return Text.empty().setStyle(style)
.append(configPrefix)
.append(team.getPrefix())
.append(playername)
.append(team.getSuffix())
.append(configSuffix);
}
} catch(Exception e) {
LOGGER.error("[Config.formatPlayername] /!\\ An error occurred while trying to format '{}'s playername /!\\", profile.getName());
ChatPatches.logInfoReportMessage(e);
}

return makeObject(chatNameFormat, profile.getName(), "", " ", style);
}

public MutableText makeDupeCounter(int dupes) {
Expand Down Expand Up @@ -236,7 +246,9 @@ public static <T> ConfigOption<T> getOption(String key) {
try {
return new ConfigOption<>( (T)config.getClass().getField(key).get(config), (T)config.getClass().getField(key).get(DEFAULTS), key );
} catch(IllegalAccessException | NoSuchFieldException e) {
LOGGER.error("[Config.getOption({})] An error occurred while trying to get an option value, please report this on GitHub:", key, e);
LOGGER.error("[Config.getOption({})] An error occurred while trying to get an option value!", key);
ChatPatches.logInfoReportMessage(e);

return new ConfigOption<>( (T)new Object(), (T)new Object(), key );
}
}
Expand Down
109 changes: 9 additions & 100 deletions src/main/java/obro1961/chatpatches/mixin/gui/ChatHudMixin.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,7 @@
import net.minecraft.client.gui.hud.ChatHudLine;
import net.minecraft.client.gui.hud.MessageIndicator;
import net.minecraft.client.util.CommandHistoryManager;
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;
import obro1961.chatpatches.accessor.ChatHudAccessor;
Expand All @@ -33,12 +29,9 @@
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

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.MESSAGE_INDEX;
import static obro1961.chatpatches.util.ChatUtils.getPart;

Expand Down Expand Up @@ -134,103 +127,18 @@ private double moveINDHoverText(double e) {
/**
* Modifies the incoming message by adding timestamps, nicer
* player names, hover events, and duplicate counters in conjunction with
* {@link #addCounter(Text, boolean)}
*
* @implNote
* <li>Doesn't modify when {@code refreshing} is true, as that signifies
* re-rendering of chat messages on the hud.</li>
* <li>This method causes all messages passed to it to be formatted in
* a new structure for clear data access. This is mostly done using
* {@link MutableText#append(Text)}, which deliberately puts message
* components at specific indices, all of which are laid out in
* {@link ChatUtils}.</li>
* {@link #addCounter(Text, boolean)}.
* <br>
* See {@link ChatUtils#modifyMessage(Text, boolean)} for detailed
* implementation specifications.
*/
@ModifyVariable(
method = "addMessage(Lnet/minecraft/text/Text;Lnet/minecraft/network/message/MessageSignatureData;ILnet/minecraft/client/gui/hud/MessageIndicator;Z)V",
at = @At("HEAD"),
argsOnly = true
)
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

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 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<Text> 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;
return addCounter(ChatUtils.modifyMessage(m, refreshing), refreshing);
}

@Inject(method = "addToMessageHistory", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/collection/ArrayListDeque;size()I"))
Expand Down Expand Up @@ -311,10 +219,11 @@ private Text addCounter(Text incoming, boolean refreshing) {
}
} catch(IndexOutOfBoundsException e) {
ChatPatches.LOGGER.error("[ChatHudMixin.addCounter] Couldn't add duplicate counter because message '{}' ({} parts) was not constructed properly.", incoming.getString(), incoming.getSiblings().size());
ChatPatches.LOGGER.error("[ChatHudMixin.addCounter] This could have also been caused by an issue with the new CompactChat dupe-condensing method.");
ChatPatches.LOGGER.error("[ChatHudMixin.addCounter] Either way, this was caused by a bug or mod incompatibility. Please report this on GitHub or on the Discord!", e);
ChatPatches.LOGGER.error("[ChatHudMixin.addCounter] This could have also been caused by an issue with the new CompactChat dupe-condensing method. Either way,");
ChatPatches.logInfoReportMessage(e);
} catch(Exception e) {
ChatPatches.LOGGER.error("[ChatHudMixin.addCounter] /!\\ Couldn't add duplicate counter because of an unexpected error. Please report this on GitHub or on the Discord! /!\\", e);
ChatPatches.LOGGER.error("[ChatHudMixin.addCounter] /!\\ Couldn't add duplicate counter because of an unexpected error! /!\\");
ChatPatches.logInfoReportMessage(e);
}

return incoming;
Expand Down
Loading

0 comments on commit 0541b30

Please sign in to comment.