use the adventure api

- migrate BungeeCord Chat API to Adventure API
- add some functions to literalText
- update some dependencies and plugins
This commit is contained in:
l4zs
2022-01-08 22:30:09 +01:00
parent 6bacddfdb5
commit 9c008dcba4
11 changed files with 156 additions and 84 deletions

View File

@@ -3,21 +3,21 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
val githubRepo = "jakobkmar/KSpigot" val githubRepo = "jakobkmar/KSpigot"
group = "net.axay" group = "net.axay"
version = "1.18.0" version = "1.18.1"
description = "A Kotlin API for Minecraft plugins using the Spigot or Paper toolchain" description = "A Kotlin API for Minecraft plugins using the Spigot or Paper toolchain"
plugins { plugins {
kotlin("jvm") version "1.6.0" kotlin("jvm") version "1.6.10"
`java-library` `java-library`
`maven-publish` `maven-publish`
signing signing
id("org.jetbrains.dokka") version "1.6.0" id("org.jetbrains.dokka") version "1.6.10"
kotlin("plugin.serialization") version "1.6.0" 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 { repositories {
@@ -27,9 +27,9 @@ repositories {
dependencies { dependencies {
paperDevBundle("1.18.1-R0.1-SNAPSHOT") paperDevBundle("1.18.1-R0.1-SNAPSHOT")
api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1") api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2")
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0-RC2") api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
api("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.6.0-RC2") api("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.6.0")
} }
tasks { tasks {

View File

@@ -1,13 +1,18 @@
@file:Suppress("MemberVisibilityCanBePrivate", "unused") @file:Suppress("MemberVisibilityCanBePrivate", "Unused")
package net.axay.kspigot.chat package net.axay.kspigot.chat
import net.md_5.bungee.api.ChatColor import net.kyori.adventure.text.Component
import net.md_5.bungee.api.chat.BaseComponent import net.kyori.adventure.text.Component.empty
import net.md_5.bungee.api.chat.ClickEvent import net.kyori.adventure.text.Component.newline
import net.md_5.bungee.api.chat.HoverEvent import net.kyori.adventure.text.event.ClickEvent
import net.md_5.bungee.api.chat.TextComponent import net.kyori.adventure.text.event.HoverEvent
import net.md_5.bungee.api.chat.hover.content.Text 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]. * Opens a [LiteralTextBuilder].
@@ -18,10 +23,10 @@ import net.md_5.bungee.api.chat.hover.content.Text
inline fun literalText( inline fun literalText(
baseText: String = "", baseText: String = "",
builder: LiteralTextBuilder.() -> Unit = { } builder: LiteralTextBuilder.() -> Unit = { }
) = LiteralTextBuilder(baseText).apply(builder).build() as TextComponent ) = LiteralTextBuilder(baseText).apply(builder).build()
class LiteralTextBuilder(val internalText: BaseComponent, ) { class LiteralTextBuilder(val internalText: Component) {
constructor(text: String) : this(TextComponent(text)) constructor(text: String) : this(Component.text(text))
var bold: Boolean? = null var bold: Boolean? = null
var italic: Boolean? = null var italic: Boolean? = null
@@ -39,12 +44,12 @@ class LiteralTextBuilder(val internalText: BaseComponent, ) {
* - `color = col("#4BD6CB")` * - `color = col("#4BD6CB")`
* - `color = KColors.MEDIUMTURQUOISE` * - `color = KColors.MEDIUMTURQUOISE`
*/ */
var color: ChatColor? = null var color: TextColor? = null
var clickEvent: ClickEvent? = null var clickEvent: ClickEvent? = null
var hoverEvent: HoverEvent? = null var hoverEvent: HoverEvent<*>? = null
val siblingText = TextComponent("") var siblingText = empty()
/** /**
* Append text to the parent. * Append text to the parent.
@@ -56,20 +61,20 @@ class LiteralTextBuilder(val internalText: BaseComponent, ) {
text: String = "", text: String = "",
builder: LiteralTextBuilder.() -> Unit = { } 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 * @param builder the builder which can be used to set the style and add child text components
*/ */
inline fun text( inline fun component(
text: BaseComponent, component: Component,
builder: LiteralTextBuilder.() -> Unit = { } 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, text: String,
builder: LiteralTextBuilder.() -> Unit = { } builder: LiteralTextBuilder.() -> Unit = { }
) { ) {
TextComponent.fromLegacyText(text).forEach { siblingText = siblingText.append(LiteralTextBuilder(LegacyComponentSerializer.legacy('§').deserialize(text)).apply(builder).build())
siblingText.addExtra(LiteralTextBuilder(it).apply(builder).build())
}
} }
/** /**
@@ -100,12 +103,36 @@ class LiteralTextBuilder(val internalText: BaseComponent, ) {
text: String = "", text: String = "",
builder: LiteralTextBuilder.() -> Unit = { } builder: LiteralTextBuilder.() -> Unit = { }
) { ) {
hoverEvent = HoverEvent( hoverEvent = HoverEvent.hoverEvent(
HoverEvent.Action.SHOW_TEXT, 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 * Sets the command which should be executed by the Player if he clicks
* on the text. * on the text.
@@ -115,7 +142,7 @@ class LiteralTextBuilder(val internalText: BaseComponent, ) {
* instead it will be suggested in the command prompt * instead it will be suggested in the command prompt
*/ */
fun onClickCommand(command: String, onlySuggest: Boolean = false) { 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. * Player clicks on this text.
*/ */
fun onClickCopy(copyText: String) { 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. * Adds a line break.
*/ */
fun newLine() { fun newLine() {
siblingText.addExtra(TextComponent("\n")) siblingText = siblingText.append(newline())
} }
/** /**
@@ -141,17 +175,24 @@ class LiteralTextBuilder(val internalText: BaseComponent, ) {
newLine() newLine()
} }
fun build() = internalText.apply { fun build(): Component {
this@LiteralTextBuilder.bold?.let { isBold = it } var style = internalText.style()
this@LiteralTextBuilder.italic?.let { isItalic = it } val decorations = style.decorations().toMutableMap()
this@LiteralTextBuilder.underline?.let { isUnderlined = it } decorations[TextDecoration.BOLD] = TextDecoration.State.byBoolean(this@LiteralTextBuilder.bold)
this@LiteralTextBuilder.strikethrough?.let { isStrikethrough = it } decorations[TextDecoration.ITALIC] = TextDecoration.State.byBoolean(this@LiteralTextBuilder.italic)
this@LiteralTextBuilder.obfuscate?.let { isObfuscated = it } decorations[TextDecoration.UNDERLINED] = TextDecoration.State.byBoolean(this@LiteralTextBuilder.underline)
this@LiteralTextBuilder.color?.let { color = it } decorations[TextDecoration.STRIKETHROUGH] = TextDecoration.State.byBoolean(this@LiteralTextBuilder.strikethrough)
this@LiteralTextBuilder.clickEvent?.let { clickEvent = it } decorations[TextDecoration.OBFUSCATED] = TextDecoration.State.byBoolean(this@LiteralTextBuilder.obfuscate)
this@LiteralTextBuilder.hoverEvent?.let { hoverEvent = it } style = style.decorations(decorations)
this@LiteralTextBuilder.color?.let { style = style.color(color(it)) }
if (siblingText.extra?.isNotEmpty() == true) this@LiteralTextBuilder.clickEvent?.let { style = style.clickEvent(it) }
addExtra(siblingText) this@LiteralTextBuilder.hoverEvent?.let { style = style.hoverEvent(it) }
return if (siblingText.children().isNotEmpty()) {
internalText.append(siblingText).style(style)
} else {
internalText.style(style)
}
} }
} }

View File

@@ -1,12 +1,13 @@
@file:Suppress("MemberVisibilityCanBePrivate") @file:Suppress("MemberVisibilityCanBePrivate", "Unused")
package net.axay.kspigot.chat 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 import org.bukkit.command.CommandSender
fun CommandSender.sendMessage(vararg components: BaseComponent) { fun CommandSender.sendMessage(vararg components: Component) {
this.spigot().sendMessage(*components) this.sendMessage(Component.join(JoinConfiguration.separator(Component.newline()), *components))
} }
/** /**
@@ -18,7 +19,7 @@ fun CommandSender.sendMessage(vararg components: BaseComponent) {
inline fun CommandSender.sendText( inline fun CommandSender.sendText(
baseText: String = "", baseText: String = "",
crossinline builder: LiteralTextBuilder.() -> Unit = { } crossinline builder: LiteralTextBuilder.() -> Unit = { }
) = this.spigot().sendMessage(literalText(baseText, builder)) ) = this.sendMessage(literalText(baseText, builder))
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@Deprecated( @Deprecated(

View File

@@ -1,4 +1,4 @@
@file:Suppress("MemberVisibilityCanBePrivate") @file:Suppress("MemberVisibilityCanBePrivate", "Unused")
package net.axay.kspigot.chat.input 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.event.unregister
import net.axay.kspigot.runnables.sync import net.axay.kspigot.runnables.sync
import net.axay.kspigot.runnables.taskRunLater 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.entity.Player
import org.bukkit.event.Listener import org.bukkit.event.Listener
@@ -16,13 +18,25 @@ import org.bukkit.event.Listener
* chat input of the player as his input. * chat input of the player as his input.
*/ */
fun Player.awaitChatInput( fun Player.awaitChatInput(
question: String = "Type your input in the chat!", question: Component = text("Type your input in the chat!"),
timeoutSeconds: Int = 1 * 60, timeoutSeconds: Int = 1 * 60,
callback: (PlayerInputResult<String>) -> Unit, callback: (PlayerInputResult<Component>) -> Unit,
) { ) {
PlayerInputChat(this, callback, timeoutSeconds, question) 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<Component>) -> Unit,
) {
awaitChatInput(text(question), timeoutSeconds, callback)
}
/** /**
* Opens a book and uses the text the player inserted * Opens a book and uses the text the player inserted
* on all sites as the players' input. * on all sites as the players' input.
@@ -41,7 +55,7 @@ fun Player.awaitBookInputAsString(
*/ */
fun Player.awaitBookInputAsList( fun Player.awaitBookInputAsList(
timeoutSeconds: Int = 1 * 60, timeoutSeconds: Int = 1 * 60,
callback: (PlayerInputResult<List<String>>) -> Unit, callback: (PlayerInputResult<List<Component>>) -> Unit,
) = PlayerInputBookPaged(this, callback, timeoutSeconds).bookItemStack ) = PlayerInputBookPaged(this, callback, timeoutSeconds).bookItemStack
/** /**

View File

@@ -7,6 +7,7 @@ import net.axay.kspigot.extensions.bukkit.content
import net.axay.kspigot.items.itemStack import net.axay.kspigot.items.itemStack
import net.axay.kspigot.items.meta import net.axay.kspigot.items.meta
import net.axay.kspigot.main.PluginInstance import net.axay.kspigot.main.PluginInstance
import net.kyori.adventure.text.Component
import org.bukkit.Material import org.bukkit.Material
import org.bukkit.NamespacedKey import org.bukkit.NamespacedKey
import org.bukkit.entity.Player import org.bukkit.entity.Player
@@ -24,10 +25,10 @@ internal class PlayerInputBookComprehensive(
internal class PlayerInputBookPaged( internal class PlayerInputBookPaged(
player: Player, player: Player,
callback: (PlayerInputResult<List<String>>) -> Unit, callback: (PlayerInputResult<List<Component>>) -> Unit,
timeoutSeconds: Int, timeoutSeconds: Int,
) : PlayerInputBook<List<String>>(player, callback, timeoutSeconds) { ) : PlayerInputBook<List<Component>>(player, callback, timeoutSeconds) {
override fun loadBookContent(bookMeta: BookMeta): List<String> = bookMeta.pages override fun loadBookContent(bookMeta: BookMeta): List<Component> = bookMeta.pages()
} }
internal abstract class PlayerInputBook<T>( internal abstract class PlayerInputBook<T>(

View File

@@ -1,28 +1,28 @@
package net.axay.kspigot.chat.input.implementations 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.PlayerInput
import net.axay.kspigot.chat.input.PlayerInputResult import net.axay.kspigot.chat.input.PlayerInputResult
import net.axay.kspigot.event.listen import net.axay.kspigot.event.listen
import net.kyori.adventure.text.Component
import org.bukkit.entity.Player import org.bukkit.entity.Player
import org.bukkit.event.EventPriority import org.bukkit.event.EventPriority
import org.bukkit.event.player.AsyncPlayerChatEvent
internal class PlayerInputChat( internal class PlayerInputChat(
player: Player, player: Player,
callback: (PlayerInputResult<String>) -> Unit, callback: (PlayerInputResult<Component>) -> Unit,
timeoutSeconds: Int, timeoutSeconds: Int,
question: String, question: Component,
) : PlayerInput<String>(player, callback, timeoutSeconds) { ) : PlayerInput<Component>(player, callback, timeoutSeconds) {
init { init {
player.sendMessage("${KColors.ORANGERED}$question") player.sendMessage(question)
} }
override val inputListeners = listOf( override val inputListeners = listOf(
listen<AsyncPlayerChatEvent>(EventPriority.LOWEST) { listen<AsyncChatEvent>(EventPriority.LOWEST) {
if (it.player == player) { if (it.player == player) {
onReceive(it.message())
it.isCancelled = true it.isCancelled = true
onReceive(it.message)
} }
} }
) )

View File

@@ -1,6 +1,9 @@
@file:Suppress("Unused")
package net.axay.kspigot.extensions package net.axay.kspigot.extensions
import net.axay.kspigot.main.PluginInstance import net.axay.kspigot.main.PluginInstance
import net.kyori.adventure.text.Component.text
import org.bukkit.Bukkit import org.bukkit.Bukkit
import org.bukkit.NamespacedKey import org.bukkit.NamespacedKey
import org.bukkit.World import org.bukkit.World
@@ -36,7 +39,7 @@ val pluginManager get() = Bukkit.getPluginManager()
* @return the number of recipients * @return the number of recipients
* @see Bukkit.broadcastMessage * @see Bukkit.broadcastMessage
*/ */
fun broadcast(msg: String) = Bukkit.broadcastMessage(msg) fun broadcast(msg: String) = Bukkit.getServer().broadcast(text(msg))
/** /**
* Shortcut to get the ConsoleSender. * Shortcut to get the ConsoleSender.

View File

@@ -1,3 +1,5 @@
@file:Suppress("Unused")
package net.axay.kspigot.extensions.bukkit package net.axay.kspigot.extensions.bukkit
import net.axay.kspigot.annotations.NMS_General 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.extensions.onlinePlayers
import net.axay.kspigot.main.PluginInstance import net.axay.kspigot.main.PluginInstance
import net.axay.kspigot.pluginmessages.PluginMessageConnect import net.axay.kspigot.pluginmessages.PluginMessageConnect
import net.md_5.bungee.api.ChatMessageType
import org.bukkit.Location import org.bukkit.Location
import org.bukkit.Material import org.bukkit.Material
import org.bukkit.attribute.Attribute 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. * Sends the given [text] as an action bar message.
*/ */
fun Player.actionBar(text: String) { fun Player.actionBar(text: String) {
spigot().sendMessage(ChatMessageType.ACTION_BAR, literalText { legacyText(text) }) sendActionBar(literalText { legacyText(text) })
} }
/** /**

View File

@@ -1,11 +1,12 @@
package net.axay.kspigot.extensions.bukkit package net.axay.kspigot.extensions.bukkit
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer
import org.bukkit.inventory.meta.BookMeta import org.bukkit.inventory.meta.BookMeta
val BookMeta.content val BookMeta.content
get() = get() =
StringBuilder().apply { StringBuilder().apply {
for (it in pages) { for (it in pages().map { LegacyComponentSerializer.legacy('§').serialize(it) }) {
if (isNotEmpty()) if (isNotEmpty())
append('\n') append('\n')
append(it) append(it)

View File

@@ -1,7 +1,8 @@
@file:Suppress("MemberVisibilityCanBePrivate", "CanBeParameter") @file:Suppress("MemberVisibilityCanBePrivate", "CanBeParameter", "Unused")
package net.axay.kspigot.gui package net.axay.kspigot.gui
import net.kyori.adventure.text.Component.text
import org.bukkit.Bukkit import org.bukkit.Bukkit
import org.bukkit.event.inventory.InventoryType import org.bukkit.event.inventory.InventoryType
import org.bukkit.inventory.Inventory import org.bukkit.inventory.Inventory
@@ -27,8 +28,8 @@ class GUIType<in T : ForInventory>(
fun createBukkitInv(holder: InventoryHolder? = null, title: String? = null): Inventory { fun createBukkitInv(holder: InventoryHolder? = null, title: String? = null): Inventory {
val realTitle = title ?: "" val realTitle = title ?: ""
return when { return when {
bukkitType != null -> Bukkit.createInventory(holder, bukkitType, realTitle) bukkitType != null -> Bukkit.createInventory(holder, bukkitType, text(realTitle))
else -> Bukkit.createInventory(holder, dimensions.slotAmount, realTitle) else -> Bukkit.createInventory(holder, dimensions.slotAmount, text(realTitle))
} }
} }
} }

View File

@@ -1,5 +1,11 @@
@file:Suppress("Unused")
package net.axay.kspigot.items 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.Bukkit
import org.bukkit.Material import org.bukkit.Material
import org.bukkit.inventory.ItemFlag 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. * Sets the lore (description) of the item.
*/ */
inline fun ItemMeta.setLore(builder: ItemMetaLoreBuilder.() -> Unit) { 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. * Adds new lines to the lore (description) of the item.
*/ */
inline fun ItemMeta.addLore(builder: ItemMetaLoreBuilder.() -> Unit) { inline fun ItemMeta.addLore(builder: ItemMetaLoreBuilder.() -> Unit) {
val newLore = lore ?: mutableListOf<String>() val newLore = lore() ?: mutableListOf<Component>()
newLore.addAll(ItemMetaLoreBuilder().apply(builder).lorelist) 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. * It exists to provide overloaded operator functions.
*/ */
class ItemMetaLoreBuilder { class ItemMetaLoreBuilder {
val lorelist = ArrayList<String>() val lorelist = ArrayList<Component>()
operator fun String.unaryPlus() { operator fun Component.unaryPlus() {
lorelist += this 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. * Provides safe access to the items' displayName.
*/ */
var ItemMeta.name: String? var ItemMeta.name: Component?
get() = if (hasDisplayName()) displayName else null get() = if (hasDisplayName()) displayName() else null
set(value) = setDisplayName(if (value == null || value == "") " " else value) set(value) = displayName(value ?: Component.space())
/** /**
* Provides safe access to the items' customModelData. * Provides safe access to the items' customModelData.
@@ -118,6 +127,6 @@ var ItemMeta.customModel: Int?
/** /**
* Provides more consistent access to the items' localizedName. * Provides more consistent access to the items' localizedName.
*/ */
var ItemMeta.localName: String var ItemMeta.localName: TranslatableComponent
get() = localizedName get() = if (hasDisplayName()) displayName() as TranslatableComponent else translatable("")
set(value) = setLocalizedName(value) set(value) = displayName(value)