Enchantments will now work when used by entities

This commit is contained in:
Will FP 2023-12-27 15:29:25 +01:00
parent fd064e31c8
commit 9a41250b35
7 changed files with 89 additions and 392 deletions

View File

@ -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<Player> {
it.heldEnchantLevels
}
registerHolderProvider(EnchantFinder.toHolderProvider())
registerSpecificRefreshFunction<Player> {
it.clearEnchantCache()
registerSpecificRefreshFunction<LivingEntity> {
it.clearEnchantmentCache()
}
registerHolderPlaceholderProvider<FoundEcoEnchantLevel> { it, _ ->

View File

@ -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
}

View File

@ -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

View File

@ -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<ItemStack>()
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

View File

@ -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<EcoEnchantLevel>() {
private val provider = this.toHolderProvider()
private val levelCache = Caffeine.newBuilder()
.expireAfterWrite(1, TimeUnit.SECONDS)
.build<UUID, List<ProvidedLevel>>()
override fun find(item: ItemStack): List<EcoEnchantLevel> {
val enchantMap = item.fast().enchants
val enchants = mutableListOf<EcoEnchantLevel>()
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<ProvidedLevel>
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<ItemStack, Int> {
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
)
}

View File

@ -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<ItemInNumericSlot, ItemInSlot>
data class ItemInSlot internal constructor(
val item: ItemStack, val slot: Collection<SlotType>
) {
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<SlotProvider>()
private val itemCache =
Caffeine.newBuilder().expireAfterWrite(2, TimeUnit.SECONDS).build<Player, Map<ItemInNumericSlot, ItemInSlot>>()
private val enchantCache = Caffeine.newBuilder().expireAfterWrite(2, TimeUnit.SECONDS)
.build<Player, Map<ItemInNumericSlot, Collection<HeldEnchant>>>()
// Higher frequency cache as less intensive
private val enchantLevelCache = Caffeine.newBuilder().expireAfterWrite(200, TimeUnit.MILLISECONDS)
.build<Player, Map<ItemInNumericSlot, Map<EcoEnchant, Int>>>()
@JvmStatic
fun registerProvider(provider: SlotProvider) {
slotProviders.add {
val found = mutableMapOf<ItemInNumericSlot, ItemInSlot>()
for ((slot, inSlot) in provider(it)) {
found[slot] = inSlot
}
found
}
}
private fun provide(player: Player): Map<ItemInNumericSlot, ItemInSlot> {
return itemCache.get(player) {
val found = mutableMapOf<ItemInNumericSlot, ItemInSlot>()
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<ItemInNumericSlot, Collection<HeldEnchant>>
get() {
return enchantCache.get(this) {
val found = mutableMapOf<ItemInNumericSlot, MutableCollection<HeldEnchant>>()
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<Int, Collection<HeldEnchant>>
get() {
return this.slotHeldEnchants.mapKeys { it.key.slot }
}
/**
* All slots holding EcoEnchants mapped to their IDs, regardless of conditions.
*/
val Player.heldEnchantsInSlots: Map<ItemInNumericSlot, Map<EcoEnchant, Int>>
get() {
return enchantLevelCache.get(this) {
val found = mutableMapOf<ItemInNumericSlot, MutableMap<EcoEnchant, Int>>()
for ((inSlot, enchants) in this.slotHeldEnchants) {
val map = mutableMapOf<EcoEnchant, Int>()
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<EcoEnchant, Int>
get() {
val found = mutableMapOf<EcoEnchant, Int>()
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<ItemInNumericSlot, Map<EcoEnchant, Int>>
get() {
val found = mutableMapOf<ItemInNumericSlot, MutableMap<EcoEnchant, Int>>()
for ((slot, enchants) in this.heldEnchantsInSlots) {
val inSlot = mutableMapOf<EcoEnchant, Int>()
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<EcoEnchant, Int>
get() {
val found = mutableMapOf<EcoEnchant, Int>()
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<ItemProvidedHolder>
get() {
val found = mutableListOf<ItemProvidedHolder>()
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<ItemInNumericSlot, ItemInSlot>()
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))
}
}
}

View File

@ -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