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/at/hannibal2/skyhanni/utils | |
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/at/hannibal2/skyhanni/utils')
4 files changed, 196 insertions, 33 deletions
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) } |