aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/PlayerChatManager.kt201
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/PlayerNameFormatter.kt205
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/AbstractChatEvent.kt10
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/CoopChatEvent.kt11
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/GuildChatEvent.kt8
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/NpcChatEvent.kt7
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/PartyChatEvent.kt7
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/PlayerAllChatEvent.kt20
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/PlayerShowItemChatEvent.kt12
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/hypixel/chat/event/PrivateMessageChatEvent.kt5
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/chat/playerchat/PlayerChatFilter.kt5
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/chat/playerchat/PlayerChatModifier.kt3
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/ChatComponentUtils.kt8
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/ComponentMatcherUtils.kt84
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt13
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/StringUtils.kt124
-rw-r--r--src/test/java/at/hannibal2/skyhanni/test/ComponentSpanTest.kt25
17 files changed, 513 insertions, 235 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)
}
diff --git a/src/test/java/at/hannibal2/skyhanni/test/ComponentSpanTest.kt b/src/test/java/at/hannibal2/skyhanni/test/ComponentSpanTest.kt
index 9b5f40829..7bef032c7 100644
--- a/src/test/java/at/hannibal2/skyhanni/test/ComponentSpanTest.kt
+++ b/src/test/java/at/hannibal2/skyhanni/test/ComponentSpanTest.kt
@@ -29,6 +29,19 @@ class ComponentSpanTest {
}
@Test
+ fun testRemovePrefix() {
+ val component = text("12345") {
+ appendSibling(text("12345"))
+ appendSibling(text("12345§r"))
+ }.intoSpan()
+ val prefixRemoved = component.removePrefix("123")
+ require(prefixRemoved.getText() == "451234512345§r")
+ require(component.stripHypixelMessage().getText() == "123451234512345")
+ require(component.stripHypixelMessage().slice().getText() == "123451234512345")
+ require(component.slice(0, 0).slice(0, 0).getText() == "")
+ }
+
+ @Test
fun testRegex() {
val component = text("12345") {
appendSibling(text("abcdef") {
@@ -37,16 +50,18 @@ class ComponentSpanTest {
appendSibling(text("12345"))
}
Pattern.compile("[0-9]*(?<middle>[a-z]+)[0-9]*").matchStyledMatcher(component) {
- require(group("middle")?.sampleStyleAtStart()?.color == EnumChatFormatting.RED)
+ require(groupOrThrow("middle")?.sampleStyleAtStart()?.color == EnumChatFormatting.RED)
}
val middlePartExtracted =
Pattern.compile("[0-9]*(?<middle>[0-9][a-z]+[0-9])[0-9]*").matchStyledMatcher(component) {
- require(group("middle")?.sampleComponents()?.size == 3)
- require(group("middle")?.sampleStyles()?.find { it.color != null }?.color == EnumChatFormatting.RED)
- group("middle")
+ require(groupOrThrow("middle")?.sampleComponents()?.size == 3)
+ require(
+ groupOrThrow("middle")?.sampleStyles()?.find { it.color != null }?.color == EnumChatFormatting.RED
+ )
+ groupOrThrow("middle")
}!!
Pattern.compile("(?<whole>c)").findStyledMatcher(middlePartExtracted) {
- require(group("whole")?.sampleStyleAtStart()?.color == EnumChatFormatting.RED)
+ require(groupOrThrow("whole")?.sampleStyleAtStart()?.color == EnumChatFormatting.RED)
}
}