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

View file

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

View file

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

View file

@ -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<Enchantment, Int>) {
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()))
}

View file

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