CustomAnvil/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt
alexcrea e30f09120d
Work with eco item (#106)
Use material key instead of material enum

This is made with the goal of making eco item work as independant item.
so item type key now depend on eco item's id 

Known issue: EcoEnchant target group still do not has the eco item id inside it and cannot be handled properly by custom anvil
2026-04-21 15:18:37 +02:00

312 lines
No EOL
11 KiB
Kotlin

package xyz.alexcrea.cuanvil.group
import io.delilaheve.CustomAnvil
import org.bukkit.NamespacedKey
import org.bukkit.configuration.ConfigurationSection
import org.bukkit.enchantments.Enchantment
import org.bukkit.inventory.ItemStack
import xyz.alexcrea.cuanvil.enchant.AdditionalTestEnchantment
import xyz.alexcrea.cuanvil.enchant.CAEnchantment
import xyz.alexcrea.cuanvil.enchant.CAEnchantmentRegistry
import xyz.alexcrea.cuanvil.util.MaterialUtil.customType
import java.util.*
import kotlin.collections.set
class EnchantConflictManager {
companion object {
// Path for the enchantments list
const val ENCH_LIST_PATH = "enchantments"
// Path for list of enchantments conflicting before level
//TODO add test and gui
const val CONFLICT_AFTER_LEVEL_LIST_PATH = "conflict_after_level"
// Path for list of enchantments conflicting before level
//TODO add test and gui
const val CONFLICT_BEFORE_LEVEL_LIST_PATH = "conflict_before_level"
// Path for group list related to the conflict
const val CONFLICT_GROUP_PATH = "notAffectedGroups"
// Path for the maximum number of enchantment before validating the conflict
const val ENCH_MAX_PATH = "maxEnchantmentBeforeConflict"
// Path for a flag: if the enchantment will be used in the last supported version
// TODO maybe replace this system by a list of "future" enchantment.
private const val FUTURE_USE_PATH = "useInFuture"
// Default name for a joining group
const val DEFAULT_GROUP_NAME = "joinedGroup"
// 1.20.5 compatibility TODO better update system
private val SWEEPING_EDGE_ENCHANT = Collections.singletonList<CAEnchantment>(
CAEnchantment.getByKey(NamespacedKey.minecraft("sweeping_edge"))
?: CAEnchantment.getByKey(Enchantment.SWEEPING_EDGE.key)
)
}
lateinit var conflictList: ArrayList<EnchantConflictGroup>
private fun warnBadKey(key: String) {
CustomAnvil.instance.logger.warning("Invalid key $key for conflict: is not a conflict")
}
// Read and prepare all conflict
fun prepareConflicts(config: ConfigurationSection, itemManager: ItemGroupManager) {
conflictList = ArrayList()
// Clear conflict if exist
for (enchant in CAEnchantmentRegistry.getInstance().values()) {
enchant.clearConflict()
}
val keys = config.getKeys(false)
for (key in keys) {
val section = config.getConfigurationSection(key)
if(section == null) {
warnBadKey(key)
continue
}
val conflict = createConflict(section, itemManager, key)
addConflict(conflict)
}
}
fun addConflict(conflict: EnchantConflictGroup) {
addConflictToEnchantments(conflict)
conflictList.add(conflict)
}
fun removeConflict(conflict: EnchantConflictGroup) {
removeConflictFromEnchantments(conflict)
conflictList.remove(conflict)
}
// Add the conflict to enchantments
private fun addConflictToEnchantments(conflict: EnchantConflictGroup) {
conflict.getEnchants().forEach { enchant ->
enchant.addConflict(conflict)
}
}
// Remove the conflict from enchantments
private fun removeConflictFromEnchantments(conflict: EnchantConflictGroup) {
conflict.getEnchants().forEach { enchant ->
enchant.removeConflict(conflict)
}
}
// create and read a conflict from a yaml section
private fun createConflict(
section: ConfigurationSection,
itemManager: ItemGroupManager,
conflictName: String
): EnchantConflictGroup {
// Is it planed for the future
val futureUse = section.getBoolean(FUTURE_USE_PATH, false)
// Create conflict
val conflict = createConflictObject(section, itemManager, conflictName)
// Read and add enchantment to conflict
val enchantList = section.getStringList(ENCH_LIST_PATH)
for (enchantName in enchantList) {
val enchants = getEnchantByIdentifier(enchantName)
if (enchants.isEmpty()) {
if (!futureUse) { //TODO future use will be deprecated once the new update system is finished
CustomAnvil.instance.logger.warning("Enchantment $enchantName do not exist but was asked for conflict $conflictName")
}
continue
}
conflict.addEnchantments(enchants)
}
if (conflict.getEnchants().isEmpty()) {
if (!futureUse) { //TODO future use will be deprecated once the new update system is finished
CustomAnvil.instance.logger.warning("Conflict $conflictName do not have valid enchantment, it will not do anything")
}
}
val conflictsAfterLevel = section.getConfigurationSection(CONFLICT_AFTER_LEVEL_LIST_PATH)
val conflictsAfterMap = conflict.getConflictAfters()
fetchConditionalRestriction(conflictsAfterMap, conflictsAfterLevel, conflictName)
val conflictsBeforeLevel = section.getConfigurationSection(CONFLICT_BEFORE_LEVEL_LIST_PATH)
val conflictsBeforeMap = conflict.getConflictsBefore()
fetchConditionalRestriction(conflictsBeforeMap, conflictsBeforeLevel, conflictName)
return conflict
}
private fun fetchConditionalRestriction(restrictions: MutableMap<CAEnchantment, Int>, section: ConfigurationSection?, conflictName: String) {
if(section == null) return
for (enchantName in section.getKeys(false)) {
val enchants = getEnchantByIdentifier(enchantName)
if (enchants.isEmpty()) {
CustomAnvil.instance.logger.warning("Enchantment $enchantName do not exist but was asked for conditional restriction for conflict $conflictName")
continue
}
val value = section.getInt(enchantName, -1)
if(value < 0) continue
for (enchant in enchants) {
val previous = restrictions.getOrDefault(enchant, value)
restrictions[enchant] = value.coerceAtMost(previous)
}
}
}
private fun getEnchantByIdentifier(enchantName: String): List<CAEnchantment> {
val key = NamespacedKey.fromString(enchantName)
if (key != null) {
val enchantment = CAEnchantment.getByKey(key)
if (enchantment != null) return Collections.singletonList(enchantment)
}
// Temporary solution for 1.20.5
when (enchantName) {
"minecraft:sweeping", "sweeping",
"minecraft:sweeping_edge", "sweeping_edge" -> {
return SWEEPING_EDGE_ENCHANT
}
}
return CAEnchantment.getListByName(enchantName)
}
private fun createConflictObject(
section: ConfigurationSection,
itemManager: ItemGroupManager,
conflictName: String
): EnchantConflictGroup {
// Get the maximum number of enchantment before validating the conflict
var minBeforeBlock = section.getInt(ENCH_MAX_PATH, 0)
if (minBeforeBlock < 0) {
minBeforeBlock = 0
CustomAnvil.instance.logger.warning("Conflict $conflictName have an invalid value of $ENCH_MAX_PATH")
CustomAnvil.instance.logger.warning("It should be more or equal to 0. default to 0")
}
// Find or create the selected group for the conflict
val groupList = section.getStringList(CONFLICT_GROUP_PATH)
val finalGroup = IncludeGroup(DEFAULT_GROUP_NAME)
for (groupName in groupList) {
finalGroup.addToPolicy(findGroup(groupName, itemManager, conflictName))
}
// Return conflict
return EnchantConflictGroup(conflictName, finalGroup, minBeforeBlock)
}
private fun findGroup(
groupName: String,
itemManager: ItemGroupManager,
conflictName: String
): AbstractMaterialGroup {
val group = itemManager.get(groupName)
if (group == null) {
CustomAnvil.instance.logger.warning("Material group $groupName do not exist but is ask by conflict $conflictName")
return IncludeGroup("error_placeholder")
}
return group
}
fun isConflicting(
appliedEnchants: Map<CAEnchantment, Int>,
item: ItemStack,
newEnchant: CAEnchantment
): ConflictType {
val type = item.customType
CustomAnvil.verboseLog("Testing conflict for ${newEnchant.key} on ${type}")
val conflictList = newEnchant.conflicts
var result = ConflictType.NO_CONFLICT
for (conflict in conflictList) {
val isBigConflict = conflict.getEnchants().size > 1
if (result == ConflictType.ITEM_CONFLICT && !isBigConflict) {
CustomAnvil.verboseLog("skipping small conflict ${conflict.name}")
continue
}
val allowed = conflict.allowed(appliedEnchants, type)
CustomAnvil.verboseLog("Was against $conflict and conflicting: ${!allowed} ")
if (!allowed) {
if (conflict.getEnchants().size <= 1) {
result = ConflictType.ITEM_CONFLICT
CustomAnvil.verboseLog("Small conflict (${conflict.name}), continuing")
} else {
CustomAnvil.verboseLog("Big conflict (${conflict.name}), stopping")
return ConflictType.ENCHANTMENT_CONFLICT
}
}
}
val immutableEnchants = Collections.unmodifiableMap(appliedEnchants)
for (appliedEnchant in appliedEnchants.keys) {
if (appliedEnchant is AdditionalTestEnchantment) {
val doConflict = appliedEnchant.isEnchantConflict(immutableEnchants, type)
if (doConflict) {
CustomAnvil.verboseLog("Big conflict by additional test, stopping")
return ConflictType.ENCHANTMENT_CONFLICT
}
}
}
if ((result != ConflictType.ITEM_CONFLICT) && (newEnchant is AdditionalTestEnchantment)) {
val partialItem = createPartialResult(item, immutableEnchants)
if (newEnchant.isItemConflict(immutableEnchants, type, partialItem)) {
return ConflictType.ITEM_CONFLICT
}
}
return result
}
private fun createPartialResult(item: ItemStack, enchantments: Map<CAEnchantment, Int>): ItemStack {
val newItem = item.clone()
CAEnchantment.clearEnchants(newItem)
enchantments.forEach { enchantment ->
enchantment.key.addEnchantmentUnsafe(newItem, enchantment.value)
}
return newItem
}
}
/**
* Provide information about the current conflict.
*/
enum class ConflictType(private val importance: Int) {
/**
* Allowed to proceed the anvil process.
*/
NO_CONFLICT(0),
/**
* Inform that the anvil process should not change the current applied enchantment.
*/
ITEM_CONFLICT(1),
/**
* Inform that the anvil process should not change the current applied enchantment.
* Also add sacrificeIllegalCost for every enchantment marked as big conflict.
*/
ENCHANTMENT_CONFLICT(2);
fun getWorstConflict(otherConflict: ConflictType): ConflictType {
return if (this.importance > otherConflict.importance) this
else otherConflict
}
}