diff --git a/defaultconfigs/1.21.11/config.yml b/defaultconfigs/1.21.11/config.yml index 44885b74..98f9a94d 100644 --- a/defaultconfigs/1.21.11/config.yml +++ b/defaultconfigs/1.21.11/config.yml @@ -119,6 +119,9 @@ permission_needed_for_dialog_rename: false # For practical reason. this only work when dialog rename is enabled dialog_rename_keep_user_text: true +# Also count left item enchantments for the final price +include_left_enchantment_for_cost: false + # Override limits for specific enchants # # Enchantments not listed here will use the value of default_limit @@ -175,8 +178,8 @@ enchant_limits: # Multipliers used to calculate the enchantment's value in repair/combining # -# Values here are pulled from the fandom wiki: -# https://minecraft.fandom.com/wiki/Anvil_mechanics#Costs_for_combining_enchantments +# Values here are pulled from the minecraft wiki: +# https://minecraft.wiki/w/Anvil_mechanics#Costs_for_combining_enchantments # # If an enchantment is missing values here, or is less than 0, it will default to 0 # @@ -483,5 +486,8 @@ debug_log: false # Whether to show verbose debug logging debug_log_verbose: false +# Whether to show inside the console or require to run /ca debug get +display_debug_in_console: false + configVersion: 1.15.5 lowMinecraftVersion: 1.21.11 diff --git a/defaultconfigs/1.21.9/config.yml b/defaultconfigs/1.21.9/config.yml index 4df58766..7b3afce1 100644 --- a/defaultconfigs/1.21.9/config.yml +++ b/defaultconfigs/1.21.9/config.yml @@ -117,6 +117,9 @@ permission_needed_for_dialog_rename: false # For practical reason. this only work when dialog rename is enabled dialog_rename_keep_user_text: true +# Also count left item enchantments for the final price +include_left_enchantment_for_cost: false + # Override limits for specific enchants # # Enchantments not listed here will use the value of default_limit @@ -172,8 +175,8 @@ enchant_limits: # Multipliers used to calculate the enchantment's value in repair/combining # -# Values here are pulled from the fandom wiki: -# https://minecraft.fandom.com/wiki/Anvil_mechanics#Costs_for_combining_enchantments +# Values here are pulled from the minecraft wiki: +# https://minecraft.wiki/w/Anvil_mechanics#Costs_for_combining_enchantments # # If an enchantment is missing values here, or is less than 0, it will default to 0 # @@ -475,5 +478,8 @@ debug_log: false # Whether to show verbose debug logging debug_log_verbose: false +# Whether to show inside the console or require to run /ca debug get +display_debug_in_console: false + configVersion: 1.11.0 lowMinecraftVersion: 1.21.9 diff --git a/defaultconfigs/1.21/config.yml b/defaultconfigs/1.21/config.yml index 5d59e5a6..cb7e382b 100644 --- a/defaultconfigs/1.21/config.yml +++ b/defaultconfigs/1.21/config.yml @@ -117,6 +117,9 @@ permission_needed_for_dialog_rename: false # For practical reason. this only work when dialog rename is enabled dialog_rename_keep_user_text: true +# Also count left item enchantments for the final price +include_left_enchantment_for_cost: false + # Override limits for specific enchants # # Enchantments not listed here will use the value of default_limit @@ -169,8 +172,8 @@ enchant_limits: # Multipliers used to calculate the enchantment's value in repair/combining # -# Values here are pulled from the fandom wiki: -# https://minecraft.fandom.com/wiki/Anvil_mechanics#Costs_for_combining_enchantments +# Values here are pulled from the minecraft wiki: +# https://minecraft.wiki/w/Anvil_mechanics#Costs_for_combining_enchantments # # If an enchantment is missing values here, or is less than 0, it will default to 0 # @@ -463,4 +466,7 @@ debug_log: false # Whether to show verbose debug logging debug_log_verbose: false +# Whether to show inside the console or require to run /ca debug get +display_debug_in_console: false + configVersion: 1.11.0 \ No newline at end of file diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/EnchantmentApi.java b/src/main/java/xyz/alexcrea/cuanvil/api/EnchantmentApi.java index c291d7cc..c0a5ff2b 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/api/EnchantmentApi.java +++ b/src/main/java/xyz/alexcrea/cuanvil/api/EnchantmentApi.java @@ -5,6 +5,7 @@ import io.delilaheve.util.ConfigOptions; import org.bukkit.NamespacedKey; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import xyz.alexcrea.cuanvil.config.ConfigHolder; @@ -38,14 +39,14 @@ public class EnchantmentApi { * @param enchantment The enchantment to register * @return True if successful. */ - public static boolean registerEnchantment(@NotNull CAEnchantment enchantment){ - if(!CAEnchantmentRegistry.getInstance().register(enchantment)) return false; + public static boolean registerEnchantment(@NotNull CAEnchantment enchantment) { + if (!CAEnchantmentRegistry.getInstance().register(enchantment)) return false; // Add enchantment to gui. - if(EnchantCostConfigGui.getInstance() != null){ + if (EnchantCostConfigGui.getInstance() != null) { EnchantCostConfigGui.getInstance().updateValueForGeneric(enchantment, true); } - if(EnchantLimitConfigGui.getInstance() != null){ + if (EnchantLimitConfigGui.getInstance() != null) { EnchantLimitConfigGui.getInstance().updateValueForGeneric(enchantment, true); } @@ -62,8 +63,8 @@ public class EnchantmentApi { * @param defaultRarity The default rarity of the provided enchantment * @return True if successful. */ - public static boolean registerEnchantment(@NotNull Enchantment enchantment, @Nullable EnchantmentRarity defaultRarity){ - if(defaultRarity == null) + public static boolean registerEnchantment(@NotNull Enchantment enchantment, @Nullable EnchantmentRarity defaultRarity) { + if (defaultRarity == null) return registerEnchantment(new CABukkitEnchantment(enchantment)); return registerEnchantment(new CABukkitEnchantment(enchantment, defaultRarity)); @@ -77,7 +78,7 @@ public class EnchantmentApi { * @param enchantment The enchantment to register * @return True if successful. */ - public static boolean registerEnchantment(@NotNull Enchantment enchantment){ + public static boolean registerEnchantment(@NotNull Enchantment enchantment) { return registerEnchantment(new CABukkitEnchantment(enchantment)); } @@ -87,12 +88,12 @@ public class EnchantmentApi { * @param enchantment The enchantment to unregister * @return True if successful. */ - public static boolean unregisterEnchantment(@Nullable CAEnchantment enchantment){ + public static boolean unregisterEnchantment(@Nullable CAEnchantment enchantment) { // Remove from gui - if(EnchantCostConfigGui.getInstance() != null){ + if (EnchantCostConfigGui.getInstance() != null) { EnchantCostConfigGui.getInstance().removeGeneric(enchantment); } - if(EnchantLimitConfigGui.getInstance() != null){ + if (EnchantLimitConfigGui.getInstance() != null) { EnchantLimitConfigGui.getInstance().removeGeneric(enchantment); } @@ -105,7 +106,7 @@ public class EnchantmentApi { * @param key The enchantment key to unregister * @return True if successful. */ - public static boolean unregisterEnchantment(@NotNull NamespacedKey key){ + public static boolean unregisterEnchantment(@NotNull NamespacedKey key) { CAEnchantment enchantment = CAEnchantment.getByKey(key); return unregisterEnchantment(enchantment); } @@ -116,7 +117,7 @@ public class EnchantmentApi { * @param enchantment The enchantment to unregister * @return True if successful. */ - public static boolean unregisterEnchantment(@NotNull Enchantment enchantment){ + public static boolean unregisterEnchantment(@NotNull Enchantment enchantment) { return unregisterEnchantment(enchantment.getKey()); } @@ -127,7 +128,7 @@ public class EnchantmentApi { * @return The custom anvil enchantment of this key. null if not found. */ @Nullable - public static CAEnchantment getByKey(@NotNull NamespacedKey key){ + public static CAEnchantment getByKey(@NotNull NamespacedKey key) { return CAEnchantment.getByKey(key); } @@ -137,29 +138,31 @@ public class EnchantmentApi { * @param name The name used to fetch * @return List of custom anvil enchantments of this name. May be empty if not found. */ - public static List getByName(@NotNull String name){ + public static List getByName(@NotNull String name) { return CAEnchantment.getByName(name); } /** * Get every registered custom anvil enchantments. + * * @return An immutable map of enchantment key as map key and custom anvil enchantment as value. */ @NotNull - public static Map getRegisteredEnchantments(){ + public static Map getRegisteredEnchantments() { return Collections.unmodifiableMap(CAEnchantmentRegistry.getInstance().registeredEnchantments()); } /** * Write the default level and rarity configuration of the enchantment. + * * @param enchantment The enchantment to write default configuration - * @param override If it should override old configuration + * @param override If it should override old configuration * @return Return false if override is false and a configuration exist. true otherwise. */ - public static boolean writeDefaultConfig(CAEnchantment enchantment, boolean override){ + public static boolean writeDefaultConfig(CAEnchantment enchantment, boolean override) { FileConfiguration config = ConfigHolder.DEFAULT_CONFIG.getConfig(); - if(tryWriteDefaultConfig(config, enchantment, override)){ + if (tryWriteDefaultConfig(config, enchantment, override)) { prepareSaveTask(); } return true; @@ -169,7 +172,7 @@ public class EnchantmentApi { boolean hasChange = false; String levelPath = ConfigOptions.ENCHANT_LIMIT_ROOT + "." + enchantment.getKey(); - if(override || !defaultConfig.isSet(levelPath)){ + if (override || !defaultConfig.isSet(levelPath)) { defaultConfig.set(levelPath, enchantment.defaultMaxLevel()); hasChange = true; } @@ -179,11 +182,11 @@ public class EnchantmentApi { String itemPath = basePath + ".item"; String bookPath = basePath + ".book"; - if(override || !defaultConfig.isSet(itemPath)){ + if (override || !defaultConfig.isSet(itemPath)) { defaultConfig.set(itemPath, rarity.getItemValue()); hasChange = true; } - if(override || !defaultConfig.isSet(bookPath)){ + if (override || !defaultConfig.isSet(bookPath)) { defaultConfig.set(bookPath, rarity.getBookValue()); hasChange = true; } @@ -195,28 +198,81 @@ public class EnchantmentApi { * Prepare a task to save custom recipe configuration. */ private static void prepareSaveTask() { - if(saveChangeTask != null) return; + if (saveChangeTask != null) return; - saveChangeTask = DependencyManager.scheduler.scheduleGlobally(CustomAnvil.instance, ()->{ + saveChangeTask = DependencyManager.scheduler.scheduleGlobally(CustomAnvil.instance, () -> { ConfigHolder.DEFAULT_CONFIG.saveToDisk(true); saveChangeTask = null; }); } /** - * Add a bulk get operator. + * Add a bulk get operator. (not needed for proper "bukkit" enchantments) + *

