diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/EcoEnchantsPlugin.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/EcoEnchantsPlugin.kt index f1478607..e4e9d622 100644 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/EcoEnchantsPlugin.kt +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/EcoEnchantsPlugin.kt @@ -32,15 +32,15 @@ import com.willfp.ecoenchants.mechanics.ExtraItemSupport import com.willfp.ecoenchants.mechanics.GrindstoneSupport import com.willfp.ecoenchants.mechanics.LootSupport import com.willfp.ecoenchants.mechanics.VillagerSupport -import com.willfp.ecoenchants.target.EnchantLookup.clearEnchantCache -import com.willfp.ecoenchants.target.EnchantLookup.heldEnchantLevels +import com.willfp.ecoenchants.target.EnchantFinder +import com.willfp.ecoenchants.target.EnchantFinder.clearEnchantmentCache import com.willfp.libreforge.NamedValue import com.willfp.libreforge.loader.LibreforgePlugin import com.willfp.libreforge.loader.configs.ConfigCategory import com.willfp.libreforge.registerHolderPlaceholderProvider -import com.willfp.libreforge.registerSpecificHolderProvider +import com.willfp.libreforge.registerHolderProvider import com.willfp.libreforge.registerSpecificRefreshFunction -import org.bukkit.entity.Player +import org.bukkit.entity.LivingEntity import org.bukkit.event.Listener internal lateinit var plugin: EcoEnchantsPlugin @@ -75,12 +75,10 @@ class EcoEnchantsPlugin : LibreforgePlugin() { } override fun handleEnable() { - registerSpecificHolderProvider { - it.heldEnchantLevels - } + registerHolderProvider(EnchantFinder.toHolderProvider()) - registerSpecificRefreshFunction { - it.clearEnchantCache() + registerSpecificRefreshFunction { + it.clearEnchantmentCache() } registerHolderPlaceholderProvider { it, _ -> diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/enchant/impl/hardcoded/EnchantmentRepairing.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/enchant/impl/hardcoded/EnchantmentRepairing.kt index 4f737041..f9ab1910 100644 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/enchant/impl/hardcoded/EnchantmentRepairing.kt +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/enchant/impl/hardcoded/EnchantmentRepairing.kt @@ -3,8 +3,8 @@ package com.willfp.ecoenchants.enchant.impl.hardcoded import com.willfp.eco.util.DurabilityUtils import com.willfp.ecoenchants.EcoEnchantsPlugin import com.willfp.ecoenchants.enchant.impl.HardcodedEcoEnchant -import com.willfp.ecoenchants.target.EnchantLookup.getActiveEnchantLevelInSlot -import com.willfp.ecoenchants.target.EnchantLookup.hasEnchantActive +import com.willfp.ecoenchants.target.EnchantFinder.getItemsWithEnchantActive +import com.willfp.ecoenchants.target.EnchantFinder.hasEnchantActive import com.willfp.libreforge.slot.impl.SlotTypeHands import org.bukkit.Bukkit @@ -29,18 +29,10 @@ class EnchantmentRepairing( if (player.hasEnchantActive(this)) { val repairPerLevel = config.getIntFromExpression("repair-per-level", player) - for ((slot, item) in player.inventory.withIndex()) { - if (item == null) { - continue - } + for ((item, level) in player.getItemsWithEnchantActive(this)) { + val isHolding = item in SlotTypeHands.getItems(player) - if (notWhileHolding && slot in SlotTypeHands.getItemSlots(player)) { - continue - } - - val level = player.getActiveEnchantLevelInSlot(this, slot) - - if (level == 0) { + if (notWhileHolding && isHolding) { continue } diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/enchant/impl/hardcoded/EnchantmentReplenish.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/enchant/impl/hardcoded/EnchantmentReplenish.kt index a46e231d..d1ba489c 100644 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/enchant/impl/hardcoded/EnchantmentReplenish.kt +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/enchant/impl/hardcoded/EnchantmentReplenish.kt @@ -4,7 +4,7 @@ import com.willfp.eco.core.EcoPlugin import com.willfp.ecoenchants.EcoEnchantsPlugin import com.willfp.ecoenchants.enchant.EcoEnchant import com.willfp.ecoenchants.enchant.impl.HardcodedEcoEnchant -import com.willfp.ecoenchants.target.EnchantLookup.hasEnchantActive +import com.willfp.ecoenchants.target.EnchantFinder.hasEnchantActive import org.bukkit.Bukkit import org.bukkit.Material import org.bukkit.block.BlockFace diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/enchant/impl/hardcoded/EnchantmentSoulbound.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/enchant/impl/hardcoded/EnchantmentSoulbound.kt index a6d900b4..193f2349 100644 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/enchant/impl/hardcoded/EnchantmentSoulbound.kt +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/enchant/impl/hardcoded/EnchantmentSoulbound.kt @@ -11,8 +11,7 @@ import com.willfp.eco.core.items.Items import com.willfp.ecoenchants.EcoEnchantsPlugin import com.willfp.ecoenchants.enchant.EcoEnchant import com.willfp.ecoenchants.enchant.impl.HardcodedEcoEnchant -import com.willfp.ecoenchants.target.EnchantLookup.getActiveEnchantLevelInSlot -import com.willfp.ecoenchants.target.EnchantLookup.hasEnchantActive +import com.willfp.ecoenchants.target.EnchantFinder.getItemsWithEnchantActive import org.bukkit.entity.Player import org.bukkit.event.EventHandler import org.bukkit.event.EventPriority @@ -20,7 +19,6 @@ import org.bukkit.event.Listener import org.bukkit.event.entity.PlayerDeathEvent import org.bukkit.event.player.PlayerJoinEvent import org.bukkit.event.player.PlayerRespawnEvent -import org.bukkit.inventory.ItemStack import org.bukkit.persistence.PersistentDataType class EnchantmentSoulbound( @@ -61,25 +59,7 @@ class EnchantmentSoulbound( } val player = event.entity - val items = mutableListOf() - - if (!player.hasEnchantActive(enchant)) { - return - } - - for ((slot, item) in player.inventory.withIndex()) { - if (item == null) { - continue - } - - val level = player.getActiveEnchantLevelInSlot(enchant, slot) - - if (level == 0) { - continue - } - - items.add(item) - } + val items = player.getItemsWithEnchantActive(enchant).keys if (items.isEmpty()) { return diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/target/EnchantFinder.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/target/EnchantFinder.kt new file mode 100644 index 00000000..38a5b6d9 --- /dev/null +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/target/EnchantFinder.kt @@ -0,0 +1,72 @@ +package com.willfp.ecoenchants.target + +import com.github.benmanes.caffeine.cache.Caffeine +import com.willfp.eco.core.fast.fast +import com.willfp.ecoenchants.enchant.EcoEnchant +import com.willfp.ecoenchants.enchant.EcoEnchantLevel +import com.willfp.libreforge.ProvidedHolder +import com.willfp.libreforge.slot.ItemHolderFinder +import com.willfp.libreforge.slot.SlotType +import com.willfp.libreforge.toDispatcher +import org.bukkit.entity.LivingEntity +import org.bukkit.inventory.ItemStack +import java.util.UUID +import java.util.concurrent.TimeUnit + +object EnchantFinder : ItemHolderFinder() { + private val provider = this.toHolderProvider() + private val levelCache = Caffeine.newBuilder() + .expireAfterWrite(1, TimeUnit.SECONDS) + .build>() + + override fun find(item: ItemStack): List { + val enchantMap = item.fast().enchants + val enchants = mutableListOf() + + for ((enchant, level) in enchantMap) { + if (enchant !is EcoEnchant) { + continue + } + + enchants += enchant.getLevel(level) + } + + return enchants + } + + override fun isValidInSlot(holder: EcoEnchantLevel, slot: SlotType): Boolean { + return slot in holder.enchant.targets.map { it.slot } + } + + internal fun LivingEntity.clearEnchantmentCache() = levelCache.invalidate(this.uniqueId) + + private val LivingEntity.cachedLevels: List + get() = levelCache.get(this.uniqueId) { + provider.provide(this.toDispatcher()) + .mapNotNull { + val level = it.holder as? EcoEnchantLevel ?: return@mapNotNull null + val item = it.provider as? ItemStack ?: return@mapNotNull null + + ProvidedLevel(level, item, it) + } + } + + fun LivingEntity.hasEnchantActive(enchant: EcoEnchant): Boolean { + return this.cachedLevels + .filter { it.level.enchant == enchant } + .any { it.level.conditions.areMet(this.toDispatcher(), it.holder) } + } + + fun LivingEntity.getItemsWithEnchantActive(enchant: EcoEnchant): Map { + return this.cachedLevels + .filter { it.level.enchant == enchant } + .filter { it.level.conditions.areMet(this.toDispatcher(), it.holder) } + .associate { it.item to it.level.level } + } + + private data class ProvidedLevel( + val level: EcoEnchantLevel, + val item: ItemStack, + val holder: ProvidedHolder + ) +} diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/target/EnchantLookup.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/target/EnchantLookup.kt deleted file mode 100644 index 9226d623..00000000 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/target/EnchantLookup.kt +++ /dev/null @@ -1,345 +0,0 @@ -package com.willfp.ecoenchants.target - -import com.github.benmanes.caffeine.cache.Caffeine -import com.willfp.eco.core.fast.fast -import com.willfp.eco.core.items.HashedItem -import com.willfp.ecoenchants.enchant.EcoEnchant -import com.willfp.ecoenchants.enchant.EcoEnchantLevel -import com.willfp.ecoenchants.enchant.FoundEcoEnchantLevel -import com.willfp.ecoenchants.plugin -import com.willfp.libreforge.ItemProvidedHolder -import com.willfp.libreforge.slot.SlotType -import com.willfp.libreforge.slot.SlotTypes -import com.willfp.libreforge.toDispatcher -import org.bukkit.entity.Player -import org.bukkit.inventory.ItemStack -import java.util.concurrent.TimeUnit - -// The Int is the inventory slot ID -typealias SlotProvider = (Player) -> Map - -data class ItemInSlot internal constructor( - val item: ItemStack, val slot: Collection -) { - constructor( - item: ItemStack, slot: SlotType - ) : this(item, listOf(slot)) -} - -data class ItemInNumericSlot internal constructor( - val item: ItemStack, val slot: Int -) { - override fun hashCode(): Int { - return HashedItem.of(item).hash * (slot + 1) - } -} - -private data class HeldEnchant( - val enchant: EcoEnchant, val level: Int -) - -object EnchantLookup { - private val slotProviders = mutableSetOf() - - private val itemCache = - Caffeine.newBuilder().expireAfterWrite(2, TimeUnit.SECONDS).build>() - - private val enchantCache = Caffeine.newBuilder().expireAfterWrite(2, TimeUnit.SECONDS) - .build>>() - - // Higher frequency cache as less intensive - private val enchantLevelCache = Caffeine.newBuilder().expireAfterWrite(200, TimeUnit.MILLISECONDS) - .build>>() - - @JvmStatic - fun registerProvider(provider: SlotProvider) { - slotProviders.add { - val found = mutableMapOf() - for ((slot, inSlot) in provider(it)) { - found[slot] = inSlot - } - found - } - } - - private fun provide(player: Player): Map { - return itemCache.get(player) { - val found = mutableMapOf() - - for (provider in slotProviders) { - val fromProvider = provider(player) - for ((slot, item) in fromProvider) { - // Basically a multimap - val current = found[slot] - - if (current == null) { - found[slot] = item - } else { - found[slot] = ItemInSlot(item.item, listOf(current.slot, item.slot).flatten()) - } - } - } - - found - } - } - - /** - * The inventory slot IDs mapped to HeldEnchants found in that slot. - */ - private val Player.slotHeldEnchants: Map> - get() { - return enchantCache.get(this) { - val found = mutableMapOf>() - - for ((slotID, inSlot) in provide(this)) { - val (item, slot) = inSlot - - val enchants = item.fast().enchants - - for ((enchant, level) in enchants) { - if (enchant !is EcoEnchant) { - continue - } - - if (slot.none { it in enchant.slots }) { - continue - } - - val held = HeldEnchant(enchant, level) - - // Prevent repeating slot IDs found in multiple TargetSlots (e.g. HANDS and ANY) - if (held in found.getOrDefault(slotID, mutableListOf())) { - continue - } - - // Basically a multimap - found[slotID] = - (found.getOrDefault(slotID, mutableListOf()) + HeldEnchant(enchant, level)).toMutableList() - } - } - - found - } - } - - /** - * Slot IDs mapped to HeldEnchants found in that slot. - */ - private val Player.slotIDHeldEnchants: Map> - get() { - return this.slotHeldEnchants.mapKeys { it.key.slot } - } - - /** - * All slots holding EcoEnchants mapped to their IDs, regardless of conditions. - */ - val Player.heldEnchantsInSlots: Map> - get() { - return enchantLevelCache.get(this) { - val found = mutableMapOf>() - - for ((inSlot, enchants) in this.slotHeldEnchants) { - val map = mutableMapOf() - for ((enchant, level) in enchants) { - map[enchant] = level - } - found[inSlot] = map - } - - found - } - } - - /** - * All EcoEnchants mapped to their IDs, regardless of conditions. - */ - val Player.heldEnchants: Map - get() { - val found = mutableMapOf() - for ((_, enchants) in this.heldEnchantsInSlots) { - for ((enchant, level) in enchants) { - found[enchant] = level - } - } - - return found - } - - /** - * All slots mapped to EcoEnchants mapped to their IDs, respecting conditions. - */ - val Player.activeEnchantsInSlots: Map> - get() { - val found = mutableMapOf>() - for ((slot, enchants) in this.heldEnchantsInSlots) { - val inSlot = mutableMapOf() - for ((enchant, level) in enchants) { - val enchantLevel = enchant.getLevel(level) - val providedHolder = ItemProvidedHolder(enchantLevel, slot.item) - if (enchantLevel.conditions.areMet(this.toDispatcher(), providedHolder)) { - inSlot[enchant] = level - } - } - found[slot] = inSlot - } - - return found - } - - /** - * All EcoEnchants mapped to their IDs, respecting conditions. - */ - val Player.activeEnchants: Map - get() { - val found = mutableMapOf() - for ((_, enchants) in this.activeEnchantsInSlots) { - for ((enchant, level) in enchants) { - found[enchant] = level - } - } - - return found - } - - /** - * Get the enchantment level on a player, ignoring conditions. - * - * @return The level, or 0 if not found. - */ - fun Player.getEnchantLevel(enchant: EcoEnchant): Int { - return this.heldEnchants[enchant] ?: 0 - } - - /** - * Get the enchantment level on a player, respecting. - * - * @return The level, or 0 if not found. - */ - fun Player.getActiveEnchantLevel(enchant: EcoEnchant): Int { - return this.activeEnchants[enchant] ?: 0 - } - - /** - * Get the enchantment level on a player in a specific slot, ignoring conditions. - * - * @return The level, or 0 if not found. - */ - fun Player.getEnchantLevelInSlot(enchant: EcoEnchant, slot: Int): Int { - val inSlot = this.slotIDHeldEnchants[slot] ?: return 0 - val heldEnchant = inSlot.firstOrNull { it.enchant == enchant } ?: return 0 - return heldEnchant.level - } - - /** - * Get the enchantment level on a player in a specific slot, respecting conditions. - * - * @return The level, or 0 if not found. - */ - fun Player.getActiveEnchantLevelInSlot(enchant: EcoEnchant, slot: Int): Int { - val level = getEnchantLevelInSlot(enchant, slot) - - if (level == 0) { - return 0 - } - - val enchantLevel = enchant.getLevel(level) - val item = this.inventory.getItem(slot) ?: return 0 - val providedHolder = ItemProvidedHolder(enchantLevel, item) - - if (!enchantLevel.conditions.areMet(this.toDispatcher(), providedHolder)) { - return 0 - } - - return level - } - - /** - * Get if a player has an enchantment, ignoring conditions. - * - * @return If the player has the enchantment. - */ - fun Player.hasEnchant(enchant: EcoEnchant): Boolean { - return this.getEnchantLevel(enchant) > 0 - } - - /** - * Get if a player has an enchantment, respecting conditions. - * - * @return If the player has the enchantment. - */ - fun Player.hasEnchantActive(enchant: EcoEnchant): Boolean { - return this.getActiveEnchantLevel(enchant) > 0 - } - - /** - * Get if a player has an enchantment in a slot, ignoring conditions. - * - * @return If the player has the enchantment. - */ - fun Player.hasEnchantInSlot(enchant: EcoEnchant, slot: Int): Boolean { - return this.getEnchantLevelInSlot(enchant, slot) > 0 - } - - /** - * Get if a player has an enchantment in a slot, respecting conditions. - * - * @return If the player has the enchantment. - */ - fun Player.hasEnchantActiveInSlot(enchant: EcoEnchant, slot: Int): Boolean { - return this.getActiveEnchantLevelInSlot(enchant, slot) > 0 - } - - val Player.heldEnchantLevels: List - get() { - val found = mutableListOf() - - for ((slot, enchants) in this.heldEnchantsInSlots) { - for ((enchant, level) in enchants) { - found.add(ItemProvidedHolder(enchant.getLevel(level), slot.item)) - } - } - - // This is such a fucking disgusting way of implementing %active_level%, - // and it's probably quite slow too. - return if (plugin.configYml.getBool("extra-placeholders.active-level")) { - found.map { - val level = it.holder as EcoEnchantLevel - - ItemProvidedHolder( - FoundEcoEnchantLevel(level, this.getActiveEnchantLevel(level.enchant)), it.provider - ) - } - } else { - found - } - } - - /** - * Clear item and enchant cache. - */ - fun Player.clearEnchantCache() { - itemCache.invalidate(player) - enchantCache.invalidate(player) - enchantLevelCache.invalidate(player) - } - - init { - fun createProvider(slot: SlotType): SlotProvider { - return { player: Player -> - val found = mutableMapOf() - - for (slotID in slot.getItemSlots(player)) { - val item = player.inventory.getItem(slotID) ?: continue - found[ItemInNumericSlot(item, slotID)] = ItemInSlot(item, slot) - } - - found - } - } - - for (slot in SlotTypes.values()) { - registerProvider(createProvider(slot)) - } - } -} diff --git a/gradle.properties b/gradle.properties index 1154d700..86e126fa 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ #libreforge-updater #Wed Dec 27 14:18:03 CET 2023 kotlin.code.style=official -libreforge-version=4.51.1 -version=11.2.1 +libreforge-version=4.52.0 +version=12.0.0