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 3202baa3..2ed1b893 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 @@ -12,6 +12,8 @@ import com.willfp.ecoenchants.config.VanillaEnchantsYml import com.willfp.ecoenchants.display.EnchantDisplay import com.willfp.ecoenchants.enchants.EcoEnchants import com.willfp.ecoenchants.enchants.LoreConversion +import com.willfp.ecoenchants.enchants.impl.EnchantmentPermanenceCurse +import com.willfp.ecoenchants.enchants.impl.EnchantmentRepairing import com.willfp.ecoenchants.enchants.impl.EnchantmentTelekinesis import com.willfp.ecoenchants.enchants.registerVanillaEnchants import com.willfp.ecoenchants.integrations.EnchantRegistrations @@ -45,6 +47,9 @@ class EcoEnchantsPlugin : LibReforgePlugin() { override fun handleReloadAdditional() { // Load hardcoded enchantments EnchantmentTelekinesis(this) + EnchantmentPermanenceCurse(this) + EnchantmentRepairing(this) + registerVanillaEnchants(this) logger.info(EcoEnchants.values().size.toString() + " Enchants Loaded") diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/enchants/DescriptionPlaceholder.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/enchants/DescriptionPlaceholder.kt new file mode 100644 index 00000000..922f2348 --- /dev/null +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/enchants/DescriptionPlaceholder.kt @@ -0,0 +1,6 @@ +package com.willfp.ecoenchants.enchants + +class DescriptionPlaceholder( + val id: String, + val value: Double +) diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/enchants/EcoEnchant.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/enchants/EcoEnchant.kt index 0a194e89..395d6162 100644 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/enchants/EcoEnchant.kt +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/enchants/EcoEnchant.kt @@ -140,9 +140,18 @@ abstract class EcoEnchant( // and that way the enchantment isn't registered. if (!config.getBool("dont-register")) { register() + doOnInit() } } + private fun doOnInit() { + onInit() + } + + protected open fun onInit() { + // Override when needed + } + private fun register() { EcoEnchants.addNewEnchant(this) } @@ -159,26 +168,46 @@ abstract class EcoEnchant( } override fun getUnformattedDescription(level: Int): String { - val placeholderValue = NumberUtils.evaluateExpression( - config.getString("placeholder"), - null, - object : PlaceholderInjectable { - override fun getPlaceholderInjections(): List { - return listOf( - StaticPlaceholder( - "level", - ) { level.toString() } - ) - } + // Fetch custom placeholders other than %placeholder% + val uncompiledPlaceholders = config.getSubsection("placeholders").getKeys(false).associateWith { + config.getString("placeholders.$it") + }.toMutableMap() - override fun clearInjectedPlaceholders() { - // Do nothing - } - } - ) + // Add %placeholder% placeholder in + uncompiledPlaceholders["placeholder"] = config.getString("placeholder") - return config.getString("description") - .replace("%placeholder%", NumberUtils.format(placeholderValue)) + // Evaluate each placeholder + val placeholders = uncompiledPlaceholders.map { (id, expr) -> + DescriptionPlaceholder( + id, + NumberUtils.evaluateExpression( + expr, + null, + object : PlaceholderInjectable { + override fun getPlaceholderInjections(): List { + return listOf( + StaticPlaceholder( + "level", + ) { level.toString() } + ) + } + + override fun clearInjectedPlaceholders() { + // Do nothing + } + } + ) + ) + } + + // Apply placeholders to description + val rawDescription = config.getString("description") + var description = rawDescription + for (placeholder in placeholders) { + description = description.replace("%${placeholder.id}%", NumberUtils.format(placeholder.value)) + } + + return description } @Deprecated( diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/enchants/impl/EnchantmentRepairing.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/enchants/impl/EnchantmentRepairing.kt new file mode 100644 index 00000000..a13ff48e --- /dev/null +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/enchants/impl/EnchantmentRepairing.kt @@ -0,0 +1,53 @@ +package com.willfp.ecoenchants.enchants.impl + +import com.willfp.eco.util.DurabilityUtils +import com.willfp.ecoenchants.EcoEnchantsPlugin +import com.willfp.ecoenchants.enchants.EcoEnchant +import com.willfp.ecoenchants.target.EnchantLookup.getActiveEnchantLevelInSlot +import com.willfp.ecoenchants.target.EnchantLookup.hasEnchantActive +import com.willfp.ecoenchants.target.TargetSlot +import org.bukkit.Bukkit + +class EnchantmentRepairing( + plugin: EcoEnchantsPlugin +) : EcoEnchant( + "repairing", + plugin, + force = false +) { + override fun onInit() { + val frequency = config.getInt("frequency").toLong() + + plugin.scheduler.runTimer(frequency, frequency) { + handleRepairing() + } + } + + private fun handleRepairing() { + val notWhileHolding = config.getBool("not-while-holding") + + for (player in Bukkit.getOnlinePlayers()) { + if (player.hasEnchantActive(this)) { + val repairPerLevel = config.getIntFromExpression("repair-per-level", player) + + for ((slot, item) in player.inventory.withIndex()) { + if (item == null) { + continue + } + + if (notWhileHolding && slot in TargetSlot.HANDS.getItemSlots(player)) { + continue + } + + val level = player.getActiveEnchantLevelInSlot(this, slot) + + if (level == 0) { + continue + } + + DurabilityUtils.repairItem(item, level * repairPerLevel) + } + } + } + } +} 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 index 69eec908..dd2128eb 100644 --- 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 @@ -2,44 +2,55 @@ package com.willfp.ecoenchants.target import com.github.benmanes.caffeine.cache.Caffeine import com.willfp.eco.core.fast.fast -import com.willfp.ecoenchants.EcoEnchantsPlugin import com.willfp.ecoenchants.enchants.EcoEnchant import com.willfp.ecoenchants.enchants.EcoEnchantLevel import org.bukkit.entity.Player import org.bukkit.inventory.ItemStack import java.util.concurrent.TimeUnit -typealias SlotProvider = (Player) -> Map +// The Int is the inventory slot ID +typealias SlotProvider = (Player) -> Map + +data class ItemInSlot( + val item: ItemStack, + val slot: TargetSlot +) + +private data class HeldEnchant( + val enchant: EcoEnchant, + val level: Int +) object EnchantLookup { - private val plugin = EcoEnchantsPlugin.instance - - private val slotProviders = mutableSetOf<(Player) -> Map>() + private val slotProviders = mutableSetOf() private val itemCache = Caffeine.newBuilder() .expireAfterWrite(2, TimeUnit.SECONDS) - .build>() + .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 ((item, slot) in provider(it)) { - if (item != null && slot != null) { - found[item] = slot - } + val found = mutableMapOf() + for ((slot, inSlot) in provider(it)) { + found[slot] = inSlot } found } } - private fun provide(player: Player): Map { + private fun provide(player: Player): Map { return itemCache.get(player) { - val found = mutableMapOf() + val found = mutableMapOf() for (provider in slotProviders) { found.putAll(provider(player)) } @@ -48,22 +59,36 @@ object EnchantLookup { } } - val Player.heldEnchants: Map + /** + * The inventory slot IDs mapped to HeldEnchants found in that slot. + */ + private val Player.slotHeldEnchants: Map> get() { - return enchantCache.get(player) { - val found = mutableMapOf() + return enchantCache.get(this) { + val found = mutableMapOf>() - for ((itemStack, slot) in provide(this)) { - val enchants = itemStack.fast().enchants + for ((slotID, inSlot) in provide(this)) { + val (item, slot) = inSlot + + // Prevent repeating slot IDs found in multiple TargetSlots (e.g. HANDS and ANY) + if (found.containsKey(slotID)) { + continue + } + + val enchants = item.fast().enchants for ((enchant, level) in enchants) { if (enchant !is EcoEnchant) { continue } - if (enchant.slots.contains(slot) || slot == TargetSlot.ANY) { - found[enchant] = found.getOrDefault(enchant, 0) + level + if (slot !in enchant.slots) { + continue } + + // Basically a multimap + found[slotID] = (found.getOrDefault(slotID, mutableListOf()) + + HeldEnchant(enchant, level)).toMutableList() } } @@ -71,6 +96,25 @@ object EnchantLookup { } } + /** + * All EcoEnchants mapped to their IDs, regardless of conditions. + */ + val Player.heldEnchants: Map + get() { + return enchantLevelCache.get(this) { + val found = mutableMapOf() + + for ((enchant, level) in it.slotHeldEnchants.values.flatten()) { + found[enchant] = found.getOrDefault(enchant, 0) + level + } + + found + } + } + + /** + * All EcoEnchants mapped to their IDs, respecting conditions. + */ val Player.activeEnchants: Map get() { return this.heldEnchants.filter { (enchant, level) -> @@ -78,22 +122,90 @@ object EnchantLookup { } } + /** + * 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.slotHeldEnchants[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 + } + + if (enchant.getLevel(level).conditions.any { !it.isMet(this) }) { + 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() = this.heldEnchants .map { (enchant, level) -> enchant.getLevel(level) } @@ -107,32 +219,21 @@ object EnchantLookup { } init { - registerProvider { - mapOf( - Pair( - it.inventory.itemInMainHand, - TargetSlot.HANDS - ) - ) - } + fun createProvider(slot: TargetSlot): SlotProvider { + return { player: Player -> + val found = mutableMapOf() - if (!plugin.configYml.getBool("no-offhand")) { - registerProvider { - mapOf( - Pair( - it.inventory.itemInOffHand, - TargetSlot.HANDS - ) - ) + for (slotID in slot.getItemSlots(player)) { + val item = player.inventory.getItem(slotID) ?: continue + found[slotID] = ItemInSlot(item, slot) + } + + found } } - registerProvider { - val items = mutableMapOf() - for (stack in it.inventory.armorContents) { - items[stack] = TargetSlot.ARMOR - } - items + for (slot in TargetSlot.values()) { + registerProvider(createProvider(slot)) } } } diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/target/EnchantmentTarget.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/target/EnchantmentTarget.kt index b5e0f581..7497fb6a 100644 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/target/EnchantmentTarget.kt +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecoenchants/target/EnchantmentTarget.kt @@ -5,6 +5,7 @@ import com.willfp.eco.core.items.Items import com.willfp.eco.core.items.TestableItem import com.willfp.eco.core.recipe.parts.EmptyTestableItem import com.willfp.ecoenchants.EcoEnchantsPlugin +import org.bukkit.entity.Player import org.bukkit.inventory.ItemStack import java.util.* @@ -66,8 +67,23 @@ internal object AllEnchantmentTarget : EnchantmentTarget { } } -enum class TargetSlot { - HANDS, - ARMOR, - ANY +enum class TargetSlot( + private val itemSlotGetter: (Player) -> Collection +) { + HANDS({ + listOf( + it.inventory.heldItemSlot, + 45 + ) + }), + + ARMOR({ + listOf(5, 6, 7, 8) + }), + + ANY({ + (0..45).toList() + }); + + fun getItemSlots(player: Player): Collection = itemSlotGetter(player) } diff --git a/eco-core/core-plugin/src/main/resources/config.yml b/eco-core/core-plugin/src/main/resources/config.yml index a4675b8e..cfe1d09b 100644 --- a/eco-core/core-plugin/src/main/resources/config.yml +++ b/eco-core/core-plugin/src/main/resources/config.yml @@ -3,9 +3,6 @@ # by Auxilor # -# If the hand slot shouldn't work in the players offhand -no-offhand: false # Restart your server after this option, doesn't work with /ecoenchants reload - # Options for enchanting items in the enchanting table enchanting-table: enabled: true # If custom enchantments should be available from enchanting tables diff --git a/eco-core/core-plugin/src/main/resources/enchants/_example.yml b/eco-core/core-plugin/src/main/resources/enchants/_example.yml index 7b81cfe5..eb5bfbbd 100644 --- a/eco-core/core-plugin/src/main/resources/enchants/_example.yml +++ b/eco-core/core-plugin/src/main/resources/enchants/_example.yml @@ -5,8 +5,10 @@ # _example.yml is not loaded. display-name: "Example" # The name of the enchantment in-game -description: "Gives a &a%placeholder%%&8 bonus to damage" # The description of the enchantment -placeholder: "%level% * 20" # The placeholder to show in the enchantment description +description: "Gives a &a%placeholder%%&8 and a &a+%damage%&8 bonus to damage" # The description of the enchantment +placeholder: "%level% * 20" # The placeholder to show in the enchantment description (optional, can only use custom placeholders) +placeholders: # Extra placeholders to show in the enchantment description (optional) + damage: "%level% * 2" # Here, %damage% would be the extra placeholder to use type: normal # The enchantment type, from types.yml targets: # The items that the enchantment can be applied to, see targets.yml @@ -26,7 +28,12 @@ enchantable: true # If the enchantment can be obtained from enchanting tables effects: - id: damage_multiplier args: - multiplier: 1 + 0.2 * %level% + multiplier: "1 + 0.2 * %level%" + triggers: + - melee_attack + - id: damage_victim + args: + damage: "2 * %level%" triggers: - melee_attack diff --git a/eco-core/core-plugin/src/main/resources/enchants/repairing.yml b/eco-core/core-plugin/src/main/resources/enchants/repairing.yml new file mode 100644 index 00000000..dfd25a9d --- /dev/null +++ b/eco-core/core-plugin/src/main/resources/enchants/repairing.yml @@ -0,0 +1,24 @@ +display-name: "Repairing" +description: "Automatically gain &a%amount%&8 durability every &a%frequency% &8seconds" +type: normal +placeholders: + frequency: "1.5" + amount: "%level%" + +targets: + - pickaxe + - sword + - axe +conflicts: [ ] +rarity: common +max-level: 1 + +tradeable: true +discoverable: true +enchantable: true + +conditions: [ ] + +repair-per-level: 1 # The amount of durability to add on each item +frequency: 30 # How often to repair the item (in ticks, doesn't support %level%) +not-while-holding: true # If items shouldn't be repaired while they are being held