unit repair done

This commit is contained in:
alexcrea 2024-02-09 00:25:12 +01:00
parent 80468f6add
commit 4195add655
6 changed files with 230 additions and 65 deletions

View file

@ -5,9 +5,12 @@ import io.delilaheve.util.EnchantmentUtil.combineWith
import io.delilaheve.util.EnchantmentUtil.enchantmentName import io.delilaheve.util.EnchantmentUtil.enchantmentName
import io.delilaheve.util.ItemUtil.canMergeWith import io.delilaheve.util.ItemUtil.canMergeWith
import io.delilaheve.util.ItemUtil.findEnchantments 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.repairFrom
import io.delilaheve.util.ItemUtil.setEnchantmentsUnsafe 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.Event
import org.bukkit.event.EventHandler import org.bukkit.event.EventHandler
import org.bukkit.event.EventPriority.HIGHEST 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.ItemStack
import org.bukkit.inventory.meta.Repairable import org.bukkit.inventory.meta.Repairable
import xyz.alexcrea.group.ConflictType import xyz.alexcrea.group.ConflictType
import xyz.alexcrea.util.UnitRepairUtil.getRepair
import kotlin.math.min import kotlin.math.min
/** /**
@ -40,61 +44,98 @@ class AnvilEventListener : Listener {
fun anvilCombineCheck(event: PrepareAnvilEvent) { fun anvilCombineCheck(event: PrepareAnvilEvent) {
val inventory = event.inventory val inventory = event.inventory
val first = inventory.getItem(ANVIL_INPUT_LEFT) ?: return 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
if (first.canMergeWith(second)) {
// Should find player // Should find player
val player = event.view.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)) {
val newEnchants = first.findEnchantments() val newEnchants = first.findEnchantments()
.combineWith(second.findEnchantments(), first.type, player) .combineWith(second.findEnchantments(), first.type, player)
val resultItem = first.clone() val resultItem = first.clone()
resultItem.setEnchantmentsUnsafe(newEnchants) resultItem.setEnchantmentsUnsafe(newEnchants)
anvilCost = calculatePenalty(first, second, resultItem) var anvilCost = calculatePenalty(first, second, resultItem)
anvilCost+= getRightValues(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 // we only need to be concerned with repair when neither item is a book
val repaired = resultItem.repairFrom(first, second) val repaired = resultItem.repairFrom(first, second)
anvilCost += if(repaired) ConfigOptions.itemRepairCost else 0 anvilCost += if(repaired) ConfigOptions.itemRepairCost else 0
} }
// Test if nothing change and stop. // Test/stop if nothing changed.
if(first == resultItem){ if(first == resultItem){
event.result = null event.result = null
return return
} }
// Rename item and add renaming cost anvilCost+= handleRename(resultItem, inventory)
resultItem.itemMeta?.let {
if(!it.displayName.contentEquals(inventory.renameText)){
it.setDisplayName(inventory.renameText)
anvilCost += ConfigOptions.itemRenameCost
resultItem.itemMeta = it
}
}
if (ConfigOptions.limitRepairCost) { if (ConfigOptions.limitRepairCost) {
anvilCost = min(anvilCost, ConfigOptions.limitRepairValue) anvilCost = min(anvilCost, ConfigOptions.limitRepairValue)
} }
event.result = resultItem event.result = resultItem
/* Because Minecraft likes to have the final say in the repair cost displayed handleDisplayedXp(inventory, event, anvilCost)
* we need to wait for the event to end before overriding it, this ensures that return
* 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) // 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) @EventHandler(ignoreCancelled = true)
fun anvilExtractionCheck(event: InventoryClickEvent) { 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 val inventory = event.inventory as? AnvilInventory ?: return
if (event.rawSlot != ANVIL_OUTPUT_SLOT) { return } if (event.rawSlot != ANVIL_OUTPUT_SLOT) { return }
val output = inventory.getItem(ANVIL_OUTPUT_SLOT) ?: return val output = inventory.getItem(ANVIL_OUTPUT_SLOT) ?: return
// Is true if there was no change. probably when there are conflict val leftItem = inventory.getItem(ANVIL_INPUT_LEFT) ?: return
if(output == inventory.getItem(ANVIL_INPUT_LEFT)){ 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 event.result = Event.Result.DENY
return return
} }
if(rightItem == null){
event.result = Event.Result.ALLOW 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 * Function to calculate work penalty of anvil work
* Also change result work penalty * 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 // Extracted From https://minecraft.fandom.com/wiki/Anvil_mechanics#Enchantment_equation
// Calculate work penality // Calculate work penality
val leftPenality = (left.itemMeta as? Repairable)?.repairCost ?: 0 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 // Try to set work penality for the result item
result.itemMeta?.let { result.itemMeta?.let {
@ -147,7 +250,7 @@ class AnvilEventListener : Listener {
var illegalPenalty = 0 var illegalPenalty = 0
var rightValue = 0 var rightValue = 0
val rightIsFormBook = right.isBook() val rightIsFormBook = right.isEnchantedBook()
val resultEnchs = result.findEnchantments() val resultEnchs = result.findEnchantments()
val resultEnchsKeys = HashSet(resultEnchs.keys) val resultEnchsKeys = HashSet(resultEnchs.keys)
@ -179,4 +282,28 @@ class AnvilEventListener : Listener {
return rightValue + illegalPenalty 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)
})
}
} }

View file

@ -17,6 +17,8 @@ object ConfigOptions {
private const val LIMIT_REPAIR_VALUE = "limit_repair_value" private const val LIMIT_REPAIR_VALUE = "limit_repair_value"
// Path for level cost on item repair // Path for level cost on item repair
private const val ITEM_REPAIR_COST = "item_repair_cost" 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 // Path for level cost on item renaming
private const val ITEM_RENAME_COST = "item_rename_cost" private const val ITEM_RENAME_COST = "item_rename_cost"
// Path for level cost on illegal enchantment on sacrifice // Path for level cost on illegal enchantment on sacrifice
@ -41,6 +43,8 @@ object ConfigOptions {
private const val DEFAULT_LIMIT_REPAIR_VALUE = 39 private const val DEFAULT_LIMIT_REPAIR_VALUE = 39
// Default value for level cost on item repair // Default value for level cost on item repair
private const val DEFAULT_ITEM_REPAIR_COST = 2 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 // Default value for level cost on item renaming
private const val DEFAULT_ITEM_RENAME_COST = 1 private const val DEFAULT_ITEM_RENAME_COST = 1
// Default value for level cost on illegal enchantment on sacrifice // Default value for level cost on illegal enchantment on sacrifice
@ -48,7 +52,7 @@ object ConfigOptions {
// Valid range for repair cost limit // Valid range for repair cost limit
private val REPAIR_LIMIT_RANGE = 1..39 private val REPAIR_LIMIT_RANGE = 1..39
// Valid range for repair cost // 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 // Valid range for rename cost
private val ITEM_RENAME_COST_RANGE = 0..255 private val ITEM_RENAME_COST_RANGE = 0..255
// Valid range for illegal enchantment conflict cost // Valid range for illegal enchantment conflict cost
@ -102,10 +106,22 @@ object ConfigOptions {
return UnsafeEnchants.instance return UnsafeEnchants.instance
.config .config
.getInt(ITEM_REPAIR_COST, DEFAULT_ITEM_REPAIR_COST) .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 ?: 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 * Value of an item rename
*/ */

