diff --git a/.gitignore b/.gitignore index a09754c..8d4941c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,5 @@ gradle-app.setting .gradletasknamecache # IntelliJ IDEA -/.idea/ +/.idea/* !/.idea/codeStyles \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..03529d1 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,26 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/src/main/kotlin/me/patrykanuszczyk/textcomponentserialization/configurationExtension.kt b/src/main/kotlin/me/patrykanuszczyk/textcomponentserialization/configurationExtension.kt new file mode 100644 index 0000000..b5f0cf9 --- /dev/null +++ b/src/main/kotlin/me/patrykanuszczyk/textcomponentserialization/configurationExtension.kt @@ -0,0 +1,29 @@ +package me.patrykanuszczyk.textcomponentserialization + +import net.md_5.bungee.api.chat.TextComponent +import org.bukkit.configuration.ConfigurationSection +import org.bukkit.inventory.ItemStack + +fun ConfigurationSection.getTextComponent(path: String): TextComponent? { + return deserializeTextComponent(this.get(path)) +} + +fun ConfigurationSection.setTextComponent(path: String, value: TextComponent?) { + this.set(path, value.serialize()) +} + +fun ConfigurationSection.getTextComponentList(path: String): List { + return this.getList(path).map { deserializeTextComponent(it) } +} + +fun ConfigurationSection.setTextComponentList(path: String, value: List) { + this.set(path, value.map { it.serialize() }) +} + +fun ConfigurationSection.getBook(path: String): ItemStack? { + return deserializeBook(this.get(path)) +} + +fun ConfigurationSection.setBook(path: String, value: ItemStack?) { + this.set(path, serializeBook(value)) +} \ No newline at end of file diff --git a/src/main/kotlin/me/patrykanuszczyk/textcomponentserialization/deserialize.kt b/src/main/kotlin/me/patrykanuszczyk/textcomponentserialization/deserialize.kt index f9b1ca0..a8d7c45 100644 --- a/src/main/kotlin/me/patrykanuszczyk/textcomponentserialization/deserialize.kt +++ b/src/main/kotlin/me/patrykanuszczyk/textcomponentserialization/deserialize.kt @@ -4,8 +4,15 @@ import net.md_5.bungee.api.ChatColor 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 org.bukkit.Material import org.bukkit.configuration.ConfigurationSection +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.meta.BookMeta +/** + * Deserializes a text component. + * @since 1.0 + */ fun deserializeTextComponent(obj: Any?): TextComponent? { val map = when (obj) { null -> return null @@ -20,16 +27,17 @@ fun deserializeTextComponent(obj: Any?): TextComponent? { component.text = map["text"] as String? ?: "" - component.extra = (map["extra"] as List<*>) - .map { - deserializeTextComponent( - it ?: throw NullPointerException("Extra object is null!") - ) - } + if (map["extra"] != null) + component.extra = (map["extra"] as List<*>) + .map { + deserializeTextComponent( + it ?: throw NullPointerException("Extra object is null!") + ) + } val color = deserializeChatColor(map["color"]) - component.color = color + component.setBold(map["bold"] as Boolean?) component.setItalic(map["italic"] as Boolean?) component.setUnderlined(map["underlined"] as Boolean?) @@ -46,8 +54,12 @@ fun deserializeTextComponent(obj: Any?): TextComponent? { return component } +/** + * Deserializes a chat color. + * @since 1.0 + */ fun deserializeChatColor(obj: Any?): ChatColor? { - if(obj == null) return null + if (obj == null) return null require(obj is String) { "Color has to be a string!" } val canonicalName = obj @@ -57,33 +69,93 @@ fun deserializeChatColor(obj: Any?): ChatColor? { return ChatColor.valueOf(canonicalName) } +/** + * Deserializes a click event. + * @since 1.0 + */ fun deserializeClickEvent(obj: Any?): ClickEvent? { - if (obj == null) return null - require(obj is ConfigurationSection) { "Click event has to be a configuration section or null!" } + val map = when(obj) { + null -> return null + is ConfigurationSection -> obj.getValues(false) + is Map<*,*> -> obj + else -> throw IllegalArgumentException("Couldn't deserialize click event from ${obj::class.qualifiedName}!") + }.toMapOf() - val actionString = obj.getString("action") + val actionString = map["action"] ?: throw NullPointerException("No action specified for click event!") val actionCanonicalName = actionString .replace(' ', '_') .toUpperCase() val action = ClickEvent.Action.valueOf(actionCanonicalName) - val value = obj.getString("value") + val value = map["value"] return ClickEvent(action, value) } +/** + * Deserializes a hover event. + * @since 1.0 + */ fun deserializeHoverEvent(obj: Any?): HoverEvent? { - if(obj == null) return null - require(obj is ConfigurationSection) { "Hover event has to be a configuration section or null!" } + val map = when(obj) { + null -> return null + is ConfigurationSection -> obj.getValues(true) + is Map<*,*> -> obj + else -> throw IllegalArgumentException("Couldn't deserialize click event from ${obj::class.qualifiedName}!") + }.toMapOf() - val actionString = obj.getString("action") + val actionString = map["action"] as String val actionCanonicalName = actionString .replace(' ', '_') .toUpperCase() val action = HoverEvent.Action.valueOf(actionCanonicalName) val value = - deserializeTextComponent(obj.get("value")) + deserializeTextComponent(map["value"]) return HoverEvent(action, arrayOf(value)) +} + +/** + * Deserializes a book. + * @since 1.0 + */ +fun deserializeBook(obj: Any?): ItemStack? { + val map = when (obj) { + null -> return null + is ConfigurationSection -> obj.getValues(true) + is Map<*, *> -> obj.toMapOf() + else -> throw IllegalArgumentException("Couldn't deserialize text component from ${obj::class.qualifiedName}!") + } + + val stack = ItemStack(Material.WRITTEN_BOOK) + + val meta = stack.itemMeta as BookMeta + + meta.title = map["title"] as String? + meta.author = map["author"] as String? + meta.generation = deserializeBookGeneration(map["generation"]) + + meta.spigot().pages = (map["pages"] as List<*>).map { + arrayOf(deserializeTextComponent(it)) + } + + stack.itemMeta = meta + + return stack +} + +/** + * Deserializes book generation. + * @since 1.0 + */ +fun deserializeBookGeneration(obj: Any?): BookMeta.Generation? { + if (obj == null) return null + require(obj is String) { "Book generation has to be a string!" } + + val canonicalName = obj + .replace(' ', '_') + .toUpperCase() + + return BookMeta.Generation.valueOf(canonicalName) } \ No newline at end of file diff --git a/src/main/kotlin/me/patrykanuszczyk/textcomponentserialization/format.kt b/src/main/kotlin/me/patrykanuszczyk/textcomponentserialization/format.kt new file mode 100644 index 0000000..0a50b4b --- /dev/null +++ b/src/main/kotlin/me/patrykanuszczyk/textcomponentserialization/format.kt @@ -0,0 +1,55 @@ +package me.patrykanuszczyk.textcomponentserialization + +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 org.bukkit.ChatColor +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.meta.BookMeta + +fun formatString(string: String, placeholders: Map? = null): String { + var stringFormatted = string + placeholders?.forEach { (key, value) -> + stringFormatted = stringFormatted.replace("{$key}", value, true) + } + return ChatColor.translateAlternateColorCodes('&', stringFormatted) +} + +fun TextComponent.format(placeholders: Map? = null): TextComponent { + val newComponent = TextComponent(this) + if (text != null) newComponent.text = formatString(text, placeholders) + //extra?.forEach { newComponent.addExtra((it as TextComponent).format(placeholders)) } + if(extra != null) + newComponent.extra = extra.map { (it as TextComponent).format(placeholders) } + if (insertion != null) newComponent.insertion = formatString(insertion, placeholders) + if (clickEvent != null) + newComponent.clickEvent = ClickEvent(clickEvent.action, formatString(clickEvent.value, placeholders)) + if (hoverEvent != null) + newComponent.hoverEvent = HoverEvent(hoverEvent.action, hoverEvent.value.map { + (it as TextComponent).format(placeholders) + }.toTypedArray()) + return newComponent +} + +fun formatBook(book: ItemStack?, placeholders: Map? = null): ItemStack? { + if (book == null) return null + + val oldMeta = book.itemMeta as BookMeta + + val newBook = ItemStack(book) + + val meta = newBook.itemMeta as BookMeta + meta.title = formatString(oldMeta.title, placeholders) + meta.author = formatString(oldMeta.author, placeholders) + meta.generation = oldMeta.generation + + meta.spigot().addPage(*oldMeta.spigot().pages.map { page -> + page.map { + (it as TextComponent).format(placeholders) + }.toTypedArray() + }.toTypedArray()) + + newBook.itemMeta = meta + + return newBook +} \ No newline at end of file diff --git a/src/main/kotlin/me/patrykanuszczyk/textcomponentserialization/isEmpty.kt b/src/main/kotlin/me/patrykanuszczyk/textcomponentserialization/isEmpty.kt new file mode 100644 index 0000000..d664614 --- /dev/null +++ b/src/main/kotlin/me/patrykanuszczyk/textcomponentserialization/isEmpty.kt @@ -0,0 +1,12 @@ +package me.patrykanuszczyk.textcomponentserialization + +import net.md_5.bungee.api.chat.TextComponent + +val TextComponent.isEmpty: Boolean + get() { + if(!text.isNullOrEmpty()) return false + + return extra.all { it is TextComponent && it.isEmpty } + } + +val TextComponent.isNotEmpty: Boolean get() = !isEmpty \ No newline at end of file diff --git a/src/main/kotlin/me/patrykanuszczyk/textcomponentserialization/mapConvert.kt b/src/main/kotlin/me/patrykanuszczyk/textcomponentserialization/mapConvert.kt index 20a716b..c9f8019 100644 --- a/src/main/kotlin/me/patrykanuszczyk/textcomponentserialization/mapConvert.kt +++ b/src/main/kotlin/me/patrykanuszczyk/textcomponentserialization/mapConvert.kt @@ -1,7 +1,6 @@ package me.patrykanuszczyk.textcomponentserialization -@JvmSynthetic -internal inline fun Map<*, *>.toMapOf(): MutableMap { +inline fun Map<*, *>.toMapOf(): MutableMap { val map = mutableMapOf() for ((key, value) in this) { diff --git a/src/main/kotlin/me/patrykanuszczyk/textcomponentserialization/serialize.kt b/src/main/kotlin/me/patrykanuszczyk/textcomponentserialization/serialize.kt index a804f24..a00b257 100644 --- a/src/main/kotlin/me/patrykanuszczyk/textcomponentserialization/serialize.kt +++ b/src/main/kotlin/me/patrykanuszczyk/textcomponentserialization/serialize.kt @@ -4,42 +4,49 @@ import net.md_5.bungee.api.ChatColor 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 org.bukkit.Material +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.meta.BookMeta + +/** + * Serializes a text component. + * @since 1.0 + */ @JvmName("serializeTextComponent") fun TextComponent?.serialize(): Any? { - if(this == null) return null + if (this == null) return null val map = mutableMapOf() if (!text.isNullOrEmpty()) - map += "text" to text + map["text"] = text if (!extra.isNullOrEmpty()) - map += "extra" to extra.map { (it as TextComponent).serialize() } + map["extra"] = extra.map { (it as TextComponent).serialize() } if (colorRaw != null) - map += "color" to serializeChatColor(colorRaw)!! + map["color"] = serializeChatColor(colorRaw)!! if (isBoldRaw != null) - map += "bold" to isBoldRaw + map["bold"] = isBoldRaw if (isItalicRaw != null) - map += "italic" to isItalicRaw + map["italic"] = isItalicRaw if (isUnderlinedRaw != null) - map += "underlined" to isUnderlinedRaw + map["underlined"] = isUnderlinedRaw if (isStrikethroughRaw != null) - map += "strikethrough" to isStrikethroughRaw + map["strikethrough"] = isStrikethroughRaw if (isObfuscatedRaw != null) - map += "obfuscated" to isObfuscatedRaw + map["obfuscated"] = isObfuscatedRaw if (insertion != null) - map += "insertion" to insertion + map["insertion"] = insertion if (clickEvent != null) { - map += "clickEvent" to serializeClickEvent( + map["clickEvent"] = serializeClickEvent( clickEvent )!! } if (hoverEvent != null) { - map += "hoverEvent" to serializeHoverEvent( + map["hoverEvent"] = serializeHoverEvent( hoverEvent )!! } @@ -49,23 +56,31 @@ fun TextComponent?.serialize(): Any? { "text" -> return map.getValue("text") "extra" -> return map.getValue("extra") } - } else if(map.keys.isEmpty()) { + } else if (map.keys.isEmpty()) { return "" } return map } +/** + * Serializes a chat color. + * @since 1.0 + */ fun serializeChatColor(color: ChatColor?): Any? { - if(color == null) return null + if (color == null) return null return color.name .replace('_', ' ') .toLowerCase() } +/** + * Serializes a click event. + * @since 1.0 + */ fun serializeClickEvent(event: ClickEvent?): Any? { - if(event == null) return null + if (event == null) return null val action = event.action.name .replace('_', ' ') @@ -79,8 +94,12 @@ fun serializeClickEvent(event: ClickEvent?): Any? { ) } +/** + * Serializes a hover event. + * @since 1.0 + */ fun serializeHoverEvent(event: HoverEvent?): Any? { - if(event == null) return null + if (event == null) return null val action = event.action.name .replace('_', ' ') @@ -88,7 +107,7 @@ fun serializeHoverEvent(event: HoverEvent?): Any? { val values = event.value - val component = (if(values.size == 1) + val component = (if (values.size == 1) values[0] else TextComponent(*values)) as TextComponent @@ -99,4 +118,45 @@ fun serializeHoverEvent(event: HoverEvent?): Any? { "action" to action, "value" to value ) +} + +/** + * Serializes a book item stack. + * @since 1.0 + */ +fun serializeBook(stack: ItemStack?): Any? { + if (stack == null) return null + require(stack.type == Material.WRITTEN_BOOK) { "Book has to be of type WRITTEN_BOOK." } + + val bookMeta = stack.itemMeta as BookMeta + val map = mutableMapOf() + + map["title"] = bookMeta.title + map["author"] = bookMeta.author + map["generation"] = serializeBookGeneration(bookMeta.generation)!! + + map["pages"] = bookMeta.spigot().pages + .map { + if (it.size == 1) + it[0] as TextComponent + else + TextComponent(*it) + } + .map { + it.serialize() + } + + return map +} + +/** + * Serializes book generation. + * @since 1.0 + */ +fun serializeBookGeneration(generation: BookMeta.Generation?): Any? { + if (generation == null) return null + + return generation.name + .replace('_', ' ') + .toLowerCase() } \ No newline at end of file