From 4195add65509c4365ae22d419bbd7508181ab32e Mon Sep 17 00:00:00 2001 From: alexcrea <42614139+alexcrea@users.noreply.github.com> Date: Fri, 9 Feb 2024 00:25:12 +0100 Subject: [PATCH] unit repair done --- .../io/delilaheve/AnvilEventListener.kt | 201 ++++++++++++++---- .../io/delilaheve/util/ConfigOptions.kt | 20 +- .../io/delilaheve/util/EnchantmentUtil.kt | 16 +- .../kotlin/io/delilaheve/util/ItemUtil.kt | 43 ++-- .../xyz/alexcrea/util/UnitRepairUtil.kt | 7 +- src/main/resources/unit_repair_item.yml | 8 +- 6 files changed, 230 insertions(+), 65 deletions(-) diff --git a/src/main/kotlin/io/delilaheve/AnvilEventListener.kt b/src/main/kotlin/io/delilaheve/AnvilEventListener.kt index f759ba0..b6382fd 100644 --- a/src/main/kotlin/io/delilaheve/AnvilEventListener.kt +++ b/src/main/kotlin/io/delilaheve/AnvilEventListener.kt @@ -5,9 +5,12 @@ import io.delilaheve.util.EnchantmentUtil.combineWith import io.delilaheve.util.EnchantmentUtil.enchantmentName import io.delilaheve.util.ItemUtil.canMergeWith import io.delilaheve.util.ItemUtil.findEnchantments -import io.delilaheve.util.ItemUtil.isBook +import io.delilaheve.util.ItemUtil.isEnchantedBook import io.delilaheve.util.ItemUtil.repairFrom import io.delilaheve.util.ItemUtil.setEnchantmentsUnsafe +import io.delilaheve.util.ItemUtil.unitRepair +import org.bukkit.Material +import org.bukkit.entity.Player import org.bukkit.event.Event import org.bukkit.event.EventHandler import org.bukkit.event.EventPriority.HIGHEST @@ -19,6 +22,7 @@ import org.bukkit.inventory.InventoryView.Property.REPAIR_COST import org.bukkit.inventory.ItemStack import org.bukkit.inventory.meta.Repairable import xyz.alexcrea.group.ConflictType +import xyz.alexcrea.util.UnitRepairUtil.getRepair import kotlin.math.min /** @@ -40,61 +44,98 @@ class AnvilEventListener : Listener { fun anvilCombineCheck(event: PrepareAnvilEvent) { val inventory = event.inventory val first = inventory.getItem(ANVIL_INPUT_LEFT) ?: return - val second = inventory.getItem(ANVIL_INPUT_RIGHT) ?: return + val second = inventory.getItem(ANVIL_INPUT_RIGHT) - var anvilCost = 0 + // Should find player + val player = event.view.player + if(!player.hasPermission(UnsafeEnchants.unsafePermission)) return + + // Test rename lonely item + if(second == null){ + val resultItem = first.clone() + var anvilCost = handleRename(resultItem, inventory) + anvilCost+= calculatePenalty(first,null,resultItem) + + // Test/stop if nothing changed. + if(first == resultItem){ + event.result = null + return + } + // We do set item here as vanilla do all of our job (renaming) + + handleDisplayedXp(inventory, event, anvilCost) + return + } + + // Test for merge if (first.canMergeWith(second)) { - // Should find player - val player = event.view.player val newEnchants = first.findEnchantments() .combineWith(second.findEnchantments(), first.type, player) val resultItem = first.clone() resultItem.setEnchantmentsUnsafe(newEnchants) - anvilCost = calculatePenalty(first, second, resultItem) + var anvilCost = calculatePenalty(first, second, resultItem) anvilCost+= getRightValues(second, resultItem) - if (!first.isBook() && !second.isBook()) { + if (!first.isEnchantedBook() && !second.isEnchantedBook()) { // we only need to be concerned with repair when neither item is a book val repaired = resultItem.repairFrom(first, second) anvilCost += if(repaired) ConfigOptions.itemRepairCost else 0 } - // Test if nothing change and stop. + // Test/stop if nothing changed. if(first == resultItem){ event.result = null return } - // Rename item and add renaming cost - resultItem.itemMeta?.let { - if(!it.displayName.contentEquals(inventory.renameText)){ - it.setDisplayName(inventory.renameText) - anvilCost += ConfigOptions.itemRenameCost - resultItem.itemMeta = it - } - } + anvilCost+= handleRename(resultItem, inventory) if (ConfigOptions.limitRepairCost) { anvilCost = min(anvilCost, ConfigOptions.limitRepairValue) } - event.result = resultItem - /* Because Minecraft likes to have the final say in the repair cost displayed - * we need to wait for the event to end before overriding it, this ensures that - * we have the final say in the process. */ - UnsafeEnchants.instance - .server - .scheduler - .runTask(UnsafeEnchants.instance, Runnable { - if (ConfigOptions.removeRepairLimit) { - inventory.maximumRepairCost = Int.MAX_VALUE - } - inventory.repairCost = anvilCost - event.view.setProperty(REPAIR_COST, anvilCost) - }) + handleDisplayedXp(inventory, event, anvilCost) + return } + + // Test for unit repair + val unitRepairAmount = first.getRepair(second) + if(unitRepairAmount != null){ + val resultItem = first.clone() + var anvilCost = handleRename(resultItem, inventory) + // We do not care about right item penalty for unit repair + anvilCost+= calculatePenalty(first,null,resultItem) + + val repairAmount = resultItem.unitRepair(second.amount, unitRepairAmount) + if(repairAmount > 0){ + anvilCost += repairAmount*ConfigOptions.unitRepairCost + } + + // Test/stop if nothing changed. + if(first == resultItem){ + event.result = null + return + } + event.result = resultItem + + handleDisplayedXp(inventory, event, anvilCost) + }else{ + event.result = null + } + } + + private fun handleRename(resultItem: ItemStack, inventory: AnvilInventory): Int{ + // Rename item and add renaming cost + resultItem.itemMeta?.let { + if(!it.displayName.contentEquals(inventory.renameText)){ + it.setDisplayName(inventory.renameText) + resultItem.itemMeta = it + return ConfigOptions.itemRenameCost + } + } + return 0 } /** @@ -102,27 +143,89 @@ class AnvilEventListener : Listener { */ @EventHandler(ignoreCancelled = true) fun anvilExtractionCheck(event: InventoryClickEvent) { - //val player = event.whoClicked as? Player ?: return + val player = event.whoClicked as? Player ?: return + if(!player.hasPermission(UnsafeEnchants.unsafePermission)) return val inventory = event.inventory as? AnvilInventory ?: return if (event.rawSlot != ANVIL_OUTPUT_SLOT) { return } val output = inventory.getItem(ANVIL_OUTPUT_SLOT) ?: return - // Is true if there was no change. probably when there are conflict - if(output == inventory.getItem(ANVIL_INPUT_LEFT)){ + val leftItem = inventory.getItem(ANVIL_INPUT_LEFT) ?: return + val rightItem = inventory.getItem(ANVIL_INPUT_RIGHT) + + val canMerge = leftItem.canMergeWith(rightItem) + val unitRepairResult = leftItem.getRepair(rightItem) + val allowed = (rightItem == null) + || (canMerge) + || (unitRepairResult != null) + // True if there was no change or not allowed + if((output == inventory.getItem(ANVIL_INPUT_LEFT)) + || !allowed){ + event.result = Event.Result.DENY return } - event.result = Event.Result.ALLOW + if(rightItem == null){ + event.result = Event.Result.ALLOW + return + } + if(canMerge){ + event.result = Event.Result.ALLOW + }else if(unitRepairResult != null){ + val resultCopy = leftItem.clone() + val resultAmount = resultCopy.unitRepair( + rightItem.amount, unitRepairResult) + + // To avoid vanilla, we cancel the event for unit repair + event.result = Event.Result.DENY + event.isCancelled = true + // And we give the item manually + // But first we check if we should give the item + if(player.itemOnCursor.type != Material.AIR) return + if(inventory.repairCost > player.level) return + + // Get repairCost + var repairCost = 0 + leftItem.itemMeta?.let { leftMeta -> + val leftName = leftMeta.displayName + output.itemMeta?.let { + if(!leftName.contentEquals(it.displayName)){ + repairCost+= ConfigOptions.itemRenameCost + } + } + } + + repairCost+= calculatePenalty(leftItem,null,resultCopy) + repairCost+= resultAmount*ConfigOptions.unitRepairCost + + if((inventory.maximumRepairCost < repairCost) + || (player.level < repairCost)) return + + // We remove what should be removed + inventory.setItem(ANVIL_INPUT_LEFT,null) + rightItem.amount-= resultAmount + inventory.setItem(ANVIL_INPUT_RIGHT,rightItem) + inventory.setItem(ANVIL_OUTPUT_SLOT, null) + + UnsafeEnchants.log("repair cost: $repairCost") + player.level-= repairCost + + // Finally, we add the item to the player + player.setItemOnCursor(output) + + return + } } /** * Function to calculate work penalty of anvil work * Also change result work penalty */ - private fun calculatePenalty(left: ItemStack, right: ItemStack, result: ItemStack): Int{ + private fun calculatePenalty(left: ItemStack, right: ItemStack?, result: ItemStack): Int{ // Extracted From https://minecraft.fandom.com/wiki/Anvil_mechanics#Enchantment_equation // Calculate work penality val leftPenality = (left.itemMeta as? Repairable)?.repairCost ?: 0 - val rightPenality = (right.itemMeta as? Repairable)?.repairCost ?: 0 + val rightPenality = + if(right == null){ 0 } + else{ (right.itemMeta as? Repairable)?.repairCost ?: 0 } // Try to set work penality for the result item result.itemMeta?.let { @@ -147,7 +250,7 @@ class AnvilEventListener : Listener { var illegalPenalty = 0 var rightValue = 0 - val rightIsFormBook = right.isBook() + val rightIsFormBook = right.isEnchantedBook() val resultEnchs = result.findEnchantments() val resultEnchsKeys = HashSet(resultEnchs.keys) @@ -179,4 +282,28 @@ class AnvilEventListener : Listener { return rightValue + illegalPenalty } + /** + * Display xp needed for the work on the anvil inventory + */ + private fun handleDisplayedXp(inventory: AnvilInventory, + event: PrepareAnvilEvent, + anvilCost: Int){ + inventory.maximumRepairCost = Int.MAX_VALUE + inventory.repairCost = anvilCost + /* Because Minecraft likes to have the final say in the repair cost displayed + * we need to wait for the event to end before overriding it, this ensures that + * we have the final say in the process. */ + UnsafeEnchants.instance + .server + .scheduler + .runTask(UnsafeEnchants.instance, Runnable { + if (ConfigOptions.removeRepairLimit) { + inventory.maximumRepairCost = Int.MAX_VALUE + } + inventory.repairCost = anvilCost + + event.view.setProperty(REPAIR_COST, anvilCost) + }) + } + } diff --git a/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt b/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt index c142495..0e7ddf8 100644 --- a/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt +++ b/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt @@ -17,6 +17,8 @@ object ConfigOptions { private const val LIMIT_REPAIR_VALUE = "limit_repair_value" // Path for level cost on item repair private const val ITEM_REPAIR_COST = "item_repair_cost" + // Path for level cost on unit repair + private const val UNIT_REPAIR_COST = "unit_repair_cost" // Path for level cost on item renaming private const val ITEM_RENAME_COST = "item_rename_cost" // Path for level cost on illegal enchantment on sacrifice @@ -41,6 +43,8 @@ object ConfigOptions { private const val DEFAULT_LIMIT_REPAIR_VALUE = 39 // Default value for level cost on item repair private const val DEFAULT_ITEM_REPAIR_COST = 2 + // Default value for level cost per unit repair + private const val DEFAULT_UNIT_REPAIR_COST = 1 // Default value for level cost on item renaming private const val DEFAULT_ITEM_RENAME_COST = 1 // Default value for level cost on illegal enchantment on sacrifice @@ -48,7 +52,7 @@ object ConfigOptions { // Valid range for repair cost limit private val REPAIR_LIMIT_RANGE = 1..39 // Valid range for repair cost - private val ITEM_REPAIR_COST_RANGE = 0..255 + private val REPAIR_COST_RANGE = 0..255 // Valid range for rename cost private val ITEM_RENAME_COST_RANGE = 0..255 // Valid range for illegal enchantment conflict cost @@ -102,10 +106,22 @@ object ConfigOptions { return UnsafeEnchants.instance .config .getInt(ITEM_REPAIR_COST, DEFAULT_ITEM_REPAIR_COST) - .takeIf { it in ITEM_REPAIR_COST_RANGE } + .takeIf { it in REPAIR_COST_RANGE } ?: DEFAULT_ITEM_REPAIR_COST } + /** + * Value of an item repair + */ + val unitRepairCost: Int + get() { + return UnsafeEnchants.instance + .config + .getInt(UNIT_REPAIR_COST, DEFAULT_UNIT_REPAIR_COST) + .takeIf { it in REPAIR_COST_RANGE } + ?: DEFAULT_UNIT_REPAIR_COST + } + /** * Value of an item rename */ diff --git a/src/main/kotlin/io/delilaheve/util/EnchantmentUtil.kt b/src/main/kotlin/io/delilaheve/util/EnchantmentUtil.kt index b60adf2..2df46ab 100644 --- a/src/main/kotlin/io/delilaheve/util/EnchantmentUtil.kt +++ b/src/main/kotlin/io/delilaheve/util/EnchantmentUtil.kt @@ -31,17 +31,13 @@ object EnchantmentUtil { other.forEach { (enchantment, level) -> // Enchantment not yet in result list if (!containsKey(enchantment)) { - if(player.hasPermission(UnsafeEnchants.unsafePermission)){ - // Add the enchantment if it doesn't have conflicts, or, if player is allowed to bypass enchantment restrictions - this[enchantment] = level - if(!player.hasPermission(UnsafeEnchants.bypassFusePermission) && - (UnsafeEnchants.conflictManager.isConflicting(this.keys,mat,enchantment) != ConflictType.NO_CONFLICT)){ - this.remove(enchantment) - } - }else if(!keys.any { enchantment.conflictsWith(it) }){ - - this[enchantment] = level + // Add the enchantment if it doesn't have conflicts, or, if player is allowed to bypass enchantment restrictions + this[enchantment] = level + if(!player.hasPermission(UnsafeEnchants.bypassFusePermission) && + (UnsafeEnchants.conflictManager.isConflicting(this.keys,mat,enchantment) != ConflictType.NO_CONFLICT)){ + this.remove(enchantment) } + } // Enchantment already in result list else{ diff --git a/src/main/kotlin/io/delilaheve/util/ItemUtil.kt b/src/main/kotlin/io/delilaheve/util/ItemUtil.kt index bd46167..3e8763b 100644 --- a/src/main/kotlin/io/delilaheve/util/ItemUtil.kt +++ b/src/main/kotlin/io/delilaheve/util/ItemUtil.kt @@ -1,13 +1,13 @@ package io.delilaheve.util import io.delilaheve.UnsafeEnchants -import org.bukkit.Material.BOOK import org.bukkit.Material.ENCHANTED_BOOK import org.bukkit.enchantments.Enchantment import org.bukkit.inventory.ItemStack import org.bukkit.inventory.meta.Damageable import org.bukkit.inventory.meta.EnchantmentStorageMeta -import org.bukkit.inventory.meta.ItemMeta +import kotlin.math.ceil +import kotlin.math.max import kotlin.math.min /** @@ -15,20 +15,15 @@ import kotlin.math.min */ object ItemUtil { - /** - * Check if this [ItemStack] is a [BOOK] or [ENCHANTED_BOOK] - */ - fun ItemStack.isBook() = type in listOf(BOOK, ENCHANTED_BOOK) - /** * Check if this [ItemStack] is an [ENCHANTED_BOOK] */ - private fun ItemStack.isEnchantedBook() = type == ENCHANTED_BOOK + fun ItemStack.isEnchantedBook() = type == ENCHANTED_BOOK /** * Find the enchantment map for this [ItemStack] and return it as a [MutableMap] */ - fun ItemStack.findEnchantments() = if (isBook()) { + fun ItemStack.findEnchantments() = if (isEnchantedBook()) { (itemMeta as? EnchantmentStorageMeta)?.storedEnchants ?: emptyMap() } else { itemMeta?.enchants ?: emptyMap() @@ -38,7 +33,7 @@ object ItemUtil { * Apply an [enchantments] map to this [ItemStack] */ fun ItemStack.setEnchantmentsUnsafe(enchantments: Map) { - if (isBook()) { + if (isEnchantedBook()) { /* For some god-forsaken reason, item meta is not mutable * so, we have to get the instance, modify it, then set it * back to the item... #BecauseMinecraft */ @@ -88,18 +83,40 @@ object ItemUtil { val combinedDurability = firstDurability + secondDurability val newDurability = min(combinedDurability, durability) it.damage = durability - newDurability - itemMeta = it as ItemMeta + itemMeta = it return true } return false } + fun ItemStack.unitRepair( + unitAmount: Int, + percentPerUnit: Double + ): Int { + (itemMeta as? Damageable)?.let { + val durability = type.maxDurability.toInt() + val firstDamage = it.damage + if( firstDamage == 0) return 0 + var unitCount = 0 + var damage = firstDamage + while((unitCount < unitAmount) && (damage > 0)){ + unitCount++ + damage = ceil(firstDamage - durability*percentPerUnit*unitCount).toInt() + } + + it.damage = max(damage, 0) + itemMeta = it + return unitCount + } + return 0 + } + /** * Check that this [ItemStack] can merge with the [other] * * The two items should either be the same type, or, the [other] is a book */ fun ItemStack.canMergeWith( - other: ItemStack - ) = type == other.type || (other.isEnchantedBook()) + other: ItemStack? + ) = (other != null) && (type == other.type || (other.isEnchantedBook())) } diff --git a/src/main/kotlin/xyz/alexcrea/util/UnitRepairUtil.kt b/src/main/kotlin/xyz/alexcrea/util/UnitRepairUtil.kt index fd53415..7f1df1d 100644 --- a/src/main/kotlin/xyz/alexcrea/util/UnitRepairUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/util/UnitRepairUtil.kt @@ -16,15 +16,18 @@ object UnitRepairUtil { * null if can't unit repaired by [other] */ fun ItemStack.getRepair( - other: ItemStack + other: ItemStack? ): Double? { + if(other == null) return null val config = UnsafeEnchants.unitRepairConfig // Get configuration section if exist val otherName = other.type.name.uppercase() var section = config.getConfigurationSection(otherName) if(section == null){ section = config.getConfigurationSection(otherName.lowercase()) - if(section == null) return null + if(section == null) { + return null + } } // Get repair amount var userDefault = config.getDouble(UNIT_REPAIR_DEFAULT_PATH,DEFAULT_DEFAULT_UNIT_REPAIR) diff --git a/src/main/resources/unit_repair_item.yml b/src/main/resources/unit_repair_item.yml index 2eb6e0d..0e96878 100644 --- a/src/main/resources/unit_repair_item.yml +++ b/src/main/resources/unit_repair_item.yml @@ -11,8 +11,14 @@ # If value > 1 it will be treated as being = 1 default_repair_amount: 0.25 -# Vanilla unit repair group is bellow +# You can add custom unit repair +# The example bellow make a shield repaired by 10% by sticks +#stick: +# shield: 0.10 + + +# Vanilla unit repair group is bellow diamond: diamond_helmet: 0.25 diamond_chestplate: 0.25