Added repairing, reworked enchantment lookups, improved enchant lookup API, added custom placeholders

This commit is contained in:
Auxilor 2022-08-31 14:22:36 +01:00
parent 41b2502275
commit 33b66dc0fd
9 changed files with 308 additions and 70 deletions

View File

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

View File

@ -0,0 +1,6 @@
package com.willfp.ecoenchants.enchants
class DescriptionPlaceholder(
val id: String,
val value: Double
)

View File

@ -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<InjectablePlaceholder> {
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<InjectablePlaceholder> {
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(

View File

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

View File

@ -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<ItemStack?, TargetSlot?>
// The Int is the inventory slot ID
typealias SlotProvider = (Player) -> Map<Int, ItemInSlot>
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<ItemStack, TargetSlot>>()
private val slotProviders = mutableSetOf<SlotProvider>()
private val itemCache = Caffeine.newBuilder()
.expireAfterWrite(2, TimeUnit.SECONDS)
.build<Player, Map<ItemStack, TargetSlot>>()
.build<Player, Map<Int, ItemInSlot>>()
private val enchantCache = Caffeine.newBuilder()
.expireAfterWrite(2, TimeUnit.SECONDS)
.build<Player, Map<Int, Collection<HeldEnchant>>>()
// Higher frequency cache as less intensive
private val enchantLevelCache = Caffeine.newBuilder()
.expireAfterWrite(200, TimeUnit.MILLISECONDS)
.build<Player, Map<EcoEnchant, Int>>()
@JvmStatic
fun registerProvider(provider: SlotProvider) {
slotProviders.add {
val found = mutableMapOf<ItemStack, TargetSlot>()
for ((item, slot) in provider(it)) {
if (item != null && slot != null) {
found[item] = slot
}
val found = mutableMapOf<Int, ItemInSlot>()
for ((slot, inSlot) in provider(it)) {
found[slot] = inSlot
}
found
}
}
private fun provide(player: Player): Map<ItemStack, TargetSlot> {
private fun provide(player: Player): Map<Int, ItemInSlot> {
return itemCache.get(player) {
val found = mutableMapOf<ItemStack, TargetSlot>()
val found = mutableMapOf<Int, ItemInSlot>()
for (provider in slotProviders) {
found.putAll(provider(player))
}
@ -48,22 +59,36 @@ object EnchantLookup {
}
}
val Player.heldEnchants: Map<EcoEnchant, Int>
/**
* The inventory slot IDs mapped to HeldEnchants found in that slot.
*/
private val Player.slotHeldEnchants: Map<Int, Collection<HeldEnchant>>
get() {
return enchantCache.get(player) {
val found = mutableMapOf<EcoEnchant, Int>()
return enchantCache.get(this) {
val found = mutableMapOf<Int, MutableCollection<HeldEnchant>>()
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<EcoEnchant, Int>
get() {
return enchantLevelCache.get(this) {
val found = mutableMapOf<EcoEnchant, Int>()
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<EcoEnchant, Int>
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<EcoEnchantLevel>
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<Int, ItemInSlot>()
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<ItemStack?, TargetSlot?>()
for (stack in it.inventory.armorContents) {
items[stack] = TargetSlot.ARMOR
}
items
for (slot in TargetSlot.values()) {
registerProvider(createProvider(slot))
}
}
}

View File

@ -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<Int>
) {
HANDS({
listOf(
it.inventory.heldItemSlot,
45
)
}),
ARMOR({
listOf(5, 6, 7, 8)
}),
ANY({
(0..45).toList()
});
fun getItemSlots(player: Player): Collection<Int> = itemSlotGetter(player)
}

View File

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

View File

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

View File

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