diff options
author | Linnea Gräf <nea@nea.moe> | 2024-05-03 17:25:52 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-05-03 17:25:52 +0200 |
commit | 323cd55125f1485c71c49568aa919e5311c6263f (patch) | |
tree | c4c246921fad48623fb81b769ee78f5fa1873993 /src/main/java | |
parent | 5b2a2d067e68d500bd1c19088dae9e624eeae51f (diff) | |
download | skyhanni-323cd55125f1485c71c49568aa919e5311c6263f.tar.gz skyhanni-323cd55125f1485c71c49568aa919e5311c6263f.tar.bz2 skyhanni-323cd55125f1485c71c49568aa919e5311c6263f.zip |
Add hover/click events back to custom chat messages (#1516)
Co-authored-by: hannibal2 <24389977+hannibal00212@users.noreply.github.com>
Diffstat (limited to 'src/main/java')
16 files changed, 493 insertions, 230 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/PlayerChatManager.kt b/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/PlayerChatManager.kt index 96fad9711..f60247c0d 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/PlayerChatManager.kt +++ b/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/PlayerChatManager.kt @@ -2,6 +2,7 @@ package at.hannibal2.skyhanni.data.hypixel.chat import at.hannibal2.skyhanni.data.IslandType import at.hannibal2.skyhanni.data.hypixel.chat.event.AbstractChatEvent +import at.hannibal2.skyhanni.data.hypixel.chat.event.CoopChatEvent import at.hannibal2.skyhanni.data.hypixel.chat.event.GuildChatEvent import at.hannibal2.skyhanni.data.hypixel.chat.event.NpcChatEvent import at.hannibal2.skyhanni.data.hypixel.chat.event.PartyChatEvent @@ -10,180 +11,188 @@ import at.hannibal2.skyhanni.data.hypixel.chat.event.PlayerShowItemChatEvent import at.hannibal2.skyhanni.data.hypixel.chat.event.PrivateMessageChatEvent import at.hannibal2.skyhanni.data.hypixel.chat.event.SystemMessageEvent import at.hannibal2.skyhanni.events.LorenzChatEvent -import at.hannibal2.skyhanni.utils.LorenzUtils -import at.hannibal2.skyhanni.utils.LorenzUtils.groupOrNull +import at.hannibal2.skyhanni.utils.ComponentMatcher +import at.hannibal2.skyhanni.utils.ComponentMatcherUtils.intoSpan +import at.hannibal2.skyhanni.utils.ComponentMatcherUtils.matchStyledMatcher +import at.hannibal2.skyhanni.utils.ComponentSpan import at.hannibal2.skyhanni.utils.LorenzUtils.isInIsland -import at.hannibal2.skyhanni.utils.NumberUtil.formatInt -import at.hannibal2.skyhanni.utils.StringUtils.matchMatcher import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern import net.minecraft.util.IChatComponent import net.minecraftforge.fml.common.eventhandler.SubscribeEvent -import java.util.regex.Matcher /** - * Reading normal chat events, and splitting them up into many different player chat events, with all avaliable extra information + * Reading normal chat events, and splitting them up into many different player chat events, with all available extra information */ class PlayerChatManager { private val patternGroup = RepoPattern.group("data.chat.player") /** - * REGEX-TEST: §8[§r§6428§r§8] §r§b[MVP§5+§b] Alea1337§f: t - * REGEX-TEST: §8[§r§e102§r§8] §r§7☠ §r§b[MVP§d+§b] cobyjoey§f§r§f: first person to type "halo0011 is my favorite player on the game I love halo0011!!!" - * REGEX-TEST: §8[§r§5396§r§8] §r§7☢ §r§b[MVP§c+§b] hannibal2§f: hello - * REGEX-TEST: §8[§r§e97§r§8] §r§7☃ §r§7Tambaloo§7§r§7: i did capital i - * REGEX-TEST: §8[§r§f76§r§8] §r§7❂ §r§a[VIP] Asymmetrically§f§r§f: i need to put on my necron - * REGEX-TEST: §8[§r§c446§r§8] §r§b§l⚛ §r§6[MVP§1++§6] XueRuu§f§r§f: TROPHY FISH! You caught a Lavahorse DIAMOND. - * REGEX-TEST: §b[MVP§c+§b] hannibal2§f: test + * REGEX-TEST: [58] §7nea89o§7: haiiiii + * REGEX-TEST: [266] ♫ §b[MVP§d+§b] lrg89§f: a */ private val globalPattern by patternGroup.pattern( "global", - "(?:§8\\[§r(?<levelColor>§.)(?<level>\\d+)§r§8] §r)?(?<author>§.+)(?<chatColor>§f|§7§r§7): (?<message>.*)" + "(?:\\[(?<level>\\d+)] )?(?<author>.+)(?<chatColor>§f|§7): (?<message>.*)" ) /** - * REGEX-TEST: §9Party §8> §b§l⚛ §b[MVP§f+§b] Dankbarkeit§f: §rx: -190, y: 5, z: -163 - * REGEX-TEST: §9Party §8> §6⚔ §6[MVP§3++§6] RealBacklight§f: §r!warp - * REGEX-TEST: §9Party §8> §b[MVP§3+§b] Eisengolem§f: §r!pt + * REGEX-TEST: §9Party §8> §b[MVP§d+§b] lrg89§f: peee + * REGEX-TEST: §9Party §8> §7nea89o§f: peee */ private val partyPattern by patternGroup.pattern( "party", - "§9Party §8> (?<author>[^:]*): §r(?<message>.*)" + "§9Party §8> (?<author>[^:]*)§f: (?<message>.*)" ) /** - * REGEX-TEST: §2Guild > §b§l⚛ §b[MVP§f+§b] Dankbarkeit§f: §rx: -190, y: 5, z: -163 - * REGEX-TEST: §2Guild > §6⚔ §6[MVP§3++§6] RealBacklight§f: §r!warp - * REGEX-TEST: §2Guild > §b[MVP§3+§b] Eisengolem§f: §r!pt - * REGEX-TEST: §2Guild > §b[MVP§d+§b] zunoff §e[VET]§f: §rwas löuft - * REGEX-TEST: §2Guild > §7stinkywinkyowo §6[O]§f: §rraven____ > hi + * REGEX-TEST: §bCo-op > §7nea89o§f: hallooooo + */ + private val coopPattern by patternGroup.pattern( + "coop", + "§bCo-op > (?<author>[^:]+)§f: (?<message>.*)" + ) + + /** + * REGEX-TEST: §2Guild > §b[MVP§d+§b] infave §e[Em]§f: CEMENT DRINKERS INCORPORATED + * REGEX-TEST: §2Guild > §6⚔ §6[MVP§3++§6] RealBacklight§f: !warp + * REGEX-TEST: §2Guild > §b[MVP§d+§b] lrg89 §e[Iron]§f: h */ private val guildPattern by patternGroup.pattern( "guild", - "§2Guild > (?<author>§.+?)(?<guildRank> §.\\[\\w*])?§f: §r(?<message>.*)" + "§2Guild > (?<author>.+?)(?<guildRank> §e\\[\\w*])?§f: (?<message>.*)" ) /** - * REGEX-TEST: §dFrom §r§b[MVP§r§3+§r§b] Eisengolem§r§7: §r§7Baum - * REGEX-TEST: §dTo §r§b[MVP§r§3+§r§b] Eisengolem§r§7: §r§7hey - * REGEX-TEST: §dTo §r§b[MVP§r§5+§r§b] Alea1337§r§7: §r§d§lBoop! + * REGEX-TEST: To nea89o: lol + * REGEX-TEST: From nea89o: hiii + * REGEX-TEST: To [MVP+] Eisengolem: Boop! + * REGEX-TEST: From [MVP+] Eisengolem: Boop! + * REGEX-TEST: To [MVP+] Eisengolem: danke */ private val privateMessagePattern by patternGroup.pattern( "privatemessage", - "§d(?<direction>From|To) §r(?<author>[^:]*)§7: §r(?<message>.*)" + "(?<direction>From|To) (?<author>[^:]*): (?<message>.*)" ) /** - * REGEX-TEST: §b[MVP§c+§b] hannibal2§f§7 is holding §r§8[§6Heroic Aspect of the Void§8] - * REGEX-TEST: §b[MVP§c+§b] hannibal2§f§7 is holding §r§8[§7[Lvl 2] §dSpider§8] - * REGEX-TEST: §b[MVP§c+§b] hannibal2§f§7 is friends with a §r§8[§7[Lvl 200] §8[§6103§8§4✦§8] §6Golden Dragon§8] - * REGEX-TEST: §b[MVP§c+§b] hannibal2§f§7 is wearing §r§8[§5Glistening Implosion Belt§8] - * REGEX-TEST: §b[MVP§c+§b] hannibal2§f§7 is friends with a §r§8[§7[Lvl 100] §dEnderman§8] - * REGEX-TEST: §b[MVP§c+§b] hannibal2§f§7 has §r§8[§6Heroic Aspect of the Void§8] - * REGEX-TEST: §8[§5396§8] §7☢ §r§b[MVP§c+§b] hannibal2§f§7 is holding §r§8[§6Buzzing InfiniVacuum™ Hooverius§8] + * REGEX-TEST: §b[MVP§c+§b] hannibal2§f§7 is holding §8[§6Heroic Aspect of the Void§8] + * REGEX-TEST: §b[MVP§c+§b] hannibal2§f§7 is holding §8[§7[Lvl 2] §dSpider§8] + * REGEX-TEST: §b[MVP§c+§b] hannibal2§f§7 is friends with a §8[§7[Lvl 200] §8[§6103§8§4✦§8] §6Golden Dragon§8] + * REGEX-TEST: §b[MVP§c+§b] hannibal2§f§7 is wearing §8[§5Glistening Implosion Belt§8] + * REGEX-TEST: §b[MVP§c+§b] hannibal2§f§7 is friends with a §8[§7[Lvl 100] §dEnderman§8] + * REGEX-TEST: §b[MVP§c+§b] hannibal2§f§7 has §8[§6Heroic Aspect of the Void§8] + * REGEX-TEST: §8[§b209§8] §b[MVP§d+§b] lrg89§f§7 is holding §8[§5Heroic Aspect of the Void§8] */ private val itemShowPattern by patternGroup.pattern( "itemshow", - "(?:§8\\[(?<levelColor>§.)(?<level>\\d+)§8] )?(?<author>.*)§f§7 (?<action>is (?:holding|friends with a|wearing)|has) §r(?<itemName>.*)" + "(?:§8\\[(?<levelColor>§.)(?<level>\\d+)§8] )?(?<author>.*)§f§7 (?<action>is (?:holding|friends with a|wearing)|has) (?<itemName>.*)" ) /** - * REGEX-TEST: §c[Tiffany] §b[MVP§c+§b] hannibal2 - * REGEX-TEST: §b[MVP§c+§b] hannibal2 - * REGEX-TEST: §6§l℻ §r§f[Gamer] §b[MVP§f+§b] SchrankLP§f§r + * REGEX-TEST: ♫ §c[Buddy ツ] §b[MVP§d+§b] lrg89 + * REGEX-TEST: ℻ §b[MVP§5+§b] Alea1337 */ private val privateIslandRankPattern by patternGroup.pattern( "privateislandrank", - ".*(?<privateIslandRank>§.\\[\\w+]).*" + "(?<prefix>.*?)(?<privateIslandRank>§.\\[(?!MVP(§.\\++)?§.]|VIP\\+*|YOU§.TUBE|ADMIN|MOD|GM)[^]]+\\])(?<suffix>.*)" ) /** - * REGEX-TEST: §8[§r§5396§r§8] §r§7☢ §r§a[✌] §b[MVP§c+§b] hannibal2§f: hey - * REGEX-TEST: §b[MVP§c+§b] hannibal2 - * REGEX-TEST: §6§l℻ §r§f[Gamer] §b[MVP§f+§b] SchrankLP§f§r - * REGEX-TEST: §7☢ §r§a[✌] §b[MVP§c+§b] hannibal2 + * REGEX-TEST: ♫ §a[✌] §f[Gamer] §b[MVP§d+§b] lrg89 + * REGEX-TEST: ℻ §b[MVP§5+§b] Alea1337 + * REGEX-TEST: ♫ §a[✌] §c[Buddy ツ] §b[MVP§d+§b] lrg89 */ - private val prrivateIslandGuestPattern by patternGroup.pattern( + private val privateIslandGuestPattern by patternGroup.pattern( "privateislandguest", - ".*(?<guest>§r§a\\[✌]).*" + "(?<prefix>.*)(?<guest>§a\\[✌] )(?<suffix>.*)" ) @SubscribeEvent fun onChat(event: LorenzChatEvent) { - val chatComponent = event.chatComponent - partyPattern.matchMatcher(event.message) { - val author = group("author") - val message = group("message") - PartyChatEvent(author, message, chatComponent).postChat(event) + val chatComponent = event.chatComponent.intoSpan().stripHypixelMessage() + coopPattern.matchStyledMatcher(chatComponent) { + val author = groupOrThrow("author") + val message = groupOrThrow("message") + CoopChatEvent(author, message, event.chatComponent).postChat(event) + return + } + partyPattern.matchStyledMatcher(chatComponent) { + PartyChatEvent(groupOrThrow("author"), groupOrThrow("message"), event.chatComponent) + .postChat(event) return } - guildPattern.matchMatcher(event.message) { - val author = group("author") - val message = group("message") - val guildRank = groupOrNull("guildRank") - GuildChatEvent(author, message, guildRank, chatComponent).postChat(event) + guildPattern.matchStyledMatcher(chatComponent) { + GuildChatEvent( + groupOrThrow("author"), + groupOrThrow("message"), + group("guildRank"), + event.chatComponent + ).postChat(event) return } - privateMessagePattern.matchMatcher(event.message) { - val direction = group("direction") - val author = group("author") - val message = group("message") - PrivateMessageChatEvent(direction, author, message, chatComponent).postChat(event) + privateMessagePattern.matchStyledMatcher(chatComponent) { + val direction = groupOrThrow("direction").getText() + val author = groupOrThrow("author") + val message = groupOrThrow("message") + PrivateMessageChatEvent(direction, author, message, event.chatComponent).postChat(event) return } - itemShowPattern.matchMatcher(event.message) { - val levelColor = groupOrNull("levelColor") - val level = groupOrNull("level")?.formatInt() - val author = group("author") - val action = group("action") - val itemName = group("itemName") - - // for consistency - val message = "§7$action §r$itemName" - PlayerShowItemChatEvent(levelColor, level, author, message, action, itemName, chatComponent).postChat(event) + itemShowPattern.matchStyledMatcher(chatComponent) { + val level = group("level") + val author = groupOrThrow("author") + val action = groupOrThrow("action") + val itemName = groupOrThrow("itemName") + + PlayerShowItemChatEvent( + level, + action, + author, + itemName, + author + action + itemName, + event.chatComponent + ).postChat(event) return } - globalPattern.matchMatcher(event.message) { + globalPattern.matchStyledMatcher(chatComponent) { if (isGlobalChat(event)) return } sendSystemMessage(event) } - private fun Matcher.isGlobalChat(event: LorenzChatEvent): Boolean { - var author = group("author") - val message = LorenzUtils.stripVanillaMessage(group("message")) - if (author.contains("[NPC]")) { - NpcChatEvent(author, message.removePrefix("§f"), event.chatComponent).postChat(event) + private fun ComponentMatcher.isGlobalChat(event: LorenzChatEvent): Boolean { + var author = groupOrThrow("author") + val message = groupOrThrow("message").removePrefix("§f") + if (author.getText().contains("[NPC]")) { + NpcChatEvent(author, message, event.chatComponent).postChat(event) return true } - var privateIslandRank: String? = null - var isAGuest = false + var privateIslandRank: ComponentSpan? = null + var privateIslandGuest: ComponentSpan? = null if (IslandType.PRIVATE_ISLAND.isInIsland() || IslandType.PRIVATE_ISLAND_GUEST.isInIsland()) { - privateIslandRankPattern.matchMatcher(author) { - val rank = group("privateIslandRank") - privateIslandRank = rank - author = author.replace(rank, "") + privateIslandGuestPattern.matchStyledMatcher(author) { + privateIslandGuest = groupOrThrow("guest") + val prefix = groupOrThrow("prefix") + val suffix = groupOrThrow("suffix") + author = prefix + suffix } - prrivateIslandGuestPattern.matchMatcher(author) { - val guest = group("guest") - isAGuest = true - author = author.replace(guest, "") + privateIslandRankPattern.matchStyledMatcher(author) { + privateIslandRank = groupOrThrow("privateIslandRank") + val prefix = groupOrThrow("prefix") + val suffix = groupOrThrow("suffix") + author = prefix + suffix } } - val chatColor = group("chatColor") - val levelColor = groupOrNull("levelColor") - val level = groupOrNull("level")?.formatInt() PlayerAllChatEvent( - levelColor = levelColor, - level = level, + levelComponent = group("level"), privateIslandRank = privateIslandRank, - isAGuest = isAGuest, - author = author, - chatColor = chatColor, - message = message, + privateIslandGuest = privateIslandGuest, + chatColor = groupOrThrow("chatColor").getText(), + authorComponent = author, + messageComponent = message, chatComponent = event.chatComponent, ).postChat(event) return true diff --git a/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/PlayerNameFormatter.kt b/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/PlayerNameFormatter.kt index 55735413b..35c1e8cd1 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/PlayerNameFormatter.kt +++ b/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/PlayerNameFormatter.kt @@ -3,6 +3,7 @@ package at.hannibal2.skyhanni.data.hypixel.chat import at.hannibal2.skyhanni.SkyHanniMod import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator import at.hannibal2.skyhanni.config.features.chat.PlayerMessagesConfig +import at.hannibal2.skyhanni.data.hypixel.chat.event.CoopChatEvent import at.hannibal2.skyhanni.data.hypixel.chat.event.GuildChatEvent import at.hannibal2.skyhanni.data.hypixel.chat.event.PartyChatEvent import at.hannibal2.skyhanni.data.hypixel.chat.event.PlayerAllChatEvent @@ -12,14 +13,19 @@ import at.hannibal2.skyhanni.features.bingo.BingoAPI import at.hannibal2.skyhanni.features.chat.playerchat.PlayerChatFilter import at.hannibal2.skyhanni.features.misc.MarkedPlayerManager import at.hannibal2.skyhanni.features.misc.compacttablist.AdvancedPlayerList +import at.hannibal2.skyhanni.utils.ChatComponentUtils +import at.hannibal2.skyhanni.utils.ComponentMatcherUtils.matchStyledMatcher +import at.hannibal2.skyhanni.utils.ComponentSpan import at.hannibal2.skyhanni.utils.LorenzUtils import at.hannibal2.skyhanni.utils.StringUtils +import at.hannibal2.skyhanni.utils.StringUtils.applyFormattingFrom import at.hannibal2.skyhanni.utils.StringUtils.cleanPlayerName -import at.hannibal2.skyhanni.utils.StringUtils.matchMatcher -import at.hannibal2.skyhanni.utils.StringUtils.replaceAll +import at.hannibal2.skyhanni.utils.StringUtils.toCleanChatComponent import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern import com.google.gson.JsonArray import com.google.gson.JsonNull +import net.minecraft.util.ChatComponentText +import net.minecraft.util.IChatComponent import net.minecraftforge.fml.common.eventhandler.SubscribeEvent /** @@ -37,111 +43,143 @@ class PlayerNameFormatter { * REGEX-TEST: §7☢ §r§bhannibal2 * REGEX-TEST: §7☢ §rhannibal2 * REGEX-TEST: §7☢ §b[MVP§c+§b] hannibal2 + * REGEX-TEST: ♫ §b[MVP§d+§b] lrg89 */ private val emblemPattern by patternGroup.pattern( "emblem", - "(?<emblem>(?:§.){1,2}.) (?<author>.*)" + "(?<emblem>(?:§.){0,2}.) (?<author>.*)" ) + private val empty: IChatComponent = ChatComponentText("") + @SubscribeEvent fun onPlayerAllChat(event: PlayerAllChatEvent) { if (!isEnabled()) return val levelColor = event.levelColor - val level = event.level - val message = event.message - val author = event.author + val levelComponent = event.levelComponent + val message = event.messageComponent + val authorComponent = event.authorComponent val privateIslandRank = event.privateIslandRank - val isAGuest = event.isAGuest + val privateIslandGuest = event.privateIslandGuest - val shouldFilter = config.chatFilter && PlayerChatFilter.shouldChatFilter(message) + val shouldFilter = config.chatFilter && PlayerChatFilter.shouldChatFilter(message.intoComponent()) val chatColor = if (shouldFilter) "§7" else if (config.sameChatColor) "§f" else event.chatColor - val name = nameFormat(author, levelColor, level, privateIslandRank = privateIslandRank, isAGuest = isAGuest) - val newMessage = "$name$chatColor: $message" - - event.chatComponent = StringUtils.replaceIfNeeded(event.chatComponent, newMessage) ?: return + val name = nameFormat( + authorComponent, + levelColor?.toString(), + level = levelComponent, + privateIslandRank = privateIslandRank, + privateIslandGuest = privateIslandGuest + ) + val all = ChatComponentText("") + all.appendSibling(name) + all.appendText(": ") + all.appendSibling(chatColor.toCleanChatComponent()) + all.appendSibling(message.intoComponent()) + event.chatComponent = StringUtils.replaceIfNeeded(event.chatComponent, all) ?: return } @SubscribeEvent - fun onPartyChat(event: PartyChatEvent) { + fun onCoopChat(event: CoopChatEvent) { if (!isEnabled()) return - val message = event.message - val author = event.author - val name = nameFormat(author) - val newMessage = "§9Party §8> $name§f: $message" - - event.chatComponent = StringUtils.replaceIfNeeded(event.chatComponent, newMessage) ?: return + event.chatComponent = StringUtils.replaceIfNeeded( + event.chatComponent, + ChatComponentUtils.text("§bCo-Op > ") { + appendSibling(nameFormat(event.authorComponent)) + appendText("§f: ") + appendSibling(event.messageComponent.intoComponent()) + } + ) ?: return } @SubscribeEvent fun onGuildChat(event: GuildChatEvent) { if (!isEnabled()) return - val message = event.message - val author = event.author - val guildRank = event.guildRank - val name = nameFormat(author, guildRank = guildRank) - val newMessage = "§2Guild > $name§f: $message" - - event.chatComponent = StringUtils.replaceIfNeeded(event.chatComponent, newMessage) ?: return + event.chatComponent = StringUtils.replaceIfNeeded( + event.chatComponent, + ChatComponentUtils.text("§2Guild > ") { + appendSibling(nameFormat(event.authorComponent, guildRank = event.guildRank)) + appendText("§f: ") + appendSibling(event.messageComponent.intoComponent()) + } + ) ?: return } @SubscribeEvent - fun onPrivateMessageChat(event: PrivateMessageChatEvent) { + fun onPartyChat(event: PartyChatEvent) { if (!isEnabled()) return - val direction = event.direction - val message = event.message - val author = event.author - val name = nameFormat(author) - val newMessage = "§d$direction §f$name§7: §f$message" + event.chatComponent = StringUtils.replaceIfNeeded( + event.chatComponent, + ChatComponentUtils.text("§9Party §8> ") { + appendSibling(nameFormat(event.authorComponent)) + appendText("§f: ") + appendSibling(event.messageComponent.intoComponent()) + } + ) ?: return + } - event.chatComponent = StringUtils.replaceIfNeeded(event.chatComponent, newMessage) ?: return + @SubscribeEvent + fun onPrivateChat(event: PrivateMessageChatEvent) { + if (!isEnabled()) return + event.chatComponent = + StringUtils.replaceIfNeeded(event.chatComponent, ChatComponentUtils.text("§d${event.direction}") { + appendText(" ") + appendSibling(nameFormat(event.authorComponent)) + appendText("§f: ") + appendSibling(event.messageComponent.intoComponent()) + }) ?: return } + @SubscribeEvent fun onPlayerShowItemChat(event: PlayerShowItemChatEvent) { if (!isEnabled()) return - val author = event.author - val action = event.action - val itemName = event.itemName - val levelColor = event.levelColor - val level = event.level - val name = nameFormat(author, levelColor = levelColor, level = level) - val newMessage = "$name §7$action §r$itemName" - - event.chatComponent = StringUtils.replaceIfNeeded(event.chatComponent, newMessage) ?: return + event.chatComponent = StringUtils.replaceIfNeeded(event.chatComponent, ChatComponentUtils.text("") { + appendSibling( + nameFormat( + event.authorComponent, + levelColor = event.levelComponent?.sampleStyleAtStart()?.color?.toString(), + level = event.levelComponent + ) + ) + appendSibling(event.action.intoComponent()) + appendText(" ") + appendSibling(event.item.intoComponent()) + }) ?: return } private fun nameFormat( - author: String, + author: ComponentSpan, levelColor: String? = null, - level: Int? = null, - guildRank: String? = null, - privateIslandRank: String? = null, - isAGuest: Boolean = false, - ): String { + level: ComponentSpan? = null, + guildRank: ComponentSpan? = null, + privateIslandRank: ComponentSpan? = null, + privateIslandGuest: ComponentSpan? = null, + ): ChatComponentText { var cleanAuthor = cleanAuthor(author) - var emblemFormat = "" - emblemPattern.matchMatcher(author) { - emblemFormat = group("emblem") - cleanAuthor = LorenzUtils.stripVanillaMessage(group("author")) + var emblemFormat = empty + emblemPattern.matchStyledMatcher(author) { + emblemFormat = componentOrThrow("emblem") + cleanAuthor = groupOrThrow("author").stripHypixelMessage() } - val name = formatAuthor(cleanAuthor, levelColor) + val name = formatAuthor(cleanAuthor.getText(), levelColor).applyFormattingFrom(cleanAuthor) val levelFormat = formatLevel(levelColor, level) - val guildRankFormat = guildRank ?: "" - val privateIslandRankFormat = privateIslandRank ?: "" - val privateIslandGuestFormat = if (isAGuest) "§a[✌]" else "" + val guildRankFormat = guildRank?.intoComponent() ?: empty + val privateIslandRankFormat = privateIslandRank?.intoComponent() ?: empty + val privateIslandGuestFormat = privateIslandGuest?.intoComponent() ?: empty - val cleanName = cleanAuthor.cleanPlayerName() + val cleanName = cleanAuthor.getText().cleanPlayerName() val (faction, ironman, bingo) = AdvancedPlayerList.tabPlayerData[cleanName]?.let { - val faction = it.faction.icon - val ironman = if (it.ironman) "§7♲" else "" - val bingo = it.bingoLevel?.let { level -> BingoAPI.getBingoIcon(level) } ?: "" + val faction = it.faction.icon.toCleanChatComponent() + val ironman = if (it.ironman) "§7♲".toCleanChatComponent() else empty + val bingo = it.bingoLevel?.let { level -> BingoAPI.getBingoIcon(level).toCleanChatComponent() } ?: empty listOf(faction, ironman, bingo) - } ?: listOf("", "", "") + } ?: listOf(empty, empty, empty) - val map = mutableMapOf<PlayerMessagesConfig.MessagePart, String>() + val map = mutableMapOf<PlayerMessagesConfig.MessagePart, IChatComponent>() map[PlayerMessagesConfig.MessagePart.SKYBLOCK_LEVEL] = levelFormat map[PlayerMessagesConfig.MessagePart.EMBLEM] = emblemFormat map[PlayerMessagesConfig.MessagePart.PLAYER_NAME] = name @@ -152,19 +190,46 @@ class PlayerNameFormatter { map[PlayerMessagesConfig.MessagePart.PRIVATE_ISLAND_RANK] = privateIslandRankFormat map[PlayerMessagesConfig.MessagePart.PRIVATE_ISLAND_GUEST] = privateIslandGuestFormat - return config.partsOrder.map { map[it] }.joinToString(" ").replaceAll(" ", " ").trim() + val all = ChatComponentText("") + for (text in config.partsOrder.mapNotNull { map[it] }) { + all.appendSibling(text) + if (!all.unformattedText.endsWith(" ")) { + all.appendText(" ") + } + } + + return all +// return config.partsOrder.map { map[it] }.joinToString(" ").replaceAll(" ", " ").trim() } - private fun formatLevel(rawColor: String?, rawLevel: Int?): String { - val color = rawColor ?: return "" - val level = rawLevel ?: error("level is null, color is not null") +// private fun String.add(iChatComponent: IChatComponent): ChatComponentText { +// val style = findStyle(iChatComponent) +// val text = ChatComponentText(this.trim()) +// text.chatStyle = style +// +// return text +// } +// +// private fun String.findStyle(iChatComponent: IChatComponent): ChatStyle? { +// for (sibling in iChatComponent.siblings) { +// if (sibling.unformattedText.contains(this)) { +// return sibling.chatStyle +// } +// } +// return null +// } + + private fun formatLevel(rawColor: String?, rawLevel: ComponentSpan?): IChatComponent { + val color = rawColor ?: return empty + val level = rawLevel?.getText() ?: error("level is null, color is not null") val levelData = "$color$level" - return if (config.hideLevelBrackets) levelData else "§8[${levelData}§8]" + val result = if (config.hideLevelBrackets) levelData else "§8[${levelData}§8]" + return result.applyFormattingFrom(rawLevel) } - private fun cleanAuthor(author: String): String { - val text = LorenzUtils.stripVanillaMessage(author) - return text.removeSuffix("§f") + private fun cleanAuthor(author: ComponentSpan): ComponentSpan { + // TODO: I don't think we even need to strip this ??? + return author.stripHypixelMessage().removePrefix("§f") } private fun formatAuthor(author: String, levelColor: String?): String { diff --git a/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/AbstractChatEvent.kt b/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/AbstractChatEvent.kt index cd0a8f92b..d7dce85c7 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/AbstractChatEvent.kt +++ b/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/AbstractChatEvent.kt @@ -1,11 +1,15 @@ package at.hannibal2.skyhanni.data.hypixel.chat.event import at.hannibal2.skyhanni.events.LorenzEvent +import at.hannibal2.skyhanni.utils.ComponentSpan import net.minecraft.util.IChatComponent open class AbstractChatEvent( - val author: String, - val message: String, + val authorComponent: ComponentSpan, + val messageComponent: ComponentSpan, var chatComponent: IChatComponent, var blockedReason: String? = null, -) : LorenzEvent() +) : LorenzEvent() { + val message by lazy { messageComponent.getText() } + val author by lazy { authorComponent.getText() } +} diff --git a/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/CoopChatEvent.kt b/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/CoopChatEvent.kt new file mode 100644 index 000000000..2aa075dc6 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/CoopChatEvent.kt @@ -0,0 +1,11 @@ +package at.hannibal2.skyhanni.data.hypixel.chat.event + +import at.hannibal2.skyhanni.utils.ComponentSpan +import net.minecraft.util.IChatComponent + +class CoopChatEvent( + authorComponent: ComponentSpan, + messageComponent: ComponentSpan, + chatComponent: IChatComponent, + blockedReason: String? = null, +) : AbstractChatEvent(authorComponent, messageComponent, chatComponent, blockedReason) diff --git a/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/GuildChatEvent.kt b/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/GuildChatEvent.kt index dd3867106..c07985d94 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/GuildChatEvent.kt +++ b/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/GuildChatEvent.kt @@ -1,11 +1,13 @@ package at.hannibal2.skyhanni.data.hypixel.chat.event +import at.hannibal2.skyhanni.utils.ComponentSpan import net.minecraft.util.IChatComponent + class GuildChatEvent( - author: String, - message: String, - val guildRank: String? = null, + author: ComponentSpan, + message: ComponentSpan, + val guildRank: ComponentSpan?, chatComponent: IChatComponent, blockedReason: String? = null, ) : AbstractChatEvent(author, message, chatComponent, blockedReason) diff --git a/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/NpcChatEvent.kt b/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/NpcChatEvent.kt index 574324520..fb0bd38a4 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/NpcChatEvent.kt +++ b/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/NpcChatEvent.kt @@ -1,10 +1,11 @@ package at.hannibal2.skyhanni.data.hypixel.chat.event +import at.hannibal2.skyhanni.utils.ComponentSpan import net.minecraft.util.IChatComponent class NpcChatEvent( - author: String, - message: String, + authorComponent: ComponentSpan, + messageComponent: ComponentSpan, chatComponent: IChatComponent, blockedReason: String? = null, -) : AbstractChatEvent(author, message, chatComponent, blockedReason) +) : AbstractChatEvent(authorComponent, messageComponent, chatComponent, blockedReason) diff --git a/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/PartyChatEvent.kt b/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/PartyChatEvent.kt index bb3abad24..a26af584b 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/PartyChatEvent.kt +++ b/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/PartyChatEvent.kt @@ -1,14 +1,15 @@ package at.hannibal2.skyhanni.data.hypixel.chat.event import at.hannibal2.skyhanni.utils.StringUtils.cleanPlayerName +import at.hannibal2.skyhanni.utils.ComponentSpan import net.minecraft.util.IChatComponent class PartyChatEvent( - author: String, - message: String, + authorComponent: ComponentSpan, + messageComponent: ComponentSpan, chatComponent: IChatComponent, blockedReason: String? = null, -) : AbstractChatEvent(author, message, chatComponent, blockedReason) { +) : AbstractChatEvent(authorComponent, messageComponent, chatComponent, blockedReason) { val cleanedAuthor by lazy { author.cleanPlayerName() } diff --git a/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/PlayerAllChatEvent.kt b/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/PlayerAllChatEvent.kt index 56ea15a64..bb2de0010 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/PlayerAllChatEvent.kt +++ b/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/PlayerAllChatEvent.kt @@ -1,15 +1,19 @@ package at.hannibal2.skyhanni.data.hypixel.chat.event +import at.hannibal2.skyhanni.utils.ComponentSpan import net.minecraft.util.IChatComponent class PlayerAllChatEvent( - val levelColor: String?, - val level: Int?, - val privateIslandRank: String? = null, - val isAGuest: Boolean, - author: String, - val chatColor: String?, - message: String, + val levelComponent: ComponentSpan?, + val privateIslandRank: ComponentSpan?, + val privateIslandGuest: ComponentSpan?, + val chatColor: String, + authorComponent: ComponentSpan, + messageComponent: ComponentSpan, chatComponent: IChatComponent, blockedReason: String? = null, -) : AbstractChatEvent(author, message, chatComponent, blockedReason) +) : AbstractChatEvent(authorComponent, messageComponent, chatComponent, blockedReason) { + val levelColor = levelComponent?.sampleStyleAtStart()?.color + val level by lazy { levelComponent?.getText()?.toInt() } + val isAGuest by lazy { privateIslandGuest != null } +} diff --git a/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/PlayerShowItemChatEvent.kt b/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/PlayerShowItemChatEvent.kt index 229264b27..8c3c301b8 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/PlayerShowItemChatEvent.kt +++ b/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/PlayerShowItemChatEvent.kt @@ -1,14 +1,14 @@ package at.hannibal2.skyhanni.data.hypixel.chat.event +import at.hannibal2.skyhanni.utils.ComponentSpan import net.minecraft.util.IChatComponent class PlayerShowItemChatEvent( - val levelColor: String?, - val level: Int?, - author: String, - message: String, - val action: String, - val itemName: String, + val levelComponent: ComponentSpan?, + val action: ComponentSpan, + author: ComponentSpan, + val item: ComponentSpan, + message: ComponentSpan, chatComponent: IChatComponent, blockedReason: String? = null, ) : AbstractChatEvent(author, message, chatComponent, blockedReason) diff --git a/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/PrivateMessageChatEvent.kt b/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/PrivateMessageChatEvent.kt index f23b1d0b0..1b61c6dff 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/PrivateMessageChatEvent.kt +++ b/src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/PrivateMessageChatEvent.kt @@ -1,11 +1,12 @@ package at.hannibal2.skyhanni.data.hypixel.chat.event +import at.hannibal2.skyhanni.utils.ComponentSpan import net.minecraft.util.IChatComponent class PrivateMessageChatEvent( val direction: String?, - author: String, - message: String, + author: ComponentSpan, + message: ComponentSpan, chatComponent: IChatComponent, blockedReason: String? = null, ) : AbstractChatEvent(author, message, chatComponent, blockedReason) diff --git a/src/main/java/at/hannibal2/skyhanni/features/chat/playerchat/PlayerChatFilter.kt b/src/main/java/at/hannibal2/skyhanni/features/chat/playerchat/PlayerChatFilter.kt index 024835aeb..cb6f68c4d 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/chat/playerchat/PlayerChatFilter.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/chat/playerchat/PlayerChatFilter.kt @@ -3,6 +3,7 @@ package at.hannibal2.skyhanni.features.chat.playerchat import at.hannibal2.skyhanni.data.jsonobjects.repo.PlayerChatFilterJson import at.hannibal2.skyhanni.events.RepositoryReloadEvent import at.hannibal2.skyhanni.utils.MultiFilter +import net.minecraft.util.IChatComponent import net.minecraftforge.fml.common.eventhandler.SubscribeEvent class PlayerChatFilter { @@ -11,8 +12,8 @@ class PlayerChatFilter { private val filters = mutableMapOf<String, MultiFilter>() - fun shouldChatFilter(original: String): Boolean { - val message = original.lowercase() + fun shouldChatFilter(original: IChatComponent): Boolean { + val message = original.formattedText.lowercase() for (filter in filters) { filter.value.matchResult(message)?.let { return true diff --git a/src/main/java/at/hannibal2/skyhanni/features/chat/playerchat/PlayerChatModifier.kt b/src/main/java/at/hannibal2/skyhanni/features/chat/playerchat/PlayerChatModifier.kt index 4aabeaf6c..2fbb9630e 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/chat/playerchat/PlayerChatModifier.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/chat/playerchat/PlayerChatModifier.kt @@ -5,6 +5,7 @@ import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator import at.hannibal2.skyhanni.data.hypixel.chat.event.SystemMessageEvent import at.hannibal2.skyhanni.features.misc.MarkedPlayerManager import at.hannibal2.skyhanni.utils.StringUtils +import net.minecraft.util.ChatComponentText import net.minecraftforge.fml.common.eventhandler.SubscribeEvent class PlayerChatModifier { @@ -21,7 +22,7 @@ class PlayerChatModifier { fun onChat(event: SystemMessageEvent) { val newMessage = cutMessage(event.chatComponent.formattedText) - event.chatComponent = StringUtils.replaceIfNeeded(event.chatComponent, newMessage) ?: return + event.chatComponent = StringUtils.replaceIfNeeded(event.chatComponent, ChatComponentText(newMessage)) ?: return } private fun cutMessage(input: String): String { diff --git a/src/main/java/at/hannibal2/skyhanni/utils/ChatComponentUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/ChatComponentUtils.kt new file mode 100644 index 000000000..4a0d8ce76 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/ChatComponentUtils.kt @@ -0,0 +1,8 @@ +package at.hannibal2.skyhanni.utils + +import net.minecraft.util.ChatComponentText + +object ChatComponentUtils { + fun text(text: String, init: ChatComponentText.() -> Unit = {}): ChatComponentText = + ChatComponentText(text).also(init) +} diff --git a/src/main/java/at/hannibal2/skyhanni/utils/ComponentMatcherUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/ComponentMatcherUtils.kt index 4e2b6eb06..f5c25ab2b 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/ComponentMatcherUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/ComponentMatcherUtils.kt @@ -111,6 +111,29 @@ class ComponentMatcher internal constructor( if (start < 0) return null return this.span.slice(start, matcher.end(name)) } + + /** + * Return a span equivalent to the group with the given name found by [matches] or [find] + */ + fun component(name: String): IChatComponent? { + return group(name)?.intoComponent() + } + + /** + * Return a span equivalent to the group with the given name found by [matches] or [find]. + * Returns not nullable object, or throws an error. + */ + fun groupOrThrow(name: String): ComponentSpan { + return group(name) ?: error("group '$name' not found in ComponentSpan!") + } + + /** + * Return a IChatComponent equivalent to the group with the given name found by [matches] or [find]. + * Returns not nullable object, or throws an error. + */ + fun componentOrThrow(name: String): IChatComponent { + return groupOrThrow(name).intoComponent() + } } /** @@ -124,7 +147,7 @@ class ComponentMatcher internal constructor( class ComponentSpan internal constructor( val textComponent: IChatComponent, private val cachedText: String, - val start: Int, val end: Int + val start: Int, val end: Int, ) { init { require(start <= end) @@ -140,10 +163,10 @@ class ComponentSpan internal constructor( /** * Slice this component span. This is equivalent to the [String.substring] operation on the [text][getText]. */ - fun slice(start: Int, end: Int): ComponentSpan { - require(0 <= start) - require(start <= end) - require(end <= length) + fun slice(start: Int = 0, end: Int = length): ComponentSpan { + require(0 <= start) { "start is bigger than 0: start=$start, cachedText=$cachedText" } + require(start <= end) { "start is bigger than length: start=$start, length=$length, cachedText=$cachedText" } + require(end <= length) { "end is bigger than length: end=$end, length=$length, cachedText=$cachedText" } return ComponentSpan(textComponent, cachedText, this.start + start, this.start + end) } @@ -241,4 +264,55 @@ class ComponentSpan internal constructor( return sampleComponents().map { it.chatStyle } } + /** + * Strip extra `§r` color codes from the beginning / end of this span. + */ + fun stripHypixelMessage(): ComponentSpan { + var start = 0 + var newString = getText() + var end = this.length + while (newString.startsWith("§r")) { + start += 2 + newString = newString.substring(2) + } + while (newString.endsWith("§r")) { + newString = newString.substring(0, newString.length - 2) + end -= 2 + } + return slice(start, end) + } + + /** + * Remove a prefix from this span if it exists. Otherwise, return this unchanged. + */ + fun removePrefix(prefix: String): ComponentSpan { + if (!getText().startsWith(prefix)) return this + return slice(prefix.length) + } + + /** + * Remove a suffix from this span if it exists. Otherwise, return this unchanged. + */ + fun removeSuffix(suffix: String): ComponentSpan { + if (!getText().endsWith(suffix)) return this + return slice(end = length - suffix.length) + } + + /** + * Append another [ComponentSpan] to this one to create a new one. This will [flatten][intoComponent] the hierarchy + * of the underlying [IChatComponent] but preserve formatting. + */ + operator fun plus(other: ComponentSpan): ComponentSpan { + val left = this.intoComponent() + val right = other.intoComponent() + left.appendSibling(right) + return left.intoSpan() + } + + companion object { + fun empty(): ComponentSpan { + return ComponentSpan(ChatComponentText(""), "", 0, 0) + } + } + } diff --git a/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt index cb5099ebe..6ea0f9aa7 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt @@ -17,6 +17,7 @@ import at.hannibal2.skyhanni.utils.ItemUtils.getItemCategoryOrNull import at.hannibal2.skyhanni.utils.NEUItems.getItemStackOrNull import at.hannibal2.skyhanni.utils.StringUtils.capAtMinecraftLength import at.hannibal2.skyhanni.utils.StringUtils.removeColor +import at.hannibal2.skyhanni.utils.StringUtils.stripHypixelMessage import at.hannibal2.skyhanni.utils.StringUtils.toDashlessUUID import at.hannibal2.skyhanni.utils.renderables.Renderable import com.google.gson.JsonPrimitive @@ -91,16 +92,10 @@ object LorenzUtils { fun SimpleDateFormat.formatCurrentTime(): String = this.format(System.currentTimeMillis()) + // TODO move to string utils + @Deprecated("outdated", ReplaceWith("originalMessage.stripHypixelMessage()")) fun stripVanillaMessage(originalMessage: String): String { - var message = originalMessage - - while (message.startsWith("§r")) { - message = message.substring(2) - } - while (message.endsWith("§r")) { - message = message.substring(0, message.length - 2) - } - return message + return originalMessage.stripHypixelMessage() } fun Double.round(decimals: Int): Double { diff --git a/src/main/java/at/hannibal2/skyhanni/utils/StringUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/StringUtils.kt index 388700128..4feb0ba13 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/StringUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/StringUtils.kt @@ -8,6 +8,8 @@ import at.hannibal2.skyhanni.utils.StringUtils.width import net.minecraft.client.Minecraft import net.minecraft.client.gui.GuiUtilRenderComponents import net.minecraft.util.ChatComponentText +import net.minecraft.util.ChatStyle +import net.minecraft.util.EnumChatFormatting import net.minecraft.util.IChatComponent import java.util.Base64 import java.util.UUID @@ -321,33 +323,83 @@ object StringUtils { fun generateRandomId() = UUID.randomUUID().toString() fun replaceIfNeeded( - original: IChatComponent, + original: ChatComponentText, newText: String, ): ChatComponentText? { - val foundCommands = mutableListOf<IChatComponent>() + return replaceIfNeeded(original, ChatComponentText(newText)) + } - addComponent(foundCommands, original) - for (sibling in original.siblings) { - addComponent(foundCommands, sibling) - } - val size = foundCommands.size - if (size > 1) { - return null - } + private val colorMap = EnumChatFormatting.entries.associateBy { it.toString()[1] } + fun enumChatFormattingByCode(char: Char): EnumChatFormatting? { + return colorMap[char] + } - val originalClean = LorenzUtils.stripVanillaMessage(original.formattedText) - val newTextClean = LorenzUtils.stripVanillaMessage(newText) - if (originalClean == newTextClean) return null + fun doLookTheSame(left: IChatComponent, right: IChatComponent): Boolean { + class ChatIterator(var component: IChatComponent) { + var queue = mutableListOf<IChatComponent>() + var idx = 0 + var colorOverride = ChatStyle() + fun next(): Pair<Char, ChatStyle>? { + while (true) { + while (idx >= component.unformattedTextForChat.length) { + queue.addAll(0, component.siblings) + colorOverride = ChatStyle() + component = queue.removeFirstOrNull() ?: return null + } + val char = component.unformattedTextForChat[idx++] + if (char == '§' && idx < component.unformattedTextForChat.length) { + val formattingChar = component.unformattedTextForChat[idx++] + val formatting = enumChatFormattingByCode(formattingChar) ?: continue + when (formatting) { + EnumChatFormatting.OBFUSCATED -> { + colorOverride.setObfuscated(true) + } + + EnumChatFormatting.BOLD -> { + colorOverride.setBold(true) + } + + EnumChatFormatting.STRIKETHROUGH -> { + colorOverride.setStrikethrough(true) + } + + EnumChatFormatting.UNDERLINE -> { + colorOverride.setUnderlined(true) + } + + EnumChatFormatting.ITALIC -> { + colorOverride.setItalic(true) + } + + else -> { + colorOverride = ChatStyle().setColor(formatting) + } + } + } else { + return Pair(char, colorOverride.setParentStyle(component.chatStyle)) + } + } + } + } - val text = ChatComponentText(newText) - if (size == 1) { - val chatStyle = foundCommands[0].chatStyle - text.chatStyle.chatClickEvent = chatStyle.chatClickEvent - text.chatStyle.chatHoverEvent = chatStyle.chatHoverEvent + val leftIt = ChatIterator(left) + val rightIt = ChatIterator(right) + while (true) { + val leftChar = leftIt.next() + val rightChar = rightIt.next() + if (leftChar == null && rightChar == null) return true + if (leftChar != rightChar) return false } + } - return text + + fun <T : IChatComponent> replaceIfNeeded( + original: T, + newText: T, + ): T? { + if (doLookTheSame(original, newText)) return null + return newText } private fun addComponent(foundCommands: MutableList<IChatComponent>, message: IChatComponent) { @@ -371,6 +423,40 @@ object StringUtils { } } + /** + * Removes starting and ending reset formattings that dont sever a benefit at all. + */ + fun String.stripHypixelMessage(): String { + var message = this + + while (message.startsWith("§r")) { + message = message.substring(2) + } + while (message.endsWith("§r")) { + message = message.substring(0, message.length - 2) + } + return message + } + + fun String.applyFormattingFrom(original: ComponentSpan): IChatComponent { + val text = ChatComponentText(this) + text.chatStyle = original.sampleStyleAtStart() + return text + } + + fun String.applyFormattingFrom(original: IChatComponent): IChatComponent { + val text = ChatComponentText(this) + text.chatStyle = original.chatStyle + return text + } + + fun String.toCleanChatComponent(): IChatComponent = ChatComponentText(this) + + fun IChatComponent.cleanPlayerName(displayName: Boolean = false): IChatComponent = + formattedText.cleanPlayerName(displayName).applyFormattingFrom(this) + + fun IChatComponent.contains(string: String): Boolean = formattedText.contains(string) + fun String.width(): Int { return Minecraft.getMinecraft().fontRendererObj.getStringWidth(this) } |