+ * Do not forget to mark your enchantments as {@link CAEnchantment#isGetOptimised() Get Optimized} + * * @param operation An optimised get enchantments operation */ - public static void addBulkGet(@NotNull BulkGetEnchantOperation operation){ + public static void addBulkGet(@NotNull BulkGetEnchantOperation operation) { CAEnchantmentRegistry.getInstance().getOptimisedGetOperators().add(operation); } /** * Add a bulk clean operator. - * @param operation An optimised clean enchantments operation + * + * @param operation An optimised clean enchantments operation (not needed for proper "bukkit" enchantments) + *

+ * Do not forget to mark your enchantments as {@link CAEnchantment#isCleanOptimised() Clean Optimized} */ - public static void addBulkClean(@NotNull BulkCleanEnchantOperation operation){ + public static void addBulkClean(@NotNull BulkCleanEnchantOperation operation) { CAEnchantmentRegistry.getInstance().getOptimisedCleanOperators().add(operation); } + /** + * Get all the enchantments of an item + * + * @param item The item to get the enchantment from + * @return A map of key of enchantment, value the level of all the enchantments of the item + * @since 1.17.6 + */ + public static Map getEnchantments(@NotNull ItemStack item) { + return CAEnchantment.getEnchants(item); + } + + /** + * Set all the enchantments to an item. Clearing previous enchantments + * + * @param item The item to get the enchantment from + * @param enchants A map of key of enchantment, value the level of all the enchantments + * @since 1.17.6 + */ + public static void setEnchantments(@NotNull ItemStack item, @NotNull Map enchants) { + clearEnchantments(item); + addEnchantments(item, enchants); + } + + /** + * Add enchantment with there respective level. + * If the enchantment is already present it will be overridden to the new level + * + * @param item The item to get the enchantment from + * @param enchants A map of key of enchantment, value the level of the new enchantments + * @since 1.17.6 + */ + public static void addEnchantments(@NotNull ItemStack item, @NotNull Map enchants) { + enchants.forEach((enchantment, level) -> + enchantment.addEnchantmentUnsafe(item, level) + ); + } + + /** + * Clear all the enchantments + * + * @param item The item to clear the enchantments from + * @since 1.17.6 + */ + public static void clearEnchantments(@NotNull ItemStack item) { + CAEnchantment.clearEnchants(item); + } + } diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/bulk/SuperEnchantBulkOperation.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/bulk/SuperEnchantBulkOperation.java index 8bc729ab..85535594 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/enchant/bulk/SuperEnchantBulkOperation.java +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/bulk/SuperEnchantBulkOperation.java @@ -41,7 +41,7 @@ public class SuperEnchantBulkOperation implements BulkGetEnchantOperation, BulkC @Override public void bulkClear(@NotNull ItemStack item, @NotNull ItemMeta meta) { - // item meta is not preferred for enchantment squared clear + // item meta is not preferred for super enchant } } diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CASuperEnchantEnchantment.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CASuperEnchantEnchantment.java index 6039dc84..22e36056 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CASuperEnchantEnchantment.java +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CASuperEnchantEnchantment.java @@ -73,4 +73,14 @@ public class CASuperEnchantEnchantment extends CAEnchantmentBase implements Addi return !enchant.canApplyTo(item.getType()); } + + @Override + public boolean isCleanOptimised() { + return true; + } + + @Override + public boolean isGetOptimised() { + return true; + } } diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/PluginSetDefault.java b/src/main/java/xyz/alexcrea/cuanvil/update/PluginSetDefault.java index 707a218c..dd70295a 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/update/PluginSetDefault.java +++ b/src/main/java/xyz/alexcrea/cuanvil/update/PluginSetDefault.java @@ -66,6 +66,12 @@ public class PluginSetDefault { nbSet += trySetDefault(config, DIALOG_RENAME_USE_PERMISSION, DEFAULT_DIALOG_RENAME_USE_PERMISSION); nbSet += trySetDefault(config, DIALOG_KEEP_USER_TEXT, DEFAULT_DIALOG_KEEP_USER_TEXT); + nbSet += trySetDefault(config, INCLUDE_LEFT_ENCHANTMENT_FOR_COST, DEFAULT_INCLUDE_LEFT_ENCHANTMENT_FOR_COST); + + nbSet += trySetDefault(config, DEBUG_LOGGING, DEFAULT_DEBUG_LOG); + nbSet += trySetDefault(config, VERBOSE_DEBUG_LOGGING, DEFAULT_VERBOSE_DEBUG_LOG); + nbSet += trySetDefault(config, SHOW_CONSOLE_DEBUG_LOGGING, DEFAULT_SHOW_CONSOLE_DEBUG_LOGGING); + if (nbSet > 0) { CustomAnvil.instance.getLogger().info("Adding " + nbSet + " absent default config values."); ConfigHolder.DEFAULT_CONFIG.saveToDisk(true); diff --git a/src/main/kotlin/io/delilaheve/CustomAnvil.kt b/src/main/kotlin/io/delilaheve/CustomAnvil.kt index 4d99faf5..321165e5 100644 --- a/src/main/kotlin/io/delilaheve/CustomAnvil.kt +++ b/src/main/kotlin/io/delilaheve/CustomAnvil.kt @@ -71,21 +71,37 @@ open class CustomAnvil : JavaPlugin() { var latestVer: String? = null + // Debug + val debugStorageQueue = ArrayDeque() + + private fun addToLogQueue(message: String) { + if(debugStorageQueue.size >= 200) { + // Let not store infinite debug logs + debugStorageQueue.removeFirst() + } + + debugStorageQueue.addLast(message) + } + /** * Logging handler */ @JvmStatic fun log(message: String) { if (ConfigOptions.debugLog) { - instance.logger.info(message) + if(ConfigOptions.showDebugLogInConsole) + instance.logger.info(message) + addToLogQueue(message) } } /** * Vebose Logging handler */ - fun verboseLog(message: String) { + @JvmStatic fun verboseLog(message: String) { if (ConfigOptions.verboseDebugLog) { - instance.logger.info(message) + if(ConfigOptions.showDebugLogInConsole) + instance.logger.info(message) + addToLogQueue(message) } } diff --git a/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt b/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt index 9dc85f97..fc8a53a9 100644 --- a/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt +++ b/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt @@ -75,6 +75,8 @@ object ConfigOptions { const val DISABLE_MERGE_OVER_ROOT = "disable-merge-over" const val IMMUTABLE_ENCHANTMENT_LIST = "immutable_enchantments" + + const val INCLUDE_LEFT_ENCHANTMENT_FOR_COST = "include_left_enchantment_for_cost" // Monetary configs const val MONETARY_USAGE_ROOT = "monetary_cost" @@ -89,6 +91,7 @@ object ConfigOptions { // Debug flag const val DEBUG_LOGGING = "debug_log" const val VERBOSE_DEBUG_LOGGING = "debug_log_verbose" + const val SHOW_CONSOLE_DEBUG_LOGGING = "display_debug_in_console" // ---------------------- // Default config values @@ -110,6 +113,8 @@ object ConfigOptions { const val DEFAULT_ENCHANT_COUNT_LIMIT = -1 + const val DEFAULT_INCLUDE_LEFT_ENCHANTMENT_FOR_COST = false + // Color related config const val DEFAULT_ALLOW_COLOR_CODE = false const val DEFAULT_ALLOW_HEXADECIMAL_COLOR = false @@ -125,8 +130,12 @@ object ConfigOptions { const val DEFAULT_MONEY_MULTIPLIER = 1.0 // Debug flag - private const val DEFAULT_DEBUG_LOG = false - private const val DEFAULT_VERBOSE_DEBUG_LOG = false + const val DEFAULT_DEBUG_LOG = false + const val DEFAULT_VERBOSE_DEBUG_LOG = false + const val DEFAULT_SHOW_CONSOLE_DEBUG_LOGGING = false + + var OVERRIDE_DEBUG_LOG: Boolean? = null + var OVERRIDE_VERBOSE_DEBUG_LOG: Boolean? = null // Dialog menu rename const val DEFAULT_DIALOG_RENAME_ENABLED = false @@ -431,11 +440,21 @@ object ConfigOptions { ?: DEFAULT_ENCHANT_COUNT_LIMIT } + val includeLeftEnchantmentForCost: Boolean + get() { + return ConfigHolder.DEFAULT_CONFIG + .config + .getBoolean(INCLUDE_LEFT_ENCHANTMENT_FOR_COST, DEFAULT_INCLUDE_LEFT_ENCHANTMENT_FOR_COST) + } + /** * Whether to show debug logging */ val debugLog: Boolean get() { + val overrider = OVERRIDE_DEBUG_LOG + if(overrider != null) return overrider + return ConfigHolder.DEFAULT_CONFIG .config .getBoolean(DEBUG_LOGGING, DEFAULT_DEBUG_LOG) @@ -446,11 +465,27 @@ object ConfigOptions { */ val verboseDebugLog: Boolean get() { + val overrider = OVERRIDE_VERBOSE_DEBUG_LOG + if(overrider != null) return overrider + return ConfigHolder.DEFAULT_CONFIG .config .getBoolean(VERBOSE_DEBUG_LOGGING, DEFAULT_VERBOSE_DEBUG_LOG) } + /** + * Whether to show debug in console + */ + val showDebugLogInConsole: Boolean + get() { + val overrider = OVERRIDE_VERBOSE_DEBUG_LOG + if(overrider != null) return overrider + + return ConfigHolder.DEFAULT_CONFIG + .config + .getBoolean(SHOW_CONSOLE_DEBUG_LOGGING, DEFAULT_SHOW_CONSOLE_DEBUG_LOGGING) + } + /** * Is the dialog menu for rename enabled */ diff --git a/src/main/kotlin/io/delilaheve/util/ItemUtil.kt b/src/main/kotlin/io/delilaheve/util/ItemUtil.kt index 89924673..4ed9753a 100644 --- a/src/main/kotlin/io/delilaheve/util/ItemUtil.kt +++ b/src/main/kotlin/io/delilaheve/util/ItemUtil.kt @@ -19,23 +19,6 @@ object ItemUtil { */ fun ItemStack.isEnchantedBook() = type == ENCHANTED_BOOK - /** - * Find the enchantment map for this [ItemStack] and return it as a [MutableMap] - */ - fun ItemStack.findEnchantments(): MutableMap = CAEnchantment.getEnchants(this) - - /** - * Apply an [enchantments] map to this [ItemStack] - */ - fun ItemStack.setEnchantmentsUnsafe(enchantments: Map) { - CAEnchantment.clearEnchants(this) - - enchantments.forEach { (enchantment, level) -> - enchantment.addEnchantmentUnsafe(this, level) - } - - } - /** * Set this [ItemStack]s durability from a combination of the * [first] and [second] item's durability values diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt index 63ac3346..ff8f4476 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt @@ -3,10 +3,8 @@ 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.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.Material @@ -16,6 +14,7 @@ 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 @@ -187,16 +186,21 @@ object AnvilMergeLogic { player: Player, first: ItemStack, second: ItemStack ): AnvilResult { - val newEnchants = first.findEnchantments() - .combineWith(second.findEnchantments(), first, player) - var hasChanged = !isIdentical(first.findEnchantments(), newEnchants) + 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) { - resultItem.setEnchantmentsUnsafe(newEnchants) + EnchantmentApi.setEnchantments(resultItem, newEnchants) // Calculate enchantment cost - AnvilXpUtil.getRightValues(second, resultItem, cost) + AnvilXpUtil.getRightValues(first, second, resultItem, cost) } // Calculate repair cost diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCommand.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCommand.kt index e2e65a6b..ca635a03 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCommand.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCommand.kt @@ -21,6 +21,7 @@ class CustomAnvilCommand(plugin: CustomAnvil) : CommandExecutor, TabCompleter { "gui", editConfigCommand, "reload", ReloadExecutor(), "diagnostic", DiagnosticExecutor(), + "debug", DebugToggleExecutor(), "help", helpCommand, ) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/DebugToggleExecutor.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/DebugToggleExecutor.kt new file mode 100644 index 00000000..c668e6e0 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/DebugToggleExecutor.kt @@ -0,0 +1,119 @@ +package xyz.alexcrea.cuanvil.command + +import io.delilaheve.CustomAnvil +import io.delilaheve.util.ConfigOptions +import net.md_5.bungee.api.chat.ClickEvent +import net.md_5.bungee.api.chat.HoverEvent +import net.md_5.bungee.api.chat.TextComponent +import net.md_5.bungee.api.chat.hover.content.Text +import org.bukkit.ChatColor +import org.bukkit.command.Command +import org.bukkit.command.CommandSender +import org.bukkit.entity.Player +import xyz.alexcrea.cuanvil.command.DiagnosticExecutor.Companion.NO_DIAG_PERM + +class DebugToggleExecutor : CASubCommand() { + + override fun description(): String { + return "Used to toggle debug logs and retrieve it" + } + + override fun allowed(sender: CommandSender): Boolean { + return sender.hasPermission(CustomAnvil.diagnosticPermission) + } + + override fun executeCommand( + sender: CommandSender, + cmd: Command, + cmdstr: String, + args: Array + ): Boolean { + if (!allowed(sender)) { + sender.sendMessage(NO_DIAG_PERM) + return false + } + + if (args.isEmpty()) { + sender.sendMessage("Need to specify a subcommand: \"toggle\" or \"get\"") + return true + } + when (args[0].lowercase()) { + "toggle" -> executeToggle(sender, args) + "get" -> executeGet(sender) + "get-and-clear" -> { + executeGet(sender) + CustomAnvil.debugStorageQueue.clear() + } + + "clear" -> { + CustomAnvil.debugStorageQueue.clear() + sender.sendMessage("Log Cleared") + } + + else -> return false + } + return true + } + + private fun executeToggle(sender: CommandSender, args: Array) { + if (args.size < 2) { + sender.sendMessage("Need to specify which type of debug to toggle: \"default\" or \"verbose\"") + return + } + when (args[1].lowercase()) { + "default" -> { + ConfigOptions.OVERRIDE_DEBUG_LOG = !ConfigOptions.debugLog + sender.sendMessage("Debug toggle to: ${ConfigOptions.debugLog}") + } + + "verbose" -> { + ConfigOptions.OVERRIDE_VERBOSE_DEBUG_LOG = !ConfigOptions.verboseDebugLog + sender.sendMessage("Debug toggle to: ${ConfigOptions.verboseDebugLog}") + } + + else -> sender.sendMessage("Invalid debug type: ${args[1]}") + } + + } + + private fun executeGet(sender: CommandSender) { + val stb = StringBuilder("Debug Log data:") + if (CustomAnvil.debugStorageQueue.isEmpty()) { + sender.sendMessage("No log to show ? make sure you tried with debug log toggled (/ca debug toggle)") + return + } + + stb.append("\nFound ${CustomAnvil.debugStorageQueue.size} lines\n") + for (log in CustomAnvil.debugStorageQueue) { + stb.append('\n').append(log) + } + + if (sender is Player) { + val message = TextComponent(ChatColor.GREEN.toString() + "Click to copy log data") + + message.clickEvent = ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, stb.toString()) + message.hoverEvent = HoverEvent(HoverEvent.Action.SHOW_TEXT, Text("ยง7Click to copy")) + + sender.spigot().sendMessage(message); + } else { + sender.sendMessage(stb.toString()) + } + } + + override fun tabCompleter(sender: CommandSender, args: Array, list: MutableList) { + if (!allowed(sender)) return + + list.addAll( + when (args.size) { + 1 -> listOf("toggle", "get", "get-and-clear", "clear") + 2 -> when (args[0].lowercase()) { + "toggle" -> listOf("default", "verbose") + else -> listOf() + } + + else -> listOf() + } + ) + } + +} diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt index c87493f2..b7bb1dd9 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt @@ -34,7 +34,7 @@ import java.util.stream.Collectors class DiagnosticExecutor : CASubCommand() { companion object { - private const val NO_DIAG_PERM = "You do not have permission to diagnostic this server" + const val NO_DIAG_PERM = "You do not have permission to diagnostic this server" fun fetchNMSType(): String { val packetManager = DependencyManager.packetManager diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt index 794e4bae..d1a49856 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt @@ -5,6 +5,7 @@ import org.bukkit.NamespacedKey import org.bukkit.configuration.ConfigurationSection import org.bukkit.enchantments.Enchantment import org.bukkit.inventory.ItemStack +import xyz.alexcrea.cuanvil.api.EnchantmentApi import xyz.alexcrea.cuanvil.enchant.AdditionalTestEnchantment import xyz.alexcrea.cuanvil.enchant.CAEnchantment import xyz.alexcrea.cuanvil.enchant.CAEnchantmentRegistry @@ -262,7 +263,8 @@ class EnchantConflictManager { } if ((result != ConflictType.ITEM_CONFLICT) && (newEnchant is AdditionalTestEnchantment)) { - val partialItem = createPartialResult(item, immutableEnchants) + val partialItem = item.clone() + EnchantmentApi.setEnchantments(partialItem, immutableEnchants) if (newEnchant.isItemConflict(immutableEnchants, type, partialItem)) { return ConflictType.ITEM_CONFLICT @@ -273,17 +275,6 @@ class EnchantConflictManager { return result } - private fun createPartialResult(item: ItemStack, enchantments: Map): ItemStack { - val newItem = item.clone() - - CAEnchantment.clearEnchants(newItem) - enchantments.forEach { enchantment -> - enchantment.key.addEnchantmentUnsafe(newItem, enchantment.value) - } - - return newItem - } - } /** diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt index 99ff8ff3..7d750d06 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt @@ -106,12 +106,17 @@ class AnvilResultListener : Listener { if (canMerge) { val result = AnvilMergeLogic.doMerge(view, player, leftItem, rightItem) - extractAnvilResult( + val worked = extractAnvilResult( event, player, view, null, 0, null, 0, result ) + if(!worked) { + CustomAnvil.verboseLog("Merge extract failed. reset the displayed price") + // Reset the price + AnvilXpUtil.setAnvilResult(inventory, view, player, result) + } return } @@ -271,11 +276,20 @@ class AnvilResultListener : Listener { val cost = result.cost if (cost.isMonetary) { val result = EconomyManager.economy!!.remove(player, cost.asMonetaryCost()) - if (!result) return false + if (!result) { + CustomAnvil.verboseLog("Could not remove monetary cost ${cost.asMonetaryCost()}") + return false + } } else { val xpCost = cost.filteredXpCost() - if (xpCost > AnvilXpUtil.maximumXpCost(result.ignoreXpRules)) return false - if (player.level < xpCost) return false + if (xpCost > AnvilXpUtil.maximumXpCost(result.ignoreXpRules)) { + CustomAnvil.verboseLog("Cost above maximum $xpCost > ${AnvilXpUtil.maximumXpCost(result.ignoreXpRules)}") + return false + } + if (player.level < xpCost) { + CustomAnvil.verboseLog("Player do not have enough xp ${player.level} < $xpCost") + return false + } player.level -= xpCost } @@ -293,7 +307,10 @@ class AnvilResultListener : Listener { rightRemoveCount: Int, result: AnvilResult ): Boolean { - if (result.isEmpty()) return false + if (result.isEmpty()) { + CustomAnvil.verboseLog("Merge result is empty") + return false + } // To avoid vanilla, we cancel the event event.result = Event.Result.DENY @@ -301,7 +318,10 @@ class AnvilResultListener : Listener { val cost = result.cost processCost(view, player, cost) - if (!cost.valid && player.gameMode != GameMode.CREATIVE) return false + if (!cost.valid && player.gameMode != GameMode.CREATIVE) { + CustomAnvil.verboseLog("Player cannot afford the cost") + return false + } // Where should we get the item val slotDestination = getActionSlot(event, player) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt index 96682973..eda35fc6 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt @@ -168,11 +168,7 @@ class PrepareAnvilListener : Listener { private fun applyResult(event: PrepareAnvilEvent, player: Player, result: AnvilResult) { event.result = result.item - if (result.item == null) { - AnvilXpUtil.onNoResult(player, event.view) - return - } - AnvilXpUtil.setAnvilInvCost(event.view, player, result.cost, result.ignoreXpRules) + AnvilXpUtil.setAnvilResult(event.view, player, result) } } \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilXpUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilXpUtil.kt index 3dcc1f21..55847180 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilXpUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilXpUtil.kt @@ -3,7 +3,6 @@ package xyz.alexcrea.cuanvil.util.anvil import io.delilaheve.CustomAnvil import io.delilaheve.util.ConfigOptions import io.delilaheve.util.EnchantmentUtil.enchantmentName -import io.delilaheve.util.ItemUtil.findEnchantments import io.delilaheve.util.ItemUtil.isEnchantedBook import org.bukkit.GameMode import org.bukkit.NamespacedKey @@ -14,7 +13,9 @@ import org.bukkit.inventory.meta.Repairable import org.bukkit.inventory.view.AnvilView import org.bukkit.persistence.PersistentDataType import xyz.alexcrea.cuanvil.anvil.AnvilCost +import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.AnvilResult import xyz.alexcrea.cuanvil.anvil.AnvilUseType +import xyz.alexcrea.cuanvil.api.EnchantmentApi import xyz.alexcrea.cuanvil.config.ConfigHolder import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.dependency.economy.EconomyManager @@ -28,7 +29,23 @@ object AnvilXpUtil { const val EXCLUSIVE_PENALTY_PREFIX = "repair_cost" /** - * Display the required cost (either as xp or as ) + * Display the required cost (either as xp or as money) or reset anvil price depending on result + */ + fun setAnvilResult( + inventory: AnvilInventory, + view: InventoryView, + player: Player, + result: AnvilResult) { + if(result.item == null) { + onNoResult(player, view) + return + } + + setAnvilInvCost(inventory, view, player, result.cost, result.ignoreXpRules) + } + + /** + * Display the required cost (either as xp or as money) */ fun setAnvilInvCost( view: AnvilView, @@ -216,17 +233,21 @@ object AnvilXpUtil { } /** - * Function to calculate right enchantment values + * Function to calculate enchantments values * it include enchantment placed on final item and conflicting enchantment */ - fun getRightValues(right: ItemStack, result: ItemStack, cost: AnvilCost) { + fun getRightValues(left: ItemStack, right: ItemStack, result: ItemStack, cost: AnvilCost) { // Calculate right value and illegal enchant penalty val rightIsFormBook = right.isEnchantedBook() - val resultEnchs = result.findEnchantments() + val rightEnchs = EnchantmentApi.getEnchantments(right) + val resultEnchs = EnchantmentApi.getEnchantments(result) val resultEnchsKeys = HashMap(resultEnchs) - for (enchantment in right.findEnchantments()) { + var rightValue = 0 + var leftValue = 0 + + for (enchantment in rightEnchs) { // count enchant as illegal enchant if it conflicts with another enchant or not in result if ((enchantment.key !in resultEnchsKeys)) { resultEnchsKeys[enchantment.key] = enchantment.value @@ -248,13 +269,31 @@ object AnvilXpUtil { val enchantmentMultiplier = ConfigOptions.enchantmentValue(enchantment.key, rightIsFormBook) val value = resultLevel * enchantmentMultiplier - CustomAnvil.log("Value for ${enchantment.key.enchantmentName} level ${enchantment.value} is $value ($resultLevel * $enchantmentMultiplier)") - cost.enchantment += value + CustomAnvil.log("Value for sacrifice item ${enchantment.key.enchantmentName} level ${enchantment.value} is $value ($resultLevel * $enchantmentMultiplier)") + rightValue += value } + if(ConfigOptions.includeLeftEnchantmentForCost) { + val leftIsFormBook = left.isEnchantedBook() + val leftEnchs = EnchantmentApi.getEnchantments(left) + for (enchantment in leftEnchs) { + // Do not process enchantment that are present on the sacrifice + if(rightEnchs.contains(enchantment.key)) continue + + val resultLevel = resultEnchs.getOrDefault(enchantment.key, 0) + + val enchantmentMultiplier = ConfigOptions.enchantmentValue(enchantment.key, leftIsFormBook) + val value = resultLevel * enchantmentMultiplier + CustomAnvil.log("Value for left item ${enchantment.key.enchantmentName} level ${enchantment.value} is $value ($resultLevel * $enchantmentMultiplier)") + leftValue += value + } + } + + cost.enchantment = rightValue + leftValue CustomAnvil.log( "Calculated right values: " + - "rightValue: ${cost.enchantment}, " + + "rightValue: ${rightValue}, " + + "leftValue: ${leftValue}, " + "illegalPenalty: ${cost.illegalPenalty}" ) } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 7d6e396a..c8fefb53 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -119,6 +119,9 @@ permission_needed_for_dialog_rename: false # For practical reason. this only work when dialog rename is enabled dialog_rename_keep_user_text: true +# Also count left item enchantments for the final price +include_left_enchantment_for_cost: false + # Override limits for specific enchants # # Enchantments not listed here will use the value of default_limit @@ -171,8 +174,8 @@ enchant_limits: # Multipliers used to calculate the enchantment's value in repair/combining # -# Values here are pulled from the fandom wiki: -# https://minecraft.fandom.com/wiki/Anvil_mechanics#Costs_for_combining_enchantments +# Values here are pulled from the minecraft wiki: +# https://minecraft.wiki/w/Anvil_mechanics#Costs_for_combining_enchantments # # If an enchantment is missing values here, or is less than 0, it will default to 0 # @@ -466,4 +469,7 @@ debug_log: false # Whether to show verbose debug logging debug_log_verbose: false +# Whether to show inside the console or require to run /ca debug get +display_debug_in_console: false + configVersion: 1.11.0 diff --git a/src/test/java/xyz/alexcrea/cuanvil/util/AnvilFuseTestUtil.java b/src/test/java/xyz/alexcrea/cuanvil/util/AnvilFuseTestUtil.java index 9fb7290b..75af4db3 100644 --- a/src/test/java/xyz/alexcrea/cuanvil/util/AnvilFuseTestUtil.java +++ b/src/test/java/xyz/alexcrea/cuanvil/util/AnvilFuseTestUtil.java @@ -13,6 +13,7 @@ import org.bukkit.inventory.meta.Repairable; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.junit.jupiter.api.Assertions; +import xyz.alexcrea.cuanvil.api.EnchantmentApi; import xyz.alexcrea.cuanvil.data.AnvilClickTestData; import xyz.alexcrea.cuanvil.data.AnvilFuseTestData; import xyz.alexcrea.cuanvil.enchant.CAEnchantment; @@ -47,7 +48,7 @@ public class AnvilFuseTestUtil { } ItemStack item = new ItemStack(material); - ItemUtil.INSTANCE.setEnchantmentsUnsafe(item, enchantmentMap); + EnchantmentApi.setEnchantments(item, enchantmentMap); ItemMeta meta = item.getItemMeta(); ((Repairable) meta).setRepairCost(repairCost);