Splitted main logic to be more readable

This commit is contained in:
alexcrea 2024-10-15 06:46:05 +02:00
parent 7029254526
commit 7c283dc7f8
No known key found for this signature in database
GPG key ID: 43FD265DB0DBF91F
8 changed files with 672 additions and 584 deletions

View file

@ -1,580 +0,0 @@
package io.delilaheve
import io.delilaheve.util.ConfigOptions
import io.delilaheve.util.EnchantmentUtil.combineWith
import io.delilaheve.util.ItemUtil.canMergeWith
import io.delilaheve.util.ItemUtil.findEnchantments
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.ChatColor
import org.bukkit.GameMode
import org.bukkit.Material
import org.bukkit.entity.HumanEntity
import org.bukkit.entity.Player
import org.bukkit.event.Event
import org.bukkit.event.EventHandler
import org.bukkit.event.EventPriority.HIGHEST
import org.bukkit.event.Listener
import org.bukkit.event.inventory.ClickType
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.event.inventory.InventoryCloseEvent
import org.bukkit.event.inventory.PrepareAnvilEvent
import org.bukkit.inventory.AnvilInventory
import org.bukkit.inventory.InventoryView.Property.REPAIR_COST
import org.bukkit.inventory.ItemStack
import xyz.alexcrea.cuanvil.config.ConfigHolder
import xyz.alexcrea.cuanvil.dependency.DependencyManager
import xyz.alexcrea.cuanvil.dependency.packet.PacketManager
import xyz.alexcrea.cuanvil.recipe.AnvilCustomRecipe
import xyz.alexcrea.cuanvil.util.AnvilXpUtil.calculatePenalty
import xyz.alexcrea.cuanvil.util.AnvilXpUtil.getRightValues
import xyz.alexcrea.cuanvil.util.AnvilXpUtil.setAnvilInvXp
import xyz.alexcrea.cuanvil.util.UnitRepairUtil.getRepair
import java.util.regex.Matcher
import java.util.regex.Pattern
import kotlin.math.min
/**
* Listener for anvil events
*/
class AnvilEventListener(private val packetManager: PacketManager) : Listener {
companion object {
// Anvil's output slot
const val ANVIL_INPUT_LEFT = 0
const val ANVIL_INPUT_RIGHT = 1
const val ANVIL_OUTPUT_SLOT = 2
// static slot container
private val NO_SLOT = SlotContainer(SlotType.NO_SLOT, 0)
private val CURSOR_SLOT = SlotContainer(SlotType.CURSOR, 0)
}
/**
* Event handler logic for when an anvil contains items to be combined
*/
@EventHandler(priority = HIGHEST, ignoreCancelled = true)
fun anvilCombineCheck(event: PrepareAnvilEvent) {
// Test if the event should bypass custom anvil.
if(DependencyManager.tryEventPreAnvilBypass(event)) return
val inventory = event.inventory
val first = inventory.getItem(ANVIL_INPUT_LEFT) ?: return
val second = inventory.getItem(ANVIL_INPUT_RIGHT)
// Should find player
val player = event.view.player
if (!player.hasPermission(CustomAnvil.affectedByPluginPermission)) return
// Test custom recipe
val recipe = getCustomRecipe(first, second)
CustomAnvil.verboseLog("custom recipe not null? ${recipe != null}")
if(recipe != null){
val amount = getCustomRecipeAmount(recipe, first, second)
val resultItem: ItemStack = recipe.resultItem!!.clone()
resultItem.amount *= amount
event.result = resultItem
setAnvilInvXp(inventory, event.view, recipe.xpCostPerCraft * amount, true)
return
}
// Test rename lonely item
if (second == null) {
val resultItem = first.clone()
var anvilCost = handleRename(resultItem, inventory, player)
// Test/stop if nothing changed.
if (first == resultItem) {
CustomAnvil.log("no right item, But input is same as output")
event.result = null
return
}
event.result = resultItem
anvilCost += calculatePenalty(first, null, resultItem)
setAnvilInvXp(inventory, event.view, anvilCost)
return
}
// Test for merge
if (first.canMergeWith(second)) {
val newEnchants = first.findEnchantments()
.combineWith(second.findEnchantments(), first, player)
val resultItem = first.clone()
resultItem.setEnchantmentsUnsafe(newEnchants)
// Calculate enchantment cost
var anvilCost = getRightValues(second, resultItem)
// 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)
anvilCost += if (repaired) ConfigOptions.itemRepairCost else 0
}
// Test/stop if nothing changed.
if (first == resultItem) {
CustomAnvil.log("Mergable with second, But input is same as output")
event.result = null
return
}
// As calculatePenalty edit result, we need to calculate penalty after checking equality
anvilCost += calculatePenalty(first, second, resultItem)
// Calculate rename cost
anvilCost += handleRename(resultItem, inventory, player)
// Finally, we set result
event.result = resultItem
setAnvilInvXp(inventory, event.view, anvilCost)
return
}
// Test for unit repair
val unitRepairAmount = first.getRepair(second)
if (unitRepairAmount != null) {
val resultItem = first.clone()
var anvilCost = handleRename(resultItem, inventory, player)
val repairAmount = resultItem.unitRepair(second.amount, unitRepairAmount)
if (repairAmount > 0) {
anvilCost += repairAmount * ConfigOptions.unitRepairCost
}
// We do not care about right item penalty for unit repair
anvilCost += calculatePenalty(first, null, resultItem, true)
// Test/stop if nothing changed.
if (first == resultItem) {
CustomAnvil.log("unit repair, But input is same as output")
event.result = null
return
}
event.result = resultItem
setAnvilInvXp(inventory, event.view, anvilCost)
} else {
CustomAnvil.log("no anvil fuse type found")
event.result = null
}
}
private fun handleRename(resultItem: ItemStack, inventory: AnvilInventory, player: HumanEntity): Int {
// Rename item and add renaming cost
resultItem.itemMeta?.let {
val displayName = ChatColor.stripColor(it.displayName)
var inventoryName = ChatColor.stripColor(inventory.renameText)
var sumCost = 0
var useColor = false
if(ConfigOptions.renameColorPossible){
val resultString = StringBuilder(inventoryName)
useColor = handleRenamingColor(resultString, player)
if(useColor) {
inventoryName = resultString.toString()
sumCost+= ConfigOptions.useOfColorCost
}
}
if ((!useColor && (!displayName.contentEquals(inventoryName))) || (useColor && !(it.displayName).contentEquals(inventoryName))) {
it.setDisplayName(inventoryName)
resultItem.itemMeta = it
sumCost+= ConfigOptions.itemRenameCost
}
return sumCost
}
return 0
}
private fun handleRenamingColor(textToColor: StringBuilder, player: HumanEntity): Boolean {
val usePermission = ConfigOptions.permissionNeededForColor
val canUseColorCode = ConfigOptions.allowColorCode && (!usePermission || player.hasPermission("ca.color.code"))
val canUseHexColor = ConfigOptions.allowHexadecimalColor && (!usePermission || player.hasPermission("ca.color.hex"))
if((!canUseColorCode) && (!canUseHexColor)) return false
var useColor = false
// Handle color code
if(canUseColorCode){
var nbReplacement = replaceAll(textToColor, "&", "§", 2)
nbReplacement -= 2 * replaceAll(textToColor, "§§", "&", 2)
if(nbReplacement > 0) useColor = true
}
if(canUseHexColor){
val nbReplacement = replaceHexToColor(textToColor, 7)
if(nbReplacement > 0) useColor = true
}
return useColor
}
/**
* Replace every instance of "from" to "to".
* @param builder The builder to replace the string from.
* @param from The source that should be replaced.
* @param to The string that should replace.
* @param endOffset Amount of character that should be ignored at the end.
* @return The number of replacement was that was done.
*/
private fun replaceAll(builder: java.lang.StringBuilder, from: String, to: String, endOffset: Int): Int {
var index = builder.indexOf(from)
var numberOfChanges = 0
while (index != -1 && index < builder.length - endOffset) {
builder.replace(index, index + from.length, to)
index += to.length
index = builder.indexOf(from, index)
numberOfChanges+=1
}
return numberOfChanges
}
val HEX_PATTERN: Pattern = Pattern.compile("#[A-Fa-f0-9]{6}") // pattern to find hexadecimal string
/**
* Replace every hex color formatted like #000000 to the minecraft format
* @param builder The builder to replace the hex color from.
* @param endOffset Amount of character that should be ignored at the end.
* @return The number of replacement was that was done.
*/
private fun replaceHexToColor(builder: StringBuilder, endOffset: Int): Int {
val matcher: Matcher = HEX_PATTERN.matcher(builder)
var numberOfChanges = 0
var startIndex = 0
while(matcher.find(startIndex)){
startIndex = matcher.start()
if(startIndex >= builder.length - endOffset) break
builder.replace(startIndex, startIndex + 1, "§x")
startIndex+=2
for (i in 0..5) {
builder.insert(startIndex, '§')
startIndex+=2
}
numberOfChanges+=1
}
return numberOfChanges
}
/**
* Event handler logic for when a player is trying to pull an item out of the anvil
*/
@EventHandler(ignoreCancelled = true)
fun anvilExtractionCheck(event: InventoryClickEvent) {
val player = event.whoClicked as? Player ?: return
if (!player.hasPermission(CustomAnvil.affectedByPluginPermission)) return
val inventory = event.inventory as? AnvilInventory ?: return
if (event.rawSlot != ANVIL_OUTPUT_SLOT) {
return
}
// Test if the event should bypass custom anvil.
if(DependencyManager.tryClickAnvilResultBypass(event, inventory)) return
val output = inventory.getItem(ANVIL_OUTPUT_SLOT) ?: return
val leftItem = inventory.getItem(ANVIL_INPUT_LEFT) ?: return
val rightItem = inventory.getItem(ANVIL_INPUT_RIGHT)
// Test custom recipe
val recipe = getCustomRecipe(leftItem, rightItem)
if(recipe != null){
event.result = Event.Result.ALLOW
onCustomCraft(
event, recipe, player,
leftItem, rightItem, output, inventory)
return
}
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
}
if (rightItem == null) {
event.result = Event.Result.ALLOW
return
}
if (canMerge) {
event.result = Event.Result.ALLOW
} else if (unitRepairResult != null) {
onUnitRepairExtract(
leftItem, rightItem, output,
unitRepairResult, event, player, inventory
)
return
}
}
private fun onCustomCraft(event: InventoryClickEvent,
recipe: AnvilCustomRecipe,
player: Player,
leftItem: ItemStack,
rightItem: ItemStack?,
output: ItemStack,
inventory: AnvilInventory) {
event.result = Event.Result.DENY
if(recipe.leftItem == null) return // in case it changed
val amount = getCustomRecipeAmount(recipe, leftItem, rightItem)
val xpCost = amount * recipe.xpCostPerCraft
if ((player.gameMode != GameMode.CREATIVE) && (player.level < xpCost)) return
// We give the item manually
// But first we check if we should give the item
val slotDestination = getActionSlot(event, player)
if (slotDestination.type == SlotType.NO_SLOT) return
// If not creative middle click...
if (event.click != ClickType.MIDDLE) {
// We remove what should be removed
leftItem.amount -= amount * recipe.leftItem!!.amount
inventory.setItem(ANVIL_INPUT_LEFT, leftItem)
if(rightItem != null){
if(recipe.rightItem == null) return // in case it changed
rightItem.amount -= amount * recipe.rightItem!!.amount
inventory.setItem(ANVIL_INPUT_RIGHT, rightItem)
}
if(player.gameMode != GameMode.CREATIVE){
player.level -= xpCost
}
// Then we try to find the new values for the anvil
val newAmount = getCustomRecipeAmount(recipe, leftItem, rightItem)
CustomAnvil.verboseLog("new amount is $newAmount")
if(newAmount <= 0 || recipe.exactCount){
inventory.setItem(ANVIL_OUTPUT_SLOT, null)
}else{
val resultItem: ItemStack = recipe.resultItem!!.clone()
resultItem.amount *= newAmount
val newXp = newAmount * newAmount
inventory.repairCost = newXp
event.view.setProperty(REPAIR_COST, newXp)
inventory.setItem(ANVIL_OUTPUT_SLOT, resultItem)
player.updateInventory()
}
}
// Finally, we add the item to the player
if (slotDestination.type == SlotType.CURSOR) {
player.setItemOnCursor(output)
} else {// We assume SlotType == SlotType.INVENTORY
player.inventory.setItem(slotDestination.slot, output)
}
}
private fun onUnitRepairExtract(
leftItem: ItemStack,
rightItem: ItemStack,
output: ItemStack,
unitRepairResult: Double,
event: InventoryClickEvent,
player: Player,
inventory: AnvilInventory
) {
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
val slotDestination = getActionSlot(event, player)
if (slotDestination.type == SlotType.NO_SLOT) return
// Test repair cost
var repairCost = 0
if (player.gameMode != GameMode.CREATIVE) {
// Get repairCost
leftItem.itemMeta?.let { leftMeta ->
val leftName = leftMeta.displayName
output.itemMeta?.let {
// Rename cost
if (!leftName.contentEquals(it.displayName)) {
repairCost += ConfigOptions.itemRenameCost
// Color cost
if(it.displayName.contains('§')){
repairCost += ConfigOptions.useOfColorCost
}
}
}
}
repairCost += calculatePenalty(leftItem, null, resultCopy)
repairCost += resultAmount * ConfigOptions.unitRepairCost
if (
!ConfigOptions.doRemoveCostLimit &&
ConfigOptions.doCapCost) {
repairCost = min(repairCost, ConfigOptions.maxAnvilCost)
}
if ((inventory.maximumRepairCost <= repairCost)
|| (player.level < repairCost)
) return
}
// If not creative middle click...
if (event.click != ClickType.MIDDLE) {
// 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)
player.level -= repairCost
}
// Finally, we add the item to the player
if (slotDestination.type == SlotType.CURSOR) {
player.setItemOnCursor(output)
} else {// We assume SlotType == SlotType.INVENTORY
player.inventory.setItem(slotDestination.slot, output)
}
}
/**
* Get the destination slot or "NO_SLOT" slot container if there is no slot available
*/
private fun getActionSlot(event: InventoryClickEvent, player: Player): SlotContainer {
if (event.isShiftClick) {
val inventory = player.inventory
val firstEmpty = inventory.firstEmpty()
if (firstEmpty == -1) {
return NO_SLOT
}
//check hotbare full
var slotIndex = 8
while (slotIndex >= 0 && ((inventory.getItem(slotIndex)?.type ?: Material.AIR) != Material.AIR)) {
slotIndex--
}
if (slotIndex >= 0) {
return SlotContainer(SlotType.INVENTORY, slotIndex)
}
slotIndex = 35 //4*9 - 1 (max of player inventory)
while (slotIndex >= 9 && ((inventory.getItem(slotIndex)?.type ?: Material.AIR) != Material.AIR)) {
slotIndex--
}
if (slotIndex < 9) {
return NO_SLOT
}
return SlotContainer(SlotType.INVENTORY, slotIndex)
} else {
if (player.itemOnCursor.type != Material.AIR) {
return NO_SLOT
}
return CURSOR_SLOT
}
}
private fun getCustomRecipe (
leftItem: ItemStack,
rightItem: ItemStack?) : AnvilCustomRecipe? {
val recipeList = ConfigHolder.CUSTOM_RECIPE_HOLDER.recipeManager.recipeByMat[leftItem.type] ?: return null
CustomAnvil.verboseLog("Testing " + recipeList.size+" recipe...")
for (recipe in recipeList) {
if(recipe.testItem(leftItem, rightItem)){
return recipe
}
}
return null
}
private fun getCustomRecipeAmount(
recipe: AnvilCustomRecipe,
leftItem: ItemStack,
rightItem: ItemStack?
): Int{
return if(recipe.exactCount) {
if(leftItem.amount != recipe.leftItem!!.amount){
0
}else if(rightItem != null && rightItem.amount != recipe.rightItem!!.amount){
0
}else{
1
}
}
else {
// test amount
val resultItem = recipe.resultItem!! // we know exist as the recipe was returned to us
val maxResultAmount = resultItem.type.maxStackSize/resultItem.amount
val maxLeftAmount = leftItem.amount/recipe.leftItem!!.amount
val maxRightAmount = if(rightItem == null){ maxLeftAmount } else{ rightItem.amount/recipe.rightItem!!.amount }
CustomAnvil.verboseLog("resultItem: $resultItem, maxResultAmount: $maxResultAmount, maxLeftAmount: $maxLeftAmount, maxRightAmount: $maxRightAmount")
min(min(maxResultAmount, maxLeftAmount), maxRightAmount)
}
}
@EventHandler
fun onAnvilClose(event: InventoryCloseEvent){
val player = event.player
if(event.inventory !is AnvilInventory) return
if(player is Player && GameMode.CREATIVE != player.gameMode){
packetManager.setInstantBuild(player, false)
}
}
}
private class SlotContainer(val type: SlotType, val slot: Int)
private enum class SlotType {
CURSOR,
INVENTORY,
NO_SLOT
}

View file

@ -13,7 +13,10 @@ import xyz.alexcrea.cuanvil.dependency.DependencyManager
import xyz.alexcrea.cuanvil.enchant.CAEnchantmentRegistry import xyz.alexcrea.cuanvil.enchant.CAEnchantmentRegistry
import xyz.alexcrea.cuanvil.gui.config.MainConfigGui import xyz.alexcrea.cuanvil.gui.config.MainConfigGui
import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant
import xyz.alexcrea.cuanvil.listener.AnvilCloseListener
import xyz.alexcrea.cuanvil.listener.AnvilResultListener
import xyz.alexcrea.cuanvil.listener.ChatEventListener import xyz.alexcrea.cuanvil.listener.ChatEventListener
import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener
import xyz.alexcrea.cuanvil.update.PluginSetDefault import xyz.alexcrea.cuanvil.update.PluginSetDefault
import xyz.alexcrea.cuanvil.update.Update_1_21 import xyz.alexcrea.cuanvil.update.Update_1_21
import xyz.alexcrea.cuanvil.update.plugin.PluginUpdates import xyz.alexcrea.cuanvil.update.plugin.PluginUpdates
@ -110,7 +113,9 @@ class CustomAnvil : JavaPlugin() {
DependencyManager.loadDependency() DependencyManager.loadDependency()
// Register anvil events // Register anvil events
server.pluginManager.registerEvents(AnvilEventListener(DependencyManager.packetManager), this) server.pluginManager.registerEvents(PrepareAnvilListener(), this)
server.pluginManager.registerEvents(AnvilResultListener(), this)
server.pluginManager.registerEvents(AnvilCloseListener(DependencyManager.packetManager), this)
// Load metrics // Load metrics
Metrics(this, bstatsPluginId) Metrics(this, bstatsPluginId)

View file

@ -4,13 +4,13 @@ import cz.kominekjan.disenchantment.events.DisenchantClickEvent
import cz.kominekjan.disenchantment.events.DisenchantEvent import cz.kominekjan.disenchantment.events.DisenchantEvent
import cz.kominekjan.disenchantment.events.ShatterClickEvent import cz.kominekjan.disenchantment.events.ShatterClickEvent
import cz.kominekjan.disenchantment.events.ShatterEvent import cz.kominekjan.disenchantment.events.ShatterEvent
import io.delilaheve.AnvilEventListener
import io.delilaheve.CustomAnvil import io.delilaheve.CustomAnvil
import org.bukkit.event.inventory.InventoryClickEvent import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.event.inventory.PrepareAnvilEvent import org.bukkit.event.inventory.PrepareAnvilEvent
import org.bukkit.inventory.AnvilInventory import org.bukkit.inventory.AnvilInventory
import org.bukkit.inventory.ItemStack import org.bukkit.inventory.ItemStack
import org.bukkit.plugin.RegisteredListener import org.bukkit.plugin.RegisteredListener
import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener
import xyz.alexcrea.cuanvil.util.AnvilXpUtil import xyz.alexcrea.cuanvil.util.AnvilXpUtil
class DisenchantmentDependency { class DisenchantmentDependency {
@ -96,7 +96,7 @@ class DisenchantmentDependency {
} }
fun testAnvilResult(event: InventoryClickEvent, inventory: AnvilInventory): Boolean { fun testAnvilResult(event: InventoryClickEvent, inventory: AnvilInventory): Boolean {
val previousResultSlot = inventory.getItem(AnvilEventListener.ANVIL_OUTPUT_SLOT)?.clone() val previousResultSlot = inventory.getItem(PrepareAnvilListener.ANVIL_OUTPUT_SLOT)?.clone()
// Test event if change the result // Test event if change the result
itemClickEvent.onDisenchantmentClickEvent(event) itemClickEvent.onDisenchantmentClickEvent(event)
@ -115,7 +115,7 @@ class DisenchantmentDependency {
} }
private fun testAnvilInventoryChange(inventory: AnvilInventory, previous: ItemStack?): Boolean { private fun testAnvilInventoryChange(inventory: AnvilInventory, previous: ItemStack?): Boolean {
val currentResult = inventory.getItem(AnvilEventListener.ANVIL_OUTPUT_SLOT) val currentResult = inventory.getItem(PrepareAnvilListener.ANVIL_OUTPUT_SLOT)
return currentResult == previous return currentResult == previous
} }

View file

@ -0,0 +1,23 @@
package xyz.alexcrea.cuanvil.listener
import org.bukkit.GameMode
import org.bukkit.entity.Player
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.inventory.InventoryCloseEvent
import org.bukkit.inventory.AnvilInventory
import xyz.alexcrea.cuanvil.dependency.packet.PacketManager
class AnvilCloseListener(private val packetManager: PacketManager) : Listener {
@EventHandler
fun onAnvilClose(event: InventoryCloseEvent){
val player = event.player
if(event.inventory !is AnvilInventory) return
if(player is Player && GameMode.CREATIVE != player.gameMode){
packetManager.setInstantBuild(player, false)
}
}
}

View file

@ -0,0 +1,292 @@
package xyz.alexcrea.cuanvil.listener
import io.delilaheve.CustomAnvil
import io.delilaheve.util.ConfigOptions
import io.delilaheve.util.ItemUtil.canMergeWith
import io.delilaheve.util.ItemUtil.unitRepair
import org.bukkit.GameMode
import org.bukkit.Material
import org.bukkit.entity.Player
import org.bukkit.event.Event
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.inventory.ClickType
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.inventory.AnvilInventory
import org.bukkit.inventory.InventoryView
import org.bukkit.inventory.ItemStack
import xyz.alexcrea.cuanvil.dependency.DependencyManager
import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener.Companion.ANVIL_INPUT_LEFT
import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener.Companion.ANVIL_INPUT_RIGHT
import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener.Companion.ANVIL_OUTPUT_SLOT
import xyz.alexcrea.cuanvil.recipe.AnvilCustomRecipe
import xyz.alexcrea.cuanvil.util.AnvilXpUtil
import xyz.alexcrea.cuanvil.util.CustomRecipeUtil
import xyz.alexcrea.cuanvil.util.UnitRepairUtil.getRepair
import kotlin.math.min
class AnvilResultListener: Listener {
companion object {
// static slot container
private val NO_SLOT = SlotContainer(SlotType.NO_SLOT, 0)
private val CURSOR_SLOT = SlotContainer(SlotType.CURSOR, 0)
}
/**
* Event handler logic for when a player is trying to pull an item out of the anvil
*/
@EventHandler(ignoreCancelled = true)
fun anvilExtractionCheck(event: InventoryClickEvent) {
val player = event.whoClicked as? Player ?: return
if (!player.hasPermission(CustomAnvil.affectedByPluginPermission)) return
val inventory = event.inventory as? AnvilInventory ?: return
if (event.rawSlot != ANVIL_OUTPUT_SLOT) {
return
}
// Test if the event should bypass custom anvil.
if(DependencyManager.tryClickAnvilResultBypass(event, inventory)) return
val output = inventory.getItem(ANVIL_OUTPUT_SLOT) ?: return
val leftItem = inventory.getItem(ANVIL_INPUT_LEFT) ?: return
val rightItem = inventory.getItem(ANVIL_INPUT_RIGHT)
// Test custom recipe
val recipe = CustomRecipeUtil.getCustomRecipe(leftItem, rightItem)
if(recipe != null){
event.result = Event.Result.ALLOW
onCustomCraft(
event, recipe, player,
leftItem, rightItem, output, inventory)
return
}
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
}
if (rightItem == null) {
event.result = Event.Result.ALLOW
return
}
if (canMerge) {
event.result = Event.Result.ALLOW
} else if (unitRepairResult != null) {
onUnitRepairExtract(
leftItem, rightItem, output,
unitRepairResult, event, player, inventory
)
return
}
}
private fun onCustomCraft(event: InventoryClickEvent,
recipe: AnvilCustomRecipe,
player: Player,
leftItem: ItemStack,
rightItem: ItemStack?,
output: ItemStack,
inventory: AnvilInventory
) {
event.result = Event.Result.DENY
if(recipe.leftItem == null) return // in case it changed
val amount = CustomRecipeUtil.getCustomRecipeAmount(recipe, leftItem, rightItem)
val xpCost = amount * recipe.xpCostPerCraft
if ((player.gameMode != GameMode.CREATIVE) && (player.level < xpCost)) return
// We give the item manually
// But first we check if we should give the item
val slotDestination = getActionSlot(event, player)
if (slotDestination.type == SlotType.NO_SLOT) return
// Handle not creative middle click...
if (event.click != ClickType.MIDDLE &&
!handleCustomCraftClick(event, recipe, inventory, player, leftItem, rightItem, amount, xpCost)) return;
// Finally, we add the item to the player
if (slotDestination.type == SlotType.CURSOR) {
player.setItemOnCursor(output)
} else {// We assume SlotType == SlotType.INVENTORY
player.inventory.setItem(slotDestination.slot, output)
}
}
private fun handleCustomCraftClick(event: InventoryClickEvent, recipe: AnvilCustomRecipe,
inventory: AnvilInventory, player: Player,
leftItem: ItemStack, rightItem: ItemStack?,
amount: Int, xpCost: Int): Boolean {
// We remove what should be removed
if(rightItem != null){
if(recipe.rightItem == null) return false// in case it changed
rightItem.amount -= amount * recipe.rightItem!!.amount
inventory.setItem(ANVIL_INPUT_RIGHT, rightItem)
}
leftItem.amount -= amount * recipe.leftItem!!.amount
inventory.setItem(ANVIL_INPUT_LEFT, leftItem)
if(player.gameMode != GameMode.CREATIVE){
player.level -= xpCost
}
// Then we try to find the new values for the anvil
val newAmount = CustomRecipeUtil.getCustomRecipeAmount(recipe, leftItem, rightItem)
CustomAnvil.verboseLog("new amount is $newAmount")
if(newAmount <= 0 || recipe.exactCount){
inventory.setItem(ANVIL_OUTPUT_SLOT, null)
}else{
val resultItem: ItemStack = recipe.resultItem!!.clone()
resultItem.amount *= newAmount
val newXp = newAmount * newAmount
inventory.repairCost = newXp
event.view.setProperty(InventoryView.Property.REPAIR_COST, newXp)
inventory.setItem(ANVIL_OUTPUT_SLOT, resultItem)
player.updateInventory()
}
return true
}
private fun onUnitRepairExtract(
leftItem: ItemStack,
rightItem: ItemStack,
output: ItemStack,
unitRepairResult: Double,
event: InventoryClickEvent,
player: Player,
inventory: AnvilInventory
) {
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
val slotDestination = getActionSlot(event, player)
if (slotDestination.type == SlotType.NO_SLOT) return
// Test repair cost
val repairCost = getUnitRepairCost(inventory, player, leftItem, output, resultCopy, resultAmount)
if(repairCost == Int.MIN_VALUE) return
// If not creative middle click...
if (event.click != ClickType.MIDDLE) {
// 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)
player.level -= repairCost
}
// Finally, we add the item to the player
if (slotDestination.type == SlotType.CURSOR) {
player.setItemOnCursor(output)
} else {// We assume SlotType == SlotType.INVENTORY
player.inventory.setItem(slotDestination.slot, output)
}
}
private fun getUnitRepairCost(inventory: AnvilInventory, player: Player,
leftItem: ItemStack, output: ItemStack,
resultCopy: ItemStack, resultAmount: Int): Int {
if (player.gameMode == GameMode.CREATIVE) return 0
var repairCost = 0;
// Get repairCost
leftItem.itemMeta?.let { leftMeta ->
val leftName = leftMeta.displayName
output.itemMeta?.let {
// Rename cost
if (!leftName.contentEquals(it.displayName)) {
repairCost += ConfigOptions.itemRenameCost
// Color cost
if(it.displayName.contains('§')){
repairCost += ConfigOptions.useOfColorCost
}
}
}
}
repairCost += AnvilXpUtil.calculatePenalty(leftItem, null, resultCopy)
repairCost += resultAmount * ConfigOptions.unitRepairCost
if (
!ConfigOptions.doRemoveCostLimit &&
ConfigOptions.doCapCost
) {
repairCost = min(repairCost, ConfigOptions.maxAnvilCost)
}
if ((inventory.maximumRepairCost <= repairCost)
|| (player.level < repairCost)
) return Int.MIN_VALUE
return repairCost
}
/**
* Get the destination slot or "NO_SLOT" slot container if there is no slot available
*/
private fun getActionSlot(event: InventoryClickEvent, player: Player): SlotContainer {
if (event.isShiftClick) {
val inventory = player.inventory
val firstEmpty = inventory.firstEmpty()
if (firstEmpty == -1) {
return NO_SLOT
}
//check hotbare full
var slotIndex = 8
while (slotIndex >= 0 && ((inventory.getItem(slotIndex)?.type ?: Material.AIR) != Material.AIR)) {
slotIndex--
}
if (slotIndex >= 0) {
return SlotContainer(SlotType.INVENTORY, slotIndex)
}
slotIndex = 35 //4*9 - 1 (max of player inventory)
while (slotIndex >= 9 && ((inventory.getItem(slotIndex)?.type ?: Material.AIR) != Material.AIR)) {
slotIndex--
}
if (slotIndex < 9) {
return NO_SLOT
}
return SlotContainer(SlotType.INVENTORY, slotIndex)
}
else if (player.itemOnCursor.type != Material.AIR) return NO_SLOT
return CURSOR_SLOT
}
private class SlotContainer(val type: SlotType, val slot: Int)
private enum class SlotType {
CURSOR,
INVENTORY,
NO_SLOT
}
}

View file

@ -0,0 +1,206 @@
package xyz.alexcrea.cuanvil.listener
import io.delilaheve.CustomAnvil
import io.delilaheve.util.ConfigOptions
import io.delilaheve.util.EnchantmentUtil.combineWith
import io.delilaheve.util.ItemUtil.canMergeWith
import io.delilaheve.util.ItemUtil.findEnchantments
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.ChatColor
import org.bukkit.entity.HumanEntity
import org.bukkit.event.EventHandler
import org.bukkit.event.EventPriority
import org.bukkit.event.Listener
import org.bukkit.event.inventory.PrepareAnvilEvent
import org.bukkit.inventory.AnvilInventory
import org.bukkit.inventory.ItemStack
import xyz.alexcrea.cuanvil.dependency.DependencyManager
import xyz.alexcrea.cuanvil.util.AnvilColorUtil
import xyz.alexcrea.cuanvil.util.AnvilXpUtil
import xyz.alexcrea.cuanvil.util.CustomRecipeUtil
import xyz.alexcrea.cuanvil.util.UnitRepairUtil.getRepair
/**
* Listener for anvil events
*/
class PrepareAnvilListener : Listener {
companion object {
// Anvil's output slot
const val ANVIL_INPUT_LEFT = 0
const val ANVIL_INPUT_RIGHT = 1
const val ANVIL_OUTPUT_SLOT = 2
}
/**
* Event handler logic for when an anvil contains items to be combined
*/
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
fun anvilCombineCheck(event: PrepareAnvilEvent) {
// Test if the event should bypass custom anvil.
if(DependencyManager.tryEventPreAnvilBypass(event)) return
val inventory = event.inventory
val first = inventory.getItem(ANVIL_INPUT_LEFT) ?: return
val second = inventory.getItem(ANVIL_INPUT_RIGHT)
// Should find player
val player = event.view.player
if (!player.hasPermission(CustomAnvil.affectedByPluginPermission)) return
// Test custom recipe
if(testCustomRecipe(event, inventory, first, second)) return
// Test rename lonely item
if(second == null) {
doRenaming(event, inventory, player, first)
return
}
// Test for merge
if (first.canMergeWith(second)) {
doMerge(event, inventory, player, first, second)
return
}
// Test for unit repair
if(testUnitRepair(event, inventory, player, first, second)) return
CustomAnvil.log("no anvil fuse type found")
event.result = null
}
private fun testCustomRecipe(event: PrepareAnvilEvent, inventory: AnvilInventory, first: ItemStack, second: ItemStack?): Boolean {
val recipe = CustomRecipeUtil.getCustomRecipe(first, second)
CustomAnvil.verboseLog("custom recipe not null? ${recipe != null}")
if(recipe == null) return false
val amount = CustomRecipeUtil.getCustomRecipeAmount(recipe, first, second)
val resultItem: ItemStack = recipe.resultItem!!.clone()
resultItem.amount *= amount
event.result = resultItem
AnvilXpUtil.setAnvilInvXp(inventory, event.view, recipe.xpCostPerCraft * amount, true)
return true
}
private fun doRenaming(event: PrepareAnvilEvent, inventory: AnvilInventory,
player: HumanEntity, first: ItemStack) {
val resultItem = first.clone()
var anvilCost = handleRename(resultItem, inventory, player)
// Test/stop if nothing changed.
if (first == resultItem) {
CustomAnvil.log("no right item, But input is same as output")
event.result = null
return
}
event.result = resultItem
anvilCost += AnvilXpUtil.calculatePenalty(first, null, resultItem)
AnvilXpUtil.setAnvilInvXp(inventory, event.view, anvilCost)
}
private fun handleRename(resultItem: ItemStack, inventory: AnvilInventory, player: HumanEntity): Int {
// Rename item and add renaming cost
resultItem.itemMeta?.let {
val displayName = ChatColor.stripColor(it.displayName)
var inventoryName = ChatColor.stripColor(inventory.renameText)
var sumCost = 0
var useColor = false
if(ConfigOptions.renameColorPossible){
val resultString = StringBuilder(inventoryName)
useColor = AnvilColorUtil.handleRenamingColor(resultString, player)
if(useColor) {
inventoryName = resultString.toString()
sumCost+= ConfigOptions.useOfColorCost
}
}
if ((!useColor && (!displayName.contentEquals(inventoryName))) || (useColor && !(it.displayName).contentEquals(inventoryName))) {
it.setDisplayName(inventoryName)
resultItem.itemMeta = it
sumCost+= ConfigOptions.itemRenameCost
}
return sumCost
}
return 0
}
private fun doMerge(event: PrepareAnvilEvent, inventory: AnvilInventory,
player: HumanEntity,
first: ItemStack, second: ItemStack) {
val newEnchants = first.findEnchantments()
.combineWith(second.findEnchantments(), first, player)
val resultItem = first.clone()
resultItem.setEnchantmentsUnsafe(newEnchants)
// Calculate enchantment cost
var anvilCost = AnvilXpUtil.getRightValues(second, resultItem)
// 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)
anvilCost += if (repaired) ConfigOptions.itemRepairCost else 0
}
// Test/stop if nothing changed.
if (first == resultItem) {
CustomAnvil.log("Mergable with second, But input is same as output")
event.result = null
return
}
// As calculatePenalty edit result, we need to calculate penalty after checking equality
anvilCost += AnvilXpUtil.calculatePenalty(first, second, resultItem)
// Calculate rename cost
anvilCost += handleRename(resultItem, inventory, player)
// Finally, we set result
event.result = resultItem
AnvilXpUtil.setAnvilInvXp(inventory, event.view, anvilCost)
}
private fun testUnitRepair(event: PrepareAnvilEvent, inventory: AnvilInventory, player: HumanEntity,
first: ItemStack, second: ItemStack): Boolean {
val unitRepairAmount = first.getRepair(second) ?: return false
val resultItem = first.clone()
var anvilCost = handleRename(resultItem, inventory, player)
val repairAmount = resultItem.unitRepair(second.amount, unitRepairAmount)
if (repairAmount > 0) {
anvilCost += repairAmount * ConfigOptions.unitRepairCost
}
// We do not care about right item penalty for unit repair
anvilCost += AnvilXpUtil.calculatePenalty(first, null, resultItem, true)
// Test/stop if nothing changed.
if (first == resultItem) {
CustomAnvil.log("unit repair, But input is same as output")
event.result = null
return true
}
event.result = resultItem
AnvilXpUtil.setAnvilInvXp(inventory, event.view, anvilCost)
return true
}
}

View file

@ -0,0 +1,88 @@
package xyz.alexcrea.cuanvil.util
import io.delilaheve.util.ConfigOptions
import org.bukkit.entity.HumanEntity
import java.util.regex.Matcher
import java.util.regex.Pattern
object AnvilColorUtil {
private val HEX_PATTERN: Pattern = Pattern.compile("#[A-Fa-f0-9]{6}") // pattern to find hexadecimal string
fun handleRenamingColor(textToColor: StringBuilder, player: HumanEntity): Boolean {
val usePermission = ConfigOptions.permissionNeededForColor
val canUseColorCode = ConfigOptions.allowColorCode && (!usePermission || player.hasPermission("ca.color.code"))
val canUseHexColor = ConfigOptions.allowHexadecimalColor && (!usePermission || player.hasPermission("ca.color.hex"))
if((!canUseColorCode) && (!canUseHexColor)) return false
var useColor = false
// Handle color code
if(canUseColorCode){
var nbReplacement = replaceAll(textToColor, "&", "§", 2)
nbReplacement -= 2 * replaceAll(textToColor, "§§", "&", 2)
if(nbReplacement > 0) useColor = true
}
if(canUseHexColor){
val nbReplacement = replaceHexToColor(textToColor, 7)
if(nbReplacement > 0) useColor = true
}
return useColor
}
/**
* Replace every instance of "from" to "to".
* @param builder The builder to replace the string from.
* @param from The source that should be replaced.
* @param to The string that should replace.
* @param endOffset Amount of character that should be ignored at the end.
* @return The number of replacement was that was done.
*/
private fun replaceAll(builder: java.lang.StringBuilder, from: String, to: String, endOffset: Int): Int {
var index = builder.indexOf(from)
var numberOfChanges = 0
while (index != -1 && index < builder.length - endOffset) {
builder.replace(index, index + from.length, to)
index += to.length
index = builder.indexOf(from, index)
numberOfChanges+=1
}
return numberOfChanges
}
/**
* Replace every hex color formatted like #000000 to the minecraft format
* @param builder The builder to replace the hex color from.
* @param endOffset Amount of character that should be ignored at the end.
* @return The number of replacement was that was done.
*/
private fun replaceHexToColor(builder: StringBuilder, endOffset: Int): Int {
val matcher: Matcher = HEX_PATTERN.matcher(builder)
var numberOfChanges = 0
var startIndex = 0
while(matcher.find(startIndex)){
startIndex = matcher.start()
if(startIndex >= builder.length - endOffset) break
builder.replace(startIndex, startIndex + 1, "§x")
startIndex+=2
for (i in 0..5) {
builder.insert(startIndex, '§')
startIndex+=2
}
numberOfChanges+=1
}
return numberOfChanges
}
}

View file

@ -0,0 +1,54 @@
package xyz.alexcrea.cuanvil.util
import io.delilaheve.CustomAnvil
import org.bukkit.inventory.ItemStack
import xyz.alexcrea.cuanvil.config.ConfigHolder
import xyz.alexcrea.cuanvil.recipe.AnvilCustomRecipe
import kotlin.math.min
object CustomRecipeUtil {
fun getCustomRecipe (
leftItem: ItemStack,
rightItem: ItemStack?) : AnvilCustomRecipe? {
val recipeList = ConfigHolder.CUSTOM_RECIPE_HOLDER.recipeManager.recipeByMat[leftItem.type] ?: return null
CustomAnvil.verboseLog("Testing " + recipeList.size + " recipe...")
for (recipe in recipeList) {
if(recipe.testItem(leftItem, rightItem)){
return recipe
}
}
return null
}
fun getCustomRecipeAmount(
recipe: AnvilCustomRecipe,
leftItem: ItemStack,
rightItem: ItemStack?
): Int{
return if(recipe.exactCount) {
if(leftItem.amount != recipe.leftItem!!.amount){
0
}else if(rightItem != null && rightItem.amount != recipe.rightItem!!.amount){
0
}else{
1
}
}
else {
// test amount
val resultItem = recipe.resultItem!! // we know exist as the recipe was returned to us
val maxResultAmount = resultItem.type.maxStackSize/resultItem.amount
val maxLeftAmount = leftItem.amount/recipe.leftItem!!.amount
val maxRightAmount = if(rightItem == null){ maxLeftAmount } else{ rightItem.amount/recipe.rightItem!!.amount }
CustomAnvil.verboseLog("resultItem: $resultItem, maxResultAmount: $maxResultAmount, maxLeftAmount: $maxLeftAmount, maxRightAmount: $maxRightAmount")
min(min(maxResultAmount, maxLeftAmount), maxRightAmount)
}
}
}