From 9c008dcba4f88fc47c17c7d4b3f8c9dd237e7131 Mon Sep 17 00:00:00 2001 From: l4zs Date: Sat, 8 Jan 2022 22:30:09 +0100 Subject: [PATCH] use the adventure api - migrate BungeeCord Chat API to Adventure API - add some functions to literalText - update some dependencies and plugins --- build.gradle.kts | 16 +-- .../axay/kspigot/chat/LiteralTextBuilder.kt | 117 ++++++++++++------ .../axay/kspigot/chat/MessageExtensions.kt | 11 +- .../axay/kspigot/chat/input/PlayerInput.kt | 22 +++- .../input/implementations/PlayerInputBook.kt | 7 +- .../input/implementations/PlayerInputChat.kt | 16 +-- .../kspigot/extensions/GeneralExtensions.kt | 5 +- .../extensions/bukkit/EntityExtensions.kt | 5 +- .../extensions/bukkit/MetaExtensions.kt | 3 +- .../kotlin/net/axay/kspigot/gui/GUIType.kt | 7 +- .../net/axay/kspigot/items/KSpigotItems.kt | 31 +++-- 11 files changed, 156 insertions(+), 84 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index bc039bf1..a979ae43 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,21 +3,21 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile val githubRepo = "jakobkmar/KSpigot" group = "net.axay" -version = "1.18.0" +version = "1.18.1" description = "A Kotlin API for Minecraft plugins using the Spigot or Paper toolchain" plugins { - kotlin("jvm") version "1.6.0" + kotlin("jvm") version "1.6.10" `java-library` `maven-publish` signing - id("org.jetbrains.dokka") version "1.6.0" - kotlin("plugin.serialization") version "1.6.0" + id("org.jetbrains.dokka") version "1.6.10" + kotlin("plugin.serialization") version "1.6.10" - id("io.papermc.paperweight.userdev") version "1.3.1" + id("io.papermc.paperweight.userdev") version "1.3.4-SNAPSHOT" } repositories { @@ -27,9 +27,9 @@ repositories { dependencies { paperDevBundle("1.18.1-R0.1-SNAPSHOT") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1") - api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0-RC2") - api("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.6.0-RC2") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0") + api("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.6.0") } tasks { diff --git a/src/main/kotlin/net/axay/kspigot/chat/LiteralTextBuilder.kt b/src/main/kotlin/net/axay/kspigot/chat/LiteralTextBuilder.kt index 1ea3484c..daf0a7ad 100644 --- a/src/main/kotlin/net/axay/kspigot/chat/LiteralTextBuilder.kt +++ b/src/main/kotlin/net/axay/kspigot/chat/LiteralTextBuilder.kt @@ -1,13 +1,18 @@ -@file:Suppress("MemberVisibilityCanBePrivate", "unused") +@file:Suppress("MemberVisibilityCanBePrivate", "Unused") package net.axay.kspigot.chat -import net.md_5.bungee.api.ChatColor -import net.md_5.bungee.api.chat.BaseComponent -import net.md_5.bungee.api.chat.ClickEvent -import net.md_5.bungee.api.chat.HoverEvent -import net.md_5.bungee.api.chat.TextComponent -import net.md_5.bungee.api.chat.hover.content.Text +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.Component.empty +import net.kyori.adventure.text.Component.newline +import net.kyori.adventure.text.event.ClickEvent +import net.kyori.adventure.text.event.HoverEvent +import net.kyori.adventure.text.format.TextColor +import net.kyori.adventure.text.format.TextColor.color +import net.kyori.adventure.text.format.TextDecoration +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer +import org.bukkit.entity.Entity +import org.bukkit.inventory.ItemStack /** * Opens a [LiteralTextBuilder]. @@ -18,10 +23,10 @@ import net.md_5.bungee.api.chat.hover.content.Text inline fun literalText( baseText: String = "", builder: LiteralTextBuilder.() -> Unit = { } -) = LiteralTextBuilder(baseText).apply(builder).build() as TextComponent +) = LiteralTextBuilder(baseText).apply(builder).build() -class LiteralTextBuilder(val internalText: BaseComponent, ) { - constructor(text: String) : this(TextComponent(text)) +class LiteralTextBuilder(val internalText: Component) { + constructor(text: String) : this(Component.text(text)) var bold: Boolean? = null var italic: Boolean? = null @@ -39,12 +44,12 @@ class LiteralTextBuilder(val internalText: BaseComponent, ) { * - `color = col("#4BD6CB")` * - `color = KColors.MEDIUMTURQUOISE` */ - var color: ChatColor? = null + var color: TextColor? = null var clickEvent: ClickEvent? = null - var hoverEvent: HoverEvent? = null + var hoverEvent: HoverEvent<*>? = null - val siblingText = TextComponent("") + var siblingText = empty() /** * Append text to the parent. @@ -56,20 +61,20 @@ class LiteralTextBuilder(val internalText: BaseComponent, ) { text: String = "", builder: LiteralTextBuilder.() -> Unit = { } ) { - siblingText.addExtra(LiteralTextBuilder(text).apply(builder).build()) + siblingText = siblingText.append(LiteralTextBuilder(text).apply(builder).build()) } /** - * Append text to the parent. + * Append a component to the parent. * - * @param text the text instance + * @param component the component * @param builder the builder which can be used to set the style and add child text components */ - inline fun text( - text: BaseComponent, + inline fun component( + component: Component, builder: LiteralTextBuilder.() -> Unit = { } ) { - siblingText.addExtra(LiteralTextBuilder(text).apply(builder).build()) + siblingText = siblingText.append(LiteralTextBuilder(component).apply(builder).build()) } /** @@ -84,9 +89,7 @@ class LiteralTextBuilder(val internalText: BaseComponent, ) { text: String, builder: LiteralTextBuilder.() -> Unit = { } ) { - TextComponent.fromLegacyText(text).forEach { - siblingText.addExtra(LiteralTextBuilder(it).apply(builder).build()) - } + siblingText = siblingText.append(LiteralTextBuilder(LegacyComponentSerializer.legacy('§').deserialize(text)).apply(builder).build()) } /** @@ -100,12 +103,36 @@ class LiteralTextBuilder(val internalText: BaseComponent, ) { text: String = "", builder: LiteralTextBuilder.() -> Unit = { } ) { - hoverEvent = HoverEvent( + hoverEvent = HoverEvent.hoverEvent( HoverEvent.Action.SHOW_TEXT, - Text(arrayOf(LiteralTextBuilder(text).apply(builder).build())) + LiteralTextBuilder(text).apply(builder).build() ) } + /** + * Sets the item which should be displayed when hovering + * over the text in the chat. + * + * @param itemStack the ItemStack + */ + fun hoverItem( + itemStack: ItemStack + ) { + hoverEvent = itemStack.asHoverEvent() + } + + /** + * Sets the entity which should be displayed when hovering + * over the text in the chat. + * + * @param entity the Entity + */ + fun hoverEntity( + entity: Entity + ) { + hoverEvent = entity.asHoverEvent() + } + /** * Sets the command which should be executed by the Player if he clicks * on the text. @@ -115,7 +142,7 @@ class LiteralTextBuilder(val internalText: BaseComponent, ) { * instead it will be suggested in the command prompt */ fun onClickCommand(command: String, onlySuggest: Boolean = false) { - clickEvent = ClickEvent(if (onlySuggest) ClickEvent.Action.SUGGEST_COMMAND else ClickEvent.Action.RUN_COMMAND, command) + clickEvent = if (onlySuggest) ClickEvent.suggestCommand(command) else ClickEvent.runCommand(command) } /** @@ -123,14 +150,21 @@ class LiteralTextBuilder(val internalText: BaseComponent, ) { * Player clicks on this text. */ fun onClickCopy(copyText: String) { - clickEvent = ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, copyText) + clickEvent = ClickEvent.copyToClipboard(copyText) + } + + /** + * Sets the Url which should be opened if the Player clicks on this text + */ + fun onClickOpenURL(url: String) { + clickEvent = ClickEvent.openUrl(url) } /** * Adds a line break. */ fun newLine() { - siblingText.addExtra(TextComponent("\n")) + siblingText = siblingText.append(newline()) } /** @@ -141,17 +175,24 @@ class LiteralTextBuilder(val internalText: BaseComponent, ) { newLine() } - fun build() = internalText.apply { - this@LiteralTextBuilder.bold?.let { isBold = it } - this@LiteralTextBuilder.italic?.let { isItalic = it } - this@LiteralTextBuilder.underline?.let { isUnderlined = it } - this@LiteralTextBuilder.strikethrough?.let { isStrikethrough = it } - this@LiteralTextBuilder.obfuscate?.let { isObfuscated = it } - this@LiteralTextBuilder.color?.let { color = it } - this@LiteralTextBuilder.clickEvent?.let { clickEvent = it } - this@LiteralTextBuilder.hoverEvent?.let { hoverEvent = it } + fun build(): Component { + var style = internalText.style() + val decorations = style.decorations().toMutableMap() + decorations[TextDecoration.BOLD] = TextDecoration.State.byBoolean(this@LiteralTextBuilder.bold) + decorations[TextDecoration.ITALIC] = TextDecoration.State.byBoolean(this@LiteralTextBuilder.italic) + decorations[TextDecoration.UNDERLINED] = TextDecoration.State.byBoolean(this@LiteralTextBuilder.underline) + decorations[TextDecoration.STRIKETHROUGH] = TextDecoration.State.byBoolean(this@LiteralTextBuilder.strikethrough) + decorations[TextDecoration.OBFUSCATED] = TextDecoration.State.byBoolean(this@LiteralTextBuilder.obfuscate) + style = style.decorations(decorations) + this@LiteralTextBuilder.color?.let { style = style.color(color(it)) } - if (siblingText.extra?.isNotEmpty() == true) - addExtra(siblingText) + this@LiteralTextBuilder.clickEvent?.let { style = style.clickEvent(it) } + this@LiteralTextBuilder.hoverEvent?.let { style = style.hoverEvent(it) } + + return if (siblingText.children().isNotEmpty()) { + internalText.append(siblingText).style(style) + } else { + internalText.style(style) + } } } diff --git a/src/main/kotlin/net/axay/kspigot/chat/MessageExtensions.kt b/src/main/kotlin/net/axay/kspigot/chat/MessageExtensions.kt index e4b7009b..48e085a9 100644 --- a/src/main/kotlin/net/axay/kspigot/chat/MessageExtensions.kt +++ b/src/main/kotlin/net/axay/kspigot/chat/MessageExtensions.kt @@ -1,12 +1,13 @@ -@file:Suppress("MemberVisibilityCanBePrivate") +@file:Suppress("MemberVisibilityCanBePrivate", "Unused") package net.axay.kspigot.chat -import net.md_5.bungee.api.chat.BaseComponent +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.JoinConfiguration import org.bukkit.command.CommandSender -fun CommandSender.sendMessage(vararg components: BaseComponent) { - this.spigot().sendMessage(*components) +fun CommandSender.sendMessage(vararg components: Component) { + this.sendMessage(Component.join(JoinConfiguration.separator(Component.newline()), *components)) } /** @@ -18,7 +19,7 @@ fun CommandSender.sendMessage(vararg components: BaseComponent) { inline fun CommandSender.sendText( baseText: String = "", crossinline builder: LiteralTextBuilder.() -> Unit = { } -) = this.spigot().sendMessage(literalText(baseText, builder)) +) = this.sendMessage(literalText(baseText, builder)) @Suppress("DEPRECATION") @Deprecated( diff --git a/src/main/kotlin/net/axay/kspigot/chat/input/PlayerInput.kt b/src/main/kotlin/net/axay/kspigot/chat/input/PlayerInput.kt index 15f44940..906c9d46 100644 --- a/src/main/kotlin/net/axay/kspigot/chat/input/PlayerInput.kt +++ b/src/main/kotlin/net/axay/kspigot/chat/input/PlayerInput.kt @@ -1,4 +1,4 @@ -@file:Suppress("MemberVisibilityCanBePrivate") +@file:Suppress("MemberVisibilityCanBePrivate", "Unused") package net.axay.kspigot.chat.input @@ -8,6 +8,8 @@ import net.axay.kspigot.chat.input.implementations.PlayerInputChat import net.axay.kspigot.event.unregister import net.axay.kspigot.runnables.sync import net.axay.kspigot.runnables.taskRunLater +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.Component.text import org.bukkit.entity.Player import org.bukkit.event.Listener @@ -16,13 +18,25 @@ import org.bukkit.event.Listener * chat input of the player as his input. */ fun Player.awaitChatInput( - question: String = "Type your input in the chat!", + question: Component = text("Type your input in the chat!"), timeoutSeconds: Int = 1 * 60, - callback: (PlayerInputResult) -> Unit, + callback: (PlayerInputResult) -> Unit, ) { PlayerInputChat(this, callback, timeoutSeconds, question) } +/** + * Asks the player a question and uses the next + * chat input of the player as his input. + */ +fun Player.awaitChatInput( + question: String = "Type your input in the chat!", + timeoutSeconds: Int = 1 * 60, + callback: (PlayerInputResult) -> Unit, +) { + awaitChatInput(text(question), timeoutSeconds, callback) +} + /** * Opens a book and uses the text the player inserted * on all sites as the players' input. @@ -41,7 +55,7 @@ fun Player.awaitBookInputAsString( */ fun Player.awaitBookInputAsList( timeoutSeconds: Int = 1 * 60, - callback: (PlayerInputResult>) -> Unit, + callback: (PlayerInputResult>) -> Unit, ) = PlayerInputBookPaged(this, callback, timeoutSeconds).bookItemStack /** diff --git a/src/main/kotlin/net/axay/kspigot/chat/input/implementations/PlayerInputBook.kt b/src/main/kotlin/net/axay/kspigot/chat/input/implementations/PlayerInputBook.kt index 5cc8c276..c434c160 100644 --- a/src/main/kotlin/net/axay/kspigot/chat/input/implementations/PlayerInputBook.kt +++ b/src/main/kotlin/net/axay/kspigot/chat/input/implementations/PlayerInputBook.kt @@ -7,6 +7,7 @@ import net.axay.kspigot.extensions.bukkit.content import net.axay.kspigot.items.itemStack import net.axay.kspigot.items.meta import net.axay.kspigot.main.PluginInstance +import net.kyori.adventure.text.Component import org.bukkit.Material import org.bukkit.NamespacedKey import org.bukkit.entity.Player @@ -24,10 +25,10 @@ internal class PlayerInputBookComprehensive( internal class PlayerInputBookPaged( player: Player, - callback: (PlayerInputResult>) -> Unit, + callback: (PlayerInputResult>) -> Unit, timeoutSeconds: Int, -) : PlayerInputBook>(player, callback, timeoutSeconds) { - override fun loadBookContent(bookMeta: BookMeta): List = bookMeta.pages +) : PlayerInputBook>(player, callback, timeoutSeconds) { + override fun loadBookContent(bookMeta: BookMeta): List = bookMeta.pages() } internal abstract class PlayerInputBook( diff --git a/src/main/kotlin/net/axay/kspigot/chat/input/implementations/PlayerInputChat.kt b/src/main/kotlin/net/axay/kspigot/chat/input/implementations/PlayerInputChat.kt index 1584ef1f..46fae1e3 100644 --- a/src/main/kotlin/net/axay/kspigot/chat/input/implementations/PlayerInputChat.kt +++ b/src/main/kotlin/net/axay/kspigot/chat/input/implementations/PlayerInputChat.kt @@ -1,28 +1,28 @@ package net.axay.kspigot.chat.input.implementations -import net.axay.kspigot.chat.KColors +import io.papermc.paper.event.player.AsyncChatEvent import net.axay.kspigot.chat.input.PlayerInput import net.axay.kspigot.chat.input.PlayerInputResult import net.axay.kspigot.event.listen +import net.kyori.adventure.text.Component import org.bukkit.entity.Player import org.bukkit.event.EventPriority -import org.bukkit.event.player.AsyncPlayerChatEvent internal class PlayerInputChat( player: Player, - callback: (PlayerInputResult) -> Unit, + callback: (PlayerInputResult) -> Unit, timeoutSeconds: Int, - question: String, -) : PlayerInput(player, callback, timeoutSeconds) { + question: Component, +) : PlayerInput(player, callback, timeoutSeconds) { init { - player.sendMessage("${KColors.ORANGERED}$question") + player.sendMessage(question) } override val inputListeners = listOf( - listen(EventPriority.LOWEST) { + listen(EventPriority.LOWEST) { if (it.player == player) { + onReceive(it.message()) it.isCancelled = true - onReceive(it.message) } } ) diff --git a/src/main/kotlin/net/axay/kspigot/extensions/GeneralExtensions.kt b/src/main/kotlin/net/axay/kspigot/extensions/GeneralExtensions.kt index 27b61599..4979e4cc 100644 --- a/src/main/kotlin/net/axay/kspigot/extensions/GeneralExtensions.kt +++ b/src/main/kotlin/net/axay/kspigot/extensions/GeneralExtensions.kt @@ -1,6 +1,9 @@ +@file:Suppress("Unused") + package net.axay.kspigot.extensions import net.axay.kspigot.main.PluginInstance +import net.kyori.adventure.text.Component.text import org.bukkit.Bukkit import org.bukkit.NamespacedKey import org.bukkit.World @@ -36,7 +39,7 @@ val pluginManager get() = Bukkit.getPluginManager() * @return the number of recipients * @see Bukkit.broadcastMessage */ -fun broadcast(msg: String) = Bukkit.broadcastMessage(msg) +fun broadcast(msg: String) = Bukkit.getServer().broadcast(text(msg)) /** * Shortcut to get the ConsoleSender. diff --git a/src/main/kotlin/net/axay/kspigot/extensions/bukkit/EntityExtensions.kt b/src/main/kotlin/net/axay/kspigot/extensions/bukkit/EntityExtensions.kt index 2a068bee..a76831eb 100644 --- a/src/main/kotlin/net/axay/kspigot/extensions/bukkit/EntityExtensions.kt +++ b/src/main/kotlin/net/axay/kspigot/extensions/bukkit/EntityExtensions.kt @@ -1,3 +1,5 @@ +@file:Suppress("Unused") + package net.axay.kspigot.extensions.bukkit import net.axay.kspigot.annotations.NMS_General @@ -5,7 +7,6 @@ import net.axay.kspigot.chat.literalText import net.axay.kspigot.extensions.onlinePlayers import net.axay.kspigot.main.PluginInstance import net.axay.kspigot.pluginmessages.PluginMessageConnect -import net.md_5.bungee.api.ChatMessageType import org.bukkit.Location import org.bukkit.Material import org.bukkit.attribute.Attribute @@ -146,7 +147,7 @@ fun Player.getHandItem(hand: EquipmentSlot?) = when (hand) { * Sends the given [text] as an action bar message. */ fun Player.actionBar(text: String) { - spigot().sendMessage(ChatMessageType.ACTION_BAR, literalText { legacyText(text) }) + sendActionBar(literalText { legacyText(text) }) } /** diff --git a/src/main/kotlin/net/axay/kspigot/extensions/bukkit/MetaExtensions.kt b/src/main/kotlin/net/axay/kspigot/extensions/bukkit/MetaExtensions.kt index 958d4162..19c7c9c7 100644 --- a/src/main/kotlin/net/axay/kspigot/extensions/bukkit/MetaExtensions.kt +++ b/src/main/kotlin/net/axay/kspigot/extensions/bukkit/MetaExtensions.kt @@ -1,11 +1,12 @@ package net.axay.kspigot.extensions.bukkit +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer import org.bukkit.inventory.meta.BookMeta val BookMeta.content get() = StringBuilder().apply { - for (it in pages) { + for (it in pages().map { LegacyComponentSerializer.legacy('§').serialize(it) }) { if (isNotEmpty()) append('\n') append(it) diff --git a/src/main/kotlin/net/axay/kspigot/gui/GUIType.kt b/src/main/kotlin/net/axay/kspigot/gui/GUIType.kt index f13c9aee..556678f7 100644 --- a/src/main/kotlin/net/axay/kspigot/gui/GUIType.kt +++ b/src/main/kotlin/net/axay/kspigot/gui/GUIType.kt @@ -1,7 +1,8 @@ -@file:Suppress("MemberVisibilityCanBePrivate", "CanBeParameter") +@file:Suppress("MemberVisibilityCanBePrivate", "CanBeParameter", "Unused") package net.axay.kspigot.gui +import net.kyori.adventure.text.Component.text import org.bukkit.Bukkit import org.bukkit.event.inventory.InventoryType import org.bukkit.inventory.Inventory @@ -27,8 +28,8 @@ class GUIType( fun createBukkitInv(holder: InventoryHolder? = null, title: String? = null): Inventory { val realTitle = title ?: "" return when { - bukkitType != null -> Bukkit.createInventory(holder, bukkitType, realTitle) - else -> Bukkit.createInventory(holder, dimensions.slotAmount, realTitle) + bukkitType != null -> Bukkit.createInventory(holder, bukkitType, text(realTitle)) + else -> Bukkit.createInventory(holder, dimensions.slotAmount, text(realTitle)) } } } diff --git a/src/main/kotlin/net/axay/kspigot/items/KSpigotItems.kt b/src/main/kotlin/net/axay/kspigot/items/KSpigotItems.kt index 6ea83f95..522abe00 100644 --- a/src/main/kotlin/net/axay/kspigot/items/KSpigotItems.kt +++ b/src/main/kotlin/net/axay/kspigot/items/KSpigotItems.kt @@ -1,5 +1,11 @@ +@file:Suppress("Unused") + package net.axay.kspigot.items +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.Component.text +import net.kyori.adventure.text.Component.translatable +import net.kyori.adventure.text.TranslatableComponent import org.bukkit.Bukkit import org.bukkit.Material import org.bukkit.inventory.ItemFlag @@ -58,16 +64,16 @@ inline fun itemMeta(material: Material, builder: ItemMeta.() -> Unit) = itemMeta * Sets the lore (description) of the item. */ inline fun ItemMeta.setLore(builder: ItemMetaLoreBuilder.() -> Unit) { - lore = ItemMetaLoreBuilder().apply(builder).lorelist + lore(ItemMetaLoreBuilder().apply(builder).lorelist) } /** * Adds new lines to the lore (description) of the item. */ inline fun ItemMeta.addLore(builder: ItemMetaLoreBuilder.() -> Unit) { - val newLore = lore ?: mutableListOf() + val newLore = lore() ?: mutableListOf() newLore.addAll(ItemMetaLoreBuilder().apply(builder).lorelist) - lore = newLore + lore(newLore) } /** @@ -75,10 +81,13 @@ inline fun ItemMeta.addLore(builder: ItemMetaLoreBuilder.() -> Unit) { * It exists to provide overloaded operator functions. */ class ItemMetaLoreBuilder { - val lorelist = ArrayList() - operator fun String.unaryPlus() { + val lorelist = ArrayList() + operator fun Component.unaryPlus() { lorelist += this } + operator fun String.unaryPlus() { + lorelist += text(this) + } } /** @@ -104,9 +113,9 @@ fun ItemMeta.removeFlags(vararg itemFlag: ItemFlag) = removeItemFlags(*itemFlag) /** * Provides safe access to the items' displayName. */ -var ItemMeta.name: String? - get() = if (hasDisplayName()) displayName else null - set(value) = setDisplayName(if (value == null || value == "") " " else value) +var ItemMeta.name: Component? + get() = if (hasDisplayName()) displayName() else null + set(value) = displayName(value ?: Component.space()) /** * Provides safe access to the items' customModelData. @@ -118,6 +127,6 @@ var ItemMeta.customModel: Int? /** * Provides more consistent access to the items' localizedName. */ -var ItemMeta.localName: String - get() = localizedName - set(value) = setLocalizedName(value) +var ItemMeta.localName: TranslatableComponent + get() = if (hasDisplayName()) displayName() as TranslatableComponent else translatable("") + set(value) = displayName(value)