View file

@ -31,17 +31,13 @@ object EnchantmentUtil {
other.forEach { (enchantment, level) -> other.forEach { (enchantment, level) ->
// Enchantment not yet in result list // Enchantment not yet in result list
if (!containsKey(enchantment)) { 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 // Add the enchantment if it doesn't have conflicts, or, if player is allowed to bypass enchantment restrictions
this[enchantment] = level this[enchantment] = level
if(!player.hasPermission(UnsafeEnchants.bypassFusePermission) && if(!player.hasPermission(UnsafeEnchants.bypassFusePermission) &&
(UnsafeEnchants.conflictManager.isConflicting(this.keys,mat,enchantment) != ConflictType.NO_CONFLICT)){ (UnsafeEnchants.conflictManager.isConflicting(this.keys,mat,enchantment) != ConflictType.NO_CONFLICT)){
this.remove(enchantment) this.remove(enchantment)
} }
}else if(!keys.any { enchantment.conflictsWith(it) }){
this[enchantment] = level
}
} }
// Enchantment already in result list // Enchantment already in result list
else{ else{

View file

@ -1,13 +1,13 @@
package io.delilaheve.util package io.delilaheve.util
import io.delilaheve.UnsafeEnchants import io.delilaheve.UnsafeEnchants
import org.bukkit.Material.BOOK
import org.bukkit.Material.ENCHANTED_BOOK import org.bukkit.Material.ENCHANTED_BOOK
import org.bukkit.enchantments.Enchantment import org.bukkit.enchantments.Enchantment
import org.bukkit.inventory.ItemStack import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.meta.Damageable import org.bukkit.inventory.meta.Damageable
import org.bukkit.inventory.meta.EnchantmentStorageMeta import org.bukkit.inventory.meta.EnchantmentStorageMeta
import org.bukkit.inventory.meta.ItemMeta import kotlin.math.ceil
import kotlin.math.max
import kotlin.math.min import kotlin.math.min
/** /**
@ -15,20 +15,15 @@ import kotlin.math.min
*/ */
object ItemUtil { 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] * 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] * 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() (itemMeta as? EnchantmentStorageMeta)?.storedEnchants ?: emptyMap()
} else { } else {
itemMeta?.enchants ?: emptyMap() itemMeta?.enchants ?: emptyMap()
@ -38,7 +33,7 @@ object ItemUtil {
* Apply an [enchantments] map to this [ItemStack] * Apply an [enchantments] map to this [ItemStack]
*/ */
fun ItemStack.setEnchantmentsUnsafe(enchantments: Map<Enchantment, Int>) { fun ItemStack.setEnchantmentsUnsafe(enchantments: Map<Enchantment, Int>) {
if (isBook()) { if (isEnchantedBook()) {
/* For some god-forsaken reason, item meta is not mutable /* For some god-forsaken reason, item meta is not mutable
* so, we have to get the instance, modify it, then set it * so, we have to get the instance, modify it, then set it
* back to the item... #BecauseMinecraft */ * back to the item... #BecauseMinecraft */
@ -88,18 +83,40 @@ object ItemUtil {
val combinedDurability = firstDurability + secondDurability val combinedDurability = firstDurability + secondDurability
val newDurability = min(combinedDurability, durability) val newDurability = min(combinedDurability, durability)
it.damage = durability - newDurability it.damage = durability - newDurability
itemMeta = it as ItemMeta itemMeta = it
return true return true
} }
return false 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] * Check that this [ItemStack] can merge with the [other]
* *
* The two items should either be the same type, or, the [other] is a book * The two items should either be the same type, or, the [other] is a book
*/ */
fun ItemStack.canMergeWith( fun ItemStack.canMergeWith(
other: ItemStack other: ItemStack?
) = type == other.type || (other.isEnchantedBook()) ) = (other != null) && (type == other.type || (other.isEnchantedBook()))
} }

View file

@ -16,15 +16,18 @@ object UnitRepairUtil {
* null if can't unit repaired by [other] * null if can't unit repaired by [other]
*/ */
fun ItemStack.getRepair( fun ItemStack.getRepair(
other: ItemStack other: ItemStack?
): Double? { ): Double? {
if(other == null) return null
val config = UnsafeEnchants.unitRepairConfig val config = UnsafeEnchants.unitRepairConfig
// Get configuration section if exist // Get configuration section if exist
val otherName = other.type.name.uppercase() val otherName = other.type.name.uppercase()
var section = config.getConfigurationSection(otherName) var section = config.getConfigurationSection(otherName)
if(section == null){ if(section == null){
section = config.getConfigurationSection(otherName.lowercase()) section = config.getConfigurationSection(otherName.lowercase())
if(section == null) return null if(section == null) {
return null
}
} }
// Get repair amount // Get repair amount
var userDefault = config.getDouble(UNIT_REPAIR_DEFAULT_PATH,DEFAULT_DEFAULT_UNIT_REPAIR) var userDefault = config.getDouble(UNIT_REPAIR_DEFAULT_PATH,DEFAULT_DEFAULT_UNIT_REPAIR)

View file

@ -11,8 +11,14 @@
# If value > 1 it will be treated as being = 1 # If value > 1 it will be treated as being = 1
default_repair_amount: 0.25 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:
diamond_helmet: 0.25 diamond_helmet: 0.25
diamond_chestplate: 0.25 diamond_chestplate: 0.25