From c6de9c32c43bb036c88f9d85f1d9c089b291faab Mon Sep 17 00:00:00 2001 From: xbaimiao <3104026189@qq.com> Date: Mon, 8 Apr 2024 10:57:45 +0800 Subject: [PATCH] =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E7=BC=93=E5=AD=98=20=E4=BC=98=E5=8C=96=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 3 + gradle.properties | 2 +- .../com/github/xbaimiao/shoppro/ShopPro.kt | 37 +- .../shoppro/core/database/Database.kt | 6 +- .../shoppro/core/database/SqlDatabase.kt | 116 ++++-- .../shoppro/core/item/KetherCondition.kt | 4 +- .../shoppro/core/item/impl/HeadShopItem.kt | 10 +- .../shoppro/core/item/impl/ItemImpl.kt | 5 +- .../core/item/impl/ItemsAdderShopItem.kt | 8 +- .../shoppro/core/item/impl/NeigeItem.kt | 11 +- .../shoppro/core/item/impl/VanillaShopItem.kt | 6 +- .../xbaimiao/shoppro/core/shop/ShopImpl.kt | 1 + .../xbaimiao/shoppro/core/vault/VaultImpl.kt | 48 ++- .../xbaimiao/shoppro/util/ItemBuilder.kt | 363 ++++++++++++++++++ .../xbaimiao/shoppro/util/ItemFlag.java | 11 + 15 files changed, 559 insertions(+), 72 deletions(-) create mode 100644 src/main/kotlin/com/github/xbaimiao/shoppro/util/ItemBuilder.kt create mode 100644 src/main/kotlin/com/github/xbaimiao/shoppro/util/ItemFlag.java diff --git a/build.gradle.kts b/build.gradle.kts index d33c137..e6d3c1a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -19,6 +19,8 @@ taboolib { contributors { name("xbaimiao") } + val bukkitNodes = bukkitNodes as MutableMap + bukkitNodes["update-info"] = "数据库添加缓存 优化性能" } env { install(NMS_UTIL, UI) @@ -39,6 +41,7 @@ repositories { } dependencies { + compileOnly("com.mojang:authlib:1.5.25") compileOnly("ink.ptms:nms-all:1.0.0") compileOnly("public:MythicMobs:4.14.1") compileOnly("public:papi:1.0.0") diff --git a/gradle.properties b/gradle.properties index 7a450c7..ff00507 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ group=com.github.xbaimiao.shoppro -version=1.2.6-SNAPSHOT +version=1.2.6 diff --git a/src/main/kotlin/com/github/xbaimiao/shoppro/ShopPro.kt b/src/main/kotlin/com/github/xbaimiao/shoppro/ShopPro.kt index 43010f2..021972a 100644 --- a/src/main/kotlin/com/github/xbaimiao/shoppro/ShopPro.kt +++ b/src/main/kotlin/com/github/xbaimiao/shoppro/ShopPro.kt @@ -6,9 +6,14 @@ import com.github.xbaimiao.shoppro.core.database.SQLiteDatabase import com.github.xbaimiao.shoppro.core.item.ItemLoaderManager import com.github.xbaimiao.shoppro.core.shop.ShopManager import com.github.xbaimiao.shoppro.core.vault.DiyCurrency +import com.github.xbaimiao.shoppro.core.vault.VaultImpl import com.xbaimiao.ktor.KtorPluginsBukkit import com.xbaimiao.ktor.KtorStat +import org.bukkit.Bukkit +import org.bukkit.event.player.PlayerJoinEvent +import org.bukkit.event.player.PlayerQuitEvent import taboolib.common.platform.Plugin +import taboolib.common.platform.event.SubscribeEvent import taboolib.common.platform.function.info import taboolib.module.configuration.Config import taboolib.module.configuration.Configuration @@ -33,7 +38,13 @@ object ShopPro : Plugin(), KtorStat { } ShopManager.load() DiyCurrency.load() + VaultImpl.startTask() + database = if (config.getBoolean("mysql.enable")) MysqlDatabase(config) else SQLiteDatabase() + + Bukkit.getOnlinePlayers().forEach { + database.loadPlayerData(it) + } } fun reload() { @@ -41,7 +52,31 @@ object ShopPro : Plugin(), KtorStat { ShopManager.shops.clear() ShopManager.load() DiyCurrency.load() + + Bukkit.getOnlinePlayers().forEach { + database.releasePlayerData(it) + } + database = if (config.getBoolean("mysql.enable")) MysqlDatabase(config) else SQLiteDatabase() + Bukkit.getOnlinePlayers().forEach { + database.loadPlayerData(it) + } } -} \ No newline at end of file + override fun onDisable() { + VaultImpl.flush() + } + + @SubscribeEvent + fun join(event: PlayerJoinEvent) { + val player = event.player + database.loadPlayerData(player) + } + + @SubscribeEvent + fun quit(event: PlayerQuitEvent) { + val player = event.player + database.releasePlayerData(player) + } + +} diff --git a/src/main/kotlin/com/github/xbaimiao/shoppro/core/database/Database.kt b/src/main/kotlin/com/github/xbaimiao/shoppro/core/database/Database.kt index 244c5ab..5f95047 100644 --- a/src/main/kotlin/com/github/xbaimiao/shoppro/core/database/Database.kt +++ b/src/main/kotlin/com/github/xbaimiao/shoppro/core/database/Database.kt @@ -29,4 +29,8 @@ interface Database { fun reset() -} \ No newline at end of file + fun loadPlayerData(player: Player) + + fun releasePlayerData(player: Player) + +} diff --git a/src/main/kotlin/com/github/xbaimiao/shoppro/core/database/SqlDatabase.kt b/src/main/kotlin/com/github/xbaimiao/shoppro/core/database/SqlDatabase.kt index a5302fb..9f665f0 100644 --- a/src/main/kotlin/com/github/xbaimiao/shoppro/core/database/SqlDatabase.kt +++ b/src/main/kotlin/com/github/xbaimiao/shoppro/core/database/SqlDatabase.kt @@ -3,6 +3,7 @@ package com.github.xbaimiao.shoppro.core.database import com.github.xbaimiao.shoppro.core.item.Item import com.github.xbaimiao.shoppro.core.item.impl.ItemsAdderShopItem import org.bukkit.entity.Player +import taboolib.common.platform.function.submitAsync import taboolib.module.database.Host import taboolib.module.database.Table import javax.sql.DataSource @@ -25,6 +26,8 @@ abstract class SqlDatabase : Database { // ServerTable 无 User val playerLine = "user" + private val playerAlreadyDataCache = HashMap>() + override fun reset() { serverTable.workspace(dataSource) { executeUpdate("DELETE FROM $serverTableName;").run() @@ -50,8 +53,20 @@ abstract class SqlDatabase : Database { } ?: LimitData.ofNull() } + private fun Item.toCacheKey(): String { + val item = this + return "${item.key}-${item.material}-${if (item is ItemsAdderShopItem) item.custom else 0}" + } + override fun getPlayerAlreadyData(player: Player, item: Item): LimitData { - return playerTable.workspace(dataSource) { + val cache = playerAlreadyDataCache[player.name] ?: error("玩家 ${player.name} 数据缓存未加载") + + val cacheKey = item.toCacheKey() + if (cache.containsKey(cacheKey)) { + return cache[cacheKey]!! + } + + val databaseLimitData = playerTable.workspace(dataSource) { select { where { playerLine eq player.uniqueId.toString() @@ -65,6 +80,57 @@ abstract class SqlDatabase : Database { }.firstOrNull { LimitData.formString(this.getString(dataLine)) } ?: LimitData.ofNull() + + cache[cacheKey] = databaseLimitData + return databaseLimitData + } + + override fun setPlayerAlreadyData(player: Player, item: Item, amount: LimitData) { + val cache = playerAlreadyDataCache[player.name] ?: error("玩家 ${player.name} 数据缓存未加载") + val cacheKey = item.toCacheKey() + cache[cacheKey] = amount + + submitAsync { + if (playerTable.workspace(dataSource) { + select { + where { + playerLine eq player.uniqueId.toString() + itemKeyLine eq item.key.toString() + itemMaterialLine eq item.material.toString() + if (item is ItemsAdderShopItem) { + itemCustomLine eq item.custom + } + } + } + }.find()) { + playerTable.workspace(dataSource) { + update { + where { + playerLine eq player.uniqueId.toString() + itemKeyLine eq item.key.toString() + itemMaterialLine eq item.material.toString() + if (item is ItemsAdderShopItem) { + itemCustomLine eq item.custom + } + } + set(dataLine, amount.toString()) + } + }.run() + } else { + val custom = if (item is ItemsAdderShopItem) item.custom else 0 + playerTable.workspace(dataSource) { + insert { + value( + item.key.toString(), + item.material.toString(), + custom, + amount.toString(), + player.uniqueId.toString() + ) + } + }.run() + } + } } override fun addAmount(item: Item, player: Player, amount: LimitData) { @@ -106,46 +172,12 @@ abstract class SqlDatabase : Database { } } - override fun setPlayerAlreadyData(player: Player, item: Item, amount: LimitData) { - if (playerTable.workspace(dataSource) { - select { - where { - playerLine eq player.uniqueId.toString() - itemKeyLine eq item.key.toString() - itemMaterialLine eq item.material.toString() - if (item is ItemsAdderShopItem) { - itemCustomLine eq item.custom - } - } - } - }.find()) { - playerTable.workspace(dataSource) { - update { - where { - playerLine eq player.uniqueId.toString() - itemKeyLine eq item.key.toString() - itemMaterialLine eq item.material.toString() - if (item is ItemsAdderShopItem) { - itemCustomLine eq item.custom - } - } - set(dataLine, amount.toString()) - } - }.run() - } else { - val custom = if (item is ItemsAdderShopItem) item.custom else 0 - playerTable.workspace(dataSource) { - insert { - value( - item.key.toString(), - item.material.toString(), - custom, - amount.toString(), - player.uniqueId.toString() - ) - } - }.run() - } + override fun loadPlayerData(player: Player) { + playerAlreadyDataCache[player.name] = HashMap() } -} \ No newline at end of file + override fun releasePlayerData(player: Player) { + playerAlreadyDataCache.remove(player.name) + } + +} diff --git a/src/main/kotlin/com/github/xbaimiao/shoppro/core/item/KetherCondition.kt b/src/main/kotlin/com/github/xbaimiao/shoppro/core/item/KetherCondition.kt index c320c57..1609518 100644 --- a/src/main/kotlin/com/github/xbaimiao/shoppro/core/item/KetherCondition.kt +++ b/src/main/kotlin/com/github/xbaimiao/shoppro/core/item/KetherCondition.kt @@ -29,7 +29,7 @@ interface KetherCondition { if (conditionIcon == null || conditionName == null) { return ItemStack(Material.AIR) } - return taboolib.platform.util.buildItem(conditionIcon!!) { + return com.github.xbaimiao.shoppro.util.buildItem(conditionIcon!!) { name = conditionName!!.colored().replacePapi(player) conditionLore.forEach { lore += it.colored().replacePapi(player) @@ -37,4 +37,4 @@ interface KetherCondition { } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/github/xbaimiao/shoppro/core/item/impl/HeadShopItem.kt b/src/main/kotlin/com/github/xbaimiao/shoppro/core/item/impl/HeadShopItem.kt index 4274d67..6dc9cdc 100644 --- a/src/main/kotlin/com/github/xbaimiao/shoppro/core/item/impl/HeadShopItem.kt +++ b/src/main/kotlin/com/github/xbaimiao/shoppro/core/item/impl/HeadShopItem.kt @@ -9,9 +9,7 @@ import org.bukkit.configuration.ConfigurationSection import org.bukkit.entity.Player import org.bukkit.inventory.ItemStack import taboolib.library.xseries.XMaterial -import taboolib.library.xseries.XSkull import taboolib.library.xseries.parseToMaterial -import taboolib.platform.util.ItemBuilder import java.util.* class HeadShopItem( @@ -29,8 +27,8 @@ class HeadShopItem( } override fun vanillaItem(player: Player): ItemStack { - return taboolib.platform.util.buildItem(material) { - skullTexture = XSkull.SkullTexture(head, UUID.randomUUID()) + return com.github.xbaimiao.shoppro.util.buildItem(material) { + skullTexture = com.github.xbaimiao.shoppro.util.ItemBuilder.SkullTexture(head, UUID.randomUUID()) } } @@ -39,10 +37,10 @@ class HeadShopItem( } override fun buildItem(player: Player): ItemStack { - return taboolib.platform.util.buildItem(material) { + return com.github.xbaimiao.shoppro.util.buildItem(material) { this.name = this@HeadShopItem.name this.lore.addAll(this@HeadShopItem.lore) - skullTexture = XSkull.SkullTexture(head, UUID.randomUUID()) + skullTexture = com.github.xbaimiao.shoppro.util.ItemBuilder.SkullTexture(head, UUID.randomUUID()) } } diff --git a/src/main/kotlin/com/github/xbaimiao/shoppro/core/item/impl/ItemImpl.kt b/src/main/kotlin/com/github/xbaimiao/shoppro/core/item/impl/ItemImpl.kt index d261804..0bea305 100644 --- a/src/main/kotlin/com/github/xbaimiao/shoppro/core/item/impl/ItemImpl.kt +++ b/src/main/kotlin/com/github/xbaimiao/shoppro/core/item/impl/ItemImpl.kt @@ -9,7 +9,6 @@ import org.bukkit.configuration.ConfigurationSection import org.bukkit.entity.Player import org.bukkit.inventory.ItemStack import taboolib.library.xseries.XMaterial -import taboolib.library.xseries.XSkull import taboolib.module.chat.colored import taboolib.platform.util.modifyLore import java.util.* @@ -49,12 +48,12 @@ class ItemImpl( } override fun buildItem(player: Player): ItemStack { - return taboolib.platform.util.buildItem(material) { + return com.github.xbaimiao.shoppro.util.buildItem(material) { this.name = this@ItemImpl.name this.lore.addAll(this@ItemImpl.lore) custom?.let { customModelData = it } head?.let { - skullTexture = XSkull.SkullTexture(it, UUID.randomUUID()) + skullTexture = com.github.xbaimiao.shoppro.util.ItemBuilder.SkullTexture(it, UUID.randomUUID()) } } } diff --git a/src/main/kotlin/com/github/xbaimiao/shoppro/core/item/impl/ItemsAdderShopItem.kt b/src/main/kotlin/com/github/xbaimiao/shoppro/core/item/impl/ItemsAdderShopItem.kt index 6450194..57b6fd4 100644 --- a/src/main/kotlin/com/github/xbaimiao/shoppro/core/item/impl/ItemsAdderShopItem.kt +++ b/src/main/kotlin/com/github/xbaimiao/shoppro/core/item/impl/ItemsAdderShopItem.kt @@ -15,7 +15,7 @@ class ItemsAdderShopItem( itemSetting: ItemSetting ) : ShopItem(itemSetting) { - final override val material: Material + override val material: Material val custom: Int @@ -34,13 +34,13 @@ class ItemsAdderShopItem( } override fun vanillaItem(player: Player): ItemStack { - return taboolib.platform.util.buildItem(material) { + return com.github.xbaimiao.shoppro.util.buildItem(material) { this.customModelData = custom } } override fun buildItem(player: Player): ItemStack { - return taboolib.platform.util.buildItem(material) { + return com.github.xbaimiao.shoppro.util.buildItem(material) { this.name = this@ItemsAdderShopItem.name this.lore.addAll(this@ItemsAdderShopItem.lore) this.customModelData = custom @@ -67,4 +67,4 @@ class ItemsAdderShopItem( } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/github/xbaimiao/shoppro/core/item/impl/NeigeItem.kt b/src/main/kotlin/com/github/xbaimiao/shoppro/core/item/impl/NeigeItem.kt index 74f3cea..4ee99c1 100644 --- a/src/main/kotlin/com/github/xbaimiao/shoppro/core/item/impl/NeigeItem.kt +++ b/src/main/kotlin/com/github/xbaimiao/shoppro/core/item/impl/NeigeItem.kt @@ -3,7 +3,6 @@ package com.github.xbaimiao.shoppro.core.item.impl import com.github.xbaimiao.shoppro.core.item.Item import com.github.xbaimiao.shoppro.core.item.ItemLoader import com.github.xbaimiao.shoppro.core.shop.Shop -import ink.ptms.zaphkiel.Zaphkiel import org.bukkit.Bukkit import org.bukkit.Material import org.bukkit.configuration.ConfigurationSection @@ -19,14 +18,14 @@ import pers.neige.neigeitems.manager.ItemManager class NeigeItem( private val neigeItem: ItemGenerator, itemSetting: ItemSetting, - private val neigeId :String + private val neigeId: String ) : VanillaShopItem(itemSetting) { override val material: Material get() = neigeItem.staticItemStack.type override fun vanillaItem(player: Player): ItemStack { - return ItemManager.getItemStack(neigeId,player)!! + return ItemManager.getItemStack(neigeId, player)!! } override fun equal(itemStack: ItemStack): Boolean { @@ -42,8 +41,8 @@ class NeigeItem( error("NeigeItems Plugin not found") } val neigeId = section.getString("material")!!.substring(prefix.length + 1) - val item = ItemManager.getItem(neigeId)?: error("NeigeItems $neigeId not found") - return NeigeItem(item, section.toItemSetting(char, shop),neigeId) + val item = ItemManager.getItem(neigeId) ?: error("NeigeItems $neigeId not found") + return NeigeItem(item, section.toItemSetting(char, shop), neigeId) } override fun parseToMaterial(section: ConfigurationSection): Material { @@ -52,4 +51,4 @@ class NeigeItem( } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/github/xbaimiao/shoppro/core/item/impl/VanillaShopItem.kt b/src/main/kotlin/com/github/xbaimiao/shoppro/core/item/impl/VanillaShopItem.kt index a68e0e0..7edf813 100644 --- a/src/main/kotlin/com/github/xbaimiao/shoppro/core/item/impl/VanillaShopItem.kt +++ b/src/main/kotlin/com/github/xbaimiao/shoppro/core/item/impl/VanillaShopItem.kt @@ -12,7 +12,7 @@ import taboolib.platform.util.hasLore open class VanillaShopItem(itemSetting: ItemSetting) : ShopItem(itemSetting) { override fun vanillaItem(player: Player): ItemStack { - return taboolib.platform.util.buildItem(material) + return com.github.xbaimiao.shoppro.util.buildItem(material) } override fun equal(itemStack: ItemStack): Boolean { @@ -20,7 +20,7 @@ open class VanillaShopItem(itemSetting: ItemSetting) : ShopItem(itemSetting) { } override fun buildItem(player: Player): ItemStack { - return taboolib.platform.util.buildItem(material) { + return com.github.xbaimiao.shoppro.util.buildItem(material) { this.name = this@VanillaShopItem.name this.lore.addAll(this@VanillaShopItem.lore) } @@ -35,4 +35,4 @@ open class VanillaShopItem(itemSetting: ItemSetting) : ShopItem(itemSetting) { } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/github/xbaimiao/shoppro/core/shop/ShopImpl.kt b/src/main/kotlin/com/github/xbaimiao/shoppro/core/shop/ShopImpl.kt index b40f0b9..d6b7d98 100644 --- a/src/main/kotlin/com/github/xbaimiao/shoppro/core/shop/ShopImpl.kt +++ b/src/main/kotlin/com/github/xbaimiao/shoppro/core/shop/ShopImpl.kt @@ -18,6 +18,7 @@ import taboolib.module.chat.colored import taboolib.module.ui.ClickType import taboolib.module.ui.openMenu import taboolib.module.ui.type.Basic +import taboolib.module.ui.type.Chest import taboolib.platform.util.giveItem import taboolib.platform.util.hasItem import taboolib.platform.util.sendLang diff --git a/src/main/kotlin/com/github/xbaimiao/shoppro/core/vault/VaultImpl.kt b/src/main/kotlin/com/github/xbaimiao/shoppro/core/vault/VaultImpl.kt index de23d07..2c9d734 100644 --- a/src/main/kotlin/com/github/xbaimiao/shoppro/core/vault/VaultImpl.kt +++ b/src/main/kotlin/com/github/xbaimiao/shoppro/core/vault/VaultImpl.kt @@ -2,16 +2,59 @@ package com.github.xbaimiao.shoppro.core.vault import com.github.xbaimiao.shoppro.util.Util.format import org.bukkit.entity.Player +import org.bukkit.event.player.PlayerQuitEvent +import taboolib.common.platform.event.EventPriority +import taboolib.common.platform.event.SubscribeEvent +import taboolib.common.platform.function.submit import taboolib.platform.compat.VaultService +import java.util.concurrent.ConcurrentLinkedQueue object VaultImpl : Currency { + private val queue = ConcurrentLinkedQueue() + + private data class ValueQueue(val player: Player, val money: Double) + + fun startTask() { + submit(period = 20) { + flush() + } + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + fun quit(event: PlayerQuitEvent) { + val iterator = queue.iterator() + var amount = 0.0 + while (iterator.hasNext()) { + val queue = iterator.next() + if (queue.player.uniqueId == event.player.uniqueId) { + amount += queue.money + iterator.remove() + } + } + if (amount > 0) { + realGiveMoney(event.player, amount) + } + } + + fun flush() { + queue.groupBy { it.player }.forEach { (player, valueQueues) -> + val amount = valueQueues.sumOf { it.money } + realGiveMoney(player, amount) + } + queue.clear() + } + + private fun realGiveMoney(player: Player, double: Double) { + VaultService.economy!!.depositPlayer(player, double.format()) + } + override fun hasMoney(player: Player, double: Double): Boolean { return getMoney(player) >= double.format() } override fun giveMoney(player: Player, double: Double) { - VaultService.economy!!.depositPlayer(player, double.format()) + queue.offer(ValueQueue(player, double)) } override fun takeMoney(player: Player, double: Double): Boolean { @@ -26,5 +69,4 @@ object VaultImpl : Currency { return VaultService.economy!!.getBalance(player).format() } - -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/github/xbaimiao/shoppro/util/ItemBuilder.kt b/src/main/kotlin/com/github/xbaimiao/shoppro/util/ItemBuilder.kt new file mode 100644 index 0000000..e6e98a1 --- /dev/null +++ b/src/main/kotlin/com/github/xbaimiao/shoppro/util/ItemBuilder.kt @@ -0,0 +1,363 @@ +package com.github.xbaimiao.shoppro.util + +import com.mojang.authlib.GameProfile +import com.mojang.authlib.properties.Property +import org.bukkit.ChatColor +import org.bukkit.Color +import org.bukkit.Material +import org.bukkit.block.banner.Pattern +import org.bukkit.enchantments.Enchantment +import org.bukkit.entity.EntityType +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.meta.* +import org.bukkit.potion.PotionData +import org.bukkit.potion.PotionEffect +import taboolib.library.reflex.Reflex.Companion.getProperty +import taboolib.library.reflex.Reflex.Companion.invokeMethod +import taboolib.library.reflex.Reflex.Companion.setProperty +import taboolib.library.xseries.XMaterial +import taboolib.module.chat.colored +import taboolib.platform.util.isAir +import java.util.* + + +/** + * 通过现有物品构建新的物品 + * + * @param itemStack 原始物品 + * @param builder 构建器 + * @return 新的物品 + * @throws IllegalArgumentException 如果物品为空气 + */ +fun buildItem(itemStack: ItemStack, builder: ItemBuilder.() -> Unit = {}): ItemStack { + if (itemStack.isAir()) { + error("air") + } + return ItemBuilder(itemStack).also(builder).build() +} + +/** + * 通过 [Material] 材质构建新的物品 + * + * @param material 材质 + * @param builder 构建器 + * @return 新的物品 + * @throws IllegalArgumentException 如果材质为空气 + */ +fun buildItem(material: Material, builder: ItemBuilder.() -> Unit = {}): ItemStack { + // 在低版本, 并没有 Material.isAir() 方法 + if (material == Material.AIR) { + error("air") + } + return ItemBuilder(material).also(builder).build() +} + +/** + * 通过 [XMaterial] 材质构建新的物品 + * + * @param material 材质 + * @param builder 构建器 + * @return 新的物品 + * @throws IllegalArgumentException 如果材质为空气 + */ +fun buildItem(material: XMaterial, builder: ItemBuilder.() -> Unit = {}): ItemStack { + // 在低版本, 并没有 Material.isAir() 方法 + if (material == XMaterial.AIR) { + error("air") + } + return ItemBuilder(material.parseMaterial() ?: error("material.parseMaterial() is null")).also(builder).build() +} + +open class ItemBuilder { + + class SkullTexture(val textures: String, val uuid: UUID? = UUID.randomUUID()) + + /** + * 物品材质 + */ + var material: Material + + /** + * 数量 + */ + var amount = 1 + + /** + * 附加值(损伤值) + */ + var damage = 0 + + /** + * 展示名称 + */ + var name: String? = null + + /** + * 描述 + */ + val lore = ArrayList() + + /** + * 标签 + */ + val flags = ArrayList() + + /** + * 附魔 + */ + val enchants = HashMap() + + /** + * 旗帜花纹 + */ + val patterns = ArrayList() + + /** + * 颜色 + */ + var color: Color? = null + + /** + * 药水效果 + */ + val potions = ArrayList() + + /** + * 基础药水效果 + */ + var potionData: PotionData? = null + + /** + * 生物类型 + */ + var spawnType: EntityType? = null + + /** + * 头颅信息 + */ + var skullOwner: String? = null + + /** + * 头颅材质信息 + */ + var skullTexture: SkullTexture? = null + + /** + * 无法破坏 + */ + var isUnbreakable = false + + /** + * CustomModelData + */ + var customModelData = -1 + + /** + * 原始数据 + * 尝试修复自定义 nbt 失效的问题 + */ + var originMeta: ItemMeta? = null + + /** + * 当构建完成时,做出最后修改 + */ + var finishing: (ItemStack) -> Unit = {} + + + /** + * 使其发光 + */ + fun shiny() { + flags += ItemFlag.HIDE_ENCHANTS + enchants[Enchantment.LURE] = 1 + } + + /** + * 隐藏所有附加信息(赋予所有 ItemFlag) + */ + fun hideAll() { + flags.addAll(ItemFlag.values()) + } + + open fun getSkinValue(skull: ItemMeta): SkullTexture? { + var profile: GameProfile? = null + try { + profile = skull.getProperty("profile") + } catch (ignored: Exception) { + } + if (profile != null && !profile.properties["textures"].isEmpty()) { + for (property in profile.properties["textures"]) { + if (property.value.isNotEmpty()) { + return SkullTexture(property.value, profile.id) + } + } + } + return null + } + + /** + * 上色 + */ + fun colored() { + if (name != null) { + name = try { + name!!.colored() + } catch (ex: NoClassDefFoundError) { + ChatColor.translateAlternateColorCodes('&', name!!) + } + } + if (lore.isNotEmpty()) { + val newLore = try { + lore.colored() + } catch (ex: NoClassDefFoundError) { + lore.map { ChatColor.translateAlternateColorCodes('&', it) } + } + lore.clear() + lore += newLore + } + } + + /** + * 构建物品 + */ + open fun build(): ItemStack { + val itemStack = ItemStack(material) + itemStack.amount = amount + if (damage != 0) { + itemStack.durability = damage.toShort() + } + val itemMeta = originMeta ?: itemStack.itemMeta ?: return itemStack + itemMeta.setDisplayName(name) + itemMeta.lore = lore + kotlin.runCatching { + itemMeta.addItemFlags(*flags.map { org.bukkit.inventory.ItemFlag.valueOf(it.name) }.toTypedArray()) + } + if (itemMeta is EnchantmentStorageMeta) { + enchants.forEach { (e, lvl) -> itemMeta.addStoredEnchant(e, lvl, true) } + } else { + enchants.forEach { (e, lvl) -> itemMeta.addEnchant(e, lvl, true) } + } + when (itemMeta) { + is LeatherArmorMeta -> { + itemMeta.setColor(color) + } + + is PotionMeta -> { + itemMeta.color = color + potions.forEach { itemMeta.addCustomEffect(it, true) } + if (potionData != null) { + itemMeta.basePotionData = potionData!! + } + } + + is SkullMeta -> { + if (skullOwner != null) { + itemMeta.owner = skullOwner + } + if (skullTexture != null) { + itemMeta.setProperty("profile", GameProfile(skullTexture!!.uuid, null).also { + it.properties.put("textures", Property("textures", skullTexture!!.textures)) + }) + } + } + } + try { + itemMeta.isUnbreakable = isUnbreakable + } catch (ex: NoSuchMethodError) { + try { + itemMeta.invokeMethod("spigot")!!.invokeMethod("setUnbreakable", isUnbreakable) + } catch (ignored: NoSuchMethodException) { + } + } + try { + if (spawnType != null && itemMeta is SpawnEggMeta) { + itemMeta.spawnedType = spawnType + } + } catch (ignored: NoClassDefFoundError) { + } + try { + if (patterns.isNotEmpty() && itemMeta is BannerMeta) { + patterns.forEach { itemMeta.addPattern(it) } + } + } catch (ignored: NoClassDefFoundError) { + } + try { + if (customModelData != -1) { + itemMeta.invokeMethod("setCustomModelData", customModelData) + } + } catch (ignored: NoSuchMethodException) { + } + itemStack.itemMeta = itemMeta + finishing(itemStack) + return itemStack + } + + constructor(material: Material) { + this.material = material + } + + /** + * 通过现有物品构建新的物品 + * 读取基本信息 + */ + constructor(item: ItemStack) { + material = item.type + amount = item.amount + damage = item.durability.toInt() + // 如果物品没有 ItemMeta 则不进行后续操作 + val itemMeta = item.itemMeta ?: return + originMeta = itemMeta + name = itemMeta.displayName + lore += itemMeta.lore ?: emptyList() + flags += kotlin.runCatching { itemMeta.itemFlags.map { ItemFlag.valueOf(it.name) } }.getOrElse { emptyList() } + enchants += if (itemMeta is EnchantmentStorageMeta) { + itemMeta.storedEnchants + } else { + itemMeta.enchants + } + when (itemMeta) { + is LeatherArmorMeta -> { + color = itemMeta.color + } + + is PotionMeta -> { + color = itemMeta.color + potions += itemMeta.customEffects + potionData = itemMeta.basePotionData + } + + is SkullMeta -> { + if (itemMeta.owner != null) { + skullOwner = itemMeta.owner + } + skullTexture = getSkinValue(itemMeta) + } + } + try { + customModelData = itemMeta.getProperty("customModelData") ?: -1 + } catch (ignored: NoSuchFieldException) { + } + try { + isUnbreakable = itemMeta.isUnbreakable + } catch (ex: NoSuchMethodError) { + try { + isUnbreakable = itemMeta.invokeMethod("spigot")!!.invokeMethod("isUnbreakable") ?: false + } catch (ignored: NoSuchMethodException) { + } + } + try { + if (itemMeta is SpawnEggMeta && itemMeta.spawnedType != null) { + spawnType = itemMeta.spawnedType + } + } catch (ignored: NoClassDefFoundError) { + } catch (ignored: UnsupportedOperationException) { + + } + try { + if (itemMeta is BannerMeta && itemMeta.patterns.isNotEmpty()) { + patterns += itemMeta.patterns + } + } catch (ignored: NoClassDefFoundError) { + } + } +} diff --git a/src/main/kotlin/com/github/xbaimiao/shoppro/util/ItemFlag.java b/src/main/kotlin/com/github/xbaimiao/shoppro/util/ItemFlag.java new file mode 100644 index 0000000..d50e3df --- /dev/null +++ b/src/main/kotlin/com/github/xbaimiao/shoppro/util/ItemFlag.java @@ -0,0 +1,11 @@ +package com.github.xbaimiao.shoppro.util; + +public enum ItemFlag { + HIDE_ENCHANTS, + HIDE_ATTRIBUTES, + HIDE_UNBREAKABLE, + HIDE_DESTROYS, + HIDE_PLACED_ON, + HIDE_POTION_EFFECTS, + HIDE_DYE +}