diff --git a/src/main/java/de/bixilon/minosoft/data/text/BaseComponent.kt b/src/main/java/de/bixilon/minosoft/data/text/BaseComponent.kt index 6962c2019..6fcd759f4 100644 --- a/src/main/java/de/bixilon/minosoft/data/text/BaseComponent.kt +++ b/src/main/java/de/bixilon/minosoft/data/text/BaseComponent.kt @@ -17,18 +17,11 @@ import de.bixilon.kutil.cast.CastUtil.nullCast import de.bixilon.kutil.json.JsonUtil.toJsonList import de.bixilon.kutil.json.JsonUtil.toJsonObject import de.bixilon.kutil.primitive.BooleanUtil.toBoolean -import de.bixilon.kutil.url.URLUtil.toURL import de.bixilon.minosoft.data.language.translate.Translator -import de.bixilon.minosoft.data.text.events.click.ClickEvent import de.bixilon.minosoft.data.text.events.click.ClickEvents -import de.bixilon.minosoft.data.text.events.click.OpenURLClickEvent import de.bixilon.minosoft.data.text.events.hover.HoverEvents import de.bixilon.minosoft.data.text.formatting.ChatCode.Companion.toColor -import de.bixilon.minosoft.data.text.formatting.ChatFormattingCode -import de.bixilon.minosoft.data.text.formatting.ChatFormattingCodes -import de.bixilon.minosoft.data.text.formatting.PostChatFormattingCodes import de.bixilon.minosoft.data.text.formatting.PreChatFormattingCodes -import de.bixilon.minosoft.data.text.formatting.color.ChatColors import de.bixilon.minosoft.data.text.formatting.color.RGBColor import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition import de.bixilon.minosoft.util.KUtil.format @@ -36,113 +29,20 @@ import de.bixilon.minosoft.util.KUtil.toResourceLocation import de.bixilon.minosoft.util.nbt.tag.NBTUtil.get import javafx.collections.ObservableList import javafx.scene.Node -import java.text.CharacterIterator -import java.text.StringCharacterIterator class BaseComponent : ChatComponent { val parts: MutableList = mutableListOf() + constructor(parts: MutableList) { + this.parts += parts + } + constructor(vararg parts: Any?) { for (part in parts) { this.parts += part.format() } } - constructor(parent: TextComponent? = null, legacy: String = "", restrictedMode: Boolean = false) { - val currentText = StringBuilder() - var currentColor = parent?.color - var currentFormatting: MutableSet = parent?.formatting?.toMutableSet() ?: mutableSetOf() - - val iterator = StringCharacterIterator(legacy) - - var char = iterator.first() - - - fun push() { - if (currentText.isEmpty()) { - return - } - val spaceSplit = currentText.split(' ') - var currentMessage = "" - - fun push(clickEvent: ClickEvent?) { - if (currentMessage.isEmpty()) { - return - } - parts += TextComponent(message = currentMessage, color = currentColor, formatting = currentFormatting.toMutableSet(), clickEvent = clickEvent) - currentMessage = "" - } - - for ((index, split) in spaceSplit.withIndex()) { - var clickEvent: ClickEvent? = null - if (split.isNotBlank()) { - for (protocol in URLProtocols.VALUES) { - if (!split.startsWith(protocol.prefix)) { - continue - } - if (protocol.restricted && restrictedMode) { - break - } - clickEvent = OpenURLClickEvent(split.toURL()) - break - } - } - if (split.isNotEmpty()) { - if (clickEvent != null) { - // push previous - push(null) - - currentMessage = split - push(clickEvent) - } else { - currentMessage += split - } - } - - if (index != spaceSplit.size - 1) { - currentMessage += " " - } - } - push(null) - currentFormatting = mutableSetOf() - currentColor = null - currentText.clear() - } - - while (char != CharacterIterator.DONE) { - if (char != ProtocolDefinition.TEXT_COMPONENT_FORMATTING_PREFIX) { - currentText.append(char) - char = iterator.next() - continue - } - - val formattingChar = iterator.next() - - ChatColors.VALUES.getOrNull(Character.digit(formattingChar, 16))?.let { - push() - currentColor = it.nullCast() - } ?: ChatFormattingCodes.getChatFormattingCodeByChar(formattingChar)?.let { - push() - - if (it == PostChatFormattingCodes.RESET) { - push() - } else { - currentFormatting.add(it) - } - } ?: let { - // ignore and ignore next char - char = iterator.next() - } - // check because of above - if (char == CharacterIterator.DONE) { - break - } - char = iterator.next() - } - - push() - } - constructor(translator: Translator? = null, parent: TextComponent? = null, json: Map, restrictedMode: Boolean = false) { var currentParent: TextComponent? = null var currentText = "" @@ -150,7 +50,7 @@ class BaseComponent : ChatComponent { fun parseExtra() { json["extra"].toJsonList()?.let { for (data in it) { - parts += ChatComponent.of(data, translator, currentParent, restrictedMode) + this += ChatComponent.of(data, translator, currentParent, restrictedMode) } } } @@ -186,7 +86,7 @@ class BaseComponent : ChatComponent { hoverEvent = hoverEvent, ) if (currentText.isNotEmpty()) { - parts += textComponent + this += textComponent } currentParent = textComponent @@ -199,7 +99,7 @@ class BaseComponent : ChatComponent { with.add(part ?: continue) } } - parts += translator?.translate(it.toResourceLocation(), currentParent, restrictedMode, *with.toTypedArray()) ?: ChatComponent.of(json["with"], translator, currentParent, restrictedMode) + this += translator?.translate(it.toResourceLocation(), currentParent, restrictedMode, *with.toTypedArray()) ?: ChatComponent.of(json["with"], translator, currentParent, restrictedMode) } } @@ -295,8 +195,15 @@ class BaseComponent : ChatComponent { return legacyText } + operator fun plusAssign(component: ChatComponent) { + if (component.length == 0) { + return + } + parts += component + } + operator fun plusAssign(text: Any?) { - parts += text.format() + this += text.format() } private fun MutableSet.addOrRemove(value: T, addOrRemove: Boolean?) { diff --git a/src/main/java/de/bixilon/minosoft/data/text/ChatComponent.kt b/src/main/java/de/bixilon/minosoft/data/text/ChatComponent.kt index 95d513164..954927f34 100644 --- a/src/main/java/de/bixilon/minosoft/data/text/ChatComponent.kt +++ b/src/main/java/de/bixilon/minosoft/data/text/ChatComponent.kt @@ -133,7 +133,7 @@ interface ChatComponent { } } - return BaseComponent(parent, string, restrictedMode) + return LegacyComponentReader.parse(parent, string, restrictedMode) } fun String.chat(): ChatComponent { diff --git a/src/main/java/de/bixilon/minosoft/data/text/LegacyComponentReader.kt b/src/main/java/de/bixilon/minosoft/data/text/LegacyComponentReader.kt new file mode 100644 index 000000000..a2995b3ea --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/data/text/LegacyComponentReader.kt @@ -0,0 +1,148 @@ +/* + * Minosoft + * Copyright (C) 2020-2023 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.data.text + +import de.bixilon.kutil.url.URLUtil.toURL +import de.bixilon.minosoft.data.text.events.click.ClickEvent +import de.bixilon.minosoft.data.text.events.click.OpenFileClickEvent +import de.bixilon.minosoft.data.text.events.click.OpenURLClickEvent +import de.bixilon.minosoft.data.text.formatting.ChatFormattingCode +import de.bixilon.minosoft.data.text.formatting.ChatFormattingCodes +import de.bixilon.minosoft.data.text.formatting.PostChatFormattingCodes +import de.bixilon.minosoft.data.text.formatting.color.ChatColors +import de.bixilon.minosoft.data.text.formatting.color.RGBColor +import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition +import java.io.File +import java.text.CharacterIterator +import java.text.StringCharacterIterator + +private typealias PartList = MutableList + +object LegacyComponentReader { + + + private fun PartList.push(sequence: SequenceBuilder, restricted: Boolean) { + if (sequence.text.isEmpty()) return + + val split = sequence.text.split(' ') + + val text = StringBuilder() + + for ((index, part) in split.withIndex()) { + val event = getClickEvent(part, restricted) + if (event == null) { + text.append(part) + + if (index < split.size - 1) { + text.append(" ") // space was lost in the split process + } + continue + } + if (text.isNotEmpty()) { + // an url follows, push the previous part + this += TextComponent(text, sequence.color, sequence.formatting.toMutableSet()) + text.clear() + } + + this += TextComponent(part, sequence.color, sequence.formatting.toMutableSet(), event) + } + if (text.isNotEmpty()) { + // data that was not pushed yet + this += TextComponent(text, sequence.color, sequence.formatting.toMutableSet()) + } + + sequence.reset() // clear it up again for next usage + } + + private fun getClickEvent(link: String, restricted: Boolean): ClickEvent? { + for (protocol in URLProtocols.VALUES) { + if (!link.startsWith(protocol.prefix)) { + continue + } + if (protocol.restricted && restricted) { + break + } + return if (protocol == URLProtocols.FILE) OpenFileClickEvent(File(link.removePrefix(protocol.prefix))) else OpenURLClickEvent(link.toURL()) + } + return null + } + + + fun parse(parent: TextComponent? = null, legacy: String = "", restricted: Boolean = false): ChatComponent { + val parts: PartList = mutableListOf() + + val sequence = SequenceBuilder(color = parent?.color, formatting = parent?.formatting?.toMutableSet() ?: mutableSetOf()) + + val iterator = StringCharacterIterator(legacy) + + var char: Char + while (true) { + char = iterator.getAndNext() + if (char == CharacterIterator.DONE) break + + if (char != ProtocolDefinition.TEXT_COMPONENT_FORMATTING_PREFIX) { + sequence.text.append(char) + continue + } + + val formattingChar = iterator.getAndNext() + + val color = ChatColors.VALUES.getOrNull(Character.digit(formattingChar, 16)) + if (color != null) { + parts.push(sequence, restricted) // try push previous, because this is a color change + sequence.color = color + continue + } + val formatting = ChatFormattingCodes.getChatFormattingCodeByChar(formattingChar) + if (formatting != null) { + parts.push(sequence, restricted) // try push previous, because this is a formatting change + + if (formatting != PostChatFormattingCodes.RESET) { + // a reset means resetting, this is done by the previous push + sequence.formatting += formatting + } + continue + } + + // ignore the next char, it is not valid formatting and should be hidden + } + + parts.push(sequence, restricted) + + return when { + parts.isEmpty() -> EmptyComponent + parts.size == 1 -> parts.first() + else -> BaseComponent(parts) + } + } + + private fun StringCharacterIterator.getAndNext(): Char { + val char = current() + next() + return char + } + + private data class SequenceBuilder( + var text: StringBuilder = StringBuilder(), + var color: RGBColor? = null, + var formatting: MutableSet = mutableSetOf(), + ) { + + fun reset() { + text.clear() + color = null + formatting.clear() + } + } +} diff --git a/src/main/java/de/bixilon/minosoft/data/text/events/click/CopyToClipboardClickEvent.kt b/src/main/java/de/bixilon/minosoft/data/text/events/click/CopyToClipboardClickEvent.kt index d98605bb6..73f350468 100644 --- a/src/main/java/de/bixilon/minosoft/data/text/events/click/CopyToClipboardClickEvent.kt +++ b/src/main/java/de/bixilon/minosoft/data/text/events/click/CopyToClipboardClickEvent.kt @@ -1,6 +1,6 @@ /* * Minosoft - * Copyright (C) 2020-2022 Moritz Zwerger + * Copyright (C) 2020-2023 Moritz Zwerger * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. * @@ -36,6 +36,15 @@ class CopyToClipboardClickEvent( dialog.show() } + override fun hashCode(): Int { + return text.hashCode() + } + + override fun equals(other: Any?): Boolean { + if (other !is CopyToClipboardClickEvent) return false + return other.text == text + } + companion object : ClickEventFactory { override val name = "copy_to_clipboard" diff --git a/src/main/java/de/bixilon/minosoft/data/text/events/click/OpenFileClickEvent.kt b/src/main/java/de/bixilon/minosoft/data/text/events/click/OpenFileClickEvent.kt index cfd681c01..984ac4af2 100644 --- a/src/main/java/de/bixilon/minosoft/data/text/events/click/OpenFileClickEvent.kt +++ b/src/main/java/de/bixilon/minosoft/data/text/events/click/OpenFileClickEvent.kt @@ -48,6 +48,15 @@ class OpenFileClickEvent( dialog.show() } + override fun hashCode(): Int { + return file.path.hashCode() + } + + override fun equals(other: Any?): Boolean { + if (other !is OpenFileClickEvent) return false + return other.file.path == file.path + } + companion object : ClickEventFactory { override val name: String = "open_file" diff --git a/src/main/java/de/bixilon/minosoft/data/text/events/click/OpenURLClickEvent.kt b/src/main/java/de/bixilon/minosoft/data/text/events/click/OpenURLClickEvent.kt index c280cd965..4a838b676 100644 --- a/src/main/java/de/bixilon/minosoft/data/text/events/click/OpenURLClickEvent.kt +++ b/src/main/java/de/bixilon/minosoft/data/text/events/click/OpenURLClickEvent.kt @@ -1,6 +1,6 @@ /* * Minosoft - * Copyright (C) 2020-2022 Moritz Zwerger + * Copyright (C) 2020-2023 Moritz Zwerger * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. * @@ -46,6 +46,15 @@ class OpenURLClickEvent( dialog.show() } + override fun hashCode(): Int { + return url.hashCode() + } + + override fun equals(other: Any?): Boolean { + if (other !is OpenURLClickEvent) return false + return other.url == url + } + companion object : ClickEventFactory { override val name: String = "open_url" diff --git a/src/main/java/de/bixilon/minosoft/data/text/events/click/SendMessageClickEvent.kt b/src/main/java/de/bixilon/minosoft/data/text/events/click/SendMessageClickEvent.kt index e1b0361f2..7d709b009 100644 --- a/src/main/java/de/bixilon/minosoft/data/text/events/click/SendMessageClickEvent.kt +++ b/src/main/java/de/bixilon/minosoft/data/text/events/click/SendMessageClickEvent.kt @@ -1,6 +1,6 @@ /* * Minosoft - * Copyright (C) 2020-2022 Moritz Zwerger + * Copyright (C) 2020-2023 Moritz Zwerger * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. * @@ -37,6 +37,15 @@ class SendMessageClickEvent( dialog.show() } + override fun hashCode(): Int { + return message.hashCode() + } + + override fun equals(other: Any?): Boolean { + if (other !is SendMessageClickEvent) return false + return other.message == message + } + companion object : ClickEventFactory, MultiNameFactory { override val name: String = "send_message" override val aliases: Set = setOf("run_command") diff --git a/src/main/java/de/bixilon/minosoft/data/text/events/click/SuggestChatClickEvent.kt b/src/main/java/de/bixilon/minosoft/data/text/events/click/SuggestChatClickEvent.kt index c952a1856..29117ac1b 100644 --- a/src/main/java/de/bixilon/minosoft/data/text/events/click/SuggestChatClickEvent.kt +++ b/src/main/java/de/bixilon/minosoft/data/text/events/click/SuggestChatClickEvent.kt @@ -1,6 +1,6 @@ /* * Minosoft - * Copyright (C) 2022 Moritz Zwerger + * Copyright (C) 2020-2023 Moritz Zwerger * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. * @@ -20,6 +20,15 @@ class SuggestChatClickEvent( val message: String, ) : ClickEvent { + override fun hashCode(): Int { + return message.hashCode() + } + + override fun equals(other: Any?): Boolean { + if (other !is SuggestChatClickEvent) return false + return other.message == message + } + companion object : ClickEventFactory, MultiNameFactory { override val name: String = "suggest_chat" override val aliases: Set = setOf("suggest_command") diff --git a/src/main/java/de/bixilon/minosoft/protocol/protocol/buffers/InByteBuffer.kt b/src/main/java/de/bixilon/minosoft/protocol/protocol/buffers/InByteBuffer.kt index fa79fd0dc..b2c48ce9a 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/protocol/buffers/InByteBuffer.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/protocol/buffers/InByteBuffer.kt @@ -28,6 +28,7 @@ import de.bixilon.minosoft.data.text.ChatComponent import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition import de.bixilon.minosoft.util.json.Jackson import de.bixilon.minosoft.util.nbt.tag.NBTTagTypes +import java.nio.charset.StandardCharsets open class InByteBuffer : de.bixilon.kutil.buffer.bytes.`in`.InByteBuffer { @@ -44,14 +45,11 @@ open class InByteBuffer : de.bixilon.kutil.buffer.bytes.`in`.InByteBuffer { return readByte() / 32.0 } - // TODO kutil 1.19.2 - /* - override fun readString(length: Int = readVarInt()): String { + override fun readString(length: Int): String { val string = String(readByteArray(length), StandardCharsets.UTF_8) check(string.length <= ProtocolDefinition.STRING_MAX_LENGTH) { "String max string length exceeded ${string.length} > ${ProtocolDefinition.STRING_MAX_LENGTH}" } return string } - */ fun readJson(): Map { return Jackson.MAPPER.readValue(readString(), Jackson.JSON_MAP_TYPE) diff --git a/src/main/java/de/bixilon/minosoft/protocol/protocol/buffers/OutByteBuffer.kt b/src/main/java/de/bixilon/minosoft/protocol/protocol/buffers/OutByteBuffer.kt index 0c567767f..feea3eea5 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/protocol/buffers/OutByteBuffer.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/protocol/buffers/OutByteBuffer.kt @@ -18,6 +18,8 @@ import de.bixilon.kotlinglm.vec3.Vec3i import de.bixilon.minosoft.data.registries.identified.Namespaces import de.bixilon.minosoft.data.registries.identified.ResourceLocation import de.bixilon.minosoft.data.text.ChatComponent +import de.bixilon.minosoft.protocol.ProtocolUtil.encodeNetwork +import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition import de.bixilon.minosoft.util.json.Jackson import de.bixilon.minosoft.util.nbt.tag.NBTTagTypes import de.bixilon.minosoft.util.nbt.tag.NBTUtil.nbtType @@ -36,8 +38,6 @@ open class OutByteBuffer : de.bixilon.kutil.buffer.bytes.out.OutByteBuffer { } - // TODO kutil 1.19.2 - /* override fun writeString(string: String) { check(string.length <= ProtocolDefinition.STRING_MAX_LENGTH) { "String max string length exceeded ${string.length} > ${ProtocolDefinition.STRING_MAX_LENGTH}" } val bytes = string.encodeNetwork() @@ -45,8 +45,6 @@ open class OutByteBuffer : de.bixilon.kutil.buffer.bytes.out.OutByteBuffer { writeBareByteArray(bytes) } - */ - protected fun writeNBTTagType(type: NBTTagTypes) { writeByte(type.ordinal) } diff --git a/src/test/java/de/bixilon/minosoft/data/text/ChatComponentTest.kt b/src/test/java/de/bixilon/minosoft/data/text/ChatComponentTest.kt index 686773228..02d8d65d3 100644 --- a/src/test/java/de/bixilon/minosoft/data/text/ChatComponentTest.kt +++ b/src/test/java/de/bixilon/minosoft/data/text/ChatComponentTest.kt @@ -1,6 +1,6 @@ /* * Minosoft - * Copyright (C) 2020-2022 Moritz Zwerger + * Copyright (C) 2020-2023 Moritz Zwerger * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. * @@ -13,7 +13,10 @@ package de.bixilon.minosoft.data.text +import de.bixilon.kutil.url.URLUtil.toURL import de.bixilon.minosoft.data.text.ChatComponent.Companion.chat +import de.bixilon.minosoft.data.text.events.click.OpenFileClickEvent +import de.bixilon.minosoft.data.text.events.click.OpenURLClickEvent import de.bixilon.minosoft.data.text.formatting.color.ChatColors import de.bixilon.minosoft.data.text.formatting.color.RGBColor.Companion.asColor import org.junit.jupiter.api.Test @@ -30,28 +33,28 @@ internal class ChatComponentTest { @Test fun testSimpleText() { - val expected = BaseComponent(parts = arrayOf(TextComponent("Test"))) + val expected = TextComponent("Test") val actual = "Test".chat() assertEquals(expected, actual) } @Test fun testSimpleColor() { - val expected = BaseComponent(parts = arrayOf(TextComponent("Test").color(ChatColors.RED))) + val expected = TextComponent("Test").color(ChatColors.RED) val actual = "§cTest".chat() assertEquals(expected, actual) } @Test fun testSimpleColoredFormatting() { - val expected = BaseComponent(parts = arrayOf(TextComponent("Test").color(ChatColors.RED).strikethrough())) + val expected = TextComponent("Test").color(ChatColors.RED).strikethrough() val actual = "§c§mTest".chat() assertEquals(expected, actual) } @Test fun testSwappedSimpleColoredFormatting() { - val expected = BaseComponent(parts = arrayOf(TextComponent("Test").color(ChatColors.RED).strikethrough())) + val expected = TextComponent("Test").color(ChatColors.RED).strikethrough() val actual = "§m§cTest".chat() assertEquals(expected, actual) } @@ -66,6 +69,47 @@ internal class ChatComponentTest { assertEquals(expected, actual) } + + @Test + fun url1() { + val expected = BaseComponent( + TextComponent("Test").color(ChatColors.RED), + TextComponent("https://bixilon.de").color(ChatColors.GREEN).clickEvent(OpenURLClickEvent("https://bixilon.de".toURL())), + ) + val actual = "§cTest§ahttps://bixilon.de".chat() + assertEquals(expected, actual) + } + + @Test + fun url2() { + val expected = BaseComponent( + TextComponent("Test ").color(ChatColors.RED), + TextComponent("file:/home/moritz").color(ChatColors.GREEN).clickEvent(OpenFileClickEvent("/home/moritz")), + ) + val actual = ChatComponent.of("§cTest §afile:/home/moritz") + assertEquals(expected, actual) + } + + @Test + fun url3() { + val expected = BaseComponent( + TextComponent("Hi, please take care of: "), + TextComponent("https://bixilon.de/technoblade").clickEvent(OpenURLClickEvent("https://bixilon.de/technoblade".toURL())), + ) + val actual = ChatComponent.of("Hi, please take care of: https://bixilon.de/technoblade") + assertEquals(expected, actual) + } + + @Test + fun restrictedMode() { + val expected = BaseComponent( + TextComponent("Test ").color(ChatColors.RED), + TextComponent("file:/home/moritz").color(ChatColors.GREEN), + ) + val actual = ChatComponent.of("§cTest §afile:/home/moritz", restrictedMode = true) + assertEquals(expected, actual) + } + @Test fun test2FormattedTexts() { val expected = BaseComponent( @@ -137,11 +181,7 @@ internal class ChatComponentTest { @Test fun testInvalidJson() { - val expected = BaseComponent( - parts = arrayOf( - TextComponent("""{text":"Test"}"""), - ) - ) + val expected = TextComponent("""{text":"Test"}""") val actual = """{text":"Test"}""".chat() assertEquals(expected, actual) } @@ -169,4 +209,24 @@ internal class ChatComponentTest { val text = ChatComponent.of("dummy§anext") assertEquals(text.getJson(), listOf(mapOf("text" to "dummy"), mapOf("text" to "next", "color" to "green"))) } + + @Test + fun hypixelMotd() { + val string = " §aHypixel Network §c[1.8-1.19]\n §c§lLUNAR MAPS §7§l§ §6§lCOSMETICS §7| §d§lSKYBLOCK 0.17.3" + val component = ChatComponent.of(string) + + val expected = BaseComponent( + " ", + TextComponent("Hypixel Network ").color(ChatColors.GREEN), + TextComponent("[1.8-1.19]\n ").color(ChatColors.RED), + TextComponent("LUNAR MAPS ").color(ChatColors.RED).bold(), + TextComponent("COSMETICS ").color(ChatColors.GOLD).bold(), + TextComponent("| ").color(ChatColors.GRAY), + TextComponent("SKYBLOCK 0.17.3").color(ChatColors.LIGHT_PURPLE).bold(), + ) + + + assertEquals(" Hypixel Network [1.8-1.19]\n LUNAR MAPS COSMETICS | SKYBLOCK 0.17.3", component.message) + assertEquals(expected, component) + } }