CustomAnvil/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt
2026-06-30 07:49:39 +02:00

330 lines
No EOL
12 KiB
Kotlin

package xyz.alexcrea.cuanvil.anvil
import io.delilaheve.CustomAnvil
import io.delilaheve.util.ConfigOptions
import io.delilaheve.util.EnchantmentUtil.combineWith
import io.delilaheve.util.ItemUtil.isEnchantedBook
import io.delilaheve.util.ItemUtil.repairFrom
import io.delilaheve.util.ItemUtil.unitRepair
import org.bukkit.ChatColor
import org.bukkit.Material
import org.bukkit.entity.HumanEntity
import org.bukkit.entity.Player
import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.meta.ItemMeta
import org.bukkit.inventory.view.AnvilView
import org.bukkit.persistence.PersistentDataType
import xyz.alexcrea.cuanvil.api.EnchantmentApi
import xyz.alexcrea.cuanvil.dependency.DependencyManager
import xyz.alexcrea.cuanvil.dialog.AnvilRenameDialog
import xyz.alexcrea.cuanvil.enchant.CAEnchantment
import xyz.alexcrea.cuanvil.recipe.AnvilCustomRecipe
import xyz.alexcrea.cuanvil.util.CasedStringUtil
import xyz.alexcrea.cuanvil.util.CustomRecipeUtil
import xyz.alexcrea.cuanvil.util.MaterialUtil.isAir
import xyz.alexcrea.cuanvil.util.MiniMessageUtil
import xyz.alexcrea.cuanvil.util.UnitRepairUtil.getRepair
import xyz.alexcrea.cuanvil.util.anvil.AnvilColorUtil
import xyz.alexcrea.cuanvil.util.anvil.AnvilLoreEditUtil
import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil
import xyz.alexcrea.cuanvil.util.config.LoreEditType
import xyz.alexcrea.cuanvil.util.dialog.AnvilRenameDialogUtil
@Suppress("UnstableApiUsage")
object AnvilMergeLogic {
open class AnvilResult {
companion object {
val EMPTY = AnvilResult(null, AnvilCost())
}
val item: ItemStack?
val cost: AnvilCost
val ignoreXpRules: Boolean
constructor(item: ItemStack?, cost: AnvilCost, ignoreXpRules: Boolean = false) {
this.item = item
this.cost = cost
this.ignoreXpRules = ignoreXpRules
}
fun isEmpty(): Boolean {
return item == null
}
}
class UnitRepairResult : AnvilResult {
companion object {
val EMPTY = UnitRepairResult(null, AnvilCost(), 0)
}
val repairAmount: Int
constructor(item: ItemStack?, cost: AnvilCost, repairAmount: Int) : super(item, cost) {
this.repairAmount = repairAmount
}
}
class CustomCraftResult : AnvilResult {
companion object {
val EMPTY = CustomCraftResult(null, CustomCraftCost(0), 0, null)
}
val customCraftCost: CustomCraftCost
val amount: Int
val recipe: AnvilCustomRecipe?
constructor(
item: ItemStack?, cost: CustomCraftCost,
amount: Int, recipe: AnvilCustomRecipe?
) : super(item, cost, true) {
this.customCraftCost = cost
this.amount = amount
this.recipe = recipe
}
}
class LoreEditResult : AnvilResult {
companion object {
val EMPTY = LoreEditResult(null, AnvilCost(), LoreEditType.APPEND_PAPER)
}
val type: LoreEditType
constructor(item: ItemStack?, cost: AnvilCost, type: LoreEditType) : super(item, cost) {
this.type = type
}
}
fun doRenaming(
view: AnvilView,
player: Player, first: ItemStack
): AnvilResult {
val resultItem = DependencyManager.cloneItem(player, first)
val cost = AnvilCost()
cost.rename = handleRename(resultItem, view, player)
// Test/stop if nothing changed.
if (first == resultItem) {
CustomAnvil.log("no right item, But input is same as output")
return AnvilResult.EMPTY
}
cost.workPenalty = AnvilXpUtil.calculatePenalty(first, null, resultItem, AnvilUseType.RENAME_ONLY)
val result =
DependencyManager.tryTreatAnvilResult(view, player, resultItem, AnvilUseType.RENAME_ONLY, cost)
return AnvilResult(result, cost)
}
private fun processDialogPCD(meta: ItemMeta, player: HumanEntity) {
val text = AnvilRenameDialogUtil.anvilRenameDialog.currentText(player)
return processPCD(meta, player, text)
}
fun processPCD(meta: ItemMeta, player: HumanEntity, text: String?) {
val keepDialog = ConfigOptions.canUseDialogRename(player) && ConfigOptions.shouldKeepRenameText
val pdc = meta.persistentDataContainer
if (!keepDialog)
pdc.remove(AnvilRenameDialog.PCD_KEEP_RENAME_TEXT_KEY)
else {
if (text == null || text.isBlank())
pdc.remove(AnvilRenameDialog.PCD_KEEP_RENAME_TEXT_KEY)
else pdc.set(AnvilRenameDialog.PCD_KEEP_RENAME_TEXT_KEY, PersistentDataType.STRING, text)
}
}
private fun handleRename(resultItem: ItemStack, view: AnvilView, player: HumanEntity): Int {
// Can be null
var renameText = ChatColor.stripColor(view.renameText)
var sumCost = 0
var useColor = false
if (ConfigOptions.renameColorPossible && renameText != null) {
val component = AnvilColorUtil.handleColor(
renameText,
AnvilColorUtil.renamePermission(player)
)
if (component != null) {
renameText = MiniMessageUtil.legacy_mm.serialize(component)
sumCost += ConfigOptions.useOfColorCost
useColor = true
}
}
// Rename item and add renaming cost
resultItem.itemMeta?.let {
val hasDisplayName = it.hasDisplayName()
val displayName = if (!hasDisplayName) null
else if (useColor) it.displayName
else ChatColor.stripColor(it.displayName)
if (!displayName.contentEquals(renameText) && !(displayName == null &&
renameText == "" ||
//TODO on recent paper check effective name instead
renameText == CasedStringUtil.snakeToUpperSpacedCase(resultItem.type.name.lowercase())
)
) {
it.setDisplayName(renameText)
processDialogPCD(it, player)
resultItem.itemMeta = it
sumCost += ConfigOptions.itemRenameCost
}
return sumCost
}
return 0
}
fun doMerge(
view: AnvilView,
player: Player,
first: ItemStack, second: ItemStack
): AnvilResult {
val firstEnchants = EnchantmentApi.getEnchantments(first)
val secondEnchants = EnchantmentApi.getEnchantments(second)
// newEnchants will be mutated by combineWith
val newEnchants = HashMap(firstEnchants)
newEnchants.combineWith(secondEnchants, first, player)
var hasChanged = !isIdentical(firstEnchants, newEnchants)
val resultItem = DependencyManager.cloneItem(player, first)
val cost = AnvilCost()
if (hasChanged) {
EnchantmentApi.setEnchantments(resultItem, newEnchants)
// Calculate enchantment cost
AnvilXpUtil.getRightValues(first, second, resultItem, cost)
}
// Calculate repair cost
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)
cost.repair = if (repaired) ConfigOptions.itemRepairCost else 0
hasChanged = hasChanged || repaired
}
// Test/stop if nothing changed.
if (!hasChanged) {
CustomAnvil.log("Mergeable with second, But input is same as output")
return AnvilResult.EMPTY
}
// As calculatePenalty edit result, we need to calculate penalty after checking equality
cost.workPenalty = AnvilXpUtil.calculatePenalty(first, second, resultItem, AnvilUseType.MERGE)
// Calculate rename cost
cost.rename = handleRename(resultItem, view, player)
val result =
DependencyManager.tryTreatAnvilResult(view, player, resultItem, AnvilUseType.MERGE, cost)
return AnvilResult(result, cost)
}
private fun isIdentical(
firstEnchants: MutableMap<CAEnchantment, Int>,
resultEnchants: MutableMap<CAEnchantment, Int>
): Boolean {
if (firstEnchants.size != resultEnchants.size) return false
for (entry in resultEnchants) {
if (firstEnchants.getOrDefault(entry.key, entry.value - 1) != entry.value) return false
}
return true
}
// return true if a custom recipe exist with these ingredients
fun testCustomRecipe(
view: AnvilView,
player: Player,
first: ItemStack, second: ItemStack?
): CustomCraftResult {
val recipe = CustomRecipeUtil.getCustomRecipe(first, second)
CustomAnvil.verboseLog("custom recipe not null? ${recipe != null}")
if (recipe == null) return CustomCraftResult.EMPTY
val amount = CustomRecipeUtil.getCustomRecipeAmount(recipe, first, second)
val resultItem: ItemStack = DependencyManager.cloneItem(player, recipe.resultItem!!)
resultItem.amount *= amount
// Maybe add an option on custom craft to ignore/not ignore penalty ??
val xpCost = recipe.determineCost(amount, first, resultItem)
val cost = CustomCraftCost(xpCost)
// This is for displayed cost
cost.recipe = if (recipe.removeExactLinearXp) AnvilXpUtil.calculateMinimumLevelForXp(xpCost)
else AnvilXpUtil.calculateLevelForXp(xpCost)
val result =
DependencyManager.tryTreatAnvilResult(view, player, resultItem, AnvilUseType.CUSTOM_CRAFT, cost)
return CustomCraftResult(result, cost, amount, recipe)
}
fun testUnitRepair(
view: AnvilView,
player: Player,
first: ItemStack, second: ItemStack
): UnitRepairResult {
val unitRepairAmount = first.getRepair(second) ?: return UnitRepairResult.EMPTY
return testUnitRepair(view, player, first, second, unitRepairAmount)
}
fun testUnitRepair(
view: AnvilView,
player: Player,
first: ItemStack, second: ItemStack,
unitRepairAmount: Double
): UnitRepairResult {
val resultItem = DependencyManager.cloneItem(player, first)
val cost = AnvilCost()
cost.rename = handleRename(resultItem, view, player)
val repairAmount = resultItem.unitRepair(second.amount, unitRepairAmount)
if (repairAmount > 0)
cost.repair = repairAmount * ConfigOptions.unitRepairCost
// We do not care about right item penalty for unit repair
cost.workPenalty = AnvilXpUtil.calculatePenalty(first, null, resultItem, AnvilUseType.UNIT_REPAIR)
// Test/stop if nothing changed.
if (first == resultItem) {
CustomAnvil.log("unit repair, But input is same as output")
return UnitRepairResult.EMPTY
}
val result =
DependencyManager.tryTreatAnvilResult(view, player, resultItem, AnvilUseType.UNIT_REPAIR, cost)
return UnitRepairResult(result, cost, repairAmount)
}
fun testLoreEdit(
player: Player,
first: ItemStack, second: ItemStack
): LoreEditResult {
val type = second.type
val result = if (Material.WRITABLE_BOOK == type)
AnvilLoreEditUtil.tryLoreEditByBook(player, first, second)
else if (Material.PAPER == type)
AnvilLoreEditUtil.tryLoreEditByPaper(player, first, second)
else LoreEditResult.EMPTY
if (result.isEmpty()) return result
if (result.item!!.isAir || first == result.item) {
CustomAnvil.log("lore edit, But input is same as output")
return LoreEditResult.EMPTY
}
return result
}
}