Fix getting cost for color renaming causing null pointer exception when
inventory name is null (Fix #47)
Add safety when executing dependency handling code
This commit is contained in:
alexcrea 2025-01-25 02:13:05 +01:00 committed by GitHub
parent 5f557e3d49
commit 33474c379a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 130 additions and 34 deletions

View file

@ -16,7 +16,7 @@ plugins {
} }
group = "xyz.alexcrea" group = "xyz.alexcrea"
version = "1.7.0" version = "1.7.1"
repositories { repositories {
// EcoEnchants // EcoEnchants

View file

@ -48,8 +48,8 @@ object ConfigOptions {
private const val KEY_ITEM = "item" private const val KEY_ITEM = "item"
// Debug flag // Debug flag
private const val DEBUG_LOGGING = "debug_log" const val DEBUG_LOGGING = "debug_log"
private const val VERBOSE_DEBUG_LOGGING = "debug_log_verbose" const val VERBOSE_DEBUG_LOGGING = "debug_log_verbose"
// ---------------------- // ----------------------
// Default config values // Default config values

View file

@ -2,6 +2,7 @@ package xyz.alexcrea.cuanvil.dependency
import io.delilaheve.CustomAnvil import io.delilaheve.CustomAnvil
import org.bukkit.Bukkit import org.bukkit.Bukkit
import org.bukkit.ChatColor
import org.bukkit.entity.HumanEntity import org.bukkit.entity.HumanEntity
import org.bukkit.event.inventory.InventoryClickEvent import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.event.inventory.PrepareAnvilEvent import org.bukkit.event.inventory.PrepareAnvilEvent
@ -15,6 +16,8 @@ import xyz.alexcrea.cuanvil.dependency.packet.PacketManagerSelector
import xyz.alexcrea.cuanvil.dependency.scheduler.BukkitScheduler import xyz.alexcrea.cuanvil.dependency.scheduler.BukkitScheduler
import xyz.alexcrea.cuanvil.dependency.scheduler.FoliaScheduler import xyz.alexcrea.cuanvil.dependency.scheduler.FoliaScheduler
import xyz.alexcrea.cuanvil.dependency.scheduler.TaskScheduler import xyz.alexcrea.cuanvil.dependency.scheduler.TaskScheduler
import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener.Companion.ANVIL_OUTPUT_SLOT
import java.util.logging.Level
object DependencyManager { object DependencyManager {
@ -96,10 +99,25 @@ object DependencyManager {
// Then handle plugin reload // Then handle plugin reload
ecoEnchantCompatibility?.handleConfigReload() ecoEnchantCompatibility?.handleConfigReload()
} }
// Return true if should bypass (either by a dependency or error)
fun tryEventPreAnvilBypass(event: PrepareAnvilEvent, player: HumanEntity): Boolean { fun tryEventPreAnvilBypass(event: PrepareAnvilEvent, player: HumanEntity): Boolean {
try {
return unsafeTryEventPreAnvilBypass(event, player)
} catch (e: Exception){
CustomAnvil.instance.logger.log(Level.SEVERE, "Error while trying to handle custom anvil supported plugin: ", e)
// Just in case to avoid illegal items
event.inventory.setItem(ANVIL_OUTPUT_SLOT, null)
// Finally, warn the player, maybe a lot of time but better warn than do nothing
event.view.player.sendMessage(ChatColor.RED.toString() + "Error while handling the anvil.")
return true
}
}
private fun unsafeTryEventPreAnvilBypass(event: PrepareAnvilEvent, player: HumanEntity): Boolean {
var bypass = false var bypass = false
// Test if disenchantment used prepare anvil // Test if disenchantment used prepare anvil
@ -118,11 +136,44 @@ object DependencyManager {
return bypass return bypass
} }
fun treatAnvilResult(event: PrepareAnvilEvent, result: ItemStack) { // Return true only if error occurred (and so should bypass rest)
fun tryTreatAnvilResult(event: PrepareAnvilEvent, result: ItemStack): Boolean {
try {
unsafeTryTreatAnvilResult(event, result)
return false
} catch (e: Exception){
CustomAnvil.instance.logger.log(Level.SEVERE, "Error while trying to handle custom anvil supported plugin: ", e)
// Just in case to avoid illegal items
event.inventory.setItem(ANVIL_OUTPUT_SLOT, null)
// Finally, warn the player, maybe a lot of time but better warn than do nothing
event.view.player.sendMessage(ChatColor.RED.toString() + "Error while handling the anvil.")
return true
}
}
private fun unsafeTryTreatAnvilResult(event: PrepareAnvilEvent, result: ItemStack) {
excellentEnchantsCompatibility?.treatAnvilResult(event, result) excellentEnchantsCompatibility?.treatAnvilResult(event, result)
} }
// Return true if should bypass (either by a dependency or error)
fun tryClickAnvilResultBypass(event: InventoryClickEvent, inventory: AnvilInventory): Boolean { fun tryClickAnvilResultBypass(event: InventoryClickEvent, inventory: AnvilInventory): Boolean {
try {
return unsafeTryClickAnvilResultBypass(event, inventory)
} catch (e: Exception){
CustomAnvil.instance.logger.log(Level.SEVERE, "Error while trying to handle custom anvil supported plugin: ", e)
// Just in case to avoid illegal items
event.inventory.setItem(ANVIL_OUTPUT_SLOT, null)
// Finally, warn the player, maybe a lot of time but better warn than do nothing
event.whoClicked.sendMessage(ChatColor.RED.toString() + "Error while handling the anvil.")
return true
}
}
private fun unsafeTryClickAnvilResultBypass(event: InventoryClickEvent, inventory: AnvilInventory): Boolean {
var bypass = false var bypass = false
// Test if disenchantment used event click // Test if disenchantment used event click

View file

@ -45,6 +45,7 @@ class AnvilResultListener: Listener {
if (event.rawSlot != ANVIL_OUTPUT_SLOT) { if (event.rawSlot != ANVIL_OUTPUT_SLOT) {
return return
} }
// Test if the event should bypass custom anvil. // Test if the event should bypass custom anvil.
if(DependencyManager.tryClickAnvilResultBypass(event, inventory)) return if(DependencyManager.tryClickAnvilResultBypass(event, inventory)) return
@ -52,7 +53,7 @@ class AnvilResultListener: Listener {
val leftItem = inventory.getItem(ANVIL_INPUT_LEFT) ?: return val leftItem = inventory.getItem(ANVIL_INPUT_LEFT) ?: return
val rightItem = inventory.getItem(ANVIL_INPUT_RIGHT) val rightItem = inventory.getItem(ANVIL_INPUT_RIGHT)
if(!GameMode.CREATIVE.equals(player.gameMode) && inventory.repairCost >= inventory.maximumRepairCost) { if(GameMode.CREATIVE != player.gameMode && inventory.repairCost >= inventory.maximumRepairCost) {
event.result = Event.Result.DENY event.result = Event.Result.DENY
return return
} }
@ -96,7 +97,6 @@ class AnvilResultListener: Listener {
} }
} }
private fun onCustomCraft(event: InventoryClickEvent, private fun onCustomCraft(event: InventoryClickEvent,
recipe: AnvilCustomRecipe, recipe: AnvilCustomRecipe,
player: Player, player: Player,
@ -122,7 +122,7 @@ class AnvilResultListener: Listener {
// Handle not creative middle click... // Handle not creative middle click...
if (event.click != ClickType.MIDDLE && if (event.click != ClickType.MIDDLE &&
!handleCustomCraftClick(event, recipe, inventory, player, leftItem, rightItem, amount, xpCost)) return; !handleCustomCraftClick(event, recipe, inventory, player, leftItem, rightItem, amount, xpCost)) return
// Finally, we add the item to the player // Finally, we add the item to the player
if (slotDestination.type == SlotType.CURSOR) { if (slotDestination.type == SlotType.CURSOR) {
@ -222,7 +222,7 @@ class AnvilResultListener: Listener {
resultCopy: ItemStack, resultAmount: Int): Int { resultCopy: ItemStack, resultAmount: Int): Int {
if (player.gameMode == GameMode.CREATIVE) return 0 if (player.gameMode == GameMode.CREATIVE) return 0
var repairCost = 0; var repairCost = 0
// Get repairCost // Get repairCost
leftItem.itemMeta?.let { leftMeta -> leftItem.itemMeta?.let { leftMeta ->
val leftName = leftMeta.displayName val leftName = leftMeta.displayName

View file

@ -22,6 +22,7 @@ import xyz.alexcrea.cuanvil.util.AnvilColorUtil
import xyz.alexcrea.cuanvil.util.AnvilXpUtil import xyz.alexcrea.cuanvil.util.AnvilXpUtil
import xyz.alexcrea.cuanvil.util.CustomRecipeUtil import xyz.alexcrea.cuanvil.util.CustomRecipeUtil
import xyz.alexcrea.cuanvil.util.UnitRepairUtil.getRepair import xyz.alexcrea.cuanvil.util.UnitRepairUtil.getRepair
import java.util.logging.Level
/** /**
* Listener for anvil events * Listener for anvil events
@ -45,7 +46,7 @@ class PrepareAnvilListener : Listener {
val player: HumanEntity = event.viewers.first() val player: HumanEntity = event.viewers.first()
// Test if the event should bypass custom anvil. // Test if the event should bypass custom anvil.
if(DependencyManager.tryEventPreAnvilBypass(event, player)) return if (DependencyManager.tryEventPreAnvilBypass(event, player)) return
val inventory = event.inventory val inventory = event.inventory
val first = inventory.getItem(ANVIL_INPUT_LEFT) ?: return val first = inventory.getItem(ANVIL_INPUT_LEFT) ?: return
@ -76,6 +77,7 @@ class PrepareAnvilListener : Listener {
} }
// return true if a custom recipe exist with these ingredient
private fun testCustomRecipe(event: PrepareAnvilEvent, inventory: AnvilInventory, private fun testCustomRecipe(event: PrepareAnvilEvent, inventory: AnvilInventory,
player: HumanEntity, player: HumanEntity,
first: ItemStack, second: ItemStack?): Boolean { first: ItemStack, second: ItemStack?): Boolean {
@ -89,7 +91,7 @@ class PrepareAnvilListener : Listener {
resultItem.amount *= amount resultItem.amount *= amount
event.result = resultItem event.result = resultItem
DependencyManager.treatAnvilResult(event, resultItem) if(DependencyManager.tryTreatAnvilResult(event, resultItem)) return true
AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, recipe.xpCostPerCraft * amount, true) AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, recipe.xpCostPerCraft * amount, true)
return true return true
@ -108,7 +110,7 @@ class PrepareAnvilListener : Listener {
} }
event.result = resultItem event.result = resultItem
DependencyManager.treatAnvilResult(event, resultItem) if(DependencyManager.tryTreatAnvilResult(event, resultItem)) return
anvilCost += AnvilXpUtil.calculatePenalty(first, null, resultItem) anvilCost += AnvilXpUtil.calculatePenalty(first, null, resultItem)
@ -116,15 +118,12 @@ class PrepareAnvilListener : Listener {
} }
private fun handleRename(resultItem: ItemStack, inventory: AnvilInventory, player: HumanEntity): Int { private fun handleRename(resultItem: ItemStack, inventory: AnvilInventory, player: HumanEntity): Int {
// Rename item and add renaming cost // Can be null
resultItem.itemMeta?.let {
val displayName = ChatColor.stripColor(it.displayName)
var inventoryName = ChatColor.stripColor(inventory.renameText) var inventoryName = ChatColor.stripColor(inventory.renameText)
var sumCost = 0 var sumCost = 0
var useColor = false var useColor = false
if(ConfigOptions.renameColorPossible){ if(ConfigOptions.renameColorPossible && inventoryName != null){
val resultString = StringBuilder(inventoryName) val resultString = StringBuilder(inventoryName)
useColor = AnvilColorUtil.handleRenamingColor(resultString, player) useColor = AnvilColorUtil.handleRenamingColor(resultString, player)
@ -136,7 +135,13 @@ class PrepareAnvilListener : Listener {
} }
} }
if ((!useColor && (!displayName.contentEquals(inventoryName))) || (useColor && !(it.displayName).contentEquals(inventoryName))) { // Rename item and add renaming cost
resultItem.itemMeta?.let {
val displayName =
if (useColor) it.displayName
else ChatColor.stripColor(it.displayName)
if (!displayName.contentEquals(inventoryName)) {
it.setDisplayName(inventoryName) it.setDisplayName(inventoryName)
resultItem.itemMeta = it resultItem.itemMeta = it
@ -178,11 +183,12 @@ class PrepareAnvilListener : Listener {
// Finally, we set result // Finally, we set result
event.result = resultItem event.result = resultItem
DependencyManager.treatAnvilResult(event, resultItem) if(DependencyManager.tryTreatAnvilResult(event, resultItem)) return
AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, anvilCost) AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, anvilCost)
} }
// return true if there is a valid unit repair with these ingredients
private fun testUnitRepair(event: PrepareAnvilEvent, inventory: AnvilInventory, player: HumanEntity, private fun testUnitRepair(event: PrepareAnvilEvent, inventory: AnvilInventory, player: HumanEntity,
first: ItemStack, second: ItemStack): Boolean { first: ItemStack, second: ItemStack): Boolean {
val unitRepairAmount = first.getRepair(second) ?: return false val unitRepairAmount = first.getRepair(second) ?: return false
@ -204,7 +210,7 @@ class PrepareAnvilListener : Listener {
return true return true
} }
event.result = resultItem event.result = resultItem
DependencyManager.treatAnvilResult(event, resultItem) if(DependencyManager.tryTreatAnvilResult(event, resultItem)) return true
AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, anvilCost) AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, anvilCost)
return true return true

View file

@ -1,11 +1,15 @@
package xyz.alexcrea.cuanvil.anvil; package xyz.alexcrea.cuanvil.anvil;
import io.delilaheve.util.ConfigOptions;
import net.kyori.adventure.text.Component;
import org.bukkit.Material;
import org.bukkit.event.inventory.InventoryType; import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.AnvilInventory; import org.bukkit.inventory.AnvilInventory;
import org.bukkit.inventory.Inventory; import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.Repairable; import org.bukkit.inventory.meta.Repairable;
import org.eclipse.aether.util.ConfigUtils;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@ -32,8 +36,9 @@ public class AnvilFuseTests extends SharedCustomAnvilTest {
AnvilFuseTests.anvil = (AnvilInventory) anvil; AnvilFuseTests.anvil = (AnvilInventory) anvil;
player.openInventory(anvil); player.openInventory(anvil);
ConfigHolder.DEFAULT_CONFIG.getConfig().set("debug_log", true); ConfigHolder.DEFAULT_CONFIG.getConfig().set(ConfigOptions.DEBUG_LOGGING, true);
ConfigHolder.DEFAULT_CONFIG.getConfig().set("debug_log_verbose", true); ConfigHolder.DEFAULT_CONFIG.getConfig().set(ConfigOptions.VERBOSE_DEBUG_LOGGING, true);
ConfigHolder.DEFAULT_CONFIG.getConfig().set(ConfigOptions.ALLOW_COLOR_CODE, true); // For rename test
} }
@BeforeEach @BeforeEach
@ -100,4 +105,23 @@ public class AnvilFuseTests extends SharedCustomAnvilTest {
AnvilFuseTestUtil.executeAnvilTest(anvil, player, data); AnvilFuseTestUtil.executeAnvilTest(anvil, player, data);
} }
// Note: currently anvil can only have null name. maybe handle differently later
@Test
public void nullNameResetTest(){
ItemStack base = new ItemStack(Material.NETHERITE_SWORD);
ItemStack expected = base.clone();
ItemMeta meta = expected.getItemMeta();
meta.displayName(Component.text("test"));
base.setItemMeta(meta);
AnvilFuseTestData data = new AnvilFuseTestData(
base, null,
expected, expected, null
// TODO add expected price
);
AnvilFuseTestUtil.executeAnvilTest(anvil, player, data);
}
} }

View file

@ -36,4 +36,19 @@ public record AnvilFuseTestData(
this(leftItem, rightItem, expectedResult, null this(leftItem, rightItem, expectedResult, null
); );
} }
public AnvilFuseTestData(
@Nullable ItemStack leftItem,
@Nullable ItemStack rightItem,
@Nullable ItemStack expectedResult,
@Nullable ItemStack expectedAfterLeftPlaced,
@Nullable ItemStack expectedAfterRightPlaced
){
this(leftItem, rightItem,
expectedResult, expectedAfterLeftPlaced, expectedAfterRightPlaced,
null, null, null
);
}
} }