From 283a65b74a4223487692e56fdb202aacc8bd96ad Mon Sep 17 00:00:00 2001 From: alexcrea <42614139+alexcrea@users.noreply.github.com> Date: Tue, 9 Sep 2025 08:56:17 +0200 Subject: [PATCH 001/207] temporarly disable all ItemsAdder pre anvil event to fix an issue --- build.gradle.kts | 2 +- .../cuanvil/dependency/DependencyManager.kt | 3 ++ .../plugins/GenericPluginDependency.kt | 32 +++++++++++-------- .../dependency/plugins/ToolStatsDependency.kt | 6 +--- 4 files changed, 24 insertions(+), 19 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 51b3651..e957613 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,7 +18,7 @@ plugins { } group = "xyz.alexcrea" -version = "1.14.2" +version = "1.14.3" val effectiveVersion = "$version" + (if (System.getenv("SMALL_COMMIT_HASH") != null) "-dev-${System.getenv("SMALL_COMMIT_HASH")!!}" else "") diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt index e906a4a..4c8e9f6 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt @@ -101,6 +101,9 @@ object DependencyManager { if (pluginManager.isPluginEnabled("ToolStats")) genericDependencies.add(ToolStatsDependency(pluginManager.getPlugin("ToolStats")!!)) + if (pluginManager.isPluginEnabled("ItemsAdder")) + genericDependencies.add(GenericPluginDependency(pluginManager.getPlugin("ItemsAdder")!!)) + for (dependency in genericDependencies) dependency.redirectListeners() diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/GenericPluginDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/GenericPluginDependency.kt index 775a057..85b66d5 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/GenericPluginDependency.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/GenericPluginDependency.kt @@ -5,20 +5,14 @@ import org.bukkit.event.inventory.PrepareAnvilEvent import org.bukkit.plugin.Plugin import org.bukkit.plugin.RegisteredListener -abstract class GenericPluginDependency(protected val plugin: Plugin) { +open class GenericPluginDependency(protected val plugin: Plugin) { - protected val preAnvil = ArrayList() - protected val postAnvil = ArrayList() + private val preAnvil = ArrayList() + private val postAnvil = ArrayList() open fun redirectListeners() { - // get PreAnvil and PostAnvil listeners - for (registeredListener in PrepareAnvilEvent.getHandlerList().registeredListeners) { - - if (registeredListener.plugin != plugin) continue - preAnvil.add(registeredListener) - } - - postAnvil.addAll(postAnvilEvents()) + fillPreAnvil(preAnvil) + fillPostAnvil(postAnvil, preAnvil) // get required PrepareAnvilEvent listener for (listener in preAnvil) { @@ -28,10 +22,22 @@ abstract class GenericPluginDependency(protected val plugin: Plugin) { for (listener in postAnvil) { InventoryClickEvent.getHandlerList().unregister(listener) } - } - protected abstract fun postAnvilEvents(): Collection + open fun fillPreAnvil(preAnvil: ArrayList){ + // get PreAnvil and PostAnvil listeners + for (registeredListener in PrepareAnvilEvent.getHandlerList().registeredListeners) { + + if (registeredListener.plugin != plugin) continue + preAnvil.add(registeredListener) + } + } + + protected open fun fillPostAnvil( + postAnvil: ArrayList, + preAnvil: ArrayList) { + + } open fun testPrepareAnvil(event: PrepareAnvilEvent): Boolean { val previousResult = event.result diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ToolStatsDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ToolStatsDependency.kt index 513038b..255f737 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ToolStatsDependency.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ToolStatsDependency.kt @@ -11,7 +11,7 @@ import java.lang.reflect.Method class ToolStatsDependency(plugin: Plugin) : GenericPluginDependency(plugin) { - // Sadly, getTokens function is private, so I need to do that + // Sadly, getTokens function is private, so I need to do some reflectino private val getTokenMethod: Method = ItemChecker::class.java.getDeclaredMethod("getTokens", ItemStack::class.java); @@ -19,10 +19,6 @@ class ToolStatsDependency(plugin: Plugin) : GenericPluginDependency(plugin) { getTokenMethod.trySetAccessible() } - override fun postAnvilEvents(): Collection { - return listOf() - } - private fun ItemChecker.getTokenSafe(item: ItemStack?): Array { if (item == null) return arrayOf() return getTokenMethod.invoke(this, item) as Array From 870f56debf247ef3bb193ca40dd3af05526ec44d Mon Sep 17 00:00:00 2001 From: alexcrea Date: Thu, 25 Sep 2025 19:07:00 +0200 Subject: [PATCH 002/207] add nms for mc 1.21.9 --- build.gradle.kts | 1 + nms/v1_21R6/.gitignore | 1 + nms/v1_21R6/build.gradle.kts | 34 +++++++++++++++++++ .../gui/version/v1_21R6_ExternGuiTester.kt | 34 +++++++++++++++++++ .../packet/versions/V1_21R6_PacketManager.kt | 33 ++++++++++++++++++ settings.gradle.kts | 2 ++ .../dependency/gui/GuiTesterSelector.kt | 1 + .../packet/PacketManagerSelector.kt | 1 + 8 files changed, 107 insertions(+) create mode 100644 nms/v1_21R6/.gitignore create mode 100644 nms/v1_21R6/build.gradle.kts create mode 100644 nms/v1_21R6/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R6_ExternGuiTester.kt create mode 100644 nms/v1_21R6/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_21R6_PacketManager.kt diff --git a/build.gradle.kts b/build.gradle.kts index 51b3651..a6a0722 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -84,6 +84,7 @@ dependencies { implementation(project(":nms:v1_21R3", configuration = "reobf")) implementation(project(":nms:v1_21R4", configuration = "reobf")) implementation(project(":nms:v1_21R5", configuration = "reobf")) + implementation(project(":nms:v1_21R6"))//, configuration = "reobf")) //TODO add back when 1.21.9 release // include kotlin for the offline jar implementation(kotlin("stdlib")) diff --git a/nms/v1_21R6/.gitignore b/nms/v1_21R6/.gitignore new file mode 100644 index 0000000..47374f1 --- /dev/null +++ b/nms/v1_21R6/.gitignore @@ -0,0 +1 @@ +.lastDeploymentsId \ No newline at end of file diff --git a/nms/v1_21R6/build.gradle.kts b/nms/v1_21R6/build.gradle.kts new file mode 100644 index 0000000..1ec1258 --- /dev/null +++ b/nms/v1_21R6/build.gradle.kts @@ -0,0 +1,34 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +group = rootProject.group +version = rootProject.version + +plugins { + id("io.papermc.paperweight.userdev") +} + +dependencies { + implementation(project(":nms:nms-common")) + + // Used for nms + paperweight.paperDevBundle("1.21.9-pre4-R0.1-SNAPSHOT") +} + +repositories { + maven("https://repo.papermc.io/repository/maven-public/") +} + +// Set target version +tasks.withType().configureEach { + sourceCompatibility = "21" + targetCompatibility = "21" + + options.encoding = "UTF-8" +} + +kotlin { + compilerOptions { + apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) + jvmTarget.set(JvmTarget.JVM_21) + } +} diff --git a/nms/v1_21R6/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R6_ExternGuiTester.kt b/nms/v1_21R6/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R6_ExternGuiTester.kt new file mode 100644 index 0000000..4e4c32b --- /dev/null +++ b/nms/v1_21R6/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R6_ExternGuiTester.kt @@ -0,0 +1,34 @@ +package xyz.alexcrea.cuanvil.dependency.gui.version + +import org.bukkit.craftbukkit.inventory.CraftInventoryView +import org.bukkit.inventory.InventoryView +import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester + +class v1_21R6_ExternGuiTester: ExternGuiTester { + override val wesjdAnvilGuiName = "Wrapper1_21_R6" + + var tested = false; + var possible = false; + + override fun getContainerClass(view: InventoryView): Class? { + // In case we are in a test environment + if(!tested) testClassExist() + if(!possible) return null + + if(view !is CraftInventoryView<*, *>) return null + val container = view.handle + + return container.javaClass + } + + fun testClassExist(){ + tested = true; + try { + Class.forName("org.bukkit.craftbukkit.inventory.CraftInventoryView") + possible = true + } catch (e: ClassNotFoundException){ + possible = false + } + } + +} \ No newline at end of file diff --git a/nms/v1_21R6/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_21R6_PacketManager.kt b/nms/v1_21R6/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_21R6_PacketManager.kt new file mode 100644 index 0000000..ee00666 --- /dev/null +++ b/nms/v1_21R6/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_21R6_PacketManager.kt @@ -0,0 +1,33 @@ +package xyz.alexcrea.cuanvil.dependency.packet.versions + +import net.minecraft.network.protocol.game.ClientboundPlayerAbilitiesPacket +import net.minecraft.world.entity.player.Abilities +import org.bukkit.craftbukkit.entity.CraftPlayer +import org.bukkit.entity.Player +import xyz.alexcrea.cuanvil.dependency.packet.PacketManager +import xyz.alexcrea.cuanvil.dependency.packet.PacketManagerBase + +class V1_21R6_PacketManager : PacketManagerBase(), PacketManager { + override val canSetInstantBuild: Boolean + get() = true + + override fun setInstantBuild(player: Player, instantBuild: Boolean) { + val nmsPlayer = (player as CraftPlayer).handle + val playerAbilities = nmsPlayer.abilities + val sendedAbilities: Abilities + if (playerAbilities.instabuild == instantBuild) { + sendedAbilities = playerAbilities + } else { + sendedAbilities = Abilities() + sendedAbilities.invulnerable = playerAbilities.invulnerable + sendedAbilities.flying = playerAbilities.flying + sendedAbilities.mayfly = playerAbilities.mayfly + sendedAbilities.instabuild = instantBuild + sendedAbilities.mayBuild = playerAbilities.mayBuild + sendedAbilities.flyingSpeed = playerAbilities.flyingSpeed + sendedAbilities.walkingSpeed = playerAbilities.walkingSpeed + } + val packet = ClientboundPlayerAbilitiesPacket(sendedAbilities) + nmsPlayer.connection.send(packet) + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 2861550..867d1bc 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -33,6 +33,8 @@ include("nms:v1_21R4") findProject(":nms:v1_21R4")?.name = "v1_21R4" include("nms:v1_21R5") findProject(":nms:v1_21R5")?.name = "v1_21R5" +include("nms:v1_21R6") +findProject(":nms:v1_21R6")?.name = "v1_21R6" include(":impl:LegacyEcoEnchant") findProject(":impl:LegacyEcoEnchant")?.name = "LegacyEcoEnchant" \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt index b8ce177..8e4623c 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt @@ -45,6 +45,7 @@ object GuiTesterSelector { 4 -> v1_21R3_ExternGuiTester() 5 -> v1_21R4_ExternGuiTester() 6, 7, 8 -> v1_21R5_ExternGuiTester() + 9 -> v1_21R6_ExternGuiTester() else -> null } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt index 450fbce..9ec231b 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt @@ -59,6 +59,7 @@ object PacketManagerSelector { 4 -> V1_21R3_PacketManager() 5 -> V1_21R4_PacketManager() 6, 7, 8 -> V1_21R5_PacketManager() + 9 -> V1_21R6_PacketManager() else -> null } From 3ab6dbc155aa3db8634e48c7c5ab036de0a26882 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Thu, 25 Sep 2025 19:39:10 +0200 Subject: [PATCH 003/207] drop 1.17.x to 1.18.x nms paper bundle are broken --- README.md | 3 +- build.gradle.kts | 3 -- nms/v1_17R1/.gitignore | 1 - nms/v1_17R1/build.gradle.kts | 35 ------------------- .../gui/version/v1_17R1_ExternGuiTester.kt | 16 --------- .../packet/versions/V1_17R1_PacketManager.kt | 33 ----------------- nms/v1_18R1/.gitignore | 1 - nms/v1_18R1/build.gradle.kts | 35 ------------------- .../gui/version/v1_18R1_ExternGuiTester.kt | 16 --------- .../packet/versions/V1_18R1_PacketManager.kt | 33 ----------------- nms/v1_18R2/.gitignore | 1 - nms/v1_18R2/build.gradle.kts | 35 ------------------- .../gui/version/v1_18R2_ExternGuiTester.kt | 16 --------- .../packet/versions/V1_18R2_PacketManager.kt | 33 ----------------- settings.gradle.kts | 6 ---- .../dependency/gui/GuiTesterSelector.kt | 13 +------ .../packet/PacketManagerSelector.kt | 14 ++------ 17 files changed, 4 insertions(+), 290 deletions(-) delete mode 100644 nms/v1_17R1/.gitignore delete mode 100644 nms/v1_17R1/build.gradle.kts delete mode 100644 nms/v1_17R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_17R1_ExternGuiTester.kt delete mode 100644 nms/v1_17R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_17R1_PacketManager.kt delete mode 100644 nms/v1_18R1/.gitignore delete mode 100644 nms/v1_18R1/build.gradle.kts delete mode 100644 nms/v1_18R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_18R1_ExternGuiTester.kt delete mode 100644 nms/v1_18R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_18R1_PacketManager.kt delete mode 100644 nms/v1_18R2/.gitignore delete mode 100644 nms/v1_18R2/build.gradle.kts delete mode 100644 nms/v1_18R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_18R2_ExternGuiTester.kt delete mode 100644 nms/v1_18R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_18R2_PacketManager.kt diff --git a/README.md b/README.md index 978ea34..897d613 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ **Custom Anvil** is a plugin that allows server administrators to customize every aspect of the anvil's mechanics. It is expected to work on 1.18 to 1.21.7 minecraft servers running spigot or paper. -(the plugin support of 1.16.5 to 1.17.1 is experimental and may encounter issues) **Custom Anvil** was previously named **Unsafe Enchants+**. It was renamed because it now affects every anvil aspect and not only unsafe enchants\ @@ -87,7 +86,7 @@ as low priority as I work for the plugin on my free time for free. One of the configurations allow displaying price about 40 and removing Too Expensive. \ By how the minecraft client work: price above 40 can only be displayed green, even if the player does not own enough experience level. -Minecraft version 1.17 to 1.21.7 do not need any dependency. Other version need ProtocoLib enabled on your server for this feature. \ +Minecraft version 1.19 to 1.21.7 do not need any dependency. Other version need ProtocoLib enabled on your server for this feature. \ You can also wait for an update of the plugin to support a newer version. Please note that 1.16.5 to 1.17.1 are not officially supported. Run at your own risk. diff --git a/build.gradle.kts b/build.gradle.kts index a6a0722..23cb67e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -69,9 +69,6 @@ dependencies { // Include nms implementation(project(":nms:nms-common")) - implementation(project(":nms:v1_17R1", configuration = "reobf")) - implementation(project(":nms:v1_18R1", configuration = "reobf")) - implementation(project(":nms:v1_18R2", configuration = "reobf")) implementation(project(":nms:v1_19R1", configuration = "reobf")) implementation(project(":nms:v1_19R2", configuration = "reobf")) implementation(project(":nms:v1_19R3", configuration = "reobf")) diff --git a/nms/v1_17R1/.gitignore b/nms/v1_17R1/.gitignore deleted file mode 100644 index 47374f1..0000000 --- a/nms/v1_17R1/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.lastDeploymentsId \ No newline at end of file diff --git a/nms/v1_17R1/build.gradle.kts b/nms/v1_17R1/build.gradle.kts deleted file mode 100644 index 9a79bca..0000000 --- a/nms/v1_17R1/build.gradle.kts +++ /dev/null @@ -1,35 +0,0 @@ -import org.jetbrains.kotlin.gradle.dsl.JvmTarget - -group = rootProject.group -version = rootProject.version - -plugins { - id("io.papermc.paperweight.userdev") -} - -dependencies { - implementation(project(":nms:nms-common")) - - // Used for nms - paperweight.paperDevBundle("1.17.1-R0.1-SNAPSHOT") -} - -repositories { - maven("https://repo.papermc.io/repository/maven-public/") - -} - -// Set target version -tasks.withType().configureEach { - sourceCompatibility = "16" - targetCompatibility = "16" - - options.encoding = "UTF-8" -} - -kotlin { - compilerOptions { - apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) - jvmTarget.set(JvmTarget.JVM_16) - } -} diff --git a/nms/v1_17R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_17R1_ExternGuiTester.kt b/nms/v1_17R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_17R1_ExternGuiTester.kt deleted file mode 100644 index 8e352e0..0000000 --- a/nms/v1_17R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_17R1_ExternGuiTester.kt +++ /dev/null @@ -1,16 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui.version - -import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftInventoryView -import org.bukkit.inventory.InventoryView -import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester - -class v1_17R1_ExternGuiTester: ExternGuiTester { - override val wesjdAnvilGuiName = "Wrapper1_17_R1" - - override fun getContainerClass(view: InventoryView): Class? { - if (view !is CraftInventoryView) return null - val container = view.handle - - return container.javaClass - } -} \ No newline at end of file diff --git a/nms/v1_17R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_17R1_PacketManager.kt b/nms/v1_17R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_17R1_PacketManager.kt deleted file mode 100644 index c820eab..0000000 --- a/nms/v1_17R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_17R1_PacketManager.kt +++ /dev/null @@ -1,33 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.packet.versions - -import net.minecraft.network.protocol.game.ClientboundPlayerAbilitiesPacket -import net.minecraft.world.entity.player.Abilities -import org.bukkit.craftbukkit.v1_17_R1.entity.CraftPlayer -import org.bukkit.entity.Player -import xyz.alexcrea.cuanvil.dependency.packet.PacketManager -import xyz.alexcrea.cuanvil.dependency.packet.PacketManagerBase - -class V1_17R1_PacketManager : PacketManagerBase(), PacketManager { - override val canSetInstantBuild: Boolean - get() = true - - override fun setInstantBuild(player: Player, instantBuild: Boolean) { - val nmsPlayer = (player as CraftPlayer).handle - val playerAbilities = nmsPlayer.abilities - val sendedAbilities: Abilities - if (playerAbilities.instabuild == instantBuild) { - sendedAbilities = playerAbilities - } else { - sendedAbilities = Abilities() - sendedAbilities.invulnerable = playerAbilities.invulnerable - sendedAbilities.flying = playerAbilities.flying - sendedAbilities.mayfly = playerAbilities.mayfly - sendedAbilities.instabuild = instantBuild - sendedAbilities.mayBuild = playerAbilities.mayBuild - sendedAbilities.flyingSpeed = playerAbilities.flyingSpeed - sendedAbilities.walkingSpeed = playerAbilities.walkingSpeed - } - val packet = ClientboundPlayerAbilitiesPacket(sendedAbilities) - nmsPlayer.connection.send(packet) - } -} diff --git a/nms/v1_18R1/.gitignore b/nms/v1_18R1/.gitignore deleted file mode 100644 index 47374f1..0000000 --- a/nms/v1_18R1/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.lastDeploymentsId \ No newline at end of file diff --git a/nms/v1_18R1/build.gradle.kts b/nms/v1_18R1/build.gradle.kts deleted file mode 100644 index 0ed9674..0000000 --- a/nms/v1_18R1/build.gradle.kts +++ /dev/null @@ -1,35 +0,0 @@ -import org.jetbrains.kotlin.gradle.dsl.JvmTarget - -group = rootProject.group -version = rootProject.version - -plugins { - id("io.papermc.paperweight.userdev") -} - -dependencies { - implementation(project(":nms:nms-common")) - - // Used for nms - paperweight.paperDevBundle("1.18.1-R0.1-SNAPSHOT") -} - -repositories { - maven("https://repo.papermc.io/repository/maven-public/") - -} - -// Set target version -tasks.withType().configureEach { - sourceCompatibility = "17" - targetCompatibility = "17" - - options.encoding = "UTF-8" -} - -kotlin { - compilerOptions { - apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) - jvmTarget.set(JvmTarget.JVM_17) - } -} diff --git a/nms/v1_18R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_18R1_ExternGuiTester.kt b/nms/v1_18R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_18R1_ExternGuiTester.kt deleted file mode 100644 index 659a0f6..0000000 --- a/nms/v1_18R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_18R1_ExternGuiTester.kt +++ /dev/null @@ -1,16 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui.version - -import org.bukkit.craftbukkit.v1_18_R1.inventory.CraftInventoryView -import org.bukkit.inventory.InventoryView -import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester - -class v1_18R1_ExternGuiTester: ExternGuiTester { - override val wesjdAnvilGuiName = "Wrapper1_18_R1" - - override fun getContainerClass(view: InventoryView): Class? { - if (view !is CraftInventoryView) return null - val container = view.handle - - return container.javaClass - } -} \ No newline at end of file diff --git a/nms/v1_18R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_18R1_PacketManager.kt b/nms/v1_18R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_18R1_PacketManager.kt deleted file mode 100644 index 71df5c7..0000000 --- a/nms/v1_18R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_18R1_PacketManager.kt +++ /dev/null @@ -1,33 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.packet.versions - -import net.minecraft.network.protocol.game.ClientboundPlayerAbilitiesPacket -import net.minecraft.world.entity.player.Abilities -import org.bukkit.craftbukkit.v1_18_R1.entity.CraftPlayer -import org.bukkit.entity.Player -import xyz.alexcrea.cuanvil.dependency.packet.PacketManager -import xyz.alexcrea.cuanvil.dependency.packet.PacketManagerBase - -class V1_18R1_PacketManager : PacketManagerBase(), PacketManager { - override val canSetInstantBuild: Boolean - get() = true - - override fun setInstantBuild(player: Player, instantBuild: Boolean) { - val nmsPlayer = (player as CraftPlayer).handle - val playerAbilities = nmsPlayer.abilities - val sendedAbilities: Abilities - if (playerAbilities.instabuild == instantBuild) { - sendedAbilities = playerAbilities - } else { - sendedAbilities = Abilities() - sendedAbilities.invulnerable = playerAbilities.invulnerable - sendedAbilities.flying = playerAbilities.flying - sendedAbilities.mayfly = playerAbilities.mayfly - sendedAbilities.instabuild = instantBuild - sendedAbilities.mayBuild = playerAbilities.mayBuild - sendedAbilities.flyingSpeed = playerAbilities.flyingSpeed - sendedAbilities.walkingSpeed = playerAbilities.walkingSpeed - } - val packet = ClientboundPlayerAbilitiesPacket(sendedAbilities) - nmsPlayer.connection.send(packet) - } -} diff --git a/nms/v1_18R2/.gitignore b/nms/v1_18R2/.gitignore deleted file mode 100644 index 47374f1..0000000 --- a/nms/v1_18R2/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.lastDeploymentsId \ No newline at end of file diff --git a/nms/v1_18R2/build.gradle.kts b/nms/v1_18R2/build.gradle.kts deleted file mode 100644 index 2cc9659..0000000 --- a/nms/v1_18R2/build.gradle.kts +++ /dev/null @@ -1,35 +0,0 @@ -import org.jetbrains.kotlin.gradle.dsl.JvmTarget - -group = rootProject.group -version = rootProject.version - -plugins { - id("io.papermc.paperweight.userdev") -} - -dependencies { - implementation(project(":nms:nms-common")) - - // Used for nms - paperweight.paperDevBundle("1.18.2-R0.1-SNAPSHOT") -} - -repositories { - maven("https://repo.papermc.io/repository/maven-public/") - -} - -// Set target version -tasks.withType().configureEach { - sourceCompatibility = "17" - targetCompatibility = "17" - - options.encoding = "UTF-8" -} - -kotlin { - compilerOptions { - apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) - jvmTarget.set(JvmTarget.JVM_17) - } -} diff --git a/nms/v1_18R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_18R2_ExternGuiTester.kt b/nms/v1_18R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_18R2_ExternGuiTester.kt deleted file mode 100644 index 1447716..0000000 --- a/nms/v1_18R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_18R2_ExternGuiTester.kt +++ /dev/null @@ -1,16 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui.version - -import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftInventoryView -import org.bukkit.inventory.InventoryView -import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester - -class v1_18R2_ExternGuiTester: ExternGuiTester { - override val wesjdAnvilGuiName = "Wrapper1_18_R2" - - override fun getContainerClass(view: InventoryView): Class? { - if (view !is CraftInventoryView) return null - val container = view.handle - - return container.javaClass - } -} \ No newline at end of file diff --git a/nms/v1_18R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_18R2_PacketManager.kt b/nms/v1_18R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_18R2_PacketManager.kt deleted file mode 100644 index ee442f5..0000000 --- a/nms/v1_18R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_18R2_PacketManager.kt +++ /dev/null @@ -1,33 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.packet.versions - -import net.minecraft.network.protocol.game.ClientboundPlayerAbilitiesPacket -import net.minecraft.world.entity.player.Abilities -import org.bukkit.craftbukkit.v1_18_R2.entity.CraftPlayer -import org.bukkit.entity.Player -import xyz.alexcrea.cuanvil.dependency.packet.PacketManager -import xyz.alexcrea.cuanvil.dependency.packet.PacketManagerBase - -class V1_18R2_PacketManager : PacketManagerBase(), PacketManager { - override val canSetInstantBuild: Boolean - get() = true - - override fun setInstantBuild(player: Player, instantBuild: Boolean) { - val nmsPlayer = (player as CraftPlayer).handle - val playerAbilities = nmsPlayer.abilities - val sendedAbilities: Abilities - if (playerAbilities.instabuild == instantBuild) { - sendedAbilities = playerAbilities - } else { - sendedAbilities = Abilities() - sendedAbilities.invulnerable = playerAbilities.invulnerable - sendedAbilities.flying = playerAbilities.flying - sendedAbilities.mayfly = playerAbilities.mayfly - sendedAbilities.instabuild = instantBuild - sendedAbilities.mayBuild = playerAbilities.mayBuild - sendedAbilities.flyingSpeed = playerAbilities.flyingSpeed - sendedAbilities.walkingSpeed = playerAbilities.walkingSpeed - } - val packet = ClientboundPlayerAbilitiesPacket(sendedAbilities) - nmsPlayer.connection.send(packet) - } -} diff --git a/settings.gradle.kts b/settings.gradle.kts index 867d1bc..a63389b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,12 +3,6 @@ rootProject.name = "CustomAnvil" // NMS subproject include("nms:nms-common") findProject(":nms:nms-common")?.name = "nms-common" -include("nms:v1_17R1") -findProject(":nms:v1_17R1")?.name = "v1_17R1" -include("nms:v1_18R1") -findProject(":nms:v1_18R1")?.name = "v1_18R1" -include("nms:v1_18R2") -findProject(":nms:v1_18R2")?.name = "v1_18R2" include("nms:v1_19R1") findProject(":nms:v1_19R1")?.name = "v1_19R1" include("nms:v1_19R2") diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt index 8e4623c..d819430 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt @@ -11,18 +11,7 @@ object GuiTesterSelector { if (versionParts[0] != 1) return null return when (versionParts[1]) { - // Can't support 1.16.5 bc 1.16.5 paper userdev do not exist - - 17 -> when (versionParts[2]) { - 0, 1 -> v1_17R1_ExternGuiTester() - else -> null - } - - 18 -> when (versionParts[2]) { - 0, 1 -> v1_18R1_ExternGuiTester() - 2 -> v1_18R2_ExternGuiTester() - else -> null - } + // Can't support 1.16.5-1.18.x paper userdev do not exist or broken 19 -> when (versionParts[2]) { 0, 1, 2 -> v1_19R1_ExternGuiTester() diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt index 9ec231b..7ba3586 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt @@ -19,24 +19,14 @@ object PacketManagerSelector { ProtocoLibWrapper() else NoPacketManager() + private val versionSpecificManager: PacketManagerBase? get() { val versionParts = UpdateUtils.currentMinecraftVersionArray() if (versionParts[0] != 1) return null return when (versionParts[1]) { - // Can't support 1.16.5 bc 1.16.5 paper userdev do not exist - - 17 -> when (versionParts[2]) { - 0, 1 -> V1_17R1_PacketManager() - else -> null - } - - 18 -> when (versionParts[2]) { - 0, 1 -> V1_18R1_PacketManager() - 2 -> V1_18R2_PacketManager() - else -> null - } + // Can't support 1.16.5 to 1.18.x bc paper userdev do not exist or broken 19 -> when (versionParts[2]) { 0, 1, 2 -> V1_19R1_PacketManager() From 5bdd2f9b4c4873e174991df5eab56c4246debc0e Mon Sep 17 00:00:00 2001 From: alexcrea Date: Thu, 25 Sep 2025 20:11:02 +0200 Subject: [PATCH 004/207] add copper items --- .../alexcrea/cuanvil/update/Update_1_21.java | 31 ++++------ .../cuanvil/update/Update_1_21_9.java | 61 +++++++++++++++++++ .../xyz/alexcrea/cuanvil/update/Version.java | 3 + .../cuanvil/update/plugin/PluginUpdates.java | 34 +++++++++-- src/main/kotlin/io/delilaheve/CustomAnvil.kt | 7 +-- .../cuanvil/command/ReloadExecutor.kt | 6 +- 6 files changed, 110 insertions(+), 32 deletions(-) create mode 100644 src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21_9.java diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21.java b/src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21.java index 332b5fe..bd5ccf9 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21.java +++ b/src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21.java @@ -1,7 +1,6 @@ package xyz.alexcrea.cuanvil.update; import io.delilaheve.CustomAnvil; -import org.bukkit.configuration.file.FileConfiguration; import xyz.alexcrea.cuanvil.config.ConfigHolder; import static xyz.alexcrea.cuanvil.update.UpdateUtils.addAbsentToList; @@ -12,31 +11,28 @@ public class Update_1_21 { private static final Version V1_21 = new Version(1, 21); - public static void handleUpdate(){ - // Assume if version path is not null then it's 1.21 + public static void handleUpdate(Version current){ + // Test if we are running in 1.21.1 + if(V1_21.greaterEqual(current)) + return; + + // if version path is not null then check if its it's before 1.21 String oldVersion = ConfigHolder.DEFAULT_CONFIG.getConfig().getString(UpdateUtils.MINECRAFT_VERSION_PATH); if(oldVersion != null){ - Version version = Version.fromString(oldVersion); - - // Test 1.21 + var version = Version.fromString(oldVersion); if(V1_21.greaterEqual(version)) return; } - Version current = UpdateUtils.currentMinecraftVersion(); - - // Test 1.21 - if(current.greaterEqual(V1_21)){ - doUpdate(); - } + doUpdate(); } private static void doUpdate() { CustomAnvil.instance.getLogger().info("Updating config to support 1.21 ..."); - FileConfiguration baseConfig = ConfigHolder.DEFAULT_CONFIG.getConfig(); - FileConfiguration groupConfig = ConfigHolder.ITEM_GROUP_HOLDER.getConfig(); - FileConfiguration conflictConfig = ConfigHolder.CONFLICT_HOLDER.getConfig(); - FileConfiguration unitConfig = ConfigHolder.UNIT_REPAIR_HOLDER.getConfig(); + var baseConfig = ConfigHolder.DEFAULT_CONFIG.getConfig(); + var groupConfig = ConfigHolder.ITEM_GROUP_HOLDER.getConfig(); + var conflictConfig = ConfigHolder.CONFLICT_HOLDER.getConfig(); + var unitConfig = ConfigHolder.UNIT_REPAIR_HOLDER.getConfig(); // Add mace to groups groupConfig.set("mace.type", "include"); @@ -81,7 +77,7 @@ public class Update_1_21 { unitConfig.set("breeze_rod.mace", 0.25); // Set version string as 1.21 - baseConfig.set(UpdateUtils.MINECRAFT_VERSION_PATH, "1.21"); + baseConfig.set(UpdateUtils.MINECRAFT_VERSION_PATH, V1_21.toString()); // Save ConfigHolder.DEFAULT_CONFIG.saveToDisk(true); @@ -94,7 +90,6 @@ public class Update_1_21 { ConfigHolder.ITEM_GROUP_HOLDER.reload(); CustomAnvil.instance.getLogger().info("Updating Done !"); - } } diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21_9.java b/src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21_9.java new file mode 100644 index 0000000..0f169bc --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21_9.java @@ -0,0 +1,61 @@ +package xyz.alexcrea.cuanvil.update; + +import io.delilaheve.CustomAnvil; +import xyz.alexcrea.cuanvil.config.ConfigHolder; + +import static xyz.alexcrea.cuanvil.update.UpdateUtils.addAbsentToList; + +// This is a temporary class that aim to handle 1.21 update. +// It will be replaced by a better system later. +public class Update_1_21_9 { + + private static final Version V1_21_9 = new Version(1, 21, 9); + + public static void handleUpdate(Version current){ + // Test if we are running in 1.21.1.9 + if(V1_21_9.greaterEqual(current)) + return; + + // if version path is not null then check if its it's before 1.21.9 + String oldVersion = ConfigHolder.DEFAULT_CONFIG.getConfig().getString(UpdateUtils.MINECRAFT_VERSION_PATH); + if(oldVersion != null){ + var version = Version.fromString(oldVersion); + if(V1_21_9.greaterEqual(version)) return; + } + + doUpdate(); + } + + private static void doUpdate() { + CustomAnvil.instance.getLogger().info("Updating config to support 1.21.9 ..."); + + var baseConfig = ConfigHolder.DEFAULT_CONFIG.getConfig(); + var groupConfig = ConfigHolder.ITEM_GROUP_HOLDER.getConfig(); + + // Add mace to groups + addAbsentToList(groupConfig, "helmets.items", "copper_helmet"); + addAbsentToList(groupConfig, "chestplate.items", "copper_chestplate"); + addAbsentToList(groupConfig, "leggings.items", "copper_leggings"); + addAbsentToList(groupConfig, "boots.items", "copper_boots"); + + addAbsentToList(groupConfig, "pickaxes.items", "copper_pickaxe"); + addAbsentToList(groupConfig, "shovels.items", "copper_shovel"); + addAbsentToList(groupConfig, "hoes.items", "copper_hoe"); + addAbsentToList(groupConfig, "axes.items", "copper_axe"); + addAbsentToList(groupConfig, "swords.items", "copper_sword"); + + // Set version string as 1.21 + baseConfig.set(UpdateUtils.MINECRAFT_VERSION_PATH, V1_21_9.toString()); + + // Save + ConfigHolder.DEFAULT_CONFIG.saveToDisk(true); + ConfigHolder.ITEM_GROUP_HOLDER.saveToDisk(true); + + // imply reload of CONFLICT_HOLDER + // We also do not need to reload base config as there is no object related to it. + ConfigHolder.ITEM_GROUP_HOLDER.reload(); + + CustomAnvil.instance.getLogger().info("Updating Done !"); + } + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/Version.java b/src/main/java/xyz/alexcrea/cuanvil/update/Version.java index e6f63cf..15476f5 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/update/Version.java +++ b/src/main/java/xyz/alexcrea/cuanvil/update/Version.java @@ -1,5 +1,7 @@ package xyz.alexcrea.cuanvil.update; +import org.jetbrains.annotations.NotNull; + import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -48,6 +50,7 @@ public record Version(int major, int minor, int patch) { this.patch <= other.patch))); } + @NotNull @Override public String toString() { return major + "." + minor + "." + patch; diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PluginUpdates.java b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PluginUpdates.java index 03c858e..0beaf63 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PluginUpdates.java +++ b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PluginUpdates.java @@ -2,6 +2,9 @@ package xyz.alexcrea.cuanvil.update.plugin; import io.delilaheve.CustomAnvil; import xyz.alexcrea.cuanvil.config.ConfigHolder; +import xyz.alexcrea.cuanvil.update.UpdateUtils; +import xyz.alexcrea.cuanvil.update.Update_1_21; +import xyz.alexcrea.cuanvil.update.Update_1_21_9; import xyz.alexcrea.cuanvil.update.Version; import javax.annotation.Nonnull; @@ -12,26 +15,37 @@ public class PluginUpdates { private static final String CONFIG_VERSION_PATH = "configVersion"; - public static void handlePluginUpdate() { + // Handle mc version update then plugin version update + public static void handleUpdates() { + handleMCVersionUpdate(); + handlePluginUpdate(); + } + + private static final Version V1_6_2 = new Version(1, 6, 2); + private static final Version V1_6_7 = new Version(1, 6, 7); + private static final Version V1_8_0 = new Version(1, 8, 0); + private static final Version V1_11_0 = new Version(1, 11, 0); + + // Handle only plugin update + private static void handlePluginUpdate() { String versionString = ConfigHolder.DEFAULT_CONFIG.getConfig().getString(CONFIG_VERSION_PATH); Version current = versionString == null ? new Version(0) : Version.fromString(versionString); Set toSave = new HashSet<>(); - if (new Version(1, 6, 2).greaterThan(current)) { + if (V1_6_2.greaterThan(current)) { PUpdate_1_6_2.handleUpdate(toSave); // We assume 1.6.7 will run. TODO a better system instead of that I guess } - if (new Version(1, 6, 7).greaterThan(current)) { + if (V1_6_7.greaterThan(current)) { PUpdate_1_6_7.handleUpdate(toSave); // We assume 1.8.0 will run. } - if (new Version(1, 8, 0).greaterThan(current)) { + if (V1_8_0.greaterThan(current)) { PUpdate_1_8_0.handleUpdate(toSave); // We assume 1.11.0 will run. } - - if (new Version(1, 11, 0).greaterThan(current)) { + if (V1_11_0.greaterThan(current)) { PUpdate_1_11_0.handleUpdate(toSave); finishConfiguration("1.11.0", toSave); @@ -39,6 +53,14 @@ public class PluginUpdates { } + // Handle minecraft version update (not plugin version update) + public static void handleMCVersionUpdate(){ + Version current = UpdateUtils.currentMinecraftVersion(); + + Update_1_21.handleUpdate(current); + Update_1_21_9.handleUpdate(current); + } + private static void finishConfiguration(@Nonnull String newVersion, @Nonnull Set toSave) { CustomAnvil.instance.getLogger().info("Configuration file updated to " + newVersion); ConfigHolder.DEFAULT_CONFIG.getConfig().set(CONFIG_VERSION_PATH, newVersion); diff --git a/src/main/kotlin/io/delilaheve/CustomAnvil.kt b/src/main/kotlin/io/delilaheve/CustomAnvil.kt index f629c2f..386969a 100644 --- a/src/main/kotlin/io/delilaheve/CustomAnvil.kt +++ b/src/main/kotlin/io/delilaheve/CustomAnvil.kt @@ -139,11 +139,8 @@ open class CustomAnvil : JavaPlugin() { return } - // temporary: handle 1.21 update - Update_1_21.handleUpdate() - - // plugin configuration updates - PluginUpdates.handlePluginUpdate() + // Handle minecraft and plugin updates + PluginUpdates.handleUpdates() // Register enchantment of compatible plugin and load configuration change. DependencyManager.handleCompatibilityConfig() diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/ReloadExecutor.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/ReloadExecutor.kt index 38a98c0..8093e09 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/ReloadExecutor.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/ReloadExecutor.kt @@ -9,7 +9,7 @@ import xyz.alexcrea.cuanvil.api.event.CAConfigReadyEvent import xyz.alexcrea.cuanvil.config.ConfigHolder import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.gui.config.global.* -import xyz.alexcrea.cuanvil.update.Update_1_21 +import xyz.alexcrea.cuanvil.update.plugin.PluginUpdates class ReloadExecutor : CommandExecutor { override fun onCommand(sender: CommandSender, cmd: Command, cmdstr: String, args: Array): Boolean { @@ -48,8 +48,8 @@ class ReloadExecutor : CommandExecutor { UnitRepairConfigGui.getCurrentInstance()?.reloadValues() CustomRecipeConfigGui.getCurrentInstance()?.reloadValues() - // temporary: handle 1.21 update - Update_1_21.handleUpdate() + // handle minecraft version update + PluginUpdates.handleMCVersionUpdate() // Handle dependency reload DependencyManager.handleConfigReload() From dd2f3204c394497be848c34b6db1e544a211f78f Mon Sep 17 00:00:00 2001 From: alexcrea Date: Thu, 25 Sep 2025 20:24:22 +0200 Subject: [PATCH 005/207] logic issue fix --- .../java/xyz/alexcrea/cuanvil/update/Update_1_21.java | 8 +++----- .../java/xyz/alexcrea/cuanvil/update/Update_1_21_9.java | 8 +++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21.java b/src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21.java index bd5ccf9..1695b34 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21.java +++ b/src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21.java @@ -5,22 +5,20 @@ import xyz.alexcrea.cuanvil.config.ConfigHolder; import static xyz.alexcrea.cuanvil.update.UpdateUtils.addAbsentToList; -// This is a temporary class that aim to handle 1.21 update. -// It will be replaced by a better system later. public class Update_1_21 { private static final Version V1_21 = new Version(1, 21); public static void handleUpdate(Version current){ - // Test if we are running in 1.21.1 - if(V1_21.greaterEqual(current)) + // Test if we are running in 1.21 or better + if(V1_21.greaterThan(current)) return; // if version path is not null then check if its it's before 1.21 String oldVersion = ConfigHolder.DEFAULT_CONFIG.getConfig().getString(UpdateUtils.MINECRAFT_VERSION_PATH); if(oldVersion != null){ var version = Version.fromString(oldVersion); - if(V1_21.greaterEqual(version)) return; + if(V1_21.lesserEqual(version)) return; } doUpdate(); diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21_9.java b/src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21_9.java index 0f169bc..0075285 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21_9.java +++ b/src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21_9.java @@ -5,22 +5,20 @@ import xyz.alexcrea.cuanvil.config.ConfigHolder; import static xyz.alexcrea.cuanvil.update.UpdateUtils.addAbsentToList; -// This is a temporary class that aim to handle 1.21 update. -// It will be replaced by a better system later. public class Update_1_21_9 { private static final Version V1_21_9 = new Version(1, 21, 9); public static void handleUpdate(Version current){ - // Test if we are running in 1.21.1.9 - if(V1_21_9.greaterEqual(current)) + // Test if we are running in 1.21.9 or better + if(V1_21_9.greaterThan(current)) return; // if version path is not null then check if its it's before 1.21.9 String oldVersion = ConfigHolder.DEFAULT_CONFIG.getConfig().getString(UpdateUtils.MINECRAFT_VERSION_PATH); if(oldVersion != null){ var version = Version.fromString(oldVersion); - if(V1_21_9.greaterEqual(version)) return; + if(V1_21_9.lesserEqual(version)) return; } doUpdate(); From 4da017c9be99ae0c0bf0bf780b7dadf83c9c7c22 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Thu, 25 Sep 2025 20:30:03 +0200 Subject: [PATCH 006/207] more pretty log --- .../java/xyz/alexcrea/cuanvil/update/Update_1_21.java | 9 ++++----- .../java/xyz/alexcrea/cuanvil/update/Update_1_21_9.java | 9 ++++----- .../alexcrea/cuanvil/update/plugin/PluginUpdates.java | 9 +++++++-- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21.java b/src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21.java index 1695b34..56de63c 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21.java +++ b/src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21.java @@ -9,19 +9,20 @@ public class Update_1_21 { private static final Version V1_21 = new Version(1, 21); - public static void handleUpdate(Version current){ + public static boolean handleUpdate(Version current){ // Test if we are running in 1.21 or better if(V1_21.greaterThan(current)) - return; + return false; // if version path is not null then check if its it's before 1.21 String oldVersion = ConfigHolder.DEFAULT_CONFIG.getConfig().getString(UpdateUtils.MINECRAFT_VERSION_PATH); if(oldVersion != null){ var version = Version.fromString(oldVersion); - if(V1_21.lesserEqual(version)) return; + if(V1_21.lesserEqual(version)) return false; } doUpdate(); + return true; } private static void doUpdate() { @@ -86,8 +87,6 @@ public class Update_1_21 { // imply reload of CONFLICT_HOLDER // We also do not need to reload base config as there is no object related to it. ConfigHolder.ITEM_GROUP_HOLDER.reload(); - - CustomAnvil.instance.getLogger().info("Updating Done !"); } } diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21_9.java b/src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21_9.java index 0075285..e749d74 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21_9.java +++ b/src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21_9.java @@ -9,19 +9,20 @@ public class Update_1_21_9 { private static final Version V1_21_9 = new Version(1, 21, 9); - public static void handleUpdate(Version current){ + public static boolean handleUpdate(Version current){ // Test if we are running in 1.21.9 or better if(V1_21_9.greaterThan(current)) - return; + return false; // if version path is not null then check if its it's before 1.21.9 String oldVersion = ConfigHolder.DEFAULT_CONFIG.getConfig().getString(UpdateUtils.MINECRAFT_VERSION_PATH); if(oldVersion != null){ var version = Version.fromString(oldVersion); - if(V1_21_9.lesserEqual(version)) return; + if(V1_21_9.lesserEqual(version)) return false; } doUpdate(); + return true; } private static void doUpdate() { @@ -52,8 +53,6 @@ public class Update_1_21_9 { // imply reload of CONFLICT_HOLDER // We also do not need to reload base config as there is no object related to it. ConfigHolder.ITEM_GROUP_HOLDER.reload(); - - CustomAnvil.instance.getLogger().info("Updating Done !"); } } diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PluginUpdates.java b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PluginUpdates.java index 0beaf63..430825c 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PluginUpdates.java +++ b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PluginUpdates.java @@ -57,8 +57,13 @@ public class PluginUpdates { public static void handleMCVersionUpdate(){ Version current = UpdateUtils.currentMinecraftVersion(); - Update_1_21.handleUpdate(current); - Update_1_21_9.handleUpdate(current); + boolean hadUpdate = false; + hadUpdate |= Update_1_21.handleUpdate(current); + hadUpdate |= Update_1_21_9.handleUpdate(current); + + if(hadUpdate){ + CustomAnvil.instance.getLogger().info("Updating Done !"); + } } private static void finishConfiguration(@Nonnull String newVersion, @Nonnull Set toSave) { From 8b7be2dd1fb3ce8f1db20e88f1ea629f722ba742 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Thu, 25 Sep 2025 20:32:05 +0200 Subject: [PATCH 007/207] add default 1.21.9 configurations --- README.md | 3 +- defaultconfigs/1.21.9/README.md | 6 + defaultconfigs/1.21.9/config.yml | 393 +++++++++++++++++++++ defaultconfigs/1.21.9/custom_recipes.yml | 5 + defaultconfigs/1.21.9/enchant_conflict.yml | 382 ++++++++++++++++++++ defaultconfigs/1.21.9/item_groups.yml | 236 +++++++++++++ defaultconfigs/1.21.9/unit_repair_item.yml | 192 ++++++++++ 7 files changed, 1216 insertions(+), 1 deletion(-) create mode 100644 defaultconfigs/1.21.9/README.md create mode 100644 defaultconfigs/1.21.9/config.yml create mode 100644 defaultconfigs/1.21.9/custom_recipes.yml create mode 100644 defaultconfigs/1.21.9/enchant_conflict.yml create mode 100644 defaultconfigs/1.21.9/item_groups.yml create mode 100644 defaultconfigs/1.21.9/unit_repair_item.yml diff --git a/README.md b/README.md index 897d613..3c207da 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,8 @@ For information about the API, please refer to [the Wiki](https://github.com/ale ### Default Plugin's Configurations For 1.18 to 1.20.6 use the [1.18 configurations](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.18)\ -For 1.21 to 1.21.7 use the [1.21 configurations](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21) +For 1.21 to 1.21.8 use the [1.21 configurations](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21)\ +From 1.21.9 use the [1.21.9 configurations](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21) --- Custom anvil [use bstat](https://bstats.org/plugin/bukkit/Unsafe%20Enchants%20Plus/20923) for metric. You can [disable it](https://bstats.org/getting-started) if you like. diff --git a/defaultconfigs/1.21.9/README.md b/defaultconfigs/1.21.9/README.md new file mode 100644 index 0000000..2b3bfa8 --- /dev/null +++ b/defaultconfigs/1.21.9/README.md @@ -0,0 +1,6 @@ +### Default Plugin's Configurations For 1.21.9 +- [config.yml](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21.9/config.yml) +- [enchant_conflict.yml](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21.9/enchant_conflict.yml) +- [item_groups.yml](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21.9/item_groups.yml) +- [unit_repair_item.yml](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21.9/unit_repair_item.yml) +- [custom_recipes.yml](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21.9/custom_recipes.yml) diff --git a/defaultconfigs/1.21.9/config.yml b/defaultconfigs/1.21.9/config.yml new file mode 100644 index 0000000..f6817ba --- /dev/null +++ b/defaultconfigs/1.21.9/config.yml @@ -0,0 +1,393 @@ +# +# It is recommended that you use /configanvil to edit theses config. +# You can still manually edit here if you like to. but if you do, don't forget to /anvilconfigreload after you changes ! +# + +# All anvil cost will be capped to limit_repair_value if enabled. +# +# In other words: +# For any anvil cost greater than limit_repair_value, Cost will be set to limit_repair_value. +limit_repair_cost: false + +# Max cost value the Anvil can get to. +# +# Valid values include 0 to 1000. +# Cost will be displayed as "Too Expensive": +# - If Cost is above 39 +# - And replace_too_expensive is disabled (false) +limit_repair_value: 39 + +# Whether the anvil's cost limit should be removed entirely. +# +# The anvil will still visually display "Too Expensive" if "replace_too_expensive" is disabled +# However, the action will be completable if xp requirement is meet. +remove_repair_limit: false + +# Whenever anvil cost is above 39 should display the true price and not "Too Expensive". +# +# However, when bypassing "Too Expensive", anvil price will be displayed as Green. +# If the action is not completable, the cost will still be displayed as "Too expensive". +# That mean you also need to change other settings like remove_repair_limit or limit_repair_cost. +# +# Require ProtocoLib. +replace_too_expensive: false + +# XP Level amount added to the anvil when the item is repaired by another item of the same type +# +# Valid values include 0 to 1000 +item_repair_cost: 2 + +# XP Level amount added to the anvil when the item is renamed +# +# Valid values include 0 to 1000 +item_rename_cost: 1 + +# XP Level amount added to the anvil when the item is repaired by an "unit" +# For example: a Diamond on a Diamond Sword +# What's considered unit for what can be edited on the unit repair configuration. +# +# Valid values include 0 to 1000 +unit_repair_cost: 1 + +# XP Level amount added to the anvil when a sacrifice enchantment +# conflict with one of the left item enchantment +# +# Valid values include 0 to 1000 +sacrifice_illegal_enchant_cost: 1 + +# Allow using color code and hexadecimal color. +# +# Color code are prefixed by "&" and hexadecimal color by "#". +# Color code will not be applied if it colors nothing. "&&" can be used to write "&". +allow_color_code: false +allow_hexadecimal_color: false + +# Toggle if color should only be applicable if the player a certain permission. +# +# permission are "ca.color.code" for use of color code and "ca.color.hex" for use of hexadecimal color. +permission_needed_for_color: true + +# Xp cost if the player use color in the items name on rename. +# +# Valid values include 0 to 1000. +use_of_color_cost: 0 + +# Default limit to apply to any enchants missing from enchant_limits +# +# Valid values include 1 to 1000 +default_limit: 5 + +# Override limits for specific enchants +# +# Enchantments not listed here will use the value of default_limit +# +# Overrides provided default from aqua_affinity to depth_strider won't change effect with extra levels +# +# Valid range of 1 - 255 for each enchantment +enchant_limits: + minecraft:aqua_affinity: 1 + minecraft:binding_curse: 1 + minecraft:channeling: 1 + minecraft:flame: 1 + minecraft:infinity: 1 + minecraft:mending: 1 + minecraft:multishot: 1 + minecraft:silk_touch: 1 + minecraft:vanishing_curse: 1 + minecraft:depth_strider: 3 # anything more than 3 is treated as 3 by the game + minecraft:protection: 4 + minecraft:fire_protection: 4 + minecraft:blast_protection: 4 + minecraft:projectile_protection: 4 + minecraft:feather_falling: 4 + minecraft:thorns: 3 + minecraft:respiration: 3 + minecraft:sharpness: 5 + minecraft:smite: 5 + minecraft:bane_of_arthropods: 5 + minecraft:knockback: 2 + minecraft:fire_aspect: 2 + minecraft:looting: 3 + minecraft:sweeping: 3 + minecraft:sweeping_edge: 3 + minecraft:efficiency: 5 + minecraft:unbreaking: 3 + minecraft:fortune: 3 + minecraft:power: 5 + minecraft:punch: 2 + minecraft:luck_of_the_sea: 3 + minecraft:lure: 3 + minecraft:frost_walker: 2 + minecraft:impaling: 5 + minecraft:riptide: 3 + minecraft:loyalty: 3 + minecraft:piercing: 4 + minecraft:quick_charge: 3 + minecraft:soul_speed: 3 + minecraft:swift_sneak: 3 + minecraft:density: 5 + minecraft:breach: 4 + minecraft:wind_burst: 3 + +# 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 +# +# If an enchantment is missing values here, or is less than 0, it will default to 0 +# +# Calculated as: [Enchantment lvl] * [multiplier] +# +# With default values protection 4 would have a value of 4 when +# coming from either a book (4 * 1) or an item (4 * 1) +enchant_values: + minecraft:aqua_affinity: + item: 4 + book: 2 + minecraft:bane_of_arthropods: + item: 2 + book: 1 + minecraft:binding_curse: + item: 8 + book: 4 + minecraft:blast_protection: + item: 4 + book: 2 + minecraft:channeling: + item: 8 + book: 4 + minecraft:depth_strider: + item: 4 + book: 2 + minecraft:efficiency: + item: 1 + book: 1 + minecraft:flame: + item: 4 + book: 2 + minecraft:feather_falling: + item: 2 + book: 1 + minecraft:fire_aspect: + item: 4 + book: 2 + minecraft:fire_protection: + item: 2 + book: 1 + minecraft:fortune: + item: 4 + book: 2 + minecraft:frost_walker: + item: 4 + book: 2 + minecraft:impaling: + item: 4 + book: 2 + minecraft:infinity: + item: 8 + book: 4 + minecraft:knockback: + item: 2 + book: 1 + minecraft:looting: + item: 4 + book: 2 + minecraft:loyalty: + item: 1 + book: 1 + minecraft:luck_of_the_sea: + item: 4 + book: 2 + minecraft:lure: + item: 4 + book: 2 + minecraft:mending: + item: 4 + book: 2 + minecraft:multishot: + item: 4 + book: 2 + minecraft:piercing: + item: 1 + book: 1 + minecraft:power: + item: 1 + book: 1 + minecraft:projectile_protection: + item: 2 + book: 1 + minecraft:protection: + item: 1 + book: 1 + minecraft:punch: + item: 4 + book: 2 + minecraft:quick_charge: + item: 2 + book: 1 + minecraft:respiration: + item: 4 + book: 2 + minecraft:riptide: + item: 4 + book: 2 + minecraft:silk_touch: + item: 8 + book: 4 + minecraft:sharpness: + item: 1 + book: 1 + minecraft:smite: + item: 2 + book: 1 + minecraft:soul_speed: + item: 8 + book: 4 + minecraft:swift_sneak: + item: 8 + book: 4 + minecraft:sweeping: + item: 4 + book: 2 + minecraft:sweeping_edge: + item: 4 + book: 2 + minecraft:thorns: + item: 8 + book: 4 + minecraft:unbreaking: + item: 2 + book: 1 + minecraft:vanishing_curse: + item: 8 + book: 4 + minecraft:density: + item: 2 + book: 1 + minecraft:breach: + item: 4 + book: 2 + minecraft:wind_burst: + item: 4 + book: 2 + +# Disable enchantment merging for level above the set value +# Enchantment merging is when, for example, 2 unbreaking II book combine to give sharpness III +# But Enchantment above this value can still be applied. following the previous example, we could still apply a unbreaking III book to a sword +# Even if disable-merge-over of unbreaking is set to 2 +# -1 mean enchantment merge for this enchantment is not disabled. default to -1 if absent. +disable-merge-over: + # Sharpness is set to -1. it equivalent to it not being set to anything (and work as vanilla) + minecraft:sharpness: -1 +# If uncommented. 2 unbreaking II book would not give an unbreaking III book. but unbreaking III book can still be applied +# minecraft:unbreaking: 2 + +# The maximum number of enchantment an item can get. -1 for infinity +# Use eco enchant enchant_limit if present by default unless "default" is not equal to -1 +enchantment_count_limit: + default: -1 + # Limit for specific items. example bellow is an example with stick + # Per item enchantment limit override eco enchant enchant_limit and default limit + items: + stick: -1 + +# Settings for lore modification +lore_edit: + book_and_quil: + # Permission is ca.lore_edit.book + use_permission: true + append: + # If adding lore using book & quil is enabled + enabled: false + # Cost used every time + fixed_cost: 1 + # Cost used for every lore line added + per_line_cost: 0 + # Use left item vanilla cost penalty if any + shared_increase: false + # Increase shared left item cost penalty + shared_additive: false + # If adding the lore consume the book & quil + do_consume: false + # Allow using color code and hexadecimal color when editing lore via book & quil + # + # Color code are prefixed by "&" and hexadecimal color by "#" + # Color code will not be applied if it colors nothing. "&&" can be used to write "&" + allow_color_code: true + allow_hexadecimal_color: true + use_cost: 0 + + remove: + # If removing lore using book & quil is enabled + enabled: false + # Cost used every time + fixed_cost: 1 + # Cost used for every lore line removed + per_line_cost: 0 + # Use left item vanilla cost penalty if any + shared_increase: false + # Increase shared left item cost penalty + shared_additive: false + # If removing the lore consume the book & quil + do_consume: false + # If the color should get back to color code or hex format + remove_color_on_remove: true + # Cost of replacing colors + remove_color_cost: 0 + + paper: + # Permission is ca.lore_edit.paper + use_permission: true + # what order should the lines should get added/removed (start/end, if invalid or not present will be end) + order: end + + append_line: + # If adding lore line using paper is enabled + enabled: false + # Cost used every time + fixed_cost: 1 + # Use left item vanilla cost penalty if any + shared_increase: false + # Increase shared left item cost penalty + shared_additive: false + # If adding the lore line consume the paper + do_consume: false + # Allow using color code and hexadecimal color when editing lore via book & quil + # + # Color code are prefixed by "&" and hexadecimal color by "#" + # Color code will not be applied if it colors nothing. "&&" can be used to write "&" + allow_color_code: true + allow_hexadecimal_color: true + color_use_cost: 0 + use_cost: 0 + + remove_line: + # If removing lore line using paper is enabled + enabled: false + # Cost used every time + fixed_cost: 1 + # Use left item vanilla cost penalty if any + shared_increase: false + # Increase shared left item cost penalty + shared_additive: false + # If removing the lore line consume the paper + do_consume: false + # If the color should get back to color code or hex format + remove_color_on_remove: true + # Cost of replacing colors + remove_color_cost: 0 + +# Whether to show debug logging +debug_log: false + +# Whether to show verbose debug logging +debug_log_verbose: false + +# In case something when wrong with CustomAnvil packet manager. +# If you see "missing class exception" or similar you may test this. +# If enabled and Protocolib absent or disabled "Replace to expensive" will not work. +# ProtocoLib may also be used if the server is in an "unsupported" version even if this option is disabled. +force_protocolib: false + +configVersion: 1.11.0 +lowMinecraftVersion: 1.21.9 diff --git a/defaultconfigs/1.21.9/custom_recipes.yml b/defaultconfigs/1.21.9/custom_recipes.yml new file mode 100644 index 0000000..57c2220 --- /dev/null +++ b/defaultconfigs/1.21.9/custom_recipes.yml @@ -0,0 +1,5 @@ +# ---------------------------------------------------- +# This config file is to store custom craft +# It is recommended to use the in game config editor for this configuration. +# /customanvilconfig With ca.config.edit permission +# ---------------------------------------------------- diff --git a/defaultconfigs/1.21.9/enchant_conflict.yml b/defaultconfigs/1.21.9/enchant_conflict.yml new file mode 100644 index 0000000..04f716f --- /dev/null +++ b/defaultconfigs/1.21.9/enchant_conflict.yml @@ -0,0 +1,382 @@ +# +# It is recommended that you use /configanvil to edit theses config. +# You can still manually edit here if you like to. but if you do, don't forget to /anvilconfigreload after you changes ! +# + +# material conflicts +# +# If you want to edit this file: +# - A conflict will apply to every item except if in one of the notAffectedGroups group +# - the conflict will count only if the user try to combine at least as +# many conflicting enchantment as "maxEnchantmentBeforeConflict" +# +# +# ---------------------------------------------------- +# These restriction are about not allowing enchantment +# on illegal items +# ---------------------------------------------------- + +restriction_aqua_affinity: + enchantments: + - minecraft:aqua_affinity + notAffectedGroups: + - enchanted_book + - helmets + +restriction_bane_of_arthropods: + enchantments: + - minecraft:bane_of_arthropods + notAffectedGroups: + - enchanted_book + - melee_weapons + - mace + +restriction_blast_protection: + enchantments: + - minecraft:blast_protection + notAffectedGroups: + - enchanted_book + - armors + +restriction_channeling: + enchantments: + - minecraft:channeling + notAffectedGroups: + - enchanted_book + - trident + +restriction_binding_curse: + enchantments: + - minecraft:binding_curse + notAffectedGroups: + - enchanted_book + - wearable + +restriction_vanishing_curse: + enchantments: + - minecraft:vanishing_curse + notAffectedGroups: + - enchanted_book + - can_vanish + +restriction_depth_strider: + enchantments: + - minecraft:depth_strider + notAffectedGroups: + - enchanted_book + - boots + +restriction_efficiency: + enchantments: + - minecraft:efficiency + notAffectedGroups: + - enchanted_book + - tools + - shears + +restriction_feather_falling: + enchantments: + - minecraft:feather_falling + notAffectedGroups: + - enchanted_book + - boots + +restriction_fire_aspect: + enchantments: + - minecraft:fire_aspect + notAffectedGroups: + - enchanted_book + - swords + - mace + +restriction_fire_protection: + enchantments: + - minecraft:fire_protection + notAffectedGroups: + - enchanted_book + - armors + +restriction_flame: + enchantments: + - minecraft:flame + notAffectedGroups: + - enchanted_book + - bow + +restriction_fortune: + enchantments: + - minecraft:fortune + notAffectedGroups: + - enchanted_book + - tools + +restriction_frost_walker: + enchantments: + - minecraft:frost_walker + notAffectedGroups: + - enchanted_book + - boots + +restriction_impaling: + enchantments: + - minecraft:impaling + notAffectedGroups: + - enchanted_book + - trident + +restriction_infinity: + enchantments: + - minecraft:infinity + notAffectedGroups: + - enchanted_book + - bow + +restriction_knockback: + enchantments: + - minecraft:knockback + notAffectedGroups: + - enchanted_book + - swords + +restriction_looting: + enchantments: + - minecraft:looting + notAffectedGroups: + - enchanted_book + - swords + +restriction_loyalty: + enchantments: + - minecraft:loyalty + notAffectedGroups: + - enchanted_book + - trident + +restriction_lure: + enchantments: + - minecraft:lure + notAffectedGroups: + - enchanted_book + - fishing_rod + +restriction_mending: + enchantments: + - minecraft:mending + notAffectedGroups: + - enchanted_book + - can_unbreak + +restriction_minecraft_multishot: + enchantments: + - minecraft:multishot + notAffectedGroups: + - enchanted_book + - crossbow + +restriction_piercing: + enchantments: + - minecraft:piercing + notAffectedGroups: + - enchanted_book + - crossbow + +restriction_power: + enchantments: + - minecraft:power + notAffectedGroups: + - enchanted_book + - bow + +restriction_projectile_protection: + enchantments: + - minecraft:projectile_protection + notAffectedGroups: + - enchanted_book + - armors + +restriction_protection: + enchantments: + - minecraft:protection + notAffectedGroups: + - enchanted_book + - armors + +restriction_punch: + enchantments: + - minecraft:punch + notAffectedGroups: + - enchanted_book + - bow + +restriction_quick_charge: + enchantments: + - minecraft:quick_charge + notAffectedGroups: + - enchanted_book + - crossbow + +restriction_respiration: + enchantments: + - minecraft:respiration + notAffectedGroups: + - enchanted_book + - helmets + +restriction_riptide: + enchantments: + - minecraft:riptide + notAffectedGroups: + - enchanted_book + - trident + +restriction_sharpness: + enchantments: + - minecraft:sharpness + notAffectedGroups: + - enchanted_book + - melee_weapons + +restriction__silk_touch: + enchantments: + - minecraft:silk_touch + notAffectedGroups: + - enchanted_book + - tools + +restriction_smite: + enchantments: + - minecraft:smite + notAffectedGroups: + - enchanted_book + - melee_weapons + - mace + +restriction_soul_speed: + enchantments: + - minecraft:soul_speed + notAffectedGroups: + - enchanted_book + - boots + +restriction_sweeping_edge: + enchantments: + - minecraft:sweeping + - minecraft:sweeping_edge + notAffectedGroups: + - enchanted_book + - swords + +# Do not exist in 1.18, that mean useInFuture will be set to true +# useInFuture set to true also mean it will not warn if there is an issue +restriction_swift_sneak: + useInFuture: true + enchantments: + - minecraft:swift_sneak + notAffectedGroups: + - enchanted_book + - leggings + +restriction_thorns: + enchantments: + - minecraft:thorns + notAffectedGroups: + - enchanted_book + - armors + +restriction__unbreaking: + enchantments: + - minecraft:unbreaking + notAffectedGroups: + - enchanted_book + - can_unbreak + +# ---------------------------------------------------- +# Now we have conflicts about enchantment Incompatibility +# We just filtered what item enchantments can be applied +# notAffectedGroups is empty as we don't want anything to not respect theses rules +# maxEnchantmentBeforeConflict is set to 1 to only have 1 on those enchantment available +# ---------------------------------------------------- + +sword_enchant_conflict: + enchantments: + - minecraft:bane_of_arthropods + - minecraft:smite + - minecraft:sharpness + - minecraft:density + - minecraft:breach + notAffectedGroups: [] + maxEnchantmentBeforeConflict: 1 + +protection_enchant_conflict: + enchantments: + - minecraft:blast_protection + - minecraft:fire_protection + - minecraft:projectile_protection + - minecraft:protection + notAffectedGroups: [] + maxEnchantmentBeforeConflict: 1 + +trident_conflict1: + enchantments: + - minecraft:channeling + - minecraft:riptide + notAffectedGroups: [] + maxEnchantmentBeforeConflict: 1 + +trident_conflict2: + enchantments: + - minecraft:loyalty + - minecraft:riptide + notAffectedGroups: [] + maxEnchantmentBeforeConflict: 1 + +boot_conflict: + enchantments: + - minecraft:depth_strider + - minecraft:frost_walker + notAffectedGroups: [] + maxEnchantmentBeforeConflict: 1 + +tool_conflict: + enchantments: + - minecraft:fortune + - minecraft:silk_touch + notAffectedGroups: [] + maxEnchantmentBeforeConflict: 1 + +bow_conflict: + enchantments: + - minecraft:mending + - minecraft:infinity + notAffectedGroups: [] + maxEnchantmentBeforeConflict: 1 + +crossbow_conflict: + enchantments: + - minecraft:multishot + - minecraft:piercing + notAffectedGroups: [] + maxEnchantmentBeforeConflict: 1 +restriction_density: + enchantments: + - minecraft:density + notAffectedGroups: + - mace + - enchanted_book +restriction_breach: + enchantments: + - minecraft:breach + notAffectedGroups: + - mace + - enchanted_book +restriction_wind_burst: + enchantments: + - minecraft:wind_burst + notAffectedGroups: + - mace + - enchanted_book + +# ---------------------------------------------------- +# Bellow is for custom conflicts. +# This is also where conflict create from the gui will be placed. +# ---------------------------------------------------- diff --git a/defaultconfigs/1.21.9/item_groups.yml b/defaultconfigs/1.21.9/item_groups.yml new file mode 100644 index 0000000..2dbd5db --- /dev/null +++ b/defaultconfigs/1.21.9/item_groups.yml @@ -0,0 +1,236 @@ +# +# It is recommended that you use /configanvil to edit theses config. +# You can still manually edit here if you like to. but if you do, don't forget to /anvilconfigreload after you changes ! +# + +# Please note this config use spigot material names. +# It should match minecraft name in most case, maybe every case, but I can't be sure +# In case there an issue with material name, you can found them here: +# https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Material.html + +# An empty Exclude group exclude nothing, so it contain everything +everything: + type: exclude + +# An empty include group will include nothing +nothing: + type: include + +# This group is an example of a group including only stone and polished granite +example_include: + type: include + items: + - stone + - polished_granite + +# This group contain everything except polished granite and elements of example_include +example_exclude: + type: exclude + items: + - polished_granite + groups: + - example_include + +# Default configuration should be vanilla enchantment conflict group +# there may have error, if you find one you can fix it ! +# https://minecraft.fandom.com/wiki/Enchanting + +swords: + type: include + items: + - wooden_sword + - stone_sword + - iron_sword + - diamond_sword + - golden_sword + - netherite_sword + - copper_sword + +axes: + type: include + items: + - wooden_axe + - stone_axe + - iron_axe + - diamond_axe + - golden_axe + - netherite_axe + - copper_axe + +melee_weapons: + type: include + groups: + - swords + - axes + +helmets: + type: include + items: + - leather_helmet + - chainmail_helmet + - iron_helmet + - diamond_helmet + - golden_helmet + - netherite_helmet + - turtle_helmet + - copper_helmet + +chestplate: + type: include + items: + - leather_chestplate + - chainmail_chestplate + - iron_chestplate + - diamond_chestplate + - golden_chestplate + - netherite_chestplate + - copper_chestplate + +leggings: + type: include + items: + - leather_leggings + - chainmail_leggings + - iron_leggings + - diamond_leggings + - golden_leggings + - netherite_leggings + - copper_leggings + +boots: + type: include + items: + - leather_boots + - chainmail_boots + - iron_boots + - diamond_boots + - golden_boots + - netherite_boots + - copper_boots + +armors: + type: include + groups: + - helmets + - chestplate + - leggings + - boots + +wearable: + type: include + items: + - elytra + - carved_pumpkin + - skeleton_skull + - wither_skeleton_skull + - zombie_head + - player_head + - creeper_head + - dragon_head + - piglin_head + groups: + - armors + +pickaxes: + type: include + items: + - wooden_pickaxe + - stone_pickaxe + - iron_pickaxe + - diamond_pickaxe + - golden_pickaxe + - netherite_pickaxe + - copper_pickaxe + +shovels: + type: include + items: + - wooden_shovel + - stone_shovel + - iron_shovel + - diamond_shovel + - golden_shovel + - netherite_shovel + - copper_shovel + +hoes: + type: include + items: + - wooden_hoe + - stone_hoe + - iron_hoe + - diamond_hoe + - golden_hoe + - netherite_hoe + - copper_hoe + +tools: + type: include + groups: + - pickaxes + - shovels + - hoes + - axes + +enchanted_book: + type: include + items: + - enchanted_book + +trident: + type: include + items: + - trident + +bow: + type: include + items: + - bow + +crossbow: + type: include + items: + - crossbow + +fishing_rod: + type: include + items: + - fishing_rod + +shears: + type: include + items: + - shears + +can_unbreak: + type: include + items: + - elytra + - flint_and_steel + - shield + - carrot_on_a_stick + - warped_fungus_on_a_stick + - brush + groups: + - melee_weapons + - tools + - armors + - trident + - bow + - crossbow + - fishing_rod + - shears + - mace + +can_vanish: + type: include + items: + - compass + groups: + - wearable + - can_unbreak +mace: + type: include + items: + - mace + diff --git a/defaultconfigs/1.21.9/unit_repair_item.yml b/defaultconfigs/1.21.9/unit_repair_item.yml new file mode 100644 index 0000000..0ce2bce --- /dev/null +++ b/defaultconfigs/1.21.9/unit_repair_item.yml @@ -0,0 +1,192 @@ +# +# It is recommended that you use /configanvil to edit theses config. +# You can still manually edit here if you like to. but if you do, don't forget to /anvilconfigreload after you changes ! +# + +# Unit repair configuration +# +# This configuration is to make custom unit repair +# A unit repair is, for example, a diamond to repair a diamond sword +# In vanilla, a unit repair 25% of object durability +# you can make a custom value here +# +# Item name should NOT combine caps and no caps (example: Stone) + +# Default value if the config is an invalid value (value <= 0 ) +# If value > 1 it will be treated as being = 1 +default_repair_amount: 0.25 + +# You can add custom unit repair +# The example bellow make a shield repaired by 10% by sticks + +# stick: +# shield: 0.10 + + +# Vanilla unit repair group is bellow +diamond: + diamond_helmet: 0.25 + diamond_chestplate: 0.25 + diamond_leggings: 0.25 + diamond_boots: 0.25 + diamond_sword: 0.25 + diamond_pickaxe: 0.25 + diamond_axe: 0.25 + diamond_shovel: 0.25 + diamond_hoe: 0.25 + +netherite_ingot: + netherite_helmet: 0.25 + netherite_chestplate: 0.25 + netherite_leggings: 0.25 + netherite_boots: 0.25 + netherite_sword: 0.25 + netherite_pickaxe: 0.25 + netherite_axe: 0.25 + netherite_shovel: 0.25 + netherite_hoe: 0.25 + +gold_ingot: + golden_helmet: 0.25 + golden_chestplate: 0.25 + golden_leggings: 0.25 + golden_boots: 0.25 + golden_sword: 0.25 + golden_pickaxe: 0.25 + golden_axe: 0.25 + golden_shovel: 0.25 + golden_hoe: 0.25 + +iron_ingot: + iron_helmet: 0.25 + iron_chestplate: 0.25 + iron_leggings: 0.25 + iron_boots: 0.25 + iron_sword: 0.25 + iron_pickaxe: 0.25 + iron_axe: 0.25 + iron_shovel: 0.25 + iron_hoe: 0.25 + +cobblestone: + stone_sword: 0.25 + stone_pickaxe: 0.25 + stone_axe: 0.25 + stone_shovel: 0.25 + stone_hoe: 0.25 + +cobbled_deepslate: + stone_sword: 0.25 + stone_pickaxe: 0.25 + stone_axe: 0.25 + stone_shovel: 0.25 + stone_hoe: 0.25 + +blackstone: + stone_sword: 0.25 + stone_pickaxe: 0.25 + stone_axe: 0.25 + stone_shovel: 0.25 + stone_hoe: 0.25 + +leather: + leather_helmet: 0.25 + leather_chestplate: 0.25 + leather_leggings: 0.25 + leather_boots: 0.25 + +phantom_membrane: + elytra: 0.25 + +scute: + turtle_helmet: 0.25 + +oak_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + +spruce_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + +birch_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + +jungle_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + +acacia_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + +dark_oak_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + +mangrove_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + +cherry_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + +bamboo_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + +crimson_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + +warped_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 +breeze_rod: + mace: 0.25 From 49845f8d6b0708b5b914dfdd232791f277a533f9 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Thu, 25 Sep 2025 20:34:00 +0200 Subject: [PATCH 008/207] prepare version bumb --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 23cb67e..9aeb5ab 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,7 +18,7 @@ plugins { } group = "xyz.alexcrea" -version = "1.14.2" +version = "1.15.0" val effectiveVersion = "$version" + (if (System.getenv("SMALL_COMMIT_HASH") != null) "-dev-${System.getenv("SMALL_COMMIT_HASH")!!}" else "") From 07f6da525d9155670a90f61b798d824e3e790f4a Mon Sep 17 00:00:00 2001 From: alexcrea <42614139+alexcrea@users.noreply.github.com> Date: Sat, 27 Sep 2025 13:19:21 +0200 Subject: [PATCH 009/207] Revert "drop 1.17.x to 1.18.x nms" This reverts commit 3ab6dbc155aa3db8634e48c7c5ab036de0a26882. --- README.md | 3 +- build.gradle.kts | 3 ++ nms/v1_17R1/.gitignore | 1 + nms/v1_17R1/build.gradle.kts | 35 +++++++++++++++++++ .../gui/version/v1_17R1_ExternGuiTester.kt | 16 +++++++++ .../packet/versions/V1_17R1_PacketManager.kt | 33 +++++++++++++++++ nms/v1_18R1/.gitignore | 1 + nms/v1_18R1/build.gradle.kts | 35 +++++++++++++++++++ .../gui/version/v1_18R1_ExternGuiTester.kt | 16 +++++++++ .../packet/versions/V1_18R1_PacketManager.kt | 33 +++++++++++++++++ nms/v1_18R2/.gitignore | 1 + nms/v1_18R2/build.gradle.kts | 35 +++++++++++++++++++ .../gui/version/v1_18R2_ExternGuiTester.kt | 16 +++++++++ .../packet/versions/V1_18R2_PacketManager.kt | 33 +++++++++++++++++ settings.gradle.kts | 6 ++++ .../dependency/gui/GuiTesterSelector.kt | 13 ++++++- .../packet/PacketManagerSelector.kt | 14 ++++++-- 17 files changed, 290 insertions(+), 4 deletions(-) create mode 100644 nms/v1_17R1/.gitignore create mode 100644 nms/v1_17R1/build.gradle.kts create mode 100644 nms/v1_17R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_17R1_ExternGuiTester.kt create mode 100644 nms/v1_17R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_17R1_PacketManager.kt create mode 100644 nms/v1_18R1/.gitignore create mode 100644 nms/v1_18R1/build.gradle.kts create mode 100644 nms/v1_18R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_18R1_ExternGuiTester.kt create mode 100644 nms/v1_18R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_18R1_PacketManager.kt create mode 100644 nms/v1_18R2/.gitignore create mode 100644 nms/v1_18R2/build.gradle.kts create mode 100644 nms/v1_18R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_18R2_ExternGuiTester.kt create mode 100644 nms/v1_18R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_18R2_PacketManager.kt diff --git a/README.md b/README.md index 3c207da..829eeb0 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ **Custom Anvil** is a plugin that allows server administrators to customize every aspect of the anvil's mechanics. It is expected to work on 1.18 to 1.21.7 minecraft servers running spigot or paper. +(the plugin support of 1.16.5 to 1.17.1 is experimental and may encounter issues) **Custom Anvil** was previously named **Unsafe Enchants+**. It was renamed because it now affects every anvil aspect and not only unsafe enchants\ @@ -86,7 +87,7 @@ as low priority as I work for the plugin on my free time for free. One of the configurations allow displaying price about 40 and removing Too Expensive. \ By how the minecraft client work: price above 40 can only be displayed green, even if the player does not own enough experience level. -Minecraft version 1.19 to 1.21.7 do not need any dependency. Other version need ProtocoLib enabled on your server for this feature. \ +Minecraft version 1.17 to 1.21.7 do not need any dependency. Other version need ProtocoLib enabled on your server for this feature. \ You can also wait for an update of the plugin to support a newer version. Please note that 1.16.5 to 1.17.1 are not officially supported. Run at your own risk. diff --git a/build.gradle.kts b/build.gradle.kts index 9aeb5ab..3bee4ce 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -69,6 +69,9 @@ dependencies { // Include nms implementation(project(":nms:nms-common")) + implementation(project(":nms:v1_17R1", configuration = "reobf")) + implementation(project(":nms:v1_18R1", configuration = "reobf")) + implementation(project(":nms:v1_18R2", configuration = "reobf")) implementation(project(":nms:v1_19R1", configuration = "reobf")) implementation(project(":nms:v1_19R2", configuration = "reobf")) implementation(project(":nms:v1_19R3", configuration = "reobf")) diff --git a/nms/v1_17R1/.gitignore b/nms/v1_17R1/.gitignore new file mode 100644 index 0000000..47374f1 --- /dev/null +++ b/nms/v1_17R1/.gitignore @@ -0,0 +1 @@ +.lastDeploymentsId \ No newline at end of file diff --git a/nms/v1_17R1/build.gradle.kts b/nms/v1_17R1/build.gradle.kts new file mode 100644 index 0000000..9a79bca --- /dev/null +++ b/nms/v1_17R1/build.gradle.kts @@ -0,0 +1,35 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +group = rootProject.group +version = rootProject.version + +plugins { + id("io.papermc.paperweight.userdev") +} + +dependencies { + implementation(project(":nms:nms-common")) + + // Used for nms + paperweight.paperDevBundle("1.17.1-R0.1-SNAPSHOT") +} + +repositories { + maven("https://repo.papermc.io/repository/maven-public/") + +} + +// Set target version +tasks.withType().configureEach { + sourceCompatibility = "16" + targetCompatibility = "16" + + options.encoding = "UTF-8" +} + +kotlin { + compilerOptions { + apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) + jvmTarget.set(JvmTarget.JVM_16) + } +} diff --git a/nms/v1_17R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_17R1_ExternGuiTester.kt b/nms/v1_17R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_17R1_ExternGuiTester.kt new file mode 100644 index 0000000..8e352e0 --- /dev/null +++ b/nms/v1_17R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_17R1_ExternGuiTester.kt @@ -0,0 +1,16 @@ +package xyz.alexcrea.cuanvil.dependency.gui.version + +import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftInventoryView +import org.bukkit.inventory.InventoryView +import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester + +class v1_17R1_ExternGuiTester: ExternGuiTester { + override val wesjdAnvilGuiName = "Wrapper1_17_R1" + + override fun getContainerClass(view: InventoryView): Class? { + if (view !is CraftInventoryView) return null + val container = view.handle + + return container.javaClass + } +} \ No newline at end of file diff --git a/nms/v1_17R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_17R1_PacketManager.kt b/nms/v1_17R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_17R1_PacketManager.kt new file mode 100644 index 0000000..c820eab --- /dev/null +++ b/nms/v1_17R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_17R1_PacketManager.kt @@ -0,0 +1,33 @@ +package xyz.alexcrea.cuanvil.dependency.packet.versions + +import net.minecraft.network.protocol.game.ClientboundPlayerAbilitiesPacket +import net.minecraft.world.entity.player.Abilities +import org.bukkit.craftbukkit.v1_17_R1.entity.CraftPlayer +import org.bukkit.entity.Player +import xyz.alexcrea.cuanvil.dependency.packet.PacketManager +import xyz.alexcrea.cuanvil.dependency.packet.PacketManagerBase + +class V1_17R1_PacketManager : PacketManagerBase(), PacketManager { + override val canSetInstantBuild: Boolean + get() = true + + override fun setInstantBuild(player: Player, instantBuild: Boolean) { + val nmsPlayer = (player as CraftPlayer).handle + val playerAbilities = nmsPlayer.abilities + val sendedAbilities: Abilities + if (playerAbilities.instabuild == instantBuild) { + sendedAbilities = playerAbilities + } else { + sendedAbilities = Abilities() + sendedAbilities.invulnerable = playerAbilities.invulnerable + sendedAbilities.flying = playerAbilities.flying + sendedAbilities.mayfly = playerAbilities.mayfly + sendedAbilities.instabuild = instantBuild + sendedAbilities.mayBuild = playerAbilities.mayBuild + sendedAbilities.flyingSpeed = playerAbilities.flyingSpeed + sendedAbilities.walkingSpeed = playerAbilities.walkingSpeed + } + val packet = ClientboundPlayerAbilitiesPacket(sendedAbilities) + nmsPlayer.connection.send(packet) + } +} diff --git a/nms/v1_18R1/.gitignore b/nms/v1_18R1/.gitignore new file mode 100644 index 0000000..47374f1 --- /dev/null +++ b/nms/v1_18R1/.gitignore @@ -0,0 +1 @@ +.lastDeploymentsId \ No newline at end of file diff --git a/nms/v1_18R1/build.gradle.kts b/nms/v1_18R1/build.gradle.kts new file mode 100644 index 0000000..0ed9674 --- /dev/null +++ b/nms/v1_18R1/build.gradle.kts @@ -0,0 +1,35 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +group = rootProject.group +version = rootProject.version + +plugins { + id("io.papermc.paperweight.userdev") +} + +dependencies { + implementation(project(":nms:nms-common")) + + // Used for nms + paperweight.paperDevBundle("1.18.1-R0.1-SNAPSHOT") +} + +repositories { + maven("https://repo.papermc.io/repository/maven-public/") + +} + +// Set target version +tasks.withType().configureEach { + sourceCompatibility = "17" + targetCompatibility = "17" + + options.encoding = "UTF-8" +} + +kotlin { + compilerOptions { + apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) + jvmTarget.set(JvmTarget.JVM_17) + } +} diff --git a/nms/v1_18R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_18R1_ExternGuiTester.kt b/nms/v1_18R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_18R1_ExternGuiTester.kt new file mode 100644 index 0000000..659a0f6 --- /dev/null +++ b/nms/v1_18R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_18R1_ExternGuiTester.kt @@ -0,0 +1,16 @@ +package xyz.alexcrea.cuanvil.dependency.gui.version + +import org.bukkit.craftbukkit.v1_18_R1.inventory.CraftInventoryView +import org.bukkit.inventory.InventoryView +import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester + +class v1_18R1_ExternGuiTester: ExternGuiTester { + override val wesjdAnvilGuiName = "Wrapper1_18_R1" + + override fun getContainerClass(view: InventoryView): Class? { + if (view !is CraftInventoryView) return null + val container = view.handle + + return container.javaClass + } +} \ No newline at end of file diff --git a/nms/v1_18R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_18R1_PacketManager.kt b/nms/v1_18R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_18R1_PacketManager.kt new file mode 100644 index 0000000..71df5c7 --- /dev/null +++ b/nms/v1_18R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_18R1_PacketManager.kt @@ -0,0 +1,33 @@ +package xyz.alexcrea.cuanvil.dependency.packet.versions + +import net.minecraft.network.protocol.game.ClientboundPlayerAbilitiesPacket +import net.minecraft.world.entity.player.Abilities +import org.bukkit.craftbukkit.v1_18_R1.entity.CraftPlayer +import org.bukkit.entity.Player +import xyz.alexcrea.cuanvil.dependency.packet.PacketManager +import xyz.alexcrea.cuanvil.dependency.packet.PacketManagerBase + +class V1_18R1_PacketManager : PacketManagerBase(), PacketManager { + override val canSetInstantBuild: Boolean + get() = true + + override fun setInstantBuild(player: Player, instantBuild: Boolean) { + val nmsPlayer = (player as CraftPlayer).handle + val playerAbilities = nmsPlayer.abilities + val sendedAbilities: Abilities + if (playerAbilities.instabuild == instantBuild) { + sendedAbilities = playerAbilities + } else { + sendedAbilities = Abilities() + sendedAbilities.invulnerable = playerAbilities.invulnerable + sendedAbilities.flying = playerAbilities.flying + sendedAbilities.mayfly = playerAbilities.mayfly + sendedAbilities.instabuild = instantBuild + sendedAbilities.mayBuild = playerAbilities.mayBuild + sendedAbilities.flyingSpeed = playerAbilities.flyingSpeed + sendedAbilities.walkingSpeed = playerAbilities.walkingSpeed + } + val packet = ClientboundPlayerAbilitiesPacket(sendedAbilities) + nmsPlayer.connection.send(packet) + } +} diff --git a/nms/v1_18R2/.gitignore b/nms/v1_18R2/.gitignore new file mode 100644 index 0000000..47374f1 --- /dev/null +++ b/nms/v1_18R2/.gitignore @@ -0,0 +1 @@ +.lastDeploymentsId \ No newline at end of file diff --git a/nms/v1_18R2/build.gradle.kts b/nms/v1_18R2/build.gradle.kts new file mode 100644 index 0000000..2cc9659 --- /dev/null +++ b/nms/v1_18R2/build.gradle.kts @@ -0,0 +1,35 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +group = rootProject.group +version = rootProject.version + +plugins { + id("io.papermc.paperweight.userdev") +} + +dependencies { + implementation(project(":nms:nms-common")) + + // Used for nms + paperweight.paperDevBundle("1.18.2-R0.1-SNAPSHOT") +} + +repositories { + maven("https://repo.papermc.io/repository/maven-public/") + +} + +// Set target version +tasks.withType().configureEach { + sourceCompatibility = "17" + targetCompatibility = "17" + + options.encoding = "UTF-8" +} + +kotlin { + compilerOptions { + apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) + jvmTarget.set(JvmTarget.JVM_17) + } +} diff --git a/nms/v1_18R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_18R2_ExternGuiTester.kt b/nms/v1_18R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_18R2_ExternGuiTester.kt new file mode 100644 index 0000000..1447716 --- /dev/null +++ b/nms/v1_18R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_18R2_ExternGuiTester.kt @@ -0,0 +1,16 @@ +package xyz.alexcrea.cuanvil.dependency.gui.version + +import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftInventoryView +import org.bukkit.inventory.InventoryView +import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester + +class v1_18R2_ExternGuiTester: ExternGuiTester { + override val wesjdAnvilGuiName = "Wrapper1_18_R2" + + override fun getContainerClass(view: InventoryView): Class? { + if (view !is CraftInventoryView) return null + val container = view.handle + + return container.javaClass + } +} \ No newline at end of file diff --git a/nms/v1_18R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_18R2_PacketManager.kt b/nms/v1_18R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_18R2_PacketManager.kt new file mode 100644 index 0000000..ee442f5 --- /dev/null +++ b/nms/v1_18R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_18R2_PacketManager.kt @@ -0,0 +1,33 @@ +package xyz.alexcrea.cuanvil.dependency.packet.versions + +import net.minecraft.network.protocol.game.ClientboundPlayerAbilitiesPacket +import net.minecraft.world.entity.player.Abilities +import org.bukkit.craftbukkit.v1_18_R2.entity.CraftPlayer +import org.bukkit.entity.Player +import xyz.alexcrea.cuanvil.dependency.packet.PacketManager +import xyz.alexcrea.cuanvil.dependency.packet.PacketManagerBase + +class V1_18R2_PacketManager : PacketManagerBase(), PacketManager { + override val canSetInstantBuild: Boolean + get() = true + + override fun setInstantBuild(player: Player, instantBuild: Boolean) { + val nmsPlayer = (player as CraftPlayer).handle + val playerAbilities = nmsPlayer.abilities + val sendedAbilities: Abilities + if (playerAbilities.instabuild == instantBuild) { + sendedAbilities = playerAbilities + } else { + sendedAbilities = Abilities() + sendedAbilities.invulnerable = playerAbilities.invulnerable + sendedAbilities.flying = playerAbilities.flying + sendedAbilities.mayfly = playerAbilities.mayfly + sendedAbilities.instabuild = instantBuild + sendedAbilities.mayBuild = playerAbilities.mayBuild + sendedAbilities.flyingSpeed = playerAbilities.flyingSpeed + sendedAbilities.walkingSpeed = playerAbilities.walkingSpeed + } + val packet = ClientboundPlayerAbilitiesPacket(sendedAbilities) + nmsPlayer.connection.send(packet) + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index a63389b..867d1bc 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,6 +3,12 @@ rootProject.name = "CustomAnvil" // NMS subproject include("nms:nms-common") findProject(":nms:nms-common")?.name = "nms-common" +include("nms:v1_17R1") +findProject(":nms:v1_17R1")?.name = "v1_17R1" +include("nms:v1_18R1") +findProject(":nms:v1_18R1")?.name = "v1_18R1" +include("nms:v1_18R2") +findProject(":nms:v1_18R2")?.name = "v1_18R2" include("nms:v1_19R1") findProject(":nms:v1_19R1")?.name = "v1_19R1" include("nms:v1_19R2") diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt index d819430..8e4623c 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt @@ -11,7 +11,18 @@ object GuiTesterSelector { if (versionParts[0] != 1) return null return when (versionParts[1]) { - // Can't support 1.16.5-1.18.x paper userdev do not exist or broken + // Can't support 1.16.5 bc 1.16.5 paper userdev do not exist + + 17 -> when (versionParts[2]) { + 0, 1 -> v1_17R1_ExternGuiTester() + else -> null + } + + 18 -> when (versionParts[2]) { + 0, 1 -> v1_18R1_ExternGuiTester() + 2 -> v1_18R2_ExternGuiTester() + else -> null + } 19 -> when (versionParts[2]) { 0, 1, 2 -> v1_19R1_ExternGuiTester() diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt index 7ba3586..9ec231b 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt @@ -19,14 +19,24 @@ object PacketManagerSelector { ProtocoLibWrapper() else NoPacketManager() - private val versionSpecificManager: PacketManagerBase? get() { val versionParts = UpdateUtils.currentMinecraftVersionArray() if (versionParts[0] != 1) return null return when (versionParts[1]) { - // Can't support 1.16.5 to 1.18.x bc paper userdev do not exist or broken + // Can't support 1.16.5 bc 1.16.5 paper userdev do not exist + + 17 -> when (versionParts[2]) { + 0, 1 -> V1_17R1_PacketManager() + else -> null + } + + 18 -> when (versionParts[2]) { + 0, 1 -> V1_18R1_PacketManager() + 2 -> V1_18R2_PacketManager() + else -> null + } 19 -> when (versionParts[2]) { 0, 1, 2 -> V1_19R1_PacketManager() From f907a4b6c4b2f0648e7a8f1234487f97afe2f348 Mon Sep 17 00:00:00 2001 From: alexcrea <42614139+alexcrea@users.noreply.github.com> Date: Sat, 27 Sep 2025 19:01:36 +0200 Subject: [PATCH 010/207] prepare release --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 3bee4ce..3dfc3c8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -84,7 +84,7 @@ dependencies { implementation(project(":nms:v1_21R3", configuration = "reobf")) implementation(project(":nms:v1_21R4", configuration = "reobf")) implementation(project(":nms:v1_21R5", configuration = "reobf")) - implementation(project(":nms:v1_21R6"))//, configuration = "reobf")) //TODO add back when 1.21.9 release + implementation(project(":nms:v1_21R6", configuration = "reobf")) // include kotlin for the offline jar implementation(kotlin("stdlib")) From 8615ec82e274788e6c9da42470d7205aa8424746 Mon Sep 17 00:00:00 2001 From: alexcrea <42614139+alexcrea@users.noreply.github.com> Date: Sat, 27 Sep 2025 19:03:04 +0200 Subject: [PATCH 011/207] Prepare for release --- nms/v1_21R6/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nms/v1_21R6/build.gradle.kts b/nms/v1_21R6/build.gradle.kts index 1ec1258..f124fdd 100644 --- a/nms/v1_21R6/build.gradle.kts +++ b/nms/v1_21R6/build.gradle.kts @@ -11,7 +11,7 @@ dependencies { implementation(project(":nms:nms-common")) // Used for nms - paperweight.paperDevBundle("1.21.9-pre4-R0.1-SNAPSHOT") + paperweight.paperDevBundle("1.21.9-R0.1-SNAPSHOT") } repositories { From c63482c9df76ecc48d9c882e8c1d272161ca291b Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 7 Oct 2025 04:41:47 +0200 Subject: [PATCH 012/207] prepare nms for 1.21.10 --- build.gradle.kts | 2 +- .../xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt | 2 +- .../alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 3dfc3c8..675c4ec 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,7 +18,7 @@ plugins { } group = "xyz.alexcrea" -version = "1.15.0" +version = "1.15.1" val effectiveVersion = "$version" + (if (System.getenv("SMALL_COMMIT_HASH") != null) "-dev-${System.getenv("SMALL_COMMIT_HASH")!!}" else "") diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt index 8e4623c..f3d2122 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt @@ -45,7 +45,7 @@ object GuiTesterSelector { 4 -> v1_21R3_ExternGuiTester() 5 -> v1_21R4_ExternGuiTester() 6, 7, 8 -> v1_21R5_ExternGuiTester() - 9 -> v1_21R6_ExternGuiTester() + 9, 10 -> v1_21R6_ExternGuiTester() else -> null } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt index 9ec231b..985b3f5 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt @@ -59,7 +59,7 @@ object PacketManagerSelector { 4 -> V1_21R3_PacketManager() 5 -> V1_21R4_PacketManager() 6, 7, 8 -> V1_21R5_PacketManager() - 9 -> V1_21R6_PacketManager() + 9, 10 -> V1_21R6_PacketManager() else -> null } From 9912da869c5619fe6ed9aa64ea128d13bcf25b91 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Sun, 19 Oct 2025 16:45:45 +0200 Subject: [PATCH 013/207] add minimessage for color and decoration only --- README.md | 7 +-- build.gradle.kts | 3 ++ defaultconfigs/1.18/config.yml | 2 + defaultconfigs/1.21.9/config.yml | 2 + defaultconfigs/1.21/config.yml | 2 + .../io/delilaheve/util/ConfigOptions.kt | 16 ++++++- .../cuanvil/listener/PrepareAnvilListener.kt | 2 +- .../alexcrea/cuanvil/util/AnvilColorUtil.kt | 48 ++++++++++++++++--- .../cuanvil/util/AnvilLoreEditUtil.kt | 6 ++- .../cuanvil/util/config/LoreEditConfigUtil.kt | 2 + .../cuanvil/util/config/LoreEditType.kt | 13 +++++ src/main/resources/config.yml | 2 + 12 files changed, 91 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 829eeb0..492d6f6 100644 --- a/README.md +++ b/README.md @@ -41,9 +41,10 @@ ca.config.edit: Allow administrator to edit the plugin's config in game # Bellow permissions also require some config change to allow usage of features # usage of these permission is toggleable in basic config gui or config.yml -# Permissions related to use of color -ca.color.code: Allow player to use color code if enabled (toggleable) -ca.color.hex: Allow player to use hexadecimal color if enabled (toggleable) +# Permissions related to use of color and minimessage +ca.color.code: Allow player to use color code on rename if enabled (toggleable) +ca.color.hex: Allow player to use hexadecimal color on rename if enabled (toggleable) +ca.color.minimessage: Allow player to use minimessage formating on rename if enabled (toggleable) # Permissions related to edition of the lore ca.lore_edit.book: Allow player to edit lore via book and quil if enabled (toggleable) diff --git a/build.gradle.kts b/build.gradle.kts index 675c4ec..ef848c7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -35,6 +35,9 @@ dependencies { // Spigot api compileOnly("org.spigotmc:spigot-api:1.18-R0.1-SNAPSHOT") + // minimessage + implementation("net.kyori:adventure-text-minimessage:4.25.0") + // Gui library val inventoryFramework = "xyz.alexcrea.cuanvil.inventoryframework:IF-CustomAnvil:0.10.18.2" implementation(inventoryFramework) diff --git a/defaultconfigs/1.18/config.yml b/defaultconfigs/1.18/config.yml index fbe43f7..c558ec3 100644 --- a/defaultconfigs/1.18/config.yml +++ b/defaultconfigs/1.18/config.yml @@ -59,8 +59,10 @@ sacrifice_illegal_enchant_cost: 1 # # Color code are prefixed by "&" and hexadecimal color by "#". # Color code will not be applied if it colors nothing. "&&" can be used to write "&". +# For minimessage search for minimessage formating https://docs.papermc.io/adventure/minimessage/format/ allow_color_code: false allow_hexadecimal_color: false +allow_minimessage: false # Toggle if color should only be applicable if the player a certain permission. # diff --git a/defaultconfigs/1.21.9/config.yml b/defaultconfigs/1.21.9/config.yml index f6817ba..9964fe8 100644 --- a/defaultconfigs/1.21.9/config.yml +++ b/defaultconfigs/1.21.9/config.yml @@ -59,8 +59,10 @@ sacrifice_illegal_enchant_cost: 1 # # Color code are prefixed by "&" and hexadecimal color by "#". # Color code will not be applied if it colors nothing. "&&" can be used to write "&". +# For minimessage search for minimessage formating https://docs.papermc.io/adventure/minimessage/format/ allow_color_code: false allow_hexadecimal_color: false +allow_minimessage: false # Toggle if color should only be applicable if the player a certain permission. # diff --git a/defaultconfigs/1.21/config.yml b/defaultconfigs/1.21/config.yml index d6e76bf..b6f0ba0 100644 --- a/defaultconfigs/1.21/config.yml +++ b/defaultconfigs/1.21/config.yml @@ -59,8 +59,10 @@ sacrifice_illegal_enchant_cost: 1 # # Color code are prefixed by "&" and hexadecimal color by "#". # Color code will not be applied if it colors nothing. "&&" can be used to write "&". +# For minimessage search for minimessage formating https://docs.papermc.io/adventure/minimessage/format/ allow_color_code: false allow_hexadecimal_color: false +allow_minimessage: false # Toggle if color should only be applicable if the player a certain permission. # diff --git a/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt b/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt index 2a8360f..877de4c 100644 --- a/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt +++ b/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt @@ -38,6 +38,7 @@ object ConfigOptions { // Color related config const val ALLOW_COLOR_CODE = "allow_color_code" const val ALLOW_HEXADECIMAL_COLOR = "allow_hexadecimal_color" + const val ALLOW_MINIMESSAGE = "allow_minimessage" const val PERMISSION_NEEDED_FOR_COLOR = "permission_needed_for_color" const val USE_OF_COLOR_COST = "use_of_color_cost" @@ -87,13 +88,14 @@ object ConfigOptions { const val DEFAULT_ITEM_RENAME_COST = 1 const val DEFAULT_SACRIFICE_ILLEGAL_COST = 1 - const val DEFAULT_ADD_BOOK_ENCHANTMENT_AS_STORED_ENCHANTMENT = false; + const val DEFAULT_ADD_BOOK_ENCHANTMENT_AS_STORED_ENCHANTMENT = false const val DEFAULT_ENCHANT_COUNT_LIMIT = -1 // Color related config const val DEFAULT_ALLOW_COLOR_CODE = false const val DEFAULT_ALLOW_HEXADECIMAL_COLOR = false + const val DEFAULT_ALLOW_MINIMESSAGE = false const val DEFAULT_PERMISSION_NEEDED_FOR_COLOR = true const val DEFAULT_USE_OF_COLOR_COST = 0 @@ -269,12 +271,22 @@ object ConfigOptions { .getBoolean(ALLOW_HEXADECIMAL_COLOR, DEFAULT_ALLOW_HEXADECIMAL_COLOR) } + /** + * Allow usage of minimessage formating + */ + val allowMinimessage: Boolean + get() { + return ConfigHolder.DEFAULT_CONFIG + .config + .getBoolean(ALLOW_MINIMESSAGE, DEFAULT_ALLOW_MINIMESSAGE) + } + /** * If one of the color component is enabled */ val renameColorPossible: Boolean get() { - return allowColorCode || allowHexadecimalColor + return allowColorCode || allowHexadecimalColor || allowHexadecimalColor } /** diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt index 8eefc97..5ba1dfa 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt @@ -196,7 +196,7 @@ class PrepareAnvilListener : Listener { useColor = AnvilColorUtil.handleColor( resultString, player, ConfigOptions.permissionNeededForColor, - ConfigOptions.allowColorCode, ConfigOptions.allowHexadecimalColor, + ConfigOptions.allowColorCode, ConfigOptions.allowHexadecimalColor, ConfigOptions.allowMinimessage, AnvilColorUtil.ColorUseType.RENAME ) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilColorUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilColorUtil.kt index 277a8d1..91fc31d 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilColorUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilColorUtil.kt @@ -1,5 +1,9 @@ package xyz.alexcrea.cuanvil.util +import net.kyori.adventure.text.minimessage.MiniMessage +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver +import net.kyori.adventure.text.minimessage.tag.standard.StandardTags +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer import org.bukkit.permissions.Permissible import java.util.regex.Matcher import java.util.regex.Pattern @@ -8,9 +12,19 @@ object AnvilColorUtil { private val HEX_PATTERN: Pattern = Pattern.compile("#[A-Fa-f0-9]{6}") // pattern to find hexadecimal string private val TRANSFORMED_HEX_PATTERN = Pattern.compile("§x(§[0-9a-fA-F]){6}") // pattern to find minecraft hex string + //TODO use only things compatible with legacy formating + private val mm = MiniMessage.builder() + .tags(TagResolver.resolver( + StandardTags.color(), + StandardTags.decorations())) + .build() + private val legacymm = LegacyComponentSerializer.legacySection() + /** + * //TODO rework on 2.x.x use (return) component and not legacy string + * * Color a stringbuilder object depending on allowed color type and player permissions on color use type - * @return if the stringbuilder was changed and color applied + * @return if the stringbuilder was changed and color applied or if minimessage formating was applied */ fun handleColor( textToColor: StringBuilder, @@ -18,9 +32,10 @@ object AnvilColorUtil { usePermission: Boolean, allowColorCode: Boolean, allowHexadecimalColor: Boolean, + allowMinimessage: Boolean, useType: ColorUseType ): Boolean { - if (!allowColorCode && !allowHexadecimalColor) return false + if (!allowColorCode && !allowHexadecimalColor && !allowMinimessage) return false val canUseColorCode = allowColorCode && (!usePermission || useType.colorCodePerm == null || player.hasPermission( @@ -30,12 +45,16 @@ object AnvilColorUtil { allowHexadecimalColor && (!usePermission || useType.hexColorPerm == null || player.hasPermission( useType.hexColorPerm )) + val canUseMinimessage = + allowMinimessage && (!usePermission || useType.minimessagePerm == null || player.hasPermission( + useType.minimessagePerm + )) if ((!canUseColorCode) && (!canUseHexColor)) return false var useColor = false // Handle color code - if (canUseColorCode) { + if (canUseColorCode) { // maybe should use LegacyComponentSerializer ? var nbReplacement = replaceAll(textToColor, "&", "§", 2) nbReplacement -= 2 * replaceAll(textToColor, "§§", "&", 2) @@ -48,6 +67,22 @@ object AnvilColorUtil { if (nbReplacement > 0) useColor = true } + if(canUseMinimessage) { + val previousStr = textToColor.toString() + + // we dance with formats here + val fromLegacy = legacymm.deserialize(previousStr) + val toMinimessage = mm.serialize(fromLegacy) + val hackySolution = toMinimessage.replace("\\<", "<") + val fromMinimessage = mm.deserialize(hackySolution) + val toLegacy = legacymm.serialize(fromMinimessage) + + if(previousStr != toLegacy){ + useColor = true + textToColor.replace(0, textToColor.length, toLegacy) + } + } + return useColor } @@ -177,10 +212,11 @@ object AnvilColorUtil { enum class ColorUseType( val colorCodePerm: String?, - val hexColorPerm: String? + val hexColorPerm: String?, + val minimessagePerm: String? ) { - RENAME("ca.color.code", "ca.color.hex"), - LORE_EDIT(null, null) + RENAME("ca.color.code", "ca.color.hex", "ca.color.minimessage"), + LORE_EDIT(null, null, null) } } \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt index dd0da1e..598a7ce 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt @@ -243,9 +243,10 @@ object AnvilLoreEditUtil { private fun colorLines(player: Permissible, lines: ArrayList, editType: LoreEditType): Int { val canUseHex = editType.allowHexColor val canUseColorCode = editType.allowColorCode + val minimessage = editType.allowMinimessage val colorCost = editType.useColorCost - // Now handle color of each lines + // Handle color and minimessage of each lines var hasUsedColor = false for ((index, line) in lines.withIndex()) { val coloredLine = StringBuilder(line) @@ -253,7 +254,8 @@ object AnvilLoreEditUtil { val lineUsedColor = AnvilColorUtil.handleColor( coloredLine, player, - false, canUseColorCode, canUseHex, + false, + canUseColorCode, canUseHex, minimessage, AnvilColorUtil.ColorUseType.LORE_EDIT ) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditConfigUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditConfigUtil.kt index f5758dc..2a2fa3b 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditConfigUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditConfigUtil.kt @@ -17,6 +17,7 @@ object LoreEditConfigUtil { // Color configs path const val ALLOW_COLOR_CODE = "allow_color_code" const val ALLOW_HEX_COLOR = "allow_hexadecimal_color" + const val ALLOW_MINIMESSAGE = "allow_minimessage" const val USE_COLOR_COST = "use_cost" const val REMOVE_COLOR_ON_LORE_REMOVE = "remove_color_on_remove" @@ -42,6 +43,7 @@ object LoreEditConfigUtil { // Color configs defaults const val DEFAULT_ALLOW_COLOR_CODE = true const val DEFAULT_ALLOW_HEX_COLOR = true + const val DEFAULT_ALLOW_MINIMESSAGE = true const val DEFAULT_USE_COLOR_COST = 0 const val DEFAULT_REMOVE_COLOR_ON_LORE_REMOVE = false diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditType.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditType.kt index ed5ef1b..bb1632e 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditType.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditType.kt @@ -3,8 +3,10 @@ package xyz.alexcrea.cuanvil.util.config import xyz.alexcrea.cuanvil.util.AnvilUseType import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.ALLOW_COLOR_CODE import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.ALLOW_HEX_COLOR +import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.ALLOW_MINIMESSAGE import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.DEFAULT_ALLOW_COLOR_CODE import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.DEFAULT_ALLOW_HEX_COLOR +import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.DEFAULT_ALLOW_MINIMESSAGE import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.DEFAULT_REMOVE_COLOR_COST import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.DEFAULT_REMOVE_COLOR_ON_LORE_REMOVE import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.DEFAULT_USE_COLOR_COST @@ -100,6 +102,17 @@ enum class LoreEditType( .getBoolean("${rootPath}.$ALLOW_HEX_COLOR", DEFAULT_ALLOW_HEX_COLOR) } + /** + * Allow usage of minimessage on lore add + */ + val allowMinimessage: Boolean + get() { + if (!isAppend) throw IllegalStateException("Can only call with an append edit type") + return CONFIG + .config + .getBoolean("${rootPath}.$ALLOW_MINIMESSAGE", DEFAULT_ALLOW_MINIMESSAGE) + } + /** * Cost when using either color code and hex color on lore add */ diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index a0f0876..3ba35b9 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -59,8 +59,10 @@ sacrifice_illegal_enchant_cost: 1 # # Color code are prefixed by "&" and hexadecimal color by "#". # Color code will not be applied if it colors nothing. "&&" can be used to write "&". +# For minimessage search for minimessage formating https://docs.papermc.io/adventure/minimessage/format/ allow_color_code: false allow_hexadecimal_color: false +allow_minimessage: false # Toggle if color should only be applicable if the player a certain permission. # From 8411b21d1c6cb000ac43fb42266453ef3eac70bb Mon Sep 17 00:00:00 2001 From: alexcrea Date: Sun, 19 Oct 2025 17:01:30 +0200 Subject: [PATCH 014/207] forgot some config --- defaultconfigs/1.18/config.yml | 8 +++++--- defaultconfigs/1.21.9/config.yml | 14 ++++++++------ defaultconfigs/1.21/config.yml | 6 ++++-- src/main/resources/config.yml | 7 +++++-- 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/defaultconfigs/1.18/config.yml b/defaultconfigs/1.18/config.yml index c558ec3..df156b7 100644 --- a/defaultconfigs/1.18/config.yml +++ b/defaultconfigs/1.18/config.yml @@ -59,7 +59,7 @@ sacrifice_illegal_enchant_cost: 1 # # Color code are prefixed by "&" and hexadecimal color by "#". # Color code will not be applied if it colors nothing. "&&" can be used to write "&". -# For minimessage search for minimessage formating https://docs.papermc.io/adventure/minimessage/format/ +# For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ allow_color_code: false allow_hexadecimal_color: false allow_minimessage: false @@ -303,9 +303,10 @@ lore_edit: # # Color code are prefixed by "&" and hexadecimal color by "#" # Color code will not be applied if it colors nothing. "&&" can be used to write "&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ allow_color_code: true allow_hexadecimal_color: true - use_cost: 0 + allow_minimessage: true remove: # If removing lore using book & quil is enabled @@ -346,10 +347,11 @@ lore_edit: # # Color code are prefixed by "&" and hexadecimal color by "#" # Color code will not be applied if it colors nothing. "&&" can be used to write "&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ allow_color_code: true allow_hexadecimal_color: true + allow_minimessage: true color_use_cost: 0 - use_cost: 0 remove_line: # If removing lore line using paper is enabled diff --git a/defaultconfigs/1.21.9/config.yml b/defaultconfigs/1.21.9/config.yml index 9964fe8..401884d 100644 --- a/defaultconfigs/1.21.9/config.yml +++ b/defaultconfigs/1.21.9/config.yml @@ -315,10 +315,11 @@ lore_edit: # # Color code are prefixed by "&" and hexadecimal color by "#" # Color code will not be applied if it colors nothing. "&&" can be used to write "&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ allow_color_code: true allow_hexadecimal_color: true - use_cost: 0 - + allow_minimessage: true + remove: # If removing lore using book & quil is enabled enabled: false @@ -336,13 +337,13 @@ lore_edit: remove_color_on_remove: true # Cost of replacing colors remove_color_cost: 0 - + paper: # Permission is ca.lore_edit.paper use_permission: true # what order should the lines should get added/removed (start/end, if invalid or not present will be end) order: end - + append_line: # If adding lore line using paper is enabled enabled: false @@ -358,11 +359,12 @@ lore_edit: # # Color code are prefixed by "&" and hexadecimal color by "#" # Color code will not be applied if it colors nothing. "&&" can be used to write "&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ allow_color_code: true allow_hexadecimal_color: true + allow_minimessage: true color_use_cost: 0 - use_cost: 0 - + remove_line: # If removing lore line using paper is enabled enabled: false diff --git a/defaultconfigs/1.21/config.yml b/defaultconfigs/1.21/config.yml index b6f0ba0..0e013ea 100644 --- a/defaultconfigs/1.21/config.yml +++ b/defaultconfigs/1.21/config.yml @@ -303,9 +303,10 @@ lore_edit: # # Color code are prefixed by "&" and hexadecimal color by "#" # Color code will not be applied if it colors nothing. "&&" can be used to write "&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ allow_color_code: true allow_hexadecimal_color: true - use_cost: 0 + allow_minimessage: true remove: # If removing lore using book & quil is enabled @@ -346,10 +347,11 @@ lore_edit: # # Color code are prefixed by "&" and hexadecimal color by "#" # Color code will not be applied if it colors nothing. "&&" can be used to write "&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ allow_color_code: true allow_hexadecimal_color: true + allow_minimessage: true color_use_cost: 0 - use_cost: 0 remove_line: # If removing lore line using paper is enabled diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 3ba35b9..1c8c010 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -303,9 +303,10 @@ lore_edit: # # Color code are prefixed by "&" and hexadecimal color by "#" # Color code will not be applied if it colors nothing. "&&" can be used to write "&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ allow_color_code: true allow_hexadecimal_color: true - use_cost: 0 + allow_minimessage: true remove: # If removing lore using book & quil is enabled @@ -329,7 +330,7 @@ lore_edit: # Permission is ca.lore_edit.paper use_permission: true # what order should the lines should get added/removed (start/end, if invalid or not present will be end) - order: "end" + order: end append_line: # If adding lore line using paper is enabled @@ -346,8 +347,10 @@ lore_edit: # # Color code are prefixed by "&" and hexadecimal color by "#" # Color code will not be applied if it colors nothing. "&&" can be used to write "&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ allow_color_code: true allow_hexadecimal_color: true + allow_minimessage: true color_use_cost: 0 remove_line: From c9e41aceb6314c978bf2371f3379a7f64121acb2 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 20 Oct 2025 11:25:43 +0200 Subject: [PATCH 015/207] some refractor --- README.md | 4 +- .../cuanvil/listener/PrepareAnvilListener.kt | 25 ++++++----- .../alexcrea/cuanvil/util/AnvilColorUtil.kt | 42 +++++++------------ .../cuanvil/util/AnvilLoreEditUtil.kt | 17 +++----- .../alexcrea/cuanvil/util/MiniMessageUtil.kt | 18 ++++++++ src/main/resources/plugin.yml | 3 ++ 6 files changed, 56 insertions(+), 53 deletions(-) create mode 100644 src/main/kotlin/xyz/alexcrea/cuanvil/util/MiniMessageUtil.kt diff --git a/README.md b/README.md index 492d6f6..cc868c5 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ the plugin can be downloaded on - Display XP cost instead of "too expensive" when above level 40. (see below for more information) - Can handle some custom enchantment plugins (see below for more information) - Gui to configure the plugin in game. -- Support of color code and hexadecimal color +- Support use of color code, hexadecimal color and minimessage for color/decoration - (Experimental) Folia support (gui do not work) --- ### Permissions: @@ -44,7 +44,7 @@ ca.config.edit: Allow administrator to edit the plugin's config in game # Permissions related to use of color and minimessage ca.color.code: Allow player to use color code on rename if enabled (toggleable) ca.color.hex: Allow player to use hexadecimal color on rename if enabled (toggleable) -ca.color.minimessage: Allow player to use minimessage formating on rename if enabled (toggleable) +ca.rename.minimessage: Allow player to use minimessage formating on rename if enabled (toggleable) (only legacy compatible at the time) # Permissions related to edition of the lore ca.lore_edit.book: Allow player to edit lore via book and quil if enabled (toggleable) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt index 5ba1dfa..17d5d0d 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt @@ -186,24 +186,23 @@ class PrepareAnvilListener : Listener { private fun handleRename(resultItem: ItemStack, inventory: AnvilInventory, player: HumanEntity): Int { // Can be null - var inventoryName = ChatColor.stripColor(inventory.renameText) + var renameText = ChatColor.stripColor(inventory.renameText) var sumCost = 0 var useColor = false - if (ConfigOptions.renameColorPossible && inventoryName != null) { - val resultString = StringBuilder(inventoryName) - - useColor = AnvilColorUtil.handleColor( - resultString, player, + if (ConfigOptions.renameColorPossible && renameText != null) { + val component = AnvilColorUtil.handleColor( + renameText, player, ConfigOptions.permissionNeededForColor, ConfigOptions.allowColorCode, ConfigOptions.allowHexadecimalColor, ConfigOptions.allowMinimessage, AnvilColorUtil.ColorUseType.RENAME ) - if (useColor) { - inventoryName = resultString.toString() + if (component != null) { + renameText = MiniMessageUtil.legacy_mm.serialize(component) sumCost += ConfigOptions.useOfColorCost + useColor = true } } @@ -214,8 +213,8 @@ class PrepareAnvilListener : Listener { else if (useColor) it.displayName else ChatColor.stripColor(it.displayName) - if (!displayName.contentEquals(inventoryName)) { - it.setDisplayName(inventoryName) + if (!displayName.contentEquals(renameText)) { + it.setDisplayName(renameText) resultItem.itemMeta = it sumCost += ConfigOptions.itemRenameCost @@ -233,10 +232,10 @@ class PrepareAnvilListener : Listener { ) { val newEnchants = first.findEnchantments() .combineWith(second.findEnchantments(), first, player) - var hasChanged = !isIdentical(first.findEnchantments(), newEnchants); + var hasChanged = !isIdentical(first.findEnchantments(), newEnchants) val resultItem = first.clone() - var anvilCost = 0; + var anvilCost = 0 if(hasChanged){ resultItem.setEnchantmentsUnsafe(newEnchants) // Calculate enchantment cost @@ -248,7 +247,7 @@ class PrepareAnvilListener : Listener { // 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 - hasChanged = hasChanged || repaired; + hasChanged = hasChanged || repaired } // Test/stop if nothing changed. diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilColorUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilColorUtil.kt index 91fc31d..2629d1f 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilColorUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilColorUtil.kt @@ -1,9 +1,6 @@ package xyz.alexcrea.cuanvil.util -import net.kyori.adventure.text.minimessage.MiniMessage -import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver -import net.kyori.adventure.text.minimessage.tag.standard.StandardTags -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer +import net.kyori.adventure.text.Component import org.bukkit.permissions.Permissible import java.util.regex.Matcher import java.util.regex.Pattern @@ -12,30 +9,20 @@ object AnvilColorUtil { private val HEX_PATTERN: Pattern = Pattern.compile("#[A-Fa-f0-9]{6}") // pattern to find hexadecimal string private val TRANSFORMED_HEX_PATTERN = Pattern.compile("§x(§[0-9a-fA-F]){6}") // pattern to find minecraft hex string - //TODO use only things compatible with legacy formating - private val mm = MiniMessage.builder() - .tags(TagResolver.resolver( - StandardTags.color(), - StandardTags.decorations())) - .build() - private val legacymm = LegacyComponentSerializer.legacySection() - /** - * //TODO rework on 2.x.x use (return) component and not legacy string - * * Color a stringbuilder object depending on allowed color type and player permissions on color use type * @return if the stringbuilder was changed and color applied or if minimessage formating was applied */ fun handleColor( - textToColor: StringBuilder, + textToColorText: String, player: Permissible, usePermission: Boolean, allowColorCode: Boolean, allowHexadecimalColor: Boolean, allowMinimessage: Boolean, useType: ColorUseType - ): Boolean { - if (!allowColorCode && !allowHexadecimalColor && !allowMinimessage) return false + ): Component? { + if (!allowColorCode && !allowHexadecimalColor && !allowMinimessage) return null val canUseColorCode = allowColorCode && (!usePermission || useType.colorCodePerm == null || player.hasPermission( @@ -50,8 +37,9 @@ object AnvilColorUtil { useType.minimessagePerm )) - if ((!canUseColorCode) && (!canUseHexColor)) return false + if (!canUseColorCode && !canUseHexColor && !canUseMinimessage) return null + val textToColor = StringBuilder(textToColorText) var useColor = false // Handle color code if (canUseColorCode) { // maybe should use LegacyComponentSerializer ? @@ -67,23 +55,23 @@ object AnvilColorUtil { if (nbReplacement > 0) useColor = true } + val previousStr = textToColor.toString() + var result: Component = MiniMessageUtil.legacy_mm.deserialize(previousStr) if(canUseMinimessage) { - val previousStr = textToColor.toString() - // we dance with formats here - val fromLegacy = legacymm.deserialize(previousStr) - val toMinimessage = mm.serialize(fromLegacy) + val toMinimessage = MiniMessageUtil.mm.serialize(result) val hackySolution = toMinimessage.replace("\\<", "<") - val fromMinimessage = mm.deserialize(hackySolution) - val toLegacy = legacymm.serialize(fromMinimessage) + val fromMinimessage = MiniMessageUtil.mm.deserialize(hackySolution) + val toLegacy = MiniMessageUtil.legacy_mm.serialize(fromMinimessage) if(previousStr != toLegacy){ useColor = true - textToColor.replace(0, textToColor.length, toLegacy) + result = fromMinimessage } } - return useColor + return if(useColor) result + else null } /** @@ -215,7 +203,7 @@ object AnvilColorUtil { val hexColorPerm: String?, val minimessagePerm: String? ) { - RENAME("ca.color.code", "ca.color.hex", "ca.color.minimessage"), + RENAME("ca.color.code", "ca.color.hex", "ca.rename.minimessage"), LORE_EDIT(null, null, null) } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt index 598a7ce..0f0d69d 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt @@ -249,27 +249,22 @@ object AnvilLoreEditUtil { // Handle color and minimessage of each lines var hasUsedColor = false for ((index, line) in lines.withIndex()) { - val coloredLine = StringBuilder(line) - - val lineUsedColor = AnvilColorUtil.handleColor( - coloredLine, + val component = AnvilColorUtil.handleColor( + line, player, false, canUseColorCode, canUseHex, minimessage, AnvilColorUtil.ColorUseType.LORE_EDIT ) - if (lineUsedColor) { + if (component != null) { hasUsedColor = true - lines[index] = coloredLine.toString() + lines[index] = MiniMessageUtil.legacy_mm.serialize(component) } } - return if (hasUsedColor) { - colorCost - } else { - 0 - } + return if (hasUsedColor) colorCost + else 0 } fun uncolorLines(player: Permissible, lines: ArrayList, editType: LoreEditType): Int { diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/MiniMessageUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/MiniMessageUtil.kt new file mode 100644 index 0000000..10500e8 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/MiniMessageUtil.kt @@ -0,0 +1,18 @@ +package xyz.alexcrea.cuanvil.util + +import net.kyori.adventure.text.minimessage.MiniMessage +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver +import net.kyori.adventure.text.minimessage.tag.standard.StandardTags +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer + +object MiniMessageUtil { + + val mm = MiniMessage.builder() + .tags(TagResolver.resolver( + StandardTags.color(), + StandardTags.decorations())) + .build() + + val legacy_mm = LegacyComponentSerializer.legacySection() + +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 0e58169..9449f8b 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -46,6 +46,9 @@ permissions: ca.color.hex: default: op description: Allow player to use hexadecimal color if enabled (toggleable) + ca.rename.minimessage: + default: op + description: Allow player to use minimessage formating on rename if enabled (toggleable) (only legacy compatible at the time) # lore edit permissions ca.lore_edit.book: default: op From 2967d500eb7d846810fcb16b0b3c3b5091bad053 Mon Sep 17 00:00:00 2001 From: alexcrea <42614139+alexcrea@users.noreply.github.com> Date: Thu, 23 Oct 2025 08:32:33 +0200 Subject: [PATCH 016/207] use correct condition Co-authored-by: Illyrius <28700752+illyrius666@users.noreply.github.com> --- src/main/kotlin/io/delilaheve/util/ConfigOptions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt b/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt index 877de4c..afb009f 100644 --- a/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt +++ b/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt @@ -286,7 +286,7 @@ object ConfigOptions { */ val renameColorPossible: Boolean get() { - return allowColorCode || allowHexadecimalColor || allowHexadecimalColor + return allowColorCode || allowHexadecimalColor || allowMinimessage } /** From 11f7bf8602573c7603012cde1cc3e7acb6e1926d Mon Sep 17 00:00:00 2001 From: alexcrea Date: Thu, 23 Oct 2025 14:26:11 +0200 Subject: [PATCH 017/207] progress on using pure component in paper --- defaultconfigs/1.18/config.yml | 34 +++- defaultconfigs/1.21.9/config.yml | 34 +++- defaultconfigs/1.21/config.yml | 34 +++- nms/nms-common/build.gradle.kts | 31 +++- .../cuanvil/dependency/util/PaperSpigtUtil.kt | 102 ++++++++++++ nms/v1_21R6/build.gradle.kts | 2 +- .../cuanvil/update/PluginSetDefault.java | 1 - .../cuanvil/command/EditConfigExecutor.kt | 4 +- .../cuanvil/dependency/DependencyManager.kt | 25 +-- .../cuanvil/listener/AnvilResultListener.kt | 22 ++- .../alexcrea/cuanvil/util/AnvilColorUtil.kt | 149 +++++++++++++----- .../cuanvil/util/AnvilLoreEditUtil.kt | 143 +++++++++++------ .../alexcrea/cuanvil/util/MiniMessageUtil.kt | 23 ++- .../cuanvil/util/config/LoreEditConfigUtil.kt | 2 - .../cuanvil/util/config/LoreEditType.kt | 22 +-- src/main/resources/config.yml | 36 ++++- .../alexcrea/cuanvil/anvil/LoreEditTests.java | 5 +- 17 files changed, 502 insertions(+), 167 deletions(-) create mode 100644 nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/util/PaperSpigtUtil.kt diff --git a/defaultconfigs/1.18/config.yml b/defaultconfigs/1.18/config.yml index df156b7..692ee2d 100644 --- a/defaultconfigs/1.18/config.yml +++ b/defaultconfigs/1.18/config.yml @@ -304,6 +304,8 @@ lore_edit: # Color code are prefixed by "&" and hexadecimal color by "#" # Color code will not be applied if it colors nothing. "&&" can be used to write "&" # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin + # but any global tag will be allowed later when v2 release allow_color_code: true allow_hexadecimal_color: true allow_minimessage: true @@ -321,10 +323,21 @@ lore_edit: shared_additive: false # If removing the lore consume the book & quil do_consume: false - # If the color should get back to color code or hex format - remove_color_on_remove: true # Cost of replacing colors remove_color_cost: 0 + # Allowed some color and tags to be reverted to plain text + # Custom anvil will prioritise format that result is a smaller resulting text + # Note that not allowing certain format will lead to some lost of color or tags. + # If configuration are exact as append appending this book should result in the exact same color + # + # Color code will be prefixed by "&" and hexadecimal color by "#". + # If color code is allowed, "&" in the text will get converted to "&&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin + # but any global tag will be allowed later when v2 release + allow_color_code: true + allow_hexadecimal_color: true + allow_minimessage: true paper: # Permission is ca.lore_edit.paper @@ -348,6 +361,8 @@ lore_edit: # Color code are prefixed by "&" and hexadecimal color by "#" # Color code will not be applied if it colors nothing. "&&" can be used to write "&" # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin + # but any global tag will be allowed later when v2 release allow_color_code: true allow_hexadecimal_color: true allow_minimessage: true @@ -364,10 +379,21 @@ lore_edit: shared_additive: false # If removing the lore line consume the paper do_consume: false - # If the color should get back to color code or hex format - remove_color_on_remove: true # Cost of replacing colors remove_color_cost: 0 + # Allowed some color and tags to be reverted to plain text + # Custom anvil will prioritise format that result is a smaller resulting text + # Note that not allowing certain format will lead to some lost of color or tags. + # If configuration are exact as append appending this paper should result in the exact same color + # + # Color code will be prefixed by "&" and hexadecimal color by "#". + # If color code is allowed, "&" in the text will get converted to "&&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin + # but any global tag will be allowed later when v2 release + allow_color_code: true + allow_hexadecimal_color: true + allow_minimessage: true # Whether to show debug logging debug_log: false diff --git a/defaultconfigs/1.21.9/config.yml b/defaultconfigs/1.21.9/config.yml index 401884d..028c401 100644 --- a/defaultconfigs/1.21.9/config.yml +++ b/defaultconfigs/1.21.9/config.yml @@ -316,6 +316,8 @@ lore_edit: # Color code are prefixed by "&" and hexadecimal color by "#" # Color code will not be applied if it colors nothing. "&&" can be used to write "&" # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin + # but any global tag will be allowed later when v2 release allow_color_code: true allow_hexadecimal_color: true allow_minimessage: true @@ -333,10 +335,21 @@ lore_edit: shared_additive: false # If removing the lore consume the book & quil do_consume: false - # If the color should get back to color code or hex format - remove_color_on_remove: true # Cost of replacing colors remove_color_cost: 0 + # Allowed some color and tags to be reverted to plain text + # Custom anvil will prioritise format that result is a smaller resulting text + # Note that not allowing certain format will lead to some lost of color or tags. + # If configuration are exact as append appending this book should result in the exact same color + # + # Color code will be prefixed by "&" and hexadecimal color by "#". + # If color code is allowed, "&" in the text will get converted to "&&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin + # but any global tag will be allowed later when v2 release + allow_color_code: true + allow_hexadecimal_color: true + allow_minimessage: true paper: # Permission is ca.lore_edit.paper @@ -360,6 +373,8 @@ lore_edit: # Color code are prefixed by "&" and hexadecimal color by "#" # Color code will not be applied if it colors nothing. "&&" can be used to write "&" # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin + # but any global tag will be allowed later when v2 release allow_color_code: true allow_hexadecimal_color: true allow_minimessage: true @@ -376,10 +391,21 @@ lore_edit: shared_additive: false # If removing the lore line consume the paper do_consume: false - # If the color should get back to color code or hex format - remove_color_on_remove: true # Cost of replacing colors remove_color_cost: 0 + # Allowed some color and tags to be reverted to plain text + # Custom anvil will prioritise format that result is a smaller resulting text + # Note that not allowing certain format will lead to some lost of color or tags. + # If configuration are exact as append appending this paper should result in the exact same color + # + # Color code will be prefixed by "&" and hexadecimal color by "#". + # If color code is allowed, "&" in the text will get converted to "&&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin + # but any global tag will be allowed later when v2 release + allow_color_code: true + allow_hexadecimal_color: true + allow_minimessage: true # Whether to show debug logging debug_log: false diff --git a/defaultconfigs/1.21/config.yml b/defaultconfigs/1.21/config.yml index 0e013ea..fe655c2 100644 --- a/defaultconfigs/1.21/config.yml +++ b/defaultconfigs/1.21/config.yml @@ -304,6 +304,8 @@ lore_edit: # Color code are prefixed by "&" and hexadecimal color by "#" # Color code will not be applied if it colors nothing. "&&" can be used to write "&" # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin + # but any global tag will be allowed later when v2 release allow_color_code: true allow_hexadecimal_color: true allow_minimessage: true @@ -321,10 +323,21 @@ lore_edit: shared_additive: false # If removing the lore consume the book & quil do_consume: false - # If the color should get back to color code or hex format - remove_color_on_remove: true # Cost of replacing colors remove_color_cost: 0 + # Allowed some color and tags to be reverted to plain text + # Custom anvil will prioritise format that result is a smaller resulting text + # Note that not allowing certain format will lead to some lost of color or tags. + # If configuration are exact as append appending this book should result in the exact same color + # + # Color code will be prefixed by "&" and hexadecimal color by "#". + # If color code is allowed, "&" in the text will get converted to "&&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin + # but any global tag will be allowed later when v2 release + allow_color_code: true + allow_hexadecimal_color: true + allow_minimessage: true paper: # Permission is ca.lore_edit.paper @@ -348,6 +361,8 @@ lore_edit: # Color code are prefixed by "&" and hexadecimal color by "#" # Color code will not be applied if it colors nothing. "&&" can be used to write "&" # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin + # but any global tag will be allowed later when v2 release allow_color_code: true allow_hexadecimal_color: true allow_minimessage: true @@ -364,10 +379,21 @@ lore_edit: shared_additive: false # If removing the lore line consume the paper do_consume: false - # If the color should get back to color code or hex format - remove_color_on_remove: true # Cost of replacing colors remove_color_cost: 0 + # Allowed some color and tags to be reverted to plain text + # Custom anvil will prioritise format that result is a smaller resulting text + # Note that not allowing certain format will lead to some lost of color or tags. + # If configuration are exact as append appending this paper should result in the exact same color + # + # Color code will be prefixed by "&" and hexadecimal color by "#". + # If color code is allowed, "&" in the text will get converted to "&&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin + # but any global tag will be allowed later when v2 release + allow_color_code: true + allow_hexadecimal_color: true + allow_minimessage: true # Whether to show debug logging debug_log: false diff --git a/nms/nms-common/build.gradle.kts b/nms/nms-common/build.gradle.kts index 964b321..6088e77 100644 --- a/nms/nms-common/build.gradle.kts +++ b/nms/nms-common/build.gradle.kts @@ -1,10 +1,35 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + group = rootProject.group version = rootProject.version +plugins { + id("io.papermc.paperweight.userdev") +} + dependencies { - // Spigot api - compileOnly("org.spigotmc:spigot-api:1.18-R0.1-SNAPSHOT") + // Used for nms + paperweight.paperDevBundle("1.21.10-R0.1-SNAPSHOT") // Protocolib compileOnly("net.dmulloy2:ProtocolLib:5.4.0") -} \ No newline at end of file +} + +repositories { + maven("https://repo.papermc.io/repository/maven-public/") +} + +// Set target version +tasks.withType().configureEach { + sourceCompatibility = "21" + targetCompatibility = "21" + + options.encoding = "UTF-8" +} + +kotlin { + compilerOptions { + apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) + jvmTarget.set(JvmTarget.JVM_21) + } +} diff --git a/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/util/PaperSpigtUtil.kt b/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/util/PaperSpigtUtil.kt new file mode 100644 index 0000000..3f709c7 --- /dev/null +++ b/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/util/PaperSpigtUtil.kt @@ -0,0 +1,102 @@ +package xyz.alexcrea.cuanvil.dependency.util + +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.meta.ItemMeta + +// Mostly made for paper, spigot and folia support +@Suppress("DEPRECATION") +object PlatformUtil { + + private fun hasClass(className: String): Boolean { + try { + Class.forName(className) + return true + } catch (_: ClassNotFoundException) { + return false + } + } + + private fun hasMethod(clazz: Class<*>, name: String, vararg parameterTypes: Class<*>): Boolean { + try { + clazz.getDeclaredMethod(name, *parameterTypes) + return true + } catch (_: NoSuchMethodException) { + return false + } + } + + val isPaper = hasClass("com.destroystokyo.paper.PaperConfig") || + hasClass("io.papermc.paper.configuration.Configuration") + + val isFolia = hasClass("io.papermc.paper.threadedregions.RegionizedServer") + + private val legacy_mm = LegacyComponentSerializer.legacySection() + + // Lore + fun ItemMeta.componentLore(): MutableList { + val lore: List? + if(isPaper){ + lore = this.lore() + } else { + val legacyLores = this.lore ?: return ArrayList() + + lore = ArrayList(legacyLores.size) + for (legacyLore in legacyLores) { + lore.add(legacy_mm.deserialize(legacyLore)) + } + } + + return lore ?: ArrayList() + } + + fun ItemMeta.setComponentLore(lore: List) { + if(isPaper){ + this.lore(lore) + } else { + val legacyLore = ArrayList(lore.size) + for (component in lore) { + legacyLore.add(if(component == null) null + else legacy_mm.serialize(component)) + } + + this.lore = legacyLore + } + } + + // Display name + private val useCustomName = hasMethod(ItemStack::class.java, "customName") + + fun ItemMeta.componentDisplayName(): Component? { + if(useCustomName){ + if(!this.hasCustomName()) return null + return this.customName() + }else if(isPaper){ + if(!this.hasDisplayName()) return null + return this.displayName() + } else { + if(!this.hasDisplayName()) return null + + val legacy = this.displayName + return legacy_mm.deserialize(legacy) + } + } + + fun ItemMeta.setComponentDisplayName(component: Component?) { + if(useCustomName){ + this.customName(component) + }else if(isPaper){ + this.displayName(component) + } else { + if(component == null){ + this.setDisplayName(null) + return + } + + val legacy = legacy_mm.serialize(component) + this.setDisplayName(legacy) + } + } + +} diff --git a/nms/v1_21R6/build.gradle.kts b/nms/v1_21R6/build.gradle.kts index f124fdd..3665d74 100644 --- a/nms/v1_21R6/build.gradle.kts +++ b/nms/v1_21R6/build.gradle.kts @@ -11,7 +11,7 @@ dependencies { implementation(project(":nms:nms-common")) // Used for nms - paperweight.paperDevBundle("1.21.9-R0.1-SNAPSHOT") + paperweight.paperDevBundle("1.21.10-R0.1-SNAPSHOT") } repositories { diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/PluginSetDefault.java b/src/main/java/xyz/alexcrea/cuanvil/update/PluginSetDefault.java index 363bd6a..f41842d 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/update/PluginSetDefault.java +++ b/src/main/java/xyz/alexcrea/cuanvil/update/PluginSetDefault.java @@ -48,7 +48,6 @@ public class PluginSetDefault { nbSet += trySetDefault(config, path + ALLOW_HEX_COLOR, DEFAULT_ALLOW_HEX_COLOR); nbSet += trySetDefault(config, path + USE_COLOR_COST, DEFAULT_USE_COLOR_COST); } else { - nbSet += trySetDefault(config, path + REMOVE_COLOR_ON_LORE_REMOVE, DEFAULT_REMOVE_COLOR_ON_LORE_REMOVE); nbSet += trySetDefault(config, path + REMOVE_COLOR_COST, DEFAULT_REMOVE_COLOR_COST); } } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/EditConfigExecutor.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/EditConfigExecutor.kt index 85761d9..f90f765 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/EditConfigExecutor.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/EditConfigExecutor.kt @@ -5,7 +5,7 @@ import org.bukkit.command.Command import org.bukkit.command.CommandExecutor import org.bukkit.command.CommandSender import org.bukkit.entity.HumanEntity -import xyz.alexcrea.cuanvil.dependency.DependencyManager +import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil import xyz.alexcrea.cuanvil.gui.config.MainConfigGui import xyz.alexcrea.cuanvil.gui.util.GuiGlobalActions @@ -16,7 +16,7 @@ class EditConfigExecutor : CommandExecutor { sender.sendMessage(GuiGlobalActions.NO_EDIT_PERM) return false } - if(DependencyManager.isFolia){ + if(PlatformUtil.isFolia){ sender.sendMessage("§cIt look like you are using Folia. Sadly Custom Anvil do not support Config gui for Folia.") sender.sendMessage("§eIt is may come in a future version.") sender.sendMessage("") diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt index 4c8e9f6..66a9aa5 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt @@ -2,10 +2,10 @@ package xyz.alexcrea.cuanvil.dependency import com.willfp.eco.core.gui.player import io.delilaheve.CustomAnvil +import net.kyori.adventure.text.Component import org.bukkit.Bukkit import org.bukkit.ChatColor import org.bukkit.entity.HumanEntity -import org.bukkit.entity.Player import org.bukkit.event.inventory.InventoryClickEvent import org.bukkit.event.inventory.PrepareAnvilEvent import org.bukkit.inventory.AnvilInventory @@ -24,13 +24,14 @@ import xyz.alexcrea.cuanvil.dependency.plugins.* import xyz.alexcrea.cuanvil.dependency.scheduler.BukkitScheduler import xyz.alexcrea.cuanvil.dependency.scheduler.FoliaScheduler import xyz.alexcrea.cuanvil.dependency.scheduler.TaskScheduler +import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil +import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.componentLore import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener.Companion.ANVIL_OUTPUT_SLOT import xyz.alexcrea.cuanvil.util.AnvilUseType import java.util.logging.Level object DependencyManager { - var isFolia: Boolean = false lateinit var scheduler: TaskScheduler lateinit var packetManager: PacketManager var externGuiTester: ExternGuiTester? = null @@ -50,8 +51,7 @@ object DependencyManager { val pluginManager = Bukkit.getPluginManager() // Bukkit or Paper scheduler ? - isFolia = testIsFolia() - scheduler = if (isFolia) { + scheduler = if (PlatformUtil.isFolia) { CustomAnvil.instance.logger.info("Folia detected... Custom Anvil Folia support is experimental. issues are more likely to happens.") FoliaScheduler() @@ -305,15 +305,15 @@ object DependencyManager { return bypass } - fun stripLore(item: ItemStack): ArrayList { - val lore = ArrayList() + fun stripLore(item: ItemStack): MutableList { val dummy = item.clone() enchantmentSquaredCompatibility?.stripLore(dummy) - val itemLore = dummy.itemMeta!!.lore - if (itemLore != null) lore.addAll(itemLore) + val itemLore = dummy.itemMeta?.componentLore() ?: return ArrayList() + val lore = ArrayList() + lore.addAll(itemLore) return lore } @@ -321,13 +321,4 @@ object DependencyManager { enchantmentSquaredCompatibility?.updateLore(item) } - private fun testIsFolia(): Boolean { - try { - Class.forName("io.papermc.paper.threadedregions.RegionizedServer") - return true - } catch (e: ClassNotFoundException) { - return false - } - } - } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt index 64e43d1..db2dfb0 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt @@ -17,6 +17,7 @@ import org.bukkit.inventory.InventoryView import org.bukkit.inventory.ItemStack import org.bukkit.inventory.meta.BookMeta import xyz.alexcrea.cuanvil.dependency.DependencyManager +import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.setComponentDisplayName 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 @@ -30,6 +31,7 @@ import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil import xyz.alexcrea.cuanvil.util.config.LoreEditType import java.util.* import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.atomic.AtomicReference import kotlin.math.min class AnvilResultListener : Listener { @@ -397,8 +399,6 @@ class AnvilResultListener : Listener { if (output != AnvilLoreEditUtil.handleLoreRemoveByBook(player, leftItem, xpCost)) return false // fill book meta - val meta = leftItem.itemMeta - if (meta == null || !meta.hasLore()) return false val lore = DependencyManager.stripLore(leftItem) if (lore.isEmpty()) return false @@ -443,10 +443,10 @@ class AnvilResultListener : Listener { if (Material.PAPER != rightItem.type) return false val paperMeta = rightItem.itemMeta ?: return false - val editType = AnvilLoreEditUtil.paperLoreEditIsAppend(leftItem, rightItem) ?: return false + val editTypeIsAppend = AnvilLoreEditUtil.paperLoreEditIsAppend(leftItem, rightItem) ?: return false val xpCost = AtomicInteger() - if (editType) { + if (editTypeIsAppend) { if (output != AnvilLoreEditUtil.handleLoreAppendByPaper(player, leftItem, rightItem, xpCost)) return false val paperCopy: ItemStack? @@ -456,7 +456,7 @@ class AnvilResultListener : Listener { // Remove custom name to paper paperCopy = rightItem.clone() paperCopy.amount = 1 - paperMeta.setDisplayName(null) + paperMeta.setComponentDisplayName(null) paperCopy.itemMeta = paperMeta } @@ -489,20 +489,18 @@ class AnvilResultListener : Listener { rightClone = null } else { val removeEnd = LoreEditConfigUtil.paperLoreOrderIsEnd - var line = if (removeEnd) lore[lore.size - 1] + val line = if (removeEnd) lore[lore.size - 1] else lore[0] - // Overkill but uncolor the line - val tempList = ArrayList(1) - tempList.add(line) - AnvilLoreEditUtil.uncolorLines(player, tempList, LoreEditType.REMOVE_PAPER) - line = tempList[0] + // uncolor the line + val ref = AtomicReference(line) + AnvilLoreEditUtil.uncolorLine(player, ref, LoreEditType.REMOVE_PAPER) rightClone = rightItem.clone() rightClone.amount = 1 val resultMeta = rightClone.itemMeta ?: return false - resultMeta.setDisplayName(line) + resultMeta.setComponentDisplayName(ref.get()) rightClone.itemMeta = resultMeta } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilColorUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilColorUtil.kt index 2629d1f..0abcaaf 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilColorUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilColorUtil.kt @@ -9,20 +9,33 @@ object AnvilColorUtil { private val HEX_PATTERN: Pattern = Pattern.compile("#[A-Fa-f0-9]{6}") // pattern to find hexadecimal string private val TRANSFORMED_HEX_PATTERN = Pattern.compile("§x(§[0-9a-fA-F]){6}") // pattern to find minecraft hex string - /** - * Color a stringbuilder object depending on allowed color type and player permissions on color use type - * @return if the stringbuilder was changed and color applied or if minimessage formating was applied - */ - fun handleColor( - textToColorText: String, + class ColorPermissions( + val canUseColorCode: Boolean, + val canUseHexColor: Boolean, + val canUseMinimessage: Boolean + ) { + fun allowed(): Boolean { + return canUseColorCode || canUseHexColor || canUseMinimessage + } + + fun onlyMinimessage(): Boolean { + return canUseMinimessage && !canUseColorCode && !canUseHexColor + } + } + + fun calculatePermissions( player: Permissible, usePermission: Boolean, allowColorCode: Boolean, allowHexadecimalColor: Boolean, allowMinimessage: Boolean, - useType: ColorUseType - ): Component? { - if (!allowColorCode && !allowHexadecimalColor && !allowMinimessage) return null + useType: ColorUseType): ColorPermissions { + if (!allowColorCode && !allowHexadecimalColor && !allowMinimessage) + return ColorPermissions( + canUseColorCode = false, + canUseHexColor = false, + canUseMinimessage = false + ) val canUseColorCode = allowColorCode && (!usePermission || useType.colorCodePerm == null || player.hasPermission( @@ -37,19 +50,49 @@ object AnvilColorUtil { useType.minimessagePerm )) - if (!canUseColorCode && !canUseHexColor && !canUseMinimessage) return null + return ColorPermissions(canUseColorCode, canUseHexColor, canUseMinimessage) + } + + /** + * Color a string depending on allowed color type, color use type and player permissions + * @return colored component or null if nothing has been colored + */ + fun handleColor( + textToColorText: String, + player: Permissible, + usePermission: Boolean, + allowColorCode: Boolean, + allowHexadecimalColor: Boolean, + allowMinimessage: Boolean, + useType: ColorUseType + ): Component? { + val permission = calculatePermissions(player, usePermission, + allowColorCode, allowHexadecimalColor, allowMinimessage, + useType) + return handleColor(textToColorText, permission) + } + + /** + * Color a string depending on permitted use + * @return colored component or null if nothing has been colored + */ + fun handleColor( + textToColorText: String, + permission: ColorPermissions + ): Component? { + if(!permission.allowed()) return null val textToColor = StringBuilder(textToColorText) var useColor = false // Handle color code - if (canUseColorCode) { // maybe should use LegacyComponentSerializer ? + if (permission.canUseColorCode) { // maybe should use LegacyComponentSerializer ? var nbReplacement = replaceAll(textToColor, "&", "§", 2) nbReplacement -= 2 * replaceAll(textToColor, "§§", "&", 2) if (nbReplacement > 0) useColor = true } - if (canUseHexColor) { + if (permission.canUseHexColor) { val nbReplacement = replaceHexToColor(textToColor, 7) if (nbReplacement > 0) useColor = true @@ -57,8 +100,8 @@ object AnvilColorUtil { val previousStr = textToColor.toString() var result: Component = MiniMessageUtil.legacy_mm.deserialize(previousStr) - if(canUseMinimessage) { - // we dance with formats here + if(permission.canUseMinimessage) { + // we dance with formats here TODO maybe extract, if possible, only the "text" part and use it for compare with previous as tag would be missing? val toMinimessage = MiniMessageUtil.mm.serialize(result) val hackySolution = toMinimessage.replace("\\<", "<") val fromMinimessage = MiniMessageUtil.mm.deserialize(hackySolution) @@ -75,46 +118,72 @@ object AnvilColorUtil { } /** - * Revert a stringbuilder to a state where applying handleColor with the same options would give the same result - * @return if the stringbuilder was changed and color unapplied + * Best effort to revert a component to the smallest allowed string + * that would result in it getting closest as possible to handleColor + * with current set of color type, color use type and player permissions + * @return the new component if had any change. null otherwise */ - fun revertColor( - colorToText: StringBuilder, + fun revertColorSmallest( + component: Component, player: Permissible, usePermission: Boolean, allowColorCode: Boolean, + allowMinimessage: Boolean, allowHexadecimalColor: Boolean, useType: ColorUseType - ): Boolean { - if (!allowColorCode && !allowHexadecimalColor) return false + ): String? { + val permission = calculatePermissions(player, usePermission, + allowColorCode, allowHexadecimalColor, allowMinimessage, + useType) + return revertColorSmallest(component, permission) + } - val canUseColorCode = - allowColorCode && (!usePermission || useType.colorCodePerm == null || player.hasPermission( - useType.colorCodePerm - )) - val canUseHexColor = - allowHexadecimalColor && (!usePermission || useType.hexColorPerm == null || player.hasPermission( - useType.hexColorPerm - )) + /** + * Best effort to revert a component to the smallest allowed string + * that would result in it getting closest as possible to handleColor + * with current set of permitted use + * @return a new component if had any change. null otherwise + */ + fun revertColorSmallest( + component: Component?, + permission: ColorPermissions + ): String? { + if(!permission.allowed() || component == null) return null - if ((!canUseColorCode) && (!canUseHexColor)) return false - var hasReversed = false + val transformed = MiniMessageUtil.mm.serialize(component) + val plainTransform = MiniMessageUtil.plain_text_mm.serialize(component) + if(transformed == plainTransform) return null + if(permission.onlyMinimessage()){ + return transformed + } + + // smol dance so we transform the component that may contain other tag into only decoration & color for legacy + val coloredMessage = MiniMessageUtil.color_only_mm.deserialize(transformed) + val legacyMessage = StringBuilder(MiniMessageUtil.legacy_mm.serialize(coloredMessage)) // Reverse hex pattern - if (canUseHexColor) { - val nbReplacement = replaceColorToHex(colorToText, 14) - - if (nbReplacement > 0) hasReversed = true + if (permission.canUseHexColor) { + replaceColorToHex(legacyMessage, 14) } - if (canUseColorCode) { - replaceAll(colorToText, "&", "&&", 1) - val nbReplacement = replaceAll(colorToText, "§", "&", 2) - - if (nbReplacement > 0) hasReversed = true + // Reverse color pattern + if (permission.canUseColorCode) { + replaceAll(legacyMessage, "&", "&&", 1) + replaceAll(legacyMessage, "§", "&", 2) } - return hasReversed + // In case we still has some § around by lack of permission we need to convert it back from legacy + // In other word it's time for dance #3 + val fromLegacy = MiniMessageUtil.legacy_mm.deserialize(legacyMessage.toString()) + val middleGround = MiniMessageUtil.color_only_mm.serialize(fromLegacy) + val hackySolution = middleGround.replace("\\<", "<") + + val result: String = + if(permission.canUseMinimessage) hackySolution + else MiniMessageUtil.mm.stripTags(hackySolution) + + return if(result == plainTransform) null + else result } /** diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt index 0f0d69d..4135df7 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt @@ -1,13 +1,18 @@ package xyz.alexcrea.cuanvil.util +import net.kyori.adventure.text.Component import org.bukkit.entity.HumanEntity import org.bukkit.inventory.ItemStack import org.bukkit.inventory.meta.BookMeta import org.bukkit.permissions.Permissible import xyz.alexcrea.cuanvil.dependency.DependencyManager +import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.componentLore +import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.setComponentLore import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil import xyz.alexcrea.cuanvil.util.config.LoreEditType +import java.util.* import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.atomic.AtomicReference object AnvilLoreEditUtil { @@ -32,24 +37,24 @@ object AnvilLoreEditUtil { val result = first.clone() val meta = result.itemMeta ?: return null - val lore = if (meta.hasLore()) { - ArrayList(meta.lore!!) - } else ArrayList() + val lore = meta.componentLore() val page = book.pages[0] val lines = ArrayList(page.split("\n")) - val colorCost = colorLines(player, lines, LoreEditType.APPEND_BOOK) + val outLines = ArrayList(lines.size) + val colorCost = colorLines(player, LoreEditType.APPEND_BOOK, + lines, outLines) - lore.addAll(lines) + lore.addAll(outLines) - meta.lore = lore + meta.setComponentLore(lore) result.itemMeta = meta if (result == first) return null // Handle xp xpCost.addAndGet(colorCost) // Cost of using color - xpCost.addAndGet(lines.size * LoreEditType.APPEND_BOOK.perLineCost) // per line cost + xpCost.addAndGet(outLines.size * LoreEditType.APPEND_BOOK.perLineCost) // per line cost xpCost.addAndGet(baseEditLoreXpCost(first, result, LoreEditType.APPEND_BOOK)) // Fixed cost and work penalty return result @@ -61,7 +66,7 @@ object AnvilLoreEditUtil { // remove lore val result = first.clone() val leftMeta = result.itemMeta ?: return null - val currentLore: ArrayList = DependencyManager.stripLore(result) + val currentLore = DependencyManager.stripLore(result) if (currentLore.isEmpty()) return null val uncolorCost = uncolorLines(player, currentLore, LoreEditType.REMOVE_BOOK) @@ -148,24 +153,23 @@ object AnvilLoreEditUtil { val result = first.clone() val meta = result.itemMeta ?: return null - val lore = if (meta.hasLore()) { - ArrayList(meta.lore!!) - } else ArrayList() + val lore = meta.componentLore() val appendEnd = LoreEditConfigUtil.paperLoreOrderIsEnd // A bit overdone to color 1 line but hey - val tempList = ArrayList(1) - tempList.add(second.itemMeta!!.displayName) - val colorCost = colorLines(player, tempList, LoreEditType.APPEND_PAPER) + val outList = ArrayList(1) + val colorCost = colorLines(player, LoreEditType.APPEND_PAPER, + Collections.singletonList(second.itemMeta!!.displayName), + outList) - val line = tempList[0] + val line = outList[0] if (appendEnd) lore.add(line) else lore.add(0, line) - meta.lore = lore + meta.setComponentLore(lore) result.itemMeta = meta if (result == first) return null @@ -185,7 +189,7 @@ object AnvilLoreEditUtil { val meta = result.itemMeta!! val removeEnd = LoreEditConfigUtil.paperLoreOrderIsEnd - val lore: ArrayList = DependencyManager.stripLore(result) + val lore = DependencyManager.stripLore(result) if (lore.isEmpty()) return null val line = if (removeEnd) lore.removeAt(lore.size - 1) @@ -197,18 +201,16 @@ object AnvilLoreEditUtil { // Update lore but make sure custom lore is put last DependencyManager.updateLore(result) - val finalLore = ArrayList() - finalLore.addAll(meta.lore ?: emptyList()) + val finalLore = ArrayList() + finalLore.addAll(meta.componentLore()) finalLore.addAll(lore) - meta.lore = finalLore + meta.setComponentLore(finalLore) result.itemMeta = meta if (result == first) return null // Get color cost to uncolor this line - val tempList = ArrayList(1) - tempList.add(line) - val uncolorCost = uncolorLines(player, tempList, LoreEditType.REMOVE_PAPER) + val uncolorCost = uncolorLine(player, line, LoreEditType.REMOVE_PAPER) // Handle other xp xpCost.addAndGet(uncolorCost) @@ -240,26 +242,37 @@ object AnvilLoreEditUtil { return xpCost } - private fun colorLines(player: Permissible, lines: ArrayList, editType: LoreEditType): Int { - val canUseHex = editType.allowHexColor - val canUseColorCode = editType.allowColorCode - val minimessage = editType.allowMinimessage + fun colorPermission(player: Permissible, editType: LoreEditType): AnvilColorUtil.ColorPermissions { + return AnvilColorUtil.calculatePermissions(player, + false, + editType.allowColorCode, + editType.allowHexColor, + editType.allowMinimessage, + AnvilColorUtil.ColorUseType.LORE_EDIT) + } + + private fun colorLine(line: String, permission: AnvilColorUtil.ColorPermissions): Component? { + return AnvilColorUtil.handleColor( + line, + permission + ) + } + + private fun colorLines(player: Permissible, editType: LoreEditType, + lines: List, outLines: MutableList): Int { + val permission = colorPermission(player, editType) val colorCost = editType.useColorCost // Handle color and minimessage of each lines var hasUsedColor = false - for ((index, line) in lines.withIndex()) { - val component = AnvilColorUtil.handleColor( - line, - player, - false, - canUseColorCode, canUseHex, minimessage, - AnvilColorUtil.ColorUseType.LORE_EDIT - ) + for (line in lines) { + val component = colorLine(line, permission) if (component != null) { hasUsedColor = true - lines[index] = MiniMessageUtil.legacy_mm.serialize(component) + outLines.add(component) + } else { + outLines.add(Component.text(line)) } } @@ -267,25 +280,31 @@ object AnvilLoreEditUtil { else 0 } - fun uncolorLines(player: Permissible, lines: ArrayList, editType: LoreEditType): Int { - if (!editType.shouldRemoveColorOnLoreRemoval) return 0 + fun uncolorLines(player: Permissible, lines: MutableList, editType: LoreEditType): Int { + val permission = colorPermission(player, editType) // Now handle color of each lines var hasUndidColor = false for ((index, line) in lines.withIndex()) { - val uncoloredLine = StringBuilder(line) + if(line == null){ + lines[index] = null + continue + } - val lineUndidColor = AnvilColorUtil.revertColor( - uncoloredLine, - player, - false, true, true, - AnvilColorUtil.ColorUseType.LORE_EDIT + val clearedLine = AnvilColorUtil.revertColorSmallest( + line, + permission ) - if (lineUndidColor) { + val result: String + if (clearedLine != null) { hasUndidColor = true - lines[index] = uncoloredLine.toString() + result = clearedLine + } else { + result = MiniMessageUtil.plain_text_mm.serialize(line) } + + lines[index] = MiniMessageUtil.plain_text_mm.deserialize(result) } return if (hasUndidColor) { @@ -295,4 +314,36 @@ object AnvilLoreEditUtil { } } + // do not output the uncolored line... + fun uncolorLine(player: Permissible, line: Component?, editType: LoreEditType): Int { + return uncolorLine(player, AtomicReference(line), editType) + } + + fun uncolorLine(player: Permissible, line: AtomicReference, editType: LoreEditType): Int { + val coloredComponent = line.get() ?: return 0 + val permission = colorPermission(player, editType) + + val clearedLine = AnvilColorUtil.revertColorSmallest( + coloredComponent, + permission + ) + + var hasUndidColor = false + val result: String + if(clearedLine != null){ + hasUndidColor = true + result = clearedLine + } else { + // Remove extra tags + result = MiniMessageUtil.plain_text_mm.serialize(coloredComponent) + } + line.set(MiniMessageUtil.plain_text_mm.deserialize(result)) + + return if (hasUndidColor) { + editType.removeColorCost + } else { + 0 + } + } + } \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/MiniMessageUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/MiniMessageUtil.kt index 10500e8..c33cb9c 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/MiniMessageUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/MiniMessageUtil.kt @@ -1,18 +1,33 @@ package xyz.alexcrea.cuanvil.util +import net.kyori.adventure.text.TextComponent import net.kyori.adventure.text.minimessage.MiniMessage import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver import net.kyori.adventure.text.minimessage.tag.standard.StandardTags import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer +import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil object MiniMessageUtil { - val mm = MiniMessage.builder() - .tags(TagResolver.resolver( - StandardTags.color(), - StandardTags.decorations())) + val color_only_mm = MiniMessage.builder() + .tags( + TagResolver.resolver( + StandardTags.color(), + StandardTags.decorations() + ) + ) .build() + val mm = if (PlatformUtil.isPaper) MiniMessage.miniMessage() + else color_only_mm + val legacy_mm = LegacyComponentSerializer.legacySection() + val plain_text_mm = PlainTextComponentSerializer.plainText() + + // Keeping track of this as most use of this can be replaced later on v2 with pure component alternative + fun fromLegacy(legacyText: String): TextComponent { + return legacy_mm.deserialize(legacyText) + } } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditConfigUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditConfigUtil.kt index 2a2fa3b..9d0eb6a 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditConfigUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditConfigUtil.kt @@ -20,7 +20,6 @@ object LoreEditConfigUtil { const val ALLOW_MINIMESSAGE = "allow_minimessage" const val USE_COLOR_COST = "use_cost" - const val REMOVE_COLOR_ON_LORE_REMOVE = "remove_color_on_remove" const val REMOVE_COLOR_COST = "remove_color_cost" // Lore order config path @@ -46,7 +45,6 @@ object LoreEditConfigUtil { const val DEFAULT_ALLOW_MINIMESSAGE = true const val DEFAULT_USE_COLOR_COST = 0 - const val DEFAULT_REMOVE_COLOR_ON_LORE_REMOVE = false const val DEFAULT_REMOVE_COLOR_COST = 0 // Lore order config default diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditType.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditType.kt index bb1632e..8bd926a 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditType.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditType.kt @@ -8,11 +8,9 @@ import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.DEFAULT_ALLOW_COLOR_C import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.DEFAULT_ALLOW_HEX_COLOR import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.DEFAULT_ALLOW_MINIMESSAGE import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.DEFAULT_REMOVE_COLOR_COST -import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.DEFAULT_REMOVE_COLOR_ON_LORE_REMOVE import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.DEFAULT_USE_COLOR_COST import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.REMOVE_COLOR_COST import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.REMOVE_COLOR_COST_RANGE -import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.REMOVE_COLOR_ON_LORE_REMOVE import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.USE_COLOR_COST import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.USE_COLOR_COST_RANGE import xyz.alexcrea.cuanvil.config.ConfigHolder.DEFAULT_CONFIG as CONFIG @@ -81,33 +79,30 @@ enum class LoreEditType( } /** - * Allow usage of color code on lore add + * Allow usage or removal of color code */ val allowColorCode: Boolean get() { - if (!isAppend) throw IllegalStateException("Can only call with an append edit type") return CONFIG .config .getBoolean("$rootPath.$ALLOW_COLOR_CODE", DEFAULT_ALLOW_COLOR_CODE) } /** - * Allow usage of hexadecimal color on lore add + * Allow usage or removal of hexadecimal color */ val allowHexColor: Boolean get() { - if (!isAppend) throw IllegalStateException("Can only call with an append edit type") return CONFIG .config .getBoolean("${rootPath}.$ALLOW_HEX_COLOR", DEFAULT_ALLOW_HEX_COLOR) } /** - * Allow usage of minimessage on lore add + * Allow usage or removal of minimessage on lore add */ val allowMinimessage: Boolean get() { - if (!isAppend) throw IllegalStateException("Can only call with an append edit type") return CONFIG .config .getBoolean("${rootPath}.$ALLOW_MINIMESSAGE", DEFAULT_ALLOW_MINIMESSAGE) @@ -127,17 +122,6 @@ enum class LoreEditType( } - /** - * Should the color code & hex color should get removed on lore remove - */ - val shouldRemoveColorOnLoreRemoval: Boolean - get() { - if (isAppend) throw IllegalStateException("Can only call with a remove edit type") - return CONFIG - .config - .getBoolean("${rootPath}.$REMOVE_COLOR_ON_LORE_REMOVE", DEFAULT_REMOVE_COLOR_ON_LORE_REMOVE) - } - /** * Cost when using either color code and hex color on lore remove */ diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 1c8c010..b645b55 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -60,6 +60,8 @@ sacrifice_illegal_enchant_cost: 1 # Color code are prefixed by "&" and hexadecimal color by "#". # Color code will not be applied if it colors nothing. "&&" can be used to write "&". # For minimessage search for minimessage formating https://docs.papermc.io/adventure/minimessage/format/ +# Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin +# but any global tag will be allowed later when v2 release allow_color_code: false allow_hexadecimal_color: false allow_minimessage: false @@ -304,6 +306,8 @@ lore_edit: # Color code are prefixed by "&" and hexadecimal color by "#" # Color code will not be applied if it colors nothing. "&&" can be used to write "&" # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin + # but any global tag will be allowed later when v2 release allow_color_code: true allow_hexadecimal_color: true allow_minimessage: true @@ -321,10 +325,21 @@ lore_edit: shared_additive: false # If removing the lore consume the book & quil do_consume: false - # If the color should get back to color code or hex format - remove_color_on_remove: true # Cost of replacing colors remove_color_cost: 0 + # Allowed some color and tags to be reverted to plain text + # Custom anvil will prioritise format that result is a smaller resulting text + # Note that not allowing certain format will lead to some lost of color or tags. + # If configuration are exact as append appending this book should result in the exact same color + # + # Color code will be prefixed by "&" and hexadecimal color by "#". + # If color code is allowed, "&" in the text will get converted to "&&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin + # but any global tag will be allowed later when v2 release + allow_color_code: true + allow_hexadecimal_color: true + allow_minimessage: true paper: # Permission is ca.lore_edit.paper @@ -348,6 +363,8 @@ lore_edit: # Color code are prefixed by "&" and hexadecimal color by "#" # Color code will not be applied if it colors nothing. "&&" can be used to write "&" # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin + # but any global tag will be allowed later when v2 release allow_color_code: true allow_hexadecimal_color: true allow_minimessage: true @@ -364,10 +381,21 @@ lore_edit: shared_additive: false # If removing the lore line consume the paper do_consume: false - # If the color should get back to color code or hex format - remove_color_on_remove: true # Cost of replacing colors remove_color_cost: 0 + # Allowed some color and tags to be reverted to plain text + # Custom anvil will prioritise format that result is a smaller resulting text + # Note that not allowing certain format will lead to some lost of color or tags. + # If configuration are exact as append appending this paper should result in the exact same color + # + # Color code will be prefixed by "&" and hexadecimal color by "#". + # If color code is allowed, "&" in the text will get converted to "&&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin + # but any global tag will be allowed later when v2 release + allow_color_code: true + allow_hexadecimal_color: true + allow_minimessage: true # Whether to show debug logging debug_log: false diff --git a/src/test/java/xyz/alexcrea/cuanvil/anvil/LoreEditTests.java b/src/test/java/xyz/alexcrea/cuanvil/anvil/LoreEditTests.java index ebfb84f..33b0f35 100644 --- a/src/test/java/xyz/alexcrea/cuanvil/anvil/LoreEditTests.java +++ b/src/test/java/xyz/alexcrea/cuanvil/anvil/LoreEditTests.java @@ -289,9 +289,9 @@ public class LoreEditTests extends SharedCustomAnvilTest { if (type.isAppend()) { ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.ALLOW_HEX_COLOR, true); ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.ALLOW_COLOR_CODE, true); + ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.ALLOW_MINIMESSAGE, true); ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.USE_COLOR_COST, 0); } else { - ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.REMOVE_COLOR_ON_LORE_REMOVE, false); ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.REMOVE_COLOR_COST, 0); } @@ -437,7 +437,6 @@ public class LoreEditTests extends SharedCustomAnvilTest { public void testColorCost(LoreEditType type) { ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.USE_COLOR_COST, COLOR_USE_COST); ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.REMOVE_COLOR_COST, COLOR_REMOVE_COST); - ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.REMOVE_COLOR_ON_LORE_REMOVE, true); TestDataContainer singleLData = singleLineTypeToTest.get(type); TestDataContainer multiLData = multiLineTypeToTest.get(type); @@ -481,8 +480,6 @@ public class LoreEditTests extends SharedCustomAnvilTest { @ParameterizedTest @MethodSource("onlyRemoveTypes") public void testColorRemoveEnabled(LoreEditType type) { - ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.REMOVE_COLOR_ON_LORE_REMOVE, true); - TestDataContainer singleLData = singleLineTypeToTest.get(type); TestDataContainer multiLData = multiLineTypeToTest.get(type); From 517fcf3430f2ce07018bfa789615ec0aa5a91338 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Thu, 23 Oct 2025 15:34:04 +0200 Subject: [PATCH 018/207] disallow use of hex code and minimessage on append --- defaultconfigs/1.18/config.yml | 12 ++++-- defaultconfigs/1.21.9/config.yml | 12 ++++-- defaultconfigs/1.21/config.yml | 12 ++++-- .../cuanvil/listener/AnvilResultListener.kt | 5 ++- .../cuanvil/listener/PrepareAnvilListener.kt | 2 +- .../alexcrea/cuanvil/util/AnvilColorUtil.kt | 43 +++++++------------ .../cuanvil/util/AnvilLoreEditUtil.kt | 11 ++--- src/main/resources/config.yml | 12 ++++-- 8 files changed, 58 insertions(+), 51 deletions(-) diff --git a/defaultconfigs/1.18/config.yml b/defaultconfigs/1.18/config.yml index 692ee2d..2caafe3 100644 --- a/defaultconfigs/1.18/config.yml +++ b/defaultconfigs/1.18/config.yml @@ -306,8 +306,10 @@ lore_edit: # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin # but any global tag will be allowed later when v2 release + # + # Note that currently minimessage would disable hex code when adding color allow_color_code: true - allow_hexadecimal_color: true + allow_hexadecimal_color: false allow_minimessage: true remove: @@ -336,7 +338,7 @@ lore_edit: # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin # but any global tag will be allowed later when v2 release allow_color_code: true - allow_hexadecimal_color: true + allow_hexadecimal_color: false allow_minimessage: true paper: @@ -363,8 +365,10 @@ lore_edit: # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin # but any global tag will be allowed later when v2 release + # + # Note that currently minimessage would disable hex code when adding color allow_color_code: true - allow_hexadecimal_color: true + allow_hexadecimal_color: false allow_minimessage: true color_use_cost: 0 @@ -392,7 +396,7 @@ lore_edit: # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin # but any global tag will be allowed later when v2 release allow_color_code: true - allow_hexadecimal_color: true + allow_hexadecimal_color: false allow_minimessage: true # Whether to show debug logging diff --git a/defaultconfigs/1.21.9/config.yml b/defaultconfigs/1.21.9/config.yml index 028c401..2602e56 100644 --- a/defaultconfigs/1.21.9/config.yml +++ b/defaultconfigs/1.21.9/config.yml @@ -318,8 +318,10 @@ lore_edit: # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin # but any global tag will be allowed later when v2 release + # + # Note that currently minimessage would disable hex code when adding color allow_color_code: true - allow_hexadecimal_color: true + allow_hexadecimal_color: false allow_minimessage: true remove: @@ -348,7 +350,7 @@ lore_edit: # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin # but any global tag will be allowed later when v2 release allow_color_code: true - allow_hexadecimal_color: true + allow_hexadecimal_color: false allow_minimessage: true paper: @@ -375,8 +377,10 @@ lore_edit: # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin # but any global tag will be allowed later when v2 release + # + # Note that currently minimessage would disable hex code when adding color allow_color_code: true - allow_hexadecimal_color: true + allow_hexadecimal_color: false allow_minimessage: true color_use_cost: 0 @@ -404,7 +408,7 @@ lore_edit: # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin # but any global tag will be allowed later when v2 release allow_color_code: true - allow_hexadecimal_color: true + allow_hexadecimal_color: false allow_minimessage: true # Whether to show debug logging diff --git a/defaultconfigs/1.21/config.yml b/defaultconfigs/1.21/config.yml index fe655c2..139148b 100644 --- a/defaultconfigs/1.21/config.yml +++ b/defaultconfigs/1.21/config.yml @@ -306,8 +306,10 @@ lore_edit: # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin # but any global tag will be allowed later when v2 release + # + # Note that currently minimessage would disable hex code when adding color allow_color_code: true - allow_hexadecimal_color: true + allow_hexadecimal_color: false allow_minimessage: true remove: @@ -336,7 +338,7 @@ lore_edit: # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin # but any global tag will be allowed later when v2 release allow_color_code: true - allow_hexadecimal_color: true + allow_hexadecimal_color: false allow_minimessage: true paper: @@ -363,8 +365,10 @@ lore_edit: # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin # but any global tag will be allowed later when v2 release + # + # Note that currently minimessage would disable hex code when adding color allow_color_code: true - allow_hexadecimal_color: true + allow_hexadecimal_color: false allow_minimessage: true color_use_cost: 0 @@ -392,7 +396,7 @@ lore_edit: # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin # but any global tag will be allowed later when v2 release allow_color_code: true - allow_hexadecimal_color: true + allow_hexadecimal_color: false allow_minimessage: true # Whether to show debug logging diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt index db2dfb0..bc3afa4 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt @@ -26,6 +26,7 @@ import xyz.alexcrea.cuanvil.util.AnvilLoreEditUtil import xyz.alexcrea.cuanvil.util.AnvilUseType import xyz.alexcrea.cuanvil.util.AnvilXpUtil import xyz.alexcrea.cuanvil.util.CustomRecipeUtil +import xyz.alexcrea.cuanvil.util.MiniMessageUtil import xyz.alexcrea.cuanvil.util.UnitRepairUtil.getRepair import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil import xyz.alexcrea.cuanvil.util.config.LoreEditType @@ -412,7 +413,9 @@ class AnvilResultListener : Listener { val bookPage = StringBuilder() lore.forEach { if (bookPage.isNotEmpty()) bookPage.append('\n') - bookPage.append(it) + if(it == null) return@forEach + + bookPage.append(MiniMessageUtil.plain_text_mm.serialize(it)) } val resultPage = bookPage.toString() diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt index 17d5d0d..f491862 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt @@ -195,7 +195,7 @@ class PrepareAnvilListener : Listener { renameText, player, ConfigOptions.permissionNeededForColor, ConfigOptions.allowColorCode, ConfigOptions.allowHexadecimalColor, ConfigOptions.allowMinimessage, - AnvilColorUtil.ColorUseType.RENAME + AnvilColorUtil.ColorUseType.RENAME, true ) if (component != null) { diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilColorUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilColorUtil.kt index 0abcaaf..320eef1 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilColorUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilColorUtil.kt @@ -29,7 +29,9 @@ object AnvilColorUtil { allowColorCode: Boolean, allowHexadecimalColor: Boolean, allowMinimessage: Boolean, - useType: ColorUseType): ColorPermissions { + useType: ColorUseType, + isAppend: Boolean = true + ): ColorPermissions { if (!allowColorCode && !allowHexadecimalColor && !allowMinimessage) return ColorPermissions( canUseColorCode = false, @@ -41,15 +43,20 @@ object AnvilColorUtil { allowColorCode && (!usePermission || useType.colorCodePerm == null || player.hasPermission( useType.colorCodePerm )) - val canUseHexColor = - allowHexadecimalColor && (!usePermission || useType.hexColorPerm == null || player.hasPermission( - useType.hexColorPerm - )) + val canUseMinimessage = allowMinimessage && (!usePermission || useType.minimessagePerm == null || player.hasPermission( useType.minimessagePerm )) + // Do not allow minimessage and hex color at the same time when coming from string to component (usually/assumed append) + val minimessageConflict = canUseMinimessage && isAppend + + val canUseHexColor = !minimessageConflict && + allowHexadecimalColor && (!usePermission || useType.hexColorPerm == null || player.hasPermission( + useType.hexColorPerm + )) + return ColorPermissions(canUseColorCode, canUseHexColor, canUseMinimessage) } @@ -64,11 +71,12 @@ object AnvilColorUtil { allowColorCode: Boolean, allowHexadecimalColor: Boolean, allowMinimessage: Boolean, - useType: ColorUseType + useType: ColorUseType, + isAppend: Boolean ): Component? { val permission = calculatePermissions(player, usePermission, allowColorCode, allowHexadecimalColor, allowMinimessage, - useType) + useType, isAppend) return handleColor(textToColorText, permission) } @@ -117,27 +125,6 @@ object AnvilColorUtil { else null } - /** - * Best effort to revert a component to the smallest allowed string - * that would result in it getting closest as possible to handleColor - * with current set of color type, color use type and player permissions - * @return the new component if had any change. null otherwise - */ - fun revertColorSmallest( - component: Component, - player: Permissible, - usePermission: Boolean, - allowColorCode: Boolean, - allowMinimessage: Boolean, - allowHexadecimalColor: Boolean, - useType: ColorUseType - ): String? { - val permission = calculatePermissions(player, usePermission, - allowColorCode, allowHexadecimalColor, allowMinimessage, - useType) - return revertColorSmallest(component, permission) - } - /** * Best effort to revert a component to the smallest allowed string * that would result in it getting closest as possible to handleColor diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt index 4135df7..943be5f 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt @@ -117,10 +117,10 @@ object AnvilLoreEditUtil { } fun tryLoreEditByBook(player: HumanEntity, first: ItemStack, second: ItemStack, xpCost: AtomicInteger): ItemStack? { - val bookType = bookLoreEditIsAppend(first, second) ?: return null + val isAppend = bookLoreEditIsAppend(first, second) ?: return null val meta = second.itemMeta as BookMeta - return if (bookType) handleLoreAppendByBook(player, first, meta, xpCost) + return if (isAppend) handleLoreAppendByBook(player, first, meta, xpCost) else handleLoreRemoveByBook(player, first, xpCost) } @@ -225,9 +225,9 @@ object AnvilLoreEditUtil { second: ItemStack, xpCost: AtomicInteger ): ItemStack? { - val bookType = paperLoreEditIsAppend(first, second) ?: return null + val isAppend = paperLoreEditIsAppend(first, second) ?: return null - return if (bookType) handleLoreAppendByPaper(player, first, second, xpCost) + return if (isAppend) handleLoreAppendByPaper(player, first, second, xpCost) else handleLoreRemoveByPaper(player, first, xpCost) } @@ -248,7 +248,8 @@ object AnvilLoreEditUtil { editType.allowColorCode, editType.allowHexColor, editType.allowMinimessage, - AnvilColorUtil.ColorUseType.LORE_EDIT) + AnvilColorUtil.ColorUseType.LORE_EDIT, + editType.isAppend) } private fun colorLine(line: String, permission: AnvilColorUtil.ColorPermissions): Component? { diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index b645b55..79c4278 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -308,8 +308,10 @@ lore_edit: # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin # but any global tag will be allowed later when v2 release + # + # Note that currently minimessage would disable hex code when adding color allow_color_code: true - allow_hexadecimal_color: true + allow_hexadecimal_color: false allow_minimessage: true remove: @@ -338,7 +340,7 @@ lore_edit: # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin # but any global tag will be allowed later when v2 release allow_color_code: true - allow_hexadecimal_color: true + allow_hexadecimal_color: false allow_minimessage: true paper: @@ -365,8 +367,10 @@ lore_edit: # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin # but any global tag will be allowed later when v2 release + # + # Note that currently minimessage would disable hex code when adding color allow_color_code: true - allow_hexadecimal_color: true + allow_hexadecimal_color: false allow_minimessage: true color_use_cost: 0 @@ -394,7 +398,7 @@ lore_edit: # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin # but any global tag will be allowed later when v2 release allow_color_code: true - allow_hexadecimal_color: true + allow_hexadecimal_color: false allow_minimessage: true # Whether to show debug logging From b9aae9e79901a3eb5e1a388021c6203e52921a82 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Thu, 23 Oct 2025 15:48:05 +0200 Subject: [PATCH 019/207] update doc --- defaultconfigs/1.18/config.yml | 8 -------- defaultconfigs/1.21.9/config.yml | 8 -------- defaultconfigs/1.21/config.yml | 8 -------- src/main/resources/config.yml | 8 -------- 4 files changed, 32 deletions(-) diff --git a/defaultconfigs/1.18/config.yml b/defaultconfigs/1.18/config.yml index 2caafe3..877cee9 100644 --- a/defaultconfigs/1.18/config.yml +++ b/defaultconfigs/1.18/config.yml @@ -304,8 +304,6 @@ lore_edit: # Color code are prefixed by "&" and hexadecimal color by "#" # Color code will not be applied if it colors nothing. "&&" can be used to write "&" # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ - # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin - # but any global tag will be allowed later when v2 release # # Note that currently minimessage would disable hex code when adding color allow_color_code: true @@ -335,8 +333,6 @@ lore_edit: # Color code will be prefixed by "&" and hexadecimal color by "#". # If color code is allowed, "&" in the text will get converted to "&&" # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ - # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin - # but any global tag will be allowed later when v2 release allow_color_code: true allow_hexadecimal_color: false allow_minimessage: true @@ -363,8 +359,6 @@ lore_edit: # Color code are prefixed by "&" and hexadecimal color by "#" # Color code will not be applied if it colors nothing. "&&" can be used to write "&" # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ - # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin - # but any global tag will be allowed later when v2 release # # Note that currently minimessage would disable hex code when adding color allow_color_code: true @@ -393,8 +387,6 @@ lore_edit: # Color code will be prefixed by "&" and hexadecimal color by "#". # If color code is allowed, "&" in the text will get converted to "&&" # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ - # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin - # but any global tag will be allowed later when v2 release allow_color_code: true allow_hexadecimal_color: false allow_minimessage: true diff --git a/defaultconfigs/1.21.9/config.yml b/defaultconfigs/1.21.9/config.yml index 2602e56..cf9460e 100644 --- a/defaultconfigs/1.21.9/config.yml +++ b/defaultconfigs/1.21.9/config.yml @@ -316,8 +316,6 @@ lore_edit: # Color code are prefixed by "&" and hexadecimal color by "#" # Color code will not be applied if it colors nothing. "&&" can be used to write "&" # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ - # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin - # but any global tag will be allowed later when v2 release # # Note that currently minimessage would disable hex code when adding color allow_color_code: true @@ -347,8 +345,6 @@ lore_edit: # Color code will be prefixed by "&" and hexadecimal color by "#". # If color code is allowed, "&" in the text will get converted to "&&" # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ - # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin - # but any global tag will be allowed later when v2 release allow_color_code: true allow_hexadecimal_color: false allow_minimessage: true @@ -375,8 +371,6 @@ lore_edit: # Color code are prefixed by "&" and hexadecimal color by "#" # Color code will not be applied if it colors nothing. "&&" can be used to write "&" # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ - # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin - # but any global tag will be allowed later when v2 release # # Note that currently minimessage would disable hex code when adding color allow_color_code: true @@ -405,8 +399,6 @@ lore_edit: # Color code will be prefixed by "&" and hexadecimal color by "#". # If color code is allowed, "&" in the text will get converted to "&&" # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ - # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin - # but any global tag will be allowed later when v2 release allow_color_code: true allow_hexadecimal_color: false allow_minimessage: true diff --git a/defaultconfigs/1.21/config.yml b/defaultconfigs/1.21/config.yml index 139148b..d7807e9 100644 --- a/defaultconfigs/1.21/config.yml +++ b/defaultconfigs/1.21/config.yml @@ -304,8 +304,6 @@ lore_edit: # Color code are prefixed by "&" and hexadecimal color by "#" # Color code will not be applied if it colors nothing. "&&" can be used to write "&" # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ - # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin - # but any global tag will be allowed later when v2 release # # Note that currently minimessage would disable hex code when adding color allow_color_code: true @@ -335,8 +333,6 @@ lore_edit: # Color code will be prefixed by "&" and hexadecimal color by "#". # If color code is allowed, "&" in the text will get converted to "&&" # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ - # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin - # but any global tag will be allowed later when v2 release allow_color_code: true allow_hexadecimal_color: false allow_minimessage: true @@ -363,8 +359,6 @@ lore_edit: # Color code are prefixed by "&" and hexadecimal color by "#" # Color code will not be applied if it colors nothing. "&&" can be used to write "&" # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ - # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin - # but any global tag will be allowed later when v2 release # # Note that currently minimessage would disable hex code when adding color allow_color_code: true @@ -393,8 +387,6 @@ lore_edit: # Color code will be prefixed by "&" and hexadecimal color by "#". # If color code is allowed, "&" in the text will get converted to "&&" # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ - # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin - # but any global tag will be allowed later when v2 release allow_color_code: true allow_hexadecimal_color: false allow_minimessage: true diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 79c4278..5476ec7 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -306,8 +306,6 @@ lore_edit: # Color code are prefixed by "&" and hexadecimal color by "#" # Color code will not be applied if it colors nothing. "&&" can be used to write "&" # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ - # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin - # but any global tag will be allowed later when v2 release # # Note that currently minimessage would disable hex code when adding color allow_color_code: true @@ -337,8 +335,6 @@ lore_edit: # Color code will be prefixed by "&" and hexadecimal color by "#". # If color code is allowed, "&" in the text will get converted to "&&" # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ - # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin - # but any global tag will be allowed later when v2 release allow_color_code: true allow_hexadecimal_color: false allow_minimessage: true @@ -365,8 +361,6 @@ lore_edit: # Color code are prefixed by "&" and hexadecimal color by "#" # Color code will not be applied if it colors nothing. "&&" can be used to write "&" # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ - # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin - # but any global tag will be allowed later when v2 release # # Note that currently minimessage would disable hex code when adding color allow_color_code: true @@ -395,8 +389,6 @@ lore_edit: # Color code will be prefixed by "&" and hexadecimal color by "#". # If color code is allowed, "&" in the text will get converted to "&&" # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ - # Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin - # but any global tag will be allowed later when v2 release allow_color_code: true allow_hexadecimal_color: false allow_minimessage: true From e9a2890cfb0bb25a0c0dd714497c6e60fad59372 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 27 Oct 2025 16:48:09 +0100 Subject: [PATCH 020/207] tag detection --- .../cuanvil/listener/PrepareAnvilListener.kt | 2 +- .../alexcrea/cuanvil/util/AnvilColorUtil.kt | 60 ++++++++++++++----- .../cuanvil/util/AnvilLoreEditUtil.kt | 3 +- 3 files changed, 46 insertions(+), 19 deletions(-) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt index f491862..17d5d0d 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt @@ -195,7 +195,7 @@ class PrepareAnvilListener : Listener { renameText, player, ConfigOptions.permissionNeededForColor, ConfigOptions.allowColorCode, ConfigOptions.allowHexadecimalColor, ConfigOptions.allowMinimessage, - AnvilColorUtil.ColorUseType.RENAME, true + AnvilColorUtil.ColorUseType.RENAME ) if (component != null) { diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilColorUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilColorUtil.kt index 320eef1..35ed486 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilColorUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilColorUtil.kt @@ -4,6 +4,7 @@ import net.kyori.adventure.text.Component import org.bukkit.permissions.Permissible import java.util.regex.Matcher import java.util.regex.Pattern +import kotlin.text.indexOf object AnvilColorUtil { private val HEX_PATTERN: Pattern = Pattern.compile("#[A-Fa-f0-9]{6}") // pattern to find hexadecimal string @@ -29,8 +30,7 @@ object AnvilColorUtil { allowColorCode: Boolean, allowHexadecimalColor: Boolean, allowMinimessage: Boolean, - useType: ColorUseType, - isAppend: Boolean = true + useType: ColorUseType ): ColorPermissions { if (!allowColorCode && !allowHexadecimalColor && !allowMinimessage) return ColorPermissions( @@ -49,10 +49,7 @@ object AnvilColorUtil { useType.minimessagePerm )) - // Do not allow minimessage and hex color at the same time when coming from string to component (usually/assumed append) - val minimessageConflict = canUseMinimessage && isAppend - - val canUseHexColor = !minimessageConflict && + val canUseHexColor = allowHexadecimalColor && (!usePermission || useType.hexColorPerm == null || player.hasPermission( useType.hexColorPerm )) @@ -71,12 +68,11 @@ object AnvilColorUtil { allowColorCode: Boolean, allowHexadecimalColor: Boolean, allowMinimessage: Boolean, - useType: ColorUseType, - isAppend: Boolean + useType: ColorUseType ): Component? { val permission = calculatePermissions(player, usePermission, allowColorCode, allowHexadecimalColor, allowMinimessage, - useType, isAppend) + useType) return handleColor(textToColorText, permission) } @@ -101,7 +97,7 @@ object AnvilColorUtil { } if (permission.canUseHexColor) { - val nbReplacement = replaceHexToColor(textToColor, 7) + val nbReplacement = replaceHexToColor(textToColor, 7, permission.canUseMinimessage) if (nbReplacement > 0) useColor = true } @@ -109,13 +105,13 @@ object AnvilColorUtil { val previousStr = textToColor.toString() var result: Component = MiniMessageUtil.legacy_mm.deserialize(previousStr) if(permission.canUseMinimessage) { - // we dance with formats here TODO maybe extract, if possible, only the "text" part and use it for compare with previous as tag would be missing? + // we dance with formats here val toMinimessage = MiniMessageUtil.mm.serialize(result) val hackySolution = toMinimessage.replace("\\<", "<") val fromMinimessage = MiniMessageUtil.mm.deserialize(hackySolution) - val toLegacy = MiniMessageUtil.legacy_mm.serialize(fromMinimessage) + val asPlain = MiniMessageUtil.plain_text_mm.serialize(fromMinimessage) - if(previousStr != toLegacy){ + if(previousStr != asPlain){ useColor = true result = fromMinimessage } @@ -162,8 +158,10 @@ object AnvilColorUtil { // In case we still has some § around by lack of permission we need to convert it back from legacy // In other word it's time for dance #3 val fromLegacy = MiniMessageUtil.legacy_mm.deserialize(legacyMessage.toString()) - val middleGround = MiniMessageUtil.color_only_mm.serialize(fromLegacy) - val hackySolution = middleGround.replace("\\<", "<") + val middleGround = MiniMessageUtil.mm.serialize(fromLegacy) + val hackySolutionStb = StringBuilder(middleGround) + replaceAll(hackySolutionStb, "\\<", "<", 2) + val hackySolution = hackySolutionStb.toString() val result: String = if(permission.canUseMinimessage) hackySolution @@ -202,7 +200,7 @@ object AnvilColorUtil { * @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 { + private fun replaceHexToColor(builder: StringBuilder, endOffset: Int, checkTag: Boolean): Int { val matcher: Matcher = HEX_PATTERN.matcher(builder) var numberOfChanges = 0 @@ -211,6 +209,10 @@ object AnvilColorUtil { while (matcher.find(startIndex)) { startIndex = matcher.start() if (startIndex >= builder.length - endOffset) break //HOW AND WHERE WOULD THIS HAPPEN ????? + if(checkTag && isInTag(builder, startIndex)) { + startIndex += 1 // Avoid infinite loop + continue + } builder.replace(startIndex, startIndex + 1, "§x") startIndex += 2 @@ -225,6 +227,32 @@ object AnvilColorUtil { return numberOfChanges } + // Simple check if < > with some smart check like <> > not taken into account + // This is easily bypassable but if the player want to bypass he has better alternative + // AKA should avoid getting into any tag + private fun isInTag(builder: StringBuilder, index: Int): Boolean { + // Check left tag we have < after last > + val left = builder.slice(0..index) + val leftIndex = left.lastIndexOf("<") + var rightIndex = left.lastIndexOf(">") + + // last < do not exist or is before last > + if(leftIndex == -1 || rightIndex > leftIndex) return false + + val right = builder.slice(index..") + + // first > do not exist or is after first < (if exist) + if (rightIndex == -1 || (newleftIndex != -1 && newleftIndex < rightIndex)) return false + + // Then finally we use minimessage to check for tag + val expectedTag = builder.substring(leftIndex, newleftIndex + 1) + val notag = MiniMessageUtil.mm.stripTags(expectedTag) + + return notag != expectedTag + } + /** * Replace every hex color from the minecraft format to a format like #000000 * @param builder The builder to replace the minecraft hex color from. diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt index 943be5f..36f0efb 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt @@ -248,8 +248,7 @@ object AnvilLoreEditUtil { editType.allowColorCode, editType.allowHexColor, editType.allowMinimessage, - AnvilColorUtil.ColorUseType.LORE_EDIT, - editType.isAppend) + AnvilColorUtil.ColorUseType.LORE_EDIT) } private fun colorLine(line: String, permission: AnvilColorUtil.ColorPermissions): Component? { From fe09a1b2c6a9133ea91c7c1f4252c52183973efc Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 3 Nov 2025 17:06:30 +0100 Subject: [PATCH 021/207] disable lore edit test --- src/test/java/xyz/alexcrea/cuanvil/anvil/LoreEditTests.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/test/java/xyz/alexcrea/cuanvil/anvil/LoreEditTests.java b/src/test/java/xyz/alexcrea/cuanvil/anvil/LoreEditTests.java index 33b0f35..668da58 100644 --- a/src/test/java/xyz/alexcrea/cuanvil/anvil/LoreEditTests.java +++ b/src/test/java/xyz/alexcrea/cuanvil/anvil/LoreEditTests.java @@ -29,9 +29,11 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +// TODO redo test as now color split should be handled in AnvilColorUtilTest and not here +// Especially since some behavior changed public class LoreEditTests extends SharedCustomAnvilTest { - private static AnvilInventory anvil; + /*private static AnvilInventory anvil; private static PlayerMock player; private static final String COLORED_LORE_LINE = "§x§1§2§3§4§5§6TEST §atest"; @@ -615,6 +617,6 @@ public class LoreEditTests extends SharedCustomAnvilTest { ).executeTest(anvil, player); } - //TODO work penalty test + //TODO work penalty test*/ } From b7f98b20fa6a42c6dfbaef66fbd104421b3d525e Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 3 Nov 2025 17:25:53 +0100 Subject: [PATCH 022/207] version up --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index ef848c7..4f7e0a3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,7 +18,7 @@ plugins { } group = "xyz.alexcrea" -version = "1.15.1" +version = "1.15.2" val effectiveVersion = "$version" + (if (System.getenv("SMALL_COMMIT_HASH") != null) "-dev-${System.getenv("SMALL_COMMIT_HASH")!!}" else "") From 9e772c7c197c27602bdaa5273074e0a9ddc99fc8 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Thu, 6 Nov 2025 22:59:52 +0100 Subject: [PATCH 023/207] fix Excellent enchant compatibility fix #93 --- build.gradle.kts | 3 ++- impl/ExcellentEnchant5_3/build.gradle.kts | 21 ++++++++++++++++++ .../plugins/ExcellentEnchant5_3Registry.java | 16 ++++++++++++++ settings.gradle.kts | 4 +++- .../plugins/ExcellentEnchantsDependency.kt | 22 ++++++++++++++++--- 5 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 impl/ExcellentEnchant5_3/build.gradle.kts create mode 100644 impl/ExcellentEnchant5_3/src/main/java/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchant5_3Registry.java diff --git a/build.gradle.kts b/build.gradle.kts index 4f7e0a3..d991b66 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,7 +18,7 @@ plugins { } group = "xyz.alexcrea" -version = "1.15.2" +version = "1.15.3" val effectiveVersion = "$version" + (if (System.getenv("SMALL_COMMIT_HASH") != null) "-dev-${System.getenv("SMALL_COMMIT_HASH")!!}" else "") @@ -52,6 +52,7 @@ dependencies { compileOnly(project(":impl:LegacyEcoEnchant")) // ExcellentEnchants + implementation(project(":impl:ExcellentEnchant5_3")) compileOnly("su.nightexpress.excellentenchants:Core:5.1.0") { exclude("org.spigotmc") } diff --git a/impl/ExcellentEnchant5_3/build.gradle.kts b/impl/ExcellentEnchant5_3/build.gradle.kts new file mode 100644 index 0000000..7004edf --- /dev/null +++ b/impl/ExcellentEnchant5_3/build.gradle.kts @@ -0,0 +1,21 @@ +group = rootProject.group +version = rootProject.version + +plugins { + kotlin("jvm") version "2.1.0" +} + +repositories { + // ExcellentEnchants + maven(url = "https://repo.nightexpressdev.com/releases") +} + +dependencies { + // Spigot api + compileOnly("org.spigotmc:spigot-api:1.18-R0.1-SNAPSHOT") + + // Excellent Enchant + compileOnly("su.nightexpress.excellentenchants:Core:5.3.0") { + exclude("org.spigotmc") + } +} \ No newline at end of file diff --git a/impl/ExcellentEnchant5_3/src/main/java/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchant5_3Registry.java b/impl/ExcellentEnchant5_3/src/main/java/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchant5_3Registry.java new file mode 100644 index 0000000..51e7302 --- /dev/null +++ b/impl/ExcellentEnchant5_3/src/main/java/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchant5_3Registry.java @@ -0,0 +1,16 @@ +package xyz.alexcrea.cuanvil.dependency.plugins; + +import org.jetbrains.annotations.NotNull; +import su.nightexpress.excellentenchants.api.enchantment.CustomEnchantment; +import su.nightexpress.excellentenchants.enchantment.EnchantRegistry; + +import java.util.Set; + +public class ExcellentEnchant5_3Registry { + + public static @NotNull Set getRegistered(){ + return EnchantRegistry.getRegistered(); + } + + +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 867d1bc..1d401da 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -37,4 +37,6 @@ include("nms:v1_21R6") findProject(":nms:v1_21R6")?.name = "v1_21R6" include(":impl:LegacyEcoEnchant") -findProject(":impl:LegacyEcoEnchant")?.name = "LegacyEcoEnchant" \ No newline at end of file +findProject(":impl:LegacyEcoEnchant")?.name = "LegacyEcoEnchant" +include("impl:ExcellentEnchant5_3") +findProject(":impl:ExcellentEnchant5_3")?.name = "ExcellentEnchant5_3" \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt index 2aee624..98d80ff 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt @@ -25,6 +25,7 @@ import su.nightexpress.excellentenchants.registry.EnchantRegistry as PreV5Enchan class ExcellentEnchantsDependency { enum class ListenerVersion(val classPath: String) { + V5_3("su.nightexpress.excellentenchants.enchantment.EnchantRegistry"), V5("su.nightexpress.excellentenchants.manager.listener.AnvilListener"), PRE_V5("su.nightexpress.excellentenchants.enchantment.listener.AnvilListener"), LEGACY("su.nightexpress.excellentenchants.enchantment.listener.EnchantAnvilListener"), @@ -49,6 +50,8 @@ class ExcellentEnchantsDependency { if (listenerVersion == null) { CustomAnvil.instance.logger.severe("Found issue with listener of Excellent Enchants. compatiblity is broken. please contact CustomAnvil devs") + } else{ + CustomAnvil.log("Support version: " + listenerVersion.name) } var isModernCurseOfFragility = true @@ -67,6 +70,13 @@ class ExcellentEnchantsDependency { // As excellent enchants is loaded before custom anvil and register enchantment to registry, we need to unregister old "vanilla" enchant. when (listenerVersion) { + ListenerVersion.V5_3 -> { + for (enchantment in ExcellentEnchant5_3Registry.getRegistered()) { + EnchantmentApi.unregisterEnchantment(enchantment.bukkitEnchantment.key) + EnchantmentApi.registerEnchantment(CAEEV5Enchantment(enchantment)) + } + } + ListenerVersion.V5 -> { for (enchantment in V5EnchantRegistry.getRegistered()) { EnchantmentApi.unregisterEnchantment(enchantment.bukkitEnchantment.key) @@ -119,7 +129,9 @@ class ExcellentEnchantsDependency { } when (listenerVersion) { - ListenerVersion.V5 -> { + ListenerVersion.V5, + ListenerVersion.V5_3 + -> { if (listener is V5AnvilListener) { this.v5AnvilListener = listener toUnregister.add(registeredListener) @@ -151,7 +163,9 @@ class ExcellentEnchantsDependency { } when (listenerVersion) { - ListenerVersion.V5 -> this.usedAnvilListener = v5AnvilListener!! + ListenerVersion.V5, + ListenerVersion.V5_3 + -> this.usedAnvilListener = v5AnvilListener!! ListenerVersion.PRE_V5 -> this.usedAnvilListener = preV5AnvilListener!! ListenerVersion.LEGACY -> this.usedAnvilListener = legacyAnvilListener!! null -> {} @@ -205,7 +219,9 @@ class ExcellentEnchantsDependency { fun testAnvilResult(event: InventoryClickEvent): Any { if (event.inventory.getItem(2) != null) { when (listenerVersion) { - ListenerVersion.V5 -> v5AnvilListener!!.onClickAnvil(event) + ListenerVersion.V5, + ListenerVersion.V5_3 + -> v5AnvilListener!!.onClickAnvil(event) ListenerVersion.PRE_V5 -> preV5AnvilListener!!.onClickAnvil(event) ListenerVersion.LEGACY -> legacyAnvilListener!!.onClickAnvil(event) null -> {} From 8afd54c94de042cd3efd9f4e4e0854f0a25974a2 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Fri, 7 Nov 2025 00:02:17 +0100 Subject: [PATCH 024/207] Custom anvil only work on nms menu --- build.gradle.kts | 2 +- .../cuanvil/dependency/gui/ExternGuiTester.kt | 20 ++++++++++++++----- .../{PaperSpigtUtil.kt => PaperSpigotUtil.kt} | 0 3 files changed, 16 insertions(+), 6 deletions(-) rename nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/util/{PaperSpigtUtil.kt => PaperSpigotUtil.kt} (100%) diff --git a/build.gradle.kts b/build.gradle.kts index d991b66..0ef5bda 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,7 +18,7 @@ plugins { } group = "xyz.alexcrea" -version = "1.15.3" +version = "1.15.4" val effectiveVersion = "$version" + (if (System.getenv("SMALL_COMMIT_HASH") != null) "-dev-${System.getenv("SMALL_COMMIT_HASH")!!}" else "") diff --git a/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/ExternGuiTester.kt b/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/ExternGuiTester.kt index c709954..3079b8a 100644 --- a/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/ExternGuiTester.kt +++ b/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/ExternGuiTester.kt @@ -1,9 +1,14 @@ package xyz.alexcrea.cuanvil.dependency.gui import org.bukkit.inventory.InventoryView +import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil interface ExternGuiTester { + object Const{ + val cannonicalPaperAnvilMenu = "net.minecraft.world.inventory.AnvilMenu" + } + val wesjdAnvilGuiName: String? fun getContainerClass(inventory: InventoryView): Class? @@ -16,12 +21,17 @@ interface ExternGuiTester { val clazz = getContainerClass(inventory) ?: return false val clazzName = clazz.name - //TODO maybe instead of testing non default, better to be testing we are default ? - if (expectWesjd(clazzName)) return true - if (expectXenondevUI(clazzName)) return true - if (expectVanePortal(clazzName)) return true + if(!PlatformUtil.isPaper){ + // Blacklist gui causing issue + if (expectWesjd(clazzName)) return true + if (expectXenondevUI(clazzName)) return true + if (expectVanePortal(clazzName)) return true - return false + return false + } + + // Only allow cannonical anvil menu class + return !Const.cannonicalPaperAnvilMenu.equals(clazzName, true) } fun expectWesjd(name: String): Boolean { diff --git a/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/util/PaperSpigtUtil.kt b/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/util/PaperSpigotUtil.kt similarity index 100% rename from nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/util/PaperSpigtUtil.kt rename to nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/util/PaperSpigotUtil.kt From ade94bdfca3110766e8dbd056e29f88911adab0d Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 10 Nov 2025 23:43:16 +0100 Subject: [PATCH 025/207] fix forgetting luck of the sea for the longest time --- build.gradle.kts | 2 +- defaultconfigs/1.18/enchant_conflict.yml | 4 ++ defaultconfigs/1.21.9/enchant_conflict.yml | 7 +++ defaultconfigs/1.21/enchant_conflict.yml | 4 ++ .../cuanvil/update/plugin/PUpdate_1_15_5.java | 27 ++++++++++ .../cuanvil/update/plugin/PluginUpdates.java | 54 +++++++++++-------- src/main/kotlin/io/delilaheve/CustomAnvil.kt | 9 +++- src/main/resources/enchant_conflict.yml | 4 ++ 8 files changed, 86 insertions(+), 25 deletions(-) create mode 100644 src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_15_5.java diff --git a/build.gradle.kts b/build.gradle.kts index 0ef5bda..901b89a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,7 +18,7 @@ plugins { } group = "xyz.alexcrea" -version = "1.15.4" +version = "1.15.5" val effectiveVersion = "$version" + (if (System.getenv("SMALL_COMMIT_HASH") != null) "-dev-${System.getenv("SMALL_COMMIT_HASH")!!}" else "") diff --git a/defaultconfigs/1.18/enchant_conflict.yml b/defaultconfigs/1.18/enchant_conflict.yml index 0e8b3f3..45d62c3 100644 --- a/defaultconfigs/1.18/enchant_conflict.yml +++ b/defaultconfigs/1.18/enchant_conflict.yml @@ -92,6 +92,10 @@ restriction_loyalty: enchantments: [ minecraft:loyalty ] notAffectedGroups: [ enchanted_book, trident ] +restriction_luck_of_the_sea: + enchantments: [ minecraft:luck_of_the_sea ] + notAffectedGroups: [ enchanted_book, fishing_rod ] + restriction_lure: enchantments: [ minecraft:lure ] notAffectedGroups: [ enchanted_book, fishing_rod ] diff --git a/defaultconfigs/1.21.9/enchant_conflict.yml b/defaultconfigs/1.21.9/enchant_conflict.yml index 04f716f..9205061 100644 --- a/defaultconfigs/1.21.9/enchant_conflict.yml +++ b/defaultconfigs/1.21.9/enchant_conflict.yml @@ -152,6 +152,13 @@ restriction_loyalty: - enchanted_book - trident +restriction_luck_of_the_sea: + enchantments: + - minecraft:luck_of_the_sea + notAffectedGroups: + - enchanted_book + - fishing_rod + restriction_lure: enchantments: - minecraft:lure diff --git a/defaultconfigs/1.21/enchant_conflict.yml b/defaultconfigs/1.21/enchant_conflict.yml index 0e8b3f3..45d62c3 100644 --- a/defaultconfigs/1.21/enchant_conflict.yml +++ b/defaultconfigs/1.21/enchant_conflict.yml @@ -92,6 +92,10 @@ restriction_loyalty: enchantments: [ minecraft:loyalty ] notAffectedGroups: [ enchanted_book, trident ] +restriction_luck_of_the_sea: + enchantments: [ minecraft:luck_of_the_sea ] + notAffectedGroups: [ enchanted_book, fishing_rod ] + restriction_lure: enchantments: [ minecraft:lure ] notAffectedGroups: [ enchanted_book, fishing_rod ] diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_15_5.java b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_15_5.java new file mode 100644 index 0000000..76f51af --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_15_5.java @@ -0,0 +1,27 @@ +package xyz.alexcrea.cuanvil.update.plugin; + +import org.bukkit.configuration.file.FileConfiguration; +import xyz.alexcrea.cuanvil.config.ConfigHolder; + +import javax.annotation.Nonnull; +import java.util.Set; + +import static xyz.alexcrea.cuanvil.update.UpdateUtils.addAbsentToList; + +public class PUpdate_1_15_5 { + + public static void handleUpdate(@Nonnull Set toSave) { + FileConfiguration config = ConfigHolder.CONFLICT_HOLDER.getConfig(); + + if (config.isConfigurationSection("restriction_luck_of_the_sea")) return; + + // We fix the luck of the see enchantment + addAbsentToList(config, "restriction_luck_of_the_sea.enchantments", + "minecraft:luck_of_the_sea"); + addAbsentToList(config, "restriction_luck_of_the_sea.notAffectedGroups", + "enchanted_book", "fishing_rod"); + + toSave.add(ConfigHolder.CONFLICT_HOLDER); + } + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PluginUpdates.java b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PluginUpdates.java index 430825c..d2cd077 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PluginUpdates.java +++ b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PluginUpdates.java @@ -9,7 +9,10 @@ import xyz.alexcrea.cuanvil.update.Version; import javax.annotation.Nonnull; import java.util.HashSet; +import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; public class PluginUpdates { @@ -21,10 +24,13 @@ public class PluginUpdates { handlePluginUpdate(); } - private static final Version V1_6_2 = new Version(1, 6, 2); - private static final Version V1_6_7 = new Version(1, 6, 7); - private static final Version V1_8_0 = new Version(1, 8, 0); - private static final Version V1_11_0 = new Version(1, 11, 0); + private static final Map>> updateMap = Map.of( + new Version(1, 6, 2), PUpdate_1_6_2::handleUpdate, + new Version(1, 6, 7), PUpdate_1_6_7::handleUpdate, + new Version(1, 8, 0), PUpdate_1_8_0::handleUpdate, + new Version(1, 11, 0), PUpdate_1_11_0::handleUpdate, + new Version(1, 15, 5), PUpdate_1_15_5::handleUpdate + ); // Handle only plugin update private static void handlePluginUpdate() { @@ -33,35 +39,32 @@ public class PluginUpdates { Set toSave = new HashSet<>(); - if (V1_6_2.greaterThan(current)) { - PUpdate_1_6_2.handleUpdate(toSave); - // We assume 1.6.7 will run. TODO a better system instead of that I guess - } - if (V1_6_7.greaterThan(current)) { - PUpdate_1_6_7.handleUpdate(toSave); - // We assume 1.8.0 will run. - } - if (V1_8_0.greaterThan(current)) { - PUpdate_1_8_0.handleUpdate(toSave); - // We assume 1.11.0 will run. - } - if (V1_11_0.greaterThan(current)) { - PUpdate_1_11_0.handleUpdate(toSave); + AtomicReference latest = new AtomicReference<>(null); - finishConfiguration("1.11.0", toSave); - } + // Hopefully, should iterate in the "insertion" order + updateMap.forEach((ver, consumer) -> { + if (ver.greaterThan(current)) { + CustomAnvil.log("handling plugin update to " + ver); + consumer.accept(toSave); + latest.set(ver); + } + }); + + if (latest.get() != null) { + finishConfiguration(latest.get().toString(), toSave); + } } // Handle minecraft version update (not plugin version update) - public static void handleMCVersionUpdate(){ + public static void handleMCVersionUpdate() { Version current = UpdateUtils.currentMinecraftVersion(); boolean hadUpdate = false; hadUpdate |= Update_1_21.handleUpdate(current); hadUpdate |= Update_1_21_9.handleUpdate(current); - if(hadUpdate){ + if (hadUpdate) { CustomAnvil.instance.getLogger().info("Updating Done !"); } } @@ -71,9 +74,16 @@ public class PluginUpdates { ConfigHolder.DEFAULT_CONFIG.getConfig().set(CONFIG_VERSION_PATH, newVersion); toSave.add(ConfigHolder.DEFAULT_CONFIG); + // save for (ConfigHolder configHolder : toSave) { configHolder.saveToDisk(true); } + + // then reload + for (ConfigHolder configHolder : toSave) { + configHolder.reload(); + } + } } diff --git a/src/main/kotlin/io/delilaheve/CustomAnvil.kt b/src/main/kotlin/io/delilaheve/CustomAnvil.kt index 386969a..e9b8f27 100644 --- a/src/main/kotlin/io/delilaheve/CustomAnvil.kt +++ b/src/main/kotlin/io/delilaheve/CustomAnvil.kt @@ -10,6 +10,7 @@ import xyz.alexcrea.cuanvil.command.EditConfigExecutor import xyz.alexcrea.cuanvil.command.ReloadExecutor import xyz.alexcrea.cuanvil.config.ConfigHolder import xyz.alexcrea.cuanvil.dependency.DependencyManager +import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil import xyz.alexcrea.cuanvil.enchant.CAEnchantmentRegistry import xyz.alexcrea.cuanvil.gui.config.MainConfigGui import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant @@ -64,7 +65,7 @@ open class CustomAnvil : JavaPlugin() { /** * Logging handler */ - fun log(message: String) { + @JvmStatic fun log(message: String) { if (ConfigOptions.debugLog) { instance.logger.info(message) } @@ -79,7 +80,6 @@ open class CustomAnvil : JavaPlugin() { } } - } /** @@ -96,6 +96,11 @@ open class CustomAnvil : JavaPlugin() { logger.warning("Please note CustomAnvil is a more recent version of UnsafeEnchantsPlus") } + if(!PlatformUtil.isPaper) { + logger.warning("It seems you are using spigot") + logger.warning("Please take notice that spigot is less supported than paper and derivatives") + } + // Add commands prepareCommand() diff --git a/src/main/resources/enchant_conflict.yml b/src/main/resources/enchant_conflict.yml index 0e8b3f3..45d62c3 100644 --- a/src/main/resources/enchant_conflict.yml +++ b/src/main/resources/enchant_conflict.yml @@ -92,6 +92,10 @@ restriction_loyalty: enchantments: [ minecraft:loyalty ] notAffectedGroups: [ enchanted_book, trident ] +restriction_luck_of_the_sea: + enchantments: [ minecraft:luck_of_the_sea ] + notAffectedGroups: [ enchanted_book, fishing_rod ] + restriction_lure: enchantments: [ minecraft:lure ] notAffectedGroups: [ enchanted_book, fishing_rod ] From 9c3c2cfd2c38b37e427bea5dbc71452fe17ef373 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 1 Dec 2025 18:06:44 +0100 Subject: [PATCH 026/207] version bump --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 901b89a..8ae931c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,7 +18,7 @@ plugins { } group = "xyz.alexcrea" -version = "1.15.5" +version = "1.15.6" val effectiveVersion = "$version" + (if (System.getenv("SMALL_COMMIT_HASH") != null) "-dev-${System.getenv("SMALL_COMMIT_HASH")!!}" else "") From 905646cdee98c42bed4a6431714ab52e47c7ee87 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 1 Dec 2025 18:49:21 +0100 Subject: [PATCH 027/207] prepare config update for 1.21.11 --- .../PluginUpdates.java => UpdateHandler.java} | 29 +++++---- .../cuanvil/update/minecraft/MCUpdate.java | 36 +++++++++++ .../update/{ => minecraft}/Update_1_21.java | 33 ++++------ .../update/minecraft/Update_1_21_11.java | 60 +++++++++++++++++++ .../update/{ => minecraft}/Update_1_21_9.java | 35 ++++------- src/main/kotlin/io/delilaheve/CustomAnvil.kt | 5 +- .../cuanvil/command/ReloadExecutor.kt | 4 +- 7 files changed, 140 insertions(+), 62 deletions(-) rename src/main/java/xyz/alexcrea/cuanvil/update/{plugin/PluginUpdates.java => UpdateHandler.java} (78%) create mode 100644 src/main/java/xyz/alexcrea/cuanvil/update/minecraft/MCUpdate.java rename src/main/java/xyz/alexcrea/cuanvil/update/{ => minecraft}/Update_1_21.java (76%) create mode 100644 src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21_11.java rename src/main/java/xyz/alexcrea/cuanvil/update/{ => minecraft}/Update_1_21_9.java (56%) diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PluginUpdates.java b/src/main/java/xyz/alexcrea/cuanvil/update/UpdateHandler.java similarity index 78% rename from src/main/java/xyz/alexcrea/cuanvil/update/plugin/PluginUpdates.java rename to src/main/java/xyz/alexcrea/cuanvil/update/UpdateHandler.java index d2cd077..34f385c 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PluginUpdates.java +++ b/src/main/java/xyz/alexcrea/cuanvil/update/UpdateHandler.java @@ -1,20 +1,22 @@ -package xyz.alexcrea.cuanvil.update.plugin; +package xyz.alexcrea.cuanvil.update; import io.delilaheve.CustomAnvil; import xyz.alexcrea.cuanvil.config.ConfigHolder; -import xyz.alexcrea.cuanvil.update.UpdateUtils; -import xyz.alexcrea.cuanvil.update.Update_1_21; -import xyz.alexcrea.cuanvil.update.Update_1_21_9; -import xyz.alexcrea.cuanvil.update.Version; +import xyz.alexcrea.cuanvil.update.minecraft.MCUpdate; +import xyz.alexcrea.cuanvil.update.minecraft.Update_1_21; +import xyz.alexcrea.cuanvil.update.minecraft.Update_1_21_11; +import xyz.alexcrea.cuanvil.update.minecraft.Update_1_21_9; +import xyz.alexcrea.cuanvil.update.plugin.*; import javax.annotation.Nonnull; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; -public class PluginUpdates { +public class UpdateHandler { private static final String CONFIG_VERSION_PATH = "configVersion"; @@ -24,7 +26,7 @@ public class PluginUpdates { handlePluginUpdate(); } - private static final Map>> updateMap = Map.of( + private static final Map>> pUpdateMap = Map.of( new Version(1, 6, 2), PUpdate_1_6_2::handleUpdate, new Version(1, 6, 7), PUpdate_1_6_7::handleUpdate, new Version(1, 8, 0), PUpdate_1_8_0::handleUpdate, @@ -32,6 +34,12 @@ public class PluginUpdates { new Version(1, 15, 5), PUpdate_1_15_5::handleUpdate ); + private static final List mcUpdateMap = List.of( + new Update_1_21(), + new Update_1_21_9(), + new Update_1_21_11() + ); + // Handle only plugin update private static void handlePluginUpdate() { String versionString = ConfigHolder.DEFAULT_CONFIG.getConfig().getString(CONFIG_VERSION_PATH); @@ -42,7 +50,7 @@ public class PluginUpdates { AtomicReference latest = new AtomicReference<>(null); // Hopefully, should iterate in the "insertion" order - updateMap.forEach((ver, consumer) -> { + pUpdateMap.forEach((ver, consumer) -> { if (ver.greaterThan(current)) { CustomAnvil.log("handling plugin update to " + ver); consumer.accept(toSave); @@ -61,8 +69,9 @@ public class PluginUpdates { Version current = UpdateUtils.currentMinecraftVersion(); boolean hadUpdate = false; - hadUpdate |= Update_1_21.handleUpdate(current); - hadUpdate |= Update_1_21_9.handleUpdate(current); + for (MCUpdate mcUpdate : mcUpdateMap) { + hadUpdate |= mcUpdate.handleUpdate(current); + } if (hadUpdate) { CustomAnvil.instance.getLogger().info("Updating Done !"); diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/MCUpdate.java b/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/MCUpdate.java new file mode 100644 index 0000000..d9db0ea --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/MCUpdate.java @@ -0,0 +1,36 @@ +package xyz.alexcrea.cuanvil.update.minecraft; + +import io.delilaheve.CustomAnvil; +import xyz.alexcrea.cuanvil.config.ConfigHolder; +import xyz.alexcrea.cuanvil.update.UpdateUtils; +import xyz.alexcrea.cuanvil.update.Version; + +public abstract class MCUpdate { + + public final Version version; + + public MCUpdate(Version version){ + this.version = version; + } + + public boolean handleUpdate(Version current){ + // Test if we are running in this update version or better + if(version.greaterThan(current)) + return false; + + // if version path is not null then check if its it's before this update version + String oldVersion = ConfigHolder.DEFAULT_CONFIG.getConfig().getString(UpdateUtils.MINECRAFT_VERSION_PATH); + if(oldVersion != null){ + var version = Version.fromString(oldVersion); + if(this.version.lesserEqual(version)) return false; + } + + CustomAnvil.instance.getLogger().info("Updating config to support " + version +" ..."); + doUpdate(); + return true; + } + + protected abstract void doUpdate(); + + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21.java b/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21.java similarity index 76% rename from src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21.java rename to src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21.java index 56de63c..3aa6073 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21.java +++ b/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21.java @@ -1,33 +1,20 @@ -package xyz.alexcrea.cuanvil.update; +package xyz.alexcrea.cuanvil.update.minecraft; import io.delilaheve.CustomAnvil; import xyz.alexcrea.cuanvil.config.ConfigHolder; +import xyz.alexcrea.cuanvil.update.UpdateUtils; +import xyz.alexcrea.cuanvil.update.Version; import static xyz.alexcrea.cuanvil.update.UpdateUtils.addAbsentToList; -public class Update_1_21 { +public class Update_1_21 extends MCUpdate { - private static final Version V1_21 = new Version(1, 21); - - public static boolean handleUpdate(Version current){ - // Test if we are running in 1.21 or better - if(V1_21.greaterThan(current)) - return false; - - // if version path is not null then check if its it's before 1.21 - String oldVersion = ConfigHolder.DEFAULT_CONFIG.getConfig().getString(UpdateUtils.MINECRAFT_VERSION_PATH); - if(oldVersion != null){ - var version = Version.fromString(oldVersion); - if(V1_21.lesserEqual(version)) return false; - } - - doUpdate(); - return true; + public Update_1_21() { + super(new Version(1, 21)); } - private static void doUpdate() { - CustomAnvil.instance.getLogger().info("Updating config to support 1.21 ..."); - + @Override + protected void doUpdate() { var baseConfig = ConfigHolder.DEFAULT_CONFIG.getConfig(); var groupConfig = ConfigHolder.ITEM_GROUP_HOLDER.getConfig(); var conflictConfig = ConfigHolder.CONFLICT_HOLDER.getConfig(); @@ -75,8 +62,8 @@ public class Update_1_21 { // Add unit repair for mace unitConfig.set("breeze_rod.mace", 0.25); - // Set version string as 1.21 - baseConfig.set(UpdateUtils.MINECRAFT_VERSION_PATH, V1_21.toString()); + // Set version string as current + baseConfig.set(UpdateUtils.MINECRAFT_VERSION_PATH, version.toString()); // Save ConfigHolder.DEFAULT_CONFIG.saveToDisk(true); diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21_11.java b/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21_11.java new file mode 100644 index 0000000..a060394 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21_11.java @@ -0,0 +1,60 @@ +package xyz.alexcrea.cuanvil.update.minecraft; + +import xyz.alexcrea.cuanvil.config.ConfigHolder; +import xyz.alexcrea.cuanvil.update.UpdateUtils; +import xyz.alexcrea.cuanvil.update.Version; + +import static xyz.alexcrea.cuanvil.update.UpdateUtils.addAbsentToList; + +public class Update_1_21_11 extends MCUpdate{ + + public Update_1_21_11() { + super(new Version(1, 21, 11)); + } + + @Override + protected void doUpdate() { + var baseConfig = ConfigHolder.DEFAULT_CONFIG.getConfig(); + var groupConfig = ConfigHolder.ITEM_GROUP_HOLDER.getConfig(); + var conflictConfig = ConfigHolder.CONFLICT_HOLDER.getConfig(); + + // Create spear group + groupConfig.set("spears.type", "include"); + addAbsentToList(groupConfig, "spears.items", + "wooden_spear", + "golden_spear", + "stone_spear", + "copper_spear", + "iron_spear", + "diamond_spear", + "netherite_spear"); + + // Add spear group to super group and enchantments + addAbsentToList(groupConfig, "melee_weapons.groups", "spears"); + + addAbsentToList(conflictConfig, "restriction_looting.notAffectedGroups", "spears"); + addAbsentToList(conflictConfig, "restriction_knockback.notAffectedGroups", "spears"); + addAbsentToList(conflictConfig, "restriction_fire_aspect.notAffectedGroups", "spears"); + + // Create lunge enchant value and group + baseConfig.set("enchant_limits.minecraft:lunge", 3); + baseConfig.set("enchant_values.minecraft:lunge.item", 2); + baseConfig.set("enchant_values.minecraft:lunge.book", 1); + + addAbsentToList(conflictConfig, "restriction_lunge.enchantments", "minecraft:lunge"); + addAbsentToList(conflictConfig, "restriction_lunge.notAffectedGroups", "spears", "enchanted_book"); + + // Set version string as current + baseConfig.set(UpdateUtils.MINECRAFT_VERSION_PATH, version.toString()); + + // Save + ConfigHolder.DEFAULT_CONFIG.saveToDisk(true); + ConfigHolder.ITEM_GROUP_HOLDER.saveToDisk(true); + ConfigHolder.CONFLICT_HOLDER.saveToDisk(true); + + // imply reload of CONFLICT_HOLDER + // We also do not need to reload base config as there is no object related to it. + ConfigHolder.ITEM_GROUP_HOLDER.reload(); + } + +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21_9.java b/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21_9.java similarity index 56% rename from src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21_9.java rename to src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21_9.java index e749d74..a4e21a7 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21_9.java +++ b/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21_9.java @@ -1,37 +1,24 @@ -package xyz.alexcrea.cuanvil.update; +package xyz.alexcrea.cuanvil.update.minecraft; import io.delilaheve.CustomAnvil; import xyz.alexcrea.cuanvil.config.ConfigHolder; +import xyz.alexcrea.cuanvil.update.UpdateUtils; +import xyz.alexcrea.cuanvil.update.Version; import static xyz.alexcrea.cuanvil.update.UpdateUtils.addAbsentToList; -public class Update_1_21_9 { +public class Update_1_21_9 extends MCUpdate{ - private static final Version V1_21_9 = new Version(1, 21, 9); - - public static boolean handleUpdate(Version current){ - // Test if we are running in 1.21.9 or better - if(V1_21_9.greaterThan(current)) - return false; - - // if version path is not null then check if its it's before 1.21.9 - String oldVersion = ConfigHolder.DEFAULT_CONFIG.getConfig().getString(UpdateUtils.MINECRAFT_VERSION_PATH); - if(oldVersion != null){ - var version = Version.fromString(oldVersion); - if(V1_21_9.lesserEqual(version)) return false; - } - - doUpdate(); - return true; + public Update_1_21_9() { + super(new Version(1, 21, 9)); } - private static void doUpdate() { - CustomAnvil.instance.getLogger().info("Updating config to support 1.21.9 ..."); - + @Override + protected void doUpdate() { var baseConfig = ConfigHolder.DEFAULT_CONFIG.getConfig(); var groupConfig = ConfigHolder.ITEM_GROUP_HOLDER.getConfig(); - // Add mace to groups + // Add cooper items to groups addAbsentToList(groupConfig, "helmets.items", "copper_helmet"); addAbsentToList(groupConfig, "chestplate.items", "copper_chestplate"); addAbsentToList(groupConfig, "leggings.items", "copper_leggings"); @@ -43,8 +30,8 @@ public class Update_1_21_9 { addAbsentToList(groupConfig, "axes.items", "copper_axe"); addAbsentToList(groupConfig, "swords.items", "copper_sword"); - // Set version string as 1.21 - baseConfig.set(UpdateUtils.MINECRAFT_VERSION_PATH, V1_21_9.toString()); + // Set version string as current + baseConfig.set(UpdateUtils.MINECRAFT_VERSION_PATH, version.toString()); // Save ConfigHolder.DEFAULT_CONFIG.saveToDisk(true); diff --git a/src/main/kotlin/io/delilaheve/CustomAnvil.kt b/src/main/kotlin/io/delilaheve/CustomAnvil.kt index e9b8f27..c747189 100644 --- a/src/main/kotlin/io/delilaheve/CustomAnvil.kt +++ b/src/main/kotlin/io/delilaheve/CustomAnvil.kt @@ -19,8 +19,7 @@ import xyz.alexcrea.cuanvil.listener.AnvilResultListener import xyz.alexcrea.cuanvil.listener.ChatEventListener import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener import xyz.alexcrea.cuanvil.update.PluginSetDefault -import xyz.alexcrea.cuanvil.update.Update_1_21 -import xyz.alexcrea.cuanvil.update.plugin.PluginUpdates +import xyz.alexcrea.cuanvil.update.UpdateHandler import xyz.alexcrea.cuanvil.util.Metrics import java.io.File import java.io.FileReader @@ -145,7 +144,7 @@ open class CustomAnvil : JavaPlugin() { } // Handle minecraft and plugin updates - PluginUpdates.handleUpdates() + UpdateHandler.handleUpdates() // Register enchantment of compatible plugin and load configuration change. DependencyManager.handleCompatibilityConfig() diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/ReloadExecutor.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/ReloadExecutor.kt index 8093e09..dc2fe8e 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/ReloadExecutor.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/ReloadExecutor.kt @@ -9,7 +9,7 @@ import xyz.alexcrea.cuanvil.api.event.CAConfigReadyEvent import xyz.alexcrea.cuanvil.config.ConfigHolder import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.gui.config.global.* -import xyz.alexcrea.cuanvil.update.plugin.PluginUpdates +import xyz.alexcrea.cuanvil.update.UpdateHandler class ReloadExecutor : CommandExecutor { override fun onCommand(sender: CommandSender, cmd: Command, cmdstr: String, args: Array): Boolean { @@ -49,7 +49,7 @@ class ReloadExecutor : CommandExecutor { CustomRecipeConfigGui.getCurrentInstance()?.reloadValues() // handle minecraft version update - PluginUpdates.handleMCVersionUpdate() + UpdateHandler.handleMCVersionUpdate() // Handle dependency reload DependencyManager.handleConfigReload() From 0aad19166f61d59fc0562a0594762b58a6a228e8 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 1 Dec 2025 18:58:40 +0100 Subject: [PATCH 028/207] prepare 1.21.11 nms --- build.gradle.kts | 1 + nms/v1_21R7/.gitignore | 1 + nms/v1_21R7/build.gradle.kts | 34 +++++++++++++++++++ .../gui/version/v1_21R7_ExternGuiTester.kt | 34 +++++++++++++++++++ .../packet/versions/V1_21R7_PacketManager.kt | 33 ++++++++++++++++++ settings.gradle.kts | 2 ++ .../dependency/gui/GuiTesterSelector.kt | 1 + .../packet/PacketManagerSelector.kt | 1 + 8 files changed, 107 insertions(+) create mode 100644 nms/v1_21R7/.gitignore create mode 100644 nms/v1_21R7/build.gradle.kts create mode 100644 nms/v1_21R7/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R7_ExternGuiTester.kt create mode 100644 nms/v1_21R7/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_21R7_PacketManager.kt diff --git a/build.gradle.kts b/build.gradle.kts index 8ae931c..57b8b93 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -89,6 +89,7 @@ dependencies { implementation(project(":nms:v1_21R4", configuration = "reobf")) implementation(project(":nms:v1_21R5", configuration = "reobf")) implementation(project(":nms:v1_21R6", configuration = "reobf")) + implementation(project(":nms:v1_21R7"))// TODO reobf on release, configuration = "reobf")) // include kotlin for the offline jar implementation(kotlin("stdlib")) diff --git a/nms/v1_21R7/.gitignore b/nms/v1_21R7/.gitignore new file mode 100644 index 0000000..47374f1 --- /dev/null +++ b/nms/v1_21R7/.gitignore @@ -0,0 +1 @@ +.lastDeploymentsId \ No newline at end of file diff --git a/nms/v1_21R7/build.gradle.kts b/nms/v1_21R7/build.gradle.kts new file mode 100644 index 0000000..9c8a6f3 --- /dev/null +++ b/nms/v1_21R7/build.gradle.kts @@ -0,0 +1,34 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +group = rootProject.group +version = rootProject.version + +plugins { + id("io.papermc.paperweight.userdev") +} + +dependencies { + implementation(project(":nms:nms-common")) + + // Used for nms + paperweight.paperDevBundle("1.21.11-pre3-R0.1-SNAPSHOT") //TODO update to 1.21.11-R0.1-SNAPSHOT on release +} + +repositories { + maven("https://repo.papermc.io/repository/maven-public/") +} + +// Set target version +tasks.withType().configureEach { + sourceCompatibility = "21" + targetCompatibility = "21" + + options.encoding = "UTF-8" +} + +kotlin { + compilerOptions { + apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) + jvmTarget.set(JvmTarget.JVM_21) + } +} diff --git a/nms/v1_21R7/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R7_ExternGuiTester.kt b/nms/v1_21R7/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R7_ExternGuiTester.kt new file mode 100644 index 0000000..c380211 --- /dev/null +++ b/nms/v1_21R7/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R7_ExternGuiTester.kt @@ -0,0 +1,34 @@ +package xyz.alexcrea.cuanvil.dependency.gui.version + +import org.bukkit.craftbukkit.inventory.CraftInventoryView +import org.bukkit.inventory.InventoryView +import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester + +class v1_21R7_ExternGuiTester: ExternGuiTester { + override val wesjdAnvilGuiName = "Wrapper1_21_R7" + + var tested = false; + var possible = false; + + override fun getContainerClass(view: InventoryView): Class? { + // In case we are in a test environment + if(!tested) testClassExist() + if(!possible) return null + + if(view !is CraftInventoryView<*, *>) return null + val container = view.handle + + return container.javaClass + } + + fun testClassExist(){ + tested = true; + try { + Class.forName("org.bukkit.craftbukkit.inventory.CraftInventoryView") + possible = true + } catch (e: ClassNotFoundException){ + possible = false + } + } + +} \ No newline at end of file diff --git a/nms/v1_21R7/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_21R7_PacketManager.kt b/nms/v1_21R7/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_21R7_PacketManager.kt new file mode 100644 index 0000000..59ae9ce --- /dev/null +++ b/nms/v1_21R7/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_21R7_PacketManager.kt @@ -0,0 +1,33 @@ +package xyz.alexcrea.cuanvil.dependency.packet.versions + +import net.minecraft.network.protocol.game.ClientboundPlayerAbilitiesPacket +import net.minecraft.world.entity.player.Abilities +import org.bukkit.craftbukkit.entity.CraftPlayer +import org.bukkit.entity.Player +import xyz.alexcrea.cuanvil.dependency.packet.PacketManager +import xyz.alexcrea.cuanvil.dependency.packet.PacketManagerBase + +class V1_21R7_PacketManager : PacketManagerBase(), PacketManager { + override val canSetInstantBuild: Boolean + get() = true + + override fun setInstantBuild(player: Player, instantBuild: Boolean) { + val nmsPlayer = (player as CraftPlayer).handle + val playerAbilities = nmsPlayer.abilities + val sendedAbilities: Abilities + if (playerAbilities.instabuild == instantBuild) { + sendedAbilities = playerAbilities + } else { + sendedAbilities = Abilities() + sendedAbilities.invulnerable = playerAbilities.invulnerable + sendedAbilities.flying = playerAbilities.flying + sendedAbilities.mayfly = playerAbilities.mayfly + sendedAbilities.instabuild = instantBuild + sendedAbilities.mayBuild = playerAbilities.mayBuild + sendedAbilities.flyingSpeed = playerAbilities.flyingSpeed + sendedAbilities.walkingSpeed = playerAbilities.walkingSpeed + } + val packet = ClientboundPlayerAbilitiesPacket(sendedAbilities) + nmsPlayer.connection.send(packet) + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 1d401da..0790502 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -35,6 +35,8 @@ include("nms:v1_21R5") findProject(":nms:v1_21R5")?.name = "v1_21R5" include("nms:v1_21R6") findProject(":nms:v1_21R6")?.name = "v1_21R6" +include("nms:v1_21R7") +findProject(":nms:v1_21R7")?.name = "v1_21R7" include(":impl:LegacyEcoEnchant") findProject(":impl:LegacyEcoEnchant")?.name = "LegacyEcoEnchant" diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt index f3d2122..aa2ab95 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt @@ -46,6 +46,7 @@ object GuiTesterSelector { 5 -> v1_21R4_ExternGuiTester() 6, 7, 8 -> v1_21R5_ExternGuiTester() 9, 10 -> v1_21R6_ExternGuiTester() + 11 -> v1_21R7_ExternGuiTester() else -> null } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt index 985b3f5..ccb8620 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt @@ -60,6 +60,7 @@ object PacketManagerSelector { 5 -> V1_21R4_PacketManager() 6, 7, 8 -> V1_21R5_PacketManager() 9, 10 -> V1_21R6_PacketManager() + 11 -> V1_21R7_PacketManager() else -> null } From eb24fb4be8bf61b2156e498c822e2200dd7348b0 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 1 Dec 2025 19:16:14 +0100 Subject: [PATCH 029/207] fix copper unit repair not being present --- .../cuanvil/update/UpdateHandler.java | 3 ++- .../update/minecraft/Update_1_21_11.java | 5 ++++ .../update/minecraft/Update_1_21_9.java | 21 ++++++++++++++- .../cuanvil/update/plugin/PUpdate_1_15_6.java | 27 +++++++++++++++++++ 4 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_15_6.java diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/UpdateHandler.java b/src/main/java/xyz/alexcrea/cuanvil/update/UpdateHandler.java index 34f385c..a563b7f 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/update/UpdateHandler.java +++ b/src/main/java/xyz/alexcrea/cuanvil/update/UpdateHandler.java @@ -31,7 +31,8 @@ public class UpdateHandler { new Version(1, 6, 7), PUpdate_1_6_7::handleUpdate, new Version(1, 8, 0), PUpdate_1_8_0::handleUpdate, new Version(1, 11, 0), PUpdate_1_11_0::handleUpdate, - new Version(1, 15, 5), PUpdate_1_15_5::handleUpdate + new Version(1, 15, 5), PUpdate_1_15_5::handleUpdate, + new Version(1, 15, 6), PUpdate_1_15_6::handleUpdate ); private static final List mcUpdateMap = List.of( diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21_11.java b/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21_11.java index a060394..90c17bf 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21_11.java +++ b/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21_11.java @@ -17,6 +17,7 @@ public class Update_1_21_11 extends MCUpdate{ var baseConfig = ConfigHolder.DEFAULT_CONFIG.getConfig(); var groupConfig = ConfigHolder.ITEM_GROUP_HOLDER.getConfig(); var conflictConfig = ConfigHolder.CONFLICT_HOLDER.getConfig(); + var unitConfig = ConfigHolder.UNIT_REPAIR_HOLDER.getConfig(); // Create spear group groupConfig.set("spears.type", "include"); @@ -36,6 +37,9 @@ public class Update_1_21_11 extends MCUpdate{ addAbsentToList(conflictConfig, "restriction_knockback.notAffectedGroups", "spears"); addAbsentToList(conflictConfig, "restriction_fire_aspect.notAffectedGroups", "spears"); + // Unit repair for spears + + // Create lunge enchant value and group baseConfig.set("enchant_limits.minecraft:lunge", 3); baseConfig.set("enchant_values.minecraft:lunge.item", 2); @@ -51,6 +55,7 @@ public class Update_1_21_11 extends MCUpdate{ ConfigHolder.DEFAULT_CONFIG.saveToDisk(true); ConfigHolder.ITEM_GROUP_HOLDER.saveToDisk(true); ConfigHolder.CONFLICT_HOLDER.saveToDisk(true); + ConfigHolder.UNIT_REPAIR_HOLDER.saveToDisk(true); // imply reload of CONFLICT_HOLDER // We also do not need to reload base config as there is no object related to it. diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21_9.java b/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21_9.java index a4e21a7..d289b9b 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21_9.java +++ b/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21_9.java @@ -1,6 +1,6 @@ package xyz.alexcrea.cuanvil.update.minecraft; -import io.delilaheve.CustomAnvil; +import org.bukkit.configuration.file.FileConfiguration; import xyz.alexcrea.cuanvil.config.ConfigHolder; import xyz.alexcrea.cuanvil.update.UpdateUtils; import xyz.alexcrea.cuanvil.update.Version; @@ -17,6 +17,7 @@ public class Update_1_21_9 extends MCUpdate{ protected void doUpdate() { var baseConfig = ConfigHolder.DEFAULT_CONFIG.getConfig(); var groupConfig = ConfigHolder.ITEM_GROUP_HOLDER.getConfig(); + var unitConfig = ConfigHolder.UNIT_REPAIR_HOLDER.getConfig(); // Add cooper items to groups addAbsentToList(groupConfig, "helmets.items", "copper_helmet"); @@ -30,16 +31,34 @@ public class Update_1_21_9 extends MCUpdate{ addAbsentToList(groupConfig, "axes.items", "copper_axe"); addAbsentToList(groupConfig, "swords.items", "copper_sword"); + // Add unit repair + addCopperUnitRepair(unitConfig); + // Set version string as current baseConfig.set(UpdateUtils.MINECRAFT_VERSION_PATH, version.toString()); // Save ConfigHolder.DEFAULT_CONFIG.saveToDisk(true); ConfigHolder.ITEM_GROUP_HOLDER.saveToDisk(true); + ConfigHolder.UNIT_REPAIR_HOLDER.saveToDisk(true); // imply reload of CONFLICT_HOLDER // We also do not need to reload base config as there is no object related to it. ConfigHolder.ITEM_GROUP_HOLDER.reload(); } + public static void addCopperUnitRepair(FileConfiguration unitConfig) { + // Add unit repair + unitConfig.set("copper_ingot.copper_helmet", 0.25); + unitConfig.set("copper_ingot.copper_chestplate", 0.25); + unitConfig.set("copper_ingot.copper_leggings", 0.25); + unitConfig.set("copper_ingot.copper_boots", 0.25); + + unitConfig.set("copper_ingot.copper_pickaxe", 0.25); + unitConfig.set("copper_ingot.copper_shovel", 0.25); + unitConfig.set("copper_ingot.copper_hoe", 0.25); + unitConfig.set("copper_ingot.copper_axe", 0.25); + unitConfig.set("copper_ingot.copper_sword", 0.25); + } + } diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_15_6.java b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_15_6.java new file mode 100644 index 0000000..fde7cfc --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_15_6.java @@ -0,0 +1,27 @@ +package xyz.alexcrea.cuanvil.update.plugin; + +import org.bukkit.configuration.file.FileConfiguration; +import xyz.alexcrea.cuanvil.config.ConfigHolder; +import xyz.alexcrea.cuanvil.update.UpdateUtils; +import xyz.alexcrea.cuanvil.update.Version; +import xyz.alexcrea.cuanvil.update.minecraft.Update_1_21_9; + +import javax.annotation.Nonnull; +import java.util.Set; + +public class PUpdate_1_15_6 { + + public static void handleUpdate(@Nonnull Set toSave) { + // fix only needed for 1.21.9 and above + Version current = UpdateUtils.currentMinecraftVersion(); + if (new Version(1, 21, 9).greaterThan(current)) return; + + FileConfiguration unitConfig = ConfigHolder.UNIT_REPAIR_HOLDER.getConfig(); + + // Add unit repair + Update_1_21_9.addCopperUnitRepair(unitConfig); + + toSave.add(ConfigHolder.UNIT_REPAIR_HOLDER); + } + +} From 8582038c715c1db187b2728d4deff6877bd178cc Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 1 Dec 2025 19:19:50 +0100 Subject: [PATCH 030/207] add unit repair for spears --- .../update/minecraft/Update_1_21_11.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21_11.java b/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21_11.java index 90c17bf..d639596 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21_11.java +++ b/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21_11.java @@ -38,7 +38,26 @@ public class Update_1_21_11 extends MCUpdate{ addAbsentToList(conflictConfig, "restriction_fire_aspect.notAffectedGroups", "spears"); // Unit repair for spears + unitConfig.set("gold_ingot.golden_spear", 0.25); + unitConfig.set("copper_ingot.copper_spear", 0.25); + unitConfig.set("iron_ingot.iron_spear", 0.25); + unitConfig.set("diamond.diamond_spear", 0.25); + unitConfig.set("netherite_ingot.netherite_spear", 0.25); + unitConfig.set("cobblestone.stone_spear", 0.25); + unitConfig.set("cobbled_deepslate.stone_spear", 0.25); + + unitConfig.set("oak_planks.wooden_spear", 0.25); + unitConfig.set("spruce_planks.wooden_spear", 0.25); + unitConfig.set("birch_planks.wooden_spear", 0.25); + unitConfig.set("jungle_planks.wooden_spear", 0.25); + unitConfig.set("acacia_planks.wooden_spear", 0.25); + unitConfig.set("dark_oak_planks.wooden_spear", 0.25); + unitConfig.set("mangrove_planks.wooden_spear", 0.25); + unitConfig.set("cherry_planks.wooden_spear", 0.25); + unitConfig.set("bamboo_planks.wooden_spear", 0.25); + unitConfig.set("crimson_planks.wooden_spear", 0.25); + unitConfig.set("warped_planks.wooden_spear", 0.25); // Create lunge enchant value and group baseConfig.set("enchant_limits.minecraft:lunge", 3); From d096ee7f35854507cd2920f24a2458d62bce7f9d Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 1 Dec 2025 19:26:19 +0100 Subject: [PATCH 031/207] less vebose about minecraft config update --- .../java/xyz/alexcrea/cuanvil/update/UpdateHandler.java | 2 +- .../xyz/alexcrea/cuanvil/update/minecraft/MCUpdate.java | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/UpdateHandler.java b/src/main/java/xyz/alexcrea/cuanvil/update/UpdateHandler.java index a563b7f..82ee0f7 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/update/UpdateHandler.java +++ b/src/main/java/xyz/alexcrea/cuanvil/update/UpdateHandler.java @@ -71,7 +71,7 @@ public class UpdateHandler { boolean hadUpdate = false; for (MCUpdate mcUpdate : mcUpdateMap) { - hadUpdate |= mcUpdate.handleUpdate(current); + hadUpdate |= mcUpdate.handleUpdate(current, hadUpdate); } if (hadUpdate) { diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/MCUpdate.java b/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/MCUpdate.java index d9db0ea..40fc587 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/MCUpdate.java +++ b/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/MCUpdate.java @@ -13,7 +13,7 @@ public abstract class MCUpdate { this.version = version; } - public boolean handleUpdate(Version current){ + public boolean handleUpdate(Version current, boolean hadUpdate){ // Test if we are running in this update version or better if(version.greaterThan(current)) return false; @@ -25,7 +25,9 @@ public abstract class MCUpdate { if(this.version.lesserEqual(version)) return false; } - CustomAnvil.instance.getLogger().info("Updating config to support " + version +" ..."); + if(!hadUpdate){ + CustomAnvil.instance.getLogger().info("Updating config to support minecraft " + current +" ..."); + } doUpdate(); return true; } From 35fb136a40a8a6cf011d25f8295b57b2c05abaf9 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 1 Dec 2025 19:30:50 +0100 Subject: [PATCH 032/207] default config for 1.21.11 --- README.md | 4 +- defaultconfigs/1.21.11/README.md | 6 + defaultconfigs/1.21.11/config.yml | 427 ++++++++++++++++++++ defaultconfigs/1.21.11/custom_recipes.yml | 5 + defaultconfigs/1.21.11/enchant_conflict.yml | 398 ++++++++++++++++++ defaultconfigs/1.21.11/item_groups.yml | 247 +++++++++++ defaultconfigs/1.21.11/unit_repair_item.yml | 220 ++++++++++ defaultconfigs/README.md | 6 +- 8 files changed, 1308 insertions(+), 5 deletions(-) create mode 100644 defaultconfigs/1.21.11/README.md create mode 100644 defaultconfigs/1.21.11/config.yml create mode 100644 defaultconfigs/1.21.11/custom_recipes.yml create mode 100644 defaultconfigs/1.21.11/enchant_conflict.yml create mode 100644 defaultconfigs/1.21.11/item_groups.yml create mode 100644 defaultconfigs/1.21.11/unit_repair_item.yml diff --git a/README.md b/README.md index cc868c5..3f5a806 100644 --- a/README.md +++ b/README.md @@ -100,9 +100,7 @@ For information about the API, please refer to [the Wiki](https://github.com/ale --- ### Default Plugin's Configurations -For 1.18 to 1.20.6 use the [1.18 configurations](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.18)\ -For 1.21 to 1.21.8 use the [1.21 configurations](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21)\ -From 1.21.9 use the [1.21.9 configurations](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21) +see [Here](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs) --- Custom anvil [use bstat](https://bstats.org/plugin/bukkit/Unsafe%20Enchants%20Plus/20923) for metric. You can [disable it](https://bstats.org/getting-started) if you like. diff --git a/defaultconfigs/1.21.11/README.md b/defaultconfigs/1.21.11/README.md new file mode 100644 index 0000000..a33a09b --- /dev/null +++ b/defaultconfigs/1.21.11/README.md @@ -0,0 +1,6 @@ +### Default Plugin's Configurations For 1.21.11 +- [config.yml](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21.11/config.yml) +- [enchant_conflict.yml](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21.11/enchant_conflict.yml) +- [item_groups.yml](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21.11/item_groups.yml) +- [unit_repair_item.yml](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21.11/unit_repair_item.yml) +- [custom_recipes.yml](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21.11/custom_recipes.yml) diff --git a/defaultconfigs/1.21.11/config.yml b/defaultconfigs/1.21.11/config.yml new file mode 100644 index 0000000..10fe8af --- /dev/null +++ b/defaultconfigs/1.21.11/config.yml @@ -0,0 +1,427 @@ +# +# It is recommended that you use /configanvil to edit theses config. +# You can still manually edit here if you like to. but if you do, don't forget to /anvilconfigreload after you changes ! +# + +# All anvil cost will be capped to limit_repair_value if enabled. +# +# In other words: +# For any anvil cost greater than limit_repair_value, Cost will be set to limit_repair_value. +limit_repair_cost: false + +# Max cost value the Anvil can get to. +# +# Valid values include 0 to 1000. +# Cost will be displayed as "Too Expensive": +# - If Cost is above 39 +# - And replace_too_expensive is disabled (false) +limit_repair_value: 39 + +# Whether the anvil's cost limit should be removed entirely. +# +# The anvil will still visually display "Too Expensive" if "replace_too_expensive" is disabled +# However, the action will be completable if xp requirement is meet. +remove_repair_limit: false + +# Whenever anvil cost is above 39 should display the true price and not "Too Expensive". +# +# However, when bypassing "Too Expensive", anvil price will be displayed as Green. +# If the action is not completable, the cost will still be displayed as "Too expensive". +# That mean you also need to change other settings like remove_repair_limit or limit_repair_cost. +# +# Require ProtocoLib. +replace_too_expensive: false + +# XP Level amount added to the anvil when the item is repaired by another item of the same type +# +# Valid values include 0 to 1000 +item_repair_cost: 2 + +# XP Level amount added to the anvil when the item is renamed +# +# Valid values include 0 to 1000 +item_rename_cost: 1 + +# XP Level amount added to the anvil when the item is repaired by an "unit" +# For example: a Diamond on a Diamond Sword +# What's considered unit for what can be edited on the unit repair configuration. +# +# Valid values include 0 to 1000 +unit_repair_cost: 1 + +# XP Level amount added to the anvil when a sacrifice enchantment +# conflict with one of the left item enchantment +# +# Valid values include 0 to 1000 +sacrifice_illegal_enchant_cost: 1 + +# Allow using color code and hexadecimal color. +# +# Color code are prefixed by "&" and hexadecimal color by "#". +# Color code will not be applied if it colors nothing. "&&" can be used to write "&". +# For minimessage search for minimessage formating https://docs.papermc.io/adventure/minimessage/format/ +# Note that only color and decoration tags are allowed for minimisage in the v1 version of this plugin +# but any global tag will be allowed later when v2 release +allow_color_code: false +allow_hexadecimal_color: false +allow_minimessage: false + +# Toggle if color should only be applicable if the player a certain permission. +# +# permission are "ca.color.code" for use of color code and "ca.color.hex" for use of hexadecimal color. +permission_needed_for_color: true + +# Xp cost if the player use color in the items name on rename. +# +# Valid values include 0 to 1000. +use_of_color_cost: 0 + +# Default limit to apply to any enchants missing from enchant_limits +# +# Valid values include 1 to 1000 +default_limit: 5 + +# Override limits for specific enchants +# +# Enchantments not listed here will use the value of default_limit +# +# Overrides provided default from aqua_affinity to depth_strider won't change effect with extra levels +# +# Valid range of 1 - 255 for each enchantment +enchant_limits: + minecraft:aqua_affinity: 1 + minecraft:binding_curse: 1 + minecraft:channeling: 1 + minecraft:flame: 1 + minecraft:infinity: 1 + minecraft:mending: 1 + minecraft:multishot: 1 + minecraft:silk_touch: 1 + minecraft:vanishing_curse: 1 + minecraft:depth_strider: 3 # anything more than 3 is treated as 3 by the game + minecraft:protection: 4 + minecraft:fire_protection: 4 + minecraft:blast_protection: 4 + minecraft:projectile_protection: 4 + minecraft:feather_falling: 4 + minecraft:thorns: 3 + minecraft:respiration: 3 + minecraft:sharpness: 5 + minecraft:smite: 5 + minecraft:bane_of_arthropods: 5 + minecraft:knockback: 2 + minecraft:fire_aspect: 2 + minecraft:looting: 3 + minecraft:sweeping: 3 + minecraft:sweeping_edge: 3 + minecraft:efficiency: 5 + minecraft:unbreaking: 3 + minecraft:fortune: 3 + minecraft:power: 5 + minecraft:punch: 2 + minecraft:luck_of_the_sea: 3 + minecraft:lure: 3 + minecraft:frost_walker: 2 + minecraft:impaling: 5 + minecraft:riptide: 3 + minecraft:loyalty: 3 + minecraft:piercing: 4 + minecraft:quick_charge: 3 + minecraft:soul_speed: 3 + minecraft:swift_sneak: 3 + minecraft:density: 5 + minecraft:breach: 4 + minecraft:wind_burst: 3 + minecraft:lunge: 3 + +# 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 +# +# If an enchantment is missing values here, or is less than 0, it will default to 0 +# +# Calculated as: [Enchantment lvl] * [multiplier] +# +# With default values protection 4 would have a value of 4 when +# coming from either a book (4 * 1) or an item (4 * 1) +enchant_values: + minecraft:aqua_affinity: + item: 4 + book: 2 + minecraft:bane_of_arthropods: + item: 2 + book: 1 + minecraft:binding_curse: + item: 8 + book: 4 + minecraft:blast_protection: + item: 4 + book: 2 + minecraft:channeling: + item: 8 + book: 4 + minecraft:depth_strider: + item: 4 + book: 2 + minecraft:efficiency: + item: 1 + book: 1 + minecraft:flame: + item: 4 + book: 2 + minecraft:feather_falling: + item: 2 + book: 1 + minecraft:fire_aspect: + item: 4 + book: 2 + minecraft:fire_protection: + item: 2 + book: 1 + minecraft:fortune: + item: 4 + book: 2 + minecraft:frost_walker: + item: 4 + book: 2 + minecraft:impaling: + item: 4 + book: 2 + minecraft:infinity: + item: 8 + book: 4 + minecraft:knockback: + item: 2 + book: 1 + minecraft:looting: + item: 4 + book: 2 + minecraft:loyalty: + item: 1 + book: 1 + minecraft:luck_of_the_sea: + item: 4 + book: 2 + minecraft:lure: + item: 4 + book: 2 + minecraft:mending: + item: 4 + book: 2 + minecraft:multishot: + item: 4 + book: 2 + minecraft:piercing: + item: 1 + book: 1 + minecraft:power: + item: 1 + book: 1 + minecraft:projectile_protection: + item: 2 + book: 1 + minecraft:protection: + item: 1 + book: 1 + minecraft:punch: + item: 4 + book: 2 + minecraft:quick_charge: + item: 2 + book: 1 + minecraft:respiration: + item: 4 + book: 2 + minecraft:riptide: + item: 4 + book: 2 + minecraft:silk_touch: + item: 8 + book: 4 + minecraft:sharpness: + item: 1 + book: 1 + minecraft:smite: + item: 2 + book: 1 + minecraft:soul_speed: + item: 8 + book: 4 + minecraft:swift_sneak: + item: 8 + book: 4 + minecraft:sweeping: + item: 4 + book: 2 + minecraft:sweeping_edge: + item: 4 + book: 2 + minecraft:thorns: + item: 8 + book: 4 + minecraft:unbreaking: + item: 2 + book: 1 + minecraft:vanishing_curse: + item: 8 + book: 4 + minecraft:density: + item: 2 + book: 1 + minecraft:breach: + item: 4 + book: 2 + minecraft:wind_burst: + item: 4 + book: 2 + minecraft:lunge: + item: 2 + book: 1 + +# Disable enchantment merging for level above the set value +# Enchantment merging is when, for example, 2 unbreaking II book combine to give sharpness III +# But Enchantment above this value can still be applied. following the previous example, we could still apply a unbreaking III book to a sword +# Even if disable-merge-over of unbreaking is set to 2 +# -1 mean enchantment merge for this enchantment is not disabled. default to -1 if absent. +disable-merge-over: + # Sharpness is set to -1. it equivalent to it not being set to anything (and work as vanilla) + minecraft:sharpness: -1 +# If uncommented. 2 unbreaking II book would not give an unbreaking III book. but unbreaking III book can still be applied +# minecraft:unbreaking: 2 + +# The maximum number of enchantment an item can get. -1 for infinity +# Use eco enchant enchant_limit if present by default unless "default" is not equal to -1 +enchantment_count_limit: + default: -1 + # Limit for specific items. example bellow is an example with stick + # Per item enchantment limit override eco enchant enchant_limit and default limit + items: + stick: -1 + +# Settings for lore modification +lore_edit: + book_and_quil: + # Permission is ca.lore_edit.book + use_permission: true + append: + # If adding lore using book & quil is enabled + enabled: false + # Cost used every time + fixed_cost: 1 + # Cost used for every lore line added + per_line_cost: 0 + # Use left item vanilla cost penalty if any + shared_increase: false + # Increase shared left item cost penalty + shared_additive: false + # If adding the lore consume the book & quil + do_consume: false + # Allow using color code and hexadecimal color when editing lore via book & quil + # + # Color code are prefixed by "&" and hexadecimal color by "#" + # Color code will not be applied if it colors nothing. "&&" can be used to write "&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + # + # Note that currently minimessage would disable hex code when adding color + allow_color_code: true + allow_hexadecimal_color: false + allow_minimessage: true + use_cost: 0 + + remove: + # If removing lore using book & quil is enabled + enabled: false + # Cost used every time + fixed_cost: 1 + # Cost used for every lore line removed + per_line_cost: 0 + # Use left item vanilla cost penalty if any + shared_increase: false + # Increase shared left item cost penalty + shared_additive: false + # If removing the lore consume the book & quil + do_consume: false + # Cost of replacing colors + remove_color_cost: 0 + # Allowed some color and tags to be reverted to plain text + # Custom anvil will prioritise format that result is a smaller resulting text + # Note that not allowing certain format will lead to some lost of color or tags. + # If configuration are exact as append appending this book should result in the exact same color + # + # Color code will be prefixed by "&" and hexadecimal color by "#". + # If color code is allowed, "&" in the text will get converted to "&&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + allow_color_code: true + allow_hexadecimal_color: false + allow_minimessage: true + + paper: + # Permission is ca.lore_edit.paper + use_permission: true + # what order should the lines should get added/removed (start/end, if invalid or not present will be end) + order: end + + append_line: + # If adding lore line using paper is enabled + enabled: false + # Cost used every time + fixed_cost: 1 + # Use left item vanilla cost penalty if any + shared_increase: false + # Increase shared left item cost penalty + shared_additive: false + # If adding the lore line consume the paper + do_consume: false + # Allow using color code and hexadecimal color when editing lore via book & quil + # + # Color code are prefixed by "&" and hexadecimal color by "#" + # Color code will not be applied if it colors nothing. "&&" can be used to write "&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + # + # Note that currently minimessage would disable hex code when adding color + allow_color_code: true + allow_hexadecimal_color: false + allow_minimessage: true + color_use_cost: 0 + use_cost: 0 + + remove_line: + # If removing lore line using paper is enabled + enabled: false + # Cost used every time + fixed_cost: 1 + # Use left item vanilla cost penalty if any + shared_increase: false + # Increase shared left item cost penalty + shared_additive: false + # If removing the lore line consume the paper + do_consume: false + # Cost of replacing colors + remove_color_cost: 0 + # Allowed some color and tags to be reverted to plain text + # Custom anvil will prioritise format that result is a smaller resulting text + # Note that not allowing certain format will lead to some lost of color or tags. + # If configuration are exact as append appending this paper should result in the exact same color + # + # Color code will be prefixed by "&" and hexadecimal color by "#". + # If color code is allowed, "&" in the text will get converted to "&&" + # For minimessage see minimessage formating https://docs.papermc.io/adventure/minimessage/format/ + allow_color_code: true + allow_hexadecimal_color: false + allow_minimessage: true + +# Whether to show debug logging +debug_log: false + +# Whether to show verbose debug logging +debug_log_verbose: false + +# In case something when wrong with CustomAnvil packet manager. +# If you see "missing class exception" or similar you may test this. +# If enabled and Protocolib absent or disabled "Replace to expensive" will not work. +# ProtocoLib may also be used if the server is in an "unsupported" version even if this option is disabled. +force_protocolib: false + +configVersion: 1.15.5 +lowMinecraftVersion: 1.21.11 diff --git a/defaultconfigs/1.21.11/custom_recipes.yml b/defaultconfigs/1.21.11/custom_recipes.yml new file mode 100644 index 0000000..57c2220 --- /dev/null +++ b/defaultconfigs/1.21.11/custom_recipes.yml @@ -0,0 +1,5 @@ +# ---------------------------------------------------- +# This config file is to store custom craft +# It is recommended to use the in game config editor for this configuration. +# /customanvilconfig With ca.config.edit permission +# ---------------------------------------------------- diff --git a/defaultconfigs/1.21.11/enchant_conflict.yml b/defaultconfigs/1.21.11/enchant_conflict.yml new file mode 100644 index 0000000..3d62dae --- /dev/null +++ b/defaultconfigs/1.21.11/enchant_conflict.yml @@ -0,0 +1,398 @@ +# +# It is recommended that you use /configanvil to edit theses config. +# You can still manually edit here if you like to. but if you do, don't forget to /anvilconfigreload after you changes ! +# + +# material conflicts +# +# If you want to edit this file: +# - A conflict will apply to every item except if in one of the notAffectedGroups group +# - the conflict will count only if the user try to combine at least as +# many conflicting enchantment as "maxEnchantmentBeforeConflict" +# +# +# ---------------------------------------------------- +# These restriction are about not allowing enchantment +# on illegal items +# ---------------------------------------------------- + +restriction_aqua_affinity: + enchantments: + - minecraft:aqua_affinity + notAffectedGroups: + - enchanted_book + - helmets + +restriction_bane_of_arthropods: + enchantments: + - minecraft:bane_of_arthropods + notAffectedGroups: + - enchanted_book + - melee_weapons + - mace + +restriction_blast_protection: + enchantments: + - minecraft:blast_protection + notAffectedGroups: + - enchanted_book + - armors + +restriction_channeling: + enchantments: + - minecraft:channeling + notAffectedGroups: + - enchanted_book + - trident + +restriction_binding_curse: + enchantments: + - minecraft:binding_curse + notAffectedGroups: + - enchanted_book + - wearable + +restriction_vanishing_curse: + enchantments: + - minecraft:vanishing_curse + notAffectedGroups: + - enchanted_book + - can_vanish + +restriction_depth_strider: + enchantments: + - minecraft:depth_strider + notAffectedGroups: + - enchanted_book + - boots + +restriction_efficiency: + enchantments: + - minecraft:efficiency + notAffectedGroups: + - enchanted_book + - tools + - shears + +restriction_feather_falling: + enchantments: + - minecraft:feather_falling + notAffectedGroups: + - enchanted_book + - boots + +restriction_fire_aspect: + enchantments: + - minecraft:fire_aspect + notAffectedGroups: + - enchanted_book + - swords + - mace + - spears + +restriction_fire_protection: + enchantments: + - minecraft:fire_protection + notAffectedGroups: + - enchanted_book + - armors + +restriction_flame: + enchantments: + - minecraft:flame + notAffectedGroups: + - enchanted_book + - bow + +restriction_fortune: + enchantments: + - minecraft:fortune + notAffectedGroups: + - enchanted_book + - tools + +restriction_frost_walker: + enchantments: + - minecraft:frost_walker + notAffectedGroups: + - enchanted_book + - boots + +restriction_impaling: + enchantments: + - minecraft:impaling + notAffectedGroups: + - enchanted_book + - trident + +restriction_infinity: + enchantments: + - minecraft:infinity + notAffectedGroups: + - enchanted_book + - bow + +restriction_knockback: + enchantments: + - minecraft:knockback + notAffectedGroups: + - enchanted_book + - swords + - spears + +restriction_looting: + enchantments: + - minecraft:looting + notAffectedGroups: + - enchanted_book + - swords + - spears + +restriction_loyalty: + enchantments: + - minecraft:loyalty + notAffectedGroups: + - enchanted_book + - trident + +restriction_luck_of_the_sea: + enchantments: + - minecraft:luck_of_the_sea + notAffectedGroups: + - enchanted_book + - fishing_rod + +restriction_lure: + enchantments: + - minecraft:lure + notAffectedGroups: + - enchanted_book + - fishing_rod + +restriction_mending: + enchantments: + - minecraft:mending + notAffectedGroups: + - enchanted_book + - can_unbreak + +restriction_minecraft_multishot: + enchantments: + - minecraft:multishot + notAffectedGroups: + - enchanted_book + - crossbow + +restriction_piercing: + enchantments: + - minecraft:piercing + notAffectedGroups: + - enchanted_book + - crossbow + +restriction_power: + enchantments: + - minecraft:power + notAffectedGroups: + - enchanted_book + - bow + +restriction_projectile_protection: + enchantments: + - minecraft:projectile_protection + notAffectedGroups: + - enchanted_book + - armors + +restriction_protection: + enchantments: + - minecraft:protection + notAffectedGroups: + - enchanted_book + - armors + +restriction_punch: + enchantments: + - minecraft:punch + notAffectedGroups: + - enchanted_book + - bow + +restriction_quick_charge: + enchantments: + - minecraft:quick_charge + notAffectedGroups: + - enchanted_book + - crossbow + +restriction_respiration: + enchantments: + - minecraft:respiration + notAffectedGroups: + - enchanted_book + - helmets + +restriction_riptide: + enchantments: + - minecraft:riptide + notAffectedGroups: + - enchanted_book + - trident + +restriction_sharpness: + enchantments: + - minecraft:sharpness + notAffectedGroups: + - enchanted_book + - melee_weapons + +restriction__silk_touch: + enchantments: + - minecraft:silk_touch + notAffectedGroups: + - enchanted_book + - tools + +restriction_smite: + enchantments: + - minecraft:smite + notAffectedGroups: + - enchanted_book + - melee_weapons + - mace + +restriction_soul_speed: + enchantments: + - minecraft:soul_speed + notAffectedGroups: + - enchanted_book + - boots + +restriction_sweeping_edge: + enchantments: + - minecraft:sweeping + - minecraft:sweeping_edge + notAffectedGroups: + - enchanted_book + - swords + +# Do not exist in 1.18, that mean useInFuture will be set to true +# useInFuture set to true also mean it will not warn if there is an issue +restriction_swift_sneak: + useInFuture: true + enchantments: + - minecraft:swift_sneak + notAffectedGroups: + - enchanted_book + - leggings + +restriction_thorns: + enchantments: + - minecraft:thorns + notAffectedGroups: + - enchanted_book + - armors + +restriction__unbreaking: + enchantments: + - minecraft:unbreaking + notAffectedGroups: + - enchanted_book + - can_unbreak + +# ---------------------------------------------------- +# Now we have conflicts about enchantment Incompatibility +# We just filtered what item enchantments can be applied +# notAffectedGroups is empty as we don't want anything to not respect theses rules +# maxEnchantmentBeforeConflict is set to 1 to only have 1 on those enchantment available +# ---------------------------------------------------- + +sword_enchant_conflict: + enchantments: + - minecraft:bane_of_arthropods + - minecraft:smite + - minecraft:sharpness + - minecraft:density + - minecraft:breach + notAffectedGroups: [] + maxEnchantmentBeforeConflict: 1 + +protection_enchant_conflict: + enchantments: + - minecraft:blast_protection + - minecraft:fire_protection + - minecraft:projectile_protection + - minecraft:protection + notAffectedGroups: [] + maxEnchantmentBeforeConflict: 1 + +trident_conflict1: + enchantments: + - minecraft:channeling + - minecraft:riptide + notAffectedGroups: [] + maxEnchantmentBeforeConflict: 1 + +trident_conflict2: + enchantments: + - minecraft:loyalty + - minecraft:riptide + notAffectedGroups: [] + maxEnchantmentBeforeConflict: 1 + +boot_conflict: + enchantments: + - minecraft:depth_strider + - minecraft:frost_walker + notAffectedGroups: [] + maxEnchantmentBeforeConflict: 1 + +tool_conflict: + enchantments: + - minecraft:fortune + - minecraft:silk_touch + notAffectedGroups: [] + maxEnchantmentBeforeConflict: 1 + +bow_conflict: + enchantments: + - minecraft:mending + - minecraft:infinity + notAffectedGroups: [] + maxEnchantmentBeforeConflict: 1 + +crossbow_conflict: + enchantments: + - minecraft:multishot + - minecraft:piercing + notAffectedGroups: [] + maxEnchantmentBeforeConflict: 1 +restriction_density: + enchantments: + - minecraft:density + notAffectedGroups: + - mace + - enchanted_book +restriction_breach: + enchantments: + - minecraft:breach + notAffectedGroups: + - mace + - enchanted_book +restriction_wind_burst: + enchantments: + - minecraft:wind_burst + notAffectedGroups: + - mace + - enchanted_book +restriction_lunge: + enchantments: + - minecraft:lunge + notAffectedGroups: + - spears + - enchanted_book + +# ---------------------------------------------------- +# Bellow is for custom conflicts. +# This is also where conflict create from the gui will be placed. +# ---------------------------------------------------- diff --git a/defaultconfigs/1.21.11/item_groups.yml b/defaultconfigs/1.21.11/item_groups.yml new file mode 100644 index 0000000..9f2a877 --- /dev/null +++ b/defaultconfigs/1.21.11/item_groups.yml @@ -0,0 +1,247 @@ +# +# It is recommended that you use /configanvil to edit theses config. +# You can still manually edit here if you like to. but if you do, don't forget to /anvilconfigreload after you changes ! +# + +# Please note this config use spigot material names. +# It should match minecraft name in most case, maybe every case, but I can't be sure +# In case there an issue with material name, you can found them here: +# https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Material.html + +# An empty Exclude group exclude nothing, so it contain everything +everything: + type: exclude + +# An empty include group will include nothing +nothing: + type: include + +# This group is an example of a group including only stone and polished granite +example_include: + type: include + items: + - stone + - polished_granite + +# This group contain everything except polished granite and elements of example_include +example_exclude: + type: exclude + items: + - polished_granite + groups: + - example_include + +# Default configuration should be vanilla enchantment conflict group +# there may have error, if you find one you can fix it ! +# https://minecraft.fandom.com/wiki/Enchanting + +swords: + type: include + items: + - wooden_sword + - stone_sword + - iron_sword + - diamond_sword + - golden_sword + - netherite_sword + - copper_sword + +axes: + type: include + items: + - wooden_axe + - stone_axe + - iron_axe + - diamond_axe + - golden_axe + - netherite_axe + - copper_axe + +melee_weapons: + type: include + groups: + - swords + - axes + - spears + +helmets: + type: include + items: + - leather_helmet + - chainmail_helmet + - iron_helmet + - diamond_helmet + - golden_helmet + - netherite_helmet + - turtle_helmet + - copper_helmet + +chestplate: + type: include + items: + - leather_chestplate + - chainmail_chestplate + - iron_chestplate + - diamond_chestplate + - golden_chestplate + - netherite_chestplate + - copper_chestplate + +leggings: + type: include + items: + - leather_leggings + - chainmail_leggings + - iron_leggings + - diamond_leggings + - golden_leggings + - netherite_leggings + - copper_leggings + +boots: + type: include + items: + - leather_boots + - chainmail_boots + - iron_boots + - diamond_boots + - golden_boots + - netherite_boots + - copper_boots + +armors: + type: include + groups: + - helmets + - chestplate + - leggings + - boots + +wearable: + type: include + items: + - elytra + - carved_pumpkin + - skeleton_skull + - wither_skeleton_skull + - zombie_head + - player_head + - creeper_head + - dragon_head + - piglin_head + groups: + - armors + +pickaxes: + type: include + items: + - wooden_pickaxe + - stone_pickaxe + - iron_pickaxe + - diamond_pickaxe + - golden_pickaxe + - netherite_pickaxe + - copper_pickaxe + +shovels: + type: include + items: + - wooden_shovel + - stone_shovel + - iron_shovel + - diamond_shovel + - golden_shovel + - netherite_shovel + - copper_shovel + +hoes: + type: include + items: + - wooden_hoe + - stone_hoe + - iron_hoe + - diamond_hoe + - golden_hoe + - netherite_hoe + - copper_hoe + +tools: + type: include + groups: + - pickaxes + - shovels + - hoes + - axes + +enchanted_book: + type: include + items: + - enchanted_book + +trident: + type: include + items: + - trident + +bow: + type: include + items: + - bow + +crossbow: + type: include + items: + - crossbow + +fishing_rod: + type: include + items: + - fishing_rod + +shears: + type: include + items: + - shears + +can_unbreak: + type: include + items: + - elytra + - flint_and_steel + - shield + - carrot_on_a_stick + - warped_fungus_on_a_stick + - brush + groups: + - melee_weapons + - tools + - armors + - trident + - bow + - crossbow + - fishing_rod + - shears + - mace + +can_vanish: + type: include + items: + - compass + groups: + - wearable + - can_unbreak +mace: + type: include + items: + - mace +spears: + type: include + items: + - wooden_spear + - golden_spear + - stone_spear + - copper_spear + - iron_spear + - diamond_spear + - netherite_spear + diff --git a/defaultconfigs/1.21.11/unit_repair_item.yml b/defaultconfigs/1.21.11/unit_repair_item.yml new file mode 100644 index 0000000..ed981b1 --- /dev/null +++ b/defaultconfigs/1.21.11/unit_repair_item.yml @@ -0,0 +1,220 @@ +# +# It is recommended that you use /configanvil to edit theses config. +# You can still manually edit here if you like to. but if you do, don't forget to /anvilconfigreload after you changes ! +# + +# Unit repair configuration +# +# This configuration is to make custom unit repair +# A unit repair is, for example, a diamond to repair a diamond sword +# In vanilla, a unit repair 25% of object durability +# you can make a custom value here +# +# Item name should NOT combine caps and no caps (example: Stone) + +# Default value if the config is an invalid value (value <= 0 ) +# If value > 1 it will be treated as being = 1 +default_repair_amount: 0.25 + +# You can add custom unit repair +# The example bellow make a shield repaired by 10% by sticks + +# stick: +# shield: 0.10 + + +# Vanilla unit repair group is bellow +diamond: + diamond_helmet: 0.25 + diamond_chestplate: 0.25 + diamond_leggings: 0.25 + diamond_boots: 0.25 + diamond_sword: 0.25 + diamond_pickaxe: 0.25 + diamond_axe: 0.25 + diamond_shovel: 0.25 + diamond_hoe: 0.25 + diamond_spear: 0.25 + +netherite_ingot: + netherite_helmet: 0.25 + netherite_chestplate: 0.25 + netherite_leggings: 0.25 + netherite_boots: 0.25 + netherite_sword: 0.25 + netherite_pickaxe: 0.25 + netherite_axe: 0.25 + netherite_shovel: 0.25 + netherite_hoe: 0.25 + netherite_spear: 0.25 + +gold_ingot: + golden_helmet: 0.25 + golden_chestplate: 0.25 + golden_leggings: 0.25 + golden_boots: 0.25 + golden_sword: 0.25 + golden_pickaxe: 0.25 + golden_axe: 0.25 + golden_shovel: 0.25 + golden_hoe: 0.25 + golden_spear: 0.25 + +iron_ingot: + iron_helmet: 0.25 + iron_chestplate: 0.25 + iron_leggings: 0.25 + iron_boots: 0.25 + iron_sword: 0.25 + iron_pickaxe: 0.25 + iron_axe: 0.25 + iron_shovel: 0.25 + iron_hoe: 0.25 + iron_spear: 0.25 + +cobblestone: + stone_sword: 0.25 + stone_pickaxe: 0.25 + stone_axe: 0.25 + stone_shovel: 0.25 + stone_hoe: 0.25 + stone_spear: 0.25 + +cobbled_deepslate: + stone_sword: 0.25 + stone_pickaxe: 0.25 + stone_axe: 0.25 + stone_shovel: 0.25 + stone_hoe: 0.25 + stone_spear: 0.25 + +blackstone: + stone_sword: 0.25 + stone_pickaxe: 0.25 + stone_axe: 0.25 + stone_shovel: 0.25 + stone_hoe: 0.25 + +leather: + leather_helmet: 0.25 + leather_chestplate: 0.25 + leather_leggings: 0.25 + leather_boots: 0.25 + +phantom_membrane: + elytra: 0.25 + +scute: + turtle_helmet: 0.25 + +oak_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + wooden_spear: 0.25 + +spruce_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + wooden_spear: 0.25 + +birch_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + wooden_spear: 0.25 + +jungle_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + wooden_spear: 0.25 + +acacia_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + wooden_spear: 0.25 + +dark_oak_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + wooden_spear: 0.25 + +mangrove_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + wooden_spear: 0.25 + +cherry_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + wooden_spear: 0.25 + +bamboo_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + wooden_spear: 0.25 + +crimson_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + wooden_spear: 0.25 + +warped_planks: + wooden_sword: 0.25 + wooden_pickaxe: 0.25 + wooden_axe: 0.25 + wooden_shovel: 0.25 + wooden_hoe: 0.25 + shield: 0.25 + wooden_spear: 0.25 +breeze_rod: + mace: 0.25 +copper_ingot: + copper_helmet: 0.25 + copper_chestplate: 0.25 + copper_leggings: 0.25 + copper_boots: 0.25 + copper_pickaxe: 0.25 + copper_shovel: 0.25 + copper_hoe: 0.25 + copper_axe: 0.25 + copper_sword: 0.25 + copper_spear: 0.25 diff --git a/defaultconfigs/README.md b/defaultconfigs/README.md index 72c88ca..a413b66 100644 --- a/defaultconfigs/README.md +++ b/defaultconfigs/README.md @@ -1,3 +1,5 @@ ### Default Plugin's Configurations -For 1.18 to 1.20.6 use [1.18 configurations](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.18) \ -For 1.21 use [1.21 configurations](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21) \ No newline at end of file +From 1.18 to 1.20.6 use [1.18 configurations](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.18) \ +From 1.21 to 1.21.8 use [1.21 configurations](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21) +From 1.21.9 to 1.21.10 use [1.21.9 configurations](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21.9) +From 1.21.11 use [1.21.11 configurations](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21.11) \ No newline at end of file From 9e7a1a963d946e88d8b0a1a69beb8d150e64e737 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Sat, 6 Dec 2025 04:11:41 +0100 Subject: [PATCH 033/207] fix default rename text issue --- build.gradle.kts | 2 +- .../xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 901b89a..8ae931c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,7 +18,7 @@ plugins { } group = "xyz.alexcrea" -version = "1.15.5" +version = "1.15.6" val effectiveVersion = "$version" + (if (System.getenv("SMALL_COMMIT_HASH") != null) "-dev-${System.getenv("SMALL_COMMIT_HASH")!!}" else "") diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt index 17d5d0d..c47d828 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt @@ -213,7 +213,8 @@ class PrepareAnvilListener : Listener { else if (useColor) it.displayName else ChatColor.stripColor(it.displayName) - if (!displayName.contentEquals(renameText)) { + + if (!displayName.contentEquals(renameText) && !(displayName == null && renameText == "")) { it.setDisplayName(renameText) resultItem.itemMeta = it From c166d2a78a6cef8b8ac8789ee8f3f65cd7364ec9 Mon Sep 17 00:00:00 2001 From: alexcrea <42614139+alexcrea@users.noreply.github.com> Date: Sun, 7 Dec 2025 15:33:39 +0100 Subject: [PATCH 034/207] start of nms generalization --- .../cuanvil/dependency/gui/ExternGuiTester.kt | 50 ++-------------- .../gui/version/v1_17R1_ExternGuiTester.kt | 16 ----- .../gui/version/v1_18R1_ExternGuiTester.kt | 16 ----- .../gui/version/v1_18R2_ExternGuiTester.kt | 16 ----- .../gui/version/v1_19R1_ExternGuiTester.kt | 16 ----- .../gui/version/v1_19R2_ExternGuiTester.kt | 16 ----- .../gui/version/v1_19R3_ExternGuiTester.kt | 16 ----- .../gui/version/v1_20R1_ExternGuiTester.kt | 16 ----- .../gui/version/v1_20R2_ExternGuiTester.kt | 17 ------ .../gui/version/v1_20R3_ExternGuiTester.kt | 17 ------ .../gui/version/v1_20R4_ExternGuiTester.kt | 17 ------ .../gui/version/v1_21R1_ExternGuiTester.kt | 17 ------ .../gui/version/v1_21R2_ExternGuiTester.kt | 34 ----------- .../gui/version/v1_21R3_ExternGuiTester.kt | 17 ------ .../gui/version/v1_21R4_ExternGuiTester.kt | 34 ----------- .../gui/version/v1_21R5_ExternGuiTester.kt | 34 ----------- .../gui/version/v1_21R6_ExternGuiTester.kt | 34 ----------- .../dependency/gui/GenericExternGuiTester.kt | 58 +++++++++++++++++++ .../dependency/gui/GuiTesterSelector.kt | 43 +------------- 19 files changed, 63 insertions(+), 421 deletions(-) delete mode 100644 nms/v1_17R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_17R1_ExternGuiTester.kt delete mode 100644 nms/v1_18R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_18R1_ExternGuiTester.kt delete mode 100644 nms/v1_18R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_18R2_ExternGuiTester.kt delete mode 100644 nms/v1_19R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_19R1_ExternGuiTester.kt delete mode 100644 nms/v1_19R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_19R2_ExternGuiTester.kt delete mode 100644 nms/v1_19R3/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_19R3_ExternGuiTester.kt delete mode 100644 nms/v1_20R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_20R1_ExternGuiTester.kt delete mode 100644 nms/v1_20R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_20R2_ExternGuiTester.kt delete mode 100644 nms/v1_20R3/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_20R3_ExternGuiTester.kt delete mode 100644 nms/v1_20R4/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_20R4_ExternGuiTester.kt delete mode 100644 nms/v1_21R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R1_ExternGuiTester.kt delete mode 100644 nms/v1_21R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R2_ExternGuiTester.kt delete mode 100644 nms/v1_21R3/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R3_ExternGuiTester.kt delete mode 100644 nms/v1_21R4/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R4_ExternGuiTester.kt delete mode 100644 nms/v1_21R5/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R5_ExternGuiTester.kt delete mode 100644 nms/v1_21R6/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R6_ExternGuiTester.kt create mode 100644 src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GenericExternGuiTester.kt diff --git a/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/ExternGuiTester.kt b/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/ExternGuiTester.kt index 3079b8a..029e4a7 100644 --- a/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/ExternGuiTester.kt +++ b/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/ExternGuiTester.kt @@ -5,55 +5,13 @@ import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil interface ExternGuiTester { - object Const{ - val cannonicalPaperAnvilMenu = "net.minecraft.world.inventory.AnvilMenu" - } - - val wesjdAnvilGuiName: String? - - fun getContainerClass(inventory: InventoryView): Class? + fun getContainerClass(view: InventoryView): Class? fun testIfGui(inventory: InventoryView): Boolean { - // this mean we are on test - //TODO review why needed knowing previous mitigations should works - if(inventory.javaClass.name.endsWith("AnvilViewMock")) return false + // container class only allow default bukkit craft view class - val clazz = getContainerClass(inventory) ?: return false - - val clazzName = clazz.name - if(!PlatformUtil.isPaper){ - // Blacklist gui causing issue - if (expectWesjd(clazzName)) return true - if (expectXenondevUI(clazzName)) return true - if (expectVanePortal(clazzName)) return true - - return false - } - - // Only allow cannonical anvil menu class - return !Const.cannonicalPaperAnvilMenu.equals(clazzName, true) - } - - fun expectWesjd(name: String): Boolean { - val expectedWesjdGuiPath = "anvilgui.version.$wesjdAnvilGuiName" - - return name.contains(expectedWesjdGuiPath) - } - - private val XenondevUIPrefix: String - get() = "xyz.xenondevs.inventoryaccess." - private val XenondevUISufix: String - get() = ".AnvilInventoryImpl" - - fun expectXenondevUI(name: String): Boolean { - return name.startsWith(XenondevUIPrefix) - && name.endsWith(XenondevUISufix) - } - - fun expectVanePortal(name: String): Boolean { - val expected = "org.oddlama.vane.core.menu.AnvilMenu\$AnvilContainer" - - return name == expected + val clazz = getContainerClass(inventory) + return clazz != null } } \ No newline at end of file diff --git a/nms/v1_17R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_17R1_ExternGuiTester.kt b/nms/v1_17R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_17R1_ExternGuiTester.kt deleted file mode 100644 index 8e352e0..0000000 --- a/nms/v1_17R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_17R1_ExternGuiTester.kt +++ /dev/null @@ -1,16 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui.version - -import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftInventoryView -import org.bukkit.inventory.InventoryView -import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester - -class v1_17R1_ExternGuiTester: ExternGuiTester { - override val wesjdAnvilGuiName = "Wrapper1_17_R1" - - override fun getContainerClass(view: InventoryView): Class? { - if (view !is CraftInventoryView) return null - val container = view.handle - - return container.javaClass - } -} \ No newline at end of file diff --git a/nms/v1_18R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_18R1_ExternGuiTester.kt b/nms/v1_18R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_18R1_ExternGuiTester.kt deleted file mode 100644 index 659a0f6..0000000 --- a/nms/v1_18R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_18R1_ExternGuiTester.kt +++ /dev/null @@ -1,16 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui.version - -import org.bukkit.craftbukkit.v1_18_R1.inventory.CraftInventoryView -import org.bukkit.inventory.InventoryView -import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester - -class v1_18R1_ExternGuiTester: ExternGuiTester { - override val wesjdAnvilGuiName = "Wrapper1_18_R1" - - override fun getContainerClass(view: InventoryView): Class? { - if (view !is CraftInventoryView) return null - val container = view.handle - - return container.javaClass - } -} \ No newline at end of file diff --git a/nms/v1_18R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_18R2_ExternGuiTester.kt b/nms/v1_18R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_18R2_ExternGuiTester.kt deleted file mode 100644 index 1447716..0000000 --- a/nms/v1_18R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_18R2_ExternGuiTester.kt +++ /dev/null @@ -1,16 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui.version - -import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftInventoryView -import org.bukkit.inventory.InventoryView -import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester - -class v1_18R2_ExternGuiTester: ExternGuiTester { - override val wesjdAnvilGuiName = "Wrapper1_18_R2" - - override fun getContainerClass(view: InventoryView): Class? { - if (view !is CraftInventoryView) return null - val container = view.handle - - return container.javaClass - } -} \ No newline at end of file diff --git a/nms/v1_19R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_19R1_ExternGuiTester.kt b/nms/v1_19R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_19R1_ExternGuiTester.kt deleted file mode 100644 index c151924..0000000 --- a/nms/v1_19R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_19R1_ExternGuiTester.kt +++ /dev/null @@ -1,16 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui.version - -import org.bukkit.craftbukkit.v1_19_R1.inventory.CraftInventoryView -import org.bukkit.inventory.InventoryView -import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester - -class v1_19R1_ExternGuiTester: ExternGuiTester { - override val wesjdAnvilGuiName = "Wrapper1_19_R1" - - override fun getContainerClass(view: InventoryView): Class? { - if (view !is CraftInventoryView) return null - val container = view.handle - - return container.javaClass - } -} \ No newline at end of file diff --git a/nms/v1_19R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_19R2_ExternGuiTester.kt b/nms/v1_19R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_19R2_ExternGuiTester.kt deleted file mode 100644 index ac46674..0000000 --- a/nms/v1_19R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_19R2_ExternGuiTester.kt +++ /dev/null @@ -1,16 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui.version - -import org.bukkit.craftbukkit.v1_19_R2.inventory.CraftInventoryView -import org.bukkit.inventory.InventoryView -import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester - -class v1_19R2_ExternGuiTester: ExternGuiTester { - override val wesjdAnvilGuiName = "Wrapper1_19_R2" - - override fun getContainerClass(view: InventoryView): Class? { - if (view !is CraftInventoryView) return null - val container = view.handle - - return container.javaClass - } -} \ No newline at end of file diff --git a/nms/v1_19R3/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_19R3_ExternGuiTester.kt b/nms/v1_19R3/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_19R3_ExternGuiTester.kt deleted file mode 100644 index 7ce5abd..0000000 --- a/nms/v1_19R3/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_19R3_ExternGuiTester.kt +++ /dev/null @@ -1,16 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui.version - -import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftInventoryView -import org.bukkit.inventory.InventoryView -import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester - -class v1_19R3_ExternGuiTester: ExternGuiTester { - override val wesjdAnvilGuiName = "Wrapper1_19_R3" - - override fun getContainerClass(view: InventoryView): Class? { - if (view !is CraftInventoryView) return null - val container = view.handle - - return container.javaClass - } -} \ No newline at end of file diff --git a/nms/v1_20R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_20R1_ExternGuiTester.kt b/nms/v1_20R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_20R1_ExternGuiTester.kt deleted file mode 100644 index dae3b98..0000000 --- a/nms/v1_20R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_20R1_ExternGuiTester.kt +++ /dev/null @@ -1,16 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui.version - -import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventoryView -import org.bukkit.inventory.InventoryView -import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester - -class v1_20R1_ExternGuiTester: ExternGuiTester { - override val wesjdAnvilGuiName = "Wrapper1_20_R1" - - override fun getContainerClass(view: InventoryView): Class? { - if (view !is CraftInventoryView) return null - val container = view.handle - - return container.javaClass - } -} diff --git a/nms/v1_20R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_20R2_ExternGuiTester.kt b/nms/v1_20R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_20R2_ExternGuiTester.kt deleted file mode 100644 index 6a8358a..0000000 --- a/nms/v1_20R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_20R2_ExternGuiTester.kt +++ /dev/null @@ -1,17 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui.version - -import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftInventoryView -import org.bukkit.inventory.InventoryView -import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester -import kotlin.jvm.javaClass - -class v1_20R2_ExternGuiTester: ExternGuiTester { - override val wesjdAnvilGuiName = "Wrapper1_20_R2" - - override fun getContainerClass(view: InventoryView): Class? { - if (view !is CraftInventoryView) return null - val container = view.handle - - return container.javaClass - } -} diff --git a/nms/v1_20R3/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_20R3_ExternGuiTester.kt b/nms/v1_20R3/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_20R3_ExternGuiTester.kt deleted file mode 100644 index 80362e2..0000000 --- a/nms/v1_20R3/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_20R3_ExternGuiTester.kt +++ /dev/null @@ -1,17 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui.version - -import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftInventoryView -import org.bukkit.inventory.InventoryView -import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester -import kotlin.jvm.javaClass - -class v1_20R3_ExternGuiTester: ExternGuiTester { - override val wesjdAnvilGuiName = "Wrapper1_20_R3" - - override fun getContainerClass(view: InventoryView): Class? { - if (view !is CraftInventoryView) return null - val container = view.handle - - return container.javaClass - } -} diff --git a/nms/v1_20R4/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_20R4_ExternGuiTester.kt b/nms/v1_20R4/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_20R4_ExternGuiTester.kt deleted file mode 100644 index 16e867c..0000000 --- a/nms/v1_20R4/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_20R4_ExternGuiTester.kt +++ /dev/null @@ -1,17 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui.version - -import org.bukkit.craftbukkit.inventory.CraftInventoryView -import org.bukkit.inventory.InventoryView -import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester -import kotlin.jvm.javaClass - -class v1_20R4_ExternGuiTester: ExternGuiTester { - override val wesjdAnvilGuiName = "Wrapper1_20_R4" - - override fun getContainerClass(view: InventoryView): Class? { - if (view !is CraftInventoryView) return null - val container = view.handle - - return container.javaClass - } -} diff --git a/nms/v1_21R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R1_ExternGuiTester.kt b/nms/v1_21R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R1_ExternGuiTester.kt deleted file mode 100644 index 3111735..0000000 --- a/nms/v1_21R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R1_ExternGuiTester.kt +++ /dev/null @@ -1,17 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui.version - -import org.bukkit.craftbukkit.inventory.CraftInventoryView -import org.bukkit.inventory.InventoryView -import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester - -class v1_21R1_ExternGuiTester: ExternGuiTester { - override val wesjdAnvilGuiName = "Wrapper1_21_R1" - - override fun getContainerClass(view: InventoryView): Class? { - if(view !is CraftInventoryView<*, *>) return null - val container = view.handle - - return container.javaClass - } - -} diff --git a/nms/v1_21R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R2_ExternGuiTester.kt b/nms/v1_21R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R2_ExternGuiTester.kt deleted file mode 100644 index 2604a16..0000000 --- a/nms/v1_21R2/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R2_ExternGuiTester.kt +++ /dev/null @@ -1,34 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui.version - -import org.bukkit.craftbukkit.inventory.CraftInventoryView -import org.bukkit.inventory.InventoryView -import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester - -class v1_21R2_ExternGuiTester: ExternGuiTester { - override val wesjdAnvilGuiName = "Wrapper1_21_R2" - - var tested = false; - var possible = false; - - override fun getContainerClass(view: InventoryView): Class? { - // In case we are in a test environment - if(!tested) testClassExist() - if(!possible) return null - - if(view !is CraftInventoryView<*, *>) return null - val container = view.handle - - return container.javaClass - } - - fun testClassExist(){ - tested = true; - try { - Class.forName("org.bukkit.craftbukkit.inventory.CraftInventoryView") - possible = true - } catch (e: ClassNotFoundException){ - possible = false - } - } - -} diff --git a/nms/v1_21R3/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R3_ExternGuiTester.kt b/nms/v1_21R3/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R3_ExternGuiTester.kt deleted file mode 100644 index 70c5df9..0000000 --- a/nms/v1_21R3/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R3_ExternGuiTester.kt +++ /dev/null @@ -1,17 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui.version - -import org.bukkit.craftbukkit.inventory.CraftInventoryView -import org.bukkit.inventory.InventoryView -import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester - -class v1_21R3_ExternGuiTester: ExternGuiTester { - override val wesjdAnvilGuiName = "Wrapper1_21_R3" - - override fun getContainerClass(view: InventoryView): Class? { - if(view !is CraftInventoryView<*, *>) return null - val container = view.handle - - return container.javaClass - } - -} diff --git a/nms/v1_21R4/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R4_ExternGuiTester.kt b/nms/v1_21R4/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R4_ExternGuiTester.kt deleted file mode 100644 index 6b76451..0000000 --- a/nms/v1_21R4/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R4_ExternGuiTester.kt +++ /dev/null @@ -1,34 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui.version - -import org.bukkit.craftbukkit.inventory.CraftInventoryView -import org.bukkit.inventory.InventoryView -import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester - -class v1_21R4_ExternGuiTester: ExternGuiTester { - override val wesjdAnvilGuiName = "Wrapper1_21_R4" - - var tested = false; - var possible = false; - - override fun getContainerClass(view: InventoryView): Class? { - // In case we are in a test environment - if(!tested) testClassExist() - if(!possible) return null - - if(view !is CraftInventoryView<*, *>) return null - val container = view.handle - - return container.javaClass - } - - fun testClassExist(){ - tested = true; - try { - Class.forName("org.bukkit.craftbukkit.inventory.CraftInventoryView") - possible = true - } catch (e: ClassNotFoundException){ - possible = false - } - } - -} diff --git a/nms/v1_21R5/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R5_ExternGuiTester.kt b/nms/v1_21R5/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R5_ExternGuiTester.kt deleted file mode 100644 index 59eadbc..0000000 --- a/nms/v1_21R5/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R5_ExternGuiTester.kt +++ /dev/null @@ -1,34 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui.version - -import org.bukkit.craftbukkit.inventory.CraftInventoryView -import org.bukkit.inventory.InventoryView -import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester - -class v1_21R5_ExternGuiTester: ExternGuiTester { - override val wesjdAnvilGuiName = "Wrapper1_21_R5" - - var tested = false; - var possible = false; - - override fun getContainerClass(view: InventoryView): Class? { - // In case we are in a test environment - if(!tested) testClassExist() - if(!possible) return null - - if(view !is CraftInventoryView<*, *>) return null - val container = view.handle - - return container.javaClass - } - - fun testClassExist(){ - tested = true; - try { - Class.forName("org.bukkit.craftbukkit.inventory.CraftInventoryView") - possible = true - } catch (e: ClassNotFoundException){ - possible = false - } - } - -} diff --git a/nms/v1_21R6/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R6_ExternGuiTester.kt b/nms/v1_21R6/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R6_ExternGuiTester.kt deleted file mode 100644 index 4e4c32b..0000000 --- a/nms/v1_21R6/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R6_ExternGuiTester.kt +++ /dev/null @@ -1,34 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui.version - -import org.bukkit.craftbukkit.inventory.CraftInventoryView -import org.bukkit.inventory.InventoryView -import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester - -class v1_21R6_ExternGuiTester: ExternGuiTester { - override val wesjdAnvilGuiName = "Wrapper1_21_R6" - - var tested = false; - var possible = false; - - override fun getContainerClass(view: InventoryView): Class? { - // In case we are in a test environment - if(!tested) testClassExist() - if(!possible) return null - - if(view !is CraftInventoryView<*, *>) return null - val container = view.handle - - return container.javaClass - } - - fun testClassExist(){ - tested = true; - try { - Class.forName("org.bukkit.craftbukkit.inventory.CraftInventoryView") - possible = true - } catch (e: ClassNotFoundException){ - possible = false - } - } - -} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GenericExternGuiTester.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GenericExternGuiTester.kt new file mode 100644 index 0000000..85e32d5 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GenericExternGuiTester.kt @@ -0,0 +1,58 @@ +package xyz.alexcrea.cuanvil.dependency.gui + +import org.bukkit.inventory.InventoryView +import xyz.alexcrea.cuanvil.dependency.MinecraftVersionUtil +import java.lang.reflect.Method + +class GenericExternGuiTester: ExternGuiTester { + + companion object { + private const val ANVIL_CLASS_NAME = "org.bukkit.craftbukkit.inventory.view.CraftAnvilView" + private const val INV_CLASS_NAME = "org.bukkit.craftbukkit.inventory.CraftInventoryView" + private const val HANDLE_METHOD_NAME = "getHandle" + } + + var tested = false + + var testedClass: String? = null + lateinit var getHandleMethod: Method + + override fun getContainerClass(view: InventoryView): Class? { + // In case we are in a test environment + if(!tested) testClassExist() + + if(!testedClass.contentEquals(view.javaClass.name)) + return null + + val container = getHandleMethod.invoke(view) + return container.javaClass + } + + fun tryFromClass(className: String) { + val clazz = Class.forName(className) + testedClass = className + + getHandleMethod = clazz.getMethod(HANDLE_METHOD_NAME) + } + + fun testClassExist() { + tested = true + + // We first try to get craft anvil interface, + // but is absent on old version so we try craft inventory view before + try { + tryFromClass(ANVIL_CLASS_NAME) + return + } + catch (_: ClassNotFoundException) {} + catch (_: NoSuchMethodException) {} + + try { + tryFromClass(INV_CLASS_NAME) + return + } + catch (_: ClassNotFoundException) {} + catch (_: NoSuchMethodException) {} + } + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt index f3d2122..f64a7f1 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt @@ -1,6 +1,5 @@ package xyz.alexcrea.cuanvil.dependency.gui -import xyz.alexcrea.cuanvil.dependency.gui.version.*; import xyz.alexcrea.cuanvil.update.UpdateUtils object GuiTesterSelector { @@ -10,47 +9,7 @@ object GuiTesterSelector { val versionParts = UpdateUtils.currentMinecraftVersionArray() if (versionParts[0] != 1) return null - return when (versionParts[1]) { - // Can't support 1.16.5 bc 1.16.5 paper userdev do not exist - - 17 -> when (versionParts[2]) { - 0, 1 -> v1_17R1_ExternGuiTester() - else -> null - } - - 18 -> when (versionParts[2]) { - 0, 1 -> v1_18R1_ExternGuiTester() - 2 -> v1_18R2_ExternGuiTester() - else -> null - } - - 19 -> when (versionParts[2]) { - 0, 1, 2 -> v1_19R1_ExternGuiTester() - 3 -> v1_19R2_ExternGuiTester() - 4 -> v1_19R3_ExternGuiTester() - else -> null - } - - 20 -> when (versionParts[2]) { - 0, 1 -> v1_20R1_ExternGuiTester() - 2 -> v1_20R2_ExternGuiTester() - 3, 4 -> v1_20R3_ExternGuiTester() - 5, 6 -> v1_20R4_ExternGuiTester() - else -> null - } - - 21 -> when (versionParts[2]) { - 0, 1 -> v1_21R1_ExternGuiTester() - 2, 3 -> v1_21R2_ExternGuiTester() - 4 -> v1_21R3_ExternGuiTester() - 5 -> v1_21R4_ExternGuiTester() - 6, 7, 8 -> v1_21R5_ExternGuiTester() - 9, 10 -> v1_21R6_ExternGuiTester() - else -> null - } - - else -> null - } + return GenericExternGuiTester() } } \ No newline at end of file From ee4936ecf51d91ea02df4702eee32a6d7133677a Mon Sep 17 00:00:00 2001 From: alexcrea Date: Thu, 25 Dec 2025 21:55:48 +0100 Subject: [PATCH 035/207] reobf for spigot --- build.gradle.kts | 4 ++-- nms/v1_21R7/build.gradle.kts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 57b8b93..b209202 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,7 +18,7 @@ plugins { } group = "xyz.alexcrea" -version = "1.15.6" +version = "1.15.7" val effectiveVersion = "$version" + (if (System.getenv("SMALL_COMMIT_HASH") != null) "-dev-${System.getenv("SMALL_COMMIT_HASH")!!}" else "") @@ -89,7 +89,7 @@ dependencies { implementation(project(":nms:v1_21R4", configuration = "reobf")) implementation(project(":nms:v1_21R5", configuration = "reobf")) implementation(project(":nms:v1_21R6", configuration = "reobf")) - implementation(project(":nms:v1_21R7"))// TODO reobf on release, configuration = "reobf")) + implementation(project(":nms:v1_21R7", configuration = "reobf")) // include kotlin for the offline jar implementation(kotlin("stdlib")) diff --git a/nms/v1_21R7/build.gradle.kts b/nms/v1_21R7/build.gradle.kts index 9c8a6f3..6b5a242 100644 --- a/nms/v1_21R7/build.gradle.kts +++ b/nms/v1_21R7/build.gradle.kts @@ -11,7 +11,7 @@ dependencies { implementation(project(":nms:nms-common")) // Used for nms - paperweight.paperDevBundle("1.21.11-pre3-R0.1-SNAPSHOT") //TODO update to 1.21.11-R0.1-SNAPSHOT on release + paperweight.paperDevBundle("1.21.11-R0.1-SNAPSHOT") } repositories { From fc94dbe16980f08f690128f5ee97ddcaf0465bb1 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Thu, 25 Dec 2025 22:33:37 +0100 Subject: [PATCH 036/207] Generic gui tester and generic paper nms --- build.gradle.kts | 1 + nms/nms-paper/.gitignore | 1 + nms/nms-paper/build.gradle.kts | 35 +++++++++++ .../dependency/datapack/DataPackTester.kt | 0 .../packet/versions/PaperPacketManager.kt | 33 +++++++++++ .../dependency/scheduler/FoliaScheduler.kt | 0 ...PacketManager.kt => PaperPacketManager.kt} | 0 .../gui/version/v1_21R7_ExternGuiTester.kt | 34 ----------- settings.gradle.kts | 2 + .../dependency/MinecraftVersionUtil.kt | 54 +++++++++++++++++ .../packet/PacketManagerSelector.kt | 58 +++++-------------- .../dependency/scheduler/BukkitScheduler.kt | 4 +- 12 files changed, 144 insertions(+), 78 deletions(-) create mode 100644 nms/nms-paper/.gitignore create mode 100644 nms/nms-paper/build.gradle.kts rename nms/{v1_20R1 => nms-paper}/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/datapack/DataPackTester.kt (100%) create mode 100644 nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/PaperPacketManager.kt rename nms/{v1_20R3 => nms-paper}/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/scheduler/FoliaScheduler.kt (100%) rename nms/v1_21R6/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/{V1_21R6_PacketManager.kt => PaperPacketManager.kt} (100%) delete mode 100644 nms/v1_21R7/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R7_ExternGuiTester.kt create mode 100644 src/main/kotlin/xyz/alexcrea/cuanvil/dependency/MinecraftVersionUtil.kt diff --git a/build.gradle.kts b/build.gradle.kts index b209202..944175b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -73,6 +73,7 @@ dependencies { // Include nms implementation(project(":nms:nms-common")) + implementation(project(":nms:nms-paper")) implementation(project(":nms:v1_17R1", configuration = "reobf")) implementation(project(":nms:v1_18R1", configuration = "reobf")) implementation(project(":nms:v1_18R2", configuration = "reobf")) diff --git a/nms/nms-paper/.gitignore b/nms/nms-paper/.gitignore new file mode 100644 index 0000000..47374f1 --- /dev/null +++ b/nms/nms-paper/.gitignore @@ -0,0 +1 @@ +.lastDeploymentsId \ No newline at end of file diff --git a/nms/nms-paper/build.gradle.kts b/nms/nms-paper/build.gradle.kts new file mode 100644 index 0000000..3b98361 --- /dev/null +++ b/nms/nms-paper/build.gradle.kts @@ -0,0 +1,35 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +group = rootProject.group +version = rootProject.version + +plugins { + id("io.papermc.paperweight.userdev") +} + +dependencies { + implementation(project(":nms:nms-common")) + + // Used for nms + paperweight.paperDevBundle("1.20.6-R0.1-SNAPSHOT") +} + +repositories { + maven("https://repo.papermc.io/repository/maven-public/") + +} + +// Set target version +tasks.withType().configureEach { + sourceCompatibility = "18" + targetCompatibility = "18" + + options.encoding = "UTF-8" +} + +kotlin { + compilerOptions { + apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) + jvmTarget.set(JvmTarget.JVM_18) + } +} diff --git a/nms/v1_20R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/datapack/DataPackTester.kt b/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/datapack/DataPackTester.kt similarity index 100% rename from nms/v1_20R1/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/datapack/DataPackTester.kt rename to nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/datapack/DataPackTester.kt diff --git a/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/PaperPacketManager.kt b/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/PaperPacketManager.kt new file mode 100644 index 0000000..7e248e1 --- /dev/null +++ b/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/PaperPacketManager.kt @@ -0,0 +1,33 @@ +package xyz.alexcrea.cuanvil.dependency.packet.versions + +import net.minecraft.network.protocol.game.ClientboundPlayerAbilitiesPacket +import net.minecraft.world.entity.player.Abilities +import org.bukkit.craftbukkit.entity.CraftPlayer +import org.bukkit.entity.Player +import xyz.alexcrea.cuanvil.dependency.packet.PacketManager +import xyz.alexcrea.cuanvil.dependency.packet.PacketManagerBase + +class PaperPacketManager : PacketManagerBase(), PacketManager { + override val canSetInstantBuild: Boolean + get() = true + + override fun setInstantBuild(player: Player, instantBuild: Boolean) { + val nmsPlayer = (player as CraftPlayer).handle + val playerAbilities = nmsPlayer.abilities + val sendedAbilities: Abilities + if (playerAbilities.instabuild == instantBuild) { + sendedAbilities = playerAbilities + } else { + sendedAbilities = Abilities() + sendedAbilities.invulnerable = playerAbilities.invulnerable + sendedAbilities.flying = playerAbilities.flying + sendedAbilities.mayfly = playerAbilities.mayfly + sendedAbilities.instabuild = instantBuild + sendedAbilities.mayBuild = playerAbilities.mayBuild + sendedAbilities.flyingSpeed = playerAbilities.flyingSpeed + sendedAbilities.walkingSpeed = playerAbilities.walkingSpeed + } + val packet = ClientboundPlayerAbilitiesPacket(sendedAbilities) + nmsPlayer.connection.send(packet) + } +} diff --git a/nms/v1_20R3/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/scheduler/FoliaScheduler.kt b/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/scheduler/FoliaScheduler.kt similarity index 100% rename from nms/v1_20R3/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/scheduler/FoliaScheduler.kt rename to nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/scheduler/FoliaScheduler.kt diff --git a/nms/v1_21R6/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_21R6_PacketManager.kt b/nms/v1_21R6/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/PaperPacketManager.kt similarity index 100% rename from nms/v1_21R6/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/V1_21R6_PacketManager.kt rename to nms/v1_21R6/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/PaperPacketManager.kt diff --git a/nms/v1_21R7/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R7_ExternGuiTester.kt b/nms/v1_21R7/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R7_ExternGuiTester.kt deleted file mode 100644 index c380211..0000000 --- a/nms/v1_21R7/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/version/v1_21R7_ExternGuiTester.kt +++ /dev/null @@ -1,34 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui.version - -import org.bukkit.craftbukkit.inventory.CraftInventoryView -import org.bukkit.inventory.InventoryView -import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester - -class v1_21R7_ExternGuiTester: ExternGuiTester { - override val wesjdAnvilGuiName = "Wrapper1_21_R7" - - var tested = false; - var possible = false; - - override fun getContainerClass(view: InventoryView): Class? { - // In case we are in a test environment - if(!tested) testClassExist() - if(!possible) return null - - if(view !is CraftInventoryView<*, *>) return null - val container = view.handle - - return container.javaClass - } - - fun testClassExist(){ - tested = true; - try { - Class.forName("org.bukkit.craftbukkit.inventory.CraftInventoryView") - possible = true - } catch (e: ClassNotFoundException){ - possible = false - } - } - -} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 0790502..85566be 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,6 +3,8 @@ rootProject.name = "CustomAnvil" // NMS subproject include("nms:nms-common") findProject(":nms:nms-common")?.name = "nms-common" +include("nms:nms-paper") +findProject(":nms:nms-paper")?.name = "nms-paper" include("nms:v1_17R1") findProject(":nms:v1_17R1")?.name = "v1_17R1" include("nms:v1_18R1") diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/MinecraftVersionUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/MinecraftVersionUtil.kt new file mode 100644 index 0000000..69ec546 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/MinecraftVersionUtil.kt @@ -0,0 +1,54 @@ +package xyz.alexcrea.cuanvil.dependency + +import xyz.alexcrea.cuanvil.update.UpdateUtils + +object MinecraftVersionUtil { + + val craftbukkitVersion: String? + get() { + val versionParts = UpdateUtils.currentMinecraftVersionArray() + if (versionParts[0] != 1) return null + + return when (versionParts[1]) { + 17 -> when (versionParts[2]) { + 0, 1 -> "1_17R1" + else -> null + } + + 18 -> when (versionParts[2]) { + 0, 1 -> "1_18R1" + 2 -> "1_18R2" + else -> null + } + + 19 -> when (versionParts[2]) { + 0, 1, 2 -> "1_19R1" + 3 -> "1_19R2" + 4 -> "1_19R3" + else -> null + } + + 20 -> when (versionParts[2]) { + 0, 1 -> "1_20R1" + 2 -> "1_20R2" + 3, 4 -> "1_20R3" + 5, 6 -> "1_20R4" + else -> null + } + + 21 -> when (versionParts[2]) { + 0, 1 -> "1_21R1" + 2, 3 -> "1_21R2" + 4 -> "1_21R3" + 5 -> "1_21R4" + 6, 7, 8 -> "1_21R5" + 9, 10 -> "1_21R6" + 11 -> "1_21R7" + else -> null + } + + else -> null + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt index ccb8620..f38b9e4 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt @@ -1,7 +1,10 @@ package xyz.alexcrea.cuanvil.dependency.packet import org.bukkit.Bukkit +import su.nightexpress.nightcore.bridge.paper.PaperBridge +import xyz.alexcrea.cuanvil.dependency.MinecraftVersionUtil import xyz.alexcrea.cuanvil.dependency.packet.versions.* +import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil import xyz.alexcrea.cuanvil.update.UpdateUtils object PacketManagerSelector { @@ -10,7 +13,9 @@ object PacketManagerSelector { return if (forceProtocolib) protocolibIfPresent else - versionSpecificManager ?: protocolibIfPresent + reobfPacketManager ?: + if(PlatformUtil.isPaper) PaperPacketManager() + else protocolibIfPresent } private val protocolibIfPresent: PacketManager @@ -19,52 +24,21 @@ object PacketManagerSelector { ProtocoLibWrapper() else NoPacketManager() - private val versionSpecificManager: PacketManagerBase? + + // Reobfuscated packet manager for spigot or paper as it remap + private val reobfPacketManager: PacketManagerBase? get() { val versionParts = UpdateUtils.currentMinecraftVersionArray() if (versionParts[0] != 1) return null - return when (versionParts[1]) { - // Can't support 1.16.5 bc 1.16.5 paper userdev do not exist + try { + val clazz = Class.forName("xyz.alexcrea.cuanvil.dependency.packet.versions." + + "V${MinecraftVersionUtil.craftbukkitVersion}_PacketManager") - 17 -> when (versionParts[2]) { - 0, 1 -> V1_17R1_PacketManager() - else -> null - } - - 18 -> when (versionParts[2]) { - 0, 1 -> V1_18R1_PacketManager() - 2 -> V1_18R2_PacketManager() - else -> null - } - - 19 -> when (versionParts[2]) { - 0, 1, 2 -> V1_19R1_PacketManager() - 3 -> V1_19R2_PacketManager() - 4 -> V1_19R3_PacketManager() - else -> null - } - - 20 -> when (versionParts[2]) { - 0, 1 -> V1_20R1_PacketManager() - 2 -> V1_20R2_PacketManager() - 3, 4 -> V1_20R3_PacketManager() - 5, 6 -> V1_20R4_PacketManager() - else -> null - } - - 21 -> when (versionParts[2]) { - 0, 1 -> V1_21R1_PacketManager() - 2, 3 -> V1_21R2_PacketManager() - 4 -> V1_21R3_PacketManager() - 5 -> V1_21R4_PacketManager() - 6, 7, 8 -> V1_21R5_PacketManager() - 9, 10 -> V1_21R6_PacketManager() - 11 -> V1_21R7_PacketManager() - else -> null - } - - else -> null + val manager = clazz.getConstructor().newInstance() + return manager as PacketManagerBase + } catch (e: ClassNotFoundException) { + return null } } } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/scheduler/BukkitScheduler.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/scheduler/BukkitScheduler.kt index b26dd42..8c04162 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/scheduler/BukkitScheduler.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/scheduler/BukkitScheduler.kt @@ -6,12 +6,12 @@ import org.bukkit.plugin.Plugin class BukkitScheduler : TaskScheduler { - override fun scheduleGlobally(plugin: Plugin, task: Runnable, time: Long): Any? { + override fun scheduleGlobally(plugin: Plugin, task: Runnable, time: Long): Any { return Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, task, time) } - override fun scheduleOnEntity(plugin: Plugin, entity: Entity, task: Runnable, time: Long): Any? { + override fun scheduleOnEntity(plugin: Plugin, entity: Entity, task: Runnable, time: Long): Any { return Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, task, time) } } From 87c99716266255cd155f78f5baa6edfe0ab9c4cb Mon Sep 17 00:00:00 2001 From: alexcrea Date: Thu, 25 Dec 2025 23:49:40 +0100 Subject: [PATCH 037/207] fix spigot and refactor some gradlew things --- build.gradle.kts | 38 ++++++++++++-------------------------- gradle.properties | 5 ++++- settings.gradle.kts | 44 ++++++++++---------------------------------- 3 files changed, 26 insertions(+), 61 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index b209202..b6ad03f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -31,6 +31,9 @@ repositories { maven(url = "https://repo.nightexpressdev.com/releases") } +val reobfNMS = providers.gradleProperty("subprojects.reobfnms") + .get().split(",") + dependencies { // Spigot api compileOnly("org.spigotmc:spigot-api:1.18-R0.1-SNAPSHOT") @@ -73,23 +76,9 @@ dependencies { // Include nms implementation(project(":nms:nms-common")) - implementation(project(":nms:v1_17R1", configuration = "reobf")) - implementation(project(":nms:v1_18R1", configuration = "reobf")) - implementation(project(":nms:v1_18R2", configuration = "reobf")) - implementation(project(":nms:v1_19R1", configuration = "reobf")) - implementation(project(":nms:v1_19R2", configuration = "reobf")) - implementation(project(":nms:v1_19R3", configuration = "reobf")) - implementation(project(":nms:v1_20R1", configuration = "reobf")) - implementation(project(":nms:v1_20R2", configuration = "reobf")) - implementation(project(":nms:v1_20R3", configuration = "reobf")) - implementation(project(":nms:v1_20R4", configuration = "reobf")) - implementation(project(":nms:v1_21R1", configuration = "reobf")) - implementation(project(":nms:v1_21R2", configuration = "reobf")) - implementation(project(":nms:v1_21R3", configuration = "reobf")) - implementation(project(":nms:v1_21R4", configuration = "reobf")) - implementation(project(":nms:v1_21R5", configuration = "reobf")) - implementation(project(":nms:v1_21R6", configuration = "reobf")) - implementation(project(":nms:v1_21R7", configuration = "reobf")) + for (nmsPart in reobfNMS) { + implementation(project(":nms:$nmsPart", configuration = "reobf")) + } // include kotlin for the offline jar implementation(kotlin("stdlib")) @@ -169,7 +158,8 @@ tasks { filesMatching("plugin.yml") { expand( "version" to effectiveVersion, - "libraries" to " \"org.jetbrains.kotlin:kotlin-stdlib:2.1.0\" " + "libraries" to " \"org.jetbrains.kotlin:kotlin-stdlib:2.1.0\" " + + ", \"net.kyori:adventure-platform-bukkit:4.4.1\"" ) } @@ -259,13 +249,9 @@ object Meta { const val snapshot = "https://s01.oss.sonatype.org/content/repositories/snapshots/" } -val disalowedDependency = setOf( - "nms-common", "kotlin-stdlib", - "v1_17R1", - "v1_18R1", "v1_18R2", "v1_19R1", "v1_19R2", "v1_19R3", - "v1_20R1", "v1_20R2", "v1_20R3", "v1_20R4", - "v1_21R1", "v1_21R2", "v1_21R3", "v1_21R4", "v1_21R5" -) +val disallowedDependency = HashSet() +disallowedDependency.addAll(reobfNMS) +disallowedDependency.addAll(listOf("nms-common", "nms-paper", "kotlin-stdlib")) publishing { repositories { @@ -333,7 +319,7 @@ publishing { val artifactNode = ((child as Node).get("artifactId") as NodeList)[0] as Node val artifactID = artifactNode.value() as String - if(disalowedDependency.contains(artifactID)) { + if(disallowedDependency.contains(artifactID)) { toRemove.add(child) } } diff --git a/gradle.properties b/gradle.properties index a1f0681..95311bf 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,4 +3,7 @@ kotlin.code.style=official # Signing signing.secretKeyRingFile=~/.gnupg/secring.gpg -kotlin.daemon.jvmargs=-Xmx8G \ No newline at end of file +kotlin.daemon.jvmargs=-Xmx8G + +# list of nms +subprojects.reobfnms=v1_17R1,v1_18R1,v1_18R2,v1_19R1,v1_19R2,v1_19R3,v1_20R1,v1_20R2,v1_20R3,v1_20R4,v1_21R1,v1_21R2,v1_21R3,v1_21R4,v1_21R5,v1_21R6,v1_21R7 \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 0790502..bf069c3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,41 +3,17 @@ rootProject.name = "CustomAnvil" // NMS subproject include("nms:nms-common") findProject(":nms:nms-common")?.name = "nms-common" -include("nms:v1_17R1") -findProject(":nms:v1_17R1")?.name = "v1_17R1" -include("nms:v1_18R1") -findProject(":nms:v1_18R1")?.name = "v1_18R1" -include("nms:v1_18R2") -findProject(":nms:v1_18R2")?.name = "v1_18R2" -include("nms:v1_19R1") -findProject(":nms:v1_19R1")?.name = "v1_19R1" -include("nms:v1_19R2") -findProject(":nms:v1_19R2")?.name = "v1_19R2" -include("nms:v1_19R3") -findProject(":nms:v1_19R3")?.name = "v1_19R3" -include("nms:v1_20R1") -findProject(":nms:v1_20R1")?.name = "v1_20R1" -include("nms:v1_20R2") -findProject(":nms:v1_20R2")?.name = "v1_20R2" -include("nms:v1_20R3") -findProject(":nms:v1_20R3")?.name = "v1_20R3" -include("nms:v1_20R4") -findProject(":nms:v1_20R4")?.name = "v1_20R4" -include("nms:v1_21R1") -findProject(":nms:v1_21R1")?.name = "v1_21R1" -include("nms:v1_21R2") -findProject(":nms:v1_21R2")?.name = "v1_21R2" -include("nms:v1_21R3") -findProject(":nms:v1_21R3")?.name = "v1_21R3" -include("nms:v1_21R4") -findProject(":nms:v1_21R4")?.name = "v1_21R4" -include("nms:v1_21R5") -findProject(":nms:v1_21R5")?.name = "v1_21R5" -include("nms:v1_21R6") -findProject(":nms:v1_21R6")?.name = "v1_21R6" -include("nms:v1_21R7") -findProject(":nms:v1_21R7")?.name = "v1_21R7" + +val reobfNMS = providers.gradleProperty("subprojects.reobfnms") + .get().split(",") + +for (nmsPart in reobfNMS) { + include("nms:$nmsPart") + findProject(":nms:$nmsPart")?.name = nmsPart +} + +// compatibility subprojects include(":impl:LegacyEcoEnchant") findProject(":impl:LegacyEcoEnchant")?.name = "LegacyEcoEnchant" include("impl:ExcellentEnchant5_3") From 1544cd315b7f15759f40460221a06f744d494020 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Fri, 26 Dec 2025 00:51:14 +0100 Subject: [PATCH 038/207] fix older version issue --- nms/nms-common/build.gradle.kts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nms/nms-common/build.gradle.kts b/nms/nms-common/build.gradle.kts index 6088e77..67936b9 100644 --- a/nms/nms-common/build.gradle.kts +++ b/nms/nms-common/build.gradle.kts @@ -21,8 +21,8 @@ repositories { // Set target version tasks.withType().configureEach { - sourceCompatibility = "21" - targetCompatibility = "21" + sourceCompatibility = "16" + targetCompatibility = "16" options.encoding = "UTF-8" } @@ -30,6 +30,6 @@ tasks.withType().configureEach { kotlin { compilerOptions { apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) - jvmTarget.set(JvmTarget.JVM_21) + jvmTarget.set(JvmTarget.JVM_16) } } From 59d3c9a85cd1b9ab5ba5cf364b0aaab24d6190f3 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Fri, 26 Dec 2025 01:04:41 +0100 Subject: [PATCH 039/207] update doc --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3f5a806..dbe2f00 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,10 @@ as low priority as I work for the plugin on my free time for free. One of the configurations allow displaying price about 40 and removing Too Expensive. \ By how the minecraft client work: price above 40 can only be displayed green, even if the player does not own enough experience level. -Minecraft version 1.17 to 1.21.7 do not need any dependency. Other version need ProtocoLib enabled on your server for this feature. \ +Minecraft version 1.17 to latest marked as supported do not need any dependency. \ +Any recent paper version also are supported for this feature. +But you should wait for update for new version containing new enchantable item or new enchantments. +Other version need ProtocoLib enabled on your server for this feature. \ You can also wait for an update of the plugin to support a newer version. Please note that 1.16.5 to 1.17.1 are not officially supported. Run at your own risk. From 89eec84a6693e7a848fe88f4293f167cdde38b46 Mon Sep 17 00:00:00 2001 From: alexcrea <42614139+alexcrea@users.noreply.github.com> Date: Fri, 26 Dec 2025 01:34:47 +0100 Subject: [PATCH 040/207] update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dbe2f00..bf119e9 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ as low priority as I work for the plugin on my free time for free. One of the configurations allow displaying price about 40 and removing Too Expensive. \ By how the minecraft client work: price above 40 can only be displayed green, even if the player does not own enough experience level. -Minecraft version 1.17 to latest marked as supported do not need any dependency. \ +Minecraft version 1.18 to latest marked as supported do not need any ProtocoLib dependency. \ Any recent paper version also are supported for this feature. But you should wait for update for new version containing new enchantable item or new enchantments. Other version need ProtocoLib enabled on your server for this feature. \ From be7f4d0bcb488db7c1e0dfc79109e5f4fbe78eb0 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 29 Dec 2025 15:20:28 +0100 Subject: [PATCH 041/207] fix big issue extern gui wrongly tested --- build.gradle.kts | 4 ++-- .../cuanvil/dependency/gui/ExternGuiTester.kt | 5 ++--- .../cuanvil/dependency/gui/GenericExternGuiTester.kt | 11 +++++++---- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index bec4d26..3affa9e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ plugins { kotlin("jvm") version "2.1.0" java id("org.jetbrains.dokka").version("1.9.20") - id("com.gradleup.shadow").version("9.0.0-beta16") + id("com.gradleup.shadow").version("9.3.0") // Maven publish `maven-publish` signing @@ -18,7 +18,7 @@ plugins { } group = "xyz.alexcrea" -version = "1.15.7" +version = "1.15.8" val effectiveVersion = "$version" + (if (System.getenv("SMALL_COMMIT_HASH") != null) "-dev-${System.getenv("SMALL_COMMIT_HASH")!!}" else "") diff --git a/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/ExternGuiTester.kt b/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/ExternGuiTester.kt index 029e4a7..8b06b10 100644 --- a/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/ExternGuiTester.kt +++ b/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/ExternGuiTester.kt @@ -1,17 +1,16 @@ package xyz.alexcrea.cuanvil.dependency.gui import org.bukkit.inventory.InventoryView -import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil interface ExternGuiTester { fun getContainerClass(view: InventoryView): Class? fun testIfGui(inventory: InventoryView): Boolean { - // container class only allow default bukkit craft view class + // container class only allow default bukkit craft view or test class val clazz = getContainerClass(inventory) - return clazz != null + return clazz == null } } \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GenericExternGuiTester.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GenericExternGuiTester.kt index 85e32d5..4ff3354 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GenericExternGuiTester.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GenericExternGuiTester.kt @@ -1,7 +1,6 @@ package xyz.alexcrea.cuanvil.dependency.gui import org.bukkit.inventory.InventoryView -import xyz.alexcrea.cuanvil.dependency.MinecraftVersionUtil import java.lang.reflect.Method class GenericExternGuiTester: ExternGuiTester { @@ -12,14 +11,16 @@ class GenericExternGuiTester: ExternGuiTester { private const val HANDLE_METHOD_NAME = "getHandle" } - var tested = false + var testExist = false + var inTesting = false var testedClass: String? = null lateinit var getHandleMethod: Method override fun getContainerClass(view: InventoryView): Class? { // In case we are in a test environment - if(!tested) testClassExist() + if(!testExist) testClassExist() + if(inTesting) return view.javaClass //TEMPORARY if(!testedClass.contentEquals(view.javaClass.name)) return null @@ -36,7 +37,7 @@ class GenericExternGuiTester: ExternGuiTester { } fun testClassExist() { - tested = true + testExist = true // We first try to get craft anvil interface, // but is absent on old version so we try craft inventory view before @@ -53,6 +54,8 @@ class GenericExternGuiTester: ExternGuiTester { } catch (_: ClassNotFoundException) {} catch (_: NoSuchMethodException) {} + + inTesting = true } } \ No newline at end of file From a6cee2d59121eb1fa7e7158390be2a2a43086eb2 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 30 Dec 2025 02:03:58 +0100 Subject: [PATCH 042/207] check air --- .../cuanvil/listener/PrepareAnvilListener.kt | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt index c47d828..04323e8 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt @@ -40,6 +40,10 @@ class PrepareAnvilListener : Listener { const val ANVIL_OUTPUT_SLOT = 2 } + private fun ItemStack?.isAir(): Boolean { + return this == null || this.isEmpty + } + /** * Event handler logic for when an anvil contains items to be combined */ @@ -79,10 +83,11 @@ class PrepareAnvilListener : Listener { if (testCustomRecipe(event, inventory, player, first, second)) return // Test rename lonely item - if (second == null) { + if (second.isAir()) { doRenaming(event, inventory, player, first) return } + second as ItemStack // not air we know it's not null // Test for merge if (first.canMergeWith(second)) { @@ -102,7 +107,7 @@ class PrepareAnvilListener : Listener { } private fun isImmutable(item: ItemStack?): Boolean { - if (item == null) return false + if (item.isAir()) return false val meta = item.itemMeta return meta != null && @@ -153,7 +158,7 @@ class PrepareAnvilListener : Listener { if (finalResult == null) return false event.result = finalResult.result - if (finalResult.result == null) return false + if (finalResult.result.isAir()) return false AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, finalResult.levelCost, true) return true @@ -179,7 +184,7 @@ class PrepareAnvilListener : Listener { if (finalResult == null) return event.result = finalResult.result - if (finalResult.result == null) return + if (finalResult.result.isAir()) return AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, finalResult.levelCost) } @@ -267,7 +272,7 @@ class PrepareAnvilListener : Listener { if (finalResult == null) return event.result = finalResult.result - if (finalResult.result == null) return + if (finalResult.result.isAir()) return AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, finalResult.levelCost) } @@ -312,7 +317,7 @@ class PrepareAnvilListener : Listener { if (finalResult == null) return false event.result = finalResult.result - if (finalResult.result == null) return false + if (finalResult.result.isAir()) return false AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, finalResult.levelCost) return true @@ -332,7 +337,7 @@ class PrepareAnvilListener : Listener { result = AnvilLoreEditUtil.tryLoreEditByPaper(player, first, second, xpCost) } - if (result == null || first == result) { + if (result.isAir() || first == result) { CustomAnvil.log("lore edit, But input is same as output") event.result = null return false From 161ef6ba912262805aecce1a45cee318be8400e3 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 30 Dec 2025 02:18:11 +0100 Subject: [PATCH 043/207] fix forgot --- .../xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt index 04323e8..ebf16ed 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt @@ -41,7 +41,7 @@ class PrepareAnvilListener : Listener { } private fun ItemStack?.isAir(): Boolean { - return this == null || this.isEmpty + return this == null || this.type.isAir || this.amount == 0 } /** @@ -87,10 +87,9 @@ class PrepareAnvilListener : Listener { doRenaming(event, inventory, player, first) return } - second as ItemStack // not air we know it's not null // Test for merge - if (first.canMergeWith(second)) { + if (first.canMergeWith(second!!)) { doMerge(event, inventory, player, first, second) return } @@ -109,7 +108,7 @@ class PrepareAnvilListener : Listener { private fun isImmutable(item: ItemStack?): Boolean { if (item.isAir()) return false - val meta = item.itemMeta + val meta = item!!.itemMeta return meta != null && (hasImmutableEnchants(meta) || hasImmutableStoredEnchants(meta)) } From fe2196626ad55eddddc000123149a9800fd8fe7d Mon Sep 17 00:00:00 2001 From: alexcrea Date: Thu, 1 Jan 2026 17:15:49 +0100 Subject: [PATCH 044/207] bring back old gui tester --- .../cuanvil/dependency/gui/ExternGuiTester.kt | 16 ----- .../cuanvil/dependency/DependencyManager.kt | 4 +- .../dependency/gui/GenericExternGuiTester.kt | 58 +++++++++++++++++-- .../dependency/gui/GuiTesterSelector.kt | 2 +- 4 files changed, 55 insertions(+), 25 deletions(-) delete mode 100644 nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/ExternGuiTester.kt diff --git a/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/ExternGuiTester.kt b/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/ExternGuiTester.kt deleted file mode 100644 index 8b06b10..0000000 --- a/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/ExternGuiTester.kt +++ /dev/null @@ -1,16 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui - -import org.bukkit.inventory.InventoryView - -interface ExternGuiTester { - - fun getContainerClass(view: InventoryView): Class? - - fun testIfGui(inventory: InventoryView): Boolean { - // container class only allow default bukkit craft view or test class - - val clazz = getContainerClass(inventory) - return clazz == null - } - -} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt index 66a9aa5..9c8c159 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt @@ -16,7 +16,7 @@ import xyz.alexcrea.cuanvil.api.event.listener.CAPreAnvilBypassEvent import xyz.alexcrea.cuanvil.api.event.listener.CATreatAnvilResultEvent import xyz.alexcrea.cuanvil.config.ConfigHolder import xyz.alexcrea.cuanvil.dependency.datapack.DataPackDependency -import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester +import xyz.alexcrea.cuanvil.dependency.gui.GenericExternGuiTester import xyz.alexcrea.cuanvil.dependency.gui.GuiTesterSelector import xyz.alexcrea.cuanvil.dependency.packet.PacketManager import xyz.alexcrea.cuanvil.dependency.packet.PacketManagerSelector @@ -34,7 +34,7 @@ object DependencyManager { lateinit var scheduler: TaskScheduler lateinit var packetManager: PacketManager - var externGuiTester: ExternGuiTester? = null + var externGuiTester: GenericExternGuiTester? = null var enchantmentSquaredCompatibility: EnchantmentSquaredDependency? = null var ecoEnchantCompatibility: EcoEnchantDependency? = null diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GenericExternGuiTester.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GenericExternGuiTester.kt index 4ff3354..0e430ef 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GenericExternGuiTester.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GenericExternGuiTester.kt @@ -1,14 +1,18 @@ package xyz.alexcrea.cuanvil.dependency.gui import org.bukkit.inventory.InventoryView +import xyz.alexcrea.cuanvil.dependency.MinecraftVersionUtil +import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil import java.lang.reflect.Method -class GenericExternGuiTester: ExternGuiTester { +class GenericExternGuiTester { companion object { private const val ANVIL_CLASS_NAME = "org.bukkit.craftbukkit.inventory.view.CraftAnvilView" private const val INV_CLASS_NAME = "org.bukkit.craftbukkit.inventory.CraftInventoryView" private const val HANDLE_METHOD_NAME = "getHandle" + + private const val CANONICAL_PAPER_ANVIL_MENU = "net.minecraft.world.inventory.AnvilMenu" } var testExist = false @@ -17,11 +21,7 @@ class GenericExternGuiTester: ExternGuiTester { var testedClass: String? = null lateinit var getHandleMethod: Method - override fun getContainerClass(view: InventoryView): Class? { - // In case we are in a test environment - if(!testExist) testClassExist() - if(inTesting) return view.javaClass //TEMPORARY - + private fun getContainerClass(view: InventoryView): Class? { if(!testedClass.contentEquals(view.javaClass.name)) return null @@ -58,4 +58,50 @@ class GenericExternGuiTester: ExternGuiTester { inTesting = true } + // Try if were in another plugin anvil inventory + fun testIfGui(inventory: InventoryView): Boolean { + // In case we are in a test environment + if(!testExist) testClassExist() + if(inTesting) return false + + val clazz = getContainerClass(inventory) ?: return false + + val clazzName = clazz.name + if(!PlatformUtil.isPaper){ + // Blacklist gui causing issue + if (expectWesjd(clazzName)) return true + if (expectXenondevUI(clazzName)) return true + if (expectVanePortal(clazzName)) return true + + return false + } + + // Only allow cannonical anvil menu class + return !CANONICAL_PAPER_ANVIL_MENU.equals(clazzName, true) + } + + // Known custom implementations + fun expectWesjd(name: String): Boolean { + val expectedWesjdGuiPath = "anvilgui.version.Wrapper${MinecraftVersionUtil.craftbukkitVersion}" + + return name.contains(expectedWesjdGuiPath) + } + + private val XenondevUIPrefix: String + get() = "xyz.xenondevs.inventoryaccess." + private val XenondevUISufix: String + get() = ".AnvilInventoryImpl" + + fun expectXenondevUI(name: String): Boolean { + return name.startsWith(XenondevUIPrefix) + && name.endsWith(XenondevUISufix) + } + + fun expectVanePortal(name: String): Boolean { + val expected = "org.oddlama.vane.core.menu.AnvilMenu\$AnvilContainer" + + return name == expected + } + + } \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt index f64a7f1..e445d8d 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt @@ -4,7 +4,7 @@ import xyz.alexcrea.cuanvil.update.UpdateUtils object GuiTesterSelector { - val selectGuiTester: ExternGuiTester? + val selectGuiTester: GenericExternGuiTester? get() { val versionParts = UpdateUtils.currentMinecraftVersionArray() if (versionParts[0] != 1) return null From a350b7fa698f54a667535f724463c547600f788e Mon Sep 17 00:00:00 2001 From: alexcrea Date: Thu, 1 Jan 2026 18:59:48 +0100 Subject: [PATCH 045/207] finally ! smaller jar is smaller --- build.gradle.kts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 3affa9e..843753f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -148,9 +148,10 @@ tasks { val name = "${rootProject.name}-${effectiveVersion}.jar" archiveFileName.set(name) - // Exclude kotlin std and its annotation - exclude("**/kotlin-stdlib*.jar") - exclude("**/annotations*.jar") + // Exclude kotlin std, annotations and adventure api + exclude("*kotlin/**") + exclude("**/annotations/**") + exclude("net/kyori/**") // Shadow necessary dependency relocate("com.github.stefvanschie.inventoryframework", "xyz.alexcrea.inventoryframework") From a373cd76f760803c3208c7c19d5796ace86c8bbf Mon Sep 17 00:00:00 2001 From: alexcrea Date: Thu, 1 Jan 2026 19:01:13 +0100 Subject: [PATCH 046/207] has to add adventure as libary for spigot sadly --- build.gradle.kts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 843753f..25aec32 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -160,8 +160,10 @@ tasks { filesMatching("plugin.yml") { expand( "version" to effectiveVersion, - "libraries" to " \"org.jetbrains.kotlin:kotlin-stdlib:2.1.0\" " + - ", \"net.kyori:adventure-platform-bukkit:4.4.1\"" + "libraries" to " \"org.jetbrains.kotlin:kotlin-stdlib:2.1.0\"" + + ", \"net.kyori:adventure-text-minimessage:4.25.0\"" + + ", \"net.kyori:adventure-text-serializer-plain:4.25.0\"" + + ", \"net.kyori:adventure-text-serializer-legacy:4.25.0\"" ) } From 474ad0f1b2245d7b575160a454c0dcd95ca8ab89 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Thu, 1 Jan 2026 19:01:56 +0100 Subject: [PATCH 047/207] version up --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 25aec32..0412138 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,7 +18,7 @@ plugins { } group = "xyz.alexcrea" -version = "1.15.8" +version = "1.15.9" val effectiveVersion = "$version" + (if (System.getenv("SMALL_COMMIT_HASH") != null) "-dev-${System.getenv("SMALL_COMMIT_HASH")!!}" else "") From 4ed9de3d3c22060a9e41b47d813d01e9755c971a Mon Sep 17 00:00:00 2001 From: alexcrea Date: Sat, 10 Jan 2026 19:09:03 +0100 Subject: [PATCH 048/207] FINALLY offline build SHOULD work --- build.gradle.kts | 67 ++++++++++++++++++------------------------------ 1 file changed, 25 insertions(+), 42 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 0412138..f005e45 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -140,30 +140,21 @@ allprojects { } + tasks { - // Online jar (use of libraries) - shadowJar { - // No suffix for this jar - val name = "${rootProject.name}-${effectiveVersion}.jar" + fun ShadowJar.configureBaseShadow(suffix: String, libraries: Array) { + val processedSuffix = if(suffix.isEmpty()) "" else "-$suffix" + val name = "${rootProject.name}-${effectiveVersion}${processedSuffix}.jar" archiveFileName.set(name) - // Exclude kotlin std, annotations and adventure api - exclude("*kotlin/**") - exclude("**/annotations/**") - exclude("net/kyori/**") - // Shadow necessary dependency relocate("com.github.stefvanschie.inventoryframework", "xyz.alexcrea.inventoryframework") - // Replace version and example fields in plugin.yml filesMatching("plugin.yml") { expand( - "version" to effectiveVersion, - "libraries" to " \"org.jetbrains.kotlin:kotlin-stdlib:2.1.0\"" + - ", \"net.kyori:adventure-text-minimessage:4.25.0\"" + - ", \"net.kyori:adventure-text-serializer-plain:4.25.0\"" + - ", \"net.kyori:adventure-text-serializer-legacy:4.25.0\"" + "version" to effectiveVersion + processedSuffix, + "libraries" to libraries.joinToString(transform = { "\"$it\"" }), ) } @@ -171,36 +162,28 @@ tasks { dependsOn(processResources) } - // Offline jar (include kotlin std in the final jar fine) - val offlineJar by // Shadow necessary dependency - registering( + // Online jar (use of libraries) + shadowJar { + configureBaseShadow("", + arrayOf( + "org.jetbrains.kotlin:kotlin-stdlib:2.1.0", + "net.kyori:adventure-text-minimessage:4.25.0", + "net.kyori:adventure-text-serializer-plain:4.25.0", + "net.kyori:adventure-text-serializer-legacy:4.25.0", + )) - // Include all project other dependencies - ShadowJar + // Exclude kotlin std, annotations and adventure api + exclude("*kotlin/**") + exclude("**/annotations/**") + exclude("net/kyori/**") + } - // Add custom anvil compiled - ::class, fun ShadowJar.() { - val name = "${rootProject.name}-${effectiveVersion}-offline.jar" - archiveFileName.set(name) + val offlineJar by registering(ShadowJar::class) { + configureBaseShadow("offline", emptyArray()) - // Shadow necessary dependency - relocate("com.github.stefvanschie.inventoryframework", "xyz.alexcrea.inventoryframework") - - filesMatching("plugin.yml") { - expand( - "version" to "$effectiveVersion-offline", - "libraries" to "" - ) - } - - // Include all project other dependencies - from(project.configurations.runtimeClasspath) - - // Add custom anvil compiled - from(sourceSets.main.get().output) - - dependsOn(processResources) - }) + from(sourceSets.main.get().output) + configurations = listOf(project.configurations.runtimeClasspath.get()) + } // Make the online and offline jar on build named("build") { From d4165df61aabcb313633331846a0b746a657c008 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Sat, 10 Jan 2026 19:40:55 +0100 Subject: [PATCH 049/207] try add "on release" workflow --- .github/workflows/gradle.yml | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index e2d9ca6..d381eb2 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -12,9 +12,11 @@ on: branches: [ "v1.x.x", "v2.x.x" ] pull_request: branches: [ "v1.x.x", "v2.x.x" ] + release: + types: [published] concurrency: - group: ${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }} cancel-in-progress: true jobs: @@ -31,17 +33,17 @@ jobs: java-version: | 21 distribution: 'temurin' - cache: 'gradle' # Configure Gradle for optimal use in GitHub Actions, including caching of downloaded dependencies. # See: https://github.com/gradle/actions/blob/main/setup-gradle/README.md - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 + uses: gradle/actions/setup-gradle@v5 - name: Make gradlew executable run: chmod +x ./gradlew - name: Get small commit hash + if: ${{ github.event_name != 'release' && success() }} run: echo "SMALL_COMMIT_HASH=$(git rev-parse --short ${{ github.sha }})" >> $GITHUB_ENV - name: Build with Gradle Wrapper @@ -63,7 +65,7 @@ jobs: echo "ONLINE_JAR_NAME=$(basename $ONLINE_JAR_PATH)" >> $GITHUB_ENV echo "OFFLINE_JAR_NAME=$(basename $OFFLINE_JAR_PATH)" >> $GITHUB_ENV - # upload the named jars + # upload the named jars as artifact - name: Upload online JAR artifact uses: actions/upload-artifact@v4 with: @@ -78,4 +80,13 @@ jobs: - name: Summarize tests results uses: jeantessier/test-summary-action@v1 - if: ${{ always() }} \ No newline at end of file + if: ${{ always() }} + + # upload the jar to release + - name: Upload jar to release + if: ${{ github.event_name == 'release' && success() }} + uses: softprops/action-gh-release@v2 + with: + files: | + ${{ env.ONLINE_JAR_PATH }} + ${{ env.OFFLINE_JAR_PATH }} \ No newline at end of file From 9e0e546367580f16e20824e02b0735b639c654de Mon Sep 17 00:00:00 2001 From: alexcrea Date: Sat, 10 Jan 2026 19:51:45 +0100 Subject: [PATCH 050/207] why did I used path and not name lol --- .github/workflows/gradle.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index d381eb2..e445613 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -88,5 +88,5 @@ jobs: uses: softprops/action-gh-release@v2 with: files: | - ${{ env.ONLINE_JAR_PATH }} - ${{ env.OFFLINE_JAR_PATH }} \ No newline at end of file + build/libs/${{ env.ONLINE_JAR_NAME }} + build/libs/${{ env.OFFLINE_JAR_NAME }} \ No newline at end of file From 5fe65799c85e4b8c6fc61a2fd70a15e97381be9b Mon Sep 17 00:00:00 2001 From: alexcrea Date: Sat, 10 Jan 2026 21:32:13 +0100 Subject: [PATCH 051/207] cache paperweight --- .github/workflows/gradle.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index e445613..e5cdec0 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -33,6 +33,26 @@ jobs: java-version: | 21 distribution: 'temurin' + cache: gradle + + - name: Cache Gradle root and wrapper + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: gradle-root-${{ runner.os }}-${{ hashFiles('build.gradle*') }} + restore-keys: gradle-root-${{ runner.os }}- + + # Setup paperweight cache + - name: Cache paperweight + uses: actions/cache@v3 + with: + path: | + ./nms/*/.gradle/caches/paperweight + key: paperweight-submodules-${{ runner.os }}-${{ hashFiles('nms/*/build.gradle*') }} + restore-keys: | + paperweight-submodules-${{ runner.os }}- # Configure Gradle for optimal use in GitHub Actions, including caching of downloaded dependencies. # See: https://github.com/gradle/actions/blob/main/setup-gradle/README.md From be3a98078f148d29678839af2598decb160e2347 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 12 Jan 2026 00:43:06 +0100 Subject: [PATCH 052/207] add hangar publish logic --- .github/workflows/gradle.yml | 12 ++++- build.gradle.kts | 100 ++++++++++++++++++++++++++++++++++- 2 files changed, 108 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index e5cdec0..87eb832 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -67,7 +67,7 @@ jobs: run: echo "SMALL_COMMIT_HASH=$(git rev-parse --short ${{ github.sha }})" >> $GITHUB_ENV - name: Build with Gradle Wrapper - run: ./gradlew build --parallel + run: ./gradlew build --parallel --stacktrace # only submit dependency on push - name: Generate and submit dependency graph @@ -109,4 +109,12 @@ jobs: with: files: | build/libs/${{ env.ONLINE_JAR_NAME }} - build/libs/${{ env.OFFLINE_JAR_NAME }} \ No newline at end of file + build/libs/${{ env.OFFLINE_JAR_NAME }} + + - name: Hangar release + if: ${{ (github.event_name != 'release' || github.event_name != 'push') && github.repository_owner == 'alexcrea' && success() }} + env: + RELEASE_CHANGELOG: ${{ github.event.release.body }} + IS_GITHUB_PRERELEASE: ${{ github.event.release.prerelease }} + HANGAR_API_TOKEN: ${{ secrets.HANGAR_API_TOKEN }} + run: ./gradlew publishAllPublicationsToHangar --stacktrace diff --git a/build.gradle.kts b/build.gradle.kts index f005e45..b3d4b51 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,7 +2,10 @@ import cn.lalaki.pub.BaseCentralPortalPlusExtension import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import groovy.util.Node import groovy.util.NodeList +import io.papermc.hangarpublishplugin.model.HangarPublication +import io.papermc.hangarpublishplugin.model.Platforms import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import java.io.ByteArrayOutputStream plugins { kotlin("jvm") version "2.1.0" @@ -15,13 +18,16 @@ plugins { id("cn.lalaki.central").version("1.2.8") // Paper id("io.papermc.paperweight.userdev") version "2.0.0-beta.17" apply false + id("io.papermc.hangar-publish-plugin") version "0.1.2" } group = "xyz.alexcrea" version = "1.15.9" +val isDevBuild = System.getenv("SMALL_COMMIT_HASH") != null +val isPreRelease = System.getenv("IS_PRERELEASE") == "true" val effectiveVersion = "$version" + - (if (System.getenv("SMALL_COMMIT_HASH") != null) "-dev-${System.getenv("SMALL_COMMIT_HASH")!!}" else "") + (if (isDevBuild) "-dev-${System.getenv("SMALL_COMMIT_HASH")!!}" else "") repositories { // EcoEnchants @@ -170,7 +176,7 @@ tasks { "net.kyori:adventure-text-minimessage:4.25.0", "net.kyori:adventure-text-serializer-plain:4.25.0", "net.kyori:adventure-text-serializer-legacy:4.25.0", - )) + )) // Exclude kotlin std, annotations and adventure api exclude("*kotlin/**") @@ -319,3 +325,93 @@ publishing { } } } + +// hangar publish + +fun executeGitCommand(vararg command: String): String { + val byteOut = ByteArrayOutputStream() + exec { + commandLine = listOf("git", *command) + standardOutput = byteOut + } + return byteOut.toString(Charsets.UTF_8.name()).trim() +} + + +fun latestCommitMessage(): String { + return executeGitCommand("log", "-1", "--pretty=%B") +} + +fun changelog(isOnline: Boolean): String { + var changelog = if(isDevBuild) latestCommitMessage() + else System.getenv("RELEASE_CHANGELOG") + + if(!isOnline) { + changelog = "This is an offline version of the plugin. \\\n" + + "This mean that this plugin libraries are shaded into this plugin \\\n" + + "You likely want to use the normal version of this plugin\n\n" + changelog + } + + return changelog +} + +hangarPublish { + + fun HangarPublication.configure(isOnline: Boolean, devChannel: String, releaseChannel: String) { + version.set(effectiveVersion + if(isOnline) "" else "-offline") + channel.set(if (isDevBuild || isPreRelease) devChannel else releaseChannel) + + changelog.set(changelog(isOnline)) + id.set("CustomAnvil") + apiKey.set(System.getenv("HANGAR_API_TOKEN")) + + platforms { + register(Platforms.PAPER) { + // Set the JAR file to upload + var task = if(isOnline) tasks.shadowJar + else tasks.named("offlineJar") + + jar.set(task.flatMap { it.archiveFile }) + + // Set platform versions from gradle.properties file + val versions: List = (property("paperVersion") as String) + .split(",") + .map { it.trim() } + platformVersions.set(versions) + + dependencies { + hangar("ProtocolLib") { + required.set(false) + } + url("Disenchantment", "https://modrinth.com/plugin/disenchantment") { + required.set(false) + } + url("ToolStats", "https://modrinth.com/plugin/toolstats") { + required.set(false) + } + url("HavenBags", "https://www.spigotmc.org/resources/havenbags-shulker-like-player-bound-bags-1-17-1-21-4.110420/") { + required.set(false) + } + url("EcoEnchants", "https://www.spigotmc.org/resources/ecoenchants-%E2%AD%95-250-enchantments-%E2%9C%85-create-custom-enchants-%E2%9C%A8-essentials-cmi-support.79573/") { + required.set(false) + } + hangar("EnchantsSquared") { + required.set(false) + } + url("ExcellentEnchants", "https://www.spigotmc.org/resources/excellentenchants-%E2%AD%90-75-vanilla-like-enchantments.61693/") { + required.set(false) + } + } + } + } + } + + publications.register("plugin") { + configure(true, "DevSnapshot", "Release") + } + + publications.register("offline") { + configure(false, "OfflineSnapshot", "OfflineRelease") + } + +} From 69f0e2936e31dc0ee84e0c32553f85300b33b595 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 12 Jan 2026 00:56:55 +0100 Subject: [PATCH 053/207] forgot paper version --- gradle.properties | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 95311bf..1a8f2e4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,4 +6,8 @@ signing.secretKeyRingFile=~/.gnupg/secring.gpg kotlin.daemon.jvmargs=-Xmx8G # list of nms -subprojects.reobfnms=v1_17R1,v1_18R1,v1_18R2,v1_19R1,v1_19R2,v1_19R3,v1_20R1,v1_20R2,v1_20R3,v1_20R4,v1_21R1,v1_21R2,v1_21R3,v1_21R4,v1_21R5,v1_21R6,v1_21R7 \ No newline at end of file +subprojects.reobfnms=v1_17R1,v1_18R1,v1_18R2,v1_19R1,v1_19R2,v1_19R3,v1_20R1,v1_20R2,v1_20R3,v1_20R4,v1_21R1,v1_21R2,v1_21R3,v1_21R4,v1_21R5,v1_21R6,v1_21R7 + +# list of version for hangar release +paperVersion=1.18-1.21.11 + From e1c794403cf614e541920049d9c3e639c51e5d6a Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 12 Jan 2026 01:05:15 +0100 Subject: [PATCH 054/207] print log on build for release debug --- build.gradle.kts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index b3d4b51..a22feea 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -25,7 +25,13 @@ group = "xyz.alexcrea" version = "1.15.9" val isDevBuild = System.getenv("SMALL_COMMIT_HASH") != null -val isPreRelease = System.getenv("IS_PRERELEASE") == "true" +val isPreRelease = System.getenv("IS_GITHUB_PRERELEASE") == "true" + +println("testing env variable") +println(System.getenv("IS_GITHUB_PRERELEASE")) +println(System.getenv("IS_GITHUB_PRERELEASE") == "true") +println(System.getenv("RELEASE_CHANGELOG")) + val effectiveVersion = "$version" + (if (isDevBuild) "-dev-${System.getenv("SMALL_COMMIT_HASH")!!}" else "") From 35c67e4207545eb5733b5759fb57df32856158cd Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 12 Jan 2026 01:17:34 +0100 Subject: [PATCH 055/207] set env variable early --- .github/workflows/gradle.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 87eb832..677d3a3 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -66,6 +66,12 @@ jobs: if: ${{ github.event_name != 'release' && success() }} run: echo "SMALL_COMMIT_HASH=$(git rev-parse --short ${{ github.sha }})" >> $GITHUB_ENV + - name: Prepare release env variable + if: ${{ github.event_name != 'release' && success() }} + run: | + echo "RELEASE_CHANGELOG=${{ github.event.release.body }}" >> $GITHUB_ENV + echo "IS_GITHUB_PRERELEASE=${{ github.event.release.prerelease }}" >> $GITHUB_ENV + - name: Build with Gradle Wrapper run: ./gradlew build --parallel --stacktrace @@ -114,7 +120,5 @@ jobs: - name: Hangar release if: ${{ (github.event_name != 'release' || github.event_name != 'push') && github.repository_owner == 'alexcrea' && success() }} env: - RELEASE_CHANGELOG: ${{ github.event.release.body }} - IS_GITHUB_PRERELEASE: ${{ github.event.release.prerelease }} HANGAR_API_TOKEN: ${{ secrets.HANGAR_API_TOKEN }} run: ./gradlew publishAllPublicationsToHangar --stacktrace From 203713385a92409154b6ebfaa016bc349179d631 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 12 Jan 2026 01:27:45 +0100 Subject: [PATCH 056/207] invert bad logic --- .github/workflows/gradle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 677d3a3..4827530 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -67,7 +67,7 @@ jobs: run: echo "SMALL_COMMIT_HASH=$(git rev-parse --short ${{ github.sha }})" >> $GITHUB_ENV - name: Prepare release env variable - if: ${{ github.event_name != 'release' && success() }} + if: ${{ github.event_name == 'release' && success() }} run: | echo "RELEASE_CHANGELOG=${{ github.event.release.body }}" >> $GITHUB_ENV echo "IS_GITHUB_PRERELEASE=${{ github.event.release.prerelease }}" >> $GITHUB_ENV From 4a2a9c5b3a60d0cc7c358f4cb9b6a80054502b02 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 12 Jan 2026 01:33:51 +0100 Subject: [PATCH 057/207] try multiline --- .github/workflows/gradle.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 4827530..abcd91c 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -69,7 +69,9 @@ jobs: - name: Prepare release env variable if: ${{ github.event_name == 'release' && success() }} run: | - echo "RELEASE_CHANGELOG=${{ github.event.release.body }}" >> $GITHUB_ENV + echo "RELEASE_CHANGELOG<> $GITHUB_ENV + echo "${{ github.event.release.body || '' }}" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV echo "IS_GITHUB_PRERELEASE=${{ github.event.release.prerelease }}" >> $GITHUB_ENV - name: Build with Gradle Wrapper From 5c32e819fdc3b143787a0cfcf2faee8aedd6d907 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 12 Jan 2026 01:45:16 +0100 Subject: [PATCH 058/207] pre release specific suffix --- build.gradle.kts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index a22feea..4adb5be 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -27,11 +27,6 @@ version = "1.15.9" val isDevBuild = System.getenv("SMALL_COMMIT_HASH") != null val isPreRelease = System.getenv("IS_GITHUB_PRERELEASE") == "true" -println("testing env variable") -println(System.getenv("IS_GITHUB_PRERELEASE")) -println(System.getenv("IS_GITHUB_PRERELEASE") == "true") -println(System.getenv("RELEASE_CHANGELOG")) - val effectiveVersion = "$version" + (if (isDevBuild) "-dev-${System.getenv("SMALL_COMMIT_HASH")!!}" else "") @@ -364,7 +359,11 @@ fun changelog(isOnline: Boolean): String { hangarPublish { fun HangarPublication.configure(isOnline: Boolean, devChannel: String, releaseChannel: String) { - version.set(effectiveVersion + if(isOnline) "" else "-offline") + var versionName = effectiveVersion + if(isPreRelease) versionName+= "-pre" + if(!isOnline) versionName+= "-offline" + + version.set(versionName) channel.set(if (isDevBuild || isPreRelease) devChannel else releaseChannel) changelog.set(changelog(isOnline)) From 73fd79b9da4485f9c316bb4c239305ecac0887f2 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 12 Jan 2026 03:13:49 +0100 Subject: [PATCH 059/207] add release discord webhook --- .github/workflows/gradle.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index abcd91c..c8e9c9e 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -124,3 +124,16 @@ jobs: env: HANGAR_API_TOKEN: ${{ secrets.HANGAR_API_TOKEN }} run: ./gradlew publishAllPublicationsToHangar --stacktrace + + + - name: Send release note to discord + if: ${{ github.event_name == 'release' && github.repository_owner == 'alexcrea' && success() }} + uses: tsickert/discord-webhook@v7.0.0 + with: + webhook-url: ${{ secrets.RELEASE_WEBHOOK_URL }} + content: | + ${{ github.event.release.prerelease == false && '<@&1338546156325568642>' || '<@&1352296092989001768>' }} + # New ${{ github.event.release.prerelease == false && '' || 'beta' }} version of custom anvil ! <:CustomAnvil:1262550667986342001>([Modrinth](https://modrinth.com/plugin/customanvil), [Hangar](https://hangar.papermc.io/alexcrea/CustomAnvil) links) and [GitHub](${{ github.event.release.html_url }} links + -# note: automated release. spigot and modrinth are not uploaded yet. it is available in hangar and github + + ${{ env.RELEASE_CHANGELOG }} \ No newline at end of file From dc7f3f5e20b83dd0fd2d6b6966e24cdf50d07f1a Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 13 Jan 2026 01:24:43 +0100 Subject: [PATCH 060/207] add modrinth release and fix discord message --- .github/workflows/gradle.yml | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index c8e9c9e..983e622 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -73,6 +73,9 @@ jobs: echo "${{ github.event.release.body || '' }}" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV echo "IS_GITHUB_PRERELEASE=${{ github.event.release.prerelease }}" >> $GITHUB_ENV + + echo "MODRINTH_VERSIONS='["1.18.x", "1.19.x", "1.20.x", "1.21.x"]'" >> $GITHUB_ENV + echo "MODRINTH_PLATFORMS='["spigot", "paper", "purpur", "folia"]'" >> $GITHUB_ENV - name: Build with Gradle Wrapper run: ./gradlew build --parallel --stacktrace @@ -125,6 +128,31 @@ jobs: HANGAR_API_TOKEN: ${{ secrets.HANGAR_API_TOKEN }} run: ./gradlew publishAllPublicationsToHangar --stacktrace + - name: Modrinth publish alpha + if: ${{ github.event_name == 'push' && github.repository_owner == 'alexcrea' && success() }} + uses: cloudnode-pro/modrinth-publish@v2 + with: + token: ${{ secrets.MODRINTH_TOKEN }} + project: S75Ueiq9 + name: dev-${{ env.SMALL_COMMIT_HASH }} + version: dev-${{ env.SMALL_COMMIT_HASH }} + loaders: ${{ env.MODRINTH_PLATFORMS }} + game-versions: ${{ env.MODRINTH_VERSIONS }} + channel: alpha + files: build/libs/${{ env.ONLINE_JAR_NAME }} + + - name: Modrinth publish release + if: ${{ github.event_name == 'release' && github.repository_owner == 'alexcrea' && success() }} + uses: cloudnode-pro/modrinth-publish@v2 + with: + token: ${{ secrets.MODRINTH_TOKEN }} + project: S75Ueiq9 + name: ${{ github.event.release.name }} + version: ${{ github.event.release.tag_name }}${{ github.event.release.prerelease == false && '' || '-pre' }} + loaders: ${{ env.MODRINTH_PLATFORMS }} + game-versions: ${{ env.MODRINTH_VERSIONS }} + channel: ${{ github.event.release.prerelease == false && 'release' || 'beta' }} + files: build/libs/${{ env.ONLINE_JAR_NAME }} - name: Send release note to discord if: ${{ github.event_name == 'release' && github.repository_owner == 'alexcrea' && success() }} @@ -133,7 +161,7 @@ jobs: webhook-url: ${{ secrets.RELEASE_WEBHOOK_URL }} content: | ${{ github.event.release.prerelease == false && '<@&1338546156325568642>' || '<@&1352296092989001768>' }} - # New ${{ github.event.release.prerelease == false && '' || 'beta' }} version of custom anvil ! <:CustomAnvil:1262550667986342001>([Modrinth](https://modrinth.com/plugin/customanvil), [Hangar](https://hangar.papermc.io/alexcrea/CustomAnvil) links) and [GitHub](${{ github.event.release.html_url }} links - -# note: automated release. spigot and modrinth are not uploaded yet. it is available in hangar and github + # New ${{ github.event.release.prerelease == false && 'beta' || '' }} version of custom anvil ! <:CustomAnvil:1262550667986342001>([Modrinth](https://modrinth.com/plugin/customanvil), [Hangar](https://hangar.papermc.io/alexcrea/CustomAnvil) and [GitHub](${{ github.event.release.html_url }}) links) + -# note: automated release. spigot is not uploaded yet. it is available in hangar and github ${{ env.RELEASE_CHANGELOG }} \ No newline at end of file From 18a0f58e684169e9272651ff8a33a6143679b74b Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 13 Jan 2026 01:51:25 +0100 Subject: [PATCH 061/207] fix modrinth release --- .github/workflows/gradle.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 983e622..5b581d2 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -74,8 +74,8 @@ jobs: echo "EOF" >> $GITHUB_ENV echo "IS_GITHUB_PRERELEASE=${{ github.event.release.prerelease }}" >> $GITHUB_ENV - echo "MODRINTH_VERSIONS='["1.18.x", "1.19.x", "1.20.x", "1.21.x"]'" >> $GITHUB_ENV - echo "MODRINTH_PLATFORMS='["spigot", "paper", "purpur", "folia"]'" >> $GITHUB_ENV + echo MODRINTH_VERSIONS='["1.18.x", "1.19.x", "1.20.x", "1.21.x"]' >> $GITHUB_ENV + echo MODRINTH_PLATFORMS='["spigot", "paper", "purpur", "folia"]' >> $GITHUB_ENV - name: Build with Gradle Wrapper run: ./gradlew build --parallel --stacktrace @@ -140,6 +140,7 @@ jobs: game-versions: ${{ env.MODRINTH_VERSIONS }} channel: alpha files: build/libs/${{ env.ONLINE_JAR_NAME }} + changelog: ${{ github.event.head_commit.message }} - name: Modrinth publish release if: ${{ github.event_name == 'release' && github.repository_owner == 'alexcrea' && success() }} @@ -153,6 +154,7 @@ jobs: game-versions: ${{ env.MODRINTH_VERSIONS }} channel: ${{ github.event.release.prerelease == false && 'release' || 'beta' }} files: build/libs/${{ env.ONLINE_JAR_NAME }} + changelog: ${{ env.RELEASE_CHANGELOG }} - name: Send release note to discord if: ${{ github.event_name == 'release' && github.repository_owner == 'alexcrea' && success() }} From 675a16c9b4bcc626880bd51a72e574814d60cb8c Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 13 Jan 2026 02:09:15 +0100 Subject: [PATCH 062/207] make modrinth publish run every time --- .github/workflows/gradle.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 5b581d2..2daf897 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -73,7 +73,9 @@ jobs: echo "${{ github.event.release.body || '' }}" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV echo "IS_GITHUB_PRERELEASE=${{ github.event.release.prerelease }}" >> $GITHUB_ENV - + + - name: Prepare publish env variable + run: | echo MODRINTH_VERSIONS='["1.18.x", "1.19.x", "1.20.x", "1.21.x"]' >> $GITHUB_ENV echo MODRINTH_PLATFORMS='["spigot", "paper", "purpur", "folia"]' >> $GITHUB_ENV From f14fe20faf11d87ceef464d11ed640b47e1d220b Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 13 Jan 2026 02:20:51 +0100 Subject: [PATCH 063/207] change message a bit again --- .github/workflows/gradle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 2daf897..2af7d05 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -166,6 +166,6 @@ jobs: content: | ${{ github.event.release.prerelease == false && '<@&1338546156325568642>' || '<@&1352296092989001768>' }} # New ${{ github.event.release.prerelease == false && 'beta' || '' }} version of custom anvil ! <:CustomAnvil:1262550667986342001>([Modrinth](https://modrinth.com/plugin/customanvil), [Hangar](https://hangar.papermc.io/alexcrea/CustomAnvil) and [GitHub](${{ github.event.release.html_url }}) links) - -# note: automated release. spigot is not uploaded yet. it is available in hangar and github + -# note: automated release. spigot is not uploaded yet. ${{ env.RELEASE_CHANGELOG }} \ No newline at end of file From ea6c5724fa5938339bb768fdc8bd858ed55007ef Mon Sep 17 00:00:00 2001 From: alexcrea Date: Fri, 16 Jan 2026 21:43:34 +0100 Subject: [PATCH 064/207] no changelog for build [skip ci] --- build.gradle.kts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index 4adb5be..1f597f7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -353,6 +353,10 @@ fun changelog(isOnline: Boolean): String { "You likely want to use the normal version of this plugin\n\n" + changelog } + if(changelog == null || changelog.isEmpty()) { + changelog = "empty changelog" + } + return changelog } From 377bc4c1d8adff518caf626547ec28388c39fe51 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 27 Jan 2026 21:05:03 +0100 Subject: [PATCH 065/207] use correct anvil combine method --- .../plugins/ExcellentEnchantsDependency.kt | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt index 98d80ff..dcd104e 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt @@ -184,11 +184,20 @@ class ExcellentEnchantsDependency { ) this.handleRechargeMethod.setAccessible(true) - this.handleCombineMethod = this.usedAnvilListener.javaClass.getDeclaredMethod( - "handleCombine", - PrepareAnvilEvent::class.java, ItemStack::class.java, ItemStack::class.java, ItemStack::class.java - ) - this.handleCombineMethod.setAccessible(true) + try { + this.usedAnvilListener.javaClass.methods.forEach { method -> CustomAnvil.instance.logger.warning { method.name } } + this.handleCombineMethod = this.usedAnvilListener.javaClass.getDeclaredMethod( + "anvilCombine", + PrepareAnvilEvent::class.java, ItemStack::class.java, ItemStack::class.java, ItemStack::class.java + ) + this.handleCombineMethod.setAccessible(true) + } catch (_: NoSuchMethodException) { + this.handleCombineMethod = this.usedAnvilListener.javaClass.getDeclaredMethod( + "handleCombine", + PrepareAnvilEvent::class.java, ItemStack::class.java, ItemStack::class.java, ItemStack::class.java + ) + this.handleCombineMethod.setAccessible(true) + } } From b7e19355a84d4139a7234bdcc8221e1d75df74e9 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 27 Jan 2026 21:05:14 +0100 Subject: [PATCH 066/207] version bump --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 1f597f7..90de68e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,7 +22,7 @@ plugins { } group = "xyz.alexcrea" -version = "1.15.9" +version = "1.15.10" val isDevBuild = System.getenv("SMALL_COMMIT_HASH") != null val isPreRelease = System.getenv("IS_GITHUB_PRERELEASE") == "true" From 76e5059632e6f957979f7b64e8b805d8d1a1af7c Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 27 Jan 2026 21:28:33 +0100 Subject: [PATCH 067/207] use reflection for enchantment definition --- .../enchant/wrapped/CAEEV5Enchantment.java | 83 +++++++++++++++++-- .../plugins/ExcellentEnchantsDependency.kt | 3 +- 2 files changed, 78 insertions(+), 8 deletions(-) diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEV5Enchantment.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEV5Enchantment.java index e91930f..a31a55c 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEV5Enchantment.java +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEV5Enchantment.java @@ -5,31 +5,32 @@ import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; import su.nightexpress.excellentenchants.api.enchantment.CustomEnchantment; import su.nightexpress.excellentenchants.api.item.ItemSet; -import su.nightexpress.excellentenchants.api.wrapper.EnchantDefinition; import xyz.alexcrea.cuanvil.enchant.AdditionalTestEnchantment; import xyz.alexcrea.cuanvil.enchant.CAEnchantment; import xyz.alexcrea.cuanvil.enchant.EnchantmentRarity; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.Map; import java.util.Set; public class CAEEV5Enchantment extends CABukkitEnchantment implements AdditionalTestEnchantment { @NotNull CustomEnchantment eeenchantment; - @NotNull EnchantDefinition definition; + @NotNull Object definition; public CAEEV5Enchantment(@NotNull CustomEnchantment enchantment) { - super(enchantment.getBukkitEnchantment(), EnchantmentRarity.getRarity(enchantment.getDefinition().getAnvilCost())); + super(enchantment.getBukkitEnchantment(), EnchantmentRarity.getRarity(getAnvilCost(enchantment))); this.eeenchantment = enchantment; - this.definition = enchantment.getDefinition(); + this.definition = getDefinition(enchantment); } @Override public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull Material itemMat) { - if (!definition.hasConflicts()) return false; + if (!hasConflicts()) return false; - Set conflicts = definition.getExclusiveSet(); + Set conflicts = getExclusiveSet(); for (CAEnchantment caEnchantment : enchantments.keySet()) { if (conflicts.contains(caEnchantment.getName())) return true; @@ -52,4 +53,74 @@ public class CAEEV5Enchantment extends CABukkitEnchantment implements Additional return true; } + + private static final Method getDefinitonMethod; + + private static final Method getAnvilCostMethod; + private static final Method hasConflictsMethod; + private static final Method getExclusiveSetMethod; + static { + var enchClazz = CustomEnchantment.class; + try { + getDefinitonMethod = enchClazz.getDeclaredMethod("getDefinition"); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + + Class definitionClazz; + try { + definitionClazz = Class.forName("su.nightexpress.excellentenchants.api.EnchantDefinition"); + } catch (ClassNotFoundException e) { + try { + definitionClazz = Class.forName("su.nightexpress.excellentenchants.api.wrapper.EnchantDefinition"); + } catch (ClassNotFoundException ex) { + throw new RuntimeException(ex); + } + } + + // Now definition methods + try { + getAnvilCostMethod = definitionClazz.getDeclaredMethod("getAnvilCost"); + hasConflictsMethod = definitionClazz.getDeclaredMethod("hasConflicts"); + getExclusiveSetMethod = definitionClazz.getDeclaredMethod("getExclusiveSet"); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + + } + + private static Object getDefinition(CustomEnchantment enchantment) { + try { + return getDefinitonMethod.invoke(enchantment); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static int getAnvilCost(CustomEnchantment enchantment) { + try { + return (int) getAnvilCostMethod.invoke(getDefinition(enchantment)); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + private boolean hasConflicts() { + try { + return (boolean) hasConflictsMethod.invoke(definition); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + + private Set getExclusiveSet() { + try { + return (Set) getExclusiveSetMethod.invoke(definition); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt index dcd104e..ebc4ad9 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt @@ -163,8 +163,8 @@ class ExcellentEnchantsDependency { } when (listenerVersion) { + ListenerVersion.V5_3, ListenerVersion.V5, - ListenerVersion.V5_3 -> this.usedAnvilListener = v5AnvilListener!! ListenerVersion.PRE_V5 -> this.usedAnvilListener = preV5AnvilListener!! ListenerVersion.LEGACY -> this.usedAnvilListener = legacyAnvilListener!! @@ -185,7 +185,6 @@ class ExcellentEnchantsDependency { this.handleRechargeMethod.setAccessible(true) try { - this.usedAnvilListener.javaClass.methods.forEach { method -> CustomAnvil.instance.logger.warning { method.name } } this.handleCombineMethod = this.usedAnvilListener.javaClass.getDeclaredMethod( "anvilCombine", PrepareAnvilEvent::class.java, ItemStack::class.java, ItemStack::class.java, ItemStack::class.java From 4dd7d6361b4e8b10aa3be346603b7f780748efc5 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Wed, 4 Feb 2026 17:37:39 +0100 Subject: [PATCH 068/207] fix custom anvil not checking enchantment key name version bump --- build.gradle.kts | 2 +- .../xyz/alexcrea/cuanvil/enchant/wrapped/CAEEV5Enchantment.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 90de68e..7597db0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,7 +22,7 @@ plugins { } group = "xyz.alexcrea" -version = "1.15.10" +version = "1.15.11" val isDevBuild = System.getenv("SMALL_COMMIT_HASH") != null val isPreRelease = System.getenv("IS_GITHUB_PRERELEASE") == "true" diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEV5Enchantment.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEV5Enchantment.java index a31a55c..813eda8 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEV5Enchantment.java +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEV5Enchantment.java @@ -34,6 +34,7 @@ public class CAEEV5Enchantment extends CABukkitEnchantment implements Additional for (CAEnchantment caEnchantment : enchantments.keySet()) { if (conflicts.contains(caEnchantment.getName())) return true; + if (conflicts.contains(caEnchantment.getKey().toString())) return true; } return false; From c8f1aa65a26260d5ce7736c00789aff6e04bf529 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Wed, 4 Feb 2026 17:41:36 +0100 Subject: [PATCH 069/207] change message a bit --- .github/workflows/gradle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 2af7d05..b5dc6b6 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -165,7 +165,7 @@ jobs: webhook-url: ${{ secrets.RELEASE_WEBHOOK_URL }} content: | ${{ github.event.release.prerelease == false && '<@&1338546156325568642>' || '<@&1352296092989001768>' }} - # New ${{ github.event.release.prerelease == false && 'beta' || '' }} version of custom anvil ! <:CustomAnvil:1262550667986342001>([Modrinth](https://modrinth.com/plugin/customanvil), [Hangar](https://hangar.papermc.io/alexcrea/CustomAnvil) and [GitHub](${{ github.event.release.html_url }}) links) + # New ${{ github.event.release.prerelease == false && '' || 'beta' }} version of custom anvil ! <:CustomAnvil:1262550667986342001>([Modrinth](https://modrinth.com/plugin/customanvil), [Hangar](https://hangar.papermc.io/alexcrea/CustomAnvil) and [GitHub](${{ github.event.release.html_url }}) links) -# note: automated release. spigot is not uploaded yet. ${{ env.RELEASE_CHANGELOG }} \ No newline at end of file From 8e3f190bb3c17e635162d3959de8ed2ae266f09f Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 10 Feb 2026 12:14:04 +0100 Subject: [PATCH 070/207] version bump --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 7597db0..f690eaa 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,7 +22,7 @@ plugins { } group = "xyz.alexcrea" -version = "1.15.11" +version = "1.15.12" val isDevBuild = System.getenv("SMALL_COMMIT_HASH") != null val isPreRelease = System.getenv("IS_GITHUB_PRERELEASE") == "true" From 9ed43f3def8fbace465c442135e9a5a9e9f065c6 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 10 Feb 2026 12:14:35 +0100 Subject: [PATCH 071/207] fix eco enchant conflict with everything --- .../xyz/alexcrea/cuanvil/enchant/wrapped/CAEcoEnchant.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEcoEnchant.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEcoEnchant.java index 6e74b73..b0e8fba 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEcoEnchant.java +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEcoEnchant.java @@ -26,6 +26,10 @@ public class CAEcoEnchant extends CABukkitEnchantment implements AdditionalTestE public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull Material itemMat) { if (enchantments.isEmpty()) return false; + // Check if there is only self + if (enchantments.size() == 1 && this.equals(enchantments.keySet().stream().findFirst().get())) + return false; + if (this.ecoEnchant.getConflictsWithEverything()) { return true; } From 2c30446bc19b75e404ee71558e6e7796fd9b245e Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 10 Feb 2026 12:39:00 +0100 Subject: [PATCH 072/207] don't use eco's pre anvil event player --- .../xyz/alexcrea/cuanvil/dependency/DependencyManager.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt index 9c8c159..42383b6 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt @@ -1,6 +1,5 @@ package xyz.alexcrea.cuanvil.dependency -import com.willfp.eco.core.gui.player import io.delilaheve.CustomAnvil import net.kyori.adventure.text.Component import org.bukkit.Bukkit @@ -300,7 +299,7 @@ object DependencyManager { if (!bypass && (externGuiTester?.testIfGui(event.view) == true)) bypass = true // Test if in an ax player warp rating gui - if (!bypass && (axPlayerWarpsCompatibility?.testIfGui(event.player) == true)) bypass = true + if (!bypass && (axPlayerWarpsCompatibility?.testIfGui(event.view.player) == true)) bypass = true return bypass } From 7aeb776ce03a1e6d4d49c01da80f0f898bc61e50 Mon Sep 17 00:00:00 2001 From: alexd <42614139+alexcrea@users.noreply.github.com> Date: Sun, 22 Feb 2026 00:42:33 +0100 Subject: [PATCH 073/207] add run dir for myself --- .gitignore | 3 +++ .run/Server.run.xml | 9 +++++++++ 2 files changed, 12 insertions(+) create mode 100644 .run/Server.run.xml diff --git a/.gitignore b/.gitignore index e7d8069..982299c 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,9 @@ /impl/*/build /impl/*/.gradle +# run folder +/run/ + # other random folders /htmlReport /.kotlin/errors diff --git a/.run/Server.run.xml b/.run/Server.run.xml new file mode 100644 index 0000000..9a8887f --- /dev/null +++ b/.run/Server.run.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file From ae8167faecaa0ad15a6609e35699364edd4b56de Mon Sep 17 00:00:00 2001 From: alexd <42614139+alexcrea@users.noreply.github.com> Date: Sun, 22 Feb 2026 01:07:14 +0100 Subject: [PATCH 074/207] safer start --- src/main/kotlin/io/delilaheve/CustomAnvil.kt | 102 +++++++++++++++---- 1 file changed, 82 insertions(+), 20 deletions(-) diff --git a/src/main/kotlin/io/delilaheve/CustomAnvil.kt b/src/main/kotlin/io/delilaheve/CustomAnvil.kt index c747189..ca25147 100644 --- a/src/main/kotlin/io/delilaheve/CustomAnvil.kt +++ b/src/main/kotlin/io/delilaheve/CustomAnvil.kt @@ -81,12 +81,91 @@ open class CustomAnvil : JavaPlugin() { } + // stop plugin if we do not force a dirty start (true by default) + // Return true if start was stopped + private fun tryDirtyStart(): Boolean { + if(!ConfigHolder.DEFAULT_CONFIG.config.getBoolean("dirty_start", false)) { + Bukkit.getPluginManager().disablePlugin(this) + return true + } + return false + } + + // stop plugin if we force a safe start (false by default) + // Return true if start was stopped + private fun trySafeStart(): Boolean { + if(ConfigHolder.DEFAULT_CONFIG.config.getBoolean("safe_start", false)) { + Bukkit.getPluginManager().disablePlugin(this) + return true + } + return false + } + /** * Setup plugin for use */ override fun onEnable() { instance = this + try { + legacyCheck() + } catch (e: Exception) { + logger.log(Level.SEVERE, "error trying to check for legacy system" , e) + if(trySafeStart()) return + } + // Add commands + try { + prepareCommand() + } catch (e: Exception) { + logger.log(Level.SEVERE, "error trying to register commands" , e) + if(trySafeStart()) return + } + + // Load default configuration + try { + if(!ConfigHolder.loadDefaultConfig()) + throw RuntimeException("Error loading configuration file") + } catch (e: Exception) { + logger.log(Level.SEVERE, "error occurred loading default configuration", e) + if(tryDirtyStart()) return + } + + // Load dependency + try { + DependencyManager.loadDependency() + } catch (e: Exception) { + logger.log(Level.SEVERE, "error loading dependency compatibility", e) + if(tryDirtyStart()) return + } + + // Register listeners + try { + registerListeners() + } catch (e: Exception) { + logger.log(Level.SEVERE, "error registering listeners", e) + if(tryDirtyStart()) return + } + + // Load metrics + try { + Metrics(this, bstatsPluginId) + } catch (_: Exception) {} + + // Load other thing later. + // It is so other dependent plugins can implement there event listener before we fire them. + DependencyManager.scheduler.scheduleGlobally(this) { loadEnchantmentSystemDirty() } + } + + private fun loadEnchantmentSystemDirty() { + try { + loadEnchantmentSystem() + } catch (e: Exception) { + logger.log(Level.SEVERE, "error initializing enchantment ssytem", e) + tryDirtyStart() + } + } + + private fun legacyCheck() { // Disable old plugin name if exist val potentialPlugin = Bukkit.getPluginManager().getPlugin("UnsafeEnchantsPlus") if (potentialPlugin != null) { @@ -99,34 +178,17 @@ open class CustomAnvil : JavaPlugin() { logger.warning("It seems you are using spigot") logger.warning("Please take notice that spigot is less supported than paper and derivatives") } + } - // Add commands - prepareCommand() - - // Load chat listener + private fun registerListeners() { + // Register chat listener chatListener = ChatEventListener() server.pluginManager.registerEvents(chatListener, this) - // Load default configuration - if (!ConfigHolder.loadDefaultConfig()) { - logger.log(Level.SEVERE,"could not load default config.") - return - } - - // Load dependency - DependencyManager.loadDependency() - // Register anvil events server.pluginManager.registerEvents(PrepareAnvilListener(), this) server.pluginManager.registerEvents(AnvilResultListener(), this) server.pluginManager.registerEvents(AnvilCloseListener(DependencyManager.packetManager), this) - - // Load metrics - Metrics(this, bstatsPluginId) - - // Load other thing later. - // It is so other dependent plugins can implement there event listener before we fire them. - DependencyManager.scheduler.scheduleGlobally(this, {loadEnchantmentSystem()}) } private fun loadEnchantmentSystem(){ From c57de03442c69c558d528211a9890619a67d7ea3 Mon Sep 17 00:00:00 2001 From: alexd <42614139+alexcrea@users.noreply.github.com> Date: Sun, 22 Feb 2026 04:02:20 +0100 Subject: [PATCH 075/207] add credits move compatibility list & remove spigot link spigot is not recomended anymore as do not have auto upload --- COMPATIBILITY.MD | 52 ++++++++++++++++++++++++++++++++++++++++++++++++ CREDITS.MD | 35 ++++++++++++++++++++++++++++++++ README.md | 39 ++++++------------------------------ 3 files changed, 93 insertions(+), 33 deletions(-) create mode 100644 COMPATIBILITY.MD create mode 100644 CREDITS.MD diff --git a/COMPATIBILITY.MD b/COMPATIBILITY.MD new file mode 100644 index 0000000..2be6f7a --- /dev/null +++ b/COMPATIBILITY.MD @@ -0,0 +1,52 @@ +### Bedrock issue +For server using geyser, bedrock player cannot use custom "recipes" in the anvil. +This is cannot be fixed on geyser or my side. + +### Plugin Compatibility +Here is various plugins that had issues with CustomAnvil +where efforts was made for compatibility and should be working right: + +some of them are cool I recommend checking them out ! + +## Supported By CustomAnvil +These plugins have compatibility handled by custom anvil. seek help on custom anvil and do not bother these developers + +#### Enchantment plugins +- [ExcellentEnchants](https://www.spigotmc.org/resources/excellentenchants-%E2%AD%90-75-vanilla-like-enchantments.61693/): + Use ExcellentEnchants item type + +- [EcoEnchant](https://www.spigotmc.org/resources/ecoenchants-%E2%AD%95-250-enchantments-%E2%9C%85-create-custom-enchants-%E2%9C%A8-essentials-cmi-support.79573/): + Need to use /anvilconfigreload or a server restart to add newly added enchantment. + Use EcoEnchant restriction system but new restriction can be added in custom anvil + +- [Enchantment²](https://www.spigotmc.org/resources/enchants-squared-the-enchantsplus-rewrite-custom-enchantments-that-act-like-vanilla-ones.86747/): + Support by Custom Anvil but still experimental. Automatic configuration. Plugin is not actively developed anymore + +#### Anvil Mechanics +- [Disenchantment](https://www.spigotmc.org/resources/disenchantment-1-21-1-1-20-6-new-book-splitting-mechanics.110741/) + Partially use Custom Anvil maximum XP settings (>= 6.1.5) + +- [HavenBags](https://www.spigotmc.org/resources/havenbags-shulker-like-player-bound-bags-1-17-1-21-4.110420/) + For bag upgrade and skin via anvil. (version >= 1.31.0) + +- [AxPlayerWarp](https://modrinth.com/project/QDJHDKvi) + For its anvil inventory usage + +- [ToolsStats](https://modrinth.com/project/oBZj9E15) + For token application using anvil + +### Known Partially Incompatible +- [UberEnchant](https://modrinth.com/plugin/uberenchant) + Anvil handling as they are doing something similar to CustomAnvil. +It is by no mean there faults and I recomend checking them out + +- [SuperEnchant](https://modrinth.com/plugin/superenchants) + Reported potential incompatibility + +- [AdvencedEnchantments](https://ae.advancedplugins.net/) + Paid plugin I do not own as I did not get commissioned for support. + may be able to use api but cannot test on my side + +If you like Custom Anvil to support a specific plugin (custom enchant or anvil mechanic). +You can ask, but please note implementing compatibility will be considered +as low priority as I work for the plugin as an hobby on my free time for free. diff --git a/CREDITS.MD b/CREDITS.MD new file mode 100644 index 0000000..5311480 --- /dev/null +++ b/CREDITS.MD @@ -0,0 +1,35 @@ +**Custom Anvil** is based on [Unsafe Enchants](https://github.com/DelilahEve/UnsafeEnchants) by DelilahEve. + +Thanks for all the contributors of bukkit, spigot, the paper team and the adventure API developers +thanks JetBrain for making IntelliJ + +### Dependencies +Here dependencies are used by custom anvil +- [IF](https://github.com/stefvanschie/IF) an inventory framework by stefvanschie +- [Mockbukkit](https://github.com/MockBukkit/MockBukkit) for unit testing +- [CentralPortalPlus](https://github.com/lalakii/central-portal-plus) by lalakii +- [test-summary-action](https://github.com/jeantessier/test-summary-action) by jeantessier +- [modrinth-publish](https://github.com/cloudnode-pro/modrinth-publish) by Zefir +- [discord-webhook](https://github.com/tsickert/discord-webhook) by tsickert + +### Compatibility +Here is to credits all the author of plugins +It partially repeat the the [Compatibility list](https://github.com/alexcrea/CustomAnvil/blob/v1.x.x/COMPATIBILITY.md) +- Big Thanks for H7KZ for [Disenchantment](https://github.com/H7KZ/Disenchantment) +- [Enchantment²](https://www.spigotmc.org/resources/enchants-squared-the-enchantsplus-rewrite-custom-enchantments-that-act-like-vanilla-ones.86747/) by Athlaeos +- [EcoEnchant](https://www.spigotmc.org/resources/ecoenchants-%E2%AD%95-250-enchantments-%E2%9C%85-create-custom-enchants-%E2%9C%A8-essentials-cmi-support.79573/) by Auxilor +- [ExcellentEnchants](https://www.spigotmc.org/resources/excellentenchants-%E2%AD%90-75-vanilla-like-enchantments.61693/) by NightExpress +- [HavenBags](https://www.spigotmc.org/resources/havenbags-shulker-like-player-bound-bags-1-17-1-21-4.110420/) by hyperdefined +- [AxPlayerWarp](https://modrinth.com/project/QDJHDKvi) by ArtillexStudios +- [ToolsStats](https://modrinth.com/project/oBZj9E15) by Valorless + +### Special Thanks + +Thanks for Microsoft leading me into using a better operating system +Thanks for all the users trying my plugin for these niche use cases +and for reporting issues and giving ideas ! + +Thanks coltonj96 for [UberEnchant](https://modrinth.com/plugin/uberenchant). +we may be incompatible with the anvil, but I do think it is a good alternative ! +I wish one day to work on cross compatibiltiy + diff --git a/README.md b/README.md index bf119e9..e1d7ec6 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,10 @@ It is expected to work on 1.18 to 1.21.7 minecraft servers running spigot or paper. (the plugin support of 1.16.5 to 1.17.1 is experimental and may encounter issues) -**Custom Anvil** was previously named **Unsafe Enchants+**. -It was renamed because it now affects every anvil aspect and not only unsafe enchants\ -**Custom Anvil** is based on [Unsafe Enchants](https://github.com/DelilahEve/UnsafeEnchants) by DelilahEve. - ### Download Locations: the plugin can be downloaded on -[Spigot](https://www.spigotmc.org/resources/custom-anvil.114884), - [modrinth](https://modrinth.com/plugin/customanvil), + [Modrinth](https://modrinth.com/plugin/customanvil), [Hangar](https://hangar.papermc.io/alexcrea/CustomAnvil) or here [on GitHub](https://github.com/alexcrea/CustomAnvil/releases/latest) @@ -57,32 +52,7 @@ anvilconfigreload or carl: Reload every config of this plugin customanvilconfig or configanvil: open a menu for administrator to edit plugin's config in game ``` ### Supported Plugins -Custom Anvil can be compatible with some custom enchantments and anvil mechanics plugins. - -Here is a list of supported custom enchantment plugins with support status: -- [Enchantment²](https://www.spigotmc.org/resources/enchants-squared-the-enchantsplus-rewrite-custom-enchantments-that-act-like-vanilla-ones.86747/): -Support by Custom Anvil but still experimental. Automatic configuration. - -- [EcoEnchant](https://www.spigotmc.org/resources/ecoenchants-%E2%AD%95-250-enchantments-%E2%9C%85-create-custom-enchants-%E2%9C%A8-essentials-cmi-support.79573/): -Support by Custom Anvil but still experimental. Need to use /anvilconfigreload or a server restart to add newly added enchantment. -Use EcoEnchant restriction system by default. - -- [ExcellentEnchants](https://www.spigotmc.org/resources/excellentenchants-%E2%AD%90-75-vanilla-like-enchantments.61693/): -Support by Custom Anvil but still experimental. Use ExcellentEnchants item type. - -- [Superenchants](https://modrinth.com/plugin/superenchants) -support by Superenchants. Use CustomAnvil to combine enchantment in anvil in survival. - -Here is a list of supported anvil mechanic plugins with support status: -- [Disenchantment](https://www.spigotmc.org/resources/disenchantment-1-21-1-1-20-6-new-book-splitting-mechanics.110741/) -support by Custom Anvil but still experimental. Mostly use Custom Anvil basic XP settings. (version >= 6.1.5) - -- [HavenBags](https://www.spigotmc.org/resources/havenbags-shulker-like-player-bound-bags-1-17-1-21-4.110420/) -support by Custom Anvil. Not really enchantment related but CustomAnvil should not impact bag upgrade and skin via anvil. (version >= 1.31.0) - -If you like Custom Anvil to support a specific plugin (custom enchant or anvil mechanic). -You can ask, but please note implementing compatibility will be considered -as low priority as I work for the plugin on my free time for free. +See the [Compatibility list](https://github.com/alexcrea/CustomAnvil/blob/v1.x.x/COMPATIBILITY.md) ### Overriding Too Expensive @@ -108,9 +78,12 @@ see [Here](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs) --- Custom anvil [use bstat](https://bstats.org/plugin/bukkit/Unsafe%20Enchants%20Plus/20923) for metric. You can [disable it](https://bstats.org/getting-started) if you like. +### Credits and Thanks +Credits and thanks can be seen [here](https://github.com/alexcrea/CustomAnvil/blob/v1.x.x/CREDITS.md) + ### Planned: - Better Folia support (make gui work. fix some dirty handled parts) -- Get restriction on unknown enchantments +- Get restriction on unknown enchantments (planned for V2) - More features for custom anvil craft ### Known issue: From 9cf06fbb93299e4609e83d614818e016df1e69a5 Mon Sep 17 00:00:00 2001 From: alexd <42614139+alexcrea@users.noreply.github.com> Date: Sun, 22 Feb 2026 04:15:30 +0100 Subject: [PATCH 076/207] add bstat in credit --- CREDITS.MD | 1 + 1 file changed, 1 insertion(+) diff --git a/CREDITS.MD b/CREDITS.MD index 5311480..8ac2464 100644 --- a/CREDITS.MD +++ b/CREDITS.MD @@ -11,6 +11,7 @@ Here dependencies are used by custom anvil - [test-summary-action](https://github.com/jeantessier/test-summary-action) by jeantessier - [modrinth-publish](https://github.com/cloudnode-pro/modrinth-publish) by Zefir - [discord-webhook](https://github.com/tsickert/discord-webhook) by tsickert +- [bstats](https://bstats.org/) for keeping me motivated ### Compatibility Here is to credits all the author of plugins From d801d8524271f466b06a1f3f49d844f4b51a9376 Mon Sep 17 00:00:00 2001 From: alexd <42614139+alexcrea@users.noreply.github.com> Date: Sun, 22 Feb 2026 04:19:07 +0100 Subject: [PATCH 077/207] better formating --- CREDITS.MD | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/CREDITS.MD b/CREDITS.MD index 8ac2464..e4f2533 100644 --- a/CREDITS.MD +++ b/CREDITS.MD @@ -1,7 +1,8 @@ -**Custom Anvil** is based on [Unsafe Enchants](https://github.com/DelilahEve/UnsafeEnchants) by DelilahEve. +Thanks **DelilahEve** for making [Unsafe Enchants](https://github.com/DelilahEve/UnsafeEnchants). \ +CustomAnvil was initially a fork of Unsafe Enchants where I wanted to add more and more and here we are now ! -Thanks for all the contributors of bukkit, spigot, the paper team and the adventure API developers -thanks JetBrain for making IntelliJ +Thanks for all the contributors of bukkit, spigot, the paper team and the adventure API developers \ +Thanks JetBrain for making IntelliJ ### Dependencies Here dependencies are used by custom anvil @@ -11,7 +12,7 @@ Here dependencies are used by custom anvil - [test-summary-action](https://github.com/jeantessier/test-summary-action) by jeantessier - [modrinth-publish](https://github.com/cloudnode-pro/modrinth-publish) by Zefir - [discord-webhook](https://github.com/tsickert/discord-webhook) by tsickert -- [bstats](https://bstats.org/) for keeping me motivated +- Thanks [bstats](https://bstats.org/) for keeping me motivated ### Compatibility Here is to credits all the author of plugins @@ -26,11 +27,11 @@ It partially repeat the the [Compatibility list](https://github.com/alexcrea/Cus ### Special Thanks -Thanks for Microsoft leading me into using a better operating system +Thanks for Microsoft leading me into using a better operating system \ Thanks for all the users trying my plugin for these niche use cases and for reporting issues and giving ideas ! Thanks coltonj96 for [UberEnchant](https://modrinth.com/plugin/uberenchant). -we may be incompatible with the anvil, but I do think it is a good alternative ! +we may be incompatible with the anvil, but I do think it is a good alternative ! \ I wish one day to work on cross compatibiltiy From 49abca2ccfee01eabcb425ada89ad91d539c0c92 Mon Sep 17 00:00:00 2001 From: alexd <42614139+alexcrea@users.noreply.github.com> Date: Fri, 27 Feb 2026 19:44:30 +0100 Subject: [PATCH 078/207] update checker --- CREDITS.MD | 1 + .../cuanvil/update/ModrinthUpdateChecker.java | 214 ++++++++++++++++++ src/main/kotlin/io/delilaheve/CustomAnvil.kt | 21 +- 3 files changed, 234 insertions(+), 2 deletions(-) create mode 100644 src/main/java/xyz/alexcrea/cuanvil/update/ModrinthUpdateChecker.java diff --git a/CREDITS.MD b/CREDITS.MD index e4f2533..b4a3ce8 100644 --- a/CREDITS.MD +++ b/CREDITS.MD @@ -13,6 +13,7 @@ Here dependencies are used by custom anvil - [modrinth-publish](https://github.com/cloudnode-pro/modrinth-publish) by Zefir - [discord-webhook](https://github.com/tsickert/discord-webhook) by tsickert - Thanks [bstats](https://bstats.org/) for keeping me motivated +- [ModrinthUpdateChecker](https://github.com/Clickism/ModrinthUpdateChecker) by Clickism and thanks to the modrinth team ### Compatibility Here is to credits all the author of plugins diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/ModrinthUpdateChecker.java b/src/main/java/xyz/alexcrea/cuanvil/update/ModrinthUpdateChecker.java new file mode 100644 index 0000000..489c636 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/update/ModrinthUpdateChecker.java @@ -0,0 +1,214 @@ +/* + * MIT License + * + * Copyright (c) 2025 Clickism + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +package xyz.alexcrea.cuanvil.update; + +import com.google.gson.*; +import org.jetbrains.annotations.Nullable; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Utility class to check for newer versions of a project hosted on Modrinth. + */ +public class ModrinthUpdateChecker { + + private static final String API_URL = "https://api.modrinth.com/v2/project/{id}/version"; + + private final String projectId; + private final String loader; + @Nullable + private final String minecraftVersion; + + @Nullable + private Boolean featured = null; + + @Nullable + public Consumer onError = null; + @Nullable + public Function getRawVersion = ModrinthUpdateChecker::getRawVersion; + + /** + * Create a new update checker for the given project. + * This will check the latest version for the given loader and any minecraft version. + * + * @param projectId the project ID + * @param loader the loader + */ + public ModrinthUpdateChecker(String projectId, String loader) { + this(projectId, loader, null); + } + + /** + * Create a new update checker for the given project. + * This will check the latest version for the given loader and minecraft version. + * + * @param projectId the project ID + * @param loader the loader + * @param minecraftVersion the minecraft version, or null for any version + */ + public ModrinthUpdateChecker(String projectId, String loader, @Nullable String minecraftVersion) { + this.projectId = projectId; + this.loader = loader; + this.minecraftVersion = minecraftVersion; + } + + /** + * Check the latest version of the project for the given loader and minecraft version + * and call the consumer with it. + * + * @param consumer the consumer + */ + public void checkVersion(Consumer consumer) { + try { + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .uri(prepareURI()) + .GET() + .build(); + + client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenAcceptAsync(response -> { + if (response.statusCode() != 200) { + if(onError != null) + onError.accept(new RuntimeException("wrong response status code: " + response.statusCode())); + return; + } + JsonArray versionsArray = JsonParser.parseString(response.body()).getAsJsonArray(); + String latestVersion = getLatestVersion(versionsArray); + if (latestVersion == null) { + if(onError != null) + onError.accept(new RuntimeException("latest version is null")); + return; + } + consumer.accept(latestVersion); + }); + } catch (Exception e) { + if(onError != null) onError.accept(e); + } + } + + /** + * Get the latest compatible version from the versions array. + * + * @param versions the versions array + * @return the latest compatible version + */ + @Nullable + protected String getLatestVersion(JsonArray versions) { + return versions.asList().stream().findFirst() + .map(JsonElement::getAsJsonObject) + .map(version -> version.get("version_number").getAsString()) + .map(getRawVersion != null ? getRawVersion : (v -> v)) + .orElse(null); + } + + /** + * Gets the raw version from a version string. + * i.E: "fabric-1.2+1.17.1" -> "1.2" + * + * @param version the version string + * @return the raw version string + */ + public static String getRawVersion(String version) { + if (version.isEmpty()) return version; + version = version.replaceAll("^\\D+", ""); + String[] split = version.split("\\+"); + return split[0]; + } + + /** + * Prepare this request uri based on current parameters. + * @return the request uri + */ + private URI prepareURI() { + var url = new StringBuilder(API_URL.replace("{id}", projectId)); + + var parameters = prepareParameters(); + String[] paramArray = new String[parameters.size()]; + int i = 0; + for (Map.Entry entry : parameters.entrySet()) { + paramArray[i++] = entry.getKey() + '=' + entry.getValue(); + } + url.append('?').append(String.join("&", paramArray)); + + return URI.create(url.toString()); + } + + /** + * Get the parameters for the version request. + * + * @return a map of key-value map of the request parameters + */ + private Map prepareParameters(){ + var parameters = new HashMap(); + + parameters.put("loaders", List.of(loader).toString()); + if(minecraftVersion != null) parameters.put("game_versions", List.of(minecraftVersion).toString()); + if(featured != null) parameters.put("featured", featured.toString()); + + parameters.put("include_changelog", "false"); + return parameters; + } + + /** + * Only get featured or non-featured versions. + * Null represent no filter. + * @param featured should be restricted to featured version ? default null if not called + * @return this + */ + public ModrinthUpdateChecker setFeatured(@Nullable Boolean featured) { + this.featured = featured; + return this; + } + + /** + * Function called on error calling the api. + * @param onError What should happen on error + * @return this + */ + public ModrinthUpdateChecker setOnError(@Nullable Consumer onError) { + this.onError = onError; + return this; + } + + /** + * Set the function to get raw version from the modrinth version. + * If null provided raw version will act as in the identity function. + * @param getRawVersion The function transforming modrinth version to raw version + * @return this + */ + public ModrinthUpdateChecker setGetRawVersion(@Nullable Function getRawVersion) { + this.getRawVersion = getRawVersion; + return this; + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/delilaheve/CustomAnvil.kt b/src/main/kotlin/io/delilaheve/CustomAnvil.kt index ca25147..b48f4a6 100644 --- a/src/main/kotlin/io/delilaheve/CustomAnvil.kt +++ b/src/main/kotlin/io/delilaheve/CustomAnvil.kt @@ -18,6 +18,7 @@ import xyz.alexcrea.cuanvil.listener.AnvilCloseListener import xyz.alexcrea.cuanvil.listener.AnvilResultListener import xyz.alexcrea.cuanvil.listener.ChatEventListener import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener +import xyz.alexcrea.cuanvil.update.ModrinthUpdateChecker import xyz.alexcrea.cuanvil.update.PluginSetDefault import xyz.alexcrea.cuanvil.update.UpdateHandler import xyz.alexcrea.cuanvil.util.Metrics @@ -31,8 +32,9 @@ import java.util.logging.Level open class CustomAnvil : JavaPlugin() { companion object { - // bstats plugin id + // pluginIDS private const val bstatsPluginId = 20923 + private const val modrinthPluginID = "S75Ueiq9" // Permission string required to use the plugin's features const val affectedByPluginPermission = "ca.affected" @@ -174,10 +176,25 @@ open class CustomAnvil : JavaPlugin() { logger.warning("Please note CustomAnvil is a more recent version of UnsafeEnchantsPlus") } - if(!PlatformUtil.isPaper) { + val isPaper = PlatformUtil.isPaper + if(!isPaper) { logger.warning("It seems you are using spigot") logger.warning("Please take notice that spigot is less supported than paper and derivatives") } + + val loader = if(isPaper) "paper" else "spigot" + + val version = description.version + val featured = if(version.contains("dev")) null else true + + ModrinthUpdateChecker(modrinthPluginID, loader, null) + .setFeatured(featured) + .setOnError { logger.log(Level.WARNING, "error trying to fetch latest update", it) } + .checkVersion { latestver: String? -> + if(latestver == null || version.contains(latestver)) return@checkVersion + + logger.warning("An update may be available: $latestver") + } } private fun registerListeners() { From 196392e20633233c28a8602113a7f12bd48d84a0 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 10 Feb 2026 14:26:53 +0100 Subject: [PATCH 079/207] workflow invert & simplify logic to put not "" first --- .github/workflows/gradle.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index b5dc6b6..01518f4 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -151,7 +151,7 @@ jobs: token: ${{ secrets.MODRINTH_TOKEN }} project: S75Ueiq9 name: ${{ github.event.release.name }} - version: ${{ github.event.release.tag_name }}${{ github.event.release.prerelease == false && '' || '-pre' }} + version: ${{ github.event.release.tag_name }}${{ github.event.release.prerelease && '-pre' || '' }} loaders: ${{ env.MODRINTH_PLATFORMS }} game-versions: ${{ env.MODRINTH_VERSIONS }} channel: ${{ github.event.release.prerelease == false && 'release' || 'beta' }} @@ -165,7 +165,7 @@ jobs: webhook-url: ${{ secrets.RELEASE_WEBHOOK_URL }} content: | ${{ github.event.release.prerelease == false && '<@&1338546156325568642>' || '<@&1352296092989001768>' }} - # New ${{ github.event.release.prerelease == false && '' || 'beta' }} version of custom anvil ! <:CustomAnvil:1262550667986342001>([Modrinth](https://modrinth.com/plugin/customanvil), [Hangar](https://hangar.papermc.io/alexcrea/CustomAnvil) and [GitHub](${{ github.event.release.html_url }}) links) + # New ${{ github.event.release.prerelease && 'beta' || '' }} version of custom anvil ! <:CustomAnvil:1262550667986342001>([Modrinth](https://modrinth.com/plugin/customanvil), [Hangar](https://hangar.papermc.io/alexcrea/CustomAnvil) and [GitHub](${{ github.event.release.html_url }}) links) -# note: automated release. spigot is not uploaded yet. ${{ env.RELEASE_CHANGELOG }} \ No newline at end of file From 3eb07a8c09ee1fff0afbe60dae0e4a2a175f46ad Mon Sep 17 00:00:00 2001 From: alexcrea <42614139+alexcrea@users.noreply.github.com> Date: Fri, 12 Sep 2025 12:41:46 +0200 Subject: [PATCH 080/207] prepare generic command --- src/main/kotlin/io/delilaheve/CustomAnvil.kt | 3 + .../alexcrea/cuanvil/command/CASubCommand.kt | 40 ++++++++++ .../cuanvil/command/CustomAnvilCmd.kt | 77 +++++++++++++++++++ .../cuanvil/command/EditConfigExecutor.kt | 17 ++-- .../cuanvil/command/ReloadExecutor.kt | 14 +++- src/main/resources/plugin.yml | 5 ++ 6 files changed, 148 insertions(+), 8 deletions(-) create mode 100644 src/main/kotlin/xyz/alexcrea/cuanvil/command/CASubCommand.kt create mode 100644 src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCmd.kt diff --git a/src/main/kotlin/io/delilaheve/CustomAnvil.kt b/src/main/kotlin/io/delilaheve/CustomAnvil.kt index b48f4a6..7e1d959 100644 --- a/src/main/kotlin/io/delilaheve/CustomAnvil.kt +++ b/src/main/kotlin/io/delilaheve/CustomAnvil.kt @@ -6,6 +6,7 @@ import org.bukkit.configuration.file.YamlConfiguration import org.bukkit.plugin.java.JavaPlugin import xyz.alexcrea.cuanvil.api.event.CAConfigReadyEvent import xyz.alexcrea.cuanvil.api.event.CAEnchantRegistryReadyEvent +import xyz.alexcrea.cuanvil.command.CustomAnvilCmd import xyz.alexcrea.cuanvil.command.EditConfigExecutor import xyz.alexcrea.cuanvil.command.ReloadExecutor import xyz.alexcrea.cuanvil.config.ConfigHolder @@ -290,6 +291,8 @@ open class CustomAnvil : JavaPlugin() { command = getCommand(commandConfigName) command?.setExecutor(EditConfigExecutor()) + + CustomAnvilCmd(this) } } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/CASubCommand.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/CASubCommand.kt new file mode 100644 index 0000000..7558127 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/CASubCommand.kt @@ -0,0 +1,40 @@ +package xyz.alexcrea.cuanvil.command + +import org.bukkit.ChatColor +import org.bukkit.command.Command +import org.bukkit.command.CommandExecutor +import org.bukkit.command.CommandSender + +abstract class CASubCommand: CommandExecutor { + + private var alreadySaid = false; + override fun onCommand( + sender: CommandSender, + cmd: Command, + cmdstr: String, + args: Array + ): Boolean { + if(!alreadySaid){ + sender.sendMessage(ChatColor.RED.toString() + + "Please not that this command will be replaced as a subcommand of `/customanvil`") + alreadySaid = true + } + + return executeCommand(sender, cmd, cmdstr, args) + } + + abstract fun executeCommand( + sender: CommandSender, + cmd: Command, + cmdstr: String, + args: Array): Boolean + + open fun allowed(sender: CommandSender): Boolean { + return true + } + + open fun tabCompleter(list: MutableList) { + + } + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCmd.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCmd.kt new file mode 100644 index 0000000..5333b94 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCmd.kt @@ -0,0 +1,77 @@ +package xyz.alexcrea.cuanvil.command + +import com.google.common.collect.ImmutableMap +import io.delilaheve.CustomAnvil +import org.bukkit.command.Command +import org.bukkit.command.CommandExecutor +import org.bukkit.command.CommandSender +import org.bukkit.command.TabCompleter +import java.util.ArrayList +import java.util.Arrays + +class CustomAnvilCmd(plugin: CustomAnvil) : CommandExecutor, TabCompleter { + + // Name of the generic command + companion object { + private const val genericCommandName = "customanvil" + } + + private val editConfigCommand = EditConfigExecutor() + private val commands: ImmutableMap + + init { + commands = ImmutableMap.of( + "gui", editConfigCommand, + "reload", ReloadExecutor() + ) + + val self = plugin.getCommand(genericCommandName)!! + self.setExecutor(this) + self.tabCompleter = this + } + + override fun onCommand( + sender: CommandSender, + cmd: Command, + cmdstr: String, + args: Array + ): Boolean { + // Find sub command to execute based on the provided command name + val subcmd: CASubCommand? = if(args.isEmpty()) { + editConfigCommand + }else { + commands[args[0].lowercase()] + } + + if(subcmd == null) { + sender.sendMessage("Invalid subcommand. run `$cmdstr help` to see available commands") + return true + } + + val newargs = args.copyOfRange(1, args.size) + return subcmd.executeCommand(sender, cmd, cmdstr, newargs) + } + + override fun onTabComplete( + sender: CommandSender, + cmd: Command, + cmdstr: String, + args: Array + ): MutableList { + val result = ArrayList() + if(args.isEmpty()) { + for (cmd in commands) { + result.add(cmd.key) + } + } else { + val subcmd = commands[args[0].lowercase()] + subcmd?.tabCompleter(result) + } + + //assumed all provided tab completed string are lowercase + return result.stream() + .filter { it.startsWith(args[args.size - 1]) } + .sorted() + .toList() + } +} diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/EditConfigExecutor.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/EditConfigExecutor.kt index f90f765..d489db2 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/EditConfigExecutor.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/EditConfigExecutor.kt @@ -2,17 +2,21 @@ package xyz.alexcrea.cuanvil.command import io.delilaheve.CustomAnvil import org.bukkit.command.Command -import org.bukkit.command.CommandExecutor import org.bukkit.command.CommandSender import org.bukkit.entity.HumanEntity import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil import xyz.alexcrea.cuanvil.gui.config.MainConfigGui import xyz.alexcrea.cuanvil.gui.util.GuiGlobalActions -class EditConfigExecutor : CommandExecutor { +class EditConfigExecutor: CASubCommand() { - override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array): Boolean { - if (!sender.hasPermission(CustomAnvil.editConfigPermission)) { + override fun executeCommand(sender: CommandSender, + cmd: Command, + cmdstr: String, + args: Array): Boolean { + if (sender !is HumanEntity) return false + + if (!allowed(sender)) { sender.sendMessage(GuiGlobalActions.NO_EDIT_PERM) return false } @@ -25,10 +29,13 @@ class EditConfigExecutor : CommandExecutor { return false } - if (sender !is HumanEntity) return false MainConfigGui.getInstance().show(sender) return true } + override fun allowed(sender: CommandSender): Boolean { + return sender.hasPermission(CustomAnvil.editConfigPermission) + } + } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/ReloadExecutor.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/ReloadExecutor.kt index dc2fe8e..f3f97a7 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/ReloadExecutor.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/ReloadExecutor.kt @@ -11,9 +11,13 @@ import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.gui.config.global.* import xyz.alexcrea.cuanvil.update.UpdateHandler -class ReloadExecutor : CommandExecutor { - override fun onCommand(sender: CommandSender, cmd: Command, cmdstr: String, args: Array): Boolean { - if (!sender.hasPermission(CustomAnvil.commandReloadPermission)) { +class ReloadExecutor : CASubCommand() { + + override fun executeCommand(sender: CommandSender, + cmd: Command, + cmdstr: String, + args: Array): Boolean { + if (!allowed(sender)) { sender.sendMessage("§cYou do not have permission to reload the config") return false } @@ -31,6 +35,10 @@ class ReloadExecutor : CommandExecutor { return commandSuccess } + override fun allowed(sender: CommandSender): Boolean { + return sender.hasPermission(CustomAnvil.commandReloadPermission) + } + /** * Execute the command, return true if success or false otherwise */ diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 9449f8b..f90f975 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -10,6 +10,8 @@ authors: [ DelilahEve, alexcrea ] libraries: [${libraries}] commands: + customanvil: + description: Generic command for custom anvil anvilconfigreload: description: Reload every config of this plugin permission: ca.command.reload @@ -36,6 +38,9 @@ permissions: ca.command.reload: default: op description: Allow administrator to reload the plugin's configs + ca.command.debug: + default: op + description: Get debug information about the plugin and server ca.config.edit: default: op description: Allow administrator to edit the plugin's config in game From 5f707c7397bab7b34fa1d35045aa43be35901314 Mon Sep 17 00:00:00 2001 From: alexcrea <42614139+alexcrea@users.noreply.github.com> Date: Tue, 16 Sep 2025 15:39:57 +0200 Subject: [PATCH 081/207] progress --- src/main/kotlin/io/delilaheve/CustomAnvil.kt | 6 ++ .../cuanvil/command/CustomAnvilCmd.kt | 4 +- .../alexcrea/cuanvil/command/Diagnostic.kt | 77 +++++++++++++++++++ src/main/resources/plugin.yml | 2 +- src/test/resources/plugin.yml | 9 ++- 5 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 src/main/kotlin/xyz/alexcrea/cuanvil/command/Diagnostic.kt diff --git a/src/main/kotlin/io/delilaheve/CustomAnvil.kt b/src/main/kotlin/io/delilaheve/CustomAnvil.kt index 7e1d959..e870bf0 100644 --- a/src/main/kotlin/io/delilaheve/CustomAnvil.kt +++ b/src/main/kotlin/io/delilaheve/CustomAnvil.kt @@ -49,9 +49,13 @@ open class CustomAnvil : JavaPlugin() { // Permission string required to reload the config const val commandReloadPermission = "ca.command.reload" + // Permission string required to get diagnostic data + const val diagnosticPermission = "ca.command.diagnostic" + // Permission string required to edit the plugin's config const val editConfigPermission = "ca.config.edit" + // Command Name to reload the config const val commandReloadName = "anvilconfigreload" @@ -292,6 +296,8 @@ open class CustomAnvil : JavaPlugin() { command = getCommand(commandConfigName) command?.setExecutor(EditConfigExecutor()) + println(getCommand("customanvil")) + println(getCommand("customanvila")) CustomAnvilCmd(this) } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCmd.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCmd.kt index 5333b94..5de1c04 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCmd.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCmd.kt @@ -22,9 +22,11 @@ class CustomAnvilCmd(plugin: CustomAnvil) : CommandExecutor, TabCompleter { init { commands = ImmutableMap.of( "gui", editConfigCommand, - "reload", ReloadExecutor() + "reload", ReloadExecutor(), + "diagnostic", Diagnostic(), ) + println(plugin.getCommand(genericCommandName)) val self = plugin.getCommand(genericCommandName)!! self.setExecutor(this) self.tabCompleter = this diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/Diagnostic.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/Diagnostic.kt new file mode 100644 index 0000000..9752894 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/Diagnostic.kt @@ -0,0 +1,77 @@ +package xyz.alexcrea.cuanvil.command + +import io.delilaheve.CustomAnvil +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.Bukkit +import org.bukkit.ChatColor +import org.bukkit.command.Command +import org.bukkit.command.CommandSender +import org.bukkit.entity.HumanEntity + +class Diagnostic: CASubCommand() { + + companion object{ + private const val NO_DIAG_PERM = "You do not have permission to diagnostic this server" + } + + override fun executeCommand( + sender: CommandSender, + cmd: Command, + cmdstr: String, + args: Array + ): Boolean { + if (!allowed(sender)) { + sender.sendMessage(NO_DIAG_PERM) + return false + } + + val stb = StringBuilder("```\n") + try { + diagnostic(stb) + } catch(e: Exception){ + // TODO append error message to diag + TODO("error not handled yet $e") + } + + stb.append("\n```") + + if (sender is HumanEntity) { + val message = TextComponent(ChatColor.GREEN.toString() + "Click to copy diagnostic 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()); + } + + return true + } + + override fun allowed(sender: CommandSender): Boolean { + return sender.hasPermission(CustomAnvil.diagnosticPermission) + } + + fun diagnostic(stb: StringBuilder){ + stb.append("Server Info\n"); + stb.append("Plugin Version: ").append(CustomAnvil.instance.description.version).append("\n"); + stb.append("Server Version: ").append(Bukkit.getVersion()).append(" (").append(Bukkit.getName()).append(')').append("\n"); + stb.append("Plugin Enabled: ").append(if(CustomAnvil.instance.isEnabled) "Yes" else "No").append("\n"); + //stb.append("NMS type: ").append(NMSMapper.hasNMS() ? "Yes" : "No").append("\n"); + stb.append("Java Version: ").append(System.getProperty("java.version")).append("\n"); + stb.append("OS: ").append(System.getProperty("os.name")).append(" ") + .append(System.getProperty("os.version")) + .append(System.getProperty("os.arch")) + .append("\n\n"); + stb.append("Architecture: ").append(System.getProperty("os.arch")).append("\n\n"); + + + + } + + +} \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index f90f975..03ed5aa 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -38,7 +38,7 @@ permissions: ca.command.reload: default: op description: Allow administrator to reload the plugin's configs - ca.command.debug: + ca.command.diagnostic: default: op description: Get debug information about the plugin and server ca.config.edit: diff --git a/src/test/resources/plugin.yml b/src/test/resources/plugin.yml index c116cce..37fb95a 100644 --- a/src/test/resources/plugin.yml +++ b/src/test/resources/plugin.yml @@ -11,6 +11,8 @@ libraries: - org.jetbrains.kotlin:kotlin-stdlib:2.0.21 commands: + customanvil: + description: Generic command for custom anvil anvilconfigreload: description: Reload every config of this plugin permission: ca.command.reload @@ -37,6 +39,9 @@ permissions: ca.command.reload: default: op description: Allow administrator to reload the plugin's configs + ca.command.diagnostic: + default: op + description: Get debug information about the plugin and server ca.config.edit: default: op description: Allow administrator to edit the plugin's config in game @@ -55,8 +60,7 @@ permissions: default: op description: Allow player to edit lore via paper if enabled (toggleable) - -# soft depend on old name (UnsafeEnchantsPlus), so I can disable it if it is on the same server (old name for this plugin) +# soft depend on old name of this plugin (UnsafeEnchantsPlus), so I can disable it if it is on the same server # Also depend to other plugin for compatibility softdepend: - UnsafeEnchantsPlus @@ -66,3 +70,4 @@ softdepend: - EcoEnchants - eco - ExcellentEnchants + - HavenBags From 63353c620593f34b9f6ddc52fc42a3a5e7febafe Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 2 Mar 2026 03:24:42 +0100 Subject: [PATCH 082/207] progress on diagnostic command --- src/main/kotlin/io/delilaheve/CustomAnvil.kt | 11 +- .../alexcrea/cuanvil/command/CASubCommand.kt | 6 +- .../cuanvil/command/CustomAnvilCmd.kt | 22 +- .../alexcrea/cuanvil/command/DebugExecutor.kt | 25 +++ .../alexcrea/cuanvil/command/Diagnostic.kt | 77 ------- .../cuanvil/command/DiagnosticExecutor.kt | 195 ++++++++++++++++++ 6 files changed, 243 insertions(+), 93 deletions(-) create mode 100644 src/main/kotlin/xyz/alexcrea/cuanvil/command/DebugExecutor.kt delete mode 100644 src/main/kotlin/xyz/alexcrea/cuanvil/command/Diagnostic.kt create mode 100644 src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt diff --git a/src/main/kotlin/io/delilaheve/CustomAnvil.kt b/src/main/kotlin/io/delilaheve/CustomAnvil.kt index e870bf0..902b76d 100644 --- a/src/main/kotlin/io/delilaheve/CustomAnvil.kt +++ b/src/main/kotlin/io/delilaheve/CustomAnvil.kt @@ -68,6 +68,8 @@ open class CustomAnvil : JavaPlugin() { // Chat message listener lateinit var chatListener: ChatEventListener + var latestVer: String? = null + /** * Logging handler */ @@ -195,10 +197,11 @@ open class CustomAnvil : JavaPlugin() { ModrinthUpdateChecker(modrinthPluginID, loader, null) .setFeatured(featured) .setOnError { logger.log(Level.WARNING, "error trying to fetch latest update", it) } - .checkVersion { latestver: String? -> - if(latestver == null || version.contains(latestver)) return@checkVersion + .checkVersion { latestVer: String? -> + CustomAnvil.latestVer = latestVer + if(latestVer == null || version.contains(latestVer)) return@checkVersion - logger.warning("An update may be available: $latestver") + logger.warning("An update may be available: $latestVer") } } @@ -296,8 +299,6 @@ open class CustomAnvil : JavaPlugin() { command = getCommand(commandConfigName) command?.setExecutor(EditConfigExecutor()) - println(getCommand("customanvil")) - println(getCommand("customanvila")) CustomAnvilCmd(this) } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/CASubCommand.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/CASubCommand.kt index 7558127..85c1a58 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/CASubCommand.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/CASubCommand.kt @@ -33,8 +33,10 @@ abstract class CASubCommand: CommandExecutor { return true } - open fun tabCompleter(list: MutableList) { - + open fun tabCompleter( + sender: CommandSender, + args: Array, + list: MutableList) { } } \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCmd.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCmd.kt index 5de1c04..421ec7b 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCmd.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCmd.kt @@ -17,14 +17,14 @@ class CustomAnvilCmd(plugin: CustomAnvil) : CommandExecutor, TabCompleter { } private val editConfigCommand = EditConfigExecutor() - private val commands: ImmutableMap + private val commands = ImmutableMap.of( + "gui", editConfigCommand, + "reload", ReloadExecutor(), + "diagnostic", DiagnosticExecutor(), + //"debug", DebugExecutor(), + ) init { - commands = ImmutableMap.of( - "gui", editConfigCommand, - "reload", ReloadExecutor(), - "diagnostic", Diagnostic(), - ) println(plugin.getCommand(genericCommandName)) val self = plugin.getCommand(genericCommandName)!! @@ -61,13 +61,17 @@ class CustomAnvilCmd(plugin: CustomAnvil) : CommandExecutor, TabCompleter { args: Array ): MutableList { val result = ArrayList() - if(args.isEmpty()) { + if(args.size < 3) { for (cmd in commands) { result.add(cmd.key) } } else { - val subcmd = commands[args[0].lowercase()] - subcmd?.tabCompleter(result) + val subcmd = commands[args[1].lowercase()] + + if(subcmd != null) { + val newArgs = args.copyOfRange(1, args.size) + subcmd.tabCompleter(sender, newArgs, result) + } } //assumed all provided tab completed string are lowercase diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/DebugExecutor.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/DebugExecutor.kt new file mode 100644 index 0000000..e7cbe9e --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/DebugExecutor.kt @@ -0,0 +1,25 @@ +package xyz.alexcrea.cuanvil.command + +import io.delilaheve.CustomAnvil +import org.bukkit.command.Command +import org.bukkit.command.CommandSender +import java.util.logging.Level + +class DebugExecutor : CASubCommand() { + + override fun executeCommand( + sender: CommandSender, + cmd: Command, + cmdstr: String, + args: Array + ): Boolean { + CustomAnvil.instance.logger.log(Level.SEVERE, "aaaaaaaaaaaaaaaaaaa"); + return true + } + + override fun tabCompleter(sender: CommandSender, args: Array, list: MutableList) { + //TODO + + } + +} diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/Diagnostic.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/Diagnostic.kt deleted file mode 100644 index 9752894..0000000 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/Diagnostic.kt +++ /dev/null @@ -1,77 +0,0 @@ -package xyz.alexcrea.cuanvil.command - -import io.delilaheve.CustomAnvil -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.Bukkit -import org.bukkit.ChatColor -import org.bukkit.command.Command -import org.bukkit.command.CommandSender -import org.bukkit.entity.HumanEntity - -class Diagnostic: CASubCommand() { - - companion object{ - private const val NO_DIAG_PERM = "You do not have permission to diagnostic this server" - } - - override fun executeCommand( - sender: CommandSender, - cmd: Command, - cmdstr: String, - args: Array - ): Boolean { - if (!allowed(sender)) { - sender.sendMessage(NO_DIAG_PERM) - return false - } - - val stb = StringBuilder("```\n") - try { - diagnostic(stb) - } catch(e: Exception){ - // TODO append error message to diag - TODO("error not handled yet $e") - } - - stb.append("\n```") - - if (sender is HumanEntity) { - val message = TextComponent(ChatColor.GREEN.toString() + "Click to copy diagnostic 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()); - } - - return true - } - - override fun allowed(sender: CommandSender): Boolean { - return sender.hasPermission(CustomAnvil.diagnosticPermission) - } - - fun diagnostic(stb: StringBuilder){ - stb.append("Server Info\n"); - stb.append("Plugin Version: ").append(CustomAnvil.instance.description.version).append("\n"); - stb.append("Server Version: ").append(Bukkit.getVersion()).append(" (").append(Bukkit.getName()).append(')').append("\n"); - stb.append("Plugin Enabled: ").append(if(CustomAnvil.instance.isEnabled) "Yes" else "No").append("\n"); - //stb.append("NMS type: ").append(NMSMapper.hasNMS() ? "Yes" : "No").append("\n"); - stb.append("Java Version: ").append(System.getProperty("java.version")).append("\n"); - stb.append("OS: ").append(System.getProperty("os.name")).append(" ") - .append(System.getProperty("os.version")) - .append(System.getProperty("os.arch")) - .append("\n\n"); - stb.append("Architecture: ").append(System.getProperty("os.arch")).append("\n\n"); - - - - } - - -} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt new file mode 100644 index 0000000..23ab4b9 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt @@ -0,0 +1,195 @@ +package xyz.alexcrea.cuanvil.command + +import io.delilaheve.CustomAnvil +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.Bukkit +import org.bukkit.ChatColor +import org.bukkit.command.Command +import org.bukkit.command.CommandSender +import org.bukkit.entity.HumanEntity +import org.bukkit.event.inventory.PrepareAnvilEvent +import org.bukkit.plugin.Plugin +import org.bukkit.plugin.RegisteredListener +import xyz.alexcrea.cuanvil.dependency.DependencyManager +import xyz.alexcrea.cuanvil.dependency.packet.NoPacketManager +import xyz.alexcrea.cuanvil.dependency.packet.ProtocoLibWrapper +import xyz.alexcrea.cuanvil.dependency.packet.versions.PaperPacketManager +import java.util.* +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" + } + + enum class DiagParams(val value: String) { + OS_PRIVACY("os_privacy"), + PLUGIN_PRIVACY("plugin_privacy"), + //NO_TEST("no_anvil_test"), + } + + private fun fetchParameters(args: Array): EnumSet { + val result = EnumSet.noneOf(DiagParams::class.java) + val argSet = HashSet() + + for (string in args) { + argSet.add(string.lowercase()) + } + + for (param in DiagParams.entries) { + if(argSet.contains(param.value)) + result.add(param) + } + + return result + } + + override fun tabCompleter( + sender: CommandSender, + args: Array, + list: MutableList) { + if(!allowed(sender)) return + + val map = fetchParameters(args) + for (param in DiagParams.entries) { + if(!map.contains(param)) + list.add(param.value) + } + + } + + override fun executeCommand( + sender: CommandSender, + cmd: Command, + cmdstr: String, + args: Array + ): Boolean { + if (!allowed(sender)) { + sender.sendMessage(NO_DIAG_PERM) + return false + } + + val stb = StringBuilder("```\n") + val params = fetchParameters(args) + try { + diagnostic(stb, params) + } catch(e: Exception){ + stb.append("\n\nError happened trying to get diagnostic data:\n") + .append(e.message).append("\n") + .append(e.stackTrace.joinToString("\n")) + } + + stb.append("\n```") + + if (sender is HumanEntity) { + val message = TextComponent(ChatColor.GREEN.toString() + "Click to copy diagnostic 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()); + } + + return true + } + + override fun allowed(sender: CommandSender): Boolean { + return sender.hasPermission(CustomAnvil.diagnosticPermission) + } + + fun diagnostic(stb: StringBuilder, params: Set){ + stb.append("Server Info\n") + stb.append("Plugin Version: ").append(CustomAnvil.instance.description.version).append("\n") + stb.append("Latest Update: ").append(CustomAnvil.latestVer).append('\n') + stb.append("Server Version: ").append(Bukkit.getVersion()).append(" (").append(Bukkit.getName()).append(')').append("\n") + stb.append("Plugin Enabled: ").append(if(CustomAnvil.instance.isEnabled) "Yes" else "No").append("\n") + stb.append("NMS type: ").append(fetchNMSType()) + if(!params.contains(DiagParams.OS_PRIVACY)) { + stb.append("Java Version: ").append(System.getProperty("java.version")).append("\n") + stb.append("OS: ").append(System.getProperty("os.name")).append(" ") + .append(System.getProperty("os.version")) + .append(System.getProperty("os.arch")) + .append("\n\n") + } + + if(!params.contains(DiagParams.PLUGIN_PRIVACY)) { + pluginListDiag(stb) + } + prepareAnvilListeners(stb) + + stb.append("\n\n") + } + + private fun fetchNMSType(): String { + val packetManager = DependencyManager.packetManager + val packetManagerClass = packetManager.javaClass + + val result: String + if(packetManagerClass == PaperPacketManager::class.java) { + result = "Paper NMS" + } else if(packetManagerClass == ProtocoLibWrapper::class.java) { + result = "Protocolib" + } else if(packetManagerClass == NoPacketManager::class.java) { + result = "None" + } else { + result = "Version Specific" + } + + return "$result ${if(packetManager.canSetInstantBuild) '✅' else '❌'}" + } + + private val Plugin.pluginNameDisplay: String + get() { + return this.name + " v" + this.description.version + } + + private fun pluginListDiag(stb: StringBuilder) { + val enabledPlugins: MutableList = ArrayList() + val disabledPlugins: MutableList = ArrayList() + for (plugin in Bukkit.getPluginManager().plugins) { + if (plugin.isEnabled) { + enabledPlugins.add(plugin) + } else { + disabledPlugins.add(plugin) + } + } + + stb.append("Enabled Plugins: ").append( + enabledPlugins.stream() + .map { plugin -> plugin!!.pluginNameDisplay } + .reduce { a: String?, b: String? -> "$a, $b" }.orElse("None") + ).append("\n") + + stb.append("Disabled Plugins: ").append( + disabledPlugins.stream() + .map { plugin -> plugin!!.pluginNameDisplay } + .reduce { a: String?, b: String? -> "$a, $b" }.orElse("None") + ).append("\n") + } + + fun prepareAnvilListeners(stb: StringBuilder) { + val eventListeners: MutableSet = Arrays + .stream( + PrepareAnvilEvent + .getHandlerList() + .getRegisteredListeners() + ) + .map { obj: RegisteredListener? -> obj!!.plugin } + .collect(Collectors.toSet()) + + eventListeners.remove(CustomAnvil.instance) + stb.append("Prepare Anvil Listeners: ").append( + if (eventListeners.isEmpty()) "None" else eventListeners.stream() + .map { plugin -> plugin!!.pluginNameDisplay } + .reduce { a: String?, b: String? -> "$a, $b" }.orElse("None") + ).append("\n\n") + } + +} \ No newline at end of file From 63f2f16b9fc6bf2d6a7ef309a6ec922527b0bfb0 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 2 Mar 2026 19:03:33 +0100 Subject: [PATCH 083/207] add anvil simulation test for diag --- .../cuanvil/command/CustomAnvilCmd.kt | 7 +- .../cuanvil/command/DiagnosticExecutor.kt | 159 ++++++++++++++---- .../cuanvil/listener/PrepareAnvilListener.kt | 8 + src/main/resources/plugin.yml | 2 + 4 files changed, 142 insertions(+), 34 deletions(-) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCmd.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCmd.kt index 421ec7b..2a46fa8 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCmd.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCmd.kt @@ -7,7 +7,6 @@ import org.bukkit.command.CommandExecutor import org.bukkit.command.CommandSender import org.bukkit.command.TabCompleter import java.util.ArrayList -import java.util.Arrays class CustomAnvilCmd(plugin: CustomAnvil) : CommandExecutor, TabCompleter { @@ -61,12 +60,12 @@ class CustomAnvilCmd(plugin: CustomAnvil) : CommandExecutor, TabCompleter { args: Array ): MutableList { val result = ArrayList() - if(args.size < 3) { - for (cmd in commands) { + if(args.size < 2) { + for (cmd in commands) { result.add(cmd.key) } } else { - val subcmd = commands[args[1].lowercase()] + val subcmd = commands[args[0].lowercase()] if(subcmd != null) { val newArgs = args.copyOfRange(1, args.size) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt index 23ab4b9..e30b6fc 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt @@ -1,5 +1,6 @@ package xyz.alexcrea.cuanvil.command +import com.github.stefvanschie.inventoryframework.inventoryview.interface_.InventoryViewUtil import io.delilaheve.CustomAnvil import net.md_5.bungee.api.chat.ClickEvent import net.md_5.bungee.api.chat.HoverEvent @@ -7,16 +8,27 @@ import net.md_5.bungee.api.chat.TextComponent import net.md_5.bungee.api.chat.hover.content.Text import org.bukkit.Bukkit import org.bukkit.ChatColor +import org.bukkit.Material import org.bukkit.command.Command import org.bukkit.command.CommandSender +import org.bukkit.enchantments.Enchantment import org.bukkit.entity.HumanEntity +import org.bukkit.entity.Player +import org.bukkit.event.inventory.InventoryType import org.bukkit.event.inventory.PrepareAnvilEvent +import org.bukkit.inventory.AnvilInventory +import org.bukkit.inventory.InventoryView +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.meta.Damageable +import org.bukkit.inventory.meta.EnchantmentStorageMeta import org.bukkit.plugin.Plugin +import org.bukkit.plugin.PluginManager import org.bukkit.plugin.RegisteredListener import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.dependency.packet.NoPacketManager import xyz.alexcrea.cuanvil.dependency.packet.ProtocoLibWrapper import xyz.alexcrea.cuanvil.dependency.packet.versions.PaperPacketManager +import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener import java.util.* import java.util.stream.Collectors @@ -30,7 +42,8 @@ class DiagnosticExecutor: CASubCommand() { enum class DiagParams(val value: String) { OS_PRIVACY("os_privacy"), PLUGIN_PRIVACY("plugin_privacy"), - //NO_TEST("no_anvil_test"), + NO_MERGE_TEST("no_merge_test"); + // TODO enchant list } private fun fetchParameters(args: Array): EnumSet { @@ -76,17 +89,22 @@ class DiagnosticExecutor: CASubCommand() { val stb = StringBuilder("```\n") val params = fetchParameters(args) + var hasError = false try { - diagnostic(stb, params) - } catch(e: Exception){ + diagnostic(sender, stb, params) + } catch(e: Throwable){ stb.append("\n\nError happened trying to get diagnostic data:\n") .append(e.message).append("\n") .append(e.stackTrace.joinToString("\n")) + hasError = true + e.printStackTrace() } stb.append("\n```") if (sender is HumanEntity) { + if(hasError) + sender.spigot().sendMessage(TextComponent(ChatColor.RED.toString() + "There was an error running the diagnostic")) val message = TextComponent(ChatColor.GREEN.toString() + "Click to copy diagnostic data") message.clickEvent = ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, stb.toString()) @@ -104,42 +122,71 @@ class DiagnosticExecutor: CASubCommand() { return sender.hasPermission(CustomAnvil.diagnosticPermission) } - fun diagnostic(stb: StringBuilder, params: Set){ + fun diagnostic(sender: CommandSender, stb: StringBuilder, params: Set){ stb.append("Server Info\n") - stb.append("Plugin Version: ").append(CustomAnvil.instance.description.version).append("\n") - stb.append("Latest Update: ").append(CustomAnvil.latestVer).append('\n') - stb.append("Server Version: ").append(Bukkit.getVersion()).append(" (").append(Bukkit.getName()).append(')').append("\n") - stb.append("Plugin Enabled: ").append(if(CustomAnvil.instance.isEnabled) "Yes" else "No").append("\n") - stb.append("NMS type: ").append(fetchNMSType()) + stb.append("\nPlugin Version: ").append(CustomAnvil.instance.description.version) + stb.append("\nLatest Update: ").append(CustomAnvil.latestVer) + stb.append("\nServer Version: ").append(Bukkit.getVersion()).append(" (").append(Bukkit.getName()).append(')') + stb.append("\nPlugin Enabled: ").append(if(CustomAnvil.instance.isEnabled) "Yes" else "No") + stb.append("\nNMS type: ").append(fetchNMSType()) if(!params.contains(DiagParams.OS_PRIVACY)) { - stb.append("Java Version: ").append(System.getProperty("java.version")).append("\n") - stb.append("OS: ").append(System.getProperty("os.name")).append(" ") + stb.append("\nJava Version: ").append(System.getProperty("java.version")) + stb.append("\nOS: ").append(System.getProperty("os.name")).append(" ") .append(System.getProperty("os.version")) .append(System.getProperty("os.arch")) - .append("\n\n") } if(!params.contains(DiagParams.PLUGIN_PRIVACY)) { - pluginListDiag(stb) + pluginListDiag(sender, stb) } prepareAnvilListeners(stb) - stb.append("\n\n") + if(!params.contains(DiagParams.NO_MERGE_TEST)){ + if(sender is Player) { + testMerge(sender, stb) + } + } + } + + private fun testMerge(player: Player, stb: StringBuilder) { + val sword = ItemStack(Material.DIAMOND_SWORD) + val damagedSword = sword.clone() + val enchantedSword = sword.clone() + val enchantedBook = ItemStack(Material.ENCHANTED_BOOK) + val unitForRepair = ItemStack(Material.DIAMOND) + + var meta = damagedSword.itemMeta + (meta as Damageable).damage = 5 + damagedSword.itemMeta = meta + + meta = enchantedSword.itemMeta + meta!!.addEnchant(Enchantment.DAMAGE_ALL, 1, true) + enchantedSword.itemMeta = meta + + meta = enchantedBook.itemMeta + (meta as EnchantmentStorageMeta).addStoredEnchant(Enchantment.DAMAGE_ALL, 1, true) + enchantedBook.itemMeta = meta + + stb.append("\n\nItem to Item repair:") + simulateAnvil(player, stb, damagedSword, damagedSword, sword) + + stb.append("\n\nUnit repair:") + simulateAnvil(player, stb, damagedSword, unitForRepair, sword) + + stb.append("\n\nEnchanting an item:") + simulateAnvil(player, stb, sword, enchantedBook, enchantedSword) } private fun fetchNMSType(): String { val packetManager = DependencyManager.packetManager val packetManagerClass = packetManager.javaClass - val result: String - if(packetManagerClass == PaperPacketManager::class.java) { - result = "Paper NMS" - } else if(packetManagerClass == ProtocoLibWrapper::class.java) { - result = "Protocolib" - } else if(packetManagerClass == NoPacketManager::class.java) { - result = "None" - } else { - result = "Version Specific" + val result = when (packetManagerClass) { + PaperPacketManager::class.java -> "Paper NMS" + ProtocoLibWrapper::class.java -> "Protocolib" + NoPacketManager::class.java -> "None" + else -> "Version Specific" + } return "$result ${if(packetManager.canSetInstantBuild) '✅' else '❌'}" @@ -150,7 +197,7 @@ class DiagnosticExecutor: CASubCommand() { return this.name + " v" + this.description.version } - private fun pluginListDiag(stb: StringBuilder) { + private fun pluginListDiag(sender: CommandSender, stb: StringBuilder) { val enabledPlugins: MutableList = ArrayList() val disabledPlugins: MutableList = ArrayList() for (plugin in Bukkit.getPluginManager().plugins) { @@ -161,17 +208,17 @@ class DiagnosticExecutor: CASubCommand() { } } - stb.append("Enabled Plugins: ").append( + stb.append("\nEnabled Plugins: ").append( enabledPlugins.stream() .map { plugin -> plugin!!.pluginNameDisplay } .reduce { a: String?, b: String? -> "$a, $b" }.orElse("None") - ).append("\n") + ) - stb.append("Disabled Plugins: ").append( + stb.append("\nDisabled Plugins: ").append( disabledPlugins.stream() .map { plugin -> plugin!!.pluginNameDisplay } .reduce { a: String?, b: String? -> "$a, $b" }.orElse("None") - ).append("\n") + ) } fun prepareAnvilListeners(stb: StringBuilder) { @@ -185,11 +232,63 @@ class DiagnosticExecutor: CASubCommand() { .collect(Collectors.toSet()) eventListeners.remove(CustomAnvil.instance) - stb.append("Prepare Anvil Listeners: ").append( + stb.append("\nPrepare Anvil Listeners: ").append( if (eventListeners.isEmpty()) "None" else eventListeners.stream() .map { plugin -> plugin!!.pluginNameDisplay } .reduce { a: String?, b: String? -> "$a, $b" }.orElse("None") - ).append("\n\n") + ) + } + + fun simulateAnvil(player: Player, stb: StringBuilder, left: ItemStack?, right: ItemStack?, result: ItemStack?) { + var invView: InventoryView + var event: PrepareAnvilEvent + try { + val fakeInv = Bukkit.createInventory(player, InventoryType.ANVIL) + invView = player.openInventory(fakeInv)!! + event = PrepareAnvilEvent(invView, result) + } catch (e: Throwable) { + // Help + val menuTypeClazz = Class.forName("org.bukkit.inventory.MenuType") + val anvilTypeField = menuTypeClazz.getField("ANVIL") + val anvilType = anvilTypeField.get(null) + val createMethod = anvilType.javaClass.getMethod("create", HumanEntity::class.java) + invView = createMethod.invoke(anvilType, player) as InventoryView + + player.openInventory(invView) + + val anvilViewClass = Class.forName("org.bukkit.inventory.view.AnvilView") + val constructor = PrepareAnvilEvent::class.java.getConstructor(anvilViewClass, ItemStack::class.java) + event = constructor.newInstance(invView, result) + } + + val fakeInv = InventoryViewUtil.getInstance().getTopInventory(invView) as AnvilInventory + fakeInv.setItem(0, left) + fakeInv.setItem(1, right) + + val xp = fakeInv.repairCost + val maxXp = fakeInv.maximumRepairCost + val mergeResult = fakeInv.getItem(2) + stb.append("\n${if(result == mergeResult) "E" else "Une"}xpected Result") + + PrepareAnvilListener().anvilCombineCheck(event) + // Now we check if item and xp same + stb.append("\nXP/Max XP: ") + .append(if(fakeInv.repairCost == xp) "Correct" else "Incorrect") + .append("/") + .append(if(fakeInv.maximumRepairCost == maxXp) "Correct" else "Incorrect") + .append(" (${fakeInv.repairCost} $xp|${fakeInv.maximumRepairCost} $maxXp)") + .append("\nMerge result: ") + .append(if(fakeInv.getItem(2) == mergeResult) "Correct" else "Incorrect") + + PrepareAnvilListener.IS_EMPTY_TEST = true + Bukkit.getPluginManager().callEvent(event) + stb.append("\nNull result test: ") + .append(if(event.result == null) "Correct" else "Incorrect") + + fakeInv.setItem(0, null) + fakeInv.setItem(1, null) + fakeInv.setItem(2, null) + player.closeInventory() } } \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt index ebf16ed..8d55f3c 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt @@ -38,6 +38,8 @@ class PrepareAnvilListener : Listener { const val ANVIL_INPUT_LEFT = 0 const val ANVIL_INPUT_RIGHT = 1 const val ANVIL_OUTPUT_SLOT = 2 + + var IS_EMPTY_TEST = false } private fun ItemStack?.isAir(): Boolean { @@ -63,6 +65,12 @@ class PrepareAnvilListener : Listener { val first = inventory.getItem(ANVIL_INPUT_LEFT) ?: return val second = inventory.getItem(ANVIL_INPUT_RIGHT) + if(IS_EMPTY_TEST) { + event.result = null + IS_EMPTY_TEST = false + return + } + if (isImmutable(first) || isImmutable(second)) { CustomAnvil.verboseLog("Skipping anvil process as one of the two item is immutable") diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 03ed5aa..5807f8d 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -12,6 +12,8 @@ libraries: [${libraries}] commands: customanvil: description: Generic command for custom anvil + aliases: + - ca anvilconfigreload: description: Reload every config of this plugin permission: ca.command.reload From 20509faed4a8002119da2916e02debd079965343 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 2 Mar 2026 19:44:24 +0100 Subject: [PATCH 084/207] add enchantment data --- .../cuanvil/command/DiagnosticExecutor.kt | 38 ++++++++++++++++--- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt index e30b6fc..9459444 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt @@ -22,12 +22,12 @@ import org.bukkit.inventory.ItemStack import org.bukkit.inventory.meta.Damageable import org.bukkit.inventory.meta.EnchantmentStorageMeta import org.bukkit.plugin.Plugin -import org.bukkit.plugin.PluginManager import org.bukkit.plugin.RegisteredListener import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.dependency.packet.NoPacketManager import xyz.alexcrea.cuanvil.dependency.packet.ProtocoLibWrapper import xyz.alexcrea.cuanvil.dependency.packet.versions.PaperPacketManager +import xyz.alexcrea.cuanvil.enchant.CAEnchantmentRegistry import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener import java.util.* import java.util.stream.Collectors @@ -42,8 +42,8 @@ class DiagnosticExecutor: CASubCommand() { enum class DiagParams(val value: String) { OS_PRIVACY("os_privacy"), PLUGIN_PRIVACY("plugin_privacy"), - NO_MERGE_TEST("no_merge_test"); - // TODO enchant list + NO_MERGE_TEST("no_merge_test"), + FULL_ENCHANTMENT_DATA("full_enchantment_data"), } private fun fetchParameters(args: Array): EnumSet { @@ -142,9 +142,13 @@ class DiagnosticExecutor: CASubCommand() { prepareAnvilListeners(stb) if(!params.contains(DiagParams.NO_MERGE_TEST)){ - if(sender is Player) { - testMerge(sender, stb) - } + if(sender is Player) testMerge(sender, stb) + } + + stb.append("\n\nEnchantments data:") + partialEnchantmentData(stb) + if(params.contains(DiagParams.FULL_ENCHANTMENT_DATA)){ + fullEnchantmentData(stb) } } @@ -291,4 +295,26 @@ class DiagnosticExecutor: CASubCommand() { player.closeInventory() } + private fun fullEnchantmentData(stb: StringBuilder) { + for (enchantment in CAEnchantmentRegistry.getInstance().values()) { + stb.append("\n- ").append(enchantment.key.toString()) + .append(" ").append(enchantment.name) + .append(" ").append(enchantment.defaultMaxLevel()) + } + } + + private fun partialEnchantmentData(stb: StringBuilder) { + val map = HashMap() + for (enchant in CAEnchantmentRegistry.getInstance().values()) { + map[enchant.key.namespace] = map.getOrDefault(enchant.key.namespace, 0) + 1 + } + + stb.append("\nNamespaces: ${ + map.entries.stream() + .map { (key, value) -> "$key ($value)" } + .reduce { a, b -> "$a, $b" }.get() + }") + + } + } \ No newline at end of file From fbc862a5a37ed2d2a2d4ad8b3d2eb6d84786374c Mon Sep 17 00:00:00 2001 From: alexcrea Date: Fri, 16 Jan 2026 23:58:13 +0100 Subject: [PATCH 085/207] try add conflict after level --- .../alexcrea/cuanvil/api/ConflictBuilder.java | 1 + .../cuanvil/group/EnchantConflictGroup.kt | 44 ++++++++++++++++--- .../cuanvil/group/EnchantConflictManager.kt | 25 ++++++++++- 3 files changed, 64 insertions(+), 6 deletions(-) diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/ConflictBuilder.java b/src/main/java/xyz/alexcrea/cuanvil/api/ConflictBuilder.java index f662140..1460766 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/api/ConflictBuilder.java +++ b/src/main/java/xyz/alexcrea/cuanvil/api/ConflictBuilder.java @@ -13,6 +13,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +//TODO add conflict after level /** * A Builder for material conflict. */ diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt index 56e923f..dbe8a09 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt @@ -7,10 +7,11 @@ import xyz.alexcrea.cuanvil.enchant.CAEnchantment class EnchantConflictGroup( val name: String, private val cantConflict: AbstractMaterialGroup, - var minBeforeBlock: Int + var minBeforeBlock: Int, ) { private val enchantments = HashSet() + private val conflictAfterLevel = HashMap() fun addEnchantment(enchant: CAEnchantment) { enchantments.add(enchant) @@ -19,19 +20,37 @@ class EnchantConflictGroup( enchantments.addAll(enchants) } - fun allowed(enchants: Set, mat: Material): Boolean { + private fun canBypassConflictByLevel(enchants: Map): Boolean { + // Either there no "conflict after" + if(conflictAfterLevel.isEmpty()) return false + + // Or we check if any conflict after enchantment is true + for (entry in conflictAfterLevel) { + if(enchants.getOrDefault(entry.key, 0) >= entry.value) + return false + } + + return true + } + + fun allowed(enchants: Map, mat: Material): Boolean { if (enchantments.size < minBeforeBlock) { CustomAnvil.verboseLog("Conflicting bc of to many enchantments") return true } - if (cantConflict.contain(mat)) { + if (cantConflict.contain(mat)) + return true + + // If empty we skip. else we + if(canBypassConflictByLevel(enchants)) return true - } // Count the amount of enchantment that are in the list var enchantAmount = 0 - for (enchantment in enchants) { + for (entry in enchants) { + val enchantment = entry.key + if (enchantment !in enchantments) continue CustomAnvil.verboseLog("Enchant ${enchantment.key} is in: ${enchantAmount + 1}/$minBeforeBlock ") if (++enchantAmount > minBeforeBlock) { @@ -56,6 +75,21 @@ class EnchantConflictGroup( enchantments.addAll(enchants) } + fun getConflictAfters(): HashMap { + return conflictAfterLevel + } + + fun putConflictAfterLevel(enchantment: CAEnchantment, level: Int): Boolean { + return null != ( + if(level < 0) conflictAfterLevel.remove(enchantment) + else conflictAfterLevel.put(enchantment, level)) + } + + fun setConflictAfterLevel(conflictAfterLevel: HashMap) { + this.conflictAfterLevel.clear() + this.conflictAfterLevel.putAll(conflictAfterLevel) + } + fun getRepresentativeMaterial(): Material { val groups = getCantConflictGroup().getGroups() val groupIterator = groups.iterator() diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt index 169d9e9..5c07e77 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt @@ -16,6 +16,10 @@ class EnchantConflictManager { // Path for the enchantments list const val ENCH_LIST_PATH = "enchantments" + // Path for the enchantments list + //TODO add test and gui + const val AFTER_LEVEL_LIST_PATH = "conflict_after_level" + // Path for group list related to the conflict const val CONFLICT_GROUP_PATH = "notAffectedGroups" @@ -110,6 +114,25 @@ class EnchantConflictManager { } } + val conflictAfterLevels = section.getConfigurationSection(AFTER_LEVEL_LIST_PATH) + if(conflictAfterLevels != null) { + for (enchantName in conflictAfterLevels.getKeys(false)) { + val enchants = getEnchantByIdentifier(enchantName) + if (enchants.isEmpty()) { + CustomAnvil.instance.logger.warning("Enchantment $enchantName do not exist but was asked for conflict after level for conflict $conflictName") + continue + } + + val value = conflictAfterLevels.getInt(enchantName, -1) + if(value < 0) continue + + for (enchant in enchants) { + val previous = conflict.getConflictAfters().getOrDefault(enchant, value) + conflict.putConflictAfterLevel(enchant, value.coerceAtMost(previous)) + } + } + } + return conflict } @@ -187,7 +210,7 @@ class EnchantConflictManager { continue } - val allowed = conflict.allowed(appliedEnchants.keys, mat) + val allowed = conflict.allowed(appliedEnchants, mat) CustomAnvil.verboseLog("Was against $conflict and conflicting: ${!allowed} ") if (!allowed) { if (conflict.getEnchants().size <= 1) { From 33a86cd3bc02bc6cf631a7ac284dc8cb51f0eefe Mon Sep 17 00:00:00 2001 From: alexcrea Date: Fri, 16 Jan 2026 23:59:34 +0100 Subject: [PATCH 086/207] version bump --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index f690eaa..5eb4561 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,7 +22,7 @@ plugins { } group = "xyz.alexcrea" -version = "1.15.12" +version = "1.16.0" val isDevBuild = System.getenv("SMALL_COMMIT_HASH") != null val isPreRelease = System.getenv("IS_GITHUB_PRERELEASE") == "true" From 2cff7bd83cc072a29156b402c51843810124aecb Mon Sep 17 00:00:00 2001 From: alexcrea Date: Sat, 17 Jan 2026 00:47:42 +0100 Subject: [PATCH 087/207] made combine with logic work --- .../io/delilaheve/util/EnchantmentUtil.kt | 124 ++++++++++-------- .../cuanvil/group/EnchantConflictGroup.kt | 3 +- 2 files changed, 68 insertions(+), 59 deletions(-) diff --git a/src/main/kotlin/io/delilaheve/util/EnchantmentUtil.kt b/src/main/kotlin/io/delilaheve/util/EnchantmentUtil.kt index bee9bda..6a22489 100644 --- a/src/main/kotlin/io/delilaheve/util/EnchantmentUtil.kt +++ b/src/main/kotlin/io/delilaheve/util/EnchantmentUtil.kt @@ -4,7 +4,6 @@ import io.delilaheve.CustomAnvil import org.bukkit.entity.HumanEntity import org.bukkit.inventory.ItemStack import xyz.alexcrea.cuanvil.config.ConfigHolder -import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.enchant.CAEnchantment import xyz.alexcrea.cuanvil.group.ConflictType import kotlin.math.max @@ -37,81 +36,90 @@ object EnchantmentUtil { var maxEnchantCount = ConfigOptions.getEnchantCountLimit(item.type) if(maxEnchantCount == null || maxEnchantCount < 0) maxEnchantCount = Int.MAX_VALUE - other.forEach { (enchantment, level) -> - if(!enchantment.isAllowed(player)) return@forEach + val allowed = other.filter { (enchantment, _) -> enchantment.isAllowed(player) } + val new = allowed.filter{ (enchantment, _) -> !containsKey(enchantment)} + val old = allowed.filter{ (enchantment, _) -> containsKey(enchantment)} - // Get max level or 255 if player can bypass - val maxLevel = if (bypassLevel) { 255 } + fun maxLevel(enchantment: CAEnchantment): Int { + val max = if (bypassLevel) { 255 } else { ConfigOptions.enchantLimit(enchantment) } - CustomAnvil.verboseLog("Max level of ${enchantment.key} is $maxLevel (bypassLevel is $bypassLevel)") + CustomAnvil.verboseLog("Max level of ${enchantment.key} is $max (bypassLevel is $bypassLevel)") + return max + } + + old.forEach { (enchantment, level) -> + // Get max level or 255 if player can bypass + val maxLevel = maxLevel(enchantment) val cappedLevel = min(level, maxLevel) - // Enchantment not yet in result list - if (!containsKey(enchantment)) { - // Do not allow new enchantment if above maximum - if(this.size >= maxEnchantCount) return@forEach - - // Add the enchantment if it doesn't have conflicts, or if player is allowed to bypass enchantment restrictions - this[enchantment] = cappedLevel - if(bypassFuse){ - CustomAnvil.verboseLog("Bypassed conflict check for ${enchantment.key}") - return@forEach - } - - val conflictType = ConfigHolder.CONFLICT_HOLDER.conflictManager - .isConflicting(this, item, enchantment) - - if (conflictType != ConflictType.NO_CONFLICT) { - CustomAnvil.verboseLog("Enchantment not yet in result list, but there is conflict (${enchantment.key}, conflict: $conflictType)") - this.remove(enchantment) - } + val oldLevel = this[enchantment]!! // <- should not be null. (enchantment already in result list) + // ... and they're not the same level + if (oldLevel != cappedLevel) { + // apply the greater of the two or left one if right is above max + this[enchantment] = max(oldLevel, cappedLevel) } - // Enchantment already in result list + // ... and they're the same level else { - val oldLevel = this[enchantment]!! // <- should not be null. (enchantment already in result list) - - if(bypassFuse){ - CustomAnvil.verboseLog("Bypassed conflict check for ${enchantment.key}") - } else { - val conflictType = ConfigHolder.CONFLICT_HOLDER.conflictManager - .isConflicting(this, item, enchantment) - - // ... and they are conflicting - if(conflictType != ConflictType.NO_CONFLICT){ + // We test if it is allowed to merge at this level + if(!bypassLevel){ + val maxBeforeDisabled = ConfigOptions.maxBeforeMergeDisabled(enchantment) + if((maxBeforeDisabled > 0) && (oldLevel >= maxBeforeDisabled)) { CustomAnvil.verboseLog( - "Enchantment already in result list, and they are conflicting (${enchantment.key}, conflict: $conflictType)") + "Reached max merge before disable for ${enchantment.key}: $oldLevel/$maxBeforeDisabled)") return@forEach } } - // ... and they're not the same level - if (oldLevel != cappedLevel) { - // apply the greater of the two or left one if right is above max - this[enchantment] = max(oldLevel, cappedLevel) + // Now we increase the enchantment level by 1 + var newLevel = oldLevel + 1 + newLevel = max(min(newLevel, maxLevel), oldLevel) + this[enchantment] = newLevel + } - } - // ... and they're the same level - else { - // We test if it is allowed to merge at this level - if(!bypassLevel){ - val maxBeforeDisabled = ConfigOptions.maxBeforeMergeDisabled(enchantment) - if((maxBeforeDisabled > 0) && (oldLevel >= maxBeforeDisabled)) { - CustomAnvil.verboseLog( - "Reached max merge before disable for ${enchantment.key}: $oldLevel/$maxBeforeDisabled)") - return@forEach - } - } - - // Now we increase the enchantment level by 1 - var newLevel = oldLevel + 1 - newLevel = max(min(newLevel, maxLevel), oldLevel) - this[enchantment] = newLevel + if(bypassFuse){ + CustomAnvil.verboseLog("Bypassed conflict check for ${enchantment.key}") + } else { + val conflictType = ConfigHolder.CONFLICT_HOLDER.conflictManager + .isConflicting(this, item, enchantment) + // ... and they are conflicting + if(conflictType != ConflictType.NO_CONFLICT){ + CustomAnvil.verboseLog( + "Enchantment already in result list, and they are conflicting (${enchantment.key}, conflict: $conflictType)") + this[enchantment] = oldLevel + return@forEach } } } + + // Try to add new now + new.forEach { (enchantment, level) -> + // Get max level or 255 if player can bypass + val maxLevel = maxLevel(enchantment) + val cappedLevel = min(level, maxLevel) + + // Do not allow new enchantment if above maximum + if(this.size >= maxEnchantCount) return@forEach + + // Add the enchantment if it doesn't have conflicts, or if player is allowed to bypass enchantment restrictions + this[enchantment] = cappedLevel + if(bypassFuse){ + CustomAnvil.verboseLog("Bypassed conflict check for ${enchantment.key}") + return@forEach + } + + val conflictType = ConfigHolder.CONFLICT_HOLDER.conflictManager + .isConflicting(this, item, enchantment) + + if (conflictType != ConflictType.NO_CONFLICT) { + CustomAnvil.verboseLog("Enchantment not yet in result list, but there is conflict (${enchantment.key}, conflict: $conflictType)") + this.remove(enchantment) + } + + } + } } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt index dbe8a09..ef8a524 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt @@ -26,7 +26,8 @@ class EnchantConflictGroup( // Or we check if any conflict after enchantment is true for (entry in conflictAfterLevel) { - if(enchants.getOrDefault(entry.key, 0) >= entry.value) + val current = enchants.getOrDefault(entry.key, 0) + if(current > entry.value) return false } From a66206a52c208635c55dfb31cfd0b46301ac8a4f Mon Sep 17 00:00:00 2001 From: alexcrea Date: Sat, 17 Jan 2026 01:13:00 +0100 Subject: [PATCH 088/207] add conflict before level --- .../cuanvil/group/EnchantConflictGroup.kt | 36 ++++++++++++++++++- .../cuanvil/group/EnchantConflictManager.kt | 30 ++++++++++++++-- 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt index ef8a524..fd1ea8c 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt @@ -12,6 +12,7 @@ class EnchantConflictGroup( private val enchantments = HashSet() private val conflictAfterLevel = HashMap() + private val conflictBeforeLevel = HashMap() fun addEnchantment(enchant: CAEnchantment) { enchantments.add(enchant) @@ -20,7 +21,7 @@ class EnchantConflictGroup( enchantments.addAll(enchants) } - private fun canBypassConflictByLevel(enchants: Map): Boolean { + private fun canBypassByBeforeLevel(enchants: Map): Boolean { // Either there no "conflict after" if(conflictAfterLevel.isEmpty()) return false @@ -34,6 +35,24 @@ class EnchantConflictGroup( return true } + private fun canBypassByAfterLevel(enchants: Map): Boolean { + // Either there no "conflict after" + if(conflictAfterLevel.isEmpty()) return false + + // Or we check if any conflict after enchantment is true + for (entry in conflictBeforeLevel) { + val current = enchants.getOrDefault(entry.key, 0) + if(current < entry.value) + return false + } + + return true + } + + private fun canBypassConflictByLevel(enchants: Map): Boolean { + return canBypassByBeforeLevel(enchants) || canBypassByAfterLevel(enchants) + } + fun allowed(enchants: Map, mat: Material): Boolean { if (enchantments.size < minBeforeBlock) { CustomAnvil.verboseLog("Conflicting bc of to many enchantments") @@ -91,6 +110,21 @@ class EnchantConflictGroup( this.conflictAfterLevel.putAll(conflictAfterLevel) } + fun getConflictBefores(): HashMap { + return conflictBeforeLevel + } + + fun putConflictBeforeLevel(enchantment: CAEnchantment, level: Int): Boolean { + return null != ( + if(level < 0) conflictBeforeLevel.remove(enchantment) + else conflictBeforeLevel.put(enchantment, level)) + } + + fun setConflictBeforeLevel(conflictBeforeLevel: HashMap) { + this.conflictBeforeLevel.clear() + this.conflictBeforeLevel.putAll(conflictBeforeLevel) + } + fun getRepresentativeMaterial(): Material { val groups = getCantConflictGroup().getGroups() val groupIterator = groups.iterator() diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt index 5c07e77..6782803 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt @@ -16,9 +16,13 @@ class EnchantConflictManager { // Path for the enchantments list const val ENCH_LIST_PATH = "enchantments" - // Path for the enchantments list + // Path for list of enchantments conflicting before level //TODO add test and gui - const val AFTER_LEVEL_LIST_PATH = "conflict_after_level" + const val CONFLICT_AFTER_LEVEL_LIST_PATH = "conflict_after_level" + + // Path for list of enchantments conflicting before level + //TODO add test and gui + const val CONFLICT_BEFORE_LEVEL_LIST_PATH = "conflict_before_level" // Path for group list related to the conflict const val CONFLICT_GROUP_PATH = "notAffectedGroups" @@ -114,7 +118,8 @@ class EnchantConflictManager { } } - val conflictAfterLevels = section.getConfigurationSection(AFTER_LEVEL_LIST_PATH) + //TODO find a way to dry this two ? + val conflictAfterLevels = section.getConfigurationSection(CONFLICT_AFTER_LEVEL_LIST_PATH) if(conflictAfterLevels != null) { for (enchantName in conflictAfterLevels.getKeys(false)) { val enchants = getEnchantByIdentifier(enchantName) @@ -133,6 +138,25 @@ class EnchantConflictManager { } } + val conflictBeforeLevels = section.getConfigurationSection(CONFLICT_BEFORE_LEVEL_LIST_PATH) + if(conflictBeforeLevels != null) { + for (enchantName in conflictBeforeLevels.getKeys(false)) { + val enchants = getEnchantByIdentifier(enchantName) + if (enchants.isEmpty()) { + CustomAnvil.instance.logger.warning("Enchantment $enchantName do not exist but was asked for conflict after level for conflict $conflictName") + continue + } + + val value = conflictBeforeLevels.getInt(enchantName, -1) + if(value < 0) continue + + for (enchant in enchants) { + val previous = conflict.getConflictBefores().getOrDefault(enchant, value) + conflict.putConflictBeforeLevel(enchant, value.coerceAtMost(previous)) + } + } + } + return conflict } From 4e15aab024537b5f1c47163409d03b5260f20f29 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Sat, 17 Jan 2026 01:21:30 +0100 Subject: [PATCH 089/207] fix minor error --- .../kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt index fd1ea8c..dda6d49 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt @@ -37,7 +37,7 @@ class EnchantConflictGroup( private fun canBypassByAfterLevel(enchants: Map): Boolean { // Either there no "conflict after" - if(conflictAfterLevel.isEmpty()) return false + if(conflictBeforeLevel.isEmpty()) return false // Or we check if any conflict after enchantment is true for (entry in conflictBeforeLevel) { From ec2384bc7f7618f675c1d1e84995ebd129c2f336 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 19 Jan 2026 21:15:25 +0100 Subject: [PATCH 090/207] fix minor feature explaination issue on config --- defaultconfigs/1.18/config.yml | 2 +- defaultconfigs/1.21.11/config.yml | 2 +- defaultconfigs/1.21.9/config.yml | 2 +- defaultconfigs/1.21/config.yml | 2 +- src/main/resources/config.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/defaultconfigs/1.18/config.yml b/defaultconfigs/1.18/config.yml index 877cee9..0b543cd 100644 --- a/defaultconfigs/1.18/config.yml +++ b/defaultconfigs/1.18/config.yml @@ -267,7 +267,7 @@ enchant_values: # Even if disable-merge-over of unbreaking is set to 2 # -1 mean enchantment merge for this enchantment is not disabled. default to -1 if absent. disable-merge-over: - # Sharpness is set to -1. it equivalent to it not being set to anything (and work as vanilla) + # Sharpness is set to -1. it equivalent to it not being set to anything (and work as vanilla on default configuration) minecraft:sharpness: -1 # If uncommented. 2 unbreaking II book would not give an unbreaking III book. but unbreaking III book can still be applied #minecraft:unbreaking: 2 diff --git a/defaultconfigs/1.21.11/config.yml b/defaultconfigs/1.21.11/config.yml index 10fe8af..9a8eb9c 100644 --- a/defaultconfigs/1.21.11/config.yml +++ b/defaultconfigs/1.21.11/config.yml @@ -285,7 +285,7 @@ enchant_values: # Even if disable-merge-over of unbreaking is set to 2 # -1 mean enchantment merge for this enchantment is not disabled. default to -1 if absent. disable-merge-over: - # Sharpness is set to -1. it equivalent to it not being set to anything (and work as vanilla) + # Sharpness is set to -1. it equivalent to it not being set to anything (and work as vanilla on default configuration) minecraft:sharpness: -1 # If uncommented. 2 unbreaking II book would not give an unbreaking III book. but unbreaking III book can still be applied # minecraft:unbreaking: 2 diff --git a/defaultconfigs/1.21.9/config.yml b/defaultconfigs/1.21.9/config.yml index cf9460e..809bab9 100644 --- a/defaultconfigs/1.21.9/config.yml +++ b/defaultconfigs/1.21.9/config.yml @@ -279,7 +279,7 @@ enchant_values: # Even if disable-merge-over of unbreaking is set to 2 # -1 mean enchantment merge for this enchantment is not disabled. default to -1 if absent. disable-merge-over: - # Sharpness is set to -1. it equivalent to it not being set to anything (and work as vanilla) + # Sharpness is set to -1. it equivalent to it not being set to anything (and work as vanilla on default configuration) minecraft:sharpness: -1 # If uncommented. 2 unbreaking II book would not give an unbreaking III book. but unbreaking III book can still be applied # minecraft:unbreaking: 2 diff --git a/defaultconfigs/1.21/config.yml b/defaultconfigs/1.21/config.yml index d7807e9..88eff1a 100644 --- a/defaultconfigs/1.21/config.yml +++ b/defaultconfigs/1.21/config.yml @@ -267,7 +267,7 @@ enchant_values: # Even if disable-merge-over of unbreaking is set to 2 # -1 mean enchantment merge for this enchantment is not disabled. default to -1 if absent. disable-merge-over: - # Sharpness is set to -1. it equivalent to it not being set to anything (and work as vanilla) + # Sharpness is set to -1. it equivalent to it not being set to anything (and work as vanilla on default configuration) minecraft:sharpness: -1 # If uncommented. 2 unbreaking II book would not give an unbreaking III book. but unbreaking III book can still be applied # minecraft:unbreaking: 2 diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 5476ec7..c1fe214 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -269,7 +269,7 @@ enchant_values: # Even if disable-merge-over of unbreaking is set to 2 # -1 mean enchantment merge for this enchantment is not disabled. default to -1 if absent. disable-merge-over: - # Sharpness is set to -1. it equivalent to it not being set to anything (and work as vanilla) + # Sharpness is set to -1. it equivalent to it not being set to anything (and work as vanilla on default configuration) minecraft:sharpness: -1 # If uncommented. 2 unbreaking II book would not give an unbreaking III book. but unbreaking III book can still be applied #minecraft:unbreaking: 2 From 39db70d7adb97e149fb13733618839ae256650e4 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Thu, 29 Jan 2026 18:49:05 +0100 Subject: [PATCH 091/207] some more logs --- src/main/kotlin/io/delilaheve/util/EnchantmentUtil.kt | 1 + .../alexcrea/cuanvil/listener/PrepareAnvilListener.kt | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/io/delilaheve/util/EnchantmentUtil.kt b/src/main/kotlin/io/delilaheve/util/EnchantmentUtil.kt index 6a22489..b0fcc97 100644 --- a/src/main/kotlin/io/delilaheve/util/EnchantmentUtil.kt +++ b/src/main/kotlin/io/delilaheve/util/EnchantmentUtil.kt @@ -30,6 +30,7 @@ object EnchantmentUtil { ) = mutableMapOf().apply { putAll(this@combineWith) + CustomAnvil.verboseLog("Testing merge") val bypassFuse = player.hasPermission(CustomAnvil.bypassFusePermission) val bypassLevel = player.hasPermission(CustomAnvil.bypassLevelPermission) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt index 8d55f3c..0c4d2e7 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt @@ -71,6 +71,11 @@ class PrepareAnvilListener : Listener { return } + if (ConfigOptions.verboseDebugLog) { + CustomAnvil.verboseLog("Testing items:") + CustomAnvil.verboseLog("first: $first") + CustomAnvil.verboseLog("second: $second") + } if (isImmutable(first) || isImmutable(second)) { CustomAnvil.verboseLog("Skipping anvil process as one of the two item is immutable") @@ -91,7 +96,9 @@ class PrepareAnvilListener : Listener { if (testCustomRecipe(event, inventory, player, first, second)) return // Test rename lonely item - if (second.isAir()) { + val isAir = second.isAir() + CustomAnvil.verboseLog("checking air in main logic: $isAir") + if (isAir) { doRenaming(event, inventory, player, first) return } From 5ff096190f4920db9779efd4078e2d1786c138e9 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 2 Mar 2026 20:26:38 +0100 Subject: [PATCH 092/207] code cleanup --- .../cuanvil/group/EnchantConflictGroup.kt | 40 ++++++------ .../cuanvil/group/EnchantConflictManager.kt | 63 ++++++++----------- 2 files changed, 46 insertions(+), 57 deletions(-) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt index dda6d49..403d630 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt @@ -11,8 +11,8 @@ class EnchantConflictGroup( ) { private val enchantments = HashSet() - private val conflictAfterLevel = HashMap() - private val conflictBeforeLevel = HashMap() + private val conflictsAfterLevel = HashMap() + private val conflictsBeforeLevel = HashMap() fun addEnchantment(enchant: CAEnchantment) { enchantments.add(enchant) @@ -23,10 +23,10 @@ class EnchantConflictGroup( private fun canBypassByBeforeLevel(enchants: Map): Boolean { // Either there no "conflict after" - if(conflictAfterLevel.isEmpty()) return false + if(conflictsAfterLevel.isEmpty()) return false // Or we check if any conflict after enchantment is true - for (entry in conflictAfterLevel) { + for (entry in conflictsAfterLevel) { val current = enchants.getOrDefault(entry.key, 0) if(current > entry.value) return false @@ -37,10 +37,10 @@ class EnchantConflictGroup( private fun canBypassByAfterLevel(enchants: Map): Boolean { // Either there no "conflict after" - if(conflictBeforeLevel.isEmpty()) return false + if(conflictsBeforeLevel.isEmpty()) return false // Or we check if any conflict after enchantment is true - for (entry in conflictBeforeLevel) { + for (entry in conflictsBeforeLevel) { val current = enchants.getOrDefault(entry.key, 0) if(current < entry.value) return false @@ -96,33 +96,33 @@ class EnchantConflictGroup( } fun getConflictAfters(): HashMap { - return conflictAfterLevel + return conflictsAfterLevel } fun putConflictAfterLevel(enchantment: CAEnchantment, level: Int): Boolean { return null != ( - if(level < 0) conflictAfterLevel.remove(enchantment) - else conflictAfterLevel.put(enchantment, level)) + if(level < 0) conflictsAfterLevel.remove(enchantment) + else conflictsAfterLevel.put(enchantment, level)) } - fun setConflictAfterLevel(conflictAfterLevel: HashMap) { - this.conflictAfterLevel.clear() - this.conflictAfterLevel.putAll(conflictAfterLevel) + fun setConflictsAfterLevel(conflictAfterLevel: HashMap) { + this.conflictsAfterLevel.clear() + this.conflictsAfterLevel.putAll(conflictAfterLevel) } - fun getConflictBefores(): HashMap { - return conflictBeforeLevel + fun getConflictsBefore(): HashMap { + return conflictsBeforeLevel } - fun putConflictBeforeLevel(enchantment: CAEnchantment, level: Int): Boolean { + fun putConflictsBeforeLevel(enchantment: CAEnchantment, level: Int): Boolean { return null != ( - if(level < 0) conflictBeforeLevel.remove(enchantment) - else conflictBeforeLevel.put(enchantment, level)) + if(level < 0) conflictsBeforeLevel.remove(enchantment) + else conflictsBeforeLevel.put(enchantment, level)) } - fun setConflictBeforeLevel(conflictBeforeLevel: HashMap) { - this.conflictBeforeLevel.clear() - this.conflictBeforeLevel.putAll(conflictBeforeLevel) + fun setConflictsBeforeLevel(conflictBeforeLevel: HashMap) { + this.conflictsBeforeLevel.clear() + this.conflictsBeforeLevel.putAll(conflictBeforeLevel) } fun getRepresentativeMaterial(): Material { diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt index 6782803..f710f76 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt @@ -9,6 +9,7 @@ import xyz.alexcrea.cuanvil.enchant.AdditionalTestEnchantment import xyz.alexcrea.cuanvil.enchant.CAEnchantment import xyz.alexcrea.cuanvil.enchant.CAEnchantmentRegistry import java.util.* +import kotlin.collections.set class EnchantConflictManager { @@ -118,48 +119,36 @@ class EnchantConflictManager { } } - //TODO find a way to dry this two ? - val conflictAfterLevels = section.getConfigurationSection(CONFLICT_AFTER_LEVEL_LIST_PATH) - if(conflictAfterLevels != null) { - for (enchantName in conflictAfterLevels.getKeys(false)) { - val enchants = getEnchantByIdentifier(enchantName) - if (enchants.isEmpty()) { - CustomAnvil.instance.logger.warning("Enchantment $enchantName do not exist but was asked for conflict after level for conflict $conflictName") - continue - } + val conflictsAfterLevel = section.getConfigurationSection(CONFLICT_AFTER_LEVEL_LIST_PATH) + val conflictsAfterMap = conflict.getConflictAfters() + fetchConditionalRestriction(conflictsAfterMap, conflictsAfterLevel, conflictName) - val value = conflictAfterLevels.getInt(enchantName, -1) - if(value < 0) continue - - for (enchant in enchants) { - val previous = conflict.getConflictAfters().getOrDefault(enchant, value) - conflict.putConflictAfterLevel(enchant, value.coerceAtMost(previous)) - } - } - } - - val conflictBeforeLevels = section.getConfigurationSection(CONFLICT_BEFORE_LEVEL_LIST_PATH) - if(conflictBeforeLevels != null) { - for (enchantName in conflictBeforeLevels.getKeys(false)) { - val enchants = getEnchantByIdentifier(enchantName) - if (enchants.isEmpty()) { - CustomAnvil.instance.logger.warning("Enchantment $enchantName do not exist but was asked for conflict after level for conflict $conflictName") - continue - } - - val value = conflictBeforeLevels.getInt(enchantName, -1) - if(value < 0) continue - - for (enchant in enchants) { - val previous = conflict.getConflictBefores().getOrDefault(enchant, value) - conflict.putConflictBeforeLevel(enchant, value.coerceAtMost(previous)) - } - } - } + val conflictsBeforeLevel = section.getConfigurationSection(CONFLICT_BEFORE_LEVEL_LIST_PATH) + val conflictsBeforeMap = conflict.getConflictsBefore() + fetchConditionalRestriction(conflictsBeforeMap, conflictsBeforeLevel, conflictName) return conflict } + private fun fetchConditionalRestriction(restrictions: MutableMap, section: ConfigurationSection?, conflictName: String) { + if(section == null) return + for (enchantName in section.getKeys(false)) { + val enchants = getEnchantByIdentifier(enchantName) + if (enchants.isEmpty()) { + CustomAnvil.instance.logger.warning("Enchantment $enchantName do not exist but was asked for conditional restriction for conflict $conflictName") + continue + } + + val value = section.getInt(enchantName, -1) + if(value < 0) continue + + for (enchant in enchants) { + val previous = restrictions.getOrDefault(enchant, value) + restrictions[enchant] = value.coerceAtMost(previous) + } + } + } + private fun getEnchantByIdentifier(enchantName: String): List { val key = NamespacedKey.fromString(enchantName) if (key != null) { From d037263e3ffd7918ab8949b5b6c1705ad9dcc6ae Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 2 Mar 2026 20:42:50 +0100 Subject: [PATCH 093/207] add fast stats as a dependency --- CREDITS.MD | 1 + build.gradle.kts | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CREDITS.MD b/CREDITS.MD index b4a3ce8..528ad15 100644 --- a/CREDITS.MD +++ b/CREDITS.MD @@ -13,6 +13,7 @@ Here dependencies are used by custom anvil - [modrinth-publish](https://github.com/cloudnode-pro/modrinth-publish) by Zefir - [discord-webhook](https://github.com/tsickert/discord-webhook) by tsickert - Thanks [bstats](https://bstats.org/) for keeping me motivated +- And [FastStats](https://faststats.dev/) alternative to bstats in beta test - [ModrinthUpdateChecker](https://github.com/Clickism/ModrinthUpdateChecker) by Clickism and thanks to the modrinth team ### Compatibility diff --git a/build.gradle.kts b/build.gradle.kts index 5eb4561..e1448f1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -36,6 +36,12 @@ repositories { // ExcellentEnchants maven(url = "https://repo.nightexpressdev.com/releases") + + // for fast stats + maven { + name = "thenextlvlReleases" + url = uri("https://repo.thenextlvl.net/releases") + } } val reobfNMS = providers.gradleProperty("subprojects.reobfnms") @@ -45,6 +51,9 @@ dependencies { // Spigot api compileOnly("org.spigotmc:spigot-api:1.18-R0.1-SNAPSHOT") + // fast stats + implementation("dev.faststats.metrics:bukkit:0.16.0") + // minimessage implementation("net.kyori:adventure-text-minimessage:4.25.0") @@ -132,7 +141,7 @@ allprojects { // Set target version tasks.withType().configureEach { sourceCompatibility = - "16" // We aim for java 16 for minecraft 1.16.5. even if it not really suported by custom anvil. + "16" // We aim for java 16 for minecraft 1.16.5. even if it not really supported by custom anvil. targetCompatibility = "16" options.encoding = "UTF-8" @@ -156,7 +165,8 @@ tasks { archiveFileName.set(name) // Shadow necessary dependency - relocate("com.github.stefvanschie.inventoryframework", "xyz.alexcrea.inventoryframework") + relocate("com.github.stefvanschie.inventoryframework", "xyz.alexcrea.customanvil.inventoryframework") + relocate("dev.faststats", "xyz.alexcrea.customanvil.faststats") filesMatching("plugin.yml") { expand( From 3e68af06ea06320e598e198eac993cb7f963f091 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 2 Mar 2026 21:58:09 +0100 Subject: [PATCH 094/207] progress on new metrics --- defaultconfigs/1.18/config.yml | 8 ++ defaultconfigs/1.21.11/config.yml | 8 ++ defaultconfigs/1.21.9/config.yml | 8 ++ defaultconfigs/1.21/config.yml | 8 ++ src/main/kotlin/io/delilaheve/CustomAnvil.kt | 10 ++- .../io/delilaheve/util/ConfigOptions.kt | 2 + .../cuanvil/command/CustomAnvilCmd.kt | 1 - .../alexcrea/cuanvil/command/DebugExecutor.kt | 25 ------- .../cuanvil/command/DiagnosticExecutor.kt | 35 +++++---- .../xyz/alexcrea/cuanvil/util/MetricsUtil.kt | 73 +++++++++++++++++++ src/main/resources/config.yml | 10 ++- 11 files changed, 141 insertions(+), 47 deletions(-) delete mode 100644 src/main/kotlin/xyz/alexcrea/cuanvil/command/DebugExecutor.kt create mode 100644 src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt diff --git a/defaultconfigs/1.18/config.yml b/defaultconfigs/1.18/config.yml index 0b543cd..69a0e18 100644 --- a/defaultconfigs/1.18/config.yml +++ b/defaultconfigs/1.18/config.yml @@ -3,6 +3,14 @@ # You can still manually edit here if you like to. but if you do, don't forget to /anvilconfigreload after you changes ! # +# What service of metric should custom anvil use +# Custom anvil collect generic information like server minecraft version, type, etc... +# It can also collect error information if error is happening (currently faststats only) +# It can also be disabled +# Please refer to README for public metric link +# possible options: auto, bstat, faststats, disabled +metric_type: auto + # All anvil cost will be capped to limit_repair_value if enabled. # # In other words: diff --git a/defaultconfigs/1.21.11/config.yml b/defaultconfigs/1.21.11/config.yml index 9a8eb9c..c281de8 100644 --- a/defaultconfigs/1.21.11/config.yml +++ b/defaultconfigs/1.21.11/config.yml @@ -3,6 +3,14 @@ # You can still manually edit here if you like to. but if you do, don't forget to /anvilconfigreload after you changes ! # +# What service of metric should custom anvil use +# Custom anvil collect generic information like server minecraft version, type, etc... +# It can also collect error information if error is happening (currently faststats only) +# It can also be disabled +# Please refer to README for public metric link +# possible options: auto, bstat, faststats, disabled +metric_type: auto + # All anvil cost will be capped to limit_repair_value if enabled. # # In other words: diff --git a/defaultconfigs/1.21.9/config.yml b/defaultconfigs/1.21.9/config.yml index 809bab9..de6f020 100644 --- a/defaultconfigs/1.21.9/config.yml +++ b/defaultconfigs/1.21.9/config.yml @@ -3,6 +3,14 @@ # You can still manually edit here if you like to. but if you do, don't forget to /anvilconfigreload after you changes ! # +# What service of metric should custom anvil use +# Custom anvil collect generic information like server minecraft version, type, etc... +# It can also collect error information if error is happening (currently faststats only) +# It can also be disabled +# Please refer to README for public metric link +# possible options: auto, bstat, faststats, disabled +metric_type: auto + # All anvil cost will be capped to limit_repair_value if enabled. # # In other words: diff --git a/defaultconfigs/1.21/config.yml b/defaultconfigs/1.21/config.yml index 88eff1a..ca4f248 100644 --- a/defaultconfigs/1.21/config.yml +++ b/defaultconfigs/1.21/config.yml @@ -3,6 +3,14 @@ # You can still manually edit here if you like to. but if you do, don't forget to /anvilconfigreload after you changes ! # +# What service of metric should custom anvil use +# Custom anvil collect generic information like server minecraft version, type, etc... +# It can also collect error information if error is happening (currently faststats only) +# It can also be disabled +# Please refer to README for public metric link +# possible options: auto, bstat, faststats, disabled +metric_type: auto + # All anvil cost will be capped to limit_repair_value if enabled. # # In other words: diff --git a/src/main/kotlin/io/delilaheve/CustomAnvil.kt b/src/main/kotlin/io/delilaheve/CustomAnvil.kt index 902b76d..e95622d 100644 --- a/src/main/kotlin/io/delilaheve/CustomAnvil.kt +++ b/src/main/kotlin/io/delilaheve/CustomAnvil.kt @@ -23,6 +23,7 @@ import xyz.alexcrea.cuanvil.update.ModrinthUpdateChecker import xyz.alexcrea.cuanvil.update.PluginSetDefault import xyz.alexcrea.cuanvil.update.UpdateHandler import xyz.alexcrea.cuanvil.util.Metrics +import xyz.alexcrea.cuanvil.util.MetricsUtil import java.io.File import java.io.FileReader import java.util.logging.Level @@ -34,7 +35,6 @@ open class CustomAnvil : JavaPlugin() { companion object { // pluginIDS - private const val bstatsPluginId = 20923 private const val modrinthPluginID = "S75Ueiq9" // Permission string required to use the plugin's features @@ -156,15 +156,17 @@ open class CustomAnvil : JavaPlugin() { } // Load metrics - try { - Metrics(this, bstatsPluginId) - } catch (_: Exception) {} + MetricsUtil.loadMetrics(this) // Load other thing later. // It is so other dependent plugins can implement there event listener before we fire them. DependencyManager.scheduler.scheduleGlobally(this) { loadEnchantmentSystemDirty() } } + override fun onDisable() { + MetricsUtil.shutdownMetrics() + } + private fun loadEnchantmentSystemDirty() { try { loadEnchantmentSystem() diff --git a/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt b/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt index afb009f..37ec7c0 100644 --- a/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt +++ b/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt @@ -21,6 +21,8 @@ object ConfigOptions { // Path for config values // ---------------------- + const val METRIC_TYPE = "metric_type" + const val CAP_ANVIL_COST = "limit_repair_cost" const val MAX_ANVIL_COST = "limit_repair_value" const val REMOVE_ANVIL_COST_LIMIT = "remove_repair_limit" diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCmd.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCmd.kt index 2a46fa8..e1c2b34 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCmd.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCmd.kt @@ -20,7 +20,6 @@ class CustomAnvilCmd(plugin: CustomAnvil) : CommandExecutor, TabCompleter { "gui", editConfigCommand, "reload", ReloadExecutor(), "diagnostic", DiagnosticExecutor(), - //"debug", DebugExecutor(), ) init { diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/DebugExecutor.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/DebugExecutor.kt deleted file mode 100644 index e7cbe9e..0000000 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/DebugExecutor.kt +++ /dev/null @@ -1,25 +0,0 @@ -package xyz.alexcrea.cuanvil.command - -import io.delilaheve.CustomAnvil -import org.bukkit.command.Command -import org.bukkit.command.CommandSender -import java.util.logging.Level - -class DebugExecutor : CASubCommand() { - - override fun executeCommand( - sender: CommandSender, - cmd: Command, - cmdstr: String, - args: Array - ): Boolean { - CustomAnvil.instance.logger.log(Level.SEVERE, "aaaaaaaaaaaaaaaaaaa"); - return true - } - - override fun tabCompleter(sender: CommandSender, args: Array, list: MutableList) { - //TODO - - } - -} diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt index 9459444..6ddc8df 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt @@ -37,6 +37,21 @@ class DiagnosticExecutor: CASubCommand() { companion object{ private const val NO_DIAG_PERM = "You do not have permission to diagnostic this server" + + fun fetchNMSType(): String { + val packetManager = DependencyManager.packetManager + val packetManagerClass = packetManager.javaClass + + val result = when (packetManagerClass) { + PaperPacketManager::class.java -> "Paper NMS" + ProtocoLibWrapper::class.java -> "Protocolib" + NoPacketManager::class.java -> "None" + else -> "Version Specific" + + } + + return "$result ${if(packetManager.canSetInstantBuild) '✅' else '❌'}" + } } enum class DiagParams(val value: String) { @@ -124,7 +139,10 @@ class DiagnosticExecutor: CASubCommand() { fun diagnostic(sender: CommandSender, stb: StringBuilder, params: Set){ stb.append("Server Info\n") - stb.append("\nPlugin Version: ").append(CustomAnvil.instance.description.version) + val version = CustomAnvil.instance.description.version + stb.append("\nPlugin Version: ").append(version) + if(version.contains("dev")) stb.append(" (alpha)") + stb.append("\nLatest Update: ").append(CustomAnvil.latestVer) stb.append("\nServer Version: ").append(Bukkit.getVersion()).append(" (").append(Bukkit.getName()).append(')') stb.append("\nPlugin Enabled: ").append(if(CustomAnvil.instance.isEnabled) "Yes" else "No") @@ -181,21 +199,6 @@ class DiagnosticExecutor: CASubCommand() { simulateAnvil(player, stb, sword, enchantedBook, enchantedSword) } - private fun fetchNMSType(): String { - val packetManager = DependencyManager.packetManager - val packetManagerClass = packetManager.javaClass - - val result = when (packetManagerClass) { - PaperPacketManager::class.java -> "Paper NMS" - ProtocoLibWrapper::class.java -> "Protocolib" - NoPacketManager::class.java -> "None" - else -> "Version Specific" - - } - - return "$result ${if(packetManager.canSetInstantBuild) '✅' else '❌'}" - } - private val Plugin.pluginNameDisplay: String get() { return this.name + " v" + this.description.version diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt new file mode 100644 index 0000000..3b912bf --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt @@ -0,0 +1,73 @@ +package xyz.alexcrea.cuanvil.util + +import dev.faststats.bukkit.BukkitMetrics +import dev.faststats.core.ErrorTracker +import dev.faststats.core.data.Metric +import io.delilaheve.CustomAnvil +import io.delilaheve.util.ConfigOptions +import xyz.alexcrea.cuanvil.command.DiagnosticExecutor +import xyz.alexcrea.cuanvil.config.ConfigHolder + +object MetricsUtil { + + private const val BSTATS_PLUGIN_ID = 20923 + private const val FASTSTATS_TOKEN = "fc282b048adcc71a77bc00ace49e8a81" + + private var ERROR_TRACKER: ErrorTracker? = null + private var FAST_STATS_METRICS: BukkitMetrics? = null + + fun loadMetrics(plugin: CustomAnvil) { + val metricString = ConfigHolder.DEFAULT_CONFIG.config.getString(ConfigOptions.METRIC_TYPE, MetricType.AUTO.value)!! + val metricType = MetricType.from(metricString) + + val nmsType = DiagnosticExecutor.fetchNMSType() + val isAlpha = CustomAnvil.instance.description.version.contains("dev") + if(metricType.allowBStats) { + try { + val metric = Metrics(plugin, BSTATS_PLUGIN_ID) + //TODO nms type custom chart + } catch (_: Exception) {} + } + + if(metricType.allowFastStats) { + ERROR_TRACKER = ErrorTracker.contextAware(); + FAST_STATS_METRICS = BukkitMetrics.factory() + .addMetric(Metric.string("nms_type") { nmsType }) + .addMetric(Metric.bool("using_alpha") { isAlpha }) + .errorTracker(ERROR_TRACKER) + .token(FASTSTATS_TOKEN) + .create(plugin) + + FAST_STATS_METRICS!!.ready() + } + } + + fun shutdownMetrics() { + FAST_STATS_METRICS?.shutdown() + } + + fun trackError(e: Throwable) { + ERROR_TRACKER?.trackError(e) + } + + fun trackError(message: String) { + ERROR_TRACKER?.trackError(message) + } +} + +enum class MetricType( + val value: String, + val allowBStats: Boolean, + val allowFastStats: Boolean, +) { + AUTO("auto", true, true), + BSTATS("bstat", true, false), + FAST_STATS("faststats", false, true), + DISABLED("disabled", false, false), + ; + + companion object { + fun from(value: String): MetricType = entries.find { it.value == value } ?: AUTO + } + +} \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index c1fe214..88ed56b 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,8 +1,16 @@ # -# It is recommended that you use /configanvil to edit theses config. +# It is recommended that you use /configanvil to edit most of these config. # You can still manually edit here if you like to. but if you do, don't forget to /anvilconfigreload after you changes ! # +# What service of metric should custom anvil use +# Custom anvil collect generic information like server minecraft version, type, etc... +# It can also collect error information if error is happening (currently faststats only) +# It can also be disabled +# Please refer to README for public metric link +# possible options: auto, bstat, faststats, disabled +metric_type: auto + # All anvil cost will be capped to limit_repair_value if enabled. # # In other words: From 48f0cab15df74b20f7cd25d758038e279d77269d Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 3 Mar 2026 00:21:03 +0100 Subject: [PATCH 095/207] error logging and bstats simple pie --- build.gradle.kts | 4 ++-- .../alexcrea/cuanvil/config/ConfigHolder.java | 4 ++++ .../cuanvil/enchant/CAEnchantmentRegistry.java | 5 ++++- .../gui/config/ask/ConfirmActionGui.java | 2 ++ .../elements/EnchantConflictSubSettingGui.java | 3 +++ src/main/kotlin/io/delilaheve/CustomAnvil.kt | 18 +++++++++++++----- .../cuanvil/dependency/DependencyManager.kt | 5 +++++ .../plugins/DisenchantmentDependency.kt | 2 ++ .../xyz/alexcrea/cuanvil/util/MetricsUtil.kt | 3 ++- 9 files changed, 37 insertions(+), 9 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index e1448f1..903c988 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -165,8 +165,8 @@ tasks { archiveFileName.set(name) // Shadow necessary dependency - relocate("com.github.stefvanschie.inventoryframework", "xyz.alexcrea.customanvil.inventoryframework") - relocate("dev.faststats", "xyz.alexcrea.customanvil.faststats") + relocate("com.github.stefvanschie.inventoryframework", "xyz.alexcrea.cuanvil.inventoryframework") + relocate("dev.faststats", "xyz.alexcrea.cuanvil.faststats") filesMatching("plugin.yml") { expand( diff --git a/src/main/java/xyz/alexcrea/cuanvil/config/ConfigHolder.java b/src/main/java/xyz/alexcrea/cuanvil/config/ConfigHolder.java index 2037e23..f6a7e80 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/config/ConfigHolder.java +++ b/src/main/java/xyz/alexcrea/cuanvil/config/ConfigHolder.java @@ -9,6 +9,7 @@ import org.jetbrains.annotations.Nullable; import xyz.alexcrea.cuanvil.group.EnchantConflictManager; import xyz.alexcrea.cuanvil.group.ItemGroupManager; import xyz.alexcrea.cuanvil.recipe.CustomAnvilRecipeManager; +import xyz.alexcrea.cuanvil.util.MetricsUtil; import java.io.File; import java.io.IOException; @@ -145,6 +146,7 @@ public abstract class ConfigHolder { sufficientSuccess = true; } catch (IOException e) { CustomAnvil.instance.getLogger().log(Level.WARNING, "Could not copy backup saving config " + base.getName(), e); + MetricsUtil.INSTANCE.trackError(e); } } // save last backup @@ -275,6 +277,7 @@ public abstract class ConfigHolder { this.deletedConfigFile.createNewFile(); } catch (IOException e) { CustomAnvil.instance.getLogger().log(Level.WARNING, "Could not create " + this.deletedConfigFile.getPath(), e); + MetricsUtil.INSTANCE.trackError(e); } loadDeletedListFile(false); @@ -312,6 +315,7 @@ public abstract class ConfigHolder { this.deletedListConfig.save(this.deletedConfigFile); } catch (IOException e) { CustomAnvil.instance.getLogger().log(Level.WARNING, "Could not save " + this.deletedConfigFile.getPath(), e); + MetricsUtil.INSTANCE.trackError(e); return false; } diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/CAEnchantmentRegistry.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/CAEnchantmentRegistry.java index 4634a11..854ed55 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/enchant/CAEnchantmentRegistry.java +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/CAEnchantmentRegistry.java @@ -10,6 +10,7 @@ import xyz.alexcrea.cuanvil.enchant.bulk.BukkitEnchantBulkOperation; import xyz.alexcrea.cuanvil.enchant.bulk.BulkCleanEnchantOperation; import xyz.alexcrea.cuanvil.enchant.bulk.BulkGetEnchantOperation; import xyz.alexcrea.cuanvil.enchant.wrapped.CABukkitEnchantment; +import xyz.alexcrea.cuanvil.util.MetricsUtil; import java.util.*; import java.util.logging.Level; @@ -85,11 +86,13 @@ public class CAEnchantmentRegistry { return false; } + var error = new IllegalStateException("enchantment " + enchantment.getKey() + " was already registered"); CustomAnvil.instance.getLogger().log(Level.WARNING, "Duplicate distinct registered enchantment. This should NOT happen any time.\n" + "If you are a custom anvil developer: Maybe custom anvil detected your enchantment as a bukkit enchantment. " + "you should maybe remove enchantment with the same key before registering yours", - new IllegalStateException("enchantment " + enchantment.getKey() + " was already registered")); + error); + MetricsUtil.INSTANCE.trackError(error); return false; } diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/ask/ConfirmActionGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/ask/ConfirmActionGui.java index 56bf848..5839663 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/ask/ConfirmActionGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/ask/ConfirmActionGui.java @@ -11,6 +11,7 @@ import org.bukkit.inventory.meta.ItemMeta; import org.jetbrains.annotations.NotNull; import xyz.alexcrea.cuanvil.gui.util.GuiGlobalActions; import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant; +import xyz.alexcrea.cuanvil.util.MetricsUtil; import java.util.Arrays; import java.util.function.Supplier; @@ -41,6 +42,7 @@ public class ConfirmActionGui extends AbstractAskGui { success = onConfirm.get(); } catch (Exception e) { CustomAnvil.instance.getLogger().log(Level.WARNING, "Could not process confirmation supplier.", e); + MetricsUtil.INSTANCE.trackError(e); success = false; } diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/EnchantConflictSubSettingGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/EnchantConflictSubSettingGui.java index 99bba84..97bdfcb 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/EnchantConflictSubSettingGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/EnchantConflictSubSettingGui.java @@ -25,6 +25,7 @@ import xyz.alexcrea.cuanvil.gui.util.GuiGlobalActions; import xyz.alexcrea.cuanvil.gui.util.GuiGlobalItems; import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant; import xyz.alexcrea.cuanvil.util.CasedStringUtil; +import xyz.alexcrea.cuanvil.util.MetricsUtil; import java.util.*; import java.util.function.Supplier; @@ -264,6 +265,7 @@ public class EnchantConflictSubSettingGui extends MappedToListSubSettingGui impl updateGuiValues(); } catch (Exception e) { CustomAnvil.instance.getLogger().log(Level.WARNING, "An error occurred while updating enchants for " + this.enchantConflict, e); + MetricsUtil.INSTANCE.trackError(e); } // Save file configuration to disk @@ -308,6 +310,7 @@ public class EnchantConflictSubSettingGui extends MappedToListSubSettingGui impl updateGuiValues(); } catch (Exception e) { CustomAnvil.instance.getLogger().log(Level.WARNING, "An error occurred while updating group for " + this.enchantConflict, e); + MetricsUtil.INSTANCE.trackError(e); } // Save file configuration to disk diff --git a/src/main/kotlin/io/delilaheve/CustomAnvil.kt b/src/main/kotlin/io/delilaheve/CustomAnvil.kt index e95622d..9b2b696 100644 --- a/src/main/kotlin/io/delilaheve/CustomAnvil.kt +++ b/src/main/kotlin/io/delilaheve/CustomAnvil.kt @@ -22,7 +22,6 @@ import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener import xyz.alexcrea.cuanvil.update.ModrinthUpdateChecker import xyz.alexcrea.cuanvil.update.PluginSetDefault import xyz.alexcrea.cuanvil.update.UpdateHandler -import xyz.alexcrea.cuanvil.util.Metrics import xyz.alexcrea.cuanvil.util.MetricsUtil import java.io.File import java.io.FileReader @@ -118,7 +117,8 @@ open class CustomAnvil : JavaPlugin() { try { legacyCheck() } catch (e: Exception) { - logger.log(Level.SEVERE, "error trying to check for legacy system" , e) + logger.log(Level.SEVERE, "error trying to check for legacy system", e) + MetricsUtil.trackError(e) if(trySafeStart()) return } @@ -126,7 +126,8 @@ open class CustomAnvil : JavaPlugin() { try { prepareCommand() } catch (e: Exception) { - logger.log(Level.SEVERE, "error trying to register commands" , e) + logger.log(Level.SEVERE, "error trying to register commands", e) + MetricsUtil.trackError(e) if(trySafeStart()) return } @@ -136,6 +137,7 @@ open class CustomAnvil : JavaPlugin() { throw RuntimeException("Error loading configuration file") } catch (e: Exception) { logger.log(Level.SEVERE, "error occurred loading default configuration", e) + MetricsUtil.trackError(e) if(tryDirtyStart()) return } @@ -144,6 +146,7 @@ open class CustomAnvil : JavaPlugin() { DependencyManager.loadDependency() } catch (e: Exception) { logger.log(Level.SEVERE, "error loading dependency compatibility", e) + MetricsUtil.trackError(e) if(tryDirtyStart()) return } @@ -152,6 +155,7 @@ open class CustomAnvil : JavaPlugin() { registerListeners() } catch (e: Exception) { logger.log(Level.SEVERE, "error registering listeners", e) + MetricsUtil.trackError(e) if(tryDirtyStart()) return } @@ -171,7 +175,8 @@ open class CustomAnvil : JavaPlugin() { try { loadEnchantmentSystem() } catch (e: Exception) { - logger.log(Level.SEVERE, "error initializing enchantment ssytem", e) + logger.log(Level.SEVERE, "error initializing enchantment system", e) + MetricsUtil.trackError(e) tryDirtyStart() } } @@ -198,7 +203,10 @@ open class CustomAnvil : JavaPlugin() { ModrinthUpdateChecker(modrinthPluginID, loader, null) .setFeatured(featured) - .setOnError { logger.log(Level.WARNING, "error trying to fetch latest update", it) } + .setOnError { + logger.log(Level.WARNING, "error trying to fetch latest update", it) + MetricsUtil.trackError(it) + } .checkVersion { latestVer: String? -> CustomAnvil.latestVer = latestVer if(latestVer == null || version.contains(latestVer)) return@checkVersion diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt index 42383b6..6794af0 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt @@ -27,6 +27,7 @@ import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.componentLore import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener.Companion.ANVIL_OUTPUT_SLOT import xyz.alexcrea.cuanvil.util.AnvilUseType +import xyz.alexcrea.cuanvil.util.MetricsUtil.trackError import java.util.logging.Level object DependencyManager { @@ -141,6 +142,7 @@ object DependencyManager { "Error while trying to handle custom anvil supported plugin: ", e ) + trackError(e) // Just in case to avoid illegal items event.inventory.setItem(ANVIL_OUTPUT_SLOT, null) @@ -180,6 +182,7 @@ object DependencyManager { "Error while trying to handle custom anvil supported plugin: ", e ) + trackError(e) // Just in case to avoid illegal items event.inventory.setItem(ANVIL_OUTPUT_SLOT, null) @@ -233,6 +236,7 @@ object DependencyManager { "Error while trying to handle custom anvil supported plugin: ", e ) + trackError(e) // Just in case to avoid illegal items event.inventory.setItem(ANVIL_OUTPUT_SLOT, null) @@ -262,6 +266,7 @@ object DependencyManager { "Error while trying to handle custom anvil supported plugin: ", e ) + trackError(e) // Just in case to avoid illegal items event.inventory.setItem(ANVIL_OUTPUT_SLOT, null) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/DisenchantmentDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/DisenchantmentDependency.kt index f014c8a..089a5fb 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/DisenchantmentDependency.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/DisenchantmentDependency.kt @@ -16,6 +16,7 @@ import org.bukkit.inventory.AnvilInventory import org.bukkit.inventory.ItemStack import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener import xyz.alexcrea.cuanvil.util.AnvilXpUtil +import xyz.alexcrea.cuanvil.util.MetricsUtil.trackError import java.util.logging.Level import kotlin.reflect.KClass @@ -38,6 +39,7 @@ class DisenchantmentDependency { Level.SEVERE, "Could not initialize disenchantment support" + "please report this bug to the developer", e ) + trackError(e) } } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt index 3b912bf..460ee3f 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt @@ -25,7 +25,8 @@ object MetricsUtil { if(metricType.allowBStats) { try { val metric = Metrics(plugin, BSTATS_PLUGIN_ID) - //TODO nms type custom chart + metric.addCustomChart(Metrics.SimplePie("nms_type") { nmsType }) + metric.addCustomChart(Metrics.SimplePie("using_alpha") { isAlpha.toString() }) } catch (_: Exception) {} } From 7044860267d9934a5e97f9cdce9940defb369193 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 3 Mar 2026 03:12:35 +0100 Subject: [PATCH 096/207] some fix and add disable error telemetry config --- defaultconfigs/1.18/config.yml | 7 ++++++- defaultconfigs/1.21.11/config.yml | 7 ++++++- defaultconfigs/1.21.9/config.yml | 7 ++++++- defaultconfigs/1.21/config.yml | 7 ++++++- .../io/delilaheve/util/ConfigOptions.kt | 1 + .../cuanvil/command/CustomAnvilCmd.kt | 20 ++++++++++++++----- .../xyz/alexcrea/cuanvil/util/MetricsUtil.kt | 11 +++++++--- src/main/resources/config.yml | 7 ++++++- 8 files changed, 54 insertions(+), 13 deletions(-) diff --git a/defaultconfigs/1.18/config.yml b/defaultconfigs/1.18/config.yml index 69a0e18..47070ba 100644 --- a/defaultconfigs/1.18/config.yml +++ b/defaultconfigs/1.18/config.yml @@ -8,9 +8,14 @@ # It can also collect error information if error is happening (currently faststats only) # It can also be disabled # Please refer to README for public metric link -# possible options: auto, bstat, faststats, disabled +# Possible options: auto, bstat, faststats, disabled (auto by default) metric_type: auto +# Allow to report errors made caused by this plugin (only for faststats) +# This allows me to fix potentials issue that I'm not aware of +# Accept true or false (true by default) +metric_collect_errors: true + # All anvil cost will be capped to limit_repair_value if enabled. # # In other words: diff --git a/defaultconfigs/1.21.11/config.yml b/defaultconfigs/1.21.11/config.yml index c281de8..0d9e089 100644 --- a/defaultconfigs/1.21.11/config.yml +++ b/defaultconfigs/1.21.11/config.yml @@ -8,9 +8,14 @@ # It can also collect error information if error is happening (currently faststats only) # It can also be disabled # Please refer to README for public metric link -# possible options: auto, bstat, faststats, disabled +# Possible options: auto, bstat, faststats, disabled (auto by default) metric_type: auto +# Allow to report errors made caused by this plugin (only for faststats) +# This allows me to fix potentials issue that I'm not aware of +# Accept true or false (true by default) +metric_collect_errors: true + # All anvil cost will be capped to limit_repair_value if enabled. # # In other words: diff --git a/defaultconfigs/1.21.9/config.yml b/defaultconfigs/1.21.9/config.yml index de6f020..d3538b2 100644 --- a/defaultconfigs/1.21.9/config.yml +++ b/defaultconfigs/1.21.9/config.yml @@ -8,9 +8,14 @@ # It can also collect error information if error is happening (currently faststats only) # It can also be disabled # Please refer to README for public metric link -# possible options: auto, bstat, faststats, disabled +# Possible options: auto, bstat, faststats, disabled (auto by default) metric_type: auto +# Allow to report errors made caused by this plugin (only for faststats) +# This allows me to fix potentials issue that I'm not aware of +# Accept true or false (true by default) +metric_collect_errors: true + # All anvil cost will be capped to limit_repair_value if enabled. # # In other words: diff --git a/defaultconfigs/1.21/config.yml b/defaultconfigs/1.21/config.yml index ca4f248..c1677fb 100644 --- a/defaultconfigs/1.21/config.yml +++ b/defaultconfigs/1.21/config.yml @@ -8,9 +8,14 @@ # It can also collect error information if error is happening (currently faststats only) # It can also be disabled # Please refer to README for public metric link -# possible options: auto, bstat, faststats, disabled +# Possible options: auto, bstat, faststats, disabled (auto by default) metric_type: auto +# Allow to report errors made caused by this plugin (only for faststats) +# This allows me to fix potentials issue that I'm not aware of +# Accept true or false (true by default) +metric_collect_errors: true + # All anvil cost will be capped to limit_repair_value if enabled. # # In other words: diff --git a/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt b/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt index 37ec7c0..8bd76d2 100644 --- a/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt +++ b/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt @@ -22,6 +22,7 @@ object ConfigOptions { // ---------------------- const val METRIC_TYPE = "metric_type" + const val METRIC_COLLECT_ERROR = "metric_collect_errors" const val CAP_ANVIL_COST = "limit_repair_cost" const val MAX_ANVIL_COST = "limit_repair_value" diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCmd.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCmd.kt index e1c2b34..f1fcb88 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCmd.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCmd.kt @@ -6,6 +6,7 @@ import org.bukkit.command.Command import org.bukkit.command.CommandExecutor import org.bukkit.command.CommandSender import org.bukkit.command.TabCompleter +import xyz.alexcrea.cuanvil.util.MetricsUtil import java.util.ArrayList class CustomAnvilCmd(plugin: CustomAnvil) : CommandExecutor, TabCompleter { @@ -37,10 +38,14 @@ class CustomAnvilCmd(plugin: CustomAnvil) : CommandExecutor, TabCompleter { args: Array ): Boolean { // Find sub command to execute based on the provided command name - val subcmd: CASubCommand? = if(args.isEmpty()) { - editConfigCommand + val subcmd: CASubCommand? + val newargs: Array + if(args.isEmpty()) { + subcmd = editConfigCommand + newargs = args }else { - commands[args[0].lowercase()] + subcmd = commands[args[0].lowercase()] + newargs = args.copyOfRange(1, args.size) } if(subcmd == null) { @@ -48,8 +53,13 @@ class CustomAnvilCmd(plugin: CustomAnvil) : CommandExecutor, TabCompleter { return true } - val newargs = args.copyOfRange(1, args.size) - return subcmd.executeCommand(sender, cmd, cmdstr, newargs) + try { + return subcmd.executeCommand(sender, cmd, cmdstr, newargs) + } catch (e: Throwable) { + MetricsUtil.trackError(e) + sender.sendMessage("§cError running this command") + return false + } } override fun onTabComplete( diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt index 460ee3f..ad441a5 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt @@ -17,7 +17,8 @@ object MetricsUtil { private var FAST_STATS_METRICS: BukkitMetrics? = null fun loadMetrics(plugin: CustomAnvil) { - val metricString = ConfigHolder.DEFAULT_CONFIG.config.getString(ConfigOptions.METRIC_TYPE, MetricType.AUTO.value)!! + val config = ConfigHolder.DEFAULT_CONFIG.config + val metricString = config.getString(ConfigOptions.METRIC_TYPE, MetricType.AUTO.value)!! val metricType = MetricType.from(metricString) val nmsType = DiagnosticExecutor.fetchNMSType() @@ -31,15 +32,19 @@ object MetricsUtil { } if(metricType.allowFastStats) { - ERROR_TRACKER = ErrorTracker.contextAware(); + val reportErrors = config.getBoolean(ConfigOptions.METRIC_COLLECT_ERROR, true) + if(reportErrors) + ERROR_TRACKER = ErrorTracker.contextAware() + FAST_STATS_METRICS = BukkitMetrics.factory() .addMetric(Metric.string("nms_type") { nmsType }) + .addMetric(Metric.bool("replace_too_expensive") { ConfigOptions.doReplaceTooExpensive }) .addMetric(Metric.bool("using_alpha") { isAlpha }) .errorTracker(ERROR_TRACKER) .token(FASTSTATS_TOKEN) .create(plugin) - FAST_STATS_METRICS!!.ready() + if(reportErrors) FAST_STATS_METRICS!!.ready() } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 88ed56b..7cc1768 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -8,9 +8,14 @@ # It can also collect error information if error is happening (currently faststats only) # It can also be disabled # Please refer to README for public metric link -# possible options: auto, bstat, faststats, disabled +# Possible options: auto, bstat, faststats, disabled (auto by default) metric_type: auto +# Allow to report errors made caused by this plugin (only for faststats) +# This allows me to fix potentials issue that I'm not aware of +# Accept true or false (true by default) +metric_collect_errors: true + # All anvil cost will be capped to limit_repair_value if enabled. # # In other words: From 621222fc01002883f501ea1050896128e87d8b9c Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 3 Mar 2026 03:35:15 +0100 Subject: [PATCH 097/207] better telemetry disclosure --- README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e1d7ec6..fe552b8 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ Please note that 1.16.5 to 1.17.1 are not officially supported. Run at your own ### For custom enchantment plugin developers For information about the API, please refer to [the Wiki](https://github.com/alexcrea/CustomAnvil/wiki) \ -(Please note that the wiki is currently incomplete)​ +(Please note that the wiki is currently incomplete) --- @@ -76,7 +76,14 @@ For information about the API, please refer to [the Wiki](https://github.com/ale see [Here](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs) --- -Custom anvil [use bstat](https://bstats.org/plugin/bukkit/Unsafe%20Enchants%20Plus/20923) for metric. You can [disable it](https://bstats.org/getting-started) if you like. +### Metric And Telemetry +Custom anvil [use bstat](https://bstats.org/plugin/bukkit/Unsafe%20Enchants%20Plus/20923) +and [faststats](https://faststats.dev/project/customanvil/minecraft-plugin) for metric and error reporting. + +You can select specific telemetry or disable all in config.yml. \ +You can also [disable bstat](https://bstats.org/getting-started) and [faststats](https://faststats.dev/info) in their /plugin folder if you like too. + +faststats is in beta testing please report me or them any error you encounter ### Credits and Thanks Credits and thanks can be seen [here](https://github.com/alexcrea/CustomAnvil/blob/v1.x.x/CREDITS.md) From bc7ed5af85b220b3f887ead5694c54b87667bc49 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 3 Mar 2026 03:59:58 +0100 Subject: [PATCH 098/207] add permission on tab completer and pre execute --- .../kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCmd.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCmd.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCmd.kt index f1fcb88..c3de43b 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCmd.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCmd.kt @@ -24,8 +24,6 @@ class CustomAnvilCmd(plugin: CustomAnvil) : CommandExecutor, TabCompleter { ) init { - - println(plugin.getCommand(genericCommandName)) val self = plugin.getCommand(genericCommandName)!! self.setExecutor(this) self.tabCompleter = this @@ -48,7 +46,7 @@ class CustomAnvilCmd(plugin: CustomAnvil) : CommandExecutor, TabCompleter { newargs = args.copyOfRange(1, args.size) } - if(subcmd == null) { + if(subcmd == null || !subcmd.allowed(sender)) { sender.sendMessage("Invalid subcommand. run `$cmdstr help` to see available commands") return true } @@ -78,6 +76,8 @@ class CustomAnvilCmd(plugin: CustomAnvil) : CommandExecutor, TabCompleter { if(subcmd != null) { val newArgs = args.copyOfRange(1, args.size) + if(!subcmd.allowed(sender)) return result + subcmd.tabCompleter(sender, newArgs, result) } } From 04408350139044b5b01765c742e3c43696664933 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 3 Mar 2026 04:08:04 +0100 Subject: [PATCH 099/207] Help Command --- src/main/kotlin/io/delilaheve/CustomAnvil.kt | 4 +-- .../alexcrea/cuanvil/command/CASubCommand.kt | 4 +++ ...ustomAnvilCmd.kt => CustomAnvilCommand.kt} | 11 +++++-- .../cuanvil/command/DiagnosticExecutor.kt | 4 +++ .../cuanvil/command/EditConfigExecutor.kt | 4 +++ .../alexcrea/cuanvil/command/HelpExecutor.kt | 32 +++++++++++++++++++ .../cuanvil/command/ReloadExecutor.kt | 4 +++ 7 files changed, 58 insertions(+), 5 deletions(-) rename src/main/kotlin/xyz/alexcrea/cuanvil/command/{CustomAnvilCmd.kt => CustomAnvilCommand.kt} (88%) create mode 100644 src/main/kotlin/xyz/alexcrea/cuanvil/command/HelpExecutor.kt diff --git a/src/main/kotlin/io/delilaheve/CustomAnvil.kt b/src/main/kotlin/io/delilaheve/CustomAnvil.kt index 9b2b696..44ca0a8 100644 --- a/src/main/kotlin/io/delilaheve/CustomAnvil.kt +++ b/src/main/kotlin/io/delilaheve/CustomAnvil.kt @@ -6,7 +6,7 @@ import org.bukkit.configuration.file.YamlConfiguration import org.bukkit.plugin.java.JavaPlugin import xyz.alexcrea.cuanvil.api.event.CAConfigReadyEvent import xyz.alexcrea.cuanvil.api.event.CAEnchantRegistryReadyEvent -import xyz.alexcrea.cuanvil.command.CustomAnvilCmd +import xyz.alexcrea.cuanvil.command.CustomAnvilCommand import xyz.alexcrea.cuanvil.command.EditConfigExecutor import xyz.alexcrea.cuanvil.command.ReloadExecutor import xyz.alexcrea.cuanvil.config.ConfigHolder @@ -309,7 +309,7 @@ open class CustomAnvil : JavaPlugin() { command = getCommand(commandConfigName) command?.setExecutor(EditConfigExecutor()) - CustomAnvilCmd(this) + CustomAnvilCommand(this) } } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/CASubCommand.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/CASubCommand.kt index 85c1a58..b06d07f 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/CASubCommand.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/CASubCommand.kt @@ -39,4 +39,8 @@ abstract class CASubCommand: CommandExecutor { list: MutableList) { } + open fun description(): String { + return "no description" + } + } \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCmd.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCommand.kt similarity index 88% rename from src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCmd.kt rename to src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCommand.kt index c3de43b..e5e689e 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCmd.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCommand.kt @@ -9,7 +9,7 @@ import org.bukkit.command.TabCompleter import xyz.alexcrea.cuanvil.util.MetricsUtil import java.util.ArrayList -class CustomAnvilCmd(plugin: CustomAnvil) : CommandExecutor, TabCompleter { +class CustomAnvilCommand(plugin: CustomAnvil) : CommandExecutor, TabCompleter { // Name of the generic command companion object { @@ -17,16 +17,20 @@ class CustomAnvilCmd(plugin: CustomAnvil) : CommandExecutor, TabCompleter { } private val editConfigCommand = EditConfigExecutor() + private val helpCommand = HelpExecutor() private val commands = ImmutableMap.of( "gui", editConfigCommand, "reload", ReloadExecutor(), "diagnostic", DiagnosticExecutor(), + "help", helpCommand, ) init { val self = plugin.getCommand(genericCommandName)!! self.setExecutor(this) self.tabCompleter = this + + helpCommand.commands = commands } override fun onCommand( @@ -68,8 +72,9 @@ class CustomAnvilCmd(plugin: CustomAnvil) : CommandExecutor, TabCompleter { ): MutableList { val result = ArrayList() if(args.size < 2) { - for (cmd in commands) { - result.add(cmd.key) + for ((key, cmd) in commands) { + if(!cmd.allowed(sender)) continue + result.add(key) } } else { val subcmd = commands[args[0].lowercase()] diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt index 6ddc8df..e550bd1 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt @@ -204,6 +204,10 @@ class DiagnosticExecutor: CASubCommand() { return this.name + " v" + this.description.version } + override fun description(): String { + return "Basic diagnostic of this plugin" + } + private fun pluginListDiag(sender: CommandSender, stb: StringBuilder) { val enabledPlugins: MutableList = ArrayList() val disabledPlugins: MutableList = ArrayList() diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/EditConfigExecutor.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/EditConfigExecutor.kt index d489db2..fba89b7 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/EditConfigExecutor.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/EditConfigExecutor.kt @@ -38,4 +38,8 @@ class EditConfigExecutor: CASubCommand() { return sender.hasPermission(CustomAnvil.editConfigPermission) } + override fun description(): String { + return "Gui to edit the plugin's config" + } + } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/HelpExecutor.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/HelpExecutor.kt new file mode 100644 index 0000000..697f1ee --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/HelpExecutor.kt @@ -0,0 +1,32 @@ +package xyz.alexcrea.cuanvil.command + +import com.google.common.collect.ImmutableMap +import org.bukkit.command.Command +import org.bukkit.command.CommandSender + +class HelpExecutor: CASubCommand() { + + lateinit var commands: ImmutableMap + + override fun executeCommand(sender: CommandSender, + cmd: Command, + cmdstr: String, + args: Array): Boolean { + + val stb = StringBuilder("List of available commands:") + for ((key, cmd) in commands) { + if(!cmd.allowed(sender)) continue + + stb.append("\n- $key: ").append(cmd.description()) + } + + sender.sendMessage(stb.toString()) + + return true + } + + override fun description(): String { + return "Help command" + } + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/ReloadExecutor.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/ReloadExecutor.kt index f3f97a7..ef23e7d 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/ReloadExecutor.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/ReloadExecutor.kt @@ -39,6 +39,10 @@ class ReloadExecutor : CASubCommand() { return sender.hasPermission(CustomAnvil.commandReloadPermission) } + override fun description(): String { + return "Reload the configuration of this plugin" + } + /** * Execute the command, return true if success or false otherwise */ From 77c8494166ae426ef7b116109fd7c80204f28812 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 3 Mar 2026 04:23:07 +0100 Subject: [PATCH 100/207] Add last detected error in diag optional --- .../cuanvil/command/DiagnosticExecutor.kt | 17 +++++++++++++++++ .../xyz/alexcrea/cuanvil/util/MetricsUtil.kt | 3 +++ 2 files changed, 20 insertions(+) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt index e550bd1..a89318c 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt @@ -29,6 +29,7 @@ import xyz.alexcrea.cuanvil.dependency.packet.ProtocoLibWrapper import xyz.alexcrea.cuanvil.dependency.packet.versions.PaperPacketManager import xyz.alexcrea.cuanvil.enchant.CAEnchantmentRegistry import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener +import xyz.alexcrea.cuanvil.util.MetricsUtil import java.util.* import java.util.stream.Collectors @@ -59,6 +60,7 @@ class DiagnosticExecutor: CASubCommand() { PLUGIN_PRIVACY("plugin_privacy"), NO_MERGE_TEST("no_merge_test"), FULL_ENCHANTMENT_DATA("full_enchantment_data"), + INCLUDE_LAST_ERROR("include_last_error"), } private fun fetchParameters(args: Array): EnumSet { @@ -154,6 +156,8 @@ class DiagnosticExecutor: CASubCommand() { .append(System.getProperty("os.arch")) } + stb.append("\nHad detect error: ").append(if(MetricsUtil.lastError != null) "Yes" else "No") + if(!params.contains(DiagParams.PLUGIN_PRIVACY)) { pluginListDiag(sender, stb) } @@ -168,6 +172,10 @@ class DiagnosticExecutor: CASubCommand() { if(params.contains(DiagParams.FULL_ENCHANTMENT_DATA)){ fullEnchantmentData(stb) } + + if(params.contains(DiagParams.INCLUDE_LAST_ERROR)){ + includeLastError(stb) + } } private fun testMerge(player: Player, stb: StringBuilder) { @@ -323,5 +331,14 @@ class DiagnosticExecutor: CASubCommand() { }") } + + private fun includeLastError(stb: StringBuilder) { + val e = MetricsUtil.lastError ?: return + + stb.append("\n\nLast stack trace: ${e.stackTraceToString()}") + + + + } } \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt index ad441a5..07e21fe 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt @@ -52,8 +52,11 @@ object MetricsUtil { FAST_STATS_METRICS?.shutdown() } + var lastError: Throwable? = null + fun trackError(e: Throwable) { ERROR_TRACKER?.trackError(e) + lastError = e } fun trackError(message: String) { From 5caa56be592ac1066fd249c7015a08a47f670799 Mon Sep 17 00:00:00 2001 From: alexcrea <42614139+alexcrea@users.noreply.github.com> Date: Tue, 3 Mar 2026 15:07:38 +0100 Subject: [PATCH 101/207] lowercase .md --- CREDITS.MD => CREDITS.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename CREDITS.MD => CREDITS.md (100%) diff --git a/CREDITS.MD b/CREDITS.md similarity index 100% rename from CREDITS.MD rename to CREDITS.md From 7bb0c1523d78434e9690d453f719927c8bb1be57 Mon Sep 17 00:00:00 2001 From: alexcrea <42614139+alexcrea@users.noreply.github.com> Date: Tue, 3 Mar 2026 15:07:54 +0100 Subject: [PATCH 102/207] lowercase .md --- COMPATIBILITY.MD => COMPATIBILITY.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename COMPATIBILITY.MD => COMPATIBILITY.md (100%) diff --git a/COMPATIBILITY.MD b/COMPATIBILITY.md similarity index 100% rename from COMPATIBILITY.MD rename to COMPATIBILITY.md From 9b8a2d0f329b0463453fe26f26b505ba728cd5e3 Mon Sep 17 00:00:00 2001 From: alexcrea <42614139+alexcrea@users.noreply.github.com> Date: Tue, 3 Mar 2026 15:21:40 +0100 Subject: [PATCH 103/207] [ci skip] update permissions in readme --- README.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index fe552b8..22d9da1 100644 --- a/README.md +++ b/README.md @@ -28,14 +28,20 @@ the plugin can be downloaded on --- ### Permissions: ```yml +# Generic and bypass permissions ca.affected: Player with this permission will be affected by the plugin ca.bypass.fuse: Allow player to combine every enchantments to every item (no custom limit) ca.bypass.level: Allow player to bypass every level limit (no custom limit) -ca.command.reload: Allow administrator to reload the plugin's configs -ca.config.edit: Allow administrator to edit the plugin's config in game -# Bellow permissions also require some config change to allow usage of features -# usage of these permission is toggleable in basic config gui or config.yml +# Command permissions +ca.command.reload: Allow administrator to reload the plugin's configs +ca.command.diagnostic: Allow adminastator to diagnistic some simple problem with the plugin +ca.config.edit: Allow administrator to edit the plugin's config in game + +# ----------------------------------------------------------------------------- +# Bellow permissions also require some config change to allow usage of features +# Usage of these permission is toggleable in basic config gui or config.yml +# ----------------------------------------------------------------------------- # Permissions related to use of color and minimessage ca.color.code: Allow player to use color code on rename if enabled (toggleable) ca.color.hex: Allow player to use hexadecimal color on rename if enabled (toggleable) @@ -47,10 +53,9 @@ ca.lore_edit.paper: Allow player to edit lore via paper if enabled (toggleable) ``` ### Commands -```yml -anvilconfigreload or carl: Reload every config of this plugin -customanvilconfig or configanvil: open a menu for administrator to edit plugin's config in game -``` + +run `customanvil help` to get information about available commands (need permissions to use them) + ### Supported Plugins See the [Compatibility list](https://github.com/alexcrea/CustomAnvil/blob/v1.x.x/COMPATIBILITY.md) From e08a02a84adbecbbb308716ea9f85684bf813cb9 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 3 Mar 2026 15:46:35 +0100 Subject: [PATCH 104/207] fix class error on java 17 --- .../cuanvil/command/DiagnosticExecutor.kt | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt index a89318c..5d71119 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt @@ -43,14 +43,18 @@ class DiagnosticExecutor: CASubCommand() { val packetManager = DependencyManager.packetManager val packetManagerClass = packetManager.javaClass - val result = when (packetManagerClass) { - PaperPacketManager::class.java -> "Paper NMS" - ProtocoLibWrapper::class.java -> "Protocolib" - NoPacketManager::class.java -> "None" - else -> "Version Specific" - + val className = packetManagerClass.name + val result = if(className.contains("PaperPacket")) { + "Paper" + } else { + when (packetManagerClass) { + ProtocoLibWrapper::class.java -> "Protocolib" + NoPacketManager::class.java -> "None" + else -> "Version Specific" + } } + return "$result ${if(packetManager.canSetInstantBuild) '✅' else '❌'}" } } From b532ce7dc6170b17119d8ae35ab7277f09df9047 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 3 Mar 2026 15:51:41 +0100 Subject: [PATCH 105/207] avoid error on java < 21 --- .../xyz/alexcrea/cuanvil/util/MetricsUtil.kt | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt index 07e21fe..dbec6e2 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt @@ -32,22 +32,32 @@ object MetricsUtil { } if(metricType.allowFastStats) { - val reportErrors = config.getBoolean(ConfigOptions.METRIC_COLLECT_ERROR, true) - if(reportErrors) - ERROR_TRACKER = ErrorTracker.contextAware() - - FAST_STATS_METRICS = BukkitMetrics.factory() - .addMetric(Metric.string("nms_type") { nmsType }) - .addMetric(Metric.bool("replace_too_expensive") { ConfigOptions.doReplaceTooExpensive }) - .addMetric(Metric.bool("using_alpha") { isAlpha }) - .errorTracker(ERROR_TRACKER) - .token(FASTSTATS_TOKEN) - .create(plugin) - - if(reportErrors) FAST_STATS_METRICS!!.ready() + // Check support java 21 (metric only work in java 21) + val versionParts = System.getProperty("java.version").split(".") + val majorVersion = versionParts[0].toInt() + if (majorVersion >= 21) try { + faststatTelemetry(plugin, nmsType, isAlpha) + } catch (_: Throwable) {} } } + private fun faststatTelemetry(plugin: CustomAnvil, nmsType: String, isAlpha: Boolean) { + val config = ConfigHolder.DEFAULT_CONFIG.config + val reportErrors = config.getBoolean(ConfigOptions.METRIC_COLLECT_ERROR, true) + if(reportErrors) + ERROR_TRACKER = ErrorTracker.contextAware() + + FAST_STATS_METRICS = BukkitMetrics.factory() + .addMetric(Metric.string("nms_type") { nmsType }) + .addMetric(Metric.bool("replace_too_expensive") { ConfigOptions.doReplaceTooExpensive }) + .addMetric(Metric.bool("using_alpha") { isAlpha }) + .errorTracker(ERROR_TRACKER) + .token(FASTSTATS_TOKEN) + .create(plugin) + + if(reportErrors) FAST_STATS_METRICS!!.ready() + } + fun shutdownMetrics() { FAST_STATS_METRICS?.shutdown() } From ab3e4a32baa1e5955d8c48620e4f5091fdc9ac31 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 3 Mar 2026 16:19:45 +0100 Subject: [PATCH 106/207] [ci skip] version bump --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 903c988..457fd2e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,7 +22,7 @@ plugins { } group = "xyz.alexcrea" -version = "1.16.0" +version = "1.16.1" val isDevBuild = System.getenv("SMALL_COMMIT_HASH") != null val isPreRelease = System.getenv("IS_GITHUB_PRERELEASE") == "true" From 6a4c861eab2765cb3157791c7bdd2f13f5badc72 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 9 Mar 2026 21:22:49 +0100 Subject: [PATCH 107/207] update faststat --- build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 457fd2e..2a7e922 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,7 +22,7 @@ plugins { } group = "xyz.alexcrea" -version = "1.16.1" +version = "1.16.2" val isDevBuild = System.getenv("SMALL_COMMIT_HASH") != null val isPreRelease = System.getenv("IS_GITHUB_PRERELEASE") == "true" @@ -52,7 +52,7 @@ dependencies { compileOnly("org.spigotmc:spigot-api:1.18-R0.1-SNAPSHOT") // fast stats - implementation("dev.faststats.metrics:bukkit:0.16.0") + implementation("dev.faststats.metrics:bukkit:0.18.0") // minimessage implementation("net.kyori:adventure-text-minimessage:4.25.0") From 6afe51acca892eb63dca15a0a8ca0d6326abd8c1 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 9 Mar 2026 22:16:35 +0100 Subject: [PATCH 108/207] reduce faststat java major version --- src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt index dbec6e2..80a84c2 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt @@ -32,10 +32,10 @@ object MetricsUtil { } if(metricType.allowFastStats) { - // Check support java 21 (metric only work in java 21) + // Check support java 17 (metric only work in java 17) val versionParts = System.getProperty("java.version").split(".") val majorVersion = versionParts[0].toInt() - if (majorVersion >= 21) try { + if (majorVersion >= 17) try { faststatTelemetry(plugin, nmsType, isAlpha) } catch (_: Throwable) {} } From f59071f504bf492450ef56341934137a5acefcbc Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 9 Mar 2026 22:43:57 +0100 Subject: [PATCH 109/207] prioritize paper nms on paper servers --- .../cuanvil/command/DiagnosticExecutor.kt | 1 - .../dependency/packet/PacketManagerSelector.kt | 16 ++++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt index 5d71119..053a3fc 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt @@ -26,7 +26,6 @@ import org.bukkit.plugin.RegisteredListener import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.dependency.packet.NoPacketManager import xyz.alexcrea.cuanvil.dependency.packet.ProtocoLibWrapper -import xyz.alexcrea.cuanvil.dependency.packet.versions.PaperPacketManager import xyz.alexcrea.cuanvil.enchant.CAEnchantmentRegistry import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener import xyz.alexcrea.cuanvil.util.MetricsUtil diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt index f38b9e4..775d197 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt @@ -8,14 +8,22 @@ import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil import xyz.alexcrea.cuanvil.update.UpdateUtils object PacketManagerSelector { + + private const val PAPER_CRAFT_PLAYER_CLASS = "org.bukkit.craftbukkit.entity.CraftPlayer" + fun selectPacketManager(forceProtocolib: Boolean): PacketManager { // Try to find version return if (forceProtocolib) protocolibIfPresent - else - reobfPacketManager ?: - if(PlatformUtil.isPaper) PaperPacketManager() - else protocolibIfPresent + else { + try { + Class.forName(PAPER_CRAFT_PLAYER_CLASS) + + return PaperPacketManager() + } catch (_: ClassNotFoundException) { + return reobfPacketManager ?: protocolibIfPresent + } + } } private val protocolibIfPresent: PacketManager From c96dd7d308324dedae17db63a78da657385ad305 Mon Sep 17 00:00:00 2001 From: alexd <42614139+alexcrea@users.noreply.github.com> Date: Mon, 23 Mar 2026 17:57:47 +0100 Subject: [PATCH 110/207] simplify and extend enchant limit --- defaultconfigs/1.18/config.yml | 8 +-- defaultconfigs/1.21.11/config.yml | 8 +-- defaultconfigs/1.21.9/config.yml | 8 +-- defaultconfigs/1.21/config.yml | 8 +-- .../alexcrea/cuanvil/api/EnchantmentApi.java | 5 +- .../config/global/EnchantLimitConfigGui.java | 16 +++-- .../gui/config/settings/IntSettingsGui.java | 11 ++- .../cuanvil/update/PluginSetDefault.java | 1 - .../io/delilaheve/util/ConfigOptions.kt | 71 +++++++------------ src/main/resources/config.yml | 8 +-- 10 files changed, 58 insertions(+), 86 deletions(-) diff --git a/defaultconfigs/1.18/config.yml b/defaultconfigs/1.18/config.yml index 47070ba..5a85d1d 100644 --- a/defaultconfigs/1.18/config.yml +++ b/defaultconfigs/1.18/config.yml @@ -87,18 +87,14 @@ permission_needed_for_color: true # Valid values include 0 to 1000. use_of_color_cost: 0 -# Default limit to apply to any enchants missing from enchant_limits -# -# Valid values include 1 to 1000 -default_limit: 5 - # Override limits for specific enchants # # Enchantments not listed here will use the value of default_limit # # Overrides provided default from aqua_affinity to depth_strider won't change effect with extra levels # -# Valid range of 1 - 255 for each enchantment +# Valid range of 0 - 255 for each enchantment +# -1 mean keep default enchant_limits: minecraft:aqua_affinity: 1 minecraft:binding_curse: 1 diff --git a/defaultconfigs/1.21.11/config.yml b/defaultconfigs/1.21.11/config.yml index 0d9e089..267df3d 100644 --- a/defaultconfigs/1.21.11/config.yml +++ b/defaultconfigs/1.21.11/config.yml @@ -89,18 +89,14 @@ permission_needed_for_color: true # Valid values include 0 to 1000. use_of_color_cost: 0 -# Default limit to apply to any enchants missing from enchant_limits -# -# Valid values include 1 to 1000 -default_limit: 5 - # Override limits for specific enchants # # Enchantments not listed here will use the value of default_limit # # Overrides provided default from aqua_affinity to depth_strider won't change effect with extra levels # -# Valid range of 1 - 255 for each enchantment +# Valid range of 0 - 255 for each enchantment +# -1 mean keep default enchant_limits: minecraft:aqua_affinity: 1 minecraft:binding_curse: 1 diff --git a/defaultconfigs/1.21.9/config.yml b/defaultconfigs/1.21.9/config.yml index d3538b2..87d03a9 100644 --- a/defaultconfigs/1.21.9/config.yml +++ b/defaultconfigs/1.21.9/config.yml @@ -87,18 +87,14 @@ permission_needed_for_color: true # Valid values include 0 to 1000. use_of_color_cost: 0 -# Default limit to apply to any enchants missing from enchant_limits -# -# Valid values include 1 to 1000 -default_limit: 5 - # Override limits for specific enchants # # Enchantments not listed here will use the value of default_limit # # Overrides provided default from aqua_affinity to depth_strider won't change effect with extra levels # -# Valid range of 1 - 255 for each enchantment +# Valid range of 0 - 255 for each enchantment +# -1 mean keep default enchant_limits: minecraft:aqua_affinity: 1 minecraft:binding_curse: 1 diff --git a/defaultconfigs/1.21/config.yml b/defaultconfigs/1.21/config.yml index c1677fb..b6b620c 100644 --- a/defaultconfigs/1.21/config.yml +++ b/defaultconfigs/1.21/config.yml @@ -87,18 +87,14 @@ permission_needed_for_color: true # Valid values include 0 to 1000. use_of_color_cost: 0 -# Default limit to apply to any enchants missing from enchant_limits -# -# Valid values include 1 to 1000 -default_limit: 5 - # Override limits for specific enchants # # Enchantments not listed here will use the value of default_limit # # Overrides provided default from aqua_affinity to depth_strider won't change effect with extra levels # -# Valid range of 1 - 255 for each enchantment +# Valid range of 0 - 255 for each enchantment +# -1 mean keep default enchant_limits: minecraft:aqua_affinity: 1 minecraft:binding_curse: 1 diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/EnchantmentApi.java b/src/main/java/xyz/alexcrea/cuanvil/api/EnchantmentApi.java index 76f1ac2..ac98225 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/api/EnchantmentApi.java +++ b/src/main/java/xyz/alexcrea/cuanvil/api/EnchantmentApi.java @@ -1,6 +1,7 @@ package xyz.alexcrea.cuanvil.api; import io.delilaheve.CustomAnvil; +import io.delilaheve.util.ConfigOptions; import org.bukkit.NamespacedKey; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.enchantments.Enchantment; @@ -180,13 +181,13 @@ public class EnchantmentApi { private static boolean tryWriteDefaultConfig(FileConfiguration defaultConfig, CAEnchantment enchantment, boolean override) { boolean hasChange = false; - String levelPath = "enchant_limits." + enchantment.getKey(); + String levelPath = ConfigOptions.ENCHANT_LIMIT_ROOT + "." + enchantment.getKey(); if(override || !defaultConfig.isSet(levelPath)){ defaultConfig.set(levelPath, enchantment.defaultMaxLevel()); hasChange = true; } - String basePath = "enchant_values." + enchantment.getKey(); + String basePath = ConfigOptions.ENCHANT_VALUES_ROOT + "." + enchantment.getKey(); EnchantmentRarity rarity = enchantment.defaultRarity(); String itemPath = basePath + ".item"; diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/EnchantLimitConfigGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/EnchantLimitConfigGui.java index d624bff..a95a5bb 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/EnchantLimitConfigGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/EnchantLimitConfigGui.java @@ -17,7 +17,7 @@ import java.util.Locale; */ public class EnchantLimitConfigGui extends AbstractEnchantConfigGui { - private static final String SECTION_NAME = "enchant_limits"; + private static final String SECTION_NAME = ConfigOptions.ENCHANT_LIMIT_ROOT; private static EnchantLimitConfigGui INSTANCE = null; @@ -41,18 +41,26 @@ public class EnchantLimitConfigGui extends AbstractEnchantConfigGui §e" + planned + " §r(§c-" + (now - planned) + "§r)"); + meta.setDisplayName("§e" + displayNow + " §f-> §e" + planned + " §r(§c-" + holder.valueDisplayName(now - planned) + "§r)"); meta.setLore(Collections.singletonList(AbstractSettingGui.CLICK_LORE)); item.setItemMeta(meta); @@ -116,7 +117,7 @@ public class IntSettingsGui extends AbstractSettingGui { ItemMeta meta = item.getItemMeta(); assert meta != null; - meta.setDisplayName("§e" + now + " §f-> §e" + planned + " §r(§a+" + (planned - now) + "§r)"); + meta.setDisplayName("§e" + displayNow + " §f-> §e" + planned + " §r(§a+" + holder.valueDisplayName(planned - now) + "§r)"); meta.setLore(Collections.singletonList(AbstractSettingGui.CLICK_LORE)); item.setItemMeta(meta); @@ -131,7 +132,7 @@ public class IntSettingsGui extends AbstractSettingGui { ItemMeta resultMeta = resultPaper.getItemMeta(); assert resultMeta != null; - resultMeta.setDisplayName("§fValue: §e" + now); + resultMeta.setDisplayName("§fValue: §e" + displayNow); resultMeta.setLore(holder.displayLore); resultPaper.setItemMeta(resultMeta); @@ -389,6 +390,10 @@ public class IntSettingsGui extends AbstractSettingGui { return getItem(itemMat, CasedStringUtil.detectToUpperSpacedCase(configPath)); } + public String valueDisplayName(int value) { + return String.valueOf(value); + } + } } diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/PluginSetDefault.java b/src/main/java/xyz/alexcrea/cuanvil/update/PluginSetDefault.java index f41842d..774c5da 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/update/PluginSetDefault.java +++ b/src/main/java/xyz/alexcrea/cuanvil/update/PluginSetDefault.java @@ -30,7 +30,6 @@ public class PluginSetDefault { nbSet += trySetDefault(config, ALLOW_HEXADECIMAL_COLOR, DEFAULT_ALLOW_HEXADECIMAL_COLOR); nbSet += trySetDefault(config, PERMISSION_NEEDED_FOR_COLOR, DEFAULT_PERMISSION_NEEDED_FOR_COLOR); nbSet += trySetDefault(config, USE_OF_COLOR_COST, DEFAULT_USE_OF_COLOR_COST); - nbSet += trySetDefault(config, DEFAULT_LIMIT_PATH, DEFAULT_ENCHANT_LIMIT); // Lore Edit defaults for (@NotNull LoreEditType value : LoreEditType.values()) { diff --git a/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt b/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt index 8bd76d2..c69f2fb 100644 --- a/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt +++ b/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt @@ -57,8 +57,6 @@ object ConfigOptions { const val ENCHANT_COUNT_LIMIT_DEFAULT = "$ENCHANT_COUNT_LIMIT_ROOT.default" const val ENCHANT_COUNT_LIMIT_ITEMS = "$ENCHANT_COUNT_LIMIT_ROOT.items" - const val DEFAULT_LIMIT_PATH = "default_limit" - const val ENCHANT_LIMIT_ROOT = "enchant_limits" const val ENCHANT_VALUES_ROOT = "enchant_values" @@ -102,8 +100,6 @@ object ConfigOptions { const val DEFAULT_PERMISSION_NEEDED_FOR_COLOR = true const val DEFAULT_USE_OF_COLOR_COST = 0 - const val DEFAULT_ENCHANT_LIMIT = 5 - // Debug flag private const val DEFAULT_DEBUG_LOG = false private const val DEFAULT_VERBOSE_DEBUG_LOG = false @@ -133,8 +129,7 @@ object ConfigOptions { val USE_OF_COLOR_COST_RANGE = 0..1000 // Valid range for an enchantment limit - @JvmField - val ENCHANT_LIMIT_RANGE = 1..255 + const val ENCHANT_LIMIT = 255 // Valid range for an enchantment count limit @JvmField @@ -348,16 +343,6 @@ object ConfigOptions { return WorkPenaltyPart(penaltyIncrease, penaltyAdditive, exclusivePenaltyIncrease, exclusivePenaltyAdditive) } - /** - * Default enchantment limit - */ - private val defaultEnchantLimit: Int - get() { - return ConfigHolder.DEFAULT_CONFIG - .config - .getInt(DEFAULT_LIMIT_PATH, DEFAULT_ENCHANT_LIMIT) - } - /** * Get material enchantment count limit * @@ -422,42 +407,37 @@ object ConfigOptions { * Get the given [enchantment]'s limit */ fun enchantLimit(enchantment: CAEnchantment): Int { + val limit = rawEnchantLimit(enchantment) + if(limit >= 0) return limit.coerceAtMost(ENCHANT_LIMIT) + + // get default + return enchantment.defaultMaxLevel() + } + + /** + * Get the given [enchantment]'s limit + */ + fun rawEnchantLimit(enchantment: CAEnchantment): Int { // Test namespace var limit = enchantLimit(enchantment.key.toString()) - if (limit != null) return limit + if (limit >= 0) return limit // Test legacy (name only) limit = enchantLimit(enchantment.enchantmentName) - if (limit != null) return limit + if (limit >= 0) return limit - // get default (and test old legacy if present) - return getDefaultLevel(enchantment.enchantmentName) + // Default to negative + return -1 } /** * Get the given [enchantmentName]'s limit */ - private fun enchantLimit(enchantmentName: String): Int? { + private fun enchantLimit(enchantmentName: String): Int { val path = "${ENCHANT_LIMIT_ROOT}.$enchantmentName" - return CustomAnvil.instance - .config - .getInt(path, ENCHANT_LIMIT_RANGE.first - 1) - .takeIf { it in ENCHANT_LIMIT_RANGE } - } - - /** - * Get default value if enchantment do not exist on config - */ - private fun getDefaultLevel( - enchantmentName: String, // compatibility with 1.20.5. TODO better update system - ): Int { - if (enchantmentName == "sweeping_edge") { - val limit = enchantLimit("sweeping") - if (limit != null) return limit - - } - return defaultEnchantLimit + return CustomAnvil.instance.config + .getInt(path, -1) } /** @@ -529,20 +509,20 @@ object ConfigOptions { fun maxBeforeMergeDisabled(enchantment: CAEnchantment): Int { val key = enchantment.key.toString() var value = maxBeforeMergeDisabled(key) - if (value != null) return value + if (value >= 0) return value // Legacy name val legacy = enchantment.enchantmentName value = maxBeforeMergeDisabled(legacy) - if (value != null) return value + if (value >= 0) return value if (key == "minecraft:sweeping_edge") { value = maxBeforeMergeDisabled("minecraft:sweeping") - if (value != null) return value + if (value >= 0) return value // legacy name of legacy enchantment name value = maxBeforeMergeDisabled("sweeping") - if (value != null) return value + if (value >= 0) return value } return DEFAULT_MAX_BEFORE_MERGE_DISABLED @@ -552,14 +532,13 @@ object ConfigOptions { * Get the given [enchantmentName]'s level before merge is disabled * a negative value would mean never disabled */ - private fun maxBeforeMergeDisabled(enchantmentName: String): Int? { + private fun maxBeforeMergeDisabled(enchantmentName: String): Int { // find if set val path = "${DISABLE_MERGE_OVER_ROOT}.$enchantmentName" return CustomAnvil.instance .config - .getInt(path, ENCHANT_LIMIT_RANGE.min() - 1) - .takeIf { it in ENCHANT_LIMIT_RANGE } + .getInt(path, -1) } fun isImmutable(key: NamespacedKey): Boolean { diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 7cc1768..95729b7 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -89,18 +89,14 @@ permission_needed_for_color: true # Valid values include 0 to 1000. use_of_color_cost: 0 -# Default limit to apply to any enchants missing from enchant_limits -# -# Valid values include 1 to 1000 -default_limit: 5 - # Override limits for specific enchants # # Enchantments not listed here will use the value of default_limit # # Overrides provided default from aqua_affinity to depth_strider won't change effect with extra levels # -# Valid range of 1 - 255 for each enchantment +# Valid range of 0 - 255 for each enchantment +# -1 mean keep default enchant_limits: minecraft:aqua_affinity: 1 minecraft:binding_curse: 1 From 882e50e44961478dafa6c3db9bb6651f6a2494e1 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 23 Mar 2026 18:04:14 +0100 Subject: [PATCH 111/207] remove force protocolib in config --- defaultconfigs/1.18/config.yml | 6 ------ defaultconfigs/1.21.11/config.yml | 6 ------ defaultconfigs/1.21.9/config.yml | 6 ------ defaultconfigs/1.21/config.yml | 6 ------ src/main/resources/config.yml | 6 ------ 5 files changed, 30 deletions(-) diff --git a/defaultconfigs/1.18/config.yml b/defaultconfigs/1.18/config.yml index 5a85d1d..eb45a8a 100644 --- a/defaultconfigs/1.18/config.yml +++ b/defaultconfigs/1.18/config.yml @@ -406,10 +406,4 @@ debug_log: false # Whether to show verbose debug logging debug_log_verbose: false -# In case something when wrong with CustomAnvil packet manager. -# If you see "missing class exception" or similar you may test this. -# If enabled and Protocolib absent or disabled "Replace to expensive" will not work. -# ProtocoLib may also be used if the server is in an "unsupported" version even if this option is disabled. -force_protocolib: false - configVersion: 1.11.0 diff --git a/defaultconfigs/1.21.11/config.yml b/defaultconfigs/1.21.11/config.yml index 267df3d..c4500a8 100644 --- a/defaultconfigs/1.21.11/config.yml +++ b/defaultconfigs/1.21.11/config.yml @@ -426,11 +426,5 @@ debug_log: false # Whether to show verbose debug logging debug_log_verbose: false -# In case something when wrong with CustomAnvil packet manager. -# If you see "missing class exception" or similar you may test this. -# If enabled and Protocolib absent or disabled "Replace to expensive" will not work. -# ProtocoLib may also be used if the server is in an "unsupported" version even if this option is disabled. -force_protocolib: 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 87d03a9..91a796b 100644 --- a/defaultconfigs/1.21.9/config.yml +++ b/defaultconfigs/1.21.9/config.yml @@ -418,11 +418,5 @@ debug_log: false # Whether to show verbose debug logging debug_log_verbose: false -# In case something when wrong with CustomAnvil packet manager. -# If you see "missing class exception" or similar you may test this. -# If enabled and Protocolib absent or disabled "Replace to expensive" will not work. -# ProtocoLib may also be used if the server is in an "unsupported" version even if this option is disabled. -force_protocolib: false - configVersion: 1.11.0 lowMinecraftVersion: 1.21.9 diff --git a/defaultconfigs/1.21/config.yml b/defaultconfigs/1.21/config.yml index b6b620c..99606ba 100644 --- a/defaultconfigs/1.21/config.yml +++ b/defaultconfigs/1.21/config.yml @@ -406,10 +406,4 @@ debug_log: false # Whether to show verbose debug logging debug_log_verbose: false -# In case something when wrong with CustomAnvil packet manager. -# If you see "missing class exception" or similar you may test this. -# If enabled and Protocolib absent or disabled "Replace to expensive" will not work. -# ProtocoLib may also be used if the server is in an "unsupported" version even if this option is disabled. -force_protocolib: false - configVersion: 1.11.0 \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 95729b7..9006b0a 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -408,10 +408,4 @@ debug_log: false # Whether to show verbose debug logging debug_log_verbose: false -# In case something when wrong with CustomAnvil packet manager. -# If you see "missing class exception" or similar you may test this. -# If enabled and Protocolib absent or disabled "Replace to expensive" will not work. -# ProtocoLib may also be used if the server is in an "unsupported" version even if this option is disabled. -force_protocolib: false - configVersion: 1.11.0 From 26469982b210753f6ba3269c93d9e1c310c925d1 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 23 Mar 2026 18:26:20 +0100 Subject: [PATCH 112/207] indicate alias --- src/main/kotlin/xyz/alexcrea/cuanvil/command/CASubCommand.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/CASubCommand.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/CASubCommand.kt index b06d07f..4c71c68 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/CASubCommand.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/CASubCommand.kt @@ -16,7 +16,7 @@ abstract class CASubCommand: CommandExecutor { ): Boolean { if(!alreadySaid){ sender.sendMessage(ChatColor.RED.toString() + - "Please not that this command will be replaced as a subcommand of `/customanvil`") + "Please not that this command will be replaced as a subcommand of `/customanvil` or `/ca`") alreadySaid = true } From f0d2f07703d167259dc5183bd319e34c0b42a47a Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 23 Mar 2026 20:06:58 +0100 Subject: [PATCH 113/207] int item display better --- .../config/global/EnchantLimitConfigGui.java | 14 ++++- .../gui/config/settings/IntSettingsGui.java | 59 +++++++++++-------- 2 files changed, 45 insertions(+), 28 deletions(-) diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/EnchantLimitConfigGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/EnchantLimitConfigGui.java index a95a5bb..e9edbeb 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/EnchantLimitConfigGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/EnchantLimitConfigGui.java @@ -58,9 +58,17 @@ public class EnchantLimitConfigGui extends AbstractEnchantConfigGui "Default (" + defaultValue + ")"; + case RESET -> String.valueOf(defaultValue); + default -> "Default"; + }; + + } + else return super.valueDisplayName(type, value); } }; } diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/IntSettingsGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/IntSettingsGui.java index 5a713cf..f2ce0c1 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/IntSettingsGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/IntSettingsGui.java @@ -72,7 +72,8 @@ public class IntSettingsGui extends AbstractSettingGui { assert meta != null; meta.setDisplayName("§eReset to default value"); - meta.setLore(Collections.singletonList("§7Default value is §e" + holder.defaultVal)); + meta.setLore(Collections.singletonList("§7Default value is §e" + + holder.valueDisplayName(ValueDisplayType.RESET, holder.defaultVal))); item.setItemMeta(meta); returnToDefault = new GuiItem(item, event -> { event.setCancelled(true); @@ -86,42 +87,23 @@ public class IntSettingsGui extends AbstractSettingGui { * Update item using the setting value to match the new value. */ protected void updateValueDisplay() { - PatternPane pane = getPane(); - var displayNow = holder.valueDisplayName(now); // minus item GuiItem minusItem; if (now > holder.min) { - int planned = Math.max(holder.min, now - step); - ItemStack item = new ItemStack(Material.RED_TERRACOTTA); - ItemMeta meta = item.getItemMeta(); - assert meta != null; - - meta.setDisplayName("§e" + displayNow + " §f-> §e" + planned + " §r(§c-" + holder.valueDisplayName(now - planned) + "§r)"); - meta.setLore(Collections.singletonList(AbstractSettingGui.CLICK_LORE)); - item.setItemMeta(meta); - - minusItem = new GuiItem(item, updateNowConsumer(planned), CustomAnvil.instance); + int planned = Math.min(holder.max, now + step); + minusItem = valueEditItem(Material.RED_TERRACOTTA, ValueDisplayType.REMOVE, planned); } else { minusItem = GuiGlobalItems.backgroundItem(Material.BARRIER); } pane.bindItem('-', minusItem); //plus item - // may do a function to generalise ? GuiItem plusItem; if (now < holder.max) { int planned = Math.min(holder.max, now + step); - ItemStack item = new ItemStack(Material.GREEN_TERRACOTTA); - ItemMeta meta = item.getItemMeta(); - assert meta != null; - - meta.setDisplayName("§e" + displayNow + " §f-> §e" + planned + " §r(§a+" + holder.valueDisplayName(planned - now) + "§r)"); - meta.setLore(Collections.singletonList(AbstractSettingGui.CLICK_LORE)); - item.setItemMeta(meta); - - plusItem = new GuiItem(item, updateNowConsumer(planned), CustomAnvil.instance); + plusItem = valueEditItem(Material.GREEN_TERRACOTTA, ValueDisplayType.ADD, planned); } else { plusItem = GuiGlobalItems.backgroundItem(Material.BARRIER); } @@ -132,7 +114,7 @@ public class IntSettingsGui extends AbstractSettingGui { ItemMeta resultMeta = resultPaper.getItemMeta(); assert resultMeta != null; - resultMeta.setDisplayName("§fValue: §e" + displayNow); + resultMeta.setDisplayName("§fValue: §e" + holder.valueDisplayName(ValueDisplayType.CURRENT, now)); resultMeta.setLore(holder.displayLore); resultPaper.setItemMeta(resultMeta); @@ -150,7 +132,21 @@ public class IntSettingsGui extends AbstractSettingGui { } pane.bindItem('D', returnToDefault); + } + private GuiItem valueEditItem(Material mat, ValueDisplayType type, int planned) { + ItemStack item = new ItemStack(mat); + ItemMeta meta = item.getItemMeta(); + assert meta != null; + + var nowDisplay = holder.valueDisplayName(type, now); + var plannedDisplay = holder.valueDisplayName(type, planned); + var deltaDisplay = holder.deltaDisplay(type, now, planned); + meta.setDisplayName("§e" + nowDisplay + " §f-> §e" + plannedDisplay + " §r(§c" + deltaDisplay + "§r)"); + + meta.setLore(Collections.singletonList(AbstractSettingGui.CLICK_LORE)); + item.setItemMeta(meta); + return new GuiItem(item, updateNowConsumer(planned), CustomAnvil.instance); } /** @@ -390,10 +386,23 @@ public class IntSettingsGui extends AbstractSettingGui { return getItem(itemMat, CasedStringUtil.detectToUpperSpacedCase(configPath)); } - public String valueDisplayName(int value) { + protected String valueDisplayName(ValueDisplayType type, int value) { return String.valueOf(value); } + protected String deltaDisplay(ValueDisplayType type, int now, int planned) { + var delta = planned - now; + if(delta < 0) return "§c" + delta; + else return "§a+" + delta; + } + + } + + public enum ValueDisplayType { + ADD, + CURRENT, + REMOVE, + RESET, } } From f3c6526967f594b27398a3c194d49da175c04b06 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 23 Mar 2026 23:54:38 +0100 Subject: [PATCH 114/207] add excellent enchant limit --- build.gradle.kts | 2 +- .../build.gradle.kts | 8 ++--- .../plugins/ExcellentEnchant5_3Registry.java | 0 .../ExcellentEnchant5_4EnchantSettings.java | 12 ++++++++ settings.gradle.kts | 4 +-- .../enchant/wrapped/CAEEV5_4Enchantment.java | 29 +++++++++++++++++++ .../packet/PacketManagerSelector.kt | 2 -- .../plugins/ExcellentEnchantsDependency.kt | 15 ++++++---- 8 files changed, 56 insertions(+), 16 deletions(-) rename impl/{ExcellentEnchant5_3 => ExcellentEnchant5_4}/build.gradle.kts (57%) rename impl/{ExcellentEnchant5_3 => ExcellentEnchant5_4}/src/main/java/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchant5_3Registry.java (100%) create mode 100644 impl/ExcellentEnchant5_4/src/main/java/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchant5_4EnchantSettings.java create mode 100644 src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEV5_4Enchantment.java diff --git a/build.gradle.kts b/build.gradle.kts index 2a7e922..6006fdc 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -71,7 +71,7 @@ dependencies { compileOnly(project(":impl:LegacyEcoEnchant")) // ExcellentEnchants - implementation(project(":impl:ExcellentEnchant5_3")) + implementation(project(":impl:ExcellentEnchant5_4")) compileOnly("su.nightexpress.excellentenchants:Core:5.1.0") { exclude("org.spigotmc") } diff --git a/impl/ExcellentEnchant5_3/build.gradle.kts b/impl/ExcellentEnchant5_4/build.gradle.kts similarity index 57% rename from impl/ExcellentEnchant5_3/build.gradle.kts rename to impl/ExcellentEnchant5_4/build.gradle.kts index 7004edf..21335d5 100644 --- a/impl/ExcellentEnchant5_3/build.gradle.kts +++ b/impl/ExcellentEnchant5_4/build.gradle.kts @@ -11,11 +11,7 @@ repositories { } dependencies { - // Spigot api - compileOnly("org.spigotmc:spigot-api:1.18-R0.1-SNAPSHOT") - // Excellent Enchant - compileOnly("su.nightexpress.excellentenchants:Core:5.3.0") { - exclude("org.spigotmc") - } + compileOnly("su.nightexpress.excellentenchants:Core:5.4.1") + compileOnly("su.nightexpress.nightcore:main:2.14.1") } \ No newline at end of file diff --git a/impl/ExcellentEnchant5_3/src/main/java/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchant5_3Registry.java b/impl/ExcellentEnchant5_4/src/main/java/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchant5_3Registry.java similarity index 100% rename from impl/ExcellentEnchant5_3/src/main/java/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchant5_3Registry.java rename to impl/ExcellentEnchant5_4/src/main/java/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchant5_3Registry.java diff --git a/impl/ExcellentEnchant5_4/src/main/java/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchant5_4EnchantSettings.java b/impl/ExcellentEnchant5_4/src/main/java/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchant5_4EnchantSettings.java new file mode 100644 index 0000000..49dde3e --- /dev/null +++ b/impl/ExcellentEnchant5_4/src/main/java/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchant5_4EnchantSettings.java @@ -0,0 +1,12 @@ +package xyz.alexcrea.cuanvil.dependency.plugins; + +import su.nightexpress.excellentenchants.EnchantsAPI; + +public class ExcellentEnchant5_4EnchantSettings { + + + public static int anvilLimit() { + return EnchantsAPI.getEnchantManager().getSettings().getAnvilEnchantsLimit(); + } + +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 81418f0..9de7d8c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -18,5 +18,5 @@ for (nmsPart in reobfNMS) { // compatibility subprojects include(":impl:LegacyEcoEnchant") findProject(":impl:LegacyEcoEnchant")?.name = "LegacyEcoEnchant" -include("impl:ExcellentEnchant5_3") -findProject(":impl:ExcellentEnchant5_3")?.name = "ExcellentEnchant5_3" \ No newline at end of file +include("impl:ExcellentEnchant5_4") +findProject(":impl:ExcellentEnchant5_4")?.name = "ExcellentEnchant5_4" \ No newline at end of file diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEV5_4Enchantment.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEV5_4Enchantment.java new file mode 100644 index 0000000..335430f --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEV5_4Enchantment.java @@ -0,0 +1,29 @@ +package xyz.alexcrea.cuanvil.enchant.wrapped; + +import org.bukkit.Material; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.excellentenchants.api.enchantment.CustomEnchantment; +import xyz.alexcrea.cuanvil.dependency.plugins.ExcellentEnchant5_4EnchantSettings; +import xyz.alexcrea.cuanvil.enchant.CAEnchantment; + +import java.util.Map; + +public class CAEEV5_4Enchantment extends CAEEV5Enchantment { + + public CAEEV5_4Enchantment(@NotNull CustomEnchantment enchantment) { + super(enchantment); + } + + @Override + public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull Material itemMat) { + if(super.isEnchantConflict(enchantments, itemMat)) return true; + + var limit = ExcellentEnchant5_4EnchantSettings.anvilLimit(); + var count = enchantments.keySet().stream() + .filter(key -> key instanceof CAEEV5_4Enchantment) + .count(); + + return count > limit; + } + +} diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt index 775d197..4c3d680 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt @@ -1,10 +1,8 @@ package xyz.alexcrea.cuanvil.dependency.packet import org.bukkit.Bukkit -import su.nightexpress.nightcore.bridge.paper.PaperBridge import xyz.alexcrea.cuanvil.dependency.MinecraftVersionUtil import xyz.alexcrea.cuanvil.dependency.packet.versions.* -import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil import xyz.alexcrea.cuanvil.update.UpdateUtils object PacketManagerSelector { diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt index ebc4ad9..54ea2f4 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt @@ -11,6 +11,7 @@ import xyz.alexcrea.cuanvil.api.EnchantmentApi import xyz.alexcrea.cuanvil.api.event.listener.CATreatAnvilResultEvent import xyz.alexcrea.cuanvil.enchant.wrapped.CAEEPreV5Enchantment import xyz.alexcrea.cuanvil.enchant.wrapped.CAEEV5Enchantment +import xyz.alexcrea.cuanvil.enchant.wrapped.CAEEV5_4Enchantment import xyz.alexcrea.cuanvil.enchant.wrapped.CALegacyEEEnchantment import java.lang.reflect.Method import su.nightexpress.excellentenchants.api.EnchantRegistry as V5EnchantRegistry @@ -25,6 +26,7 @@ import su.nightexpress.excellentenchants.registry.EnchantRegistry as PreV5Enchan class ExcellentEnchantsDependency { enum class ListenerVersion(val classPath: String) { + V5_4("su.nightexpress.excellentenchants.enchantment.EnchantSettings"), V5_3("su.nightexpress.excellentenchants.enchantment.EnchantRegistry"), V5("su.nightexpress.excellentenchants.manager.listener.AnvilListener"), PRE_V5("su.nightexpress.excellentenchants.enchantment.listener.AnvilListener"), @@ -70,14 +72,14 @@ class ExcellentEnchantsDependency { // As excellent enchants is loaded before custom anvil and register enchantment to registry, we need to unregister old "vanilla" enchant. when (listenerVersion) { - ListenerVersion.V5_3 -> { + ListenerVersion.V5_4 -> { for (enchantment in ExcellentEnchant5_3Registry.getRegistered()) { EnchantmentApi.unregisterEnchantment(enchantment.bukkitEnchantment.key) - EnchantmentApi.registerEnchantment(CAEEV5Enchantment(enchantment)) + EnchantmentApi.registerEnchantment(CAEEV5_4Enchantment(enchantment)) } } - ListenerVersion.V5 -> { + ListenerVersion.V5, ListenerVersion.V5_3 -> { for (enchantment in V5EnchantRegistry.getRegistered()) { EnchantmentApi.unregisterEnchantment(enchantment.bukkitEnchantment.key) EnchantmentApi.registerEnchantment(CAEEV5Enchantment(enchantment)) @@ -130,7 +132,8 @@ class ExcellentEnchantsDependency { when (listenerVersion) { ListenerVersion.V5, - ListenerVersion.V5_3 + ListenerVersion.V5_3, + ListenerVersion.V5_4, -> { if (listener is V5AnvilListener) { this.v5AnvilListener = listener @@ -165,6 +168,7 @@ class ExcellentEnchantsDependency { when (listenerVersion) { ListenerVersion.V5_3, ListenerVersion.V5, + ListenerVersion.V5_4, -> this.usedAnvilListener = v5AnvilListener!! ListenerVersion.PRE_V5 -> this.usedAnvilListener = preV5AnvilListener!! ListenerVersion.LEGACY -> this.usedAnvilListener = legacyAnvilListener!! @@ -228,7 +232,8 @@ class ExcellentEnchantsDependency { if (event.inventory.getItem(2) != null) { when (listenerVersion) { ListenerVersion.V5, - ListenerVersion.V5_3 + ListenerVersion.V5_3, + ListenerVersion.V5_4, -> v5AnvilListener!!.onClickAnvil(event) ListenerVersion.PRE_V5 -> preV5AnvilListener!!.onClickAnvil(event) ListenerVersion.LEGACY -> legacyAnvilListener!!.onClickAnvil(event) From 60ebdbf107bbab01057babe623b33f4c917551ac Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 24 Mar 2026 01:40:22 +0100 Subject: [PATCH 115/207] fix bad copy paste --- .../alexcrea/cuanvil/gui/config/settings/IntSettingsGui.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/IntSettingsGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/IntSettingsGui.java index f2ce0c1..56dcaf3 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/IntSettingsGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/IntSettingsGui.java @@ -102,7 +102,7 @@ public class IntSettingsGui extends AbstractSettingGui { //plus item GuiItem plusItem; if (now < holder.max) { - int planned = Math.min(holder.max, now + step); + int planned = Math.max(holder.min, now - step); plusItem = valueEditItem(Material.GREEN_TERRACOTTA, ValueDisplayType.ADD, planned); } else { plusItem = GuiGlobalItems.backgroundItem(Material.BARRIER); From 3d50e0ec8291fc21bc5584d00a1d58aac49ad330 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 24 Mar 2026 01:40:46 +0100 Subject: [PATCH 116/207] bruh --- .../alexcrea/cuanvil/gui/config/settings/IntSettingsGui.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/IntSettingsGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/IntSettingsGui.java index 56dcaf3..73121a6 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/IntSettingsGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/IntSettingsGui.java @@ -92,7 +92,7 @@ public class IntSettingsGui extends AbstractSettingGui { // minus item GuiItem minusItem; if (now > holder.min) { - int planned = Math.min(holder.max, now + step); + int planned = Math.max(holder.min, now - step); minusItem = valueEditItem(Material.RED_TERRACOTTA, ValueDisplayType.REMOVE, planned); } else { minusItem = GuiGlobalItems.backgroundItem(Material.BARRIER); @@ -102,7 +102,7 @@ public class IntSettingsGui extends AbstractSettingGui { //plus item GuiItem plusItem; if (now < holder.max) { - int planned = Math.max(holder.min, now - step); + int planned = Math.min(holder.max, now + step); plusItem = valueEditItem(Material.GREEN_TERRACOTTA, ValueDisplayType.ADD, planned); } else { plusItem = GuiGlobalItems.backgroundItem(Material.BARRIER); From e5167971f403c79f6605ff9e00cf32778d6b3466 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 24 Mar 2026 02:47:06 +0100 Subject: [PATCH 117/207] [ci skip] faststat ver up --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 6006fdc..9453ce7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -52,7 +52,7 @@ dependencies { compileOnly("org.spigotmc:spigot-api:1.18-R0.1-SNAPSHOT") // fast stats - implementation("dev.faststats.metrics:bukkit:0.18.0") + implementation("dev.faststats.metrics:bukkit:0.19.0") // minimessage implementation("net.kyori:adventure-text-minimessage:4.25.0") From f520d5e3dbd80ba11dbdeb325bd4cbe71951b2dd Mon Sep 17 00:00:00 2001 From: alexcrea <42614139+alexcrea@users.noreply.github.com> Date: Wed, 25 Mar 2026 02:25:47 +0100 Subject: [PATCH 118/207] [ci skip] try better changelog --- .github/workflows/gradle.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 01518f4..9c76b3c 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -69,9 +69,7 @@ jobs: - name: Prepare release env variable if: ${{ github.event_name == 'release' && success() }} run: | - echo "RELEASE_CHANGELOG<> $GITHUB_ENV - echo "${{ github.event.release.body || '' }}" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV + printf 'RELEASE_CHANGELOG=%s\n' "${{ github.event.release.body || '' }}" >> $GITHUB_ENV echo "IS_GITHUB_PRERELEASE=${{ github.event.release.prerelease }}" >> $GITHUB_ENV - name: Prepare publish env variable @@ -168,4 +166,4 @@ jobs: # New ${{ github.event.release.prerelease && 'beta' || '' }} version of custom anvil ! <:CustomAnvil:1262550667986342001>([Modrinth](https://modrinth.com/plugin/customanvil), [Hangar](https://hangar.papermc.io/alexcrea/CustomAnvil) and [GitHub](${{ github.event.release.html_url }}) links) -# note: automated release. spigot is not uploaded yet. - ${{ env.RELEASE_CHANGELOG }} \ No newline at end of file + ${{ env.RELEASE_CHANGELOG }} From 7fbf68dff3403b9abf6c6e38d294809e03d723ec Mon Sep 17 00:00:00 2001 From: alexcrea Date: Wed, 25 Mar 2026 03:30:50 +0100 Subject: [PATCH 119/207] [ci skip] add deprecation warning --- .../java/xyz/alexcrea/cuanvil/update/UpdateHandler.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/UpdateHandler.java b/src/main/java/xyz/alexcrea/cuanvil/update/UpdateHandler.java index 82ee0f7..660accb 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/update/UpdateHandler.java +++ b/src/main/java/xyz/alexcrea/cuanvil/update/UpdateHandler.java @@ -77,6 +77,12 @@ public class UpdateHandler { if (hadUpdate) { CustomAnvil.instance.getLogger().info("Updating Done !"); } + + if(current.major() == 1 && current.minor() < 21) { + var logger = CustomAnvil.instance.getLogger(); + logger.warning("Your are running an old version of minecraft (lower than 1.21)"); + logger.warning("Custom Anvil will stop supporting this version on the first of july 2026"); + } } private static void finishConfiguration(@Nonnull String newVersion, @Nonnull Set toSave) { From d061bfc6f41622ca30b6e83c400ca1f836beea98 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Wed, 25 Mar 2026 03:52:18 +0100 Subject: [PATCH 120/207] Revert "[ci skip] try better changelog" This reverts commit f520d5e3dbd80ba11dbdeb325bd4cbe71951b2dd. --- .github/workflows/gradle.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 9c76b3c..01518f4 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -69,7 +69,9 @@ jobs: - name: Prepare release env variable if: ${{ github.event_name == 'release' && success() }} run: | - printf 'RELEASE_CHANGELOG=%s\n' "${{ github.event.release.body || '' }}" >> $GITHUB_ENV + echo "RELEASE_CHANGELOG<> $GITHUB_ENV + echo "${{ github.event.release.body || '' }}" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV echo "IS_GITHUB_PRERELEASE=${{ github.event.release.prerelease }}" >> $GITHUB_ENV - name: Prepare publish env variable @@ -166,4 +168,4 @@ jobs: # New ${{ github.event.release.prerelease && 'beta' || '' }} version of custom anvil ! <:CustomAnvil:1262550667986342001>([Modrinth](https://modrinth.com/plugin/customanvil), [Hangar](https://hangar.papermc.io/alexcrea/CustomAnvil) and [GitHub](${{ github.event.release.html_url }}) links) -# note: automated release. spigot is not uploaded yet. - ${{ env.RELEASE_CHANGELOG }} + ${{ env.RELEASE_CHANGELOG }} \ No newline at end of file From 45fe037a925dbe3ae21c3bf1af4bd1d91aa3376b Mon Sep 17 00:00:00 2001 From: alexcrea Date: Wed, 25 Mar 2026 11:07:22 +0100 Subject: [PATCH 121/207] [ci skip] version bump --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 9453ce7..78f0724 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,7 +22,7 @@ plugins { } group = "xyz.alexcrea" -version = "1.16.2" +version = "1.16.3" val isDevBuild = System.getenv("SMALL_COMMIT_HASH") != null val isPreRelease = System.getenv("IS_GITHUB_PRERELEASE") == "true" From 1a710863271c824e59713b54e166147effdc911b Mon Sep 17 00:00:00 2001 From: TrashyPixl Date: Thu, 26 Mar 2026 03:57:42 +0100 Subject: [PATCH 122/207] ci: use native github actions features for environment variables --- .github/workflows/gradle.yml | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 01518f4..bbc9f70 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -25,6 +25,10 @@ jobs: permissions: contents: write + env: + MODRINTH_VERSIONS: '["1.18.x", "1.19.x", "1.20.x", "1.21.x"]' + MODRINTH_PLATFORMS: '["spigot", "paper", "purpur", "folia"]' + steps: - uses: actions/checkout@v4 - name: Set up JDKs @@ -66,19 +70,6 @@ jobs: if: ${{ github.event_name != 'release' && success() }} run: echo "SMALL_COMMIT_HASH=$(git rev-parse --short ${{ github.sha }})" >> $GITHUB_ENV - - name: Prepare release env variable - if: ${{ github.event_name == 'release' && success() }} - run: | - echo "RELEASE_CHANGELOG<> $GITHUB_ENV - echo "${{ github.event.release.body || '' }}" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - echo "IS_GITHUB_PRERELEASE=${{ github.event.release.prerelease }}" >> $GITHUB_ENV - - - name: Prepare publish env variable - run: | - echo MODRINTH_VERSIONS='["1.18.x", "1.19.x", "1.20.x", "1.21.x"]' >> $GITHUB_ENV - echo MODRINTH_PLATFORMS='["spigot", "paper", "purpur", "folia"]' >> $GITHUB_ENV - - name: Build with Gradle Wrapper run: ./gradlew build --parallel --stacktrace @@ -128,6 +119,7 @@ jobs: if: ${{ (github.event_name != 'release' || github.event_name != 'push') && github.repository_owner == 'alexcrea' && success() }} env: HANGAR_API_TOKEN: ${{ secrets.HANGAR_API_TOKEN }} + RELEASE_CHANGELOG: ${{ github.event.release.body }} run: ./gradlew publishAllPublicationsToHangar --stacktrace - name: Modrinth publish alpha @@ -156,7 +148,7 @@ jobs: game-versions: ${{ env.MODRINTH_VERSIONS }} channel: ${{ github.event.release.prerelease == false && 'release' || 'beta' }} files: build/libs/${{ env.ONLINE_JAR_NAME }} - changelog: ${{ env.RELEASE_CHANGELOG }} + changelog: ${{ github.event.release.body }} - name: Send release note to discord if: ${{ github.event_name == 'release' && github.repository_owner == 'alexcrea' && success() }} @@ -168,4 +160,4 @@ jobs: # New ${{ github.event.release.prerelease && 'beta' || '' }} version of custom anvil ! <:CustomAnvil:1262550667986342001>([Modrinth](https://modrinth.com/plugin/customanvil), [Hangar](https://hangar.papermc.io/alexcrea/CustomAnvil) and [GitHub](${{ github.event.release.html_url }}) links) -# note: automated release. spigot is not uploaded yet. - ${{ env.RELEASE_CHANGELOG }} \ No newline at end of file + ${{ github.event.release.body }} From 7612eac7653efc0b69620686bae9e977dbe59eb5 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 3 Feb 2026 17:22:32 +0100 Subject: [PATCH 123/207] progress on using namespaced key instead of material --- build.gradle.kts | 8 ++- .../cuanvil/api/MaterialGroupApi.java | 9 +-- .../enchant/AdditionalTestEnchantment.java | 10 +-- .../enchant/wrapped/CAEEPreV5Enchantment.java | 7 ++- .../enchant/wrapped/CAEEV5Enchantment.java | 9 +-- .../cuanvil/enchant/wrapped/CAEcoEnchant.java | 7 ++- .../wrapped/CAIncompatibleAllEnchant.java | 5 +- .../wrapped/CALegacyEEEnchantment.java | 7 ++- .../enchant/wrapped/CALegacyEcoEnchant.java | 11 ++-- .../gui/config/SelectMaterialContainer.java | 13 ++-- .../gui/config/ask/SelectItemTypeGui.java | 3 +- .../elements/GroupConfigSubSettingGui.java | 13 ++-- .../settings/MaterialSelectSettingGui.java | 39 ++++++------ .../cuanvil/update/plugin/PUpdate_1_11_0.java | 13 +++- .../io/delilaheve/util/ConfigOptions.kt | 6 +- .../io/delilaheve/util/EnchantmentUtil.kt | 3 +- .../kotlin/io/delilaheve/util/ItemUtil.kt | 3 +- .../dependency/datapack/DataPackDependency.kt | 2 +- .../plugins/EcoItemDependencyUtil.kt | 34 +++++++++++ .../plugins/EnchantmentSquaredDependency.kt | 6 +- .../cuanvil/group/AbstractMaterialGroup.kt | 22 ++++--- .../cuanvil/group/EnchantConflictGroup.kt | 3 +- .../cuanvil/group/EnchantConflictManager.kt | 11 ++-- .../alexcrea/cuanvil/group/ExcludeGroup.kt | 15 ++--- .../alexcrea/cuanvil/group/IncludeGroup.kt | 15 ++--- .../cuanvil/group/ItemGroupManager.kt | 2 +- .../xyz/alexcrea/cuanvil/group/NegativeSet.kt | 51 ++++++++++++++++ .../cuanvil/listener/PrepareAnvilListener.kt | 17 +++--- .../cuanvil/recipe/AnvilCustomRecipe.kt | 7 +-- .../xyz/alexcrea/cuanvil/util/MaterialUtil.kt | 61 +++++++++++++++++++ .../alexcrea/cuanvil/util/UnitRepairUtil.kt | 5 +- .../cuanvil/api/MaterialGroupApiTests.java | 4 +- 32 files changed, 297 insertions(+), 124 deletions(-) create mode 100644 src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/EcoItemDependencyUtil.kt create mode 100644 src/main/kotlin/xyz/alexcrea/cuanvil/group/NegativeSet.kt create mode 100644 src/main/kotlin/xyz/alexcrea/cuanvil/util/MaterialUtil.kt diff --git a/build.gradle.kts b/build.gradle.kts index 78f0724..49f0576 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -65,11 +65,15 @@ dependencies { // EnchantsSquaredRewritten compileOnly(files("libs/EnchantsSquared.jar")) - // EcoEnchants - compileOnly("com.willfp:EcoEnchants:12.11.1") + // EcoEnchants & item + compileOnly("com.willfp:libreforge:4.79.0:all") compileOnly("com.willfp:eco:6.74.5") + + compileOnly("com.willfp:EcoEnchants:12.11.1") compileOnly(project(":impl:LegacyEcoEnchant")) + compileOnly("com.willfp:EcoItems:5.66.0") + // ExcellentEnchants implementation(project(":impl:ExcellentEnchant5_4")) compileOnly("su.nightexpress.excellentenchants:Core:5.1.0") { diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/MaterialGroupApi.java b/src/main/java/xyz/alexcrea/cuanvil/api/MaterialGroupApi.java index 48dd500..cd71c7a 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/api/MaterialGroupApi.java +++ b/src/main/java/xyz/alexcrea/cuanvil/api/MaterialGroupApi.java @@ -3,6 +3,7 @@ package xyz.alexcrea.cuanvil.api; import io.delilaheve.CustomAnvil; import io.delilaheve.util.ConfigOptions; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.configuration.file.FileConfiguration; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -123,7 +124,7 @@ public class MaterialGroupApi { FileConfiguration config = ConfigHolder.ITEM_GROUP_HOLDER.getConfig(); String basePath = group.getName() + "."; - Set materialSet = group.getNonGroupInheritedMaterials(); + Set materialSet = group.getNonGroupInheritedMaterials(); Set groupSet = group.getGroups(); boolean empty = true; @@ -153,7 +154,7 @@ public class MaterialGroupApi { FileConfiguration config = ConfigHolder.ITEM_GROUP_HOLDER.getConfig(); String basePath = group.getName() + "."; - EnumSet materials = group.getMaterials(); + Set materials = group.getMaterials(); if (materials.isEmpty()) return false; @@ -163,8 +164,8 @@ public class MaterialGroupApi { return true; } - public static List materialSetToStringList(@NotNull Set materials) { - return materials.stream().map(material -> material.getKey().getKey().toLowerCase()).toList(); + public static List materialSetToStringList(@NotNull Set materials) { + return materials.stream().map(NamespacedKey::toString).toList(); } public static List materialGroupSetToStringList(@NotNull Set groups) { diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/AdditionalTestEnchantment.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/AdditionalTestEnchantment.java index 832e5af..821838f 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/enchant/AdditionalTestEnchantment.java +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/AdditionalTestEnchantment.java @@ -1,6 +1,7 @@ package xyz.alexcrea.cuanvil.enchant; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; @@ -11,24 +12,23 @@ public interface AdditionalTestEnchantment { /** * Test if the provided enchantments can be compatible with this enchantment. only non-Custom Anvil conflict. * @param enchantments Immutable map of validated enchantments for the item. - * @param itemMat Material of the tested item. + * @param itemType Material namespaced key of the tested item. * @return If there is a conflict with the enchantments. */ boolean isEnchantConflict( @NotNull Map enchantments, - @NotNull Material itemMat); - + @NotNull NamespacedKey itemType); /** * Test if the provided item can be compatible with this enchantment. only non-Custom Anvil conflict. * @param enchantments Immutable map of validated enchantments for the item. - * @param itemMat Material of the tested item. + * @param itemType Material namespaced key of the tested item. * @param item Provide a new instance of the used item stack with the partial enchantment applied. * @return If there is a conflict with the enchantment and the item. */ boolean isItemConflict( @NotNull Map enchantments, - @NotNull Material itemMat, + @NotNull NamespacedKey itemType, @NotNull ItemStack item); } diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEPreV5Enchantment.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEPreV5Enchantment.java index d3082c9..783798d 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEPreV5Enchantment.java +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEPreV5Enchantment.java @@ -1,6 +1,7 @@ package xyz.alexcrea.cuanvil.enchant.wrapped; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; import su.nightexpress.excellentenchants.api.enchantment.CustomEnchantment; @@ -39,7 +40,7 @@ public class CAEEPreV5Enchantment extends CABukkitEnchantment implements Additio } @Override - public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull Material itemMat) { + public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType) { if (!definition.hasConflicts()) return false; Set conflicts = definition.getConflicts(); @@ -52,8 +53,8 @@ public class CAEEPreV5Enchantment extends CABukkitEnchantment implements Additio } @Override - public boolean isItemConflict(@NotNull Map enchantments, @NotNull Material itemMat, @NotNull ItemStack item) { - if (Material.ENCHANTED_BOOK.equals(itemMat)) return false; + public boolean isItemConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType, @NotNull ItemStack item) { + if (Material.ENCHANTED_BOOK.getKey().equals(itemType)) return false; return !definition.getSupportedItems().is(item); } diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEV5Enchantment.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEV5Enchantment.java index 813eda8..2d8f945 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEV5Enchantment.java +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEV5Enchantment.java @@ -1,6 +1,7 @@ package xyz.alexcrea.cuanvil.enchant.wrapped; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; import su.nightexpress.excellentenchants.api.enchantment.CustomEnchantment; @@ -27,7 +28,7 @@ public class CAEEV5Enchantment extends CABukkitEnchantment implements Additional } @Override - public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull Material itemMat) { + public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType) { if (!hasConflicts()) return false; Set conflicts = getExclusiveSet(); @@ -41,10 +42,10 @@ public class CAEEV5Enchantment extends CABukkitEnchantment implements Additional } @Override - public boolean isItemConflict(@NotNull Map enchantments, @NotNull Material itemMat, @NotNull ItemStack item) { - if (Material.ENCHANTED_BOOK.equals(itemMat)) return false; + public boolean isItemConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType, @NotNull ItemStack item) { + if (Material.ENCHANTED_BOOK.getKey().equals(itemType)) return false; - String key = itemMat.getKey().getKey(); + String key = itemType.getKey(); ItemSet primary = eeenchantment.getPrimaryItems(); if (primary.getMaterials().contains(key)) return false; diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEcoEnchant.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEcoEnchant.java index b0e8fba..32d1346 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEcoEnchant.java +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEcoEnchant.java @@ -4,6 +4,7 @@ import com.willfp.ecoenchants.enchant.EcoEnchant; import com.willfp.ecoenchants.target.EnchantmentTarget; import com.willfp.ecoenchants.type.EnchantmentType; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; import xyz.alexcrea.cuanvil.enchant.AdditionalTestEnchantment; @@ -23,7 +24,7 @@ public class CAEcoEnchant extends CABukkitEnchantment implements AdditionalTestE } @Override - public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull Material itemMat) { + public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType) { if (enchantments.isEmpty()) return false; // Check if there is only self @@ -61,9 +62,9 @@ public class CAEcoEnchant extends CABukkitEnchantment implements AdditionalTestE @Override public boolean isItemConflict(@NotNull Map enchantments, - @NotNull Material itemMat, + @NotNull NamespacedKey itemType, @NotNull ItemStack item) { - if (Material.ENCHANTED_BOOK.equals(itemMat)) { + if (Material.ENCHANTED_BOOK.getKey().equals(itemType)) { return false; } diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAIncompatibleAllEnchant.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAIncompatibleAllEnchant.java index 218ce87..552ecd4 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAIncompatibleAllEnchant.java +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAIncompatibleAllEnchant.java @@ -1,6 +1,7 @@ package xyz.alexcrea.cuanvil.enchant.wrapped; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.enchantments.Enchantment; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; @@ -24,12 +25,12 @@ public class CAIncompatibleAllEnchant extends CABukkitEnchantment implements Add @Override - public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull Material itemMat) { + public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType) { return !enchantments.isEmpty() && !(enchantments.size() == 1 && enchantments.containsKey(this)); } @Override - public boolean isItemConflict(@NotNull Map enchantments, @NotNull Material itemMat, @NotNull ItemStack item) { + public boolean isItemConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType, @NotNull ItemStack item) { return false; } } diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CALegacyEEEnchantment.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CALegacyEEEnchantment.java index 191f8f3..74068d4 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CALegacyEEEnchantment.java +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CALegacyEEEnchantment.java @@ -1,6 +1,7 @@ package xyz.alexcrea.cuanvil.enchant.wrapped; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; import su.nightexpress.excellentenchants.api.enchantment.EnchantmentData; @@ -22,7 +23,7 @@ public class CALegacyEEEnchantment extends CABukkitEnchantment implements Additi } @Override - public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull Material itemMat) { + public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType) { if (!eeenchantment.hasConflicts()) return false; Set conflicts = eeenchantment.getConflicts(); @@ -35,8 +36,8 @@ public class CALegacyEEEnchantment extends CABukkitEnchantment implements Additi } @Override - public boolean isItemConflict(@NotNull Map enchantments, @NotNull Material itemMat, @NotNull ItemStack item) { - if (Material.ENCHANTED_BOOK.equals(itemMat)) return false; + public boolean isItemConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType, @NotNull ItemStack item) { + if (Material.ENCHANTED_BOOK.getKey().equals(itemType)) return false; return !eeenchantment.getSupportedItems().is(item); } diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CALegacyEcoEnchant.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CALegacyEcoEnchant.java index 3b4242d..cb24def 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CALegacyEcoEnchant.java +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CALegacyEcoEnchant.java @@ -4,12 +4,14 @@ import com.willfp.ecoenchants.enchantments.EcoEnchant; import com.willfp.ecoenchants.enchantments.meta.EnchantmentTarget; import com.willfp.ecoenchants.enchantments.meta.EnchantmentType; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.enchantments.Enchantment; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; import xyz.alexcrea.cuanvil.enchant.AdditionalTestEnchantment; import xyz.alexcrea.cuanvil.enchant.CAEnchantment; import xyz.alexcrea.cuanvil.enchant.EnchantmentRarity; +import xyz.alexcrea.cuanvil.util.MaterialUtil; import java.util.Map; @@ -23,7 +25,7 @@ public class CALegacyEcoEnchant extends CABukkitEnchantment implements Additiona } @Override - public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull Material itemMat) { + public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType) { if (enchantments.isEmpty()) return false; EnchantmentType type = this.ecoEnchant.getType(); @@ -48,14 +50,15 @@ public class CALegacyEcoEnchant extends CABukkitEnchantment implements Additiona @Override public boolean isItemConflict(@NotNull Map enchantments, - @NotNull Material itemMat, + @NotNull NamespacedKey itemType, @NotNull ItemStack item) { - if (Material.ENCHANTED_BOOK.equals(itemMat)) { + if (Material.ENCHANTED_BOOK.getKey().equals(itemType)) { return false; } + var mat = MaterialUtil.INSTANCE.getMatFromKey(itemType); for (EnchantmentTarget target : this.ecoEnchant.getTargets()) { - if (target.getMaterials().contains(itemMat)) { + if (target.getMaterials().contains(mat)) { return false; } } diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/SelectMaterialContainer.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/SelectMaterialContainer.java index 2f76694..3756341 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/SelectMaterialContainer.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/SelectMaterialContainer.java @@ -1,34 +1,35 @@ package xyz.alexcrea.cuanvil.gui.config; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import xyz.alexcrea.cuanvil.util.CasedStringUtil; import java.util.*; public interface SelectMaterialContainer { - EnumSet getSelectedMaterials(); + Set getSelectedMaterials(); - boolean setSelectedMaterials(EnumSet materials); + boolean setSelectedMaterials(Set materials); - EnumSet illegalMaterials(); + Set illegalMaterials(); static List getMaterialLore(SelectMaterialContainer container, String containerType, String action){ // Prepare material lore ArrayList groupLore = new ArrayList<>(); groupLore.add("§7Allow you to select a list of §ematerials §7that this " + containerType + " should " + action); - Set materialSet = container.getSelectedMaterials(); + Set materialSet = container.getSelectedMaterials(); if (materialSet.isEmpty()) { groupLore.add("§7There is no "+action+"d material for this "+containerType+"."); } else { groupLore.add("§7List of "+action+"d materials for this "+containerType+":"); - Iterator materialIterator = materialSet.iterator(); + Iterator materialIterator = materialSet.iterator(); boolean greaterThanMax = materialSet.size() > 5; int maxindex = (greaterThanMax ? 4 : materialSet.size()); for (int i = 0; i < maxindex; i++) { // format string like "- Stone Sword" - String formattedName = CasedStringUtil.snakeToUpperSpacedCase(materialIterator.next().name().toLowerCase()); + String formattedName = CasedStringUtil.snakeToUpperSpacedCase(materialIterator.next().getKey().toLowerCase()); groupLore.add("§7- §e" + formattedName); } diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/ask/SelectItemTypeGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/ask/SelectItemTypeGui.java index b2d6afe..66411bd 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/ask/SelectItemTypeGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/ask/SelectItemTypeGui.java @@ -12,6 +12,7 @@ import org.jetbrains.annotations.NotNull; import xyz.alexcrea.cuanvil.gui.util.GuiGlobalActions; import xyz.alexcrea.cuanvil.gui.util.GuiGlobalItems; import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant; +import xyz.alexcrea.cuanvil.util.MaterialUtil; import java.util.Arrays; import java.util.concurrent.atomic.AtomicReference; @@ -52,7 +53,7 @@ public class SelectItemTypeGui extends AbstractAskGui { event.setCancelled(true); ItemStack cursor = event.getWhoClicked().getItemOnCursor(); - if(cursor.getType().isAir()) return; + if(MaterialUtil.INSTANCE.isAir(cursor)) return; ItemStack finalItem; if(materialOnly){ diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/GroupConfigSubSettingGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/GroupConfigSubSettingGui.java index c49fec7..3d05674 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/GroupConfigSubSettingGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/GroupConfigSubSettingGui.java @@ -5,6 +5,7 @@ import com.github.stefvanschie.inventoryframework.pane.PatternPane; import com.github.stefvanschie.inventoryframework.pane.util.Pattern; import io.delilaheve.CustomAnvil; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.entity.HumanEntity; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.inventory.ItemFlag; @@ -325,19 +326,19 @@ public class GroupConfigSubSettingGui extends MappedToListSubSettingGui implemen // ---------------------------- @Override - public EnumSet getSelectedMaterials() { + public Set getSelectedMaterials() { return this.group.getNonGroupInheritedMaterials(); } @Override - public boolean setSelectedMaterials(EnumSet materials) { + public boolean setSelectedMaterials(Set materials) { this.group.setNonGroupInheritedMaterials(materials); // Write to file configuration String[] groupNames = new String[materials.size()]; int index = 0; - for (Material otherGroup : materials) { - groupNames[index++] = otherGroup.name().toLowerCase(); + for (NamespacedKey otherGroup : materials) { + groupNames[index++] = otherGroup.getKey().toLowerCase(); } ConfigHolder.ITEM_GROUP_HOLDER.getConfig().set(this.group.getName()+"."+ItemGroupManager.MATERIAL_LIST_PATH, groupNames); @@ -353,8 +354,8 @@ public class GroupConfigSubSettingGui extends MappedToListSubSettingGui implemen } @Override - public EnumSet illegalMaterials() { - return EnumSet.of(Material.AIR); + public Set illegalMaterials() { + return Set.of(Material.AIR.getKey()); } // ---------------------------- diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/MaterialSelectSettingGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/MaterialSelectSettingGui.java index a3963ce..fc519ff 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/MaterialSelectSettingGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/MaterialSelectSettingGui.java @@ -5,6 +5,7 @@ import com.github.stefvanschie.inventoryframework.gui.type.util.Gui; import com.github.stefvanschie.inventoryframework.pane.util.Pattern; import io.delilaheve.CustomAnvil; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.entity.HumanEntity; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.inventory.ItemFlag; @@ -18,18 +19,19 @@ import xyz.alexcrea.cuanvil.gui.util.GuiGlobalActions; import xyz.alexcrea.cuanvil.gui.util.GuiGlobalItems; import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant; import xyz.alexcrea.cuanvil.util.CasedStringUtil; +import xyz.alexcrea.cuanvil.util.MaterialUtil; import java.util.*; import java.util.function.Consumer; -public class MaterialSelectSettingGui extends MappedElementListConfigGui { +public class MaterialSelectSettingGui extends MappedElementListConfigGui { private final SelectMaterialContainer selector; private final Gui backGui; private boolean instantRemove; - private final List defaultMaterials; - private final EnumSet illegalMaterials; + private final List defaultMaterials; + private final Set illegalMaterials; private final int defaultMaterialHash; private int nowMaterialHash; @@ -161,8 +163,7 @@ public class MaterialSelectSettingGui extends MappedElementListConfigGui result = EnumSet.noneOf(Material.class); - result.addAll(this.elementGuiMap.keySet()); + Set result = new HashSet<>(this.elementGuiMap.keySet()); if(!this.selector.setSelectedMaterials(result)){ player.sendMessage("§cSomething went wrong while saving the change of value."); @@ -185,8 +186,8 @@ public class MaterialSelectSettingGui extends MappedElementListConfigGui getEveryDisplayableInstanceOfGeneric() { + protected Collection getEveryDisplayableInstanceOfGeneric() { return this.defaultMaterials; } @Override - protected void updateElement(Material material, GuiItem element) { + protected void updateElement(NamespacedKey material, GuiItem element) { // Nothing happen here I think } @Override - protected GuiItem newElementRequested(Material material, GuiItem newItem) { + protected GuiItem newElementRequested(NamespacedKey material, GuiItem newItem) { newItem.setAction(event -> { if(this.instantRemove){ removeMaterial(material); }else { - String materialName = CasedStringUtil.snakeToUpperSpacedCase(material.name().toLowerCase()); + String materialName = CasedStringUtil.snakeToUpperSpacedCase(material.getKey().toLowerCase()); // Create and show confirm remove gui. ConfirmActionGui confirmGui = new ConfirmActionGui( @@ -250,7 +251,7 @@ public class MaterialSelectSettingGui extends MappedElementListConfigGui materialList){ + private static int hashFromMaterialList(List materialList){ int defaultMaterialHash = 0; - for (Material material : materialList) { + for (NamespacedKey material : materialList) { defaultMaterialHash ^= material.hashCode(); } return defaultMaterialHash; diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_11_0.java b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_11_0.java index 6d6baca..9740971 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_11_0.java +++ b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_11_0.java @@ -1,6 +1,7 @@ package xyz.alexcrea.cuanvil.update.plugin; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.FileConfiguration; import org.jetbrains.annotations.NotNull; @@ -11,6 +12,7 @@ import xyz.alexcrea.cuanvil.group.AbstractMaterialGroup; import xyz.alexcrea.cuanvil.group.IncludeGroup; import javax.annotation.Nonnull; +import java.util.Arrays; import java.util.List; import java.util.Set; @@ -69,7 +71,12 @@ public class PUpdate_1_11_0 { // Create new group IncludeGroup group = new IncludeGroup(toolset); - group.addAll(toolMats); + NamespacedKey[] keys = new NamespacedKey[toolMats.length]; + for (int i = 0; i < toolMats.length; i++) { + keys[i] = toolMats[i].getKey(); + } + + group.addAll(keys); MaterialGroupApi.addMaterialGroup(group, true); @@ -77,8 +84,8 @@ public class PUpdate_1_11_0 { if (tools == null) return; if (!(tools instanceof IncludeGroup include)) return; - List mats = List.of(toolMats); - Set matSet = include.getNonGroupInheritedMaterials(); + List mats = List.of(keys); + Set matSet = include.getNonGroupInheritedMaterials(); if (!matSet.containsAll(mats)) return; mats.forEach(matSet::remove); diff --git a/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt b/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt index c69f2fb..b761784 100644 --- a/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt +++ b/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt @@ -348,7 +348,7 @@ object ConfigOptions { * * @return the current enchantment limit. -1 if none */ - fun getEnchantCountLimit(type: Material): Int? { + fun getEnchantCountLimit(type: NamespacedKey): Int? { val limit = materialEnchantCountLimit(type) if(limit != null) return limit @@ -362,8 +362,8 @@ object ConfigOptions { * * @return The current enchantment limit. -1 if none */ - private fun materialEnchantCountLimit(type: Material): Int? { - val path = "$ENCHANT_COUNT_LIMIT_ITEMS.${type.key.key.lowercase()}" + private fun materialEnchantCountLimit(type: NamespacedKey): Int? { + val path = "$ENCHANT_COUNT_LIMIT_ITEMS.${type.key.lowercase()}" if(!ConfigHolder.DEFAULT_CONFIG.config.isInt(path)) return null diff --git a/src/main/kotlin/io/delilaheve/util/EnchantmentUtil.kt b/src/main/kotlin/io/delilaheve/util/EnchantmentUtil.kt index b0fcc97..af959f2 100644 --- a/src/main/kotlin/io/delilaheve/util/EnchantmentUtil.kt +++ b/src/main/kotlin/io/delilaheve/util/EnchantmentUtil.kt @@ -6,6 +6,7 @@ import org.bukkit.inventory.ItemStack import xyz.alexcrea.cuanvil.config.ConfigHolder import xyz.alexcrea.cuanvil.enchant.CAEnchantment import xyz.alexcrea.cuanvil.group.ConflictType +import xyz.alexcrea.cuanvil.util.MaterialUtil.customType import kotlin.math.max import kotlin.math.min @@ -34,7 +35,7 @@ object EnchantmentUtil { val bypassFuse = player.hasPermission(CustomAnvil.bypassFusePermission) val bypassLevel = player.hasPermission(CustomAnvil.bypassLevelPermission) - var maxEnchantCount = ConfigOptions.getEnchantCountLimit(item.type) + var maxEnchantCount = ConfigOptions.getEnchantCountLimit(item.customType) if(maxEnchantCount == null || maxEnchantCount < 0) maxEnchantCount = Int.MAX_VALUE val allowed = other.filter { (enchantment, _) -> enchantment.isAllowed(player) } diff --git a/src/main/kotlin/io/delilaheve/util/ItemUtil.kt b/src/main/kotlin/io/delilaheve/util/ItemUtil.kt index a85af39..414d37e 100644 --- a/src/main/kotlin/io/delilaheve/util/ItemUtil.kt +++ b/src/main/kotlin/io/delilaheve/util/ItemUtil.kt @@ -4,6 +4,7 @@ import org.bukkit.Material.ENCHANTED_BOOK import org.bukkit.inventory.ItemStack import org.bukkit.inventory.meta.Damageable import xyz.alexcrea.cuanvil.enchant.CAEnchantment +import xyz.alexcrea.cuanvil.util.MaterialUtil.customType import kotlin.math.ceil import kotlin.math.max import kotlin.math.min @@ -90,5 +91,5 @@ object ItemUtil { */ fun ItemStack.canMergeWith( other: ItemStack? - ) = (other != null) && (type == other.type || (other.isEnchantedBook())) + ) = (other != null) && (customType == other.customType || (other.isEnchantedBook())) } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/datapack/DataPackDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/datapack/DataPackDependency.kt index f397200..b0e9e52 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/datapack/DataPackDependency.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/datapack/DataPackDependency.kt @@ -145,7 +145,7 @@ object DataPackDependency { CustomAnvil.instance.logger.warning("Could not find material $name for item group $groupName") continue } - group.addToPolicy(mat) + group.addToPolicy(mat.key) } for (name in section.getStringList("groups")) { val otherGroup = MaterialGroupApi.getGroup(name) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/EcoItemDependencyUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/EcoItemDependencyUtil.kt new file mode 100644 index 0000000..14c7d9f --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/EcoItemDependencyUtil.kt @@ -0,0 +1,34 @@ +package xyz.alexcrea.cuanvil.dependency.plugins + +import com.willfp.ecoitems.items.EcoItem +import com.willfp.ecoitems.items.EcoItems +import com.willfp.ecoitems.items.ecoItem +import org.bukkit.Material +import org.bukkit.NamespacedKey +import org.bukkit.inventory.ItemStack + +object EcoItemDependencyUtil { + + fun ecoItemNamespace(item: ItemStack): NamespacedKey? { + val ecoi = item.ecoItem ?: return null + + return ecoi.id + } + + fun ecoItemFromKey(key: NamespacedKey): EcoItem? { + return EcoItems.getByID(key.toString()) + } + + fun ecoItemMaterialFromKey(key: NamespacedKey): Material? { + val ecoi = ecoItemFromKey(key) ?: return null + + return ecoi.itemStack.type + } + + fun newEcoItemstack(key: NamespacedKey): ItemStack? { + val ecoi = ecoItemFromKey(key) ?: return null + + return ecoi.itemStack + } + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/EnchantmentSquaredDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/EnchantmentSquaredDependency.kt index f4da612..d769986 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/EnchantmentSquaredDependency.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/EnchantmentSquaredDependency.kt @@ -102,15 +102,15 @@ class EnchantmentSquaredDependency(private val enchantmentSquaredPlugin: Plugin) private fun writeMissingGroups(){ // Write group that do not exist on custom anvil. val shield = IncludeGroup("shield") - shield.addToPolicy(Material.SHIELD) + shield.addToPolicy(Material.SHIELD.key) MaterialGroupApi.addMaterialGroup(shield) val elytra = IncludeGroup("elytra") - elytra.addToPolicy(Material.ELYTRA) + elytra.addToPolicy(Material.ELYTRA.key) MaterialGroupApi.addMaterialGroup(elytra) val trinkets = IncludeGroup("trinkets") - trinkets.addToPolicy(Material.ROTTEN_FLESH) + trinkets.addToPolicy(Material.ROTTEN_FLESH.key) MaterialGroupApi.addMaterialGroup(trinkets) } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/group/AbstractMaterialGroup.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/group/AbstractMaterialGroup.kt index ec6e7bc..d0d2bda 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/group/AbstractMaterialGroup.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/group/AbstractMaterialGroup.kt @@ -1,7 +1,8 @@ package xyz.alexcrea.cuanvil.group import org.bukkit.Material -import java.util.* +import org.bukkit.NamespacedKey +import xyz.alexcrea.cuanvil.util.MaterialUtil abstract class AbstractMaterialGroup(private val name: String) { protected val includedMaterial by lazy { createDefaultSet() } @@ -9,12 +10,12 @@ abstract class AbstractMaterialGroup(private val name: String) { /** * Get the group default set */ - protected abstract fun createDefaultSet(): EnumSet + protected abstract fun createDefaultSet(): MutableSet /** * Get if a material is allowed following the group policy */ - open fun contain(mat: Material): Boolean { + open fun contain(mat: NamespacedKey): Boolean { return mat in getMaterials() } @@ -27,13 +28,13 @@ abstract class AbstractMaterialGroup(private val name: String) { * Push a material to this group to follow this group policy * @return this instance. */ - abstract fun addToPolicy(mat: Material): AbstractMaterialGroup + abstract fun addToPolicy(type: NamespacedKey): AbstractMaterialGroup /** * Push a list of material to this group to follow this group policy * @return this instance. */ - fun addAll(vararg materials: Material): AbstractMaterialGroup { + fun addAll(vararg materials: NamespacedKey): AbstractMaterialGroup { for (material in materials) { addToPolicy(material) } @@ -60,19 +61,19 @@ abstract class AbstractMaterialGroup(private val name: String) { /** * Get the group contained material as a set */ - abstract fun getMaterials(): EnumSet + abstract fun getMaterials(): Set /** * Get the group non-inherited material as a set */ - open fun getNonGroupInheritedMaterials(): EnumSet { + open fun getNonGroupInheritedMaterials(): Set { return includedMaterial } /** * Get the group non-inherited material as a set */ - open fun setNonGroupInheritedMaterials(materials: EnumSet) { + open fun setNonGroupInheritedMaterials(materials: Set) { this.includedMaterial.clear() this.includedMaterial.addAll(materials) } @@ -102,8 +103,9 @@ abstract class AbstractMaterialGroup(private val name: String) { // Test inner material val matIterator = includedMaterial.iterator() while (matIterator.hasNext()) { - val material = matIterator.next() - if (material.isAir) continue + val key = matIterator.next() + val material = MaterialUtil.getMatFromKey(key) + if (material == null || material.isAir) continue return material } // Test included group representative material diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt index 403d630..59841ac 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt @@ -2,6 +2,7 @@ package xyz.alexcrea.cuanvil.group import io.delilaheve.CustomAnvil import org.bukkit.Material +import org.bukkit.NamespacedKey import xyz.alexcrea.cuanvil.enchant.CAEnchantment class EnchantConflictGroup( @@ -53,7 +54,7 @@ class EnchantConflictGroup( return canBypassByBeforeLevel(enchants) || canBypassByAfterLevel(enchants) } - fun allowed(enchants: Map, mat: Material): Boolean { + fun allowed(enchants: Map, mat: NamespacedKey): Boolean { if (enchantments.size < minBeforeBlock) { CustomAnvil.verboseLog("Conflicting bc of to many enchantments") return true diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt index f710f76..1dce406 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt @@ -8,6 +8,7 @@ import org.bukkit.inventory.ItemStack import xyz.alexcrea.cuanvil.enchant.AdditionalTestEnchantment import xyz.alexcrea.cuanvil.enchant.CAEnchantment import xyz.alexcrea.cuanvil.enchant.CAEnchantmentRegistry +import xyz.alexcrea.cuanvil.util.MaterialUtil.customType import java.util.* import kotlin.collections.set @@ -211,8 +212,8 @@ class EnchantConflictManager { item: ItemStack, newEnchant: CAEnchantment ): ConflictType { - val mat = item.type - CustomAnvil.verboseLog("Testing conflict for ${newEnchant.key} on ${mat.key}") + val type = item.customType + CustomAnvil.verboseLog("Testing conflict for ${newEnchant.key} on ${type}") val conflictList = newEnchant.conflicts var result = ConflictType.NO_CONFLICT @@ -223,7 +224,7 @@ class EnchantConflictManager { continue } - val allowed = conflict.allowed(appliedEnchants, mat) + val allowed = conflict.allowed(appliedEnchants, type) CustomAnvil.verboseLog("Was against $conflict and conflicting: ${!allowed} ") if (!allowed) { if (conflict.getEnchants().size <= 1) { @@ -239,7 +240,7 @@ class EnchantConflictManager { val immutableEnchants = Collections.unmodifiableMap(appliedEnchants) for (appliedEnchant in appliedEnchants.keys) { if (appliedEnchant is AdditionalTestEnchantment) { - val doConflict = appliedEnchant.isEnchantConflict(immutableEnchants, mat) + val doConflict = appliedEnchant.isEnchantConflict(immutableEnchants, type) if (doConflict) { CustomAnvil.verboseLog("Big conflict by additional test, stopping") return ConflictType.ENCHANTMENT_CONFLICT @@ -251,7 +252,7 @@ class EnchantConflictManager { if ((result != ConflictType.ITEM_CONFLICT) && (newEnchant is AdditionalTestEnchantment)) { val partialItem = createPartialResult(item, immutableEnchants) - if (newEnchant.isItemConflict(immutableEnchants, mat, partialItem)) { + if (newEnchant.isItemConflict(immutableEnchants, type, partialItem)) { return ConflictType.ITEM_CONFLICT } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/group/ExcludeGroup.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/group/ExcludeGroup.kt index 7684c3f..58ea48c 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/group/ExcludeGroup.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/group/ExcludeGroup.kt @@ -1,11 +1,12 @@ package xyz.alexcrea.cuanvil.group -import org.bukkit.Material +import org.bukkit.NamespacedKey import java.util.* class ExcludeGroup(name: String) : AbstractMaterialGroup(name) { - override fun createDefaultSet(): EnumSet { - return EnumSet.allOf(Material::class.java) + + override fun createDefaultSet(): MutableSet { + return NegativeSet(HashSet()) } private var includedGroup: MutableSet = HashSet() @@ -20,9 +21,9 @@ class ExcludeGroup(name: String) : AbstractMaterialGroup(name) { return false } - override fun addToPolicy(mat: Material): ExcludeGroup { - includedMaterial.remove(mat) - groupItems.remove(mat) + override fun addToPolicy(type: NamespacedKey): ExcludeGroup { + includedMaterial.remove(type) + groupItems.remove(type) return this } @@ -60,7 +61,7 @@ class ExcludeGroup(name: String) : AbstractMaterialGroup(name) { } } - override fun getMaterials(): EnumSet { + override fun getMaterials(): MutableSet { return groupItems } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/group/IncludeGroup.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/group/IncludeGroup.kt index 848789f..fc9614b 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/group/IncludeGroup.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/group/IncludeGroup.kt @@ -1,11 +1,12 @@ package xyz.alexcrea.cuanvil.group import org.bukkit.Material +import org.bukkit.NamespacedKey import java.util.* class IncludeGroup(name: String) : AbstractMaterialGroup(name) { - override fun createDefaultSet(): EnumSet { - return EnumSet.noneOf(Material::class.java) + override fun createDefaultSet(): MutableSet { + return HashSet() } private var includedGroup: MutableSet = HashSet() @@ -20,9 +21,9 @@ class IncludeGroup(name: String) : AbstractMaterialGroup(name) { return false } - override fun addToPolicy(mat: Material): IncludeGroup { - includedMaterial.add(mat) - groupItems.add(mat) + override fun addToPolicy(type: NamespacedKey): IncludeGroup { + includedMaterial.add(type) + groupItems.add(type) return this } @@ -47,7 +48,7 @@ class IncludeGroup(name: String) : AbstractMaterialGroup(name) { } } - override fun setNonGroupInheritedMaterials(materials: EnumSet) { + override fun setNonGroupInheritedMaterials(materials: Set) { super.setNonGroupInheritedMaterials(materials) updateMaterials() @@ -66,7 +67,7 @@ class IncludeGroup(name: String) : AbstractMaterialGroup(name) { } } - override fun getMaterials(): EnumSet { + override fun getMaterials(): MutableSet { return groupItems } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/group/ItemGroupManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/group/ItemGroupManager.kt index 65eef34..51b8249 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/group/ItemGroupManager.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/group/ItemGroupManager.kt @@ -91,7 +91,7 @@ class ItemGroupManager { } continue } - group.addToPolicy(material) + group.addToPolicy(material.key) } // Read group to include in this group policy. diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/group/NegativeSet.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/group/NegativeSet.kt new file mode 100644 index 0000000..386ba5f --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/group/NegativeSet.kt @@ -0,0 +1,51 @@ +package xyz.alexcrea.cuanvil.group + +class NegativeSet(val negate: MutableSet) : MutableSet { + + override fun iterator(): MutableIterator { + TODO("Not yet implemented") // can't be implemented I guess + } + + override fun add(element: T): Boolean { + return negate.remove(element) + } + + override fun remove(element: T): Boolean { + return negate.add(element) + } + + override fun addAll(elements: Collection): Boolean { + return negate.removeAll(elements) + } + + override fun removeAll(elements: Collection): Boolean { + return negate.addAll(elements) + } + + override fun retainAll(elements: Collection): Boolean { + TODO("Not yet implemented") + } + + override fun clear() { + TODO("Not yet implemented") + } + + override fun isEmpty(): Boolean { + TODO("Not yet implemented") + } + + override val size get() = TODO("Not yet implemented") + + override fun contains(element: T): Boolean { + return !negate.contains(element) + } + + override fun containsAll(elements: Collection): Boolean { + for (elm in elements) { + if(negate.contains(elm)) return false + } + + return true + } + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt index 0c4d2e7..4fe3034 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt @@ -24,6 +24,7 @@ import org.bukkit.inventory.meta.ItemMeta import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.enchant.CAEnchantment import xyz.alexcrea.cuanvil.util.* +import xyz.alexcrea.cuanvil.util.MaterialUtil.isAir import xyz.alexcrea.cuanvil.util.UnitRepairUtil.getRepair import java.util.concurrent.atomic.AtomicInteger @@ -42,10 +43,6 @@ class PrepareAnvilListener : Listener { var IS_EMPTY_TEST = false } - private fun ItemStack?.isAir(): Boolean { - return this == null || this.type.isAir || this.amount == 0 - } - /** * Event handler logic for when an anvil contains items to be combined */ @@ -121,7 +118,7 @@ class PrepareAnvilListener : Listener { } private fun isImmutable(item: ItemStack?): Boolean { - if (item.isAir()) return false + if (item.isAir) return false val meta = item!!.itemMeta return meta != null && @@ -172,7 +169,7 @@ class PrepareAnvilListener : Listener { if (finalResult == null) return false event.result = finalResult.result - if (finalResult.result.isAir()) return false + if (finalResult.result.isAir) return false AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, finalResult.levelCost, true) return true @@ -198,7 +195,7 @@ class PrepareAnvilListener : Listener { if (finalResult == null) return event.result = finalResult.result - if (finalResult.result.isAir()) return + if (finalResult.result.isAir) return AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, finalResult.levelCost) } @@ -286,7 +283,7 @@ class PrepareAnvilListener : Listener { if (finalResult == null) return event.result = finalResult.result - if (finalResult.result.isAir()) return + if (finalResult.result.isAir) return AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, finalResult.levelCost) } @@ -331,7 +328,7 @@ class PrepareAnvilListener : Listener { if (finalResult == null) return false event.result = finalResult.result - if (finalResult.result.isAir()) return false + if (finalResult.result.isAir) return false AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, finalResult.levelCost) return true @@ -351,7 +348,7 @@ class PrepareAnvilListener : Listener { result = AnvilLoreEditUtil.tryLoreEditByPaper(player, first, second, xpCost) } - if (result.isAir() || first == result) { + if (result.isAir || first == result) { CustomAnvil.log("lore edit, But input is same as output") event.result = null return false diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/recipe/AnvilCustomRecipe.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/recipe/AnvilCustomRecipe.kt index fa1a977..4f83c14 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/recipe/AnvilCustomRecipe.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/recipe/AnvilCustomRecipe.kt @@ -7,6 +7,7 @@ import xyz.alexcrea.cuanvil.config.ConfigHolder import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant import xyz.alexcrea.cuanvil.util.AnvilUseType import xyz.alexcrea.cuanvil.util.AnvilXpUtil +import xyz.alexcrea.cuanvil.util.MaterialUtil.isAir class AnvilCustomRecipe( val name: String, @@ -80,11 +81,7 @@ class AnvilCustomRecipe( } fun validate(): Boolean { - return (leftItem != null) && !(leftItem!!.type.isAir) && (leftItem!!.amount > 0) && - //(rightItem != null) && !(rightItem!!.type.isAir) && (rightItem!!.amount > 0) && - ((rightItem == null) || (!(rightItem!!.type.isAir) && (rightItem!!.amount > 0))) && - (resultItem != null) && !(resultItem!!.type.isAir) && (resultItem!!.amount > 0) - + return !leftItem.isAir && !rightItem.isAir && !resultItem.isAir } fun saveToFile(writeFile: Boolean, doBackup: Boolean) { diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/MaterialUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/MaterialUtil.kt new file mode 100644 index 0000000..c4907c8 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/MaterialUtil.kt @@ -0,0 +1,61 @@ +package xyz.alexcrea.cuanvil.util + +import org.bukkit.Bukkit +import org.bukkit.Material +import org.bukkit.NamespacedKey +import org.bukkit.inventory.ItemStack +import xyz.alexcrea.cuanvil.dependency.plugins.EcoItemDependencyUtil + +object MaterialUtil { + + val ItemStack?.isAir: Boolean + get() { + return this == null || this.type.isAir || this.amount == 0 + } + + val NamespacedKey?.isAir: Boolean + get() { + return Material.AIR.key == this + } + + private val HasEcoItem = Bukkit.getPluginManager().isPluginEnabled("EcoItems") + + val ItemStack.customType: NamespacedKey + get() { + if(HasEcoItem) { + val result = EcoItemDependencyUtil.ecoItemNamespace(this) + if(result != null) return result + } + + return this.type.key + } + + private fun bukkitMaterialFromKey(key: NamespacedKey): Material? { + //TODO on paper only transition Registry.MATERIAL.get(key) + return Material.matchMaterial(key.toString()) + } + + fun getMatFromKey(key: NamespacedKey): Material? { + if(HasEcoItem) { + val result = EcoItemDependencyUtil.ecoItemMaterialFromKey(key) + if(result != null) return result + } + + return bukkitMaterialFromKey(key) + } + + fun itemFromKey(key: NamespacedKey): ItemStack { + if(HasEcoItem) { + val result = EcoItemDependencyUtil.newEcoItemstack(key) + if(result != null) return result + } + + return ItemStack(bukkitMaterialFromKey(key)!!) + } + + fun materialExist(key: NamespacedKey): Boolean { + return getMatFromKey(key) != null + } + + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/UnitRepairUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/UnitRepairUtil.kt index 2047e79..8463e27 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/UnitRepairUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/UnitRepairUtil.kt @@ -3,6 +3,7 @@ package xyz.alexcrea.cuanvil.util import org.bukkit.configuration.ConfigurationSection import org.bukkit.inventory.ItemStack import xyz.alexcrea.cuanvil.config.ConfigHolder +import xyz.alexcrea.cuanvil.util.MaterialUtil.customType object UnitRepairUtil { @@ -22,7 +23,7 @@ object UnitRepairUtil { if (other == null) return null val config = ConfigHolder.UNIT_REPAIR_HOLDER.config // Get configuration section if exist - val otherName = other.type.name.lowercase() + val otherName = other.customType.key.lowercase() var section = config.getConfigurationSection(otherName) if (section == null) { section = config.getConfigurationSection(otherName.uppercase()) @@ -44,7 +45,7 @@ object UnitRepairUtil { * If value is set to less than or equal to 0 then it will be set to default */ private fun getRepairAmount(item: ItemStack, section: ConfigurationSection, default: Double): Double? { - val itemName = item.type.name.lowercase() + val itemName = item.customType.key.lowercase() val repairValue = if (section.isDouble(itemName)) { section.getDouble(itemName) } else if (section.isDouble(itemName.uppercase())) { diff --git a/src/test/java/xyz/alexcrea/cuanvil/api/MaterialGroupApiTests.java b/src/test/java/xyz/alexcrea/cuanvil/api/MaterialGroupApiTests.java index 164bc58..3fcafe7 100644 --- a/src/test/java/xyz/alexcrea/cuanvil/api/MaterialGroupApiTests.java +++ b/src/test/java/xyz/alexcrea/cuanvil/api/MaterialGroupApiTests.java @@ -15,7 +15,7 @@ public class MaterialGroupApiTests extends ConfigResetCustomAnvilTest { void groupAddAndRemove() { String groupName = "group"; IncludeGroup group = new IncludeGroup(groupName); - group.addToPolicy(Material.DIAMOND_PICKAXE); // We do not want it to be empty + group.addToPolicy(Material.DIAMOND_PICKAXE.getKey()); // We do not want it to be empty // Group not being set should not exist assertFalse(doGroupExist(groupName)); @@ -48,7 +48,7 @@ public class MaterialGroupApiTests extends ConfigResetCustomAnvilTest { void writeGroup_Reload() { String groupName = "group"; IncludeGroup group = new IncludeGroup(groupName); - group.addToPolicy(Material.DIAMOND_PICKAXE); // We do not want it to be empty + group.addToPolicy(Material.DIAMOND_PICKAXE.getKey()); // We do not want it to be empty // Group not being set should not exist assertFalse(doGroupExist(groupName)); From d4e94872d8403923c7c4bfd2aabc8f1cbc104112 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Wed, 4 Feb 2026 15:10:54 +0100 Subject: [PATCH 124/207] fix recipe error --- .../kotlin/xyz/alexcrea/cuanvil/recipe/AnvilCustomRecipe.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/recipe/AnvilCustomRecipe.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/recipe/AnvilCustomRecipe.kt index 4f83c14..57eb124 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/recipe/AnvilCustomRecipe.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/recipe/AnvilCustomRecipe.kt @@ -81,7 +81,9 @@ class AnvilCustomRecipe( } fun validate(): Boolean { - return !leftItem.isAir && !rightItem.isAir && !resultItem.isAir + return !leftItem.isAir && + (rightItem == null || !resultItem.isAir) && + !resultItem.isAir } fun saveToFile(writeFile: Boolean, doBackup: Boolean) { @@ -159,7 +161,7 @@ class AnvilCustomRecipe( CustomAnvil.verboseLog("Testing $name $leftItem") // We assume this function can be call only if leftItem != null - // Test is valid + // Test if valid if (!validate()) return false val leftSimilar = leftItem!!.isSimilar(item1) From daa1c6171fc1d9863cb2c3604a99859b94a7e408 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Sun, 12 Apr 2026 20:39:31 +0200 Subject: [PATCH 125/207] check section exist for group & conflict --- .../alexcrea/cuanvil/group/EnchantConflictManager.kt | 11 ++++++++++- .../xyz/alexcrea/cuanvil/group/ItemGroupManager.kt | 11 ++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt index f710f76..670dea8 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt @@ -48,6 +48,11 @@ class EnchantConflictManager { lateinit var conflictList: ArrayList + + private fun warnBadKey(key: String) { + CustomAnvil.instance.logger.warning("Invalid key $key for conflict: is not a conflict") + } + // Read and prepare all conflict fun prepareConflicts(config: ConfigurationSection, itemManager: ItemGroupManager) { conflictList = ArrayList() @@ -59,7 +64,11 @@ class EnchantConflictManager { val keys = config.getKeys(false) for (key in keys) { - val section = config.getConfigurationSection(key)!! + val section = config.getConfigurationSection(key) + if(section == null) { + warnBadKey(key) + continue + } val conflict = createConflict(section, itemManager, key) addConflict(conflict) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/group/ItemGroupManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/group/ItemGroupManager.kt index 65eef34..5c6cc9c 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/group/ItemGroupManager.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/group/ItemGroupManager.kt @@ -31,6 +31,8 @@ class ItemGroupManager { for (key in keys) { if (groupMap.containsKey(key)) continue + if (!config.isConfigurationSection(key)) + continue createGroup(config, keys, key) } } @@ -51,6 +53,7 @@ class ItemGroupManager { key: String ): AbstractMaterialGroup { val groupSection = config.getConfigurationSection(key)!! + val groupType = groupSection.getString(GROUP_TYPE_PATH, null) // Create Material group according to the group type @@ -105,11 +108,13 @@ class ItemGroupManager { continue } // Get other group or create it if not yet created - val otherGroup = if (!groupMap.containsKey(groupName)) { + val otherGroup = + if (!groupMap.containsKey(groupName)) { + if(!config.isConfigurationSection(groupName)) continue createGroup(config, keys, groupName) - } else { - groupMap[groupName]!! } + else groupMap[groupName]!! + // Avoid self reference or it will create an infinite loop if (otherGroup.isReferencing(group)) { CustomAnvil.instance.logger.warning( From b42fb42d83a030b3d45c9d8c9833c428f3a3fddd Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 21 Apr 2026 14:20:17 +0200 Subject: [PATCH 126/207] fix weird isAir error ? --- .../xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt index 4fe3034..7126dd9 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt @@ -93,7 +93,7 @@ class PrepareAnvilListener : Listener { if (testCustomRecipe(event, inventory, player, first, second)) return // Test rename lonely item - val isAir = second.isAir() + val isAir = second.isAir CustomAnvil.verboseLog("checking air in main logic: $isAir") if (isAir) { doRenaming(event, inventory, player, first) From 12bfcb75cee90ca893c5e1209f865106813d6027 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 21 Apr 2026 15:12:33 +0200 Subject: [PATCH 127/207] add more spigot warnings --- .../cuanvil/gui/config/global/BasicConfigGui.java | 13 ++++++++++--- src/main/kotlin/io/delilaheve/CustomAnvil.kt | 5 +++++ .../cuanvil/dependency/MinecraftVersionUtil.kt | 5 +++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/BasicConfigGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/BasicConfigGui.java index a07cb3c..97f8e54 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/BasicConfigGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/BasicConfigGui.java @@ -14,6 +14,7 @@ import org.bukkit.inventory.meta.ItemMeta; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import xyz.alexcrea.cuanvil.config.ConfigHolder; +import xyz.alexcrea.cuanvil.dependency.MinecraftVersionUtil; import xyz.alexcrea.cuanvil.dependency.packet.PacketManager; import xyz.alexcrea.cuanvil.gui.ValueUpdatableGui; import xyz.alexcrea.cuanvil.gui.config.MainConfigGui; @@ -282,9 +283,15 @@ public class BasicConfigGui extends ChestGui implements ValueUpdatableGui { lore.add("§7If the player do not have the required xp level, the action will not be completable."); if(!this.packetManager.getCanSetInstantBuild()){ - lore.add(""); - lore.add("§4/!\\§cCaution§4/!\\ §cYou need ProtocoLib installed and working or a newer version of this plugin for this to work."); - lore.add("§cCurrently ProtocoLib is not detected."); + if(MinecraftVersionUtil.INSTANCE.isTooNewForSpigot()){ + lore.add(""); + lore.add("§4/!\\§cCaution§4/!\\ §cYou need ProtocoLib installed and working or a paper server."); + lore.add("§cCurrently ProtocoLib is not detected."); + } else { + lore.add(""); + lore.add("§4/!\\§cCaution§4/!\\ §cYou need ProtocoLib installed and working or a newer version of this plugin for this to work."); + lore.add("§cCurrently ProtocoLib is not detected."); + } } String[] loreAsArray = new String[lore.size()]; diff --git a/src/main/kotlin/io/delilaheve/CustomAnvil.kt b/src/main/kotlin/io/delilaheve/CustomAnvil.kt index 44ca0a8..3190878 100644 --- a/src/main/kotlin/io/delilaheve/CustomAnvil.kt +++ b/src/main/kotlin/io/delilaheve/CustomAnvil.kt @@ -11,6 +11,7 @@ import xyz.alexcrea.cuanvil.command.EditConfigExecutor import xyz.alexcrea.cuanvil.command.ReloadExecutor import xyz.alexcrea.cuanvil.config.ConfigHolder import xyz.alexcrea.cuanvil.dependency.DependencyManager +import xyz.alexcrea.cuanvil.dependency.MinecraftVersionUtil import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil import xyz.alexcrea.cuanvil.enchant.CAEnchantmentRegistry import xyz.alexcrea.cuanvil.gui.config.MainConfigGui @@ -194,6 +195,10 @@ open class CustomAnvil : JavaPlugin() { if(!isPaper) { logger.warning("It seems you are using spigot") logger.warning("Please take notice that spigot is less supported than paper and derivatives") + if(MinecraftVersionUtil.isTooNewForSpigot) { + logger.warning("If replace too expensive is not working this is likely because of spigot") + logger.warning("As native nms is not supported for spigot starting 26.1") + } } val loader = if(isPaper) "paper" else "spigot" diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/MinecraftVersionUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/MinecraftVersionUtil.kt index 69ec546..d981f81 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/MinecraftVersionUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/MinecraftVersionUtil.kt @@ -51,4 +51,9 @@ object MinecraftVersionUtil { } } + val isTooNewForSpigot: Boolean get() { + val versionParts = UpdateUtils.currentMinecraftVersionArray() + return versionParts[0] != 1 + } + } \ No newline at end of file From bce43649bc8865b2030cd7ed4bc78c2cae6eba8c Mon Sep 17 00:00:00 2001 From: alexcrea Date: Thu, 23 Apr 2026 12:28:30 +0200 Subject: [PATCH 128/207] fix build error --- .../alexcrea/cuanvil/enchant/wrapped/CAEEV5_4Enchantment.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEV5_4Enchantment.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEV5_4Enchantment.java index 335430f..a86877f 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEV5_4Enchantment.java +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEV5_4Enchantment.java @@ -1,6 +1,7 @@ package xyz.alexcrea.cuanvil.enchant.wrapped; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.jetbrains.annotations.NotNull; import su.nightexpress.excellentenchants.api.enchantment.CustomEnchantment; import xyz.alexcrea.cuanvil.dependency.plugins.ExcellentEnchant5_4EnchantSettings; @@ -15,7 +16,7 @@ public class CAEEV5_4Enchantment extends CAEEV5Enchantment { } @Override - public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull Material itemMat) { + public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemMat) { if(super.isEnchantConflict(enchantments, itemMat)) return true; var limit = ExcellentEnchant5_4EnchantSettings.anvilLimit(); From 1b86996317d94b4399d61717137f31f46ed2726c Mon Sep 17 00:00:00 2001 From: alexcrea Date: Thu, 23 Apr 2026 12:32:41 +0200 Subject: [PATCH 129/207] add 26.1 ver to modrinth versions --- .github/workflows/gradle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index bbc9f70..8c4a93c 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -26,7 +26,7 @@ jobs: contents: write env: - MODRINTH_VERSIONS: '["1.18.x", "1.19.x", "1.20.x", "1.21.x"]' + MODRINTH_VERSIONS: '["1.18.x", "1.19.x", "1.20.x", "1.21.x", "26.1.x"]' MODRINTH_PLATFORMS: '["spigot", "paper", "purpur", "folia"]' steps: From d867ca6c851065776bef93bf091fc1f161dbd377 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Thu, 23 Apr 2026 14:16:48 +0200 Subject: [PATCH 130/207] forgot paper version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 1a8f2e4..19e22ef 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,5 +9,5 @@ kotlin.daemon.jvmargs=-Xmx8G subprojects.reobfnms=v1_17R1,v1_18R1,v1_18R2,v1_19R1,v1_19R2,v1_19R3,v1_20R1,v1_20R2,v1_20R3,v1_20R4,v1_21R1,v1_21R2,v1_21R3,v1_21R4,v1_21R5,v1_21R6,v1_21R7 # list of version for hangar release -paperVersion=1.18-1.21.11 +paperVersion=1.18-26.1.2 From 24db2594353cfc77f22f41ec2a89a2c69dffcabc Mon Sep 17 00:00:00 2001 From: alexcrea Date: Thu, 14 May 2026 06:28:50 +0200 Subject: [PATCH 131/207] add super enchant as lib --- libs/SuperEnchants-4.6.2-all.jar | Bin 0 -> 2238421 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 libs/SuperEnchants-4.6.2-all.jar diff --git a/libs/SuperEnchants-4.6.2-all.jar b/libs/SuperEnchants-4.6.2-all.jar new file mode 100644 index 0000000000000000000000000000000000000000..f4c832a1934d80afac3004e998b438d4aad6fc94 GIT binary patch literal 2238421 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

&)Jf{ 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 z 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

J(-U^V9>^U;HByxr!nqVJ0Va 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

T3&H6jqx%%y~TejdJ{@>nn{iIoG`W? zAXQeSAxV8hFA%?8!l<_tCt4CtjaC^_Z|pC}lWT7J4n(|INBEv2o*LLTj(`(v(=8+})rWv-mmV9;@Co>%UNs&&R3VI4JDV;vW**HQ;b zhm!^L&(u)DUkxhCxkwOhQxE^+0M)Lk3?mlbAu-DgxTSQtgdNpJ$<|H=WSqp;qrz-d z9)WnEC8Hlzj2DBpDqQKC!)s@xTPH=3Sp8nDMs{>)EPD;#QY+Mg1vVKSDEXg;|YN{SX`RtqrGeyBJYGV>*t<0K1 zfiJVzs~@}~cZVE46y&`^2p;lUG;Y-HJj_VlX|+y$Z!K;+394EXrOTORT2!n~KrRa` zVG()paRwh5B}6F9D0dEYEI!3vJR@9q;Pt+|d-0?_J}aDv;)H#-Fv)ThvKyhJLk>ku zK!L17YhuT03XT_R5D5gqwZ(WQi6-O`D$}c*zGANIk(@yQEVcbzatjivS3gV&Z_E?g9)$|{QDkq_3 zYH1QXAA{6swP>_RPw&S4aAI8uqG2z=flNrQe5#1EIt0_w zlE_dZt>)U>g&d3(UBeNe^6#=wK* zv87J;kkkM(TWxc#wOJpY>rd4-=$>WVz{4S~J!A`Bu8I+g#ydA<$0X~cwOuhrEdeMe z+BAZll{cxNPEiB2 z1^r~Hgg)1Q)=TNYa_4QBvj>8d=PfqCedn( zt{AKVN^)Kp=`?v?ZYYcgUY^yyX!r@a8Q&DO33bT^NMi?|8Rhn_`ixB_BYjDg)tmC_ zZ3lWy5?btu8PD>TLcrFAN$>a+ErCcfODY$NkX{fLBr_inu36RDJzA4lHOJ$oUZA}- zCWw{$HzTDgyIi-P!$*{+XdS*UM%a^bF4<*l(wl&0$(=2ThYnxAY5P3}dDwZdtT&?e zjoX_3yw!U|Tu!0iNUqz&ny$ZpqJ`LT*Sy(x#4EtyV)Bh){WZvk?Mks}R@H;_&TxMh zgeOI_c73utH;B)m$&j41xX@i|`2ytRSB-G(WB7soiVup4AEoQ+;4*DH{1RvKZ6oE2 z6pNRh6;QVWKZ18>E0L!CSvgs159<&D=kCd8ag^W3iOxjimy>~PsSZX7^prgJ1EO>m z$;PQcs z?2wCQyhia%FfRgww1e^wqNZ~hZUk8zp#wMb0Nm=>3%eN+zTC1oY6rEf4mG4E+GUX9 zVV^~H#W&{04G8NN*?Rc+WQ}&tO@}a}D~O{b%>BwL;-yAu=`R6{WXh!{Y@XlaBJAma z^l2Xrd$cfn)TTXNT0w}zr)iShMJ(Jd>?``ys;*mH7H(|4Ji%G~J^qpo9|Qt#(gs0c zUEnV-|K%ZuT&L3U^g2a~lJ|(0V5<1Tz&i_9H3}n;kApz(il~orFISLF-acbq>D4Y~H zuL|#jNDb!;7Mxq2tw;h3z7~jAImtEBJFkR+4#VjnkTBO!R;gVl6q#4T)2JdftQV6I+&SvvHPv%SPvo=e1~Z!{Dz@Vg-5%J9q$|xq_1i|{ zme5L|lmgGJ?nA{i%I;UH_Z5f+A>qtHqU$2W(XpK(m#axD73v8khHlHjoel@C$0Mif z%}=YP)q$PUi#kaPn)jNx4s#LVDgYK6pcRoMW}a zY|m^@`}?ND%-B7wukTjeHh+VgZByvDsrUv8QE}${q)&(BuufzY*2&He8h}XLtZR3* z{JBhH8SQDE)MIenSWUcC{|H>g*Dr`QUZG{~&<>V7*e*%Ea}#xDILUog&JEM^X!A8r z#uy8AZr;g{S4ZFR3XZ!s*@+(2l|-Z$6Q>xeNsk`=$7u_yFD6{USW;6n7*`SG8vDp(uo{fG0cQuS3saT&vVnrcQdu48j1 z@=AqjHe(092rD&fu13=3X_i?%kEut%pk`IN?1w-(0;+0uV`m01U8+n-{)kaC#-{l-?8XQu!c z&&MB@R(NKa|Jzx_!Oq4x$GEaSe9{{VBaqcL@CDLo z2t+S@>^TImBXIj?&Jo4fh(1`~VTes= zBZCPfLuD(`u*rkcd+(ZyiggR&w^@`)b`qE^^KR9bZVSz2V2Ev?IeN*^#wcW5Jz!f} zJXaG;pOv9ZF1AC4K$EO6uBnZ61VX{}_fI~mSq4g|EL?qhYS9zV2E8}a^lbb+uw-e2 zL*g54Pz7EoCfRgZ5R)4NWCXF;X|(C+>1%D|R^P?@*d3=`t%vDqnj(#I&nsOuzljkz zbevb^H(WDWI8gc#vz~gq^O9&m!oi>5;_OAEskWTm_=nYxkH8+Dac0##!ok4ITUvo; zVx#p{eLX)0V|sob`Q-W&yjCWw_0RH7cN*58x^9QBOb~q#TIr(9{Pm;b8ks9lE8Ozk zp`Le>X=!BqzhnXW>Wyc{7t2VO??IFsAAcZ^Bs@4w?rEq^a&@jPe21|SY?B9wjt<#b zP7O+0kZMVv;fSQd7`c6sXeu6B*)7^ViN+V8t_k~G*@89~S}jo8#5?U?6~nQ2iinPe z?Ed5~R5a=Lx-KH7GOt(tQWZZPXJ+avuDdhO5y*r{tMk)5u~82063@i-qw}RoxE8g3 zEIm(^B|j^;iR_@zQ`~yhw;J>h+^6x6cdu8ySu|y(KB&)Oc`(A4SfA_flp$#kg$9N3 ziZuSZqU5lqDeC2dp473Bo-GfX(L*dV9y`{FBuj>QBS)EI&A&Y4<=4I+ilcDAz_GwZ zlI#RSyvFT4-NrOmuwYA|3)>TecO-&^^v@gj?5tkiD0gCS6N}l@nu~T1G9D1QvFzYFvuG_4`#SEucqNGE#6iY<3 zFpC-ANFuax<&LLKPpX&HOpXJ%yR6GdGA;d5!2BR zBY*g6jl)eyO7Ae9mP}6v)E79q&!yunj$mNIfI!pi-rg60X=Lz#6gF;0uwS$V`qqzK zo7FaaGAY`OlavmI%NL_SWLv}#(=#)x&3}iyioTBc6Z#J4cKroA29_sWV&1hC)5AZi z_qo<1OU%F)Q>Q$xAe|?)UQr39xEIT8(C3!Q9}@`E*&KcGWe*9neA{P#^w_AJX)R3s ztRYt1bV!|Z=+?a}lXZ_h?VfWl#qb?*?H06YmjqFKyuBn(I%9D876$hTk@-WaL!6Lx z2eK>3MqylWCj-Vh7phur(ML|2zqWPDIwt@cAQPY85K3}ndCSqYzYfI0Dz0p|jVkYm zxqJ6VOs16d$S^_40vup=pauDDo_&ek$A-|Q#*Z!!E*DlNw$ybYp~%XPWC(!I9)bg= zA?%xi1HoW&c@bTMVB(IsTTnV>wBsv|g;n4=r@OUy8Etaj);sb8LkO*uD_K?mrz_Am z&Gl*`tB#&mdag~@zt#%k9I->TA(wzh_7?vXc=Q%N4LtkISc?7arE@dZ^GsiU9~F@j zL4uYRVUCur`7t{PpwV`kEql|V(RR3Pi(u8#9Nu=74qUuBYH=q-Xh`DC0~;4HcySU!8-q{UHSp-sgX)cZ>E6Us70rPm3+cAN6Ij85abA%VTm&TdBMbe7lWQ`YBo zd-oUUzdpub3!!3tMc2}U4prukGlyO2^+6?TiRl_i8Ay$mtk`6K4SQwSd9lT%ZDQ`)L<{njg!==Kf*(b!#a0*9K7Fs`@``#Szt=3J0U1SY~Ec+ryP zXjfpq4EzEaIXff*Q=7oxdT72@b9d-v8CwU-Veq&T1Cz`?AQzQDLv$HKSf}u4avgcd z@Rx85hfK(O;(77Xr2wc9D+@f67Ogt-UQF{jI(M>dg~3@nzued^Ptg~7eO(>J?O|;I z(7A$U_znRLqa*fGX8nrASjG8b_%o2?jw?ax)06 z0(avUEwdgvq1;2iE;@IYNfC15X4f>7w)|HWL*RVt2R{WX^Q3OZ%pKtb`MJeQ2ZBM% z)XY&F)uD2R7E-X`UTCuL){1aYg-sC!NECdz}2e)3TR`CTXt47fJt9y zIO(i90|nUf9pO8Y|4?EhN*|Ftm_%lT+!`&&C=3zCRbT9rUg1R?U)m|eu=7=G_*f$B z=5w(Tx5Rh%h}KWZ{T4diDmSxzMq@2R2T*7>cdmph_iZ z<3)uGg=Cd6d#vFZMGD{n5tC56{y+^cF*ShR45?kICnB3X`q+Ba55-d@CNwe^efCWJ zRTA3JY3**@i&z?Ck%Gy7m=I_lXcnQ_C=Y)HM2d5lXcxYreo$4q<)pXGr_=7?l0ARa zaWKfh++e+=#;U`c4b5rh;e}*83qnF1sLn)w0 zh$%6Y?~`YB1Lw(*@_}>AVv)u(x9R_DRd`eZNj=OyF7wf9#!q_DV()_uWY0+%ZXuY) zF}@b^9uI>FK2v7MgIv-c%4@%OYDeRto42lc94@DJIX7g(+fn+(KX4;7Jbrp)IAn*dm;VIlWY zTHbw+jCH~VdNrhF^Go7Hc;YRQ5e&8f4MnB!vdMp=gk?&L+@dE>dcZmc!QtYu&X{mH zpq}1nF>VjKNxEc2KjsoSJ&ng7;#77*&*ZL1@y^7`An@N#kIaRbE*UTMs#p8 zH;x!`aGAJCoPwMU9u2whKh0*nRe_igHMs5noNRkhWMd#dbqTtkx`fDo89a+wI~v*R z894q=@Jys)YGf^?XKVYzrF8f|wKX{{8$>>2uKqcize+VZb8lg$zZ=WLY?w(3_%}o) z2#UCGW}wG`i>Me|01=O9uN5Ri=<|3xG4j{CNKv$yI#-je4p*ZY4&C1GoY)&2HMu13S=HY@x~@`=9MO*1`mq z4bjTIzCR4J~6lsg5tT+pk4;+LlGpLXM)5C zV9Q{(o*OrEk9J7RXDx^OPyg``ZDAxIxcwo#SmFHleMm_goB#Wj{r^9#%O4j2vJ1-h z*HhZ0p@S8YF8NG@rC$6m2_P6N(^C9`ni?_4+`l<{L>O`S?EpIy#9Uk-zJk*Js-S z&sVutUDv8Q^+)5?#eF}*`y+jutG`U$`b}-1ahssG>MlI3ep_I+HmWJ(2VcTj5oLOt zkaQa3@`%0UVFjY?7n*t*^9&6Ki|MS1H_PbkX&$Td*S0m5{z3`}%wV*&@a$FCxARtTlS&{pKDPe0$zhweG^| zauLyTnvlnB@$Hm*Xqt>C)7t(fL1pNeem@Zks3Fe$!1NbtaQg~V=gjiG{=jae4aXbUWo_hBPxQ>3mWt>+oM z$VCK$X0z$*-P4m!7Zu1mr^j22J>PKl{w^UT+&gypO8}!jo;&+dFqwM@pX2L*CH{bo zd?l2a_8I(EJYDj9-GfKjVq>)!Jsi_h&*EcNnU=z;5)LQ|a+HW2x-no3q8o&AF9pXOQqN}=>Ai_J$`MH)-U(91uc34i-mJ|9-> z`@7)!Ewfmz8gsBpOEnvWh@TLFpdQFWB%Uu*rwTfcCizb}6m9NhplD8>>^XBZ5Q?n< z4lP*_`e+=>SHd>@X}QmI9frKKMS z!|s?7uf!Bjul=jXvH(*8kjDp?`-qv1e3C5j4EijP{-AB&P3yXAGUqD>*tU&1mFBR?hIhJRV$lmH~ zMsECMp``9l0Wam`a%*#KWy$M$k`oBB?1kDuf)3aiqbmZ_sv}K4!yzhwJ`(2i-3TIp ziGiIRWaDTR(>8(CG**AacS>HPO!2`^yF~0BYGVmVbrPYJ3|u-OYGIk$ccr=(nKl27-W20p8=WHz(9pu_NFqUI z+&u%PqV99pvbP2k*l`lfm1@dpp|4&rsma(l%``SoOTndMY>S864rLy>`-Juh$HEbU z*v1!>zML*8KgJQ=f2wp~UBvh+Oo7RH4tdlEZ5&JI%Q#z%iY)<<=~2FV`nDZvop zNg*^#7UP)A?wI8^r9kn@g$R)%$qh~AzcD(=E0vhAz%1p&K`zh#dJI?Z%*YF_YBs(lp!(hSUe|gZ6cw9TE%fzF?$F_$rDPa@J=zmrLD4} z3fVISWMg4$$x?~q{QZW40ynA2=giOp*(1`4o&Y6H%Aiip8;@1`RC|b3>;}F&C7H`o zySEA4?`Pu7SRn8QOG`6c_(t2EzTY=>NCuV<-xG@`)`7>kW|FMlfS;!NPMq5AOwRs; z&Kn$56Rx2Tqgq(gdM8ZiPaLOv=g@8E_>F+dT~YJCU480zSp}XB|W9&3(%s2 z*mA;1KDjo^*ynOF3;R?&c8n@Zl&9;Vzfg@n-y;USp*5wNN8fG=B6w8js4$ zJz&S2DVYgkB$ft~cjZW(iiHjujFLN&+p^p1^X(f4l3Cr~Dij@n_*y3uFrbZ5=_>)E`78?8+8+1`I9rIVPuRKh^xNEr%iP2NhK zpxIIy23Qg4UkNR)rVR}bt+-;(Av{FH|E(H`v=VrP5=E5i~zOi5(t~hfUBAh zZYDFT;C2)P-7e%UFD`@(IHx+ zG+xlJ2>cLSXn3P+-;OZ1&AELTm>Wa5UI|Y=4{Kqc_vRBt=VaZkysNx?$jkNG5~kJ1 zwf4Lj*kqIjh?bl6fl5xfsj!5M_&Fkp!Xo#@_FPN*+f5b2u5`FNu8_JoqeL`5wvK!n zXPR8o$zcwdyNBJds*@|RY{*f-qxFr&Lz`715+e7F-npQuV7 zIHKXNY&ieWF9%LTcn~=wc;+vMNVl5sRsR6(BOr9ln^3o~$)IMu)x;e#1i;_V+W8+?B$0g2=$5 z@%M#vd4;gKi}nzDa6w4t{1*Ow4=+R)M8{a=RtmOF%Abe`2tb-MKL;z8+=u`Q*B5!l z=kanj0jAOiz`cnx2O%kV9!ExqlhUE6@Zm%x_L2ASNK>gRCsHnje->{e>8y-g`9WfV zT(tQ@mfcpc1zFgn9SD#u(uTQK;_am!NzLgROjc(+zatZkLx}zZ`fl&(Ep>((Q7sy! zZ*g0Y{}MGv9skvcu}s*Gi1nsCUDr^+7faU$#0ny)VdWhJud_i~B!DRpw3jwHp;)$2 zWf0f*(kb}+L>}#MGvSar+_5hC1RJVRn<$PCzn0{%Q zLmR!FWB)DMv&=v*-(S0@-{Emz-K;U<3I^#A^UX)tzD)RK2>C>KsG$L1#fyTVu&`k; zd}{k1H3tBFs7jjpr2q^4e3H&c8gkSCR0Grt>LfKa!+j9c6?M110T-xOb%S)rgH%N+ z8ecSyD_X?LECx6o;nvNuW%GbW0c>SCsa#JQ9hzb&NuT;f@LW@$qh5ta9q#)x>@+#b8ropRsu{(h(S{y`tT z1&+CtVb@$yQOSBsw!R| zzDhri47sTc12@8MP1CMPktoS!UV+}6-O=A5g!f!%lK!ReANCgjG9Io#mFC0};~Ji| zs>7vf0ySMCYtA;GUWo|#2M4d}^XGdK9{gwgEWR{kSabHAWWt^@#qcUNS1|}cB#*?y zuc`Cf*e<~1`tjxFQ&VopLctbc zh=V?C2)K#WP6lb}P@4EA<@VtQ&h=M9@0N70MYeTWs$^_-A0qr6*zuWVv2Sc_Y@aO$ z`OLfvD)AC!^^Qw`y;5D7D7

* + * @deprecated use #{@link #getCost()} instead * @return The current cost. */ + @Deprecated(forRemoval = true, since = "1.17.0") public int getLevelCost() { - return levelCost; + return cost.sum(); } /** @@ -123,9 +126,33 @@ public class CATreatAnvilResultEvent extends Event { *
  • Item rename
  • * * + * @deprecated use #{@link #getCost()} and set value on this instead * @param levelCost The new cost. */ + @Deprecated(forRemoval = true, since = "1.17.0") public void setLevelCost(int levelCost) { - this.levelCost = levelCost; + cost.setGeneric(levelCost - cost.getGeneric() - cost.sum()); } + + /** + * Allow access to the current cost of the event + * Note that modifying this object will change the event resulting cost + * + *

    Important note:

    + * the final price are re calculated on click for the following use case: + *
      + *
    • Custom craft
    • + *
    • Unit repair
    • + *
    • Lore edit
    • + *
    + * This value will be used as final price for: + *
  • Item merge
  • + *
  • Item rename
  • + * + * @return the current anvil cost + */ + public AnvilCost getCost() { + return cost; + } + } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt index 0b5f396..4e59adc 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt @@ -30,6 +30,7 @@ import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.componentLore import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener.Companion.ANVIL_OUTPUT_SLOT import xyz.alexcrea.cuanvil.util.AnvilUseType +import xyz.alexcrea.cuanvil.util.AnvilXpUtil import xyz.alexcrea.cuanvil.util.MetricsUtil.trackError import java.util.logging.Level @@ -235,12 +236,12 @@ object DependencyManager { event: PrepareAnvilEvent, result: ItemStack, useType: AnvilUseType, - cost: Int + cost: AnvilXpUtil.AnvilCost ): CATreatAnvilResultEvent? { val treatEvent = CATreatAnvilResultEvent(event, useType, result, cost) try { unsafeTryTreatAnvilResult(treatEvent) - return treatEvent; + return treatEvent } catch (e: Exception) { logExceptionAndClear(event.view.player, event.inventory, e) return null diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/DisenchantmentDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/DisenchantmentDependency.kt index 089a5fb..3dfba9c 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/DisenchantmentDependency.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/DisenchantmentDependency.kt @@ -16,6 +16,7 @@ import org.bukkit.inventory.AnvilInventory import org.bukkit.inventory.ItemStack import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener import xyz.alexcrea.cuanvil.util.AnvilXpUtil +import xyz.alexcrea.cuanvil.util.AnvilXpUtil.AnvilCost import xyz.alexcrea.cuanvil.util.MetricsUtil.trackError import java.util.logging.Level import kotlin.reflect.KClass @@ -58,14 +59,14 @@ class DisenchantmentDependency { DisenchantEvent.onEvent(event) if (event.result != null) { CustomAnvil.log("Detected pre anvil item extract bypass.") - AnvilXpUtil.setAnvilInvXp(event.inventory, event.view, player, event.inventory.repairCost) + AnvilXpUtil.setAnvilInvCost(event.inventory, event.view, player, AnvilCost(event.inventory.repairCost)) return true } ShatterEvent.onEvent(event) if (event.result != null) { CustomAnvil.log("Detected pre anvil split enchant bypass.") - AnvilXpUtil.setAnvilInvXp(event.inventory, event.view, player, event.inventory.repairCost) + AnvilXpUtil.setAnvilInvCost(event.inventory, event.view, player, AnvilCost(event.inventory.repairCost)) return true } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/HavenBagsDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/HavenBagsDependency.kt index 6e7cf60..6b5f9c4 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/HavenBagsDependency.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/HavenBagsDependency.kt @@ -11,6 +11,7 @@ import valorless.havenbags.features.BagSkin import valorless.havenbags.features.BagUpgrade import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener import xyz.alexcrea.cuanvil.util.AnvilXpUtil +import xyz.alexcrea.cuanvil.util.AnvilXpUtil.AnvilCost class HavenBagsDependency { @@ -53,14 +54,14 @@ class HavenBagsDependency { bagSkin.onPrepareAnvil(event) if (event.result != null) { CustomAnvil.log("Detected pre anvil heaven bag anvil skin.") - AnvilXpUtil.setAnvilInvXp(event.inventory, event.view, player, event.inventory.repairCost) + AnvilXpUtil.setAnvilInvCost(event.inventory, event.view, player, AnvilCost(event.inventory.repairCost)) return true } bagUpgrade.onPrepareAnvil(event) if (event.result != null) { CustomAnvil.log("Detected pre anvil heaven bag anvil upgrade.") - AnvilXpUtil.setAnvilInvXp(event.inventory, event.view, player, event.inventory.repairCost) + AnvilXpUtil.setAnvilInvCost(event.inventory, event.view, player, AnvilCost(event.inventory.repairCost)) return true } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt index bc3afa4..1886589 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt @@ -25,6 +25,7 @@ import xyz.alexcrea.cuanvil.recipe.AnvilCustomRecipe import xyz.alexcrea.cuanvil.util.AnvilLoreEditUtil import xyz.alexcrea.cuanvil.util.AnvilUseType import xyz.alexcrea.cuanvil.util.AnvilXpUtil +import xyz.alexcrea.cuanvil.util.AnvilXpUtil.AnvilCost import xyz.alexcrea.cuanvil.util.CustomRecipeUtil import xyz.alexcrea.cuanvil.util.MiniMessageUtil import xyz.alexcrea.cuanvil.util.UnitRepairUtil.getRepair @@ -350,13 +351,13 @@ class AnvilResultListener : Listener { } private fun getFromLoreEditXpCost( - xpCost: AtomicInteger, + cost: AnvilCost, player: Player, inventory: AnvilInventory, ): Int { if (GameMode.CREATIVE == player.gameMode) return 0 - val repairCost = xpCost.get() + val repairCost = cost.sum() return if ((inventory.maximumRepairCost <= repairCost) || (player.level < repairCost) ) Int.MIN_VALUE @@ -376,9 +377,9 @@ class AnvilResultListener : Listener { val editType = AnvilLoreEditUtil.bookLoreEditIsAppend(leftItem, rightItem) ?: return false - val xpCost = AtomicInteger() + val cost = AnvilCost() if (editType) { - if (output != AnvilLoreEditUtil.handleLoreAppendByBook(player, leftItem, bookMeta, xpCost)) return false + if (output != AnvilLoreEditUtil.handleLoreAppendByBook(player, leftItem, bookMeta, cost)) return false // Remove pages to book val clearedBook: ItemStack? @@ -394,10 +395,10 @@ class AnvilResultListener : Listener { event, player, inventory, null, 0, clearedBook, 0, - output, getFromLoreEditXpCost(xpCost, player, inventory) + output, getFromLoreEditXpCost(cost, player, inventory) ) } else { - if (output != AnvilLoreEditUtil.handleLoreRemoveByBook(player, leftItem, xpCost)) return false + if (output != AnvilLoreEditUtil.handleLoreRemoveByBook(player, leftItem, cost)) return false // fill book meta val lore = DependencyManager.stripLore(leftItem) @@ -430,7 +431,7 @@ class AnvilResultListener : Listener { event, player, inventory, null, 0, rightCopy, 0, - output, getFromLoreEditXpCost(xpCost, player, inventory) + output, getFromLoreEditXpCost(cost, player, inventory) ) } } @@ -448,9 +449,9 @@ class AnvilResultListener : Listener { val editTypeIsAppend = AnvilLoreEditUtil.paperLoreEditIsAppend(leftItem, rightItem) ?: return false - val xpCost = AtomicInteger() + val cost = AnvilCost() if (editTypeIsAppend) { - if (output != AnvilLoreEditUtil.handleLoreAppendByPaper(player, leftItem, rightItem, xpCost)) return false + if (output != AnvilLoreEditUtil.handleLoreAppendByPaper(player, leftItem, rightItem, cost)) return false val paperCopy: ItemStack? if (LoreEditType.APPEND_PAPER.doConsume) { @@ -468,18 +469,18 @@ class AnvilResultListener : Listener { event, player, inventory, paperCopy, 0, rightItem, 1, - output, getFromLoreEditXpCost(xpCost, player, inventory) + output, getFromLoreEditXpCost(cost, player, inventory) ) } else { extractAnvilResult( event, player, inventory, null, 0, paperCopy, 0, - output, getFromLoreEditXpCost(xpCost, player, inventory) + output, getFromLoreEditXpCost(cost, player, inventory) ) } } else { - if (output != AnvilLoreEditUtil.handleLoreRemoveByPaper(player, leftItem, xpCost)) return false + if (output != AnvilLoreEditUtil.handleLoreRemoveByPaper(player, leftItem, cost)) return false val leftMeta = leftItem.itemMeta if (leftMeta == null || !leftMeta.hasLore()) return false @@ -512,14 +513,14 @@ class AnvilResultListener : Listener { event, player, inventory, rightClone, 0, rightItem, 1, - output, getFromLoreEditXpCost(xpCost, player, inventory) + output, getFromLoreEditXpCost(cost, player, inventory) ) } else { extractAnvilResult( event, player, inventory, null, 0, rightClone, 0, - output, getFromLoreEditXpCost(xpCost, player, inventory) + output, getFromLoreEditXpCost(cost, player, inventory) ) } } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt index 0525a85..bf8d3dd 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt @@ -26,10 +26,10 @@ import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.dialog.AnvilRenameDialog import xyz.alexcrea.cuanvil.enchant.CAEnchantment import xyz.alexcrea.cuanvil.util.* +import xyz.alexcrea.cuanvil.util.AnvilXpUtil.AnvilCost import xyz.alexcrea.cuanvil.util.MaterialUtil.isAir import xyz.alexcrea.cuanvil.util.UnitRepairUtil.getRepair import xyz.alexcrea.cuanvil.util.dialog.AnvilRenameDialogUtil -import java.util.concurrent.atomic.AtomicInteger /** * Listener for anvil events @@ -60,7 +60,7 @@ class PrepareAnvilListener : Listener { // Test if custom anvil is bypassed before immutability test if (DependencyManager.earlyTryEventPreAnvilBypass(event, player)) { // even if we got bypassed we still want to set price - AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, event.inventory.repairCost) + AnvilXpUtil.setAnvilInvCost(inventory, event.view, player, AnvilCost(event.inventory.repairCost)) return } @@ -90,7 +90,7 @@ class PrepareAnvilListener : Listener { // Test if the event should bypass custom anvil. if (DependencyManager.tryEventPreAnvilBypass(event, player)) { // even if we got bypassed we still want to set price - AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, event.inventory.repairCost) + AnvilXpUtil.setAnvilInvCost(inventory, event.view, player, AnvilCost(event.inventory.repairCost)) return } @@ -198,17 +198,17 @@ class PrepareAnvilListener : Listener { // Maybe add an option on custom craft to ignore/not ignore penalty ?? val xpCost = recipe.determineCost(amount, first, resultItem) - val levelCost = - if (recipe.removeExactLinearXp) AnvilXpUtil.calculateMinimumLevelForXp(xpCost) + val cost = AnvilCost() + cost.recipe = if (recipe.removeExactLinearXp) AnvilXpUtil.calculateMinimumLevelForXp(xpCost) else AnvilXpUtil.calculateLevelForXp(xpCost) - val finalResult = DependencyManager.tryTreatAnvilResult(event, resultItem, AnvilUseType.CUSTOM_CRAFT, levelCost) + val finalResult = DependencyManager.tryTreatAnvilResult(event, resultItem, AnvilUseType.CUSTOM_CRAFT, cost) if (finalResult == null) return false event.result = finalResult.result if (finalResult.result.isAir) return false - AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, finalResult.levelCost, true) + AnvilXpUtil.setAnvilInvCost(inventory, event.view, player, cost, true) return true } @@ -217,7 +217,8 @@ class PrepareAnvilListener : Listener { player: HumanEntity, first: ItemStack ) { val resultItem = DependencyManager.cloneItem(event, first) - var anvilCost = handleRename(resultItem, inventory, player) + val cost = AnvilCost() + cost.rename = handleRename(resultItem, inventory, player) // Test/stop if nothing changed. if (first == resultItem) { @@ -226,15 +227,15 @@ class PrepareAnvilListener : Listener { return } - anvilCost += AnvilXpUtil.calculatePenalty(first, null, resultItem, AnvilUseType.RENAME_ONLY) + cost.penalty = AnvilXpUtil.calculatePenalty(first, null, resultItem, AnvilUseType.RENAME_ONLY) - val finalResult = DependencyManager.tryTreatAnvilResult(event, resultItem, AnvilUseType.RENAME_ONLY, anvilCost) + val finalResult = DependencyManager.tryTreatAnvilResult(event, resultItem, AnvilUseType.RENAME_ONLY, cost) if (finalResult == null) return event.result = finalResult.result if (finalResult.result.isAir) return - AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, finalResult.levelCost) + AnvilXpUtil.setAnvilInvCost(inventory, event.view, player, cost) } private fun handleRename(resultItem: ItemStack, inventory: AnvilInventory, player: HumanEntity): Int { @@ -291,18 +292,18 @@ class PrepareAnvilListener : Listener { var hasChanged = !isIdentical(first.findEnchantments(), newEnchants) val resultItem = DependencyManager.cloneItem(event, first) - var anvilCost = 0 + val cost = AnvilCost() if(hasChanged){ resultItem.setEnchantmentsUnsafe(newEnchants) // Calculate enchantment cost - anvilCost+= AnvilXpUtil.getRightValues(second, resultItem) + cost.enchantment = 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 + cost.repair = if (repaired) ConfigOptions.itemRepairCost else 0 hasChanged = hasChanged || repaired } @@ -313,18 +314,18 @@ class PrepareAnvilListener : Listener { return } // As calculatePenalty edit result, we need to calculate penalty after checking equality - anvilCost += AnvilXpUtil.calculatePenalty(first, second, resultItem, AnvilUseType.MERGE) + cost.penalty = AnvilXpUtil.calculatePenalty(first, second, resultItem, AnvilUseType.MERGE) // Calculate rename cost - anvilCost += handleRename(resultItem, inventory, player) + cost.rename = handleRename(resultItem, inventory, player) // Finally, we set result - val finalResult = DependencyManager.tryTreatAnvilResult(event, resultItem, AnvilUseType.MERGE, anvilCost) + val finalResult = DependencyManager.tryTreatAnvilResult(event, resultItem, AnvilUseType.MERGE, cost) if (finalResult == null) return event.result = finalResult.result if (finalResult.result.isAir) return - AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, finalResult.levelCost) + AnvilXpUtil.setAnvilInvCost(inventory, event.view, player, cost) } private fun isIdentical( @@ -347,14 +348,15 @@ class PrepareAnvilListener : Listener { val unitRepairAmount = first.getRepair(second) ?: return false val resultItem = DependencyManager.cloneItem(event, first) - var anvilCost = handleRename(resultItem, inventory, player) + val cost = AnvilCost() + cost.rename = handleRename(resultItem, inventory, player) val repairAmount = resultItem.unitRepair(second.amount, unitRepairAmount) if (repairAmount > 0) { - anvilCost += repairAmount * ConfigOptions.unitRepairCost + cost.repair = repairAmount * ConfigOptions.unitRepairCost } // We do not care about right item penalty for unit repair - anvilCost += AnvilXpUtil.calculatePenalty(first, null, resultItem, AnvilUseType.UNIT_REPAIR) + cost.penalty = AnvilXpUtil.calculatePenalty(first, null, resultItem, AnvilUseType.UNIT_REPAIR) // Test/stop if nothing changed. if (first == resultItem) { @@ -363,13 +365,13 @@ class PrepareAnvilListener : Listener { return true } - val finalResult = DependencyManager.tryTreatAnvilResult(event, resultItem, AnvilUseType.UNIT_REPAIR, anvilCost) + val finalResult = DependencyManager.tryTreatAnvilResult(event, resultItem, AnvilUseType.UNIT_REPAIR, cost) if (finalResult == null) return false event.result = finalResult.result if (finalResult.result.isAir) return false - AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, finalResult.levelCost) + AnvilXpUtil.setAnvilInvCost(inventory, event.view, player, cost) return true } @@ -380,11 +382,11 @@ class PrepareAnvilListener : Listener { val type = second.type var result: ItemStack? = null - val xpCost = AtomicInteger() + val cost = AnvilCost() if (Material.WRITABLE_BOOK == type) { - result = AnvilLoreEditUtil.tryLoreEditByBook(player, first, second, xpCost) + result = AnvilLoreEditUtil.tryLoreEditByBook(player, first, second, cost) } else if (Material.PAPER == type) { - result = AnvilLoreEditUtil.tryLoreEditByPaper(player, first, second, xpCost) + result = AnvilLoreEditUtil.tryLoreEditByPaper(player, first, second, cost) } if (result.isAir || first == result) { @@ -394,7 +396,7 @@ class PrepareAnvilListener : Listener { } event.result = result - AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, xpCost.get()) + AnvilXpUtil.setAnvilInvCost(inventory, event.view, player, cost) return true } } \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt index 36f0efb..85a0ee8 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt @@ -8,10 +8,10 @@ import org.bukkit.permissions.Permissible import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.componentLore import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.setComponentLore +import xyz.alexcrea.cuanvil.util.AnvilXpUtil.AnvilCost import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil import xyz.alexcrea.cuanvil.util.config.LoreEditType import java.util.* -import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicReference object AnvilLoreEditUtil { @@ -31,7 +31,7 @@ object AnvilLoreEditUtil { player: Permissible, first: ItemStack, book: BookMeta, - xpCost: AtomicInteger + cost: AnvilCost ): ItemStack? { if (!hasLoreEditByBookPermission(player)) return null @@ -53,14 +53,14 @@ object AnvilLoreEditUtil { if (result == first) return null // Handle xp - xpCost.addAndGet(colorCost) // Cost of using color - xpCost.addAndGet(outLines.size * LoreEditType.APPEND_BOOK.perLineCost) // per line cost - xpCost.addAndGet(baseEditLoreXpCost(first, result, LoreEditType.APPEND_BOOK)) // Fixed cost and work penalty + cost.lore = colorCost // Cost of using color + cost.lore += outLines.size * LoreEditType.APPEND_BOOK.perLineCost // per line cost + baseEditLoreXpCost(cost, first, result, LoreEditType.APPEND_BOOK) // Fixed cost and work penalty return result } - fun handleLoreRemoveByBook(player: Permissible, first: ItemStack, xpCost: AtomicInteger): ItemStack? { + fun handleLoreRemoveByBook(player: Permissible, first: ItemStack, cost: AnvilCost): ItemStack? { if (!hasLoreEditByBookPermission(player)) return null // remove lore @@ -78,9 +78,9 @@ object AnvilLoreEditUtil { if (result == first) return null // Handle xp - xpCost.addAndGet(uncolorCost) - xpCost.addAndGet(currentLore.size * LoreEditType.REMOVE_BOOK.perLineCost) - xpCost.addAndGet(baseEditLoreXpCost(first, result, LoreEditType.REMOVE_BOOK)) + cost.lore = uncolorCost + cost.lore+= currentLore.size * LoreEditType.REMOVE_BOOK.perLineCost + baseEditLoreXpCost(cost, first, result, LoreEditType.REMOVE_BOOK) return result } @@ -116,12 +116,12 @@ object AnvilLoreEditUtil { return null } - fun tryLoreEditByBook(player: HumanEntity, first: ItemStack, second: ItemStack, xpCost: AtomicInteger): ItemStack? { + fun tryLoreEditByBook(player: HumanEntity, first: ItemStack, second: ItemStack, cost: AnvilCost): ItemStack? { val isAppend = bookLoreEditIsAppend(first, second) ?: return null val meta = second.itemMeta as BookMeta - return if (isAppend) handleLoreAppendByBook(player, first, meta, xpCost) - else handleLoreRemoveByBook(player, first, xpCost) + return if (isAppend) handleLoreAppendByBook(player, first, meta, cost) + else handleLoreRemoveByBook(player, first, cost) } // Return true if appended, false if removed, null if neither @@ -147,7 +147,7 @@ object AnvilLoreEditUtil { player: Permissible, first: ItemStack, second: ItemStack, - xpCost: AtomicInteger + cost: AnvilCost ): ItemStack? { if (!hasLoreEditByPaperPermission(player)) return null @@ -175,13 +175,13 @@ object AnvilLoreEditUtil { if (result == first) return null // Handle xp - xpCost.addAndGet(colorCost) - xpCost.addAndGet(baseEditLoreXpCost(first, result, LoreEditType.APPEND_PAPER)) + cost.lore = colorCost + baseEditLoreXpCost(cost, first, result, LoreEditType.APPEND_PAPER) return result } - fun handleLoreRemoveByPaper(player: Permissible, first: ItemStack, xpCost: AtomicInteger): ItemStack? { + fun handleLoreRemoveByPaper(player: Permissible, first: ItemStack, cost: AnvilCost): ItemStack? { if (!hasLoreEditByPaperPermission(player)) return null // remove lore line @@ -213,8 +213,8 @@ object AnvilLoreEditUtil { val uncolorCost = uncolorLine(player, line, LoreEditType.REMOVE_PAPER) // Handle other xp - xpCost.addAndGet(uncolorCost) - xpCost.addAndGet(baseEditLoreXpCost(first, result, LoreEditType.REMOVE_PAPER)) + cost.lore = uncolorCost + baseEditLoreXpCost(cost, first, result, LoreEditType.REMOVE_PAPER) return result } @@ -223,23 +223,23 @@ object AnvilLoreEditUtil { player: HumanEntity, first: ItemStack, second: ItemStack, - xpCost: AtomicInteger + cost: AnvilCost ): ItemStack? { val isAppend = paperLoreEditIsAppend(first, second) ?: return null - return if (isAppend) handleLoreAppendByPaper(player, first, second, xpCost) - else handleLoreRemoveByPaper(player, first, xpCost) + return if (isAppend) handleLoreAppendByPaper(player, first, second, cost) + else handleLoreRemoveByPaper(player, first, cost) } private fun baseEditLoreXpCost( + cost: AnvilCost, first: ItemStack, result: ItemStack, editType: LoreEditType - ): Int { - var xpCost = editType.fixedCost + ) { + cost.lore+= editType.fixedCost - xpCost += AnvilXpUtil.calculatePenalty(first, null, result, editType.useType) - return xpCost + cost.penalty = AnvilXpUtil.calculatePenalty(first, null, result, editType.useType) } fun colorPermission(player: Permissible, editType: LoreEditType): AnvilColorUtil.ColorPermissions { diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt index f60581f..5acc9b8 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt @@ -23,16 +23,55 @@ object AnvilXpUtil { const val EXCLUSIVE_PENALTY_PREFIX = "repair_cost" + class AnvilCost { + private val isAlone: Boolean + + var generic = 0 + var enchantment = 0 + var repair = 0 + var rename = 0 + var lore = 0 + var penalty = 0 + var recipe = 0 + + fun sum(): Int { + return generic + enchantment + repair + rename + lore + penalty + recipe + } + + constructor(generic: Int) { + this.generic = generic + isAlone = true + } + constructor() { + isAlone = false + } + } + + /** + * Display the required cost (either as xp or as ) + */ + fun setAnvilInvCost( + inventory: AnvilInventory, + view: InventoryView, + player: HumanEntity, + cost: AnvilCost, + ignoreRules: Boolean = false + ) { + // TODO check require money or xp cost & display appropriately + setAnvilInvXp(inventory, view, player, cost.sum(), ignoreRules) + } + /** * Display xp needed for the work on the anvil inventory */ - fun setAnvilInvXp( + private fun setAnvilInvXp( inventory: AnvilInventory, view: InventoryView, player: HumanEntity, anvilCost: Int, ignoreRules: Boolean = false ) { + // Test repair cost limit val finalAnvilCost = if ( !ignoreRules && @@ -78,7 +117,6 @@ object AnvilXpUtil { } player.updateInventory() - } } From 2c3e43cb846bd4fa60c2714c62352671f0deb24e Mon Sep 17 00:00:00 2001 From: alexcrea Date: Sun, 24 May 2026 11:55:37 +0200 Subject: [PATCH 158/207] moved to vault unlocked --- build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 4effecb..36cc2d6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -47,7 +47,7 @@ repositories { } // For vault unlocked - maven { url = uri("https://jitpack.io") } + maven { url = uri("https://repo.codemc.io/repository/creatorfromhell/") } } val reobfNMS = providers.gradleProperty("subprojects.reobfnms") @@ -107,7 +107,7 @@ dependencies { compileOnly("dev.lone:api-itemsadder:4.0.10") // Vault api - compileOnly("com.github.MilkBowl:VaultAPI:1.7") + compileOnly("net.milkbowl.vault:VaultUnlockedAPI:2.16") // Include nms implementation(project(":nms:nms-common")) From 856c1e08bd3199ba31a663871f0cee6314a8b82e Mon Sep 17 00:00:00 2001 From: alexcrea Date: Sun, 24 May 2026 20:47:26 +0200 Subject: [PATCH 159/207] add monetary config and generic progress --- defaultconfigs/1.18/config.yml | 19 ++++++++++ defaultconfigs/1.21.11/config.yml | 19 ++++++++++ defaultconfigs/1.21.9/config.yml | 19 ++++++++++ defaultconfigs/1.21/config.yml | 19 ++++++++++ src/main/kotlin/io/delilaheve/CustomAnvil.kt | 2 +- .../io/delilaheve/util/ConfigOptions.kt | 33 ++++++++++++++++ .../cuanvil/dependency/DependencyManager.kt | 4 +- .../dependency/econmy/EconomyManager.kt | 31 --------------- .../dependency/economy/EconomyManager.kt | 27 +++++++++++++ .../economy/UnlockedEconomyManager.kt | 35 +++++++++++++++++ .../dependency/economy/VaultEconomyManager.kt | 19 ++++++++++ .../cuanvil/listener/PrepareAnvilListener.kt | 38 +++++-------------- .../cuanvil/util/AnvilLoreEditUtil.kt | 2 +- .../xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt | 19 ++++------ src/main/resources/config.yml | 19 ++++++++++ 15 files changed, 230 insertions(+), 75 deletions(-) delete mode 100644 src/main/kotlin/xyz/alexcrea/cuanvil/dependency/econmy/EconomyManager.kt create mode 100644 src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/EconomyManager.kt create mode 100644 src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/UnlockedEconomyManager.kt create mode 100644 src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/VaultEconomyManager.kt diff --git a/defaultconfigs/1.18/config.yml b/defaultconfigs/1.18/config.yml index 12322e9..3edea26 100644 --- a/defaultconfigs/1.18/config.yml +++ b/defaultconfigs/1.18/config.yml @@ -430,6 +430,25 @@ lore_edit: allow_hexadecimal_color: false allow_minimessage: true +# Allow to replace the xp cost by a monetary cost +# If enabled it will not be bound to the experience level limits +monetary_cost: + enabled: false + # If using vault unlocked this allow to specify what currency should be used for anvil usage + # default being the default currency + currency: default + # multiply the anvil cost by a value to allow to have price a big bigger than like 40 + multipliers: + # global multipliers. all usage type will be multiplied by this value + global: 1.0 + # usage specific type. it will only apply for specific xp "reason" + enchantment: 1.0 # related to enchantments level + repair: 1.0 # for repairing via unit repair (per unit) + rename: 1.0 # for renaming the item + lore_edit: 1.0 # for changing the lore of the item (only if lore edit is enabled) + work_penalty: 1.0 # for work penalty (aka use penalty) + recipe: 1.0 # for custom anvil recipe cost + # Whether to show debug logging debug_log: false diff --git a/defaultconfigs/1.21.11/config.yml b/defaultconfigs/1.21.11/config.yml index 39c8504..53ffabd 100644 --- a/defaultconfigs/1.21.11/config.yml +++ b/defaultconfigs/1.21.11/config.yml @@ -450,6 +450,25 @@ lore_edit: allow_hexadecimal_color: false allow_minimessage: true +# Allow to replace the xp cost by a monetary cost +# If enabled it will not be bound to the experience level limits +monetary_cost: + enabled: false + # If using vault unlocked this allow to specify what currency should be used for anvil usage + # default being the default currency + currency: default + # multiply the anvil cost by a value to allow to have price a big bigger than like 40 + multipliers: + # global multipliers. all usage type will be multiplied by this value + global: 1.0 + # usage specific type. it will only apply for specific xp "reason" + enchantment: 1.0 # related to enchantments level + repair: 1.0 # for repairing via unit repair (per unit) + rename: 1.0 # for renaming the item + lore_edit: 1.0 # for changing the lore of the item (only if lore edit is enabled) + work_penalty: 1.0 # for work penalty (aka use penalty) + recipe: 1.0 # for custom anvil recipe cost + # Whether to show debug logging debug_log: false diff --git a/defaultconfigs/1.21.9/config.yml b/defaultconfigs/1.21.9/config.yml index 84eccba..40e558d 100644 --- a/defaultconfigs/1.21.9/config.yml +++ b/defaultconfigs/1.21.9/config.yml @@ -442,6 +442,25 @@ lore_edit: allow_hexadecimal_color: false allow_minimessage: true +# Allow to replace the xp cost by a monetary cost +# If enabled it will not be bound to the experience level limits +monetary_cost: + enabled: false + # If using vault unlocked this allow to specify what currency should be used for anvil usage + # default being the default currency + currency: default + # multiply the anvil cost by a value to allow to have price a big bigger than like 40 + multipliers: + # global multipliers. all usage type will be multiplied by this value + global: 1.0 + # usage specific type. it will only apply for specific xp "reason" + enchantment: 1.0 # related to enchantments level + repair: 1.0 # for repairing via unit repair (per unit) + rename: 1.0 # for renaming the item + lore_edit: 1.0 # for changing the lore of the item (only if lore edit is enabled) + work_penalty: 1.0 # for work penalty (aka use penalty) + recipe: 1.0 # for custom anvil recipe cost + # Whether to show debug logging debug_log: false diff --git a/defaultconfigs/1.21/config.yml b/defaultconfigs/1.21/config.yml index df62e6a..87372a5 100644 --- a/defaultconfigs/1.21/config.yml +++ b/defaultconfigs/1.21/config.yml @@ -430,6 +430,25 @@ lore_edit: allow_hexadecimal_color: false allow_minimessage: true +# Allow to replace the xp cost by a monetary cost +# If enabled it will not be bound to the experience level limits +monetary_cost: + enabled: false + # If using vault unlocked this allow to specify what currency should be used for anvil usage + # default being the default currency + currency: default + # multiply the anvil cost by a value to allow to have price a big bigger than like 40 + multipliers: + # global multipliers. all usage type will be multiplied by this value + global: 1.0 + # usage specific type. it will only apply for specific xp "reason" + enchantment: 1.0 # related to enchantments level + repair: 1.0 # for repairing via unit repair (per unit) + rename: 1.0 # for renaming the item + lore_edit: 1.0 # for changing the lore of the item (only if lore edit is enabled) + work_penalty: 1.0 # for work penalty (aka use penalty) + recipe: 1.0 # for custom anvil recipe cost + # Whether to show debug logging debug_log: false diff --git a/src/main/kotlin/io/delilaheve/CustomAnvil.kt b/src/main/kotlin/io/delilaheve/CustomAnvil.kt index 9548450..7f05fc6 100644 --- a/src/main/kotlin/io/delilaheve/CustomAnvil.kt +++ b/src/main/kotlin/io/delilaheve/CustomAnvil.kt @@ -12,7 +12,7 @@ import xyz.alexcrea.cuanvil.command.ReloadExecutor import xyz.alexcrea.cuanvil.config.ConfigHolder import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.dependency.MinecraftVersionUtil -import xyz.alexcrea.cuanvil.dependency.econmy.EconomyManager +import xyz.alexcrea.cuanvil.dependency.economy.EconomyManager import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil import xyz.alexcrea.cuanvil.enchant.CAEnchantmentRegistry import xyz.alexcrea.cuanvil.gui.config.MainConfigGui diff --git a/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt b/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt index 46f79bc..657fa38 100644 --- a/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt +++ b/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt @@ -72,6 +72,11 @@ object ConfigOptions { const val IMMUTABLE_ENCHANTMENT_LIST = "immutable_enchantments" + // Monetary configs + const val MONETARY_USAGE_ROOT = "monetary_cost" + const val SHOULD_USE_MONEY = "$MONETARY_USAGE_ROOT.enabled" + const val MONEY_CURRENCY = "$MONETARY_USAGE_ROOT.currency" + const val MONETARY_MULTIPLIER_ROOT = "$MONETARY_USAGE_ROOT.multipliers" // Keys for specific enchantment values private const val KEY_BOOK = "book" @@ -110,6 +115,11 @@ object ConfigOptions { const val DEFAULT_PER_COLOR_CODE_PERMISSION = false + // Monetary configs + const val DEFAULT_SHOULD_USE_MONEY = false + const val DEFAULT_MONEY_CURRENCY = "default" + const val DEFAULT_MONEY_MULTIPLIER = 1.0 + // Debug flag private const val DEFAULT_DEBUG_LOG = false private const val DEFAULT_VERBOSE_DEBUG_LOG = false @@ -625,4 +635,27 @@ object ConfigOptions { return false } + /* + * Monetary configs + */ + val shouldUseMoney: Boolean + get() { + return ConfigHolder.DEFAULT_CONFIG + .config + .getBoolean(SHOULD_USE_MONEY, DEFAULT_SHOULD_USE_MONEY) + } + + val usedCurrency: String + get() { + return ConfigHolder.DEFAULT_CONFIG + .config + .getString(MONEY_CURRENCY, DEFAULT_MONEY_CURRENCY)!! + } + + fun getMonetaryMultiplier(type: String): Double { + return ConfigHolder.DEFAULT_CONFIG + .config + .getDouble("$MONETARY_MULTIPLIER_ROOT.$type", DEFAULT_MONEY_MULTIPLIER) + } + } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt index 4e59adc..69b0237 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt @@ -237,11 +237,11 @@ object DependencyManager { result: ItemStack, useType: AnvilUseType, cost: AnvilXpUtil.AnvilCost - ): CATreatAnvilResultEvent? { + ): ItemStack? { val treatEvent = CATreatAnvilResultEvent(event, useType, result, cost) try { unsafeTryTreatAnvilResult(treatEvent) - return treatEvent + return treatEvent.result } catch (e: Exception) { logExceptionAndClear(event.view.player, event.inventory, e) return null diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/econmy/EconomyManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/econmy/EconomyManager.kt deleted file mode 100644 index d3b8c21..0000000 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/econmy/EconomyManager.kt +++ /dev/null @@ -1,31 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.econmy - -import net.milkbowl.vault.economy.Economy -import org.bukkit.OfflinePlayer -import org.bukkit.plugin.Plugin -import org.bukkit.plugin.RegisteredServiceProvider - -object EconomyManager { - - private var economy: Economy? = null - - fun setupEconomy(plugin: Plugin) { - if (economy != null) return - - if (plugin.server.pluginManager.getPlugin("Vault") == null) - return - - val rsp: RegisteredServiceProvider? = - plugin.server.servicesManager.getRegistration(Economy::class.java) - if (rsp == null) return - - economy = rsp.getProvider() - } - - fun has(player: OfflinePlayer, amount: Double): Boolean { - return economy?.has(player, amount) == true - } - - - -} diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/EconomyManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/EconomyManager.kt new file mode 100644 index 0000000..0777162 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/EconomyManager.kt @@ -0,0 +1,27 @@ +package xyz.alexcrea.cuanvil.dependency.economy + +import org.bukkit.plugin.Plugin + +interface EconomyManager { + + companion object { + var economy: EconomyManager? = null + + fun setupEconomy(plugin: Plugin) { + if (plugin.server.pluginManager.getPlugin("Vault") == null) + return + if(UnlockedEconomyManager.unlockedAvailable()) + economy = UnlockedEconomyManager(plugin) + + if(economy == null || !economy!!.initialized()) + economy = VaultEconomyManager(plugin) + } + + } + + fun initialized(): Boolean + + + + +} diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/UnlockedEconomyManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/UnlockedEconomyManager.kt new file mode 100644 index 0000000..303c559 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/UnlockedEconomyManager.kt @@ -0,0 +1,35 @@ +package xyz.alexcrea.cuanvil.dependency.economy + +import net.milkbowl.vault2.economy.Economy +import org.bukkit.plugin.Plugin + +class UnlockedEconomyManager: EconomyManager { + + val plugin: String + val economy: Economy? + + companion object { + fun unlockedAvailable(): Boolean { + try { + Class.forName("net.milkbowl.vault2.economy.Economy") + return true + } catch (_: ClassNotFoundException) { + return false + } + } + } + + constructor(plugin: Plugin) { + this.plugin = plugin.name + + val rsp = plugin.server.servicesManager.getRegistration(Economy::class.java) + economy = rsp?.getProvider() + } + + override fun initialized(): Boolean { + return economy != null + } + + + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/VaultEconomyManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/VaultEconomyManager.kt new file mode 100644 index 0000000..50837bf --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/VaultEconomyManager.kt @@ -0,0 +1,19 @@ +package xyz.alexcrea.cuanvil.dependency.economy + +import net.milkbowl.vault.economy.Economy +import org.bukkit.plugin.Plugin +class VaultEconomyManager : EconomyManager { + + val economy: Economy? + + constructor(plugin: Plugin) { + val rsp = plugin.server.servicesManager.getRegistration(Economy::class.java) + economy = rsp?.getProvider() + } + + override fun initialized(): Boolean { + return economy != null + } + + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt index bf8d3dd..00623d5 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt @@ -202,12 +202,7 @@ class PrepareAnvilListener : Listener { cost.recipe = if (recipe.removeExactLinearXp) AnvilXpUtil.calculateMinimumLevelForXp(xpCost) else AnvilXpUtil.calculateLevelForXp(xpCost) - val finalResult = DependencyManager.tryTreatAnvilResult(event, resultItem, AnvilUseType.CUSTOM_CRAFT, cost) - if (finalResult == null) return false - - event.result = finalResult.result - if (finalResult.result.isAir) return false - + event.result = DependencyManager.tryTreatAnvilResult(event, resultItem, AnvilUseType.CUSTOM_CRAFT, cost) AnvilXpUtil.setAnvilInvCost(inventory, event.view, player, cost, true) return true } @@ -227,14 +222,9 @@ class PrepareAnvilListener : Listener { return } - cost.penalty = AnvilXpUtil.calculatePenalty(first, null, resultItem, AnvilUseType.RENAME_ONLY) - - val finalResult = DependencyManager.tryTreatAnvilResult(event, resultItem, AnvilUseType.RENAME_ONLY, cost) - if (finalResult == null) return - - event.result = finalResult.result - if (finalResult.result.isAir) return + cost.workPenalty = AnvilXpUtil.calculatePenalty(first, null, resultItem, AnvilUseType.RENAME_ONLY) + event.result = DependencyManager.tryTreatAnvilResult(event, resultItem, AnvilUseType.RENAME_ONLY, cost) AnvilXpUtil.setAnvilInvCost(inventory, event.view, player, cost) } @@ -296,7 +286,7 @@ class PrepareAnvilListener : Listener { if(hasChanged){ resultItem.setEnchantmentsUnsafe(newEnchants) // Calculate enchantment cost - cost.enchantment = AnvilXpUtil.getRightValues(second, resultItem) + AnvilXpUtil.getRightValues(second, resultItem, cost) } // Calculate repair cost @@ -309,22 +299,17 @@ class PrepareAnvilListener : Listener { // Test/stop if nothing changed. if (!hasChanged) { - CustomAnvil.log("Mergable with second, But input is same as output") + CustomAnvil.log("Mergeable with second, But input is same as output") event.result = null return } // As calculatePenalty edit result, we need to calculate penalty after checking equality - cost.penalty = AnvilXpUtil.calculatePenalty(first, second, resultItem, AnvilUseType.MERGE) + cost.workPenalty = AnvilXpUtil.calculatePenalty(first, second, resultItem, AnvilUseType.MERGE) // Calculate rename cost cost.rename = handleRename(resultItem, inventory, player) // Finally, we set result - val finalResult = DependencyManager.tryTreatAnvilResult(event, resultItem, AnvilUseType.MERGE, cost) - if (finalResult == null) return - - event.result = finalResult.result - if (finalResult.result.isAir) return - + event.result = DependencyManager.tryTreatAnvilResult(event, resultItem, AnvilUseType.MERGE, cost) AnvilXpUtil.setAnvilInvCost(inventory, event.view, player, cost) } @@ -356,7 +341,7 @@ class PrepareAnvilListener : Listener { cost.repair = repairAmount * ConfigOptions.unitRepairCost } // We do not care about right item penalty for unit repair - cost.penalty = AnvilXpUtil.calculatePenalty(first, null, resultItem, AnvilUseType.UNIT_REPAIR) + cost.workPenalty = AnvilXpUtil.calculatePenalty(first, null, resultItem, AnvilUseType.UNIT_REPAIR) // Test/stop if nothing changed. if (first == resultItem) { @@ -365,12 +350,7 @@ class PrepareAnvilListener : Listener { return true } - val finalResult = DependencyManager.tryTreatAnvilResult(event, resultItem, AnvilUseType.UNIT_REPAIR, cost) - if (finalResult == null) return false - - event.result = finalResult.result - if (finalResult.result.isAir) return false - + event.result = DependencyManager.tryTreatAnvilResult(event, resultItem, AnvilUseType.UNIT_REPAIR, cost) AnvilXpUtil.setAnvilInvCost(inventory, event.view, player, cost) return true } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt index 85a0ee8..16eca40 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt @@ -239,7 +239,7 @@ object AnvilLoreEditUtil { ) { cost.lore+= editType.fixedCost - cost.penalty = AnvilXpUtil.calculatePenalty(first, null, result, editType.useType) + cost.workPenalty = AnvilXpUtil.calculatePenalty(first, null, result, editType.useType) } fun colorPermission(player: Permissible, editType: LoreEditType): AnvilColorUtil.ColorPermissions { diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt index 5acc9b8..6565166 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt @@ -31,11 +31,12 @@ object AnvilXpUtil { var repair = 0 var rename = 0 var lore = 0 - var penalty = 0 + var illegalPenalty = 0 + var workPenalty = 0 var recipe = 0 fun sum(): Int { - return generic + enchantment + repair + rename + lore + penalty + recipe + return generic + enchantment + repair + rename + lore + illegalPenalty + workPenalty + recipe } constructor(generic: Int) { @@ -197,10 +198,8 @@ object AnvilXpUtil { * Function to calculate right enchantment values * it include enchantment placed on final item and conflicting enchantment */ - fun getRightValues(right: ItemStack, result: ItemStack): Int { + fun getRightValues(right: ItemStack, result: ItemStack, cost: AnvilCost) { // Calculate right value and illegal enchant penalty - var illegalPenalty = 0 - var rightValue = 0 val rightIsFormBook = right.isEnchantedBook() val resultEnchs = result.findEnchantments() @@ -218,7 +217,7 @@ object AnvilXpUtil { resultEnchsKeys.remove(enchantment.key) if (ConflictType.ENCHANTMENT_CONFLICT == conflictType) { - illegalPenalty += ConfigOptions.sacrificeIllegalCost + cost.illegalPenalty += ConfigOptions.sacrificeIllegalCost CustomAnvil.verboseLog("Big conflict. Adding illegal price penalty") } continue @@ -229,16 +228,14 @@ 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)") - rightValue += value + cost.enchantment += value } CustomAnvil.log( "Calculated right values: " + - "rightValue: $rightValue, " + - "illegalPenalty: $illegalPenalty" + "rightValue: ${cost.enchantment}, " + + "illegalPenalty: ${cost.illegalPenalty}" ) - - return rightValue + illegalPenalty } /** diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 49b5e5a..f5e2c5a 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -432,6 +432,25 @@ lore_edit: allow_hexadecimal_color: false allow_minimessage: true +# Allow to replace the xp cost by a monetary cost +# If enabled it will not be bound to the experience level limits +monetary_cost: + enabled: false + # If using vault unlocked this allow to specify what currency should be used for anvil usage + # default being the default currency + currency: default + # multiply the anvil cost by a value to allow to have price a big bigger than like 40 + multipliers: + # global multipliers. all usage type will be multiplied by this value + global: 1.0 + # usage specific type. it will only apply for specific xp "reason" + enchantment: 1.0 # related to enchantments level + repair: 1.0 # for repairing via unit repair (per unit) + rename: 1.0 # for renaming the item + lore_edit: 1.0 # for changing the lore of the item (only if lore edit is enabled) + work_penalty: 1.0 # for work penalty (aka use penalty) + recipe: 1.0 # for custom anvil recipe cost + # Whether to show debug logging debug_log: false From 1660250ee1e40f3bdbc33470611ff15571f5ccb4 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Sun, 24 May 2026 21:27:17 +0200 Subject: [PATCH 160/207] monetary dependency functions --- .../io/delilaheve/util/ConfigOptions.kt | 8 +++-- .../dependency/economy/EconomyManager.kt | 7 +++- .../economy/UnlockedEconomyManager.kt | 35 +++++++++++++++++++ .../dependency/economy/VaultEconomyManager.kt | 18 ++++++++++ 4 files changed, 64 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt b/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt index 657fa38..487bb87 100644 --- a/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt +++ b/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt @@ -7,6 +7,7 @@ import xyz.alexcrea.cuanvil.config.ConfigHolder import xyz.alexcrea.cuanvil.config.WorkPenaltyType import xyz.alexcrea.cuanvil.config.WorkPenaltyType.WorkPenaltyPart import xyz.alexcrea.cuanvil.dependency.DependencyManager +import xyz.alexcrea.cuanvil.dependency.economy.EconomyManager import xyz.alexcrea.cuanvil.enchant.CAEnchantment import xyz.alexcrea.cuanvil.util.AnvilUseType import java.util.* @@ -640,9 +641,10 @@ object ConfigOptions { */ val shouldUseMoney: Boolean get() { - return ConfigHolder.DEFAULT_CONFIG - .config - .getBoolean(SHOULD_USE_MONEY, DEFAULT_SHOULD_USE_MONEY) + return EconomyManager.economy?.initialized() == true && + ConfigHolder.DEFAULT_CONFIG + .config + .getBoolean(SHOULD_USE_MONEY, DEFAULT_SHOULD_USE_MONEY) } val usedCurrency: String diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/EconomyManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/EconomyManager.kt index 0777162..dc1038a 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/EconomyManager.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/EconomyManager.kt @@ -1,6 +1,8 @@ package xyz.alexcrea.cuanvil.dependency.economy +import org.bukkit.entity.Player import org.bukkit.plugin.Plugin +import java.math.BigDecimal interface EconomyManager { @@ -21,7 +23,10 @@ interface EconomyManager { fun initialized(): Boolean + // We assume "initialized" got checked before these function get called + fun has(player: Player, money: BigDecimal): Boolean + fun remove(player: Player, money: BigDecimal): Boolean - + fun format(money: BigDecimal): String; } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/UnlockedEconomyManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/UnlockedEconomyManager.kt index 303c559..9bea81c 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/UnlockedEconomyManager.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/UnlockedEconomyManager.kt @@ -1,7 +1,10 @@ package xyz.alexcrea.cuanvil.dependency.economy +import io.delilaheve.util.ConfigOptions import net.milkbowl.vault2.economy.Economy +import org.bukkit.entity.Player import org.bukkit.plugin.Plugin +import java.math.BigDecimal class UnlockedEconomyManager: EconomyManager { @@ -30,6 +33,38 @@ class UnlockedEconomyManager: EconomyManager { return economy != null } + private fun currency(): String { + val configured = ConfigOptions.usedCurrency + + return if ("default".equals(configured, true)) + economy!!.getDefaultCurrency(plugin) + else configured + } + + override fun has(player: Player, money: BigDecimal): Boolean { + if(money.signum() <= 0) return true + + return economy!!.has(plugin, + player.uniqueId, + player.world.name, + currency(), + money) + } + + override fun remove(player: Player, money: BigDecimal): Boolean { + if(money.signum() <= 0) return true + + return economy!!.withdraw(plugin, + player.uniqueId, + player.world.name, + currency(), + money) + .transactionSuccess() + } + + override fun format(money: BigDecimal): String { + return economy!!.format(plugin, money, currency()) + } } \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/VaultEconomyManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/VaultEconomyManager.kt index 50837bf..79a8036 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/VaultEconomyManager.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/VaultEconomyManager.kt @@ -1,7 +1,10 @@ package xyz.alexcrea.cuanvil.dependency.economy import net.milkbowl.vault.economy.Economy +import org.bukkit.entity.Player import org.bukkit.plugin.Plugin +import java.math.BigDecimal + class VaultEconomyManager : EconomyManager { val economy: Economy? @@ -15,5 +18,20 @@ class VaultEconomyManager : EconomyManager { return economy != null } + override fun has(player: Player, money: BigDecimal): Boolean { + if(money.signum() <= 0) return true + + return economy!!.has(player, money.toDouble()) + } + + override fun remove(player: Player, money: BigDecimal): Boolean { + if(money.signum() <= 0) return true + + return economy!!.withdrawPlayer(player, money.toDouble()).transactionSuccess() + } + + override fun format(money: BigDecimal): String { + return economy!!.format(money.toDouble()) + } } \ No newline at end of file From ac9f4921258b8a952be913b7066b71bb79c550e1 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 25 May 2026 17:50:02 +0200 Subject: [PATCH 161/207] monetary minimum version & rename impl --- defaultconfigs/1.18/config.yml | 2 ++ defaultconfigs/1.21.11/config.yml | 2 ++ defaultconfigs/1.21.9/config.yml | 2 ++ defaultconfigs/1.21/config.yml | 2 ++ .../xyz/alexcrea/cuanvil/util/RenameAnvilUtil.kt | 12 ++++++++++++ .../kotlin/io/delilaheve/util/ConfigOptions.kt | 15 +++++++++++---- src/main/resources/config.yml | 3 +++ 7 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 nms/v1_21R1/src/main/kotlin/xyz/alexcrea/cuanvil/util/RenameAnvilUtil.kt diff --git a/defaultconfigs/1.18/config.yml b/defaultconfigs/1.18/config.yml index 3edea26..1a1663a 100644 --- a/defaultconfigs/1.18/config.yml +++ b/defaultconfigs/1.18/config.yml @@ -432,6 +432,8 @@ lore_edit: # Allow to replace the xp cost by a monetary cost # If enabled it will not be bound to the experience level limits +# +# This feature can only be enabled starting with 1.21 monetary_cost: enabled: false # If using vault unlocked this allow to specify what currency should be used for anvil usage diff --git a/defaultconfigs/1.21.11/config.yml b/defaultconfigs/1.21.11/config.yml index 53ffabd..2daa373 100644 --- a/defaultconfigs/1.21.11/config.yml +++ b/defaultconfigs/1.21.11/config.yml @@ -452,6 +452,8 @@ lore_edit: # Allow to replace the xp cost by a monetary cost # If enabled it will not be bound to the experience level limits +# +# This feature can only be enabled starting with 1.21 monetary_cost: enabled: false # If using vault unlocked this allow to specify what currency should be used for anvil usage diff --git a/defaultconfigs/1.21.9/config.yml b/defaultconfigs/1.21.9/config.yml index 40e558d..8532881 100644 --- a/defaultconfigs/1.21.9/config.yml +++ b/defaultconfigs/1.21.9/config.yml @@ -444,6 +444,8 @@ lore_edit: # Allow to replace the xp cost by a monetary cost # If enabled it will not be bound to the experience level limits +# +# This feature can only be enabled starting with 1.21 monetary_cost: enabled: false # If using vault unlocked this allow to specify what currency should be used for anvil usage diff --git a/defaultconfigs/1.21/config.yml b/defaultconfigs/1.21/config.yml index 87372a5..25e47dc 100644 --- a/defaultconfigs/1.21/config.yml +++ b/defaultconfigs/1.21/config.yml @@ -432,6 +432,8 @@ lore_edit: # Allow to replace the xp cost by a monetary cost # If enabled it will not be bound to the experience level limits +# +# This feature can only be enabled starting with 1.21 monetary_cost: enabled: false # If using vault unlocked this allow to specify what currency should be used for anvil usage diff --git a/nms/v1_21R1/src/main/kotlin/xyz/alexcrea/cuanvil/util/RenameAnvilUtil.kt b/nms/v1_21R1/src/main/kotlin/xyz/alexcrea/cuanvil/util/RenameAnvilUtil.kt new file mode 100644 index 0000000..279016b --- /dev/null +++ b/nms/v1_21R1/src/main/kotlin/xyz/alexcrea/cuanvil/util/RenameAnvilUtil.kt @@ -0,0 +1,12 @@ +package xyz.alexcrea.cuanvil.util + +import org.bukkit.inventory.InventoryView + +// TODO yet another cleanup to do on legacy removal branch +object RenameAnvilUtil { + + fun rename(view: InventoryView, name: String) { + view.title = name + } + +} \ No newline at end of file diff --git a/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt b/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt index 487bb87..ddaa348 100644 --- a/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt +++ b/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt @@ -9,7 +9,10 @@ import xyz.alexcrea.cuanvil.config.WorkPenaltyType.WorkPenaltyPart import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.dependency.economy.EconomyManager import xyz.alexcrea.cuanvil.enchant.CAEnchantment +import xyz.alexcrea.cuanvil.update.UpdateUtils +import xyz.alexcrea.cuanvil.update.Version import xyz.alexcrea.cuanvil.util.AnvilUseType +import java.math.BigDecimal import java.util.* /** @@ -87,6 +90,9 @@ object ConfigOptions { const val DEBUG_LOGGING = "debug_log" const val VERBOSE_DEBUG_LOGGING = "debug_log_verbose" + // Minimum versions + val MINIMUM_MONETARY_COST_VER = Version(21, 0, 0) + // ---------------------- // Default config values // ---------------------- @@ -637,11 +643,12 @@ object ConfigOptions { } /* - * Monetary configs + * Monetary configs (only for 1.21+) */ val shouldUseMoney: Boolean get() { return EconomyManager.economy?.initialized() == true && + UpdateUtils.currentMinecraftVersion().greaterEqual(MINIMUM_MONETARY_COST_VER) && ConfigHolder.DEFAULT_CONFIG .config .getBoolean(SHOULD_USE_MONEY, DEFAULT_SHOULD_USE_MONEY) @@ -654,10 +661,10 @@ object ConfigOptions { .getString(MONEY_CURRENCY, DEFAULT_MONEY_CURRENCY)!! } - fun getMonetaryMultiplier(type: String): Double { - return ConfigHolder.DEFAULT_CONFIG + fun getMonetaryMultiplier(type: String): BigDecimal { + return BigDecimal(ConfigHolder.DEFAULT_CONFIG .config - .getDouble("$MONETARY_MULTIPLIER_ROOT.$type", DEFAULT_MONEY_MULTIPLIER) + .getDouble("$MONETARY_MULTIPLIER_ROOT.$type", DEFAULT_MONEY_MULTIPLIER)) } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index f5e2c5a..5ba61ec 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -434,6 +434,8 @@ lore_edit: # Allow to replace the xp cost by a monetary cost # If enabled it will not be bound to the experience level limits +# +# This feature can only be enabled starting with 1.21 monetary_cost: enabled: false # If using vault unlocked this allow to specify what currency should be used for anvil usage @@ -448,6 +450,7 @@ monetary_cost: repair: 1.0 # for repairing via unit repair (per unit) rename: 1.0 # for renaming the item lore_edit: 1.0 # for changing the lore of the item (only if lore edit is enabled) + illegal_penalty: 1.0 # for trying to combine illegal enchantment work_penalty: 1.0 # for work penalty (aka use penalty) recipe: 1.0 # for custom anvil recipe cost From 1b3447d0416fff9994fff3521da88ebbefc36087 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 25 May 2026 18:05:29 +0200 Subject: [PATCH 162/207] monetary cost display --- .../{RenameAnvilUtil.kt => AnvilTitleUtil.kt} | 2 +- .../io/delilaheve/util/ConfigOptions.kt | 2 +- .../cuanvil/dependency/DependencyManager.kt | 5 +- .../dependency/economy/EconomyManager.kt | 4 +- .../economy/UnlockedEconomyManager.kt | 18 +++-- .../dependency/economy/VaultEconomyManager.kt | 4 +- .../plugins/DisenchantmentDependency.kt | 4 +- .../dependency/plugins/HavenBagsDependency.kt | 4 +- .../cuanvil/listener/PrepareAnvilListener.kt | 14 ++-- .../xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt | 69 +++++++++++++++++-- 10 files changed, 97 insertions(+), 29 deletions(-) rename nms/v1_21R1/src/main/kotlin/xyz/alexcrea/cuanvil/util/{RenameAnvilUtil.kt => AnvilTitleUtil.kt} (89%) diff --git a/nms/v1_21R1/src/main/kotlin/xyz/alexcrea/cuanvil/util/RenameAnvilUtil.kt b/nms/v1_21R1/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilTitleUtil.kt similarity index 89% rename from nms/v1_21R1/src/main/kotlin/xyz/alexcrea/cuanvil/util/RenameAnvilUtil.kt rename to nms/v1_21R1/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilTitleUtil.kt index 279016b..805b150 100644 --- a/nms/v1_21R1/src/main/kotlin/xyz/alexcrea/cuanvil/util/RenameAnvilUtil.kt +++ b/nms/v1_21R1/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilTitleUtil.kt @@ -3,7 +3,7 @@ package xyz.alexcrea.cuanvil.util import org.bukkit.inventory.InventoryView // TODO yet another cleanup to do on legacy removal branch -object RenameAnvilUtil { +object AnvilTitleUtil { fun rename(view: InventoryView, name: String) { view.title = name diff --git a/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt b/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt index ddaa348..9ee7317 100644 --- a/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt +++ b/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt @@ -91,7 +91,7 @@ object ConfigOptions { const val VERBOSE_DEBUG_LOGGING = "debug_log_verbose" // Minimum versions - val MINIMUM_MONETARY_COST_VER = Version(21, 0, 0) + val MINIMUM_MONETARY_COST_VER = Version(1, 21, 0) // ---------------------- // Default config values diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt index 69b0237..418dbf1 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt @@ -7,6 +7,7 @@ import org.bukkit.Bukkit import org.bukkit.ChatColor import org.bukkit.command.CommandSender import org.bukkit.entity.HumanEntity +import org.bukkit.entity.Player import org.bukkit.event.inventory.InventoryClickEvent import org.bukkit.event.inventory.PrepareAnvilEvent import org.bukkit.inventory.AnvilInventory @@ -199,7 +200,7 @@ object DependencyManager { } // Return true if should bypass (either by a dependency or error) - fun tryEventPreAnvilBypass(event: PrepareAnvilEvent, player: HumanEntity): Boolean { + fun tryEventPreAnvilBypass(event: PrepareAnvilEvent, player: Player): Boolean { try { return unsafeTryEventPreAnvilBypass(event, player) } catch (e: Exception) { @@ -208,7 +209,7 @@ object DependencyManager { } } - private fun unsafeTryEventPreAnvilBypass(event: PrepareAnvilEvent, player: HumanEntity): Boolean { + private fun unsafeTryEventPreAnvilBypass(event: PrepareAnvilEvent, player: Player): Boolean { // Run the event val bypassEvent = CAPreAnvilBypassEvent(event) Bukkit.getPluginManager().callEvent(bypassEvent) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/EconomyManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/EconomyManager.kt index dc1038a..67a6c1e 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/EconomyManager.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/EconomyManager.kt @@ -12,10 +12,10 @@ interface EconomyManager { fun setupEconomy(plugin: Plugin) { if (plugin.server.pluginManager.getPlugin("Vault") == null) return - if(UnlockedEconomyManager.unlockedAvailable()) + if (UnlockedEconomyManager.unlockedAvailable()) economy = UnlockedEconomyManager(plugin) - if(economy == null || !economy!!.initialized()) + if (economy == null || !economy!!.initialized()) economy = VaultEconomyManager(plugin) } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/UnlockedEconomyManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/UnlockedEconomyManager.kt index 9bea81c..da66f30 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/UnlockedEconomyManager.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/UnlockedEconomyManager.kt @@ -6,7 +6,7 @@ import org.bukkit.entity.Player import org.bukkit.plugin.Plugin import java.math.BigDecimal -class UnlockedEconomyManager: EconomyManager { +class UnlockedEconomyManager : EconomyManager { val plugin: String val economy: Economy? @@ -42,23 +42,27 @@ class UnlockedEconomyManager: EconomyManager { } override fun has(player: Player, money: BigDecimal): Boolean { - if(money.signum() <= 0) return true + if (money.signum() <= 0) return true - return economy!!.has(plugin, + return economy!!.has( + plugin, player.uniqueId, player.world.name, currency(), - money) + money + ) } override fun remove(player: Player, money: BigDecimal): Boolean { - if(money.signum() <= 0) return true + if (money.signum() <= 0) return true - return economy!!.withdraw(plugin, + return economy!!.withdraw( + plugin, player.uniqueId, player.world.name, currency(), - money) + money + ) .transactionSuccess() } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/VaultEconomyManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/VaultEconomyManager.kt index 79a8036..058485e 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/VaultEconomyManager.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/VaultEconomyManager.kt @@ -19,13 +19,13 @@ class VaultEconomyManager : EconomyManager { } override fun has(player: Player, money: BigDecimal): Boolean { - if(money.signum() <= 0) return true + if (money.signum() <= 0) return true return economy!!.has(player, money.toDouble()) } override fun remove(player: Player, money: BigDecimal): Boolean { - if(money.signum() <= 0) return true + if (money.signum() <= 0) return true return economy!!.withdrawPlayer(player, money.toDouble()).transactionSuccess() } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/DisenchantmentDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/DisenchantmentDependency.kt index 3dfba9c..7a7a5eb 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/DisenchantmentDependency.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/DisenchantmentDependency.kt @@ -8,7 +8,7 @@ import com.jankominek.disenchantment.events.ShatterEvent import com.jankominek.disenchantment.listeners.DisenchantClickListener import com.jankominek.disenchantment.listeners.ShatterClickListener import io.delilaheve.CustomAnvil -import org.bukkit.entity.HumanEntity +import org.bukkit.entity.Player import org.bukkit.event.Listener import org.bukkit.event.inventory.InventoryClickEvent import org.bukkit.event.inventory.PrepareAnvilEvent @@ -51,7 +51,7 @@ class DisenchantmentDependency { InventoryClickEvent.getHandlerList().unregister(listener) } - fun testPrepareAnvil(event: PrepareAnvilEvent, player: HumanEntity): Boolean { + fun testPrepareAnvil(event: PrepareAnvilEvent, player: Player): Boolean { val previousResult = event.result event.result = null diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/HavenBagsDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/HavenBagsDependency.kt index 6b5f9c4..b2e7ef4 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/HavenBagsDependency.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/HavenBagsDependency.kt @@ -1,7 +1,7 @@ package xyz.alexcrea.cuanvil.dependency.plugins import io.delilaheve.CustomAnvil -import org.bukkit.entity.HumanEntity +import org.bukkit.entity.Player import org.bukkit.event.inventory.InventoryClickEvent import org.bukkit.event.inventory.PrepareAnvilEvent import org.bukkit.inventory.AnvilInventory @@ -46,7 +46,7 @@ class HavenBagsDependency { } - fun testPrepareAnvil(event: PrepareAnvilEvent, player: HumanEntity): Boolean { + fun testPrepareAnvil(event: PrepareAnvilEvent, player: Player): Boolean { val previousResult = event.result event.result = null diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt index 00623d5..c71e82e 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt @@ -13,6 +13,7 @@ import io.delilaheve.util.ItemUtil.unitRepair import org.bukkit.ChatColor import org.bukkit.Material import org.bukkit.entity.HumanEntity +import org.bukkit.entity.Player import org.bukkit.event.EventHandler import org.bukkit.event.EventPriority import org.bukkit.event.Listener @@ -54,7 +55,8 @@ class PrepareAnvilListener : Listener { @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) fun anvilCombineCheck(event: PrepareAnvilEvent) { // Should find player - val player: HumanEntity = InventoryViewUtil.getInstance().getPlayer(event.view) + val player = InventoryViewUtil.getInstance().getPlayer(event.view) + if(player !is Player) return val inventory = event.inventory // Test if custom anvil is bypassed before immutability test @@ -183,7 +185,7 @@ class PrepareAnvilListener : Listener { // return true if a custom recipe exist with these ingredients private fun testCustomRecipe( event: PrepareAnvilEvent, inventory: AnvilInventory, - player: HumanEntity, + player: Player, first: ItemStack, second: ItemStack? ): Boolean { val recipe = CustomRecipeUtil.getCustomRecipe(first, second) @@ -209,7 +211,7 @@ class PrepareAnvilListener : Listener { private fun doRenaming( event: PrepareAnvilEvent, inventory: AnvilInventory, - player: HumanEntity, first: ItemStack + player: Player, first: ItemStack ) { val resultItem = DependencyManager.cloneItem(event, first) val cost = AnvilCost() @@ -274,7 +276,7 @@ class PrepareAnvilListener : Listener { private fun doMerge( event: PrepareAnvilEvent, inventory: AnvilInventory, - player: HumanEntity, + player: Player, first: ItemStack, second: ItemStack ) { val newEnchants = first.findEnchantments() @@ -327,7 +329,7 @@ class PrepareAnvilListener : Listener { // return true if there is a valid unit repair with these ingredients private fun testUnitRepair( - event: PrepareAnvilEvent, inventory: AnvilInventory, player: HumanEntity, + event: PrepareAnvilEvent, inventory: AnvilInventory, player: Player, first: ItemStack, second: ItemStack ): Boolean { val unitRepairAmount = first.getRepair(second) ?: return false @@ -356,7 +358,7 @@ class PrepareAnvilListener : Listener { } private fun testLoreEdit( - event: PrepareAnvilEvent, inventory: AnvilInventory, player: HumanEntity, + event: PrepareAnvilEvent, inventory: AnvilInventory, player: Player, first: ItemStack, second: ItemStack ): Boolean { val type = second.type diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt index 6565166..07f3d23 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt @@ -2,9 +2,12 @@ package xyz.alexcrea.cuanvil.util import io.delilaheve.CustomAnvil import io.delilaheve.util.ConfigOptions +import io.delilaheve.util.ConfigOptions.getMonetaryMultiplier as moneyMultiplier import io.delilaheve.util.EnchantmentUtil.enchantmentName import io.delilaheve.util.ItemUtil.findEnchantments import io.delilaheve.util.ItemUtil.isEnchantedBook +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.TextColor import org.bukkit.GameMode import org.bukkit.NamespacedKey import org.bukkit.entity.HumanEntity @@ -16,7 +19,9 @@ import org.bukkit.inventory.meta.Repairable import org.bukkit.persistence.PersistentDataType import xyz.alexcrea.cuanvil.config.ConfigHolder import xyz.alexcrea.cuanvil.dependency.DependencyManager +import xyz.alexcrea.cuanvil.dependency.economy.EconomyManager import xyz.alexcrea.cuanvil.group.ConflictType +import java.math.BigDecimal import kotlin.math.min object AnvilXpUtil { @@ -43,6 +48,7 @@ object AnvilXpUtil { this.generic = generic isAlone = true } + constructor() { isAlone = false } @@ -54,12 +60,14 @@ object AnvilXpUtil { fun setAnvilInvCost( inventory: AnvilInventory, view: InventoryView, - player: HumanEntity, + player: Player, cost: AnvilCost, ignoreRules: Boolean = false ) { - // TODO check require money or xp cost & display appropriately - setAnvilInvXp(inventory, view, player, cost.sum(), ignoreRules) + if (ConfigOptions.shouldUseMoney) + setAnvilPrice(inventory, view, player, cost) + else + setAnvilInvXp(inventory, view, player, cost.sum(), ignoreRules) } /** @@ -72,7 +80,7 @@ object AnvilXpUtil { anvilCost: Int, ignoreRules: Boolean = false ) { - + // Test repair cost limit val finalAnvilCost = if ( !ignoreRules && @@ -121,6 +129,59 @@ object AnvilXpUtil { } } + fun asMonetaryCost(cost: AnvilCost): BigDecimal { + // multiply by per use type multipliers + return BigDecimal(cost.generic) + .add(BigDecimal(cost.enchantment).multiply(moneyMultiplier("enchantment"))) + .add(BigDecimal(cost.repair).multiply(moneyMultiplier("repair"))) + .add(BigDecimal(cost.rename).multiply(moneyMultiplier("rename"))) + .add(BigDecimal(cost.lore).multiply(moneyMultiplier("lore_edit"))) + .add(BigDecimal(cost.enchantment).multiply(moneyMultiplier("enchantment"))) + .add(BigDecimal(cost.illegalPenalty).multiply(moneyMultiplier("work_penalty"))) + .add(BigDecimal(cost.workPenalty).multiply(moneyMultiplier("work_penalty"))) + .add(BigDecimal(cost.recipe).multiply(moneyMultiplier("recipe"))) + .multiply(moneyMultiplier("global")) + } + + /** + * Display monetary cost needed for the work on the anvil inventory + */ + private fun setAnvilPrice( + inventory: AnvilInventory, + view: InventoryView, + player: Player, + cost: AnvilCost, + ) { + val finalCost = asMonetaryCost(cost) + + val has = EconomyManager.economy!!.has(player, finalCost) + + val text = "Cost: " + (if(has) "§2" else "§4") + + EconomyManager.economy!!.format(finalCost) + AnvilTitleUtil.rename(view, text) + + clearAnvilXpCost(inventory, view, player) + } + + private fun clearAnvilXpCost( + inventory: AnvilInventory, + view: InventoryView, + player: HumanEntity, + ) { + // TODO for 2.x.x use anvil view & set directly there + inventory.repairCost = 0 + + // retry after a tick + DependencyManager.scheduler.scheduleOnEntity( + CustomAnvil.instance, player + ) { + inventory.repairCost = 0 + + if (player !is Player) return@scheduleOnEntity + player.updateInventory() + } + } + /** * Function to calculate work penalty of anvil work * Also change result work penalty if right item is not null From fb27ad2e552890095aa3ad734f9d59f6d600dd21 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Thu, 28 May 2026 20:11:02 +0200 Subject: [PATCH 163/207] avoid looping on same name --- .../xyz/alexcrea/cuanvil/dialog/AnvilRenameDialogImpl.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dialog/AnvilRenameDialogImpl.kt b/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dialog/AnvilRenameDialogImpl.kt index 603a215..fb5fa0c 100644 --- a/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dialog/AnvilRenameDialogImpl.kt +++ b/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dialog/AnvilRenameDialogImpl.kt @@ -109,13 +109,16 @@ class AnvilRenameDialogImpl( private fun setName(player: HumanEntity, view: CraftAnvilView, name: String?) { val menu = (containerField.get(view) as AnvilMenu) + val isSameName = menu.itemName == name menu.itemName = name if(name == null) lastNames.remove(player.uniqueId) else lastNames[player.uniqueId] = name - CraftEventFactory.callPrepareResultEvent(menu, 2); + + if(!isSameName) + CraftEventFactory.callPrepareResultEvent(menu, 2); } private fun nameFromItem(player: HumanEntity, item: ItemStack?): String? { From 171a8cad6dc6296902dea40767aaa391ab83a445 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Thu, 28 May 2026 20:21:40 +0200 Subject: [PATCH 164/207] monetary cost require dialog rename --- defaultconfigs/1.18/config.yml | 5 +++- defaultconfigs/1.21.11/config.yml | 5 +++- defaultconfigs/1.21.9/config.yml | 5 +++- defaultconfigs/1.21/config.yml | 5 +++- .../alexcrea/cuanvil/util/AnvilTitleUtil.kt | 2 ++ .../io/delilaheve/util/ConfigOptions.kt | 28 +++++++++++++------ .../cuanvil/listener/PrepareAnvilListener.kt | 21 ++++++-------- .../xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt | 10 +++++-- src/main/resources/config.yml | 5 +++- 9 files changed, 57 insertions(+), 29 deletions(-) rename nms/{v1_21R1 => nms-paper}/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilTitleUtil.kt (86%) diff --git a/defaultconfigs/1.18/config.yml b/defaultconfigs/1.18/config.yml index 1a1663a..fb70505 100644 --- a/defaultconfigs/1.18/config.yml +++ b/defaultconfigs/1.18/config.yml @@ -433,7 +433,10 @@ lore_edit: # Allow to replace the xp cost by a monetary cost # If enabled it will not be bound to the experience level limits # -# This feature can only be enabled starting with 1.21 +# It also requires to enable dialog rename (set "enable_dialog_rename: false" a bit higher) +# If dialog rename permission is enabled and player do not have the permission merge will fall back to vanilla xp cost +# +# As this feature require dialog rename, it can only be enabled starting with paper 1.21.6 monetary_cost: enabled: false # If using vault unlocked this allow to specify what currency should be used for anvil usage diff --git a/defaultconfigs/1.21.11/config.yml b/defaultconfigs/1.21.11/config.yml index 2daa373..b70798c 100644 --- a/defaultconfigs/1.21.11/config.yml +++ b/defaultconfigs/1.21.11/config.yml @@ -453,7 +453,10 @@ lore_edit: # Allow to replace the xp cost by a monetary cost # If enabled it will not be bound to the experience level limits # -# This feature can only be enabled starting with 1.21 +# It also requires to enable dialog rename (set "enable_dialog_rename: false" a bit higher) +# If dialog rename permission is enabled and player do not have the permission merge will fall back to vanilla xp cost +# +# As this feature require dialog rename, it can only be enabled starting with paper 1.21.6 monetary_cost: enabled: false # If using vault unlocked this allow to specify what currency should be used for anvil usage diff --git a/defaultconfigs/1.21.9/config.yml b/defaultconfigs/1.21.9/config.yml index 8532881..31bdb1f 100644 --- a/defaultconfigs/1.21.9/config.yml +++ b/defaultconfigs/1.21.9/config.yml @@ -445,7 +445,10 @@ lore_edit: # Allow to replace the xp cost by a monetary cost # If enabled it will not be bound to the experience level limits # -# This feature can only be enabled starting with 1.21 +# It also requires to enable dialog rename (set "enable_dialog_rename: false" a bit higher) +# If dialog rename permission is enabled and player do not have the permission merge will fall back to vanilla xp cost +# +# As this feature require dialog rename, it can only be enabled starting with paper 1.21.6 monetary_cost: enabled: false # If using vault unlocked this allow to specify what currency should be used for anvil usage diff --git a/defaultconfigs/1.21/config.yml b/defaultconfigs/1.21/config.yml index 25e47dc..1dafa54 100644 --- a/defaultconfigs/1.21/config.yml +++ b/defaultconfigs/1.21/config.yml @@ -433,7 +433,10 @@ lore_edit: # Allow to replace the xp cost by a monetary cost # If enabled it will not be bound to the experience level limits # -# This feature can only be enabled starting with 1.21 +# It also requires to enable dialog rename (set "enable_dialog_rename: false" a bit higher) +# If dialog rename permission is enabled and player do not have the permission merge will fall back to vanilla xp cost +# +# As this feature require dialog rename, it can only be enabled starting with paper 1.21.6 monetary_cost: enabled: false # If using vault unlocked this allow to specify what currency should be used for anvil usage diff --git a/nms/v1_21R1/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilTitleUtil.kt b/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilTitleUtil.kt similarity index 86% rename from nms/v1_21R1/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilTitleUtil.kt rename to nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilTitleUtil.kt index 805b150..cc92b78 100644 --- a/nms/v1_21R1/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilTitleUtil.kt +++ b/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilTitleUtil.kt @@ -6,6 +6,8 @@ import org.bukkit.inventory.InventoryView object AnvilTitleUtil { fun rename(view: InventoryView, name: String) { + if(view.title == name) return + view.title = name } diff --git a/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt b/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt index 9ee7317..b163de8 100644 --- a/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt +++ b/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt @@ -3,15 +3,16 @@ package io.delilaheve.util import io.delilaheve.CustomAnvil import io.delilaheve.util.EnchantmentUtil.enchantmentName import org.bukkit.NamespacedKey +import org.bukkit.entity.HumanEntity import xyz.alexcrea.cuanvil.config.ConfigHolder import xyz.alexcrea.cuanvil.config.WorkPenaltyType import xyz.alexcrea.cuanvil.config.WorkPenaltyType.WorkPenaltyPart import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.dependency.economy.EconomyManager import xyz.alexcrea.cuanvil.enchant.CAEnchantment -import xyz.alexcrea.cuanvil.update.UpdateUtils import xyz.alexcrea.cuanvil.update.Version import xyz.alexcrea.cuanvil.util.AnvilUseType +import xyz.alexcrea.cuanvil.util.dialog.AnvilRenameDialogUtil import java.math.BigDecimal import java.util.* @@ -90,9 +91,6 @@ object ConfigOptions { const val DEBUG_LOGGING = "debug_log" const val VERBOSE_DEBUG_LOGGING = "debug_log_verbose" - // Minimum versions - val MINIMUM_MONETARY_COST_VER = Version(1, 21, 0) - // ---------------------- // Default config values // ---------------------- @@ -181,6 +179,11 @@ object ConfigOptions { // Default max before merge disabled (negative mean enabled) const val DEFAULT_MAX_BEFORE_MERGE_DISABLED = -1 + // ----------- + // Permissions + // ----------- + private const val RENAME_DIALOG_PERMISSION = "ca.rename.dialog" + // ------------- // Get methods // ------------- @@ -469,6 +472,13 @@ object ConfigOptions { .getBoolean(DIALOG_RENAME_USE_PERMISSION, DEFAULT_DIALOG_RENAME_USE_PERMISSION) } + fun canUseDialogRename(player: HumanEntity): Boolean { + if(!doRenameDialog || !AnvilRenameDialogUtil.anvilRenameDialog.canSendDialog()) return false + if(doRenameDialogUsePermission && !player.hasPermission(RENAME_DIALOG_PERMISSION)) return false + + return true + } + /** * Do the dialog menu require permission */ @@ -643,16 +653,16 @@ object ConfigOptions { } /* - * Monetary configs (only for 1.21+) + * Monetary configs (only for 1.21.6+) + * Also require dialog rename */ - val shouldUseMoney: Boolean - get() { + fun shouldUseMoney(player: HumanEntity): Boolean { return EconomyManager.economy?.initialized() == true && - UpdateUtils.currentMinecraftVersion().greaterEqual(MINIMUM_MONETARY_COST_VER) && + canUseDialogRename(player) && ConfigHolder.DEFAULT_CONFIG .config .getBoolean(SHOULD_USE_MONEY, DEFAULT_SHOULD_USE_MONEY) - } + } val usedCurrency: String get() { diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt index c71e82e..fc04dcf 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt @@ -1,6 +1,7 @@ package xyz.alexcrea.cuanvil.listener import com.github.stefvanschie.inventoryframework.util.InventoryViewUtil +import com.jankominek.disenchantment.utils.AnvilCostUtils import io.delilaheve.CustomAnvil import io.delilaheve.util.ConfigOptions import io.delilaheve.util.EnchantmentUtil.combineWith @@ -19,6 +20,7 @@ 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.InventoryView import org.bukkit.inventory.ItemStack import org.bukkit.inventory.meta.EnchantmentStorageMeta import org.bukkit.inventory.meta.ItemMeta @@ -45,8 +47,6 @@ class PrepareAnvilListener : Listener { const val ANVIL_OUTPUT_SLOT = 2 var IS_EMPTY_TEST = false - - private const val RENAME_DIALOG_PERMISSION = "ca.rename.dialog" } /** @@ -121,29 +121,26 @@ class PrepareAnvilListener : Listener { // Test for lore edit if (testLoreEdit(event, inventory, player, first, second)) return - CustomAnvil.log("no anvil fuse type found") event.result = null } + private fun setNoResult(event: PrepareAnvilEvent, view: InventoryView) { + event.result = null + AnvilXpUtil.onNoResult(view) + } + private fun tryRenameDialog( player: HumanEntity, event: PrepareAnvilEvent ) { - if(!canUseRenameDialog(player)) return + if(!ConfigOptions.canUseDialogRename(player)) return AnvilRenameDialogUtil.anvilRenameDialog.tryShowDialog(player, event) } - private fun canUseRenameDialog(player: HumanEntity): Boolean { - if(!ConfigOptions.doRenameDialog || !AnvilRenameDialogUtil.anvilRenameDialog.canSendDialog()) return false - if(ConfigOptions.doRenameDialogUsePermission && !player.hasPermission(RENAME_DIALOG_PERMISSION)) return false - - return true - } - private fun processDialogPCD(it: ItemMeta, player: HumanEntity) { - val keepDialog = canUseRenameDialog(player) && ConfigOptions.shouldKeepRenameText + val keepDialog = ConfigOptions.canUseDialogRename(player) && ConfigOptions.shouldKeepRenameText val pdc = it.persistentDataContainer if(!keepDialog) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt index 07f3d23..c8829f3 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt @@ -6,8 +6,6 @@ import io.delilaheve.util.ConfigOptions.getMonetaryMultiplier as moneyMultiplier import io.delilaheve.util.EnchantmentUtil.enchantmentName import io.delilaheve.util.ItemUtil.findEnchantments import io.delilaheve.util.ItemUtil.isEnchantedBook -import net.kyori.adventure.text.Component -import net.kyori.adventure.text.format.TextColor import org.bukkit.GameMode import org.bukkit.NamespacedKey import org.bukkit.entity.HumanEntity @@ -21,6 +19,7 @@ import xyz.alexcrea.cuanvil.config.ConfigHolder import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.dependency.economy.EconomyManager import xyz.alexcrea.cuanvil.group.ConflictType +import xyz.alexcrea.cuanvil.util.dialog.AnvilRenameDialogUtil import java.math.BigDecimal import kotlin.math.min @@ -64,7 +63,7 @@ object AnvilXpUtil { cost: AnvilCost, ignoreRules: Boolean = false ) { - if (ConfigOptions.shouldUseMoney) + if (ConfigOptions.shouldUseMoney(player)) setAnvilPrice(inventory, view, player, cost) else setAnvilInvXp(inventory, view, player, cost.sum(), ignoreRules) @@ -228,6 +227,11 @@ object AnvilXpUtil { return resultSum } + fun onNoResult(player: HumanEntity, view: InventoryView) { + if (ConfigOptions.shouldUseMoney(player)) + AnvilTitleUtil.rename(view, "") + } + private fun exclusivePenaltyKey(useType: AnvilUseType): NamespacedKey { return NamespacedKey(CustomAnvil.instance, "${EXCLUSIVE_PENALTY_PREFIX}_${useType.typeName}") } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 5ba61ec..27c0248 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -435,7 +435,10 @@ lore_edit: # Allow to replace the xp cost by a monetary cost # If enabled it will not be bound to the experience level limits # -# This feature can only be enabled starting with 1.21 +# It also requires to enable dialog rename (set "enable_dialog_rename: false" a bit higher) +# If dialog rename permission is enabled and player do not have the permission merge will fall back to vanilla xp cost +# +# As this feature require dialog rename, it can only be enabled starting with paper 1.21.6 monetary_cost: enabled: false # If using vault unlocked this allow to specify what currency should be used for anvil usage From 7aac325c709b3cd73d46cac94f5873cbacdd86e6 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Fri, 29 May 2026 00:39:12 +0200 Subject: [PATCH 165/207] hell --- .../cuanvil/dialog/AnvilRenameDialog.kt | 2 + .../cuanvil/dialog/AnvilRenameDialogImpl.kt | 127 +++++++++++------- .../alexcrea/cuanvil/util/AnvilTitleUtil.kt | 37 ++++- .../cuanvil/listener/PrepareAnvilListener.kt | 6 +- .../xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt | 10 +- .../util/dialog/AnvilRenameDialogUtil.kt | 4 + 6 files changed, 130 insertions(+), 56 deletions(-) diff --git a/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dialog/AnvilRenameDialog.kt b/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dialog/AnvilRenameDialog.kt index 62e1cf0..df6e883 100644 --- a/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dialog/AnvilRenameDialog.kt +++ b/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dialog/AnvilRenameDialog.kt @@ -18,4 +18,6 @@ interface AnvilRenameDialog { fun currentText(player: HumanEntity): String? + fun isOpenFor(player: HumanEntity): Boolean + } \ No newline at end of file diff --git a/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dialog/AnvilRenameDialogImpl.kt b/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dialog/AnvilRenameDialogImpl.kt index fb5fa0c..c6973f1 100644 --- a/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dialog/AnvilRenameDialogImpl.kt +++ b/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dialog/AnvilRenameDialogImpl.kt @@ -32,7 +32,7 @@ class AnvilRenameDialogImpl( val keepUserPreviousDialog: Supplier, val maxLength: Supplier, val plugin: Plugin, -): AnvilRenameDialog { +) : AnvilRenameDialog { companion object { private const val RENAME_TEXT_KEY = "rename" @@ -43,16 +43,24 @@ class AnvilRenameDialogImpl( // Need to be able to translate it later ! private val USER_FACING_RENAME_TITLE = Component.text("Rename Your Item") - private val USER_FACING_WARNING = Component.text("Note that the repair text will appear blank after Confirm\n" + - "But the name will be correctly applied") + private val USER_FACING_WARNING = Component.text( + "Note that the repair text will appear blank after Confirm\n" + + "But the name will be correctly applied" + ) private val USER_FACING_CONFIRM = Component.text("Confirm").color(TextColor.fromHexString("#40FF40")) private val USER_FACING_CANCEL = Component.text("Cancel").color(TextColor.fromHexString("#FF4040")) } private val lastNames = HashMap() + private val lastRenames = HashMap() + + private val lastLeftItem = HashMap() private val runTaskMap = HashMap() + // For monetary cost + val hasUiOpen = HashSet() + private val containerField = CraftInventoryView::class.java.getDeclaredField("container") init { @@ -63,75 +71,90 @@ class AnvilRenameDialogImpl( return true } - fun makeDialog(initial: String?, callback: Consumer): Dialog { + fun makeDialog(playerID: UUID, initial: String?, callback: Consumer): Dialog { val maxLength = this.maxLength.get() val initialFinal = initial?.take(maxLength) val baseBuilder = DialogBase.builder(USER_FACING_RENAME_TITLE) - .canCloseWithEscape(true) + .canCloseWithEscape(false) .afterAction(DialogBase.DialogAfterAction.CLOSE) - .inputs(listOf( - DialogInput.text(RENAME_TEXT_KEY, Component.text("Rename text")) - .maxLength(maxLength) - .initial(initialFinal ?: "") - .labelVisible(false) - .width(MAX_WIDTH) - .build(), + .inputs( + listOf( + DialogInput.text(RENAME_TEXT_KEY, Component.text("Rename text")) + .maxLength(maxLength) + .initial(initialFinal ?: "") + .labelVisible(false) + .width(MAX_WIDTH) + .build(), ), ) - baseBuilder.body(listOf( + baseBuilder.body( + listOf( DialogBody.plainMessage(USER_FACING_WARNING, MAX_WIDTH) - )) + ) + ) - return Dialog.create { builder -> builder.empty() - .base(baseBuilder.build()) - .type(DialogType.confirmation( - ActionButton.builder(USER_FACING_CONFIRM) - .action(DialogAction.customClick({ response, _ -> - val text = response.getText(RENAME_TEXT_KEY)!! - callback.accept(text) - }, ClickCallback.Options.builder().build())) - .build(), - ActionButton.builder(USER_FACING_CANCEL) - .build(), - )) + return Dialog.create { builder -> + builder.empty() + .base(baseBuilder.build()) + .type( + DialogType.confirmation( + ActionButton.builder(USER_FACING_CONFIRM) + .action(DialogAction.customClick({ response, _ -> + hasUiOpen.remove(playerID) + val text = response.getText(RENAME_TEXT_KEY)!! + callback.accept(text) + }, ClickCallback.Options.builder().build())) + .build(), + ActionButton.builder(USER_FACING_CANCEL) + .action(DialogAction.customClick({ response, _ -> + hasUiOpen.remove(playerID) + }, ClickCallback.Options.builder().build())) + .build(), + ) + ) } } private fun setResult(player: HumanEntity, view: CraftAnvilView, result: String?) { val defaultName = PLAIN_TEXT_SERIALIZER.serializeOrNull(view.getItem(0)?.effectiveName()) - if(defaultName == result) { - setName(player, view, "") - if(defaultName != null) lastNames[player.uniqueId] = defaultName - } - else setName(player, view, result) + if (defaultName == result) { + setName(player, view, "", null) + if (defaultName != null) lastNames[player.uniqueId] = defaultName + } else setName(player, view, lastNames[player.uniqueId], result) } - private fun setName(player: HumanEntity, view: CraftAnvilView, name: String?) { + private fun setName(player: HumanEntity, view: CraftAnvilView, name: String?, rename: String?) { val menu = (containerField.get(view) as AnvilMenu) val isSameName = menu.itemName == name menu.itemName = name - if(name == null) + if (name == null) lastNames.remove(player.uniqueId) else lastNames[player.uniqueId] = name - if(!isSameName) + if (rename == null) + lastRenames.remove(player.uniqueId) + else + lastRenames[player.uniqueId] = rename + + if (!isSameName) CraftEventFactory.callPrepareResultEvent(menu, 2); } private fun nameFromItem(player: HumanEntity, item: ItemStack?): String? { // Already has text - if(item?.hasItemMeta() != true || !item.itemMeta.hasCustomName()) + if (item?.hasItemMeta() != true || !item.itemMeta.hasCustomName()) return PLAIN_TEXT_SERIALIZER.serializeOrNull(item?.effectiveName()) - if(keepUserPreviousDialog.get() && item.hasItemMeta()) { + if (keepUserPreviousDialog.get() && item.hasItemMeta()) { val lastName = item.itemMeta.persistentDataContainer.get( AnvilRenameDialog.PCD_KEEP_RENAME_TEXT_KEY, - PersistentDataType.STRING) + PersistentDataType.STRING + ) - if(lastName != null) return lastName + if (lastName != null) return lastName } return fromFormated.apply(player, item.effectiveName()) @@ -139,33 +162,37 @@ class AnvilRenameDialogImpl( private fun tryShowDialogScheduled(player: HumanEntity, event: PrepareAnvilEvent) { val view = event.view - if(view !is CraftAnvilView) return + if (view !is CraftAnvilView) return val renameText = view.renameText val leftItem = view.getItem(0) val leftItemStr = leftItem?.toString() - val lastName = lastNames.getOrDefault(player.uniqueId, null) - if(lastLeftItem.getOrDefault(player.uniqueId, null) != leftItemStr) { - if(leftItemStr == null) + val lastName = lastNames.getOrDefault(player.uniqueId, null) + val lastRename = lastRenames.getOrDefault(player.uniqueId, null) + + if (lastLeftItem.getOrDefault(player.uniqueId, null) != leftItemStr) { + if (leftItemStr == null) lastLeftItem.remove(player.uniqueId) else lastLeftItem[player.uniqueId] = leftItemStr - setName(player, view, nameFromItem(player, leftItem)) + setName(player, view, renameText, nameFromItem(player, leftItem)) return } - if(lastName == renameText) + if (lastName == renameText) return - if(renameText?.isBlank() == true) { - setName(player, view, lastNames[player.uniqueId]) + if (renameText?.isBlank() == true) { + setName(player, view, lastName, lastRename) return } - val dialog = makeDialog(lastName) + val dialog = makeDialog(player.uniqueId, lastRename) { result -> setResult(player, view, result) } player.showDialog(dialog) + + hasUiOpen.add(player.uniqueId) } // We need to wait for a short time as changing item will change the name BEFORE the item change @@ -181,7 +208,7 @@ class AnvilRenameDialogImpl( {}, 2 ) - if(task == null) return + if (task == null) return runTaskMap[player.uniqueId] = task } @@ -196,4 +223,8 @@ class AnvilRenameDialogImpl( return lastNames[player.uniqueId] } + override fun isOpenFor(player: HumanEntity): Boolean { + return hasUiOpen.contains(player.uniqueId) + } + } \ No newline at end of file diff --git a/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilTitleUtil.kt b/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilTitleUtil.kt index cc92b78..e40e7ed 100644 --- a/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilTitleUtil.kt +++ b/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilTitleUtil.kt @@ -1,14 +1,45 @@ package xyz.alexcrea.cuanvil.util +import io.papermc.paper.threadedregions.scheduler.ScheduledTask +import org.bukkit.entity.HumanEntity import org.bukkit.inventory.InventoryView +import org.bukkit.plugin.Plugin +import xyz.alexcrea.cuanvil.dialog.AnvilRenameDialog +import java.util.HashMap +import java.util.UUID -// TODO yet another cleanup to do on legacy removal branch object AnvilTitleUtil { - fun rename(view: InventoryView, name: String) { - if(view.title == name) return + private val runTaskMap = HashMap() + + private fun actualRename(view: InventoryView, name: String, player: HumanEntity, anvilDialog: AnvilRenameDialog) { + runTaskMap.remove(player.uniqueId) + if (view.title == name) return + + // We assume rename impl is used + if (anvilDialog.isOpenFor(player)) return view.title = name } + // We don't want to rename instantly it is causing issue with rename text + // especially as it can "override" current ui when it is rename ui time but rename ui also need some delay + fun rename(view: InventoryView, name: String, player: HumanEntity, anvilDialog: AnvilRenameDialog, plugin: Plugin) { + runTaskMap.remove(player.uniqueId)?.cancel() + + val task = player.scheduler.runDelayed( + plugin, + { _ -> + run { actualRename(view, name, player, anvilDialog) } + }, + { + runTaskMap.remove(player.uniqueId) + }, + 2 + ) + + if (task == null) return + runTaskMap[player.uniqueId] = task + } + } \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt index fc04dcf..5ef3e27 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt @@ -59,6 +59,8 @@ class PrepareAnvilListener : Listener { if(player !is Player) return val inventory = event.inventory + tryRenameDialog(player, event) + // Test if custom anvil is bypassed before immutability test if (DependencyManager.earlyTryEventPreAnvilBypass(event, player)) { // even if we got bypassed we still want to set price @@ -87,8 +89,6 @@ class PrepareAnvilListener : Listener { return } - tryRenameDialog(player, event) - // Test if the event should bypass custom anvil. if (DependencyManager.tryEventPreAnvilBypass(event, player)) { // even if we got bypassed we still want to set price @@ -127,7 +127,7 @@ class PrepareAnvilListener : Listener { private fun setNoResult(event: PrepareAnvilEvent, view: InventoryView) { event.result = null - AnvilXpUtil.onNoResult(view) + // TODO AnvilXpUtil.onNoResult(view) } private fun tryRenameDialog( diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt index c8829f3..2d7bf0c 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt @@ -157,7 +157,10 @@ object AnvilXpUtil { val text = "Cost: " + (if(has) "§2" else "§4") + EconomyManager.economy!!.format(finalCost) - AnvilTitleUtil.rename(view, text) + AnvilTitleUtil.rename(view, text, + player, + AnvilRenameDialogUtil.anvilRenameDialog, + CustomAnvil.instance) clearAnvilXpCost(inventory, view, player) } @@ -229,7 +232,10 @@ object AnvilXpUtil { fun onNoResult(player: HumanEntity, view: InventoryView) { if (ConfigOptions.shouldUseMoney(player)) - AnvilTitleUtil.rename(view, "") + AnvilTitleUtil.rename(view, "", + player, + AnvilRenameDialogUtil.anvilRenameDialog, + CustomAnvil.instance) } private fun exclusivePenaltyKey(useType: AnvilUseType): NamespacedKey { diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/dialog/AnvilRenameDialogUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/dialog/AnvilRenameDialogUtil.kt index dbff77b..a83d8b0 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/dialog/AnvilRenameDialogUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/dialog/AnvilRenameDialogUtil.kt @@ -47,5 +47,9 @@ object AnvilRenameDialogUtil { return null } + override fun isOpenFor(player: HumanEntity): Boolean { + return false + } + } } \ No newline at end of file From 3992ce1662012a0c4ce4180dd6f51e22458b9f0b Mon Sep 17 00:00:00 2001 From: alexcrea Date: Fri, 29 May 2026 02:48:16 +0200 Subject: [PATCH 166/207] no price on no result --- .../cuanvil/listener/PrepareAnvilListener.kt | 53 +++++++++++-------- .../xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt | 2 +- .../cuanvil/util/JustForEasierHotswapUtil.kt | 17 ++++++ 3 files changed, 48 insertions(+), 24 deletions(-) create mode 100644 src/main/kotlin/xyz/alexcrea/cuanvil/util/JustForEasierHotswapUtil.kt diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt index 5ef3e27..ec5cac2 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt @@ -54,25 +54,26 @@ class PrepareAnvilListener : Listener { */ @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) fun anvilCombineCheck(event: PrepareAnvilEvent) { - // Should find player - val player = InventoryViewUtil.getInstance().getPlayer(event.view) - if(player !is Player) return + val view = event.view val inventory = event.inventory + val player = JustForEasierHotswapUtil.getPlayerFromView(view) + if(player !is Player) return + tryRenameDialog(player, event) // Test if custom anvil is bypassed before immutability test if (DependencyManager.earlyTryEventPreAnvilBypass(event, player)) { // even if we got bypassed we still want to set price - AnvilXpUtil.setAnvilInvCost(inventory, event.view, player, AnvilCost(event.inventory.repairCost)) + AnvilXpUtil.setAnvilInvCost(inventory, view, player, AnvilCost(event.inventory.repairCost)) return } - val first = inventory.getItem(ANVIL_INPUT_LEFT) ?: return + val first = inventory.getItem(ANVIL_INPUT_LEFT) val second = inventory.getItem(ANVIL_INPUT_RIGHT) if(IS_EMPTY_TEST) { - event.result = null + setNoResult(event, player, view) IS_EMPTY_TEST = false return } @@ -85,33 +86,40 @@ class PrepareAnvilListener : Listener { if (isImmutable(first) || isImmutable(second)) { CustomAnvil.verboseLog("Skipping anvil process as one of the two item is immutable") - event.result = null + setNoResult(event, player, view) return } // Test if the event should bypass custom anvil. if (DependencyManager.tryEventPreAnvilBypass(event, player)) { // even if we got bypassed we still want to set price - AnvilXpUtil.setAnvilInvCost(inventory, event.view, player, AnvilCost(event.inventory.repairCost)) + AnvilXpUtil.setAnvilInvCost(inventory, view, player, AnvilCost(event.inventory.repairCost)) return } if (!player.hasPermission(CustomAnvil.affectedByPluginPermission)) return + if(first == null) { + setNoResult(event, player, view) + return + } + // Test custom recipe if (testCustomRecipe(event, inventory, player, first, second)) return // Test rename lonely item - val isAir = second.isAir - CustomAnvil.verboseLog("checking air in main logic: $isAir") - if (isAir) { - doRenaming(event, inventory, player, first) + val shouldTryRename = second.isAir + CustomAnvil.verboseLog("checking air in main logic: $shouldTryRename") + if (shouldTryRename) { + if(!doRenaming(event, inventory, player, first)) + setNoResult(event, player, view) return } // Test for merge if (first.canMergeWith(second!!)) { - doMerge(event, inventory, player, first, second) + if(!doMerge(event, inventory, player, first, second)) + setNoResult(event, player, view) return } @@ -121,13 +129,12 @@ class PrepareAnvilListener : Listener { // Test for lore edit if (testLoreEdit(event, inventory, player, first, second)) return - event.result = null - + setNoResult(event, player, view) } - private fun setNoResult(event: PrepareAnvilEvent, view: InventoryView) { + private fun setNoResult(event: PrepareAnvilEvent, player: Player, view: InventoryView) { event.result = null - // TODO AnvilXpUtil.onNoResult(view) + AnvilXpUtil.onNoResult(player, view) } private fun tryRenameDialog( @@ -209,7 +216,7 @@ class PrepareAnvilListener : Listener { private fun doRenaming( event: PrepareAnvilEvent, inventory: AnvilInventory, player: Player, first: ItemStack - ) { + ): Boolean { val resultItem = DependencyManager.cloneItem(event, first) val cost = AnvilCost() cost.rename = handleRename(resultItem, inventory, player) @@ -217,14 +224,14 @@ class PrepareAnvilListener : Listener { // Test/stop if nothing changed. if (first == resultItem) { CustomAnvil.log("no right item, But input is same as output") - event.result = null - return + return false } cost.workPenalty = AnvilXpUtil.calculatePenalty(first, null, resultItem, AnvilUseType.RENAME_ONLY) event.result = DependencyManager.tryTreatAnvilResult(event, resultItem, AnvilUseType.RENAME_ONLY, cost) AnvilXpUtil.setAnvilInvCost(inventory, event.view, player, cost) + return true } private fun handleRename(resultItem: ItemStack, inventory: AnvilInventory, player: HumanEntity): Int { @@ -275,7 +282,7 @@ class PrepareAnvilListener : Listener { event: PrepareAnvilEvent, inventory: AnvilInventory, player: Player, first: ItemStack, second: ItemStack - ) { + ): Boolean { val newEnchants = first.findEnchantments() .combineWith(second.findEnchantments(), first, player) var hasChanged = !isIdentical(first.findEnchantments(), newEnchants) @@ -299,8 +306,7 @@ class PrepareAnvilListener : Listener { // Test/stop if nothing changed. if (!hasChanged) { CustomAnvil.log("Mergeable with second, But input is same as output") - event.result = null - return + return false } // As calculatePenalty edit result, we need to calculate penalty after checking equality cost.workPenalty = AnvilXpUtil.calculatePenalty(first, second, resultItem, AnvilUseType.MERGE) @@ -310,6 +316,7 @@ class PrepareAnvilListener : Listener { // Finally, we set result event.result = DependencyManager.tryTreatAnvilResult(event, resultItem, AnvilUseType.MERGE, cost) AnvilXpUtil.setAnvilInvCost(inventory, event.view, player, cost) + return true } private fun isIdentical( diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt index 2d7bf0c..a581ed3 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt @@ -232,7 +232,7 @@ object AnvilXpUtil { fun onNoResult(player: HumanEntity, view: InventoryView) { if (ConfigOptions.shouldUseMoney(player)) - AnvilTitleUtil.rename(view, "", + AnvilTitleUtil.rename(view, "Repair & Name", player, AnvilRenameDialogUtil.anvilRenameDialog, CustomAnvil.instance) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/JustForEasierHotswapUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/JustForEasierHotswapUtil.kt new file mode 100644 index 0000000..ea0f7ec --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/JustForEasierHotswapUtil.kt @@ -0,0 +1,17 @@ +package xyz.alexcrea.cuanvil.util + +import com.github.stefvanschie.inventoryframework.util.InventoryViewUtil +import org.bukkit.entity.HumanEntity +import org.bukkit.inventory.InventoryView + +// Hotswap to not relocate +// So I just put small thing calling relocating method here to enable to hotswap more class +// Especially for PrepareAnvilListener +// Will be able to replace that on legacy removal so really temporary +object JustForEasierHotswapUtil { + + fun getPlayerFromView(view: InventoryView): HumanEntity { + return InventoryViewUtil.getInstance().getPlayer(view) + } + +} \ No newline at end of file From 2d31a7f5a8dac3a618659fec3896bc1ed07813de Mon Sep 17 00:00:00 2001 From: alexcrea Date: Fri, 29 May 2026 03:18:59 +0200 Subject: [PATCH 167/207] seems to work better --- .../cuanvil/dialog/AnvilRenameDialogImpl.kt | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dialog/AnvilRenameDialogImpl.kt b/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dialog/AnvilRenameDialogImpl.kt index c6973f1..e210daa 100644 --- a/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dialog/AnvilRenameDialogImpl.kt +++ b/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dialog/AnvilRenameDialogImpl.kt @@ -18,6 +18,7 @@ import org.bukkit.craftbukkit.inventory.CraftInventoryView import org.bukkit.craftbukkit.inventory.view.CraftAnvilView import org.bukkit.entity.HumanEntity import org.bukkit.event.inventory.PrepareAnvilEvent +import org.bukkit.inventory.InventoryView import org.bukkit.inventory.ItemStack import org.bukkit.persistence.PersistentDataType import org.bukkit.plugin.Plugin @@ -49,6 +50,10 @@ class AnvilRenameDialogImpl( ) private val USER_FACING_CONFIRM = Component.text("Confirm").color(TextColor.fromHexString("#40FF40")) private val USER_FACING_CANCEL = Component.text("Cancel").color(TextColor.fromHexString("#FF4040")) + + fun itemDefaultName(item: ItemStack?): String? { + return PLAIN_TEXT_SERIALIZER.serializeOrNull(item?.effectiveName()) + } } private val lastNames = HashMap() @@ -76,7 +81,7 @@ class AnvilRenameDialogImpl( val initialFinal = initial?.take(maxLength) val baseBuilder = DialogBase.builder(USER_FACING_RENAME_TITLE) - .canCloseWithEscape(false) + .canCloseWithEscape(true) .afterAction(DialogBase.DialogAfterAction.CLOSE) .inputs( listOf( @@ -116,18 +121,18 @@ class AnvilRenameDialogImpl( } } - private fun setResult(player: HumanEntity, view: CraftAnvilView, result: String?) { - val defaultName = PLAIN_TEXT_SERIALIZER.serializeOrNull(view.getItem(0)?.effectiveName()) + private fun setResult(player: HumanEntity, view: InventoryView, result: String?) { + val defaultName = itemDefaultName(view.getItem(0)) if (defaultName == result) { setName(player, view, "", null) if (defaultName != null) lastNames[player.uniqueId] = defaultName - } else setName(player, view, lastNames[player.uniqueId], result) + } else setName(player, view, result, result) } - private fun setName(player: HumanEntity, view: CraftAnvilView, name: String?, rename: String?) { + private fun setName(player: HumanEntity, view: InventoryView, name: String?, rename: String?) { val menu = (containerField.get(view) as AnvilMenu) val isSameName = menu.itemName == name - menu.itemName = name + menu.itemName = rename if (name == null) lastNames.remove(player.uniqueId) @@ -180,10 +185,10 @@ class AnvilRenameDialogImpl( return } - if (lastName == renameText) + if (lastName == renameText || lastRename == renameText) return - if (renameText?.isBlank() == true) { + if (renameText?.isBlank() == true || renameText == itemDefaultName(leftItem)) { setName(player, view, lastName, lastRename) return } @@ -215,6 +220,7 @@ class AnvilRenameDialogImpl( override fun closeInventory(player: HumanEntity) { lastNames.remove(player.uniqueId) + lastRenames.remove(player.uniqueId) lastLeftItem.remove(player.uniqueId) runTaskMap.remove(player.uniqueId)?.cancel() } From bf8144ad06146f0983f58771ce165ce3f1ab55a4 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Sat, 30 May 2026 03:53:31 +0200 Subject: [PATCH 168/207] result work for unity repair and custom craft --- .../listener/CATreatAnvilResultEvent.java | 4 +- .../cuanvil/listener/AnvilResultListener.kt | 77 +++++++++++++------ .../xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt | 64 ++++++++------- 3 files changed, 90 insertions(+), 55 deletions(-) diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResultEvent.java b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResultEvent.java index 4d68aca..7bc8fa3 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResultEvent.java +++ b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResultEvent.java @@ -109,7 +109,7 @@ public class CATreatAnvilResultEvent extends Event { */ @Deprecated(forRemoval = true, since = "1.17.0") public int getLevelCost() { - return cost.sum(); + return cost.asXpCost(); } /** @@ -131,7 +131,7 @@ public class CATreatAnvilResultEvent extends Event { */ @Deprecated(forRemoval = true, since = "1.17.0") public void setLevelCost(int levelCost) { - cost.setGeneric(levelCost - cost.getGeneric() - cost.sum()); + cost.setGeneric(levelCost - cost.getGeneric() - cost.asXpCost()); } /** diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt index 1886589..77bca4a 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt @@ -17,6 +17,7 @@ import org.bukkit.inventory.InventoryView import org.bukkit.inventory.ItemStack import org.bukkit.inventory.meta.BookMeta import xyz.alexcrea.cuanvil.dependency.DependencyManager +import xyz.alexcrea.cuanvil.dependency.economy.EconomyManager import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.setComponentDisplayName import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener.Companion.ANVIL_INPUT_LEFT import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener.Companion.ANVIL_INPUT_RIGHT @@ -32,7 +33,6 @@ import xyz.alexcrea.cuanvil.util.UnitRepairUtil.getRepair import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil import xyz.alexcrea.cuanvil.util.config.LoreEditType import java.util.* -import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicReference import kotlin.math.min @@ -89,6 +89,7 @@ class AnvilResultListener : Listener { // Rename if (rightItem == null) { + // BRUH event.result = Event.Result.ALLOW return } @@ -246,14 +247,13 @@ class AnvilResultListener : Listener { rightItem: ItemStack?, rightRemoveCount: Int, output: ItemStack, - repairCost: Int, + cost: AnvilCost, ): Boolean { // To avoid vanilla, we cancel the event event.result = Event.Result.DENY event.isCancelled = true - // Assumed if player do not have enough xp then it returned MIN_VALUE - if (repairCost == Int.MIN_VALUE) return false + if (!cost.valid) return false // Where should we get the item val slotDestination = getActionSlot(event, player) @@ -261,6 +261,13 @@ class AnvilResultListener : Listener { // If not creative middle click... if (event.click != ClickType.MIDDLE) { + if(cost.isMonetary) { + val result = EconomyManager.economy!!.remove(player, cost.asMonetaryCost()) + if(!result) return false + } else { + player.level -= cost.asXpCost() + } + // We remove what should be removed if (leftItem != null) leftItem.amount -= leftRemoveCount inventory.setItem(ANVIL_INPUT_LEFT, leftItem) @@ -269,7 +276,7 @@ class AnvilResultListener : Listener { inventory.setItem(ANVIL_INPUT_RIGHT, rightItem) inventory.setItem(ANVIL_OUTPUT_SLOT, null) - player.level -= repairCost + } // Finally, we add the item to the player @@ -313,55 +320,75 @@ class AnvilResultListener : Listener { inventory: AnvilInventory, player: Player, leftItem: ItemStack, output: ItemStack, resultCopy: ItemStack, resultAmount: Int - ): Int { - if (player.gameMode == GameMode.CREATIVE) return 0 + ): AnvilCost { + if (player.gameMode == GameMode.CREATIVE) return AnvilCost(0) - var repairCost = 0 + val cost = AnvilCost() // Get repairCost leftItem.itemMeta?.let { leftMeta -> val leftName = leftMeta.displayName output.itemMeta?.let { // Rename cost if (!leftName.contentEquals(it.displayName)) { - repairCost += ConfigOptions.itemRenameCost + cost.rename += ConfigOptions.itemRenameCost // Color cost if (it.displayName.contains('§')) { - repairCost += ConfigOptions.useOfColorCost + cost.rename += ConfigOptions.useOfColorCost } } } } - repairCost += AnvilXpUtil.calculatePenalty(leftItem, null, resultCopy, AnvilUseType.UNIT_REPAIR) - repairCost += resultAmount * ConfigOptions.unitRepairCost + cost.workPenalty = AnvilXpUtil.calculatePenalty(leftItem, null, resultCopy, AnvilUseType.UNIT_REPAIR) + cost.repair = resultAmount * ConfigOptions.unitRepairCost + + var sum = cost.repair if ( !ConfigOptions.doRemoveCostLimit && ConfigOptions.doCapCost ) { - repairCost = min(repairCost, ConfigOptions.maxAnvilCost) + val final = min(sum, ConfigOptions.maxAnvilCost) + cost.generic += (final - sum) + + sum = final } - if ((inventory.maximumRepairCost <= repairCost) - || (player.level < repairCost) - ) return Int.MIN_VALUE + if (ConfigOptions.shouldUseMoney(player)) { + cost.isMonetary = true + if (!EconomyManager.economy!!.has(player, cost.asMonetaryCost())) + cost.valid = false + } else { + if ((inventory.maximumRepairCost <= sum) + || (player.level < sum) + ) cost.valid = false + } - return repairCost + return cost } private fun getFromLoreEditXpCost( cost: AnvilCost, player: Player, inventory: AnvilInventory, - ): Int { - if (GameMode.CREATIVE == player.gameMode) return 0 + ): AnvilCost { + if (GameMode.CREATIVE == player.gameMode) return AnvilCost(0) - val repairCost = cost.sum() - return if ((inventory.maximumRepairCost <= repairCost) - || (player.level < repairCost) - ) Int.MIN_VALUE - else repairCost + if (ConfigOptions.shouldUseMoney(player)) { + cost.isMonetary = true + if (!EconomyManager.economy!!.has(player, cost.asMonetaryCost())) + cost.valid = false + } else { + val repairCost = cost.asXpCost() + + if ((inventory.maximumRepairCost <= repairCost) + || (player.level < repairCost) + ) + cost.valid = false + } + + return cost } private fun handleBookLoreEdit( @@ -414,7 +441,7 @@ class AnvilResultListener : Listener { val bookPage = StringBuilder() lore.forEach { if (bookPage.isNotEmpty()) bookPage.append('\n') - if(it == null) return@forEach + if (it == null) return@forEach bookPage.append(MiniMessageUtil.plain_text_mm.serialize(it)) } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt index a581ed3..cedc07c 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt @@ -29,6 +29,8 @@ object AnvilXpUtil { class AnvilCost { private val isAlone: Boolean + var valid = true // Get set as invalid if cost can be satisfied + var isMonetary = false var generic = 0 var enchantment = 0 @@ -39,10 +41,6 @@ object AnvilXpUtil { var workPenalty = 0 var recipe = 0 - fun sum(): Int { - return generic + enchantment + repair + rename + lore + illegalPenalty + workPenalty + recipe - } - constructor(generic: Int) { this.generic = generic isAlone = true @@ -51,6 +49,24 @@ object AnvilXpUtil { constructor() { isAlone = false } + + fun asXpCost(): Int { + return generic + enchantment + repair + rename + lore + illegalPenalty + workPenalty + recipe + } + + fun asMonetaryCost(): BigDecimal { + // multiply by per use type multipliers + return BigDecimal(generic) + .add(BigDecimal(enchantment).multiply(moneyMultiplier("enchantment"))) + .add(BigDecimal(repair).multiply(moneyMultiplier("repair"))) + .add(BigDecimal(rename).multiply(moneyMultiplier("rename"))) + .add(BigDecimal(lore).multiply(moneyMultiplier("lore_edit"))) + .add(BigDecimal(enchantment).multiply(moneyMultiplier("enchantment"))) + .add(BigDecimal(illegalPenalty).multiply(moneyMultiplier("work_penalty"))) + .add(BigDecimal(workPenalty).multiply(moneyMultiplier("work_penalty"))) + .add(BigDecimal(recipe).multiply(moneyMultiplier("recipe"))) + .multiply(moneyMultiplier("global")) + } } /** @@ -63,10 +79,11 @@ object AnvilXpUtil { cost: AnvilCost, ignoreRules: Boolean = false ) { - if (ConfigOptions.shouldUseMoney(player)) + if (ConfigOptions.shouldUseMoney(player)) { + cost.isMonetary = true setAnvilPrice(inventory, view, player, cost) - else - setAnvilInvXp(inventory, view, player, cost.sum(), ignoreRules) + } else + setAnvilInvXp(inventory, view, player, cost.asXpCost(), ignoreRules) } /** @@ -128,20 +145,6 @@ object AnvilXpUtil { } } - fun asMonetaryCost(cost: AnvilCost): BigDecimal { - // multiply by per use type multipliers - return BigDecimal(cost.generic) - .add(BigDecimal(cost.enchantment).multiply(moneyMultiplier("enchantment"))) - .add(BigDecimal(cost.repair).multiply(moneyMultiplier("repair"))) - .add(BigDecimal(cost.rename).multiply(moneyMultiplier("rename"))) - .add(BigDecimal(cost.lore).multiply(moneyMultiplier("lore_edit"))) - .add(BigDecimal(cost.enchantment).multiply(moneyMultiplier("enchantment"))) - .add(BigDecimal(cost.illegalPenalty).multiply(moneyMultiplier("work_penalty"))) - .add(BigDecimal(cost.workPenalty).multiply(moneyMultiplier("work_penalty"))) - .add(BigDecimal(cost.recipe).multiply(moneyMultiplier("recipe"))) - .multiply(moneyMultiplier("global")) - } - /** * Display monetary cost needed for the work on the anvil inventory */ @@ -151,16 +154,19 @@ object AnvilXpUtil { player: Player, cost: AnvilCost, ) { - val finalCost = asMonetaryCost(cost) + val finalCost = cost.asMonetaryCost() - val has = EconomyManager.economy!!.has(player, finalCost) + val has = player.gameMode == GameMode.CREATIVE || + EconomyManager.economy!!.has(player, finalCost) - val text = "Cost: " + (if(has) "§2" else "§4") + + val text = "Cost: " + (if (has) "§2" else "§4") + EconomyManager.economy!!.format(finalCost) - AnvilTitleUtil.rename(view, text, + AnvilTitleUtil.rename( + view, text, player, AnvilRenameDialogUtil.anvilRenameDialog, - CustomAnvil.instance) + CustomAnvil.instance + ) clearAnvilXpCost(inventory, view, player) } @@ -232,10 +238,12 @@ object AnvilXpUtil { fun onNoResult(player: HumanEntity, view: InventoryView) { if (ConfigOptions.shouldUseMoney(player)) - AnvilTitleUtil.rename(view, "Repair & Name", + AnvilTitleUtil.rename( + view, "Repair & Name", player, AnvilRenameDialogUtil.anvilRenameDialog, - CustomAnvil.instance) + CustomAnvil.instance + ) } private fun exclusivePenaltyKey(useType: AnvilUseType): NamespacedKey { From df92b4bf913532ad5782144bc780c03cfe54c5fd Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 1 Jun 2026 13:48:59 +0200 Subject: [PATCH 169/207] update faststat to 0.24.0 --- build.gradle.kts | 2 +- .../xyz/alexcrea/cuanvil/util/MetricsUtil.kt | 24 ++++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 1ff46b1..db82f1c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -55,7 +55,7 @@ dependencies { compileOnly("org.spigotmc:spigot-api:1.18-R0.1-SNAPSHOT") // fast stats - implementation("dev.faststats.metrics:bukkit:0.22.1") + implementation("dev.faststats.metrics:bukkit:0.24.0") // minimessage implementation("net.kyori:adventure-text-minimessage:4.25.0") diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt index 80a84c2..81ac870 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt @@ -1,8 +1,8 @@ package xyz.alexcrea.cuanvil.util -import dev.faststats.bukkit.BukkitMetrics -import dev.faststats.core.ErrorTracker -import dev.faststats.core.data.Metric +import dev.faststats.ErrorTracker +import dev.faststats.bukkit.BukkitContext +import dev.faststats.data.Metric import io.delilaheve.CustomAnvil import io.delilaheve.util.ConfigOptions import xyz.alexcrea.cuanvil.command.DiagnosticExecutor @@ -14,7 +14,7 @@ object MetricsUtil { private const val FASTSTATS_TOKEN = "fc282b048adcc71a77bc00ace49e8a81" private var ERROR_TRACKER: ErrorTracker? = null - private var FAST_STATS_METRICS: BukkitMetrics? = null + private var FAST_STATS_METRICS: BukkitContext? = null fun loadMetrics(plugin: CustomAnvil) { val config = ConfigHolder.DEFAULT_CONFIG.config @@ -47,13 +47,15 @@ object MetricsUtil { if(reportErrors) ERROR_TRACKER = ErrorTracker.contextAware() - FAST_STATS_METRICS = BukkitMetrics.factory() - .addMetric(Metric.string("nms_type") { nmsType }) - .addMetric(Metric.bool("replace_too_expensive") { ConfigOptions.doReplaceTooExpensive }) - .addMetric(Metric.bool("using_alpha") { isAlpha }) - .errorTracker(ERROR_TRACKER) - .token(FASTSTATS_TOKEN) - .create(plugin) + FAST_STATS_METRICS = BukkitContext.Factory(plugin, FASTSTATS_TOKEN) + .metrics { factory -> factory + .addMetric(Metric.string("nms_type") { nmsType }) + .addMetric(Metric.bool("replace_too_expensive") { ConfigOptions.doReplaceTooExpensive }) + .addMetric(Metric.bool("using_alpha") { isAlpha }) + .create() + } + .errorTrackerService(ERROR_TRACKER) + .create() if(reportErrors) FAST_STATS_METRICS!!.ready() } From 7a705f3bfcef7bc648bd56a53aa40060110c3799 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 2 Jun 2026 13:29:26 +0200 Subject: [PATCH 170/207] move a lot of function to AnvilMergeLogic.kt --- .../listener/CATreatAnvilResultEvent.java | 8 +- .../cuanvil/config/WorkPenaltyType.java | 2 +- .../settings/WorkPenaltyTypeSettingGui.java | 2 +- .../cuanvil/update/plugin/PUpdate_1_8_0.java | 2 +- .../io/delilaheve/util/ConfigOptions.kt | 3 +- .../alexcrea/cuanvil/anvil/AnvilMergeLogic.kt | 271 ++++++++++++++++ .../cuanvil/{util => anvil}/AnvilUseType.kt | 26 +- .../cuanvil/dependency/DependencyManager.kt | 15 +- .../plugins/DisenchantmentDependency.kt | 4 +- .../dependency/plugins/HavenBagsDependency.kt | 4 +- .../cuanvil/listener/AnvilResultListener.kt | 8 +- .../cuanvil/listener/PrepareAnvilListener.kt | 295 +++--------------- .../cuanvil/recipe/AnvilCustomRecipe.kt | 4 +- .../util/{ => anvil}/AnvilColorUtil.kt | 4 +- .../util/{ => anvil}/AnvilLoreEditUtil.kt | 18 +- .../util/{ => anvil}/AnvilUseTypeUtil.kt | 2 +- .../cuanvil/util/{ => anvil}/AnvilXpUtil.kt | 6 +- .../cuanvil/util/config/LoreEditType.kt | 2 +- .../util/dialog/AnvilRenameDialogUtil.kt | 2 +- 19 files changed, 373 insertions(+), 305 deletions(-) create mode 100644 src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt rename src/main/kotlin/xyz/alexcrea/cuanvil/{util => anvil}/AnvilUseType.kt (66%) rename src/main/kotlin/xyz/alexcrea/cuanvil/util/{ => anvil}/AnvilColorUtil.kt (99%) rename src/main/kotlin/xyz/alexcrea/cuanvil/util/{ => anvil}/AnvilLoreEditUtil.kt (96%) rename src/main/kotlin/xyz/alexcrea/cuanvil/util/{ => anvil}/AnvilUseTypeUtil.kt (96%) rename src/main/kotlin/xyz/alexcrea/cuanvil/util/{ => anvil}/AnvilXpUtil.kt (98%) diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResultEvent.java b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResultEvent.java index 7bc8fa3..ddbaf23 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResultEvent.java +++ b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResultEvent.java @@ -6,8 +6,8 @@ import org.bukkit.event.inventory.PrepareAnvilEvent; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import xyz.alexcrea.cuanvil.util.AnvilUseType; -import xyz.alexcrea.cuanvil.util.AnvilXpUtil.AnvilCost; +import xyz.alexcrea.cuanvil.anvil.AnvilUseType; +import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil.AnvilCost; /** * Called after custom anvil processed the click on the result on the anvil inventory. @@ -18,8 +18,12 @@ import xyz.alexcrea.cuanvil.util.AnvilXpUtil.AnvilCost; * and {@link CAEarlyPreAnvilBypassEvent} for your use case *

    * A null result will cancel this pre anvil event + * + * @deprecated Prepare anvil Event should not be provided as it can be called on result and therefor not have prepare anvil event + * TODO a replacement is necessary but not yet made */ @SuppressWarnings("unused") +@Deprecated(forRemoval = true, since = "1.17.0") public class CATreatAnvilResultEvent extends Event { private static final HandlerList HANDLERS = new HandlerList(); diff --git a/src/main/java/xyz/alexcrea/cuanvil/config/WorkPenaltyType.java b/src/main/java/xyz/alexcrea/cuanvil/config/WorkPenaltyType.java index 75b0861..d374999 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/config/WorkPenaltyType.java +++ b/src/main/java/xyz/alexcrea/cuanvil/config/WorkPenaltyType.java @@ -2,7 +2,7 @@ package xyz.alexcrea.cuanvil.config; import com.google.common.collect.ImmutableMap; import org.jetbrains.annotations.Nullable; -import xyz.alexcrea.cuanvil.util.AnvilUseType; +import xyz.alexcrea.cuanvil.anvil.AnvilUseType; import java.util.EnumMap; diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/WorkPenaltyTypeSettingGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/WorkPenaltyTypeSettingGui.java index 888aa25..4345aa1 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/WorkPenaltyTypeSettingGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/WorkPenaltyTypeSettingGui.java @@ -11,11 +11,11 @@ import org.bukkit.entity.HumanEntity; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.jetbrains.annotations.NotNull; +import xyz.alexcrea.cuanvil.anvil.AnvilUseType; import xyz.alexcrea.cuanvil.config.ConfigHolder; import xyz.alexcrea.cuanvil.config.WorkPenaltyType; import xyz.alexcrea.cuanvil.gui.config.global.BasicConfigGui; import xyz.alexcrea.cuanvil.gui.util.GuiGlobalActions; -import xyz.alexcrea.cuanvil.util.AnvilUseType; import java.util.ArrayList; import java.util.EnumMap; diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_8_0.java b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_8_0.java index 7685f92..81ce1aa 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_8_0.java +++ b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_8_0.java @@ -2,10 +2,10 @@ package xyz.alexcrea.cuanvil.update.plugin; import io.delilaheve.util.ConfigOptions; import org.bukkit.configuration.file.FileConfiguration; +import xyz.alexcrea.cuanvil.anvil.AnvilUseType; import xyz.alexcrea.cuanvil.config.ConfigHolder; import xyz.alexcrea.cuanvil.config.WorkPenaltyType; import xyz.alexcrea.cuanvil.gui.config.settings.WorkPenaltyTypeSettingGui; -import xyz.alexcrea.cuanvil.util.AnvilUseType; import javax.annotation.Nonnull; import java.util.EnumMap; diff --git a/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt b/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt index b163de8..9dc85f9 100644 --- a/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt +++ b/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt @@ -4,14 +4,13 @@ import io.delilaheve.CustomAnvil import io.delilaheve.util.EnchantmentUtil.enchantmentName import org.bukkit.NamespacedKey import org.bukkit.entity.HumanEntity +import xyz.alexcrea.cuanvil.anvil.AnvilUseType import xyz.alexcrea.cuanvil.config.ConfigHolder import xyz.alexcrea.cuanvil.config.WorkPenaltyType import xyz.alexcrea.cuanvil.config.WorkPenaltyType.WorkPenaltyPart import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.dependency.economy.EconomyManager import xyz.alexcrea.cuanvil.enchant.CAEnchantment -import xyz.alexcrea.cuanvil.update.Version -import xyz.alexcrea.cuanvil.util.AnvilUseType import xyz.alexcrea.cuanvil.util.dialog.AnvilRenameDialogUtil import java.math.BigDecimal import java.util.* diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt new file mode 100644 index 0000000..9840cb8 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt @@ -0,0 +1,271 @@ +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 +import org.bukkit.entity.HumanEntity +import org.bukkit.entity.Player +import org.bukkit.inventory.AnvilInventory +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.meta.ItemMeta +import org.bukkit.persistence.PersistentDataType +import xyz.alexcrea.cuanvil.dependency.DependencyManager +import xyz.alexcrea.cuanvil.dialog.AnvilRenameDialog +import xyz.alexcrea.cuanvil.enchant.CAEnchantment +import xyz.alexcrea.cuanvil.util.CasedStringUtil +import xyz.alexcrea.cuanvil.util.CustomRecipeUtil +import xyz.alexcrea.cuanvil.util.MaterialUtil.isAir +import xyz.alexcrea.cuanvil.util.MiniMessageUtil +import xyz.alexcrea.cuanvil.util.UnitRepairUtil.getRepair +import xyz.alexcrea.cuanvil.util.anvil.AnvilColorUtil +import xyz.alexcrea.cuanvil.util.anvil.AnvilLoreEditUtil +import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil +import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil.AnvilCost +import xyz.alexcrea.cuanvil.util.dialog.AnvilRenameDialogUtil + +object AnvilMergeUtil { + + open class AnvilResult { + companion object { + val EMPTY = AnvilResult(null, AnvilCost()) + } + + val item: ItemStack? + val cost: AnvilCost + val ignoreXpRules: Boolean + + constructor(item: ItemStack?, cost: AnvilCost, ignoreXpRules: Boolean = false) { + this.item = item + this.cost = cost + this.ignoreXpRules = ignoreXpRules + } + + fun isEmpty(): Boolean { + return item == null + } + } + + class UnitRepairResult: AnvilResult { + companion object { + val EMPTY = UnitRepairResult(null, AnvilCost(), 0) + } + + val repairAmount: Int + + constructor(item: ItemStack?, cost: AnvilCost, repairAmount: Int) : super(item, cost) { + this.repairAmount = repairAmount + } + } + + fun doRenaming(inventory: AnvilInventory, + player: Player, first: ItemStack + ): AnvilResult { + val resultItem = DependencyManager.cloneItem(player, first) + val cost = AnvilCost() + cost.rename = handleRename(resultItem, inventory, player) + + // Test/stop if nothing changed. + if (first == resultItem) { + CustomAnvil.log("no right item, But input is same as output") + return AnvilResult.EMPTY + } + + cost.workPenalty = AnvilXpUtil.calculatePenalty(first, null, resultItem, AnvilUseType.RENAME_ONLY) + val result = DependencyManager.tryTreatAnvilResult(resultItem, AnvilUseType.RENAME_ONLY, cost) + + return AnvilResult(result, cost) + } + + private fun processDialogPCD(it: ItemMeta, player: HumanEntity) { + val keepDialog = ConfigOptions.canUseDialogRename(player) && ConfigOptions.shouldKeepRenameText + + val pdc = it.persistentDataContainer + if(!keepDialog) + pdc.remove(AnvilRenameDialog.PCD_KEEP_RENAME_TEXT_KEY) + else { + val text = AnvilRenameDialogUtil.anvilRenameDialog.currentText(player) + if(text == null || text.isBlank()) + pdc.remove(AnvilRenameDialog.PCD_KEEP_RENAME_TEXT_KEY) + else pdc.set(AnvilRenameDialog.PCD_KEEP_RENAME_TEXT_KEY, PersistentDataType.STRING, text) + } + } + + private fun handleRename(resultItem: ItemStack, inventory: AnvilInventory, player: HumanEntity): Int { + // Can be null + var renameText = ChatColor.stripColor(inventory.renameText) + + var sumCost = 0 + var useColor = false + if (ConfigOptions.renameColorPossible && renameText != null) { + val component = AnvilColorUtil.handleColor( + renameText, + AnvilColorUtil.renamePermission(player)) + + if (component != null) { + renameText = MiniMessageUtil.legacy_mm.serialize(component) + + sumCost += ConfigOptions.useOfColorCost + useColor = true + } + } + + // Rename item and add renaming cost + resultItem.itemMeta?.let { + val hasDisplayName = it.hasDisplayName() + val displayName = if (!hasDisplayName) null + else if (useColor) it.displayName + else ChatColor.stripColor(it.displayName) + + + if (!displayName.contentEquals(renameText) && !(displayName == null && + renameText == "" || + //TODO on recent paper check effective name instead + renameText == CasedStringUtil.snakeToUpperSpacedCase(resultItem.type.name.lowercase()) + )) { + it.setDisplayName(renameText) + processDialogPCD(it, player) + resultItem.itemMeta = it + + sumCost += ConfigOptions.itemRenameCost + } + + return sumCost + } + return 0 + } + + fun doMerge( + inventory: AnvilInventory, + player: Player, + first: ItemStack, second: ItemStack + ): AnvilResult { + val newEnchants = first.findEnchantments() + .combineWith(second.findEnchantments(), first, player) + var hasChanged = !isIdentical(first.findEnchantments(), newEnchants) + + val resultItem = DependencyManager.cloneItem(player, first) + val cost = AnvilCost() + if(hasChanged){ + resultItem.setEnchantmentsUnsafe(newEnchants) + // Calculate enchantment cost + AnvilXpUtil.getRightValues(second, resultItem, cost) + } + + // Calculate repair cost + if (!first.isEnchantedBook() && !second.isEnchantedBook()) { + // we only need to be concerned with repair when neither item is a book + val repaired = resultItem.repairFrom(first, second) + cost.repair = if (repaired) ConfigOptions.itemRepairCost else 0 + hasChanged = hasChanged || repaired + } + + // Test/stop if nothing changed. + if (!hasChanged) { + CustomAnvil.log("Mergeable with second, But input is same as output") + return AnvilResult.EMPTY + } + // As calculatePenalty edit result, we need to calculate penalty after checking equality + cost.workPenalty = AnvilXpUtil.calculatePenalty(first, second, resultItem, AnvilUseType.MERGE) + // Calculate rename cost + cost.rename = handleRename(resultItem, inventory, player) + + val result = DependencyManager.tryTreatAnvilResult(resultItem, AnvilUseType.MERGE, cost) + + return AnvilResult(result, cost) + } + + private fun isIdentical( + firstEnchants: MutableMap, + resultEnchants: MutableMap + ): Boolean { + if(firstEnchants.size != resultEnchants.size) return false + for (entry in resultEnchants) { + if(firstEnchants.getOrDefault(entry.key, entry.value-1) != entry.value) return false + } + + return true + } + + // return true if a custom recipe exist with these ingredients + fun testCustomRecipe( + player: Player, + first: ItemStack, second: ItemStack? + ): AnvilResult { + val recipe = CustomRecipeUtil.getCustomRecipe(first, second) + CustomAnvil.verboseLog("custom recipe not null? ${recipe != null}") + if (recipe == null) return AnvilResult.EMPTY + + val amount = CustomRecipeUtil.getCustomRecipeAmount(recipe, first, second) + + val resultItem: ItemStack = DependencyManager.cloneItem(player, recipe.resultItem!!) + resultItem.amount *= amount + + // Maybe add an option on custom craft to ignore/not ignore penalty ?? + val xpCost = recipe.determineCost(amount, first, resultItem) + + val cost = AnvilCost() + cost.recipe = if (recipe.removeExactLinearXp) AnvilXpUtil.calculateMinimumLevelForXp(xpCost) + else AnvilXpUtil.calculateLevelForXp(xpCost) + + val result = DependencyManager.tryTreatAnvilResult(resultItem, AnvilUseType.CUSTOM_CRAFT, cost) + return AnvilResult(result, cost, true) + } + + fun testUnitRepair( + inventory: AnvilInventory, + player: Player, + first: ItemStack, second: ItemStack + ): UnitRepairResult { + val unitRepairAmount = first.getRepair(second) ?: return UnitRepairResult.EMPTY + + val resultItem = DependencyManager.cloneItem(player, first) + val cost = AnvilCost() + cost.rename = handleRename(resultItem, inventory, player) + + val repairAmount = resultItem.unitRepair(second.amount, unitRepairAmount) + if (repairAmount > 0) + cost.repair = repairAmount * ConfigOptions.unitRepairCost + + // We do not care about right item penalty for unit repair + cost.workPenalty = AnvilXpUtil.calculatePenalty(first, null, resultItem, AnvilUseType.UNIT_REPAIR) + + // Test/stop if nothing changed. + if (first == resultItem) { + CustomAnvil.log("unit repair, But input is same as output") + return UnitRepairResult.EMPTY + } + + val result = DependencyManager.tryTreatAnvilResult(resultItem, AnvilUseType.UNIT_REPAIR, cost) + return UnitRepairResult(result, cost, repairAmount) + } + + fun testLoreEdit( + player: Player, + first: ItemStack, second: ItemStack + ): AnvilResult { + val type = second.type + var resultItem: ItemStack? = null + + val cost = AnvilCost() + if (Material.WRITABLE_BOOK == type) { + resultItem = AnvilLoreEditUtil.tryLoreEditByBook(player, first, second, cost) + } else if (Material.PAPER == type) { + resultItem = AnvilLoreEditUtil.tryLoreEditByPaper(player, first, second, cost) + } + + if (resultItem.isAir || first == resultItem) { + CustomAnvil.log("lore edit, But input is same as output") + return AnvilResult.EMPTY + } + + return AnvilResult(resultItem, cost) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilUseType.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilUseType.kt similarity index 66% rename from src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilUseType.kt rename to src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilUseType.kt index d17e908..67782f3 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilUseType.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilUseType.kt @@ -1,60 +1,60 @@ -package xyz.alexcrea.cuanvil.util +package xyz.alexcrea.cuanvil.anvil import org.bukkit.Material -import xyz.alexcrea.cuanvil.config.WorkPenaltyType.WorkPenaltyPart -import xyz.alexcrea.cuanvil.util.config.LoreEditType +import xyz.alexcrea.cuanvil.config.WorkPenaltyType +import xyz.alexcrea.cuanvil.util.anvil.AnvilUseTypeUtil enum class AnvilUseType( val typeName: String, val path: String, - val defaultPenalty: WorkPenaltyPart, + val defaultPenalty: WorkPenaltyType.WorkPenaltyPart, val displayName: String, val displayMat: Material ) { RENAME_ONLY( "rename_only", - WorkPenaltyPart(false, true), + WorkPenaltyType.WorkPenaltyPart(false, true), "Rename Only", Material.NAME_TAG ), MERGE( "merge", - WorkPenaltyPart(true, true), + WorkPenaltyType.WorkPenaltyPart(true, true), "Merge", Material.ANVIL ), UNIT_REPAIR( "unit_repair", - WorkPenaltyPart(true, true), + WorkPenaltyType.WorkPenaltyPart(true, true), "Unit Repair", Material.DIAMOND ), CUSTOM_CRAFT( "custom_craft", - WorkPenaltyPart(false, false), + WorkPenaltyType.WorkPenaltyPart(false, false), "Custom Craft", Material.CRAFTING_TABLE ), LORE_EDIT_BOOK_APPEND( "lore_edit_book_append", "lore_edit.book_and_quil.append", - WorkPenaltyPart(false, false), + WorkPenaltyType.WorkPenaltyPart(false, false), "Book Add", Material.WRITABLE_BOOK ), LORE_EDIT_BOOK_REMOVE( "lore_edit_book_remove", "lore_edit.book_and_quil.remove", - WorkPenaltyPart(false, false), + WorkPenaltyType.WorkPenaltyPart(false, false), "Book Remove", Material.WRITABLE_BOOK ), LORE_EDIT_PAPER_APPEND( "lore_edit_paper_append", "lore_edit.paper.append_line", - WorkPenaltyPart(false, false), + WorkPenaltyType.WorkPenaltyPart(false, false), "Paper Add", Material.WRITABLE_BOOK ), LORE_EDIT_PAPER_REMOVE( "lore_edit_paper_remove", "lore_edit.paper.remove_line", - WorkPenaltyPart(false, false), + WorkPenaltyType.WorkPenaltyPart(false, false), "Paper Remove", Material.WRITABLE_BOOK ), ; constructor( typeName: String, - defaultPenalty: WorkPenaltyPart, + defaultPenalty: WorkPenaltyType.WorkPenaltyPart, displayName: String, displayMat: Material ) : this( diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt index 418dbf1..4ad6c9b 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt @@ -13,6 +13,7 @@ import org.bukkit.event.inventory.PrepareAnvilEvent import org.bukkit.inventory.AnvilInventory import org.bukkit.inventory.Inventory import org.bukkit.inventory.ItemStack +import xyz.alexcrea.cuanvil.anvil.AnvilUseType import xyz.alexcrea.cuanvil.api.event.listener.CAClickResultBypassEvent import xyz.alexcrea.cuanvil.api.event.listener.CAEarlyPreAnvilBypassEvent import xyz.alexcrea.cuanvil.api.event.listener.CAPreAnvilBypassEvent @@ -30,9 +31,8 @@ import xyz.alexcrea.cuanvil.dependency.scheduler.TaskScheduler import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.componentLore import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener.Companion.ANVIL_OUTPUT_SLOT -import xyz.alexcrea.cuanvil.util.AnvilUseType -import xyz.alexcrea.cuanvil.util.AnvilXpUtil import xyz.alexcrea.cuanvil.util.MetricsUtil.trackError +import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil import java.util.logging.Level object DependencyManager { @@ -234,19 +234,20 @@ object DependencyManager { // Return null if there was an issue fun tryTreatAnvilResult( - event: PrepareAnvilEvent, result: ItemStack, useType: AnvilUseType, cost: AnvilXpUtil.AnvilCost ): ItemStack? { - val treatEvent = CATreatAnvilResultEvent(event, useType, result, cost) + //TODO + /*val treatEvent = CATreatAnvilResultEvent(event, useType, result, cost) try { unsafeTryTreatAnvilResult(treatEvent) return treatEvent.result } catch (e: Exception) { logExceptionAndClear(event.view.player, event.inventory, e) return null - } + }*/ + return result } private fun unsafeTryTreatAnvilResult(event: CATreatAnvilResultEvent) { @@ -295,11 +296,11 @@ object DependencyManager { } // Clone item and use plugin specific clone if needed - fun cloneItem(event: PrepareAnvilEvent, item: ItemStack): ItemStack { + fun cloneItem(player: HumanEntity, item: ItemStack): ItemStack { try { return unsafeCloneItem(item) } catch (e: Exception) { - logException(event.view.player, e) + logException(player, e) return item.clone() } } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/DisenchantmentDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/DisenchantmentDependency.kt index 7a7a5eb..32ca99d 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/DisenchantmentDependency.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/DisenchantmentDependency.kt @@ -15,9 +15,9 @@ import org.bukkit.event.inventory.PrepareAnvilEvent import org.bukkit.inventory.AnvilInventory import org.bukkit.inventory.ItemStack import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener -import xyz.alexcrea.cuanvil.util.AnvilXpUtil -import xyz.alexcrea.cuanvil.util.AnvilXpUtil.AnvilCost import xyz.alexcrea.cuanvil.util.MetricsUtil.trackError +import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil +import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil.AnvilCost import java.util.logging.Level import kotlin.reflect.KClass diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/HavenBagsDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/HavenBagsDependency.kt index b2e7ef4..62d9e4e 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/HavenBagsDependency.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/HavenBagsDependency.kt @@ -10,8 +10,8 @@ import valorless.havenbags.HavenBags import valorless.havenbags.features.BagSkin import valorless.havenbags.features.BagUpgrade import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener -import xyz.alexcrea.cuanvil.util.AnvilXpUtil -import xyz.alexcrea.cuanvil.util.AnvilXpUtil.AnvilCost +import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil +import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil.AnvilCost class HavenBagsDependency { diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt index 77bca4a..7497e7f 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt @@ -16,6 +16,7 @@ import org.bukkit.inventory.AnvilInventory import org.bukkit.inventory.InventoryView import org.bukkit.inventory.ItemStack import org.bukkit.inventory.meta.BookMeta +import xyz.alexcrea.cuanvil.anvil.AnvilUseType import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.dependency.economy.EconomyManager import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.setComponentDisplayName @@ -23,13 +24,12 @@ import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener.Companion.ANVIL_INPUT_ 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.AnvilLoreEditUtil -import xyz.alexcrea.cuanvil.util.AnvilUseType -import xyz.alexcrea.cuanvil.util.AnvilXpUtil -import xyz.alexcrea.cuanvil.util.AnvilXpUtil.AnvilCost import xyz.alexcrea.cuanvil.util.CustomRecipeUtil import xyz.alexcrea.cuanvil.util.MiniMessageUtil import xyz.alexcrea.cuanvil.util.UnitRepairUtil.getRepair +import xyz.alexcrea.cuanvil.util.anvil.AnvilLoreEditUtil +import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil +import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil.AnvilCost import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil import xyz.alexcrea.cuanvil.util.config.LoreEditType import java.util.* diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt index ec5cac2..312baba 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt @@ -1,18 +1,8 @@ package xyz.alexcrea.cuanvil.listener -import com.github.stefvanschie.inventoryframework.util.InventoryViewUtil -import com.jankominek.disenchantment.utils.AnvilCostUtils 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.Material import org.bukkit.entity.HumanEntity import org.bukkit.entity.Player import org.bukkit.event.EventHandler @@ -20,18 +10,20 @@ 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.InventoryView import org.bukkit.inventory.ItemStack import org.bukkit.inventory.meta.EnchantmentStorageMeta import org.bukkit.inventory.meta.ItemMeta -import org.bukkit.persistence.PersistentDataType +import xyz.alexcrea.cuanvil.anvil.AnvilMergeUtil.AnvilResult +import xyz.alexcrea.cuanvil.anvil.AnvilMergeUtil.doMerge +import xyz.alexcrea.cuanvil.anvil.AnvilMergeUtil.doRenaming +import xyz.alexcrea.cuanvil.anvil.AnvilMergeUtil.testCustomRecipe +import xyz.alexcrea.cuanvil.anvil.AnvilMergeUtil.testLoreEdit +import xyz.alexcrea.cuanvil.anvil.AnvilMergeUtil.testUnitRepair import xyz.alexcrea.cuanvil.dependency.DependencyManager -import xyz.alexcrea.cuanvil.dialog.AnvilRenameDialog -import xyz.alexcrea.cuanvil.enchant.CAEnchantment -import xyz.alexcrea.cuanvil.util.* -import xyz.alexcrea.cuanvil.util.AnvilXpUtil.AnvilCost +import xyz.alexcrea.cuanvil.util.JustForEasierHotswapUtil import xyz.alexcrea.cuanvil.util.MaterialUtil.isAir -import xyz.alexcrea.cuanvil.util.UnitRepairUtil.getRepair +import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil +import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil.AnvilCost import xyz.alexcrea.cuanvil.util.dialog.AnvilRenameDialogUtil /** @@ -73,8 +65,8 @@ class PrepareAnvilListener : Listener { val second = inventory.getItem(ANVIL_INPUT_RIGHT) if(IS_EMPTY_TEST) { - setNoResult(event, player, view) IS_EMPTY_TEST = false + applyResult(event, player, AnvilResult.EMPTY) return } @@ -86,7 +78,7 @@ class PrepareAnvilListener : Listener { if (isImmutable(first) || isImmutable(second)) { CustomAnvil.verboseLog("Skipping anvil process as one of the two item is immutable") - setNoResult(event, player, view) + applyResult(event, player, AnvilResult.EMPTY) return } @@ -99,42 +91,44 @@ class PrepareAnvilListener : Listener { if (!player.hasPermission(CustomAnvil.affectedByPluginPermission)) return - if(first == null) { - setNoResult(event, player, view) - return - } + val result = getResult(inventory, player, first, second) + applyResult(event, player, result) + } + + fun getResult( + inventory: AnvilInventory, + player: Player, + first: ItemStack?, second: ItemStack?) : AnvilResult + { + if(first == null) + return AnvilResult.EMPTY // Test custom recipe - if (testCustomRecipe(event, inventory, player, first, second)) return + var result = testCustomRecipe(player, first, second) + if (!result.isEmpty()) + return result // Test rename lonely item val shouldTryRename = second.isAir CustomAnvil.verboseLog("checking air in main logic: $shouldTryRename") - if (shouldTryRename) { - if(!doRenaming(event, inventory, player, first)) - setNoResult(event, player, view) - return - } + if (shouldTryRename) + return doRenaming(inventory, player, first) // Test for merge - if (first.canMergeWith(second!!)) { - if(!doMerge(event, inventory, player, first, second)) - setNoResult(event, player, view) - return - } + if (first.canMergeWith(second!!)) + return doMerge(inventory, player, first, second) // Test for unit repair - if (testUnitRepair(event, inventory, player, first, second)) return + result = testUnitRepair(inventory, player, first, second) + if (!result.isEmpty()) + return result // Test for lore edit - if (testLoreEdit(event, inventory, player, first, second)) return + result = testLoreEdit(player, first, second) + if (!result.isEmpty()) + return result - setNoResult(event, player, view) - } - - private fun setNoResult(event: PrepareAnvilEvent, player: Player, view: InventoryView) { - event.result = null - AnvilXpUtil.onNoResult(player, view) + return AnvilResult.EMPTY } private fun tryRenameDialog( @@ -146,20 +140,6 @@ class PrepareAnvilListener : Listener { AnvilRenameDialogUtil.anvilRenameDialog.tryShowDialog(player, event) } - private fun processDialogPCD(it: ItemMeta, player: HumanEntity) { - val keepDialog = ConfigOptions.canUseDialogRename(player) && ConfigOptions.shouldKeepRenameText - - val pdc = it.persistentDataContainer - if(!keepDialog) - pdc.remove(AnvilRenameDialog.PCD_KEEP_RENAME_TEXT_KEY) - else { - val text = AnvilRenameDialogUtil.anvilRenameDialog.currentText(player) - if(text == null || text.isBlank()) - pdc.remove(AnvilRenameDialog.PCD_KEEP_RENAME_TEXT_KEY) - else pdc.set(AnvilRenameDialog.PCD_KEEP_RENAME_TEXT_KEY, PersistentDataType.STRING, text) - } - } - private fun isImmutable(item: ItemStack?): Boolean { if (item.isAir) return false @@ -186,203 +166,14 @@ class PrepareAnvilListener : Listener { return false } - // return true if a custom recipe exist with these ingredients - private fun testCustomRecipe( - event: PrepareAnvilEvent, inventory: AnvilInventory, - player: Player, - first: ItemStack, second: ItemStack? - ): Boolean { - val recipe = CustomRecipeUtil.getCustomRecipe(first, second) - CustomAnvil.verboseLog("custom recipe not null? ${recipe != null}") - if (recipe == null) return false + private fun applyResult(event: PrepareAnvilEvent, player: Player, result: AnvilResult) { + event.result = result.item - val amount = CustomRecipeUtil.getCustomRecipeAmount(recipe, first, second) - - val resultItem: ItemStack = DependencyManager.cloneItem(event, recipe.resultItem!!) - resultItem.amount *= amount - - // Maybe add an option on custom craft to ignore/not ignore penalty ?? - val xpCost = recipe.determineCost(amount, first, resultItem) - - val cost = AnvilCost() - cost.recipe = if (recipe.removeExactLinearXp) AnvilXpUtil.calculateMinimumLevelForXp(xpCost) - else AnvilXpUtil.calculateLevelForXp(xpCost) - - event.result = DependencyManager.tryTreatAnvilResult(event, resultItem, AnvilUseType.CUSTOM_CRAFT, cost) - AnvilXpUtil.setAnvilInvCost(inventory, event.view, player, cost, true) - return true + if(result.item == null) { + AnvilXpUtil.onNoResult(player, event.view) + return + } + AnvilXpUtil.setAnvilInvCost(event.inventory, event.view, player, result.cost, result.ignoreXpRules) } - private fun doRenaming( - event: PrepareAnvilEvent, inventory: AnvilInventory, - player: Player, first: ItemStack - ): Boolean { - val resultItem = DependencyManager.cloneItem(event, first) - val cost = AnvilCost() - cost.rename = handleRename(resultItem, inventory, player) - - // Test/stop if nothing changed. - if (first == resultItem) { - CustomAnvil.log("no right item, But input is same as output") - return false - } - - cost.workPenalty = AnvilXpUtil.calculatePenalty(first, null, resultItem, AnvilUseType.RENAME_ONLY) - - event.result = DependencyManager.tryTreatAnvilResult(event, resultItem, AnvilUseType.RENAME_ONLY, cost) - AnvilXpUtil.setAnvilInvCost(inventory, event.view, player, cost) - return true - } - - private fun handleRename(resultItem: ItemStack, inventory: AnvilInventory, player: HumanEntity): Int { - // Can be null - var renameText = ChatColor.stripColor(inventory.renameText) - - var sumCost = 0 - var useColor = false - if (ConfigOptions.renameColorPossible && renameText != null) { - val component = AnvilColorUtil.handleColor( - renameText, - AnvilColorUtil.renamePermission(player)) - - if (component != null) { - renameText = MiniMessageUtil.legacy_mm.serialize(component) - - sumCost += ConfigOptions.useOfColorCost - useColor = true - } - } - - // Rename item and add renaming cost - resultItem.itemMeta?.let { - val hasDisplayName = it.hasDisplayName() - val displayName = if (!hasDisplayName) null - else if (useColor) it.displayName - else ChatColor.stripColor(it.displayName) - - - if (!displayName.contentEquals(renameText) && !(displayName == null && - renameText == "" || - //TODO on recent paper check effective name instead - renameText == CasedStringUtil.snakeToUpperSpacedCase(resultItem.type.name.lowercase()) - )) { - it.setDisplayName(renameText) - processDialogPCD(it, player) - resultItem.itemMeta = it - - sumCost += ConfigOptions.itemRenameCost - } - - return sumCost - } - return 0 - } - - private fun doMerge( - event: PrepareAnvilEvent, inventory: AnvilInventory, - player: Player, - first: ItemStack, second: ItemStack - ): Boolean { - val newEnchants = first.findEnchantments() - .combineWith(second.findEnchantments(), first, player) - var hasChanged = !isIdentical(first.findEnchantments(), newEnchants) - - val resultItem = DependencyManager.cloneItem(event, first) - val cost = AnvilCost() - if(hasChanged){ - resultItem.setEnchantmentsUnsafe(newEnchants) - // Calculate enchantment cost - AnvilXpUtil.getRightValues(second, resultItem, cost) - } - - // Calculate repair cost - if (!first.isEnchantedBook() && !second.isEnchantedBook()) { - // we only need to be concerned with repair when neither item is a book - val repaired = resultItem.repairFrom(first, second) - cost.repair = if (repaired) ConfigOptions.itemRepairCost else 0 - hasChanged = hasChanged || repaired - } - - // Test/stop if nothing changed. - if (!hasChanged) { - CustomAnvil.log("Mergeable with second, But input is same as output") - return false - } - // As calculatePenalty edit result, we need to calculate penalty after checking equality - cost.workPenalty = AnvilXpUtil.calculatePenalty(first, second, resultItem, AnvilUseType.MERGE) - // Calculate rename cost - cost.rename = handleRename(resultItem, inventory, player) - - // Finally, we set result - event.result = DependencyManager.tryTreatAnvilResult(event, resultItem, AnvilUseType.MERGE, cost) - AnvilXpUtil.setAnvilInvCost(inventory, event.view, player, cost) - return true - } - - private fun isIdentical( - firstEnchants: MutableMap, - resultEnchants: MutableMap - ): Boolean { - if(firstEnchants.size != resultEnchants.size) return false - for (entry in resultEnchants) { - if(firstEnchants.getOrDefault(entry.key, entry.value-1) != entry.value) return false - } - - return true - } - - // return true if there is a valid unit repair with these ingredients - private fun testUnitRepair( - event: PrepareAnvilEvent, inventory: AnvilInventory, player: Player, - first: ItemStack, second: ItemStack - ): Boolean { - val unitRepairAmount = first.getRepair(second) ?: return false - - val resultItem = DependencyManager.cloneItem(event, first) - val cost = AnvilCost() - cost.rename = handleRename(resultItem, inventory, player) - - val repairAmount = resultItem.unitRepair(second.amount, unitRepairAmount) - if (repairAmount > 0) { - cost.repair = repairAmount * ConfigOptions.unitRepairCost - } - // We do not care about right item penalty for unit repair - cost.workPenalty = AnvilXpUtil.calculatePenalty(first, null, resultItem, AnvilUseType.UNIT_REPAIR) - - // Test/stop if nothing changed. - if (first == resultItem) { - CustomAnvil.log("unit repair, But input is same as output") - event.result = null - return true - } - - event.result = DependencyManager.tryTreatAnvilResult(event, resultItem, AnvilUseType.UNIT_REPAIR, cost) - AnvilXpUtil.setAnvilInvCost(inventory, event.view, player, cost) - return true - } - - private fun testLoreEdit( - event: PrepareAnvilEvent, inventory: AnvilInventory, player: Player, - first: ItemStack, second: ItemStack - ): Boolean { - val type = second.type - var result: ItemStack? = null - - val cost = AnvilCost() - if (Material.WRITABLE_BOOK == type) { - result = AnvilLoreEditUtil.tryLoreEditByBook(player, first, second, cost) - } else if (Material.PAPER == type) { - result = AnvilLoreEditUtil.tryLoreEditByPaper(player, first, second, cost) - } - - if (result.isAir || first == result) { - CustomAnvil.log("lore edit, But input is same as output") - event.result = null - return false - } - - event.result = result - AnvilXpUtil.setAnvilInvCost(inventory, event.view, player, cost) - return true - } } \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/recipe/AnvilCustomRecipe.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/recipe/AnvilCustomRecipe.kt index 57eb124..fd079c3 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/recipe/AnvilCustomRecipe.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/recipe/AnvilCustomRecipe.kt @@ -3,11 +3,11 @@ package xyz.alexcrea.cuanvil.recipe import io.delilaheve.CustomAnvil import org.bukkit.configuration.ConfigurationSection import org.bukkit.inventory.ItemStack +import xyz.alexcrea.cuanvil.anvil.AnvilUseType import xyz.alexcrea.cuanvil.config.ConfigHolder import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant -import xyz.alexcrea.cuanvil.util.AnvilUseType -import xyz.alexcrea.cuanvil.util.AnvilXpUtil import xyz.alexcrea.cuanvil.util.MaterialUtil.isAir +import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil class AnvilCustomRecipe( val name: String, diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilColorUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilColorUtil.kt similarity index 99% rename from src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilColorUtil.kt rename to src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilColorUtil.kt index 0492d32..9564b2e 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilColorUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilColorUtil.kt @@ -1,11 +1,11 @@ -package xyz.alexcrea.cuanvil.util +package xyz.alexcrea.cuanvil.util.anvil import io.delilaheve.util.ConfigOptions import net.kyori.adventure.text.Component import org.bukkit.permissions.Permissible +import xyz.alexcrea.cuanvil.util.MiniMessageUtil import java.util.regex.Matcher import java.util.regex.Pattern -import kotlin.text.indexOf object AnvilColorUtil { private val HEX_PATTERN: Pattern = Pattern.compile("#[A-Fa-f0-9]{6}") // pattern to find hexadecimal string diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilLoreEditUtil.kt similarity index 96% rename from src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt rename to src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilLoreEditUtil.kt index 16eca40..edb4a77 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilLoreEditUtil.kt @@ -1,4 +1,4 @@ -package xyz.alexcrea.cuanvil.util +package xyz.alexcrea.cuanvil.util.anvil import net.kyori.adventure.text.Component import org.bukkit.entity.HumanEntity @@ -8,7 +8,7 @@ import org.bukkit.permissions.Permissible import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.componentLore import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.setComponentLore -import xyz.alexcrea.cuanvil.util.AnvilXpUtil.AnvilCost +import xyz.alexcrea.cuanvil.util.MiniMessageUtil import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil import xyz.alexcrea.cuanvil.util.config.LoreEditType import java.util.* @@ -31,7 +31,7 @@ object AnvilLoreEditUtil { player: Permissible, first: ItemStack, book: BookMeta, - cost: AnvilCost + cost: AnvilXpUtil.AnvilCost ): ItemStack? { if (!hasLoreEditByBookPermission(player)) return null @@ -60,7 +60,7 @@ object AnvilLoreEditUtil { return result } - fun handleLoreRemoveByBook(player: Permissible, first: ItemStack, cost: AnvilCost): ItemStack? { + fun handleLoreRemoveByBook(player: Permissible, first: ItemStack, cost: AnvilXpUtil.AnvilCost): ItemStack? { if (!hasLoreEditByBookPermission(player)) return null // remove lore @@ -116,7 +116,7 @@ object AnvilLoreEditUtil { return null } - fun tryLoreEditByBook(player: HumanEntity, first: ItemStack, second: ItemStack, cost: AnvilCost): ItemStack? { + fun tryLoreEditByBook(player: HumanEntity, first: ItemStack, second: ItemStack, cost: AnvilXpUtil.AnvilCost): ItemStack? { val isAppend = bookLoreEditIsAppend(first, second) ?: return null val meta = second.itemMeta as BookMeta @@ -147,7 +147,7 @@ object AnvilLoreEditUtil { player: Permissible, first: ItemStack, second: ItemStack, - cost: AnvilCost + cost: AnvilXpUtil.AnvilCost ): ItemStack? { if (!hasLoreEditByPaperPermission(player)) return null @@ -181,7 +181,7 @@ object AnvilLoreEditUtil { return result } - fun handleLoreRemoveByPaper(player: Permissible, first: ItemStack, cost: AnvilCost): ItemStack? { + fun handleLoreRemoveByPaper(player: Permissible, first: ItemStack, cost: AnvilXpUtil.AnvilCost): ItemStack? { if (!hasLoreEditByPaperPermission(player)) return null // remove lore line @@ -223,7 +223,7 @@ object AnvilLoreEditUtil { player: HumanEntity, first: ItemStack, second: ItemStack, - cost: AnvilCost + cost: AnvilXpUtil.AnvilCost ): ItemStack? { val isAppend = paperLoreEditIsAppend(first, second) ?: return null @@ -232,7 +232,7 @@ object AnvilLoreEditUtil { } private fun baseEditLoreXpCost( - cost: AnvilCost, + cost: AnvilXpUtil.AnvilCost, first: ItemStack, result: ItemStack, editType: LoreEditType diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilUseTypeUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilUseTypeUtil.kt similarity index 96% rename from src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilUseTypeUtil.kt rename to src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilUseTypeUtil.kt index c72a35a..a9c7f39 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilUseTypeUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilUseTypeUtil.kt @@ -1,4 +1,4 @@ -package xyz.alexcrea.cuanvil.util +package xyz.alexcrea.cuanvil.util.anvil import io.delilaheve.util.ConfigOptions diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilXpUtil.kt similarity index 98% rename from src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt rename to src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilXpUtil.kt index cedc07c..0f94661 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilXpUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilXpUtil.kt @@ -1,8 +1,7 @@ -package xyz.alexcrea.cuanvil.util +package xyz.alexcrea.cuanvil.util.anvil import io.delilaheve.CustomAnvil import io.delilaheve.util.ConfigOptions -import io.delilaheve.util.ConfigOptions.getMonetaryMultiplier as moneyMultiplier import io.delilaheve.util.EnchantmentUtil.enchantmentName import io.delilaheve.util.ItemUtil.findEnchantments import io.delilaheve.util.ItemUtil.isEnchantedBook @@ -15,13 +14,16 @@ import org.bukkit.inventory.InventoryView import org.bukkit.inventory.ItemStack import org.bukkit.inventory.meta.Repairable import org.bukkit.persistence.PersistentDataType +import xyz.alexcrea.cuanvil.anvil.AnvilUseType import xyz.alexcrea.cuanvil.config.ConfigHolder import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.dependency.economy.EconomyManager import xyz.alexcrea.cuanvil.group.ConflictType +import xyz.alexcrea.cuanvil.util.AnvilTitleUtil import xyz.alexcrea.cuanvil.util.dialog.AnvilRenameDialogUtil import java.math.BigDecimal import kotlin.math.min +import io.delilaheve.util.ConfigOptions.getMonetaryMultiplier as moneyMultiplier object AnvilXpUtil { diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditType.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditType.kt index 8bd926a..8dab543 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditType.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditType.kt @@ -1,6 +1,6 @@ package xyz.alexcrea.cuanvil.util.config -import xyz.alexcrea.cuanvil.util.AnvilUseType +import xyz.alexcrea.cuanvil.anvil.AnvilUseType import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.ALLOW_COLOR_CODE import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.ALLOW_HEX_COLOR import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.ALLOW_MINIMESSAGE diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/dialog/AnvilRenameDialogUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/dialog/AnvilRenameDialogUtil.kt index a83d8b0..07d4e08 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/dialog/AnvilRenameDialogUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/dialog/AnvilRenameDialogUtil.kt @@ -8,7 +8,7 @@ import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil import xyz.alexcrea.cuanvil.dialog.AnvilRenameDialog import xyz.alexcrea.cuanvil.dialog.AnvilRenameDialogImpl import xyz.alexcrea.cuanvil.update.UpdateUtils -import xyz.alexcrea.cuanvil.util.AnvilColorUtil +import xyz.alexcrea.cuanvil.util.anvil.AnvilColorUtil object AnvilRenameDialogUtil { From e6293be1c68dc93a86f6afa689669a9b638245ff Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 2 Jun 2026 14:01:49 +0200 Subject: [PATCH 171/207] deduplicate unit repair logic --- .../alexcrea/cuanvil/anvil/AnvilMergeLogic.kt | 9 ++ .../cuanvil/listener/AnvilResultListener.kt | 108 ++++++++---------- 2 files changed, 57 insertions(+), 60 deletions(-) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt index 9840cb8..ce0fe0d 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt @@ -225,6 +225,15 @@ object AnvilMergeUtil { ): UnitRepairResult { val unitRepairAmount = first.getRepair(second) ?: return UnitRepairResult.EMPTY + return testUnitRepair(inventory, player, first, second, unitRepairAmount) + } + + fun testUnitRepair( + inventory: AnvilInventory, + player: Player, + first: ItemStack, second: ItemStack, + unitRepairAmount: Double + ): UnitRepairResult { val resultItem = DependencyManager.cloneItem(player, first) val cost = AnvilCost() cost.rename = handleRename(resultItem, inventory, player) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt index 7497e7f..2235183 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt @@ -3,7 +3,6 @@ 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 @@ -16,7 +15,8 @@ import org.bukkit.inventory.AnvilInventory import org.bukkit.inventory.InventoryView import org.bukkit.inventory.ItemStack import org.bukkit.inventory.meta.BookMeta -import xyz.alexcrea.cuanvil.anvil.AnvilUseType +import xyz.alexcrea.cuanvil.anvil.AnvilMergeUtil +import xyz.alexcrea.cuanvil.anvil.AnvilMergeUtil.AnvilResult import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.dependency.economy.EconomyManager import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.setComponentDisplayName @@ -65,8 +65,9 @@ class AnvilResultListener : Listener { val leftItem = inventory.getItem(ANVIL_INPUT_LEFT) ?: return val rightItem = inventory.getItem(ANVIL_INPUT_RIGHT) + // Deny by default. allow if working + event.result = Event.Result.DENY if (GameMode.CREATIVE != player.gameMode && inventory.repairCost >= inventory.maximumRepairCost) { - event.result = Event.Result.DENY return } @@ -83,7 +84,6 @@ class AnvilResultListener : Listener { // Do not continue if there was no change if ((output == inventory.getItem(ANVIL_INPUT_LEFT))) { - event.result = Event.Result.DENY return } @@ -102,10 +102,10 @@ class AnvilResultListener : Listener { } // Unit repair - val unitRepairResult = leftItem.getRepair(rightItem) + val unitRepairResult = leftItem.getRepair(rightItem) // Maybe this should be handlded "above" and like prepare result if (unitRepairResult != null) { onUnitRepairExtract( - leftItem, rightItem, output, + leftItem, rightItem, unitRepairResult, event, player, inventory ) return @@ -238,6 +238,25 @@ class AnvilResultListener : Listener { return true } + // I don't know about side effect of "handling cost" at this level and not before so let be safe + private fun extractAnvilResult( + event: InventoryClickEvent, + player: Player, + inventory: AnvilInventory, + leftItem: ItemStack?, + leftRemoveCount: Int, + rightItem: ItemStack?, + rightRemoveCount: Int, + result: AnvilResult + ): Boolean { + if(result.isEmpty()) return false + + processCost(inventory, player, result.cost) + + return extractAnvilResult(event, player, inventory, leftItem, leftRemoveCount, rightItem, + rightRemoveCount, result.item!!, result.cost) + } + private fun extractAnvilResult( event: InventoryClickEvent, player: Player, @@ -290,59 +309,7 @@ class AnvilResultListener : Listener { 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 - ) - - // Get repair cost - val repairCost = getUnitRepairCost(inventory, player, leftItem, output, resultCopy, resultAmount) - - // And then we give the item manually - extractAnvilResult( - event, player, inventory, - null, 0, - rightItem, resultAmount, - resultCopy, repairCost - ) - } - - private fun getUnitRepairCost( - inventory: AnvilInventory, player: Player, - leftItem: ItemStack, output: ItemStack, - resultCopy: ItemStack, resultAmount: Int - ): AnvilCost { - if (player.gameMode == GameMode.CREATIVE) return AnvilCost(0) - - val cost = AnvilCost() - // Get repairCost - leftItem.itemMeta?.let { leftMeta -> - val leftName = leftMeta.displayName - output.itemMeta?.let { - // Rename cost - if (!leftName.contentEquals(it.displayName)) { - cost.rename += ConfigOptions.itemRenameCost - - // Color cost - if (it.displayName.contains('§')) { - cost.rename += ConfigOptions.useOfColorCost - } - } - } - } - - cost.workPenalty = AnvilXpUtil.calculatePenalty(leftItem, null, resultCopy, AnvilUseType.UNIT_REPAIR) - cost.repair = resultAmount * ConfigOptions.unitRepairCost - + private fun processCost(inventory: AnvilInventory, player: Player, cost: AnvilCost) { var sum = cost.repair if ( @@ -364,8 +331,29 @@ class AnvilResultListener : Listener { || (player.level < sum) ) cost.valid = false } + } - return cost + private fun onUnitRepairExtract( + leftItem: ItemStack, + rightItem: ItemStack, + unitRepairResult: Double, + event: InventoryClickEvent, + player: Player, + inventory: AnvilInventory + ) { + val result = AnvilMergeUtil.testUnitRepair(inventory, player, + leftItem.clone(), rightItem, + unitRepairResult) + + if(result.isEmpty()) return + + // And then we give the item manually + extractAnvilResult( + event, player, inventory, + null, 0, + rightItem, result.repairAmount, + result + ) } private fun getFromLoreEditXpCost( From 106bc724a13f350f87aeccbbba9478bd039b84d9 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 2 Jun 2026 14:36:09 +0200 Subject: [PATCH 172/207] deduplicate lore edit logic --- .../alexcrea/cuanvil/anvil/AnvilMergeLogic.kt | 38 +- .../cuanvil/listener/AnvilResultListener.kt | 328 +++++++++--------- .../cuanvil/listener/PrepareAnvilListener.kt | 12 +- .../cuanvil/util/anvil/AnvilLoreEditUtil.kt | 37 +- .../cuanvil/util/config/LoreEditType.kt | 13 +- 5 files changed, 230 insertions(+), 198 deletions(-) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt index ce0fe0d..b7d8bdb 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt @@ -28,9 +28,10 @@ import xyz.alexcrea.cuanvil.util.anvil.AnvilColorUtil import xyz.alexcrea.cuanvil.util.anvil.AnvilLoreEditUtil import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil.AnvilCost +import xyz.alexcrea.cuanvil.util.config.LoreEditType import xyz.alexcrea.cuanvil.util.dialog.AnvilRenameDialogUtil -object AnvilMergeUtil { +object AnvilMergeLogic { open class AnvilResult { companion object { @@ -64,6 +65,19 @@ object AnvilMergeUtil { } } + + class LoreEditResult: AnvilResult { + companion object { + val EMPTY = LoreEditResult(null, AnvilCost(), LoreEditType.APPEND_PAPER) + } + + val type: LoreEditType + + constructor(item: ItemStack?, cost: AnvilCost, type: LoreEditType) : super(item, cost) { + this.type = type + } + } + fun doRenaming(inventory: AnvilInventory, player: Player, first: ItemStack ): AnvilResult { @@ -258,23 +272,23 @@ object AnvilMergeUtil { fun testLoreEdit( player: Player, first: ItemStack, second: ItemStack - ): AnvilResult { + ): LoreEditResult { val type = second.type - var resultItem: ItemStack? = null - val cost = AnvilCost() - if (Material.WRITABLE_BOOK == type) { - resultItem = AnvilLoreEditUtil.tryLoreEditByBook(player, first, second, cost) - } else if (Material.PAPER == type) { - resultItem = AnvilLoreEditUtil.tryLoreEditByPaper(player, first, second, cost) - } + val result = if (Material.WRITABLE_BOOK == type) + AnvilLoreEditUtil.tryLoreEditByBook(player, first, second) + else if (Material.PAPER == type) + AnvilLoreEditUtil.tryLoreEditByPaper(player, first, second) + else LoreEditResult.EMPTY - if (resultItem.isAir || first == resultItem) { + if(result.isEmpty()) return result + + if (result.item!!.isAir || first == result.item) { CustomAnvil.log("lore edit, But input is same as output") - return AnvilResult.EMPTY + return LoreEditResult.EMPTY } - return AnvilResult(resultItem, cost) + return result } } \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt index 2235183..3e0371a 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt @@ -15,8 +15,9 @@ import org.bukkit.inventory.AnvilInventory import org.bukkit.inventory.InventoryView import org.bukkit.inventory.ItemStack import org.bukkit.inventory.meta.BookMeta -import xyz.alexcrea.cuanvil.anvil.AnvilMergeUtil -import xyz.alexcrea.cuanvil.anvil.AnvilMergeUtil.AnvilResult +import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic +import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.AnvilResult +import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.LoreEditResult import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.dependency.economy.EconomyManager import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.setComponentDisplayName @@ -112,14 +113,14 @@ class AnvilResultListener : Listener { } // For lore edit - if (handleBookLoreEdit(event, inventory, player, leftItem, rightItem, output)) { - return - } else if (handlePaperLoreEdit(event, inventory, player, leftItem, rightItem, output)) { + val loreResult = AnvilMergeLogic.testLoreEdit(player, leftItem, rightItem) + if(!loreResult.isEmpty()) { + if(loreResult.type.isBook) + handleBookLoreEdit(event, inventory, player, leftItem, rightItem, loreResult) + else + handlePaperLoreEdit(event, inventory, player, leftItem, rightItem, loreResult) return } - - // Else there was no working situation somehow so we deny - event.result = Event.Result.DENY } private fun onCustomCraft( @@ -341,7 +342,7 @@ class AnvilResultListener : Listener { player: Player, inventory: AnvilInventory ) { - val result = AnvilMergeUtil.testUnitRepair(inventory, player, + val result = AnvilMergeLogic.testUnitRepair(inventory, player, leftItem.clone(), rightItem, unitRepairResult) @@ -356,99 +357,90 @@ class AnvilResultListener : Listener { ) } - private fun getFromLoreEditXpCost( - cost: AnvilCost, - player: Player, - inventory: AnvilInventory, - ): AnvilCost { - if (GameMode.CREATIVE == player.gameMode) return AnvilCost(0) - - if (ConfigOptions.shouldUseMoney(player)) { - cost.isMonetary = true - if (!EconomyManager.economy!!.has(player, cost.asMonetaryCost())) - cost.valid = false - } else { - val repairCost = cost.asXpCost() - - if ((inventory.maximumRepairCost <= repairCost) - || (player.level < repairCost) - ) - cost.valid = false - } - - return cost - } - private fun handleBookLoreEdit( event: InventoryClickEvent, inventory: AnvilInventory, player: Player, leftItem: ItemStack, rightItem: ItemStack, - output: ItemStack, - ): Boolean { - if (Material.WRITABLE_BOOK != rightItem.type) return false - val bookMeta = rightItem.itemMeta as BookMeta? ?: return false + result: LoreEditResult + ) { + if (result.type.isAppend) + handleBookLoreAppend(event, inventory, player, rightItem, result) + else + handleBookLoreRemove(event, inventory, player, leftItem, rightItem, result) + } - val editType = AnvilLoreEditUtil.bookLoreEditIsAppend(leftItem, rightItem) ?: return false + private fun handleBookLoreAppend( + event: InventoryClickEvent, + inventory: AnvilInventory, + player: Player, + rightItem: ItemStack, + result: LoreEditResult + ) { + val bookMeta = rightItem.itemMeta as BookMeta? ?: return - val cost = AnvilCost() - if (editType) { - if (output != AnvilLoreEditUtil.handleLoreAppendByBook(player, leftItem, bookMeta, cost)) return false - - // Remove pages to book - val clearedBook: ItemStack? - if (LoreEditType.APPEND_BOOK.doConsume) { - clearedBook = null - } else { - clearedBook = rightItem.clone() - bookMeta.pages = Collections.emptyList() - clearedBook.itemMeta = bookMeta - } - - return extractAnvilResult( - event, player, inventory, - null, 0, - clearedBook, 0, - output, getFromLoreEditXpCost(cost, player, inventory) - ) + // Remove pages to book + val clearedBook: ItemStack? + if (LoreEditType.APPEND_BOOK.doConsume) { + clearedBook = null } else { - if (output != AnvilLoreEditUtil.handleLoreRemoveByBook(player, leftItem, cost)) return false + clearedBook = rightItem.clone() + bookMeta.pages = Collections.emptyList() + clearedBook.itemMeta = bookMeta + } - // fill book meta - val lore = DependencyManager.stripLore(leftItem) - if (lore.isEmpty()) return false + extractAnvilResult( + event, player, inventory, + null, 0, + clearedBook, 0, + result + ) + } - val rightCopy: ItemStack? - if (LoreEditType.REMOVE_BOOK.doConsume) { - rightCopy = null - } else { - // Uncolor the page - AnvilLoreEditUtil.uncolorLines(player, lore, LoreEditType.REMOVE_BOOK) + private fun handleBookLoreRemove( + event: InventoryClickEvent, + inventory: AnvilInventory, + player: Player, + leftItem: ItemStack, + rightItem: ItemStack, + result: LoreEditResult + ){ + val bookMeta = rightItem.itemMeta as BookMeta? ?: return - val bookPage = StringBuilder() - lore.forEach { - if (bookPage.isNotEmpty()) bookPage.append('\n') - if (it == null) return@forEach + // fill book meta + val lore = DependencyManager.stripLore(leftItem) + if (lore.isEmpty()) return - bookPage.append(MiniMessageUtil.plain_text_mm.serialize(it)) - } + val rightCopy: ItemStack? + if (LoreEditType.REMOVE_BOOK.doConsume) { + rightCopy = null + } else { + // Uncolor the page + AnvilLoreEditUtil.uncolorLines(player, lore, LoreEditType.REMOVE_BOOK) - val resultPage = bookPage.toString() - //TODO maybe check page size ? bc it may be too big ??? + val bookPage = StringBuilder() + lore.forEach { + if (bookPage.isNotEmpty()) bookPage.append('\n') + if (it == null) return@forEach - rightCopy = rightItem.clone() - bookMeta.setPages(resultPage) - rightCopy.itemMeta = bookMeta + bookPage.append(MiniMessageUtil.plain_text_mm.serialize(it)) } - return extractAnvilResult( - event, player, inventory, - null, 0, - rightCopy, 0, - output, getFromLoreEditXpCost(cost, player, inventory) - ) + val resultPage = bookPage.toString() + //TODO maybe check page size ? bc it may be too big ??? + + rightCopy = rightItem.clone() + bookMeta.setPages(resultPage) + rightCopy.itemMeta = bookMeta } + + extractAnvilResult( + event, player, inventory, + null, 0, + rightCopy, 0, + result + ) } private fun handlePaperLoreEdit( @@ -457,89 +449,101 @@ class AnvilResultListener : Listener { player: Player, leftItem: ItemStack, rightItem: ItemStack, - output: ItemStack, - ): Boolean { - if (Material.PAPER != rightItem.type) return false - val paperMeta = rightItem.itemMeta ?: return false + result: LoreEditResult + ) { + if (result.type.isAppend) + handlePaperLoreAppend(event, inventory, player, rightItem, result) + else + handlePaperLoreRemove(event, inventory, player, leftItem, rightItem, result) + } - val editTypeIsAppend = AnvilLoreEditUtil.paperLoreEditIsAppend(leftItem, rightItem) ?: return false + private fun handlePaperLoreAppend( + event: InventoryClickEvent, + inventory: AnvilInventory, + player: Player, + rightItem: ItemStack, + result: LoreEditResult + ) { + val paperMeta = rightItem.itemMeta ?: return - val cost = AnvilCost() - if (editTypeIsAppend) { - if (output != AnvilLoreEditUtil.handleLoreAppendByPaper(player, leftItem, rightItem, cost)) return false - - val paperCopy: ItemStack? - if (LoreEditType.APPEND_PAPER.doConsume) { - paperCopy = null - } else { - // Remove custom name to paper - paperCopy = rightItem.clone() - paperCopy.amount = 1 - paperMeta.setComponentDisplayName(null) - paperCopy.itemMeta = paperMeta - } - - return if (rightItem.amount > 1) { - extractAnvilResult( - event, player, inventory, - paperCopy, 0, - rightItem, 1, - output, getFromLoreEditXpCost(cost, player, inventory) - ) - } else { - extractAnvilResult( - event, player, inventory, - null, 0, - paperCopy, 0, - output, getFromLoreEditXpCost(cost, player, inventory) - ) - } + val paperCopy: ItemStack? + if (LoreEditType.APPEND_PAPER.doConsume) { + paperCopy = null } else { - if (output != AnvilLoreEditUtil.handleLoreRemoveByPaper(player, leftItem, cost)) return false - - val leftMeta = leftItem.itemMeta - if (leftMeta == null || !leftMeta.hasLore()) return false - val lore = DependencyManager.stripLore(leftItem) - if (lore.isEmpty()) return false - - // Create result item - val rightClone: ItemStack? - if (LoreEditType.REMOVE_PAPER.doConsume) { - rightClone = null - } else { - val removeEnd = LoreEditConfigUtil.paperLoreOrderIsEnd - val line = if (removeEnd) lore[lore.size - 1] - else lore[0] - - // uncolor the line - val ref = AtomicReference(line) - AnvilLoreEditUtil.uncolorLine(player, ref, LoreEditType.REMOVE_PAPER) - - rightClone = rightItem.clone() - rightClone.amount = 1 - - val resultMeta = rightClone.itemMeta ?: return false - resultMeta.setComponentDisplayName(ref.get()) - rightClone.itemMeta = resultMeta - } - - return if (rightItem.amount > 1) { - extractAnvilResult( - event, player, inventory, - rightClone, 0, - rightItem, 1, - output, getFromLoreEditXpCost(cost, player, inventory) - ) - } else { - extractAnvilResult( - event, player, inventory, - null, 0, - rightClone, 0, - output, getFromLoreEditXpCost(cost, player, inventory) - ) - } + // Remove custom name to paper + paperCopy = rightItem.clone() + paperCopy.amount = 1 + paperMeta.setComponentDisplayName(null) + paperCopy.itemMeta = paperMeta } + if (rightItem.amount > 1) { + extractAnvilResult( + event, player, inventory, + paperCopy, 0, + rightItem, 1, + result + ) + } else { + extractAnvilResult( + event, player, inventory, + null, 0, + paperCopy, 0, + result + ) + } + } + + private fun handlePaperLoreRemove( + event: InventoryClickEvent, + inventory: AnvilInventory, + player: Player, + leftItem: ItemStack, + rightItem: ItemStack, + result: LoreEditResult + ) { + val leftMeta = leftItem.itemMeta + if (leftMeta == null || !leftMeta.hasLore()) return + + val lore = DependencyManager.stripLore(leftItem) + if (lore.isEmpty()) return + + // Create result item + val rightClone: ItemStack? + if (LoreEditType.REMOVE_PAPER.doConsume) { + rightClone = null + } else { + val removeEnd = LoreEditConfigUtil.paperLoreOrderIsEnd + val line = if (removeEnd) lore[lore.size - 1] + else lore[0] + + // uncolor the line + val ref = AtomicReference(line) + AnvilLoreEditUtil.uncolorLine(player, ref, LoreEditType.REMOVE_PAPER) + + rightClone = rightItem.clone() + rightClone.amount = 1 + + val resultMeta = rightClone.itemMeta ?: return + resultMeta.setComponentDisplayName(ref.get()) + rightClone.itemMeta = resultMeta + } + + if (rightItem.amount > 1) { + extractAnvilResult( + event, player, inventory, + rightClone, 0, + rightItem, 1, + result + ) + } else { + extractAnvilResult( + event, player, inventory, + null, 0, + rightClone, 0, + result + ) + } } /** diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt index 312baba..0c7121f 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt @@ -13,12 +13,12 @@ import org.bukkit.inventory.AnvilInventory import org.bukkit.inventory.ItemStack import org.bukkit.inventory.meta.EnchantmentStorageMeta import org.bukkit.inventory.meta.ItemMeta -import xyz.alexcrea.cuanvil.anvil.AnvilMergeUtil.AnvilResult -import xyz.alexcrea.cuanvil.anvil.AnvilMergeUtil.doMerge -import xyz.alexcrea.cuanvil.anvil.AnvilMergeUtil.doRenaming -import xyz.alexcrea.cuanvil.anvil.AnvilMergeUtil.testCustomRecipe -import xyz.alexcrea.cuanvil.anvil.AnvilMergeUtil.testLoreEdit -import xyz.alexcrea.cuanvil.anvil.AnvilMergeUtil.testUnitRepair +import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.AnvilResult +import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.doMerge +import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.doRenaming +import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.testCustomRecipe +import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.testLoreEdit +import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.testUnitRepair import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.util.JustForEasierHotswapUtil import xyz.alexcrea.cuanvil.util.MaterialUtil.isAir diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilLoreEditUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilLoreEditUtil.kt index edb4a77..e9bb633 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilLoreEditUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilLoreEditUtil.kt @@ -5,10 +5,12 @@ import org.bukkit.entity.HumanEntity import org.bukkit.inventory.ItemStack import org.bukkit.inventory.meta.BookMeta import org.bukkit.permissions.Permissible +import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.LoreEditResult import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.componentLore import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.setComponentLore import xyz.alexcrea.cuanvil.util.MiniMessageUtil +import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil.AnvilCost import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil import xyz.alexcrea.cuanvil.util.config.LoreEditType import java.util.* @@ -31,7 +33,7 @@ object AnvilLoreEditUtil { player: Permissible, first: ItemStack, book: BookMeta, - cost: AnvilXpUtil.AnvilCost + cost: AnvilCost ): ItemStack? { if (!hasLoreEditByBookPermission(player)) return null @@ -60,7 +62,7 @@ object AnvilLoreEditUtil { return result } - fun handleLoreRemoveByBook(player: Permissible, first: ItemStack, cost: AnvilXpUtil.AnvilCost): ItemStack? { + fun handleLoreRemoveByBook(player: Permissible, first: ItemStack, cost: AnvilCost): ItemStack? { if (!hasLoreEditByBookPermission(player)) return null // remove lore @@ -116,12 +118,17 @@ object AnvilLoreEditUtil { return null } - fun tryLoreEditByBook(player: HumanEntity, first: ItemStack, second: ItemStack, cost: AnvilXpUtil.AnvilCost): ItemStack? { - val isAppend = bookLoreEditIsAppend(first, second) ?: return null + fun tryLoreEditByBook(player: HumanEntity, first: ItemStack, second: ItemStack): LoreEditResult { + val isAppend = bookLoreEditIsAppend(first, second) ?: return LoreEditResult.EMPTY + val type = if(isAppend) LoreEditType.APPEND_BOOK else LoreEditType.REMOVE_BOOK val meta = second.itemMeta as BookMeta - return if (isAppend) handleLoreAppendByBook(player, first, meta, cost) + val cost = AnvilCost() + val item = if (isAppend) + handleLoreAppendByBook(player, first, meta, cost) else handleLoreRemoveByBook(player, first, cost) + + return LoreEditResult(item, cost, type) } // Return true if appended, false if removed, null if neither @@ -147,7 +154,7 @@ object AnvilLoreEditUtil { player: Permissible, first: ItemStack, second: ItemStack, - cost: AnvilXpUtil.AnvilCost + cost: AnvilCost ): ItemStack? { if (!hasLoreEditByPaperPermission(player)) return null @@ -181,7 +188,7 @@ object AnvilLoreEditUtil { return result } - fun handleLoreRemoveByPaper(player: Permissible, first: ItemStack, cost: AnvilXpUtil.AnvilCost): ItemStack? { + fun handleLoreRemoveByPaper(player: Permissible, first: ItemStack, cost: AnvilCost): ItemStack? { if (!hasLoreEditByPaperPermission(player)) return null // remove lore line @@ -222,17 +229,21 @@ object AnvilLoreEditUtil { fun tryLoreEditByPaper( player: HumanEntity, first: ItemStack, - second: ItemStack, - cost: AnvilXpUtil.AnvilCost - ): ItemStack? { - val isAppend = paperLoreEditIsAppend(first, second) ?: return null + second: ItemStack + ): LoreEditResult { + val isAppend = paperLoreEditIsAppend(first, second) ?: return LoreEditResult.EMPTY + val type = if(isAppend) LoreEditType.APPEND_BOOK else LoreEditType.REMOVE_BOOK - return if (isAppend) handleLoreAppendByPaper(player, first, second, cost) + val cost = AnvilCost() + val item = if (isAppend) + handleLoreAppendByPaper(player, first, second, cost) else handleLoreRemoveByPaper(player, first, cost) + + return LoreEditResult(item, cost, type) } private fun baseEditLoreXpCost( - cost: AnvilXpUtil.AnvilCost, + cost: AnvilCost, first: ItemStack, result: ItemStack, editType: LoreEditType diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditType.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditType.kt index 8dab543..c094c71 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditType.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/config/LoreEditType.kt @@ -18,20 +18,23 @@ import xyz.alexcrea.cuanvil.config.ConfigHolder.DEFAULT_CONFIG as CONFIG enum class LoreEditType( val rootPath: String, val useType: AnvilUseType, + val isBook: Boolean, val isAppend: Boolean, val isMultiLine: Boolean, ) { - APPEND_BOOK(AnvilUseType.LORE_EDIT_BOOK_APPEND, true, true), - REMOVE_BOOK(AnvilUseType.LORE_EDIT_BOOK_REMOVE, false, true), - APPEND_PAPER(AnvilUseType.LORE_EDIT_PAPER_APPEND, true, false), - REMOVE_PAPER(AnvilUseType.LORE_EDIT_PAPER_REMOVE, false, false), + APPEND_BOOK(AnvilUseType.LORE_EDIT_BOOK_APPEND, true, true, true), + REMOVE_BOOK(AnvilUseType.LORE_EDIT_BOOK_REMOVE, true, false, true), + APPEND_PAPER(AnvilUseType.LORE_EDIT_PAPER_APPEND, false, true, false), + REMOVE_PAPER(AnvilUseType.LORE_EDIT_PAPER_REMOVE, false, false, false), ; constructor( useType: AnvilUseType, + isPaper: Boolean, isAppend: Boolean, isMultiLine: Boolean, - ) : this(useType.path, useType, isAppend, isMultiLine) + ) : this(useType.path, useType, + isPaper, isAppend, isMultiLine) /** * If this edit type is enabled From edceba879fc34f733cd4c61eeb5b0f0f4bedf618 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 2 Jun 2026 16:18:16 +0200 Subject: [PATCH 173/207] custom craft logic deduplication --- .../alexcrea/cuanvil/anvil/AnvilMergeLogic.kt | 27 ++++++++-- .../cuanvil/listener/AnvilResultListener.kt | 54 ++++++++----------- .../cuanvil/listener/PrepareAnvilListener.kt | 2 +- .../cuanvil/util/anvil/AnvilXpUtil.kt | 4 +- .../cuanvil/util/AnvilFuseTestUtil.java | 4 +- 5 files changed, 51 insertions(+), 40 deletions(-) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt index b7d8bdb..8699285 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt @@ -19,6 +19,7 @@ import org.bukkit.persistence.PersistentDataType import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.dialog.AnvilRenameDialog import xyz.alexcrea.cuanvil.enchant.CAEnchantment +import xyz.alexcrea.cuanvil.recipe.AnvilCustomRecipe import xyz.alexcrea.cuanvil.util.CasedStringUtil import xyz.alexcrea.cuanvil.util.CustomRecipeUtil import xyz.alexcrea.cuanvil.util.MaterialUtil.isAir @@ -28,6 +29,7 @@ import xyz.alexcrea.cuanvil.util.anvil.AnvilColorUtil import xyz.alexcrea.cuanvil.util.anvil.AnvilLoreEditUtil import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil.AnvilCost +import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil.CustomCraftCost import xyz.alexcrea.cuanvil.util.config.LoreEditType import xyz.alexcrea.cuanvil.util.dialog.AnvilRenameDialogUtil @@ -65,6 +67,22 @@ object AnvilMergeLogic { } } + class CustomCraftResult: AnvilResult { + companion object { + val EMPTY = CustomCraftResult(null, CustomCraftCost(0), 0, null) + } + + val customCraftCost: CustomCraftCost + val amount: Int + val recipe: AnvilCustomRecipe? + + constructor(item: ItemStack?, cost: CustomCraftCost, + amount: Int, recipe: AnvilCustomRecipe?) : super(item, cost, true) { + this.customCraftCost = cost + this.amount = amount + this.recipe = recipe + } + } class LoreEditResult: AnvilResult { companion object { @@ -211,10 +229,10 @@ object AnvilMergeLogic { fun testCustomRecipe( player: Player, first: ItemStack, second: ItemStack? - ): AnvilResult { + ): CustomCraftResult { val recipe = CustomRecipeUtil.getCustomRecipe(first, second) CustomAnvil.verboseLog("custom recipe not null? ${recipe != null}") - if (recipe == null) return AnvilResult.EMPTY + if (recipe == null) return CustomCraftResult.EMPTY val amount = CustomRecipeUtil.getCustomRecipeAmount(recipe, first, second) @@ -224,12 +242,13 @@ object AnvilMergeLogic { // Maybe add an option on custom craft to ignore/not ignore penalty ?? val xpCost = recipe.determineCost(amount, first, resultItem) - val cost = AnvilCost() + val cost = CustomCraftCost(xpCost) + // This is for displayed cost cost.recipe = if (recipe.removeExactLinearXp) AnvilXpUtil.calculateMinimumLevelForXp(xpCost) else AnvilXpUtil.calculateLevelForXp(xpCost) val result = DependencyManager.tryTreatAnvilResult(resultItem, AnvilUseType.CUSTOM_CRAFT, cost) - return AnvilResult(result, cost, true) + return CustomCraftResult(result, cost, amount, recipe) } fun testUnitRepair( diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt index 3e0371a..d63e2a6 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt @@ -17,7 +17,9 @@ import org.bukkit.inventory.ItemStack import org.bukkit.inventory.meta.BookMeta import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.AnvilResult +import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.CustomCraftResult import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.LoreEditResult +import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.UnitRepairResult import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.dependency.economy.EconomyManager import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.setComponentDisplayName @@ -27,7 +29,6 @@ import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener.Companion.ANVIL_OUTPUT import xyz.alexcrea.cuanvil.recipe.AnvilCustomRecipe import xyz.alexcrea.cuanvil.util.CustomRecipeUtil import xyz.alexcrea.cuanvil.util.MiniMessageUtil -import xyz.alexcrea.cuanvil.util.UnitRepairUtil.getRepair import xyz.alexcrea.cuanvil.util.anvil.AnvilLoreEditUtil import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil.AnvilCost @@ -73,12 +74,11 @@ class AnvilResultListener : Listener { } // Test custom recipe - val recipe = CustomRecipeUtil.getCustomRecipe(leftItem, rightItem) - if (recipe != null) { - event.result = Event.Result.ALLOW + val customRecipeResult = AnvilMergeLogic.testCustomRecipe(player, leftItem, rightItem) + if (!customRecipeResult.isEmpty()) { onCustomCraft( - event, recipe, player, - leftItem, rightItem, output, inventory + event, player, inventory, + leftItem, rightItem, customRecipeResult ) return } @@ -103,11 +103,13 @@ class AnvilResultListener : Listener { } // Unit repair - val unitRepairResult = leftItem.getRepair(rightItem) // Maybe this should be handlded "above" and like prepare result - if (unitRepairResult != null) { + val unitRepairResult = AnvilMergeLogic.testUnitRepair( + inventory, player, + leftItem, rightItem) + if (unitRepairResult.isEmpty()) { onUnitRepairExtract( - leftItem, rightItem, - unitRepairResult, event, player, inventory + rightItem, event, player, inventory, + unitRepairResult ) return } @@ -125,19 +127,14 @@ class AnvilResultListener : Listener { private fun onCustomCraft( event: InventoryClickEvent, - recipe: AnvilCustomRecipe, player: Player, + inventory: AnvilInventory, leftItem: ItemStack, rightItem: ItemStack?, - output: ItemStack, - inventory: AnvilInventory + result: CustomCraftResult, ) { - event.result = Event.Result.DENY - - if (recipe.leftItem == null) return // in case it changed - - val amount = CustomRecipeUtil.getCustomRecipeAmount(recipe, leftItem, rightItem) - val xpCost = recipe.determineCost(amount, leftItem, output) + val recipe = result.recipe!! + val xpCost = result.customCraftCost.rawCost val finalCost = if (recipe.removeExactLinearXp) xpCost else AnvilXpUtil.calculateLevelForXp(xpCost) @@ -166,7 +163,7 @@ class AnvilResultListener : Listener { player, leftItem, rightItem, - amount, + result.amount, finalCost, recipe.removeExactLinearXp ) @@ -174,9 +171,9 @@ class AnvilResultListener : Listener { // Finally, we add the item to the player if (slotDestination.type == SlotType.CURSOR) { - player.setItemOnCursor(output) + player.setItemOnCursor(result.item) } else {// We assume SlotType == SlotType.INVENTORY - player.inventory.setItem(slotDestination.slot, output) + player.inventory.setItem(slotDestination.slot, result.item) } } @@ -335,20 +332,13 @@ class AnvilResultListener : Listener { } private fun onUnitRepairExtract( - leftItem: ItemStack, rightItem: ItemStack, - unitRepairResult: Double, event: InventoryClickEvent, player: Player, - inventory: AnvilInventory + inventory: AnvilInventory, + result: UnitRepairResult, ) { - val result = AnvilMergeLogic.testUnitRepair(inventory, player, - leftItem.clone(), rightItem, - unitRepairResult) - - if(result.isEmpty()) return - - // And then we give the item manually + // We give the item manually extractAnvilResult( event, player, inventory, null, 0, diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt index 0c7121f..00a2053 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt @@ -104,7 +104,7 @@ class PrepareAnvilListener : Listener { return AnvilResult.EMPTY // Test custom recipe - var result = testCustomRecipe(player, first, second) + var result: AnvilResult = testCustomRecipe(player, first, second) if (!result.isEmpty()) return result 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 0f94661..997b260 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilXpUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilXpUtil.kt @@ -29,7 +29,7 @@ object AnvilXpUtil { const val EXCLUSIVE_PENALTY_PREFIX = "repair_cost" - class AnvilCost { + open class AnvilCost { private val isAlone: Boolean var valid = true // Get set as invalid if cost can be satisfied var isMonetary = false @@ -71,6 +71,8 @@ object AnvilXpUtil { } } + class CustomCraftCost(val rawCost: Int): AnvilCost() + /** * Display the required cost (either as xp or as ) */ diff --git a/src/test/java/xyz/alexcrea/cuanvil/util/AnvilFuseTestUtil.java b/src/test/java/xyz/alexcrea/cuanvil/util/AnvilFuseTestUtil.java index 1d3c5f4..6f5c7bb 100644 --- a/src/test/java/xyz/alexcrea/cuanvil/util/AnvilFuseTestUtil.java +++ b/src/test/java/xyz/alexcrea/cuanvil/util/AnvilFuseTestUtil.java @@ -195,7 +195,7 @@ public class AnvilFuseTestUtil { simulateClick(anvil, player, data.expectedResult()); - // Should have similated the click + // Should have simulated the click assertEqual(data.leftItem(), anvil.getFirstItem()); assertEqual(data.rightItem(), anvil.getSecondItem()); assertEqual(data.resultSlotItem(), anvil.getResult()); @@ -260,7 +260,7 @@ public class AnvilFuseTestUtil { } public static boolean isAir(@Nullable ItemStack item) { - return item == null || item.isEmpty(); + return item == null || item.isEmpty() || item.getAmount() == 0; } public static void assertPriceEqual(Integer expectedPrice, int price) { From d0078e528d91bf841f350500a235e64d1391ae4d Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 2 Jun 2026 16:50:14 +0200 Subject: [PATCH 174/207] fix creative price and other small fixes --- .../cuanvil/listener/AnvilResultListener.kt | 66 ++++++++++--------- .../cuanvil/listener/PrepareAnvilListener.kt | 4 +- .../cuanvil/util/JustForEasierHotswapUtil.kt | 17 ----- 3 files changed, 38 insertions(+), 49 deletions(-) delete mode 100644 src/main/kotlin/xyz/alexcrea/cuanvil/util/JustForEasierHotswapUtil.kt diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt index d63e2a6..552e777 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt @@ -90,15 +90,29 @@ class AnvilResultListener : Listener { // Rename if (rightItem == null) { - // BRUH - event.result = Event.Result.ALLOW + val result = AnvilMergeLogic.doRenaming(inventory, player, leftItem) + if(result.isEmpty()) return + + extractAnvilResult( + event, player, inventory, + null, 0, + null, 0, + result + ) return } // Merge val canMerge = leftItem.canMergeWith(rightItem) if (canMerge) { - event.result = Event.Result.ALLOW + val result = AnvilMergeLogic.doMerge(inventory, player, leftItem, rightItem) + + extractAnvilResult( + event, player, inventory, + null, 0, + null, 0, + result + ) return } @@ -195,6 +209,7 @@ class AnvilResultListener : Listener { inventory.setItem(ANVIL_INPUT_LEFT, leftItem) if (player.gameMode != GameMode.CREATIVE) { + //TODO monetary cost ? somehow if (linearCost) { val levelXp = AnvilXpUtil.calculateXpForLevel(player.level) val delta = AnvilXpUtil.calculateXpForLevel(player.level + 1) - levelXp @@ -236,7 +251,18 @@ class AnvilResultListener : Listener { return true } - // I don't know about side effect of "handling cost" at this level and not before so let be safe + private fun tryRemoveCost(player: Player, cost: AnvilCost): Boolean { + if(player.gameMode == GameMode.CREATIVE) return true + if(cost.isMonetary) { + val result = EconomyManager.economy!!.remove(player, cost.asMonetaryCost()) + if(!result) return false + } else { + player.level -= cost.asXpCost() + } + + return true + } + private fun extractAnvilResult( event: InventoryClickEvent, player: Player, @@ -249,28 +275,13 @@ class AnvilResultListener : Listener { ): Boolean { if(result.isEmpty()) return false - processCost(inventory, player, result.cost) - - return extractAnvilResult(event, player, inventory, leftItem, leftRemoveCount, rightItem, - rightRemoveCount, result.item!!, result.cost) - } - - private fun extractAnvilResult( - event: InventoryClickEvent, - player: Player, - inventory: AnvilInventory, - leftItem: ItemStack?, - leftRemoveCount: Int, - rightItem: ItemStack?, - rightRemoveCount: Int, - output: ItemStack, - cost: AnvilCost, - ): Boolean { // To avoid vanilla, we cancel the event event.result = Event.Result.DENY event.isCancelled = true + val cost = result.cost - if (!cost.valid) return false + processCost(inventory, player, cost) + if (!cost.valid && player.gameMode != GameMode.CREATIVE) return false // Where should we get the item val slotDestination = getActionSlot(event, player) @@ -278,12 +289,7 @@ class AnvilResultListener : Listener { // If not creative middle click... if (event.click != ClickType.MIDDLE) { - if(cost.isMonetary) { - val result = EconomyManager.economy!!.remove(player, cost.asMonetaryCost()) - if(!result) return false - } else { - player.level -= cost.asXpCost() - } + if(!tryRemoveCost(player, cost)) return false // We remove what should be removed if (leftItem != null) leftItem.amount -= leftRemoveCount @@ -298,9 +304,9 @@ class AnvilResultListener : Listener { // Finally, we add the item to the player if (SlotType.CURSOR == slotDestination.type) { - player.setItemOnCursor(output) + player.setItemOnCursor(result.item) } else {// We assume SlotType == SlotType.INVENTORY - player.inventory.setItem(slotDestination.slot, output) + player.inventory.setItem(slotDestination.slot, result.item) } // TODO probably anvil damage & sound here ?? diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt index 00a2053..21438ae 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt @@ -1,5 +1,6 @@ package xyz.alexcrea.cuanvil.listener +import com.github.stefvanschie.inventoryframework.util.InventoryViewUtil import io.delilaheve.CustomAnvil import io.delilaheve.util.ConfigOptions import io.delilaheve.util.ItemUtil.canMergeWith @@ -20,7 +21,6 @@ import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.testCustomRecipe import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.testLoreEdit import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.testUnitRepair import xyz.alexcrea.cuanvil.dependency.DependencyManager -import xyz.alexcrea.cuanvil.util.JustForEasierHotswapUtil import xyz.alexcrea.cuanvil.util.MaterialUtil.isAir import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil.AnvilCost @@ -49,7 +49,7 @@ class PrepareAnvilListener : Listener { val view = event.view val inventory = event.inventory - val player = JustForEasierHotswapUtil.getPlayerFromView(view) + val player = InventoryViewUtil.getInstance().getPlayer(view) if(player !is Player) return tryRenameDialog(player, event) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/JustForEasierHotswapUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/JustForEasierHotswapUtil.kt deleted file mode 100644 index ea0f7ec..0000000 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/JustForEasierHotswapUtil.kt +++ /dev/null @@ -1,17 +0,0 @@ -package xyz.alexcrea.cuanvil.util - -import com.github.stefvanschie.inventoryframework.util.InventoryViewUtil -import org.bukkit.entity.HumanEntity -import org.bukkit.inventory.InventoryView - -// Hotswap to not relocate -// So I just put small thing calling relocating method here to enable to hotswap more class -// Especially for PrepareAnvilListener -// Will be able to replace that on legacy removal so really temporary -object JustForEasierHotswapUtil { - - fun getPlayerFromView(view: InventoryView): HumanEntity { - return InventoryViewUtil.getInstance().getPlayer(view) - } - -} \ No newline at end of file From 2768c0a0dc77972e449e834e0a3fb101d589ae17 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 2 Jun 2026 23:53:18 +0200 Subject: [PATCH 175/207] custom craft monetary cost fixed --- .../cuanvil/listener/AnvilResultListener.kt | 80 +++++++++++-------- .../cuanvil/util/anvil/AnvilXpUtil.kt | 10 ++- 2 files changed, 56 insertions(+), 34 deletions(-) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt index 552e777..549fd9a 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt @@ -26,7 +26,6 @@ import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.setComponentDisplayName 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.CustomRecipeUtil import xyz.alexcrea.cuanvil.util.MiniMessageUtil import xyz.alexcrea.cuanvil.util.anvil.AnvilLoreEditUtil @@ -34,6 +33,7 @@ import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil.AnvilCost import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil import xyz.alexcrea.cuanvil.util.config.LoreEditType +import java.math.BigDecimal import java.util.* import java.util.concurrent.atomic.AtomicReference import kotlin.math.min @@ -148,14 +148,20 @@ class AnvilResultListener : Listener { result: CustomCraftResult, ) { val recipe = result.recipe!! - val xpCost = result.customCraftCost.rawCost + val rawCost = result.customCraftCost.rawCost val finalCost = - if (recipe.removeExactLinearXp) xpCost - else AnvilXpUtil.calculateLevelForXp(xpCost) + if (recipe.removeExactLinearXp) rawCost + else AnvilXpUtil.calculateLevelForXp(rawCost) + + CustomAnvil.log("gamemode: ${player.gameMode != GameMode.CREATIVE}, " + + "cost: $finalCost, level: ${player.level}, " + + "result: ${player.totalExperience < finalCost} ${player.level < finalCost}") - CustomAnvil.log("gamemode: ${player.gameMode != GameMode.CREATIVE}, cost: $finalCost, level: ${player.level}, result: ${player.totalExperience < finalCost} ${player.level < finalCost}") if (player.gameMode != GameMode.CREATIVE) { - if (recipe.removeExactLinearXp) { + if(ConfigOptions.shouldUseMoney(player)) { + result.cost.isMonetary = true + if(!EconomyManager.economy!!.has(player, BigDecimal(rawCost))) return + } else if (recipe.removeExactLinearXp) { val levelXp = AnvilXpUtil.calculateXpForLevel(player.level) val delta = AnvilXpUtil.calculateXpForLevel(player.level + 1) - levelXp val totalXp = levelXp + player.exp * delta @@ -172,14 +178,11 @@ class AnvilResultListener : Listener { if (event.click != ClickType.MIDDLE && !handleCustomCraftClick( event, - recipe, inventory, player, leftItem, rightItem, - result.amount, - finalCost, - recipe.removeExactLinearXp + result ) ) return @@ -192,11 +195,14 @@ class AnvilResultListener : Listener { } private fun handleCustomCraftClick( - event: InventoryClickEvent, recipe: AnvilCustomRecipe, + event: InventoryClickEvent, inventory: AnvilInventory, player: Player, leftItem: ItemStack, rightItem: ItemStack?, - amount: Int, xpCost: Int, linearCost: Boolean = false + result: CustomCraftResult ): Boolean { + val amount = result.amount + val recipe = result.recipe!! + // We remove what should be removed if (rightItem != null) { if (recipe.rightItem == null) return false// in case it changed @@ -208,26 +214,7 @@ class AnvilResultListener : Listener { leftItem.amount -= amount * recipe.leftItem!!.amount inventory.setItem(ANVIL_INPUT_LEFT, leftItem) - if (player.gameMode != GameMode.CREATIVE) { - //TODO monetary cost ? somehow - if (linearCost) { - val levelXp = AnvilXpUtil.calculateXpForLevel(player.level) - val delta = AnvilXpUtil.calculateXpForLevel(player.level + 1) - levelXp - var totalXp = levelXp + player.exp * delta - totalXp -= xpCost - - val newLevel = AnvilXpUtil.calculateLevelForXp(totalXp.toInt()) - - val newLevelXp = AnvilXpUtil.calculateXpForLevel(newLevel) - val newDelta = AnvilXpUtil.calculateXpForLevel(newLevel + 1) - newLevelXp - val xp = (totalXp - newLevelXp) / newDelta - - player.level = newLevel - player.exp = xp / newDelta - } else { - player.level -= xpCost - } - } + removeCustomCraftCost(player, result) // Then we try to find the new values for the anvil val newAmount = CustomRecipeUtil.getCustomRecipeAmount(recipe, leftItem, rightItem) @@ -251,6 +238,35 @@ class AnvilResultListener : Listener { return true } + private fun removeCustomCraftCost(player: Player, result: CustomCraftResult) { + if (player.gameMode == GameMode.CREATIVE) return + + val rawCost = result.customCraftCost.rawCost + if(result.cost.isMonetary) { + EconomyManager.economy!!.remove(player, BigDecimal(rawCost)) + return + } + + if (result.recipe!!.removeExactLinearXp) { + val levelXp = AnvilXpUtil.calculateXpForLevel(player.level) + val delta = AnvilXpUtil.calculateXpForLevel(player.level + 1) - levelXp + var totalXp = levelXp + player.exp * delta + totalXp -= rawCost + + val newLevel = AnvilXpUtil.calculateLevelForXp(totalXp.toInt()) + + val newLevelXp = AnvilXpUtil.calculateXpForLevel(newLevel) + val newDelta = AnvilXpUtil.calculateXpForLevel(newLevel + 1) - newLevelXp + val xp = (totalXp - newLevelXp) / newDelta + + player.level = newLevel + player.exp = xp / newDelta + } else { + player.level -= rawCost + } + + } + private fun tryRemoveCost(player: Player, cost: AnvilCost): Boolean { if(player.gameMode == GameMode.CREATIVE) return true if(cost.isMonetary) { 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 997b260..b57a696 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilXpUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilXpUtil.kt @@ -56,7 +56,7 @@ object AnvilXpUtil { return generic + enchantment + repair + rename + lore + illegalPenalty + workPenalty + recipe } - fun asMonetaryCost(): BigDecimal { + open fun asMonetaryCost(): BigDecimal { // multiply by per use type multipliers return BigDecimal(generic) .add(BigDecimal(enchantment).multiply(moneyMultiplier("enchantment"))) @@ -71,7 +71,13 @@ object AnvilXpUtil { } } - class CustomCraftCost(val rawCost: Int): AnvilCost() + class CustomCraftCost(val rawCost: Int): AnvilCost() { + + override fun asMonetaryCost(): BigDecimal { + return BigDecimal(rawCost) + } + + } /** * Display the required cost (either as xp or as ) From bf4395ba3f2d21ecc515e4a319a6c721ba3b38f4 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Wed, 3 Jun 2026 02:27:20 +0200 Subject: [PATCH 176/207] fix multiples issues --- defaultconfigs/1.18/config.yml | 5 ++++- defaultconfigs/1.21.11/config.yml | 5 ++++- defaultconfigs/1.21.9/config.yml | 5 ++++- defaultconfigs/1.21/config.yml | 5 ++++- .../xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt | 6 +++--- .../kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilXpUtil.kt | 1 + src/main/resources/config.yml | 5 ++++- 7 files changed, 24 insertions(+), 8 deletions(-) diff --git a/defaultconfigs/1.18/config.yml b/defaultconfigs/1.18/config.yml index fb70505..b5ad3b0 100644 --- a/defaultconfigs/1.18/config.yml +++ b/defaultconfigs/1.18/config.yml @@ -436,7 +436,10 @@ lore_edit: # It also requires to enable dialog rename (set "enable_dialog_rename: false" a bit higher) # If dialog rename permission is enabled and player do not have the permission merge will fall back to vanilla xp cost # -# As this feature require dialog rename, it can only be enabled starting with paper 1.21.6 +# If you are using custom craft I recommend using Linear Xp Cost with Exact Linear Xp as normal Xp Cost will act "weird" +# But Linear Xp will act as 1$ time global multiplier. In other word: like you expect +# +# As this feature require dialog rename, it can only be enabled starting with paper 1.21.6 and later monetary_cost: enabled: false # If using vault unlocked this allow to specify what currency should be used for anvil usage diff --git a/defaultconfigs/1.21.11/config.yml b/defaultconfigs/1.21.11/config.yml index b70798c..44885b7 100644 --- a/defaultconfigs/1.21.11/config.yml +++ b/defaultconfigs/1.21.11/config.yml @@ -456,7 +456,10 @@ lore_edit: # It also requires to enable dialog rename (set "enable_dialog_rename: false" a bit higher) # If dialog rename permission is enabled and player do not have the permission merge will fall back to vanilla xp cost # -# As this feature require dialog rename, it can only be enabled starting with paper 1.21.6 +# If you are using custom craft I recommend using Linear Xp Cost with Exact Linear Xp as normal Xp Cost will act "weird" +# But Linear Xp will act as 1$ time global multiplier. In other word: like you expect +# +# As this feature require dialog rename, it can only be enabled starting with paper 1.21.6 and later monetary_cost: enabled: false # If using vault unlocked this allow to specify what currency should be used for anvil usage diff --git a/defaultconfigs/1.21.9/config.yml b/defaultconfigs/1.21.9/config.yml index 31bdb1f..4df5876 100644 --- a/defaultconfigs/1.21.9/config.yml +++ b/defaultconfigs/1.21.9/config.yml @@ -448,7 +448,10 @@ lore_edit: # It also requires to enable dialog rename (set "enable_dialog_rename: false" a bit higher) # If dialog rename permission is enabled and player do not have the permission merge will fall back to vanilla xp cost # -# As this feature require dialog rename, it can only be enabled starting with paper 1.21.6 +# If you are using custom craft I recommend using Linear Xp Cost with Exact Linear Xp as normal Xp Cost will act "weird" +# But Linear Xp will act as 1$ time global multiplier. In other word: like you expect +# +# As this feature require dialog rename, it can only be enabled starting with paper 1.21.6 and later monetary_cost: enabled: false # If using vault unlocked this allow to specify what currency should be used for anvil usage diff --git a/defaultconfigs/1.21/config.yml b/defaultconfigs/1.21/config.yml index 1dafa54..5d59e5a 100644 --- a/defaultconfigs/1.21/config.yml +++ b/defaultconfigs/1.21/config.yml @@ -436,7 +436,10 @@ lore_edit: # It also requires to enable dialog rename (set "enable_dialog_rename: false" a bit higher) # If dialog rename permission is enabled and player do not have the permission merge will fall back to vanilla xp cost # -# As this feature require dialog rename, it can only be enabled starting with paper 1.21.6 +# If you are using custom craft I recommend using Linear Xp Cost with Exact Linear Xp as normal Xp Cost will act "weird" +# But Linear Xp will act as 1$ time global multiplier. In other word: like you expect +# +# As this feature require dialog rename, it can only be enabled starting with paper 1.21.6 and later monetary_cost: enabled: false # If using vault unlocked this allow to specify what currency should be used for anvil usage diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt index 549fd9a..6b5f510 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt @@ -160,7 +160,7 @@ class AnvilResultListener : Listener { if (player.gameMode != GameMode.CREATIVE) { if(ConfigOptions.shouldUseMoney(player)) { result.cost.isMonetary = true - if(!EconomyManager.economy!!.has(player, BigDecimal(rawCost))) return + if(!EconomyManager.economy!!.has(player, result.cost.asMonetaryCost())) return } else if (recipe.removeExactLinearXp) { val levelXp = AnvilXpUtil.calculateXpForLevel(player.level) val delta = AnvilXpUtil.calculateXpForLevel(player.level + 1) - levelXp @@ -243,7 +243,7 @@ class AnvilResultListener : Listener { val rawCost = result.customCraftCost.rawCost if(result.cost.isMonetary) { - EconomyManager.economy!!.remove(player, BigDecimal(rawCost)) + EconomyManager.economy!!.remove(player, result.cost.asMonetaryCost()) return } @@ -262,7 +262,7 @@ class AnvilResultListener : Listener { player.level = newLevel player.exp = xp / newDelta } else { - player.level -= rawCost + player.level -= AnvilXpUtil.calculateLevelForXp(rawCost) } } 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 b57a696..8c39a0a 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilXpUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilXpUtil.kt @@ -75,6 +75,7 @@ object AnvilXpUtil { override fun asMonetaryCost(): BigDecimal { return BigDecimal(rawCost) + .multiply(moneyMultiplier("global")) } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 27c0248..7d6e396 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -438,7 +438,10 @@ lore_edit: # It also requires to enable dialog rename (set "enable_dialog_rename: false" a bit higher) # If dialog rename permission is enabled and player do not have the permission merge will fall back to vanilla xp cost # -# As this feature require dialog rename, it can only be enabled starting with paper 1.21.6 +# If you are using custom craft I recommend using Linear Xp Cost with Exact Linear Xp as normal Xp Cost will act "weird" +# But Linear Xp will act as 1$ time global multiplier. In other word: like you expect +# +# As this feature require dialog rename, it can only be enabled starting with paper 1.21.6 and later monetary_cost: enabled: false # If using vault unlocked this allow to specify what currency should be used for anvil usage From 31fa3d38b7b3da88d9485195ebb82a7056795163 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Sat, 6 Jun 2026 12:46:32 +0200 Subject: [PATCH 177/207] minimum version of datapack tester upped to unsure minimum java version --- .../alexcrea/cuanvil/dependency/datapack/DataPackDependency.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/datapack/DataPackDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/datapack/DataPackDependency.kt index b0e9e52..6d54456 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/datapack/DataPackDependency.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/datapack/DataPackDependency.kt @@ -17,7 +17,7 @@ import xyz.alexcrea.cuanvil.update.Version import java.io.InputStreamReader object DataPackDependency { - private val START_DETECT_VERSION = Version(1, 19, 0) + private val START_DETECT_VERSION = Version(1, 20, 5) /** * Map of the latest CustomAnvil update related to the pack From 9f06f708f50e7c2d5c89012e41b9015828385f86 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 8 Jun 2026 10:28:03 +0200 Subject: [PATCH 178/207] fix superenchant price --- .../cuanvil/enchant/wrapped/CASuperEnchantEnchantment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 669c1fa..f5943be 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CASuperEnchantEnchantment.java +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CASuperEnchantEnchantment.java @@ -24,7 +24,7 @@ public class CASuperEnchantEnchantment extends CAEnchantmentBase implements Addi private @NotNull EnchantManager enchantManager; public CASuperEnchantEnchantment(@NotNull CustomEnchant enchant, @NotNull Plugin plugin, @NotNull EnchantManager enchantManager) { - super(NamespacedKey.fromString(enchant.getId(), plugin), EnchantmentRarity.NO_RARITY, enchant.getMaxLevel()); + super(NamespacedKey.fromString(enchant.getId(), plugin), EnchantmentRarity.COMMON, enchant.getMaxLevel()); this.enchant = enchant; this.enchantManager = enchantManager; From d679cd73f98243735700aafe0f7556e433423aa1 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 9 Jun 2026 14:08:17 +0200 Subject: [PATCH 179/207] remove rename pdc on paper lore append --- .../alexcrea/cuanvil/anvil/AnvilMergeLogic.kt | 55 +++++++++++-------- .../cuanvil/listener/AnvilResultListener.kt | 39 +++++++------ 2 files changed, 55 insertions(+), 39 deletions(-) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt index 8699285..e018eff 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt @@ -55,7 +55,7 @@ object AnvilMergeLogic { } } - class UnitRepairResult: AnvilResult { + class UnitRepairResult : AnvilResult { companion object { val EMPTY = UnitRepairResult(null, AnvilCost(), 0) } @@ -67,7 +67,7 @@ object AnvilMergeLogic { } } - class CustomCraftResult: AnvilResult { + class CustomCraftResult : AnvilResult { companion object { val EMPTY = CustomCraftResult(null, CustomCraftCost(0), 0, null) } @@ -76,15 +76,17 @@ object AnvilMergeLogic { val amount: Int val recipe: AnvilCustomRecipe? - constructor(item: ItemStack?, cost: CustomCraftCost, - amount: Int, recipe: AnvilCustomRecipe?) : super(item, cost, true) { + constructor( + item: ItemStack?, cost: CustomCraftCost, + amount: Int, recipe: AnvilCustomRecipe? + ) : super(item, cost, true) { this.customCraftCost = cost this.amount = amount this.recipe = recipe } } - class LoreEditResult: AnvilResult { + class LoreEditResult : AnvilResult { companion object { val EMPTY = LoreEditResult(null, AnvilCost(), LoreEditType.APPEND_PAPER) } @@ -96,8 +98,9 @@ object AnvilMergeLogic { } } - fun doRenaming(inventory: AnvilInventory, - player: Player, first: ItemStack + fun doRenaming( + inventory: AnvilInventory, + player: Player, first: ItemStack ): AnvilResult { val resultItem = DependencyManager.cloneItem(player, first) val cost = AnvilCost() @@ -115,15 +118,19 @@ object AnvilMergeLogic { return AnvilResult(result, cost) } - private fun processDialogPCD(it: ItemMeta, player: HumanEntity) { + private fun processDialogPCD(meta: ItemMeta, player: HumanEntity) { + val text = AnvilRenameDialogUtil.anvilRenameDialog.currentText(player) + return processPCD(meta, player, text) + } + + fun processPCD(meta: ItemMeta, player: HumanEntity, text: String?) { val keepDialog = ConfigOptions.canUseDialogRename(player) && ConfigOptions.shouldKeepRenameText - val pdc = it.persistentDataContainer - if(!keepDialog) + val pdc = meta.persistentDataContainer + if (!keepDialog) pdc.remove(AnvilRenameDialog.PCD_KEEP_RENAME_TEXT_KEY) else { - val text = AnvilRenameDialogUtil.anvilRenameDialog.currentText(player) - if(text == null || text.isBlank()) + if (text == null || text.isBlank()) pdc.remove(AnvilRenameDialog.PCD_KEEP_RENAME_TEXT_KEY) else pdc.set(AnvilRenameDialog.PCD_KEEP_RENAME_TEXT_KEY, PersistentDataType.STRING, text) } @@ -138,7 +145,8 @@ object AnvilMergeLogic { if (ConfigOptions.renameColorPossible && renameText != null) { val component = AnvilColorUtil.handleColor( renameText, - AnvilColorUtil.renamePermission(player)) + AnvilColorUtil.renamePermission(player) + ) if (component != null) { renameText = MiniMessageUtil.legacy_mm.serialize(component) @@ -160,7 +168,8 @@ object AnvilMergeLogic { renameText == "" || //TODO on recent paper check effective name instead renameText == CasedStringUtil.snakeToUpperSpacedCase(resultItem.type.name.lowercase()) - )) { + ) + ) { it.setDisplayName(renameText) processDialogPCD(it, player) resultItem.itemMeta = it @@ -184,7 +193,7 @@ object AnvilMergeLogic { val resultItem = DependencyManager.cloneItem(player, first) val cost = AnvilCost() - if(hasChanged){ + if (hasChanged) { resultItem.setEnchantmentsUnsafe(newEnchants) // Calculate enchantment cost AnvilXpUtil.getRightValues(second, resultItem, cost) @@ -217,9 +226,9 @@ object AnvilMergeLogic { firstEnchants: MutableMap, resultEnchants: MutableMap ): Boolean { - if(firstEnchants.size != resultEnchants.size) return false + if (firstEnchants.size != resultEnchants.size) return false for (entry in resultEnchants) { - if(firstEnchants.getOrDefault(entry.key, entry.value-1) != entry.value) return false + if (firstEnchants.getOrDefault(entry.key, entry.value - 1) != entry.value) return false } return true @@ -262,11 +271,11 @@ object AnvilMergeLogic { } fun testUnitRepair( - inventory: AnvilInventory, - player: Player, - first: ItemStack, second: ItemStack, - unitRepairAmount: Double - ): UnitRepairResult { + inventory: AnvilInventory, + player: Player, + first: ItemStack, second: ItemStack, + unitRepairAmount: Double + ): UnitRepairResult { val resultItem = DependencyManager.cloneItem(player, first) val cost = AnvilCost() cost.rename = handleRename(resultItem, inventory, player) @@ -300,7 +309,7 @@ object AnvilMergeLogic { AnvilLoreEditUtil.tryLoreEditByPaper(player, first, second) else LoreEditResult.EMPTY - if(result.isEmpty()) return result + if (result.isEmpty()) return result if (result.item!!.isAir || first == result.item) { CustomAnvil.log("lore edit, But input is same as output") diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt index 6b5f510..a232046 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt @@ -91,7 +91,7 @@ class AnvilResultListener : Listener { // Rename if (rightItem == null) { val result = AnvilMergeLogic.doRenaming(inventory, player, leftItem) - if(result.isEmpty()) return + if (result.isEmpty()) return extractAnvilResult( event, player, inventory, @@ -119,7 +119,8 @@ class AnvilResultListener : Listener { // Unit repair val unitRepairResult = AnvilMergeLogic.testUnitRepair( inventory, player, - leftItem, rightItem) + leftItem, rightItem + ) if (unitRepairResult.isEmpty()) { onUnitRepairExtract( rightItem, event, player, inventory, @@ -130,8 +131,8 @@ class AnvilResultListener : Listener { // For lore edit val loreResult = AnvilMergeLogic.testLoreEdit(player, leftItem, rightItem) - if(!loreResult.isEmpty()) { - if(loreResult.type.isBook) + if (!loreResult.isEmpty()) { + if (loreResult.type.isBook) handleBookLoreEdit(event, inventory, player, leftItem, rightItem, loreResult) else handlePaperLoreEdit(event, inventory, player, leftItem, rightItem, loreResult) @@ -153,14 +154,16 @@ class AnvilResultListener : Listener { if (recipe.removeExactLinearXp) rawCost else AnvilXpUtil.calculateLevelForXp(rawCost) - CustomAnvil.log("gamemode: ${player.gameMode != GameMode.CREATIVE}, " + - "cost: $finalCost, level: ${player.level}, " + - "result: ${player.totalExperience < finalCost} ${player.level < finalCost}") + CustomAnvil.log( + "gamemode: ${player.gameMode != GameMode.CREATIVE}, " + + "cost: $finalCost, level: ${player.level}, " + + "result: ${player.totalExperience < finalCost} ${player.level < finalCost}" + ) if (player.gameMode != GameMode.CREATIVE) { - if(ConfigOptions.shouldUseMoney(player)) { + if (ConfigOptions.shouldUseMoney(player)) { result.cost.isMonetary = true - if(!EconomyManager.economy!!.has(player, result.cost.asMonetaryCost())) return + if (!EconomyManager.economy!!.has(player, result.cost.asMonetaryCost())) return } else if (recipe.removeExactLinearXp) { val levelXp = AnvilXpUtil.calculateXpForLevel(player.level) val delta = AnvilXpUtil.calculateXpForLevel(player.level + 1) - levelXp @@ -242,7 +245,7 @@ class AnvilResultListener : Listener { if (player.gameMode == GameMode.CREATIVE) return val rawCost = result.customCraftCost.rawCost - if(result.cost.isMonetary) { + if (result.cost.isMonetary) { EconomyManager.economy!!.remove(player, result.cost.asMonetaryCost()) return } @@ -268,10 +271,10 @@ class AnvilResultListener : Listener { } private fun tryRemoveCost(player: Player, cost: AnvilCost): Boolean { - if(player.gameMode == GameMode.CREATIVE) return true - if(cost.isMonetary) { + if (player.gameMode == GameMode.CREATIVE) return true + if (cost.isMonetary) { val result = EconomyManager.economy!!.remove(player, cost.asMonetaryCost()) - if(!result) return false + if (!result) return false } else { player.level -= cost.asXpCost() } @@ -289,7 +292,7 @@ class AnvilResultListener : Listener { rightRemoveCount: Int, result: AnvilResult ): Boolean { - if(result.isEmpty()) return false + if (result.isEmpty()) return false // To avoid vanilla, we cancel the event event.result = Event.Result.DENY @@ -305,7 +308,7 @@ class AnvilResultListener : Listener { // If not creative middle click... if (event.click != ClickType.MIDDLE) { - if(!tryRemoveCost(player, cost)) return false + if (!tryRemoveCost(player, cost)) return false // We remove what should be removed if (leftItem != null) leftItem.amount -= leftRemoveCount @@ -417,7 +420,7 @@ class AnvilResultListener : Listener { leftItem: ItemStack, rightItem: ItemStack, result: LoreEditResult - ){ + ) { val bookMeta = rightItem.itemMeta as BookMeta? ?: return // fill book meta @@ -486,6 +489,10 @@ class AnvilResultListener : Listener { paperCopy = rightItem.clone() paperCopy.amount = 1 paperMeta.setComponentDisplayName(null) + + // Remove pcd name + AnvilMergeLogic.processPCD(paperMeta, player, null) + paperCopy.itemMeta = paperMeta } From 2efb6e55e285e23c83bcec8e66f6a286e7c1cadf Mon Sep 17 00:00:00 2001 From: alexcrea Date: Wed, 10 Jun 2026 14:54:13 +0200 Subject: [PATCH 180/207] new treat anvil event --- .../listener/CAClickResultBypassEvent.java | 2 +- .../listener/CAEarlyPreAnvilBypassEvent.java | 2 +- .../event/listener/CAPreAnvilBypassEvent.java | 2 +- .../listener/CATreatAnvilResult2Event.java | 196 ++++++++++++++++++ .../listener/CATreatAnvilResultEvent.java | 4 +- .../alexcrea/cuanvil/anvil/AnvilMergeLogic.kt | 17 +- .../cuanvil/dependency/DependencyManager.kt | 16 +- .../plugins/ExcellentEnchantsDependency.kt | 13 +- .../cuanvil/listener/AnvilResultListener.kt | 11 +- .../cuanvil/listener/PrepareAnvilListener.kt | 12 +- 10 files changed, 244 insertions(+), 31 deletions(-) create mode 100644 src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResult2Event.java diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CAClickResultBypassEvent.java b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CAClickResultBypassEvent.java index a27c65e..fe5e199 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CAClickResultBypassEvent.java +++ b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CAClickResultBypassEvent.java @@ -17,7 +17,7 @@ import org.jetbrains.annotations.NotNull; * Most of the time you would likely need {@link CAPreAnvilBypassEvent} or {@link CAEarlyPreAnvilBypassEvent} * for this event to be useful. *

    - * There is also {@link CATreatAnvilResultEvent} that may be better for some use case. + * There is also {@link CATreatAnvilResult2Event} that may be better for some use case. */ public class CAClickResultBypassEvent extends Event implements Cancellable { diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CAEarlyPreAnvilBypassEvent.java b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CAEarlyPreAnvilBypassEvent.java index 2fbd275..e92b4cd 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CAEarlyPreAnvilBypassEvent.java +++ b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CAEarlyPreAnvilBypassEvent.java @@ -15,7 +15,7 @@ import org.jetbrains.annotations.NotNull; *

    * You should also use {@link CAClickResultBypassEvent} if you want to use this event for something useful. *

    - * It is also recommended that you read about {@link CAPreAnvilBypassEvent} and {@link CATreatAnvilResultEvent} + * It is also recommended that you read about {@link CAPreAnvilBypassEvent} and {@link CATreatAnvilResult2Event} * as your use case may be more prone to use theses. */ public class CAEarlyPreAnvilBypassEvent extends Event implements Cancellable { diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CAPreAnvilBypassEvent.java b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CAPreAnvilBypassEvent.java index 18334e3..9103a4b 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CAPreAnvilBypassEvent.java +++ b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CAPreAnvilBypassEvent.java @@ -18,7 +18,7 @@ import org.jetbrains.annotations.NotNull; *

    * You should also use {@link CAClickResultBypassEvent} if you want to use this event for something useful. *

    - * It is also recommended that you read about {@link CAEarlyPreAnvilBypassEvent} and {@link CATreatAnvilResultEvent} + * It is also recommended that you read about {@link CAEarlyPreAnvilBypassEvent} and {@link CATreatAnvilResult2Event} * as your use case may be more prone to use theses. */ public class CAPreAnvilBypassEvent extends Event implements Cancellable { diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResult2Event.java b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResult2Event.java new file mode 100644 index 0000000..aa8f8e1 --- /dev/null +++ b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResult2Event.java @@ -0,0 +1,196 @@ +package xyz.alexcrea.cuanvil.api.event.listener; + +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import xyz.alexcrea.cuanvil.anvil.AnvilUseType; +import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil; + +/** + * Called after custom anvil processed the click on the result on the anvil inventory. + * This event should be used to modify the result of an anvil use. + *

    + * You may also want to check {@link CAClickResultBypassEvent}, + * {@link CAPreAnvilBypassEvent} + * and {@link CAEarlyPreAnvilBypassEvent} for your use case + *

    + * A null result will cancel this event + */ +@SuppressWarnings("unused") +public class CATreatAnvilResult2Event extends Event { + + private static final HandlerList HANDLERS = new HandlerList(); + + public static HandlerList getHandlerList() { + return HANDLERS; + } + + @Override + public @NotNull HandlerList getHandlers() { + return HANDLERS; + } + + @NotNull + private final InventoryView view; + + private final AnvilUseType useType; + + @Nullable + private final ItemStack left; + @Nullable + private final ItemStack right; + + @Nullable + private ItemStack result; + + private final AnvilXpUtil.AnvilCost cost; + + @ApiStatus.Internal + public CATreatAnvilResult2Event( + @NotNull InventoryView view, + Inventory inv, + AnvilUseType useType, + @Nullable ItemStack result, + AnvilXpUtil.AnvilCost cost) { + this.view = view; + this.useType = useType; + + this.left = inv.getItem(0); // TODO use view here + this.right = inv.getItem(1); + this.result = result; + this.cost = cost; + } + + /** + * Get the bukkit inventory view. + *

    + * Temporarily marked as internal as it will get changed to anvil view on legacy removal + * so signature will change + * + * @return The inventory view of this event. + */ + @ApiStatus.Internal + public @NotNull InventoryView getView() { + return view; + } + + + /** + * Get the type of use source of the result. + * + * @return The craft use type. + */ + public AnvilUseType getUseType() { + return useType; + } + + /** + * Get the left item of the anvil use + * + * @return the left item + */ + public @Nullable ItemStack getLeftItem() { + return left; + } + + /** + * Get the right item of the anvil use + * + * @return the right item + */ + public @Nullable ItemStack getRightItem() { + return right; + } + + /** + * Get the current result + *

    + * note that it will not be null unless another listener previously set it to null. + * + * @return The current result. + */ + public @Nullable ItemStack getResult() { + return result; + } + + /** + * Set the current result + *

    + * note that a null result will cancel this anvil use. + * + * @param result The new result + */ + public void setResult(@Nullable ItemStack result) { + this.result = result; + } + + /** + * Get the level cost displayed on the anvil. + *

    Important note:

    + * the final price are re calculated on click for the following use case: + *
      + *
    • Custom craft
    • + *
    • Unit repair
    • + *
    • Lore edit
    • + *
    + * This value will be used as final price for: + *
  • Item merge
  • + *
  • Item rename
  • + * + * + * @return The current cost. + * @deprecated use #{@link #getCost()} instead + */ + @Deprecated(forRemoval = true, since = "1.17.0") + public int getLevelCost() { + return cost.asXpCost(); + } + + /** + * Set the level cost displayed on the anvil. + *

    Important note:

    + * the final price are re calculated on click for the following use case: + *
      + *
    • Custom craft
    • + *
    • Unit repair
    • + *
    • Lore edit
    • + *
    + * This value will be used as final price for: + *
  • Item merge
  • + *
  • Item rename
  • + * + * + * @param levelCost The new cost. + * @deprecated use #{@link #getCost()} and set value on this instead + */ + @Deprecated(forRemoval = true, since = "1.17.0") + public void setLevelCost(int levelCost) { + cost.setGeneric(levelCost - cost.getGeneric() - cost.asXpCost()); + } + + /** + * Allow access to the current cost of the event + * Note that modifying this object will change the event resulting cost + * + *

    Important note:

    + * the final price are re calculated on click for the following use case: + *
      + *
    • Custom craft
    • + *
    • Unit repair
    • + *
    • Lore edit
    • + *
    + * This value will be used as final price for: + *
  • Item merge
  • + *
  • Item rename
  • + * + * @return the current anvil cost + */ + public AnvilXpUtil.AnvilCost getCost() { + return cost; + } +} diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResultEvent.java b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResultEvent.java index ddbaf23..6e6358d 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResultEvent.java +++ b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResultEvent.java @@ -19,8 +19,8 @@ import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil.AnvilCost; *

    * A null result will cancel this pre anvil event * - * @deprecated Prepare anvil Event should not be provided as it can be called on result and therefor not have prepare anvil event - * TODO a replacement is necessary but not yet made + * @deprecated Prepare anvil Event cannot be provided as it can be called on result and therefore not have prepared anvil event + * use {@link CATreatAnvilResult2Event} instead */ @SuppressWarnings("unused") @Deprecated(forRemoval = true, since = "1.17.0") diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt index e018eff..67b9b7d 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt @@ -13,6 +13,7 @@ import org.bukkit.Material import org.bukkit.entity.HumanEntity import org.bukkit.entity.Player import org.bukkit.inventory.AnvilInventory +import org.bukkit.inventory.InventoryView import org.bukkit.inventory.ItemStack import org.bukkit.inventory.meta.ItemMeta import org.bukkit.persistence.PersistentDataType @@ -99,6 +100,7 @@ object AnvilMergeLogic { } fun doRenaming( + view: InventoryView, //TODO use anvil view inventory: AnvilInventory, player: Player, first: ItemStack ): AnvilResult { @@ -113,7 +115,7 @@ object AnvilMergeLogic { } cost.workPenalty = AnvilXpUtil.calculatePenalty(first, null, resultItem, AnvilUseType.RENAME_ONLY) - val result = DependencyManager.tryTreatAnvilResult(resultItem, AnvilUseType.RENAME_ONLY, cost) + val result = DependencyManager.tryTreatAnvilResult(view, inventory, player, resultItem, AnvilUseType.RENAME_ONLY, cost) return AnvilResult(result, cost) } @@ -183,6 +185,7 @@ object AnvilMergeLogic { } fun doMerge( + view: InventoryView, //TODO use anvil view instead inventory: AnvilInventory, player: Player, first: ItemStack, second: ItemStack @@ -217,7 +220,7 @@ object AnvilMergeLogic { // Calculate rename cost cost.rename = handleRename(resultItem, inventory, player) - val result = DependencyManager.tryTreatAnvilResult(resultItem, AnvilUseType.MERGE, cost) + val result = DependencyManager.tryTreatAnvilResult(view, inventory, player, resultItem, AnvilUseType.MERGE, cost) return AnvilResult(result, cost) } @@ -236,6 +239,8 @@ object AnvilMergeLogic { // return true if a custom recipe exist with these ingredients fun testCustomRecipe( + view: InventoryView, //TODO use anvil view instead + inventory: AnvilInventory, player: Player, first: ItemStack, second: ItemStack? ): CustomCraftResult { @@ -256,21 +261,23 @@ object AnvilMergeLogic { cost.recipe = if (recipe.removeExactLinearXp) AnvilXpUtil.calculateMinimumLevelForXp(xpCost) else AnvilXpUtil.calculateLevelForXp(xpCost) - val result = DependencyManager.tryTreatAnvilResult(resultItem, AnvilUseType.CUSTOM_CRAFT, cost) + val result = DependencyManager.tryTreatAnvilResult(view, inventory, player, resultItem, AnvilUseType.CUSTOM_CRAFT, cost) return CustomCraftResult(result, cost, amount, recipe) } fun testUnitRepair( + view: InventoryView, //TODO use anvil view inventory: AnvilInventory, player: Player, first: ItemStack, second: ItemStack ): UnitRepairResult { val unitRepairAmount = first.getRepair(second) ?: return UnitRepairResult.EMPTY - return testUnitRepair(inventory, player, first, second, unitRepairAmount) + return testUnitRepair(view, inventory, player, first, second, unitRepairAmount) } fun testUnitRepair( + view: InventoryView, //TODO use anvil view instead inventory: AnvilInventory, player: Player, first: ItemStack, second: ItemStack, @@ -293,7 +300,7 @@ object AnvilMergeLogic { return UnitRepairResult.EMPTY } - val result = DependencyManager.tryTreatAnvilResult(resultItem, AnvilUseType.UNIT_REPAIR, cost) + val result = DependencyManager.tryTreatAnvilResult(view, inventory, player, resultItem, AnvilUseType.UNIT_REPAIR, cost) return UnitRepairResult(result, cost, repairAmount) } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt index 4ad6c9b..0da6b30 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt @@ -12,12 +12,13 @@ import org.bukkit.event.inventory.InventoryClickEvent import org.bukkit.event.inventory.PrepareAnvilEvent import org.bukkit.inventory.AnvilInventory import org.bukkit.inventory.Inventory +import org.bukkit.inventory.InventoryView import org.bukkit.inventory.ItemStack import xyz.alexcrea.cuanvil.anvil.AnvilUseType import xyz.alexcrea.cuanvil.api.event.listener.CAClickResultBypassEvent import xyz.alexcrea.cuanvil.api.event.listener.CAEarlyPreAnvilBypassEvent import xyz.alexcrea.cuanvil.api.event.listener.CAPreAnvilBypassEvent -import xyz.alexcrea.cuanvil.api.event.listener.CATreatAnvilResultEvent +import xyz.alexcrea.cuanvil.api.event.listener.CATreatAnvilResult2Event import xyz.alexcrea.cuanvil.config.ConfigHolder import xyz.alexcrea.cuanvil.dependency.datapack.DataPackDependency import xyz.alexcrea.cuanvil.dependency.gui.GenericExternGuiTester @@ -234,23 +235,24 @@ object DependencyManager { // Return null if there was an issue fun tryTreatAnvilResult( + view: InventoryView, + inventory: Inventory, // TODO REMOVE, use view instead on legacy removal + player: HumanEntity, result: ItemStack, useType: AnvilUseType, cost: AnvilXpUtil.AnvilCost ): ItemStack? { - //TODO - /*val treatEvent = CATreatAnvilResultEvent(event, useType, result, cost) + val treatEvent = CATreatAnvilResult2Event(view, inventory, useType, result, cost) try { unsafeTryTreatAnvilResult(treatEvent) return treatEvent.result } catch (e: Exception) { - logExceptionAndClear(event.view.player, event.inventory, e) + logExceptionAndClear(player, inventory, e) return null - }*/ - return result + } } - private fun unsafeTryTreatAnvilResult(event: CATreatAnvilResultEvent) { + private fun unsafeTryTreatAnvilResult(event: CATreatAnvilResult2Event) { Bukkit.getPluginManager().callEvent(event) excellentEnchantsCompatibility?.treatAnvilResult(event) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt index 54ea2f4..39bfad7 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt @@ -8,7 +8,7 @@ import org.bukkit.event.inventory.PrepareAnvilEvent import org.bukkit.inventory.ItemStack import org.bukkit.plugin.RegisteredListener import xyz.alexcrea.cuanvil.api.EnchantmentApi -import xyz.alexcrea.cuanvil.api.event.listener.CATreatAnvilResultEvent +import xyz.alexcrea.cuanvil.api.event.listener.CATreatAnvilResult2Event import xyz.alexcrea.cuanvil.enchant.wrapped.CAEEPreV5Enchantment import xyz.alexcrea.cuanvil.enchant.wrapped.CAEEV5Enchantment import xyz.alexcrea.cuanvil.enchant.wrapped.CAEEV5_4Enchantment @@ -218,14 +218,17 @@ class ExcellentEnchantsDependency { return handleRechargeMethod.invoke(this.usedAnvilListener, event, first, second) as Boolean } - fun treatAnvilResult(event: CATreatAnvilResultEvent) { + fun treatAnvilResult(event: CATreatAnvilResult2Event) { val result = event.result if (result == null) return - val first: ItemStack = treatInput(event.event.inventory.getItem(0)) - val second: ItemStack = treatInput(event.event.inventory.getItem(1)) + val first: ItemStack = treatInput(event.leftItem) + val second: ItemStack = treatInput(event.rightItem) - handleCombineMethod.invoke(this.usedAnvilListener, event.event, first, second, result) + val fakeEvent = PrepareAnvilEvent(event.view, result) + handleCombineMethod.invoke(this.usedAnvilListener, fakeEvent, first, second, result) + + event.result = fakeEvent.result } fun testAnvilResult(event: InventoryClickEvent): Any { diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt index a232046..775a685 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt @@ -53,6 +53,7 @@ class AnvilResultListener : Listener { fun anvilExtractionCheck(event: InventoryClickEvent) { val player = event.whoClicked as? Player ?: return val inventory = event.inventory as? AnvilInventory ?: return + val view = event.view if (event.rawSlot != ANVIL_OUTPUT_SLOT) { return @@ -74,7 +75,7 @@ class AnvilResultListener : Listener { } // Test custom recipe - val customRecipeResult = AnvilMergeLogic.testCustomRecipe(player, leftItem, rightItem) + val customRecipeResult = AnvilMergeLogic.testCustomRecipe(view, inventory, player, leftItem, rightItem) if (!customRecipeResult.isEmpty()) { onCustomCraft( event, player, inventory, @@ -90,7 +91,7 @@ class AnvilResultListener : Listener { // Rename if (rightItem == null) { - val result = AnvilMergeLogic.doRenaming(inventory, player, leftItem) + val result = AnvilMergeLogic.doRenaming(view, inventory, player, leftItem) if (result.isEmpty()) return extractAnvilResult( @@ -105,7 +106,7 @@ class AnvilResultListener : Listener { // Merge val canMerge = leftItem.canMergeWith(rightItem) if (canMerge) { - val result = AnvilMergeLogic.doMerge(inventory, player, leftItem, rightItem) + val result = AnvilMergeLogic.doMerge(view, inventory, player, leftItem, rightItem) extractAnvilResult( event, player, inventory, @@ -118,7 +119,7 @@ class AnvilResultListener : Listener { // Unit repair val unitRepairResult = AnvilMergeLogic.testUnitRepair( - inventory, player, + view, inventory, player, leftItem, rightItem ) if (unitRepairResult.isEmpty()) { @@ -481,6 +482,8 @@ class AnvilResultListener : Listener { ) { val paperMeta = rightItem.itemMeta ?: return + + val paperCopy: ItemStack? if (LoreEditType.APPEND_PAPER.doConsume) { paperCopy = null diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt index 21438ae..b2f6785 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt @@ -11,6 +11,7 @@ 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.InventoryView import org.bukkit.inventory.ItemStack import org.bukkit.inventory.meta.EnchantmentStorageMeta import org.bukkit.inventory.meta.ItemMeta @@ -91,11 +92,12 @@ class PrepareAnvilListener : Listener { if (!player.hasPermission(CustomAnvil.affectedByPluginPermission)) return - val result = getResult(inventory, player, first, second) + val result = getResult(view, inventory, player, first, second) applyResult(event, player, result) } fun getResult( + view: InventoryView, //TODO use anvil view inventory: AnvilInventory, player: Player, first: ItemStack?, second: ItemStack?) : AnvilResult @@ -104,7 +106,7 @@ class PrepareAnvilListener : Listener { return AnvilResult.EMPTY // Test custom recipe - var result: AnvilResult = testCustomRecipe(player, first, second) + var result: AnvilResult = testCustomRecipe(view, inventory, player, first, second) if (!result.isEmpty()) return result @@ -112,14 +114,14 @@ class PrepareAnvilListener : Listener { val shouldTryRename = second.isAir CustomAnvil.verboseLog("checking air in main logic: $shouldTryRename") if (shouldTryRename) - return doRenaming(inventory, player, first) + return doRenaming(view, inventory, player, first) // Test for merge if (first.canMergeWith(second!!)) - return doMerge(inventory, player, first, second) + return doMerge(view, inventory, player, first, second) // Test for unit repair - result = testUnitRepair(inventory, player, first, second) + result = testUnitRepair(view, inventory, player, first, second) if (!result.isEmpty()) return result From 49b0054ecaec04170230f4d8bbae589171c79ec2 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Wed, 10 Jun 2026 14:59:28 +0200 Subject: [PATCH 181/207] move anvil cost to its own class --- .../listener/CATreatAnvilResult2Event.java | 8 +-- .../listener/CATreatAnvilResultEvent.java | 6 +- .../xyz/alexcrea/cuanvil/anvil/AnvilCost.kt | 55 +++++++++++++++++++ .../alexcrea/cuanvil/anvil/AnvilMergeLogic.kt | 14 +++-- .../cuanvil/dependency/DependencyManager.kt | 4 +- .../plugins/DisenchantmentDependency.kt | 2 +- .../dependency/plugins/HavenBagsDependency.kt | 2 +- .../cuanvil/listener/AnvilResultListener.kt | 4 +- .../cuanvil/listener/PrepareAnvilListener.kt | 2 +- .../cuanvil/util/anvil/AnvilLoreEditUtil.kt | 40 ++++++++------ .../cuanvil/util/anvil/AnvilXpUtil.kt | 54 +----------------- 11 files changed, 101 insertions(+), 90 deletions(-) create mode 100644 src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilCost.kt diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResult2Event.java b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResult2Event.java index aa8f8e1..30c5380 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResult2Event.java +++ b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResult2Event.java @@ -8,8 +8,8 @@ import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import xyz.alexcrea.cuanvil.anvil.AnvilCost; import xyz.alexcrea.cuanvil.anvil.AnvilUseType; -import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil; /** * Called after custom anvil processed the click on the result on the anvil inventory. @@ -48,7 +48,7 @@ public class CATreatAnvilResult2Event extends Event { @Nullable private ItemStack result; - private final AnvilXpUtil.AnvilCost cost; + private final AnvilCost cost; @ApiStatus.Internal public CATreatAnvilResult2Event( @@ -56,7 +56,7 @@ public class CATreatAnvilResult2Event extends Event { Inventory inv, AnvilUseType useType, @Nullable ItemStack result, - AnvilXpUtil.AnvilCost cost) { + AnvilCost cost) { this.view = view; this.useType = useType; @@ -190,7 +190,7 @@ public class CATreatAnvilResult2Event extends Event { * * @return the current anvil cost */ - public AnvilXpUtil.AnvilCost getCost() { + public AnvilCost getCost() { return cost; } } diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResultEvent.java b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResultEvent.java index 6e6358d..80965b5 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResultEvent.java +++ b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResultEvent.java @@ -6,8 +6,8 @@ import org.bukkit.event.inventory.PrepareAnvilEvent; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import xyz.alexcrea.cuanvil.anvil.AnvilCost; import xyz.alexcrea.cuanvil.anvil.AnvilUseType; -import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil.AnvilCost; /** * Called after custom anvil processed the click on the result on the anvil inventory. @@ -108,8 +108,8 @@ public class CATreatAnvilResultEvent extends Event { *

  • Item rename
  • * * - * @deprecated use #{@link #getCost()} instead * @return The current cost. + * @deprecated use #{@link #getCost()} instead */ @Deprecated(forRemoval = true, since = "1.17.0") public int getLevelCost() { @@ -130,8 +130,8 @@ public class CATreatAnvilResultEvent extends Event { *
  • Item rename
  • * * - * @deprecated use #{@link #getCost()} and set value on this instead * @param levelCost The new cost. + * @deprecated use #{@link #getCost()} and set value on this instead */ @Deprecated(forRemoval = true, since = "1.17.0") public void setLevelCost(int levelCost) { diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilCost.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilCost.kt new file mode 100644 index 0000000..f8ff89c --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilCost.kt @@ -0,0 +1,55 @@ +package xyz.alexcrea.cuanvil.anvil + +import java.math.BigDecimal +import io.delilaheve.util.ConfigOptions.getMonetaryMultiplier as moneyMultiplier + +open class AnvilCost { + private val isAlone: Boolean + var valid = true // Get set as invalid if cost can be satisfied + var isMonetary = false + + var generic = 0 + var enchantment = 0 + var repair = 0 + var rename = 0 + var lore = 0 + var illegalPenalty = 0 + var workPenalty = 0 + var recipe = 0 + + constructor(generic: Int) { + this.generic = generic + isAlone = true + } + + constructor() { + isAlone = false + } + + fun asXpCost(): Int { + return generic + enchantment + repair + rename + lore + illegalPenalty + workPenalty + recipe + } + + open fun asMonetaryCost(): BigDecimal { + // multiply by per use type multipliers + return BigDecimal(generic) + .add(BigDecimal(enchantment).multiply(moneyMultiplier("enchantment"))) + .add(BigDecimal(repair).multiply(moneyMultiplier("repair"))) + .add(BigDecimal(rename).multiply(moneyMultiplier("rename"))) + .add(BigDecimal(lore).multiply(moneyMultiplier("lore_edit"))) + .add(BigDecimal(enchantment).multiply(moneyMultiplier("enchantment"))) + .add(BigDecimal(illegalPenalty).multiply(moneyMultiplier("work_penalty"))) + .add(BigDecimal(workPenalty).multiply(moneyMultiplier("work_penalty"))) + .add(BigDecimal(recipe).multiply(moneyMultiplier("recipe"))) + .multiply(moneyMultiplier("global")) + } +} + +class CustomCraftCost(val rawCost: Int): AnvilCost() { + + override fun asMonetaryCost(): BigDecimal { + return BigDecimal(rawCost) + .multiply(moneyMultiplier("global")) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt index 67b9b7d..6b106fc 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt @@ -29,8 +29,6 @@ import xyz.alexcrea.cuanvil.util.UnitRepairUtil.getRepair import xyz.alexcrea.cuanvil.util.anvil.AnvilColorUtil import xyz.alexcrea.cuanvil.util.anvil.AnvilLoreEditUtil import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil -import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil.AnvilCost -import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil.CustomCraftCost import xyz.alexcrea.cuanvil.util.config.LoreEditType import xyz.alexcrea.cuanvil.util.dialog.AnvilRenameDialogUtil @@ -115,7 +113,8 @@ object AnvilMergeLogic { } cost.workPenalty = AnvilXpUtil.calculatePenalty(first, null, resultItem, AnvilUseType.RENAME_ONLY) - val result = DependencyManager.tryTreatAnvilResult(view, inventory, player, resultItem, AnvilUseType.RENAME_ONLY, cost) + val result = + DependencyManager.tryTreatAnvilResult(view, inventory, player, resultItem, AnvilUseType.RENAME_ONLY, cost) return AnvilResult(result, cost) } @@ -220,7 +219,8 @@ object AnvilMergeLogic { // Calculate rename cost cost.rename = handleRename(resultItem, inventory, player) - val result = DependencyManager.tryTreatAnvilResult(view, inventory, player, resultItem, AnvilUseType.MERGE, cost) + val result = + DependencyManager.tryTreatAnvilResult(view, inventory, player, resultItem, AnvilUseType.MERGE, cost) return AnvilResult(result, cost) } @@ -261,7 +261,8 @@ object AnvilMergeLogic { cost.recipe = if (recipe.removeExactLinearXp) AnvilXpUtil.calculateMinimumLevelForXp(xpCost) else AnvilXpUtil.calculateLevelForXp(xpCost) - val result = DependencyManager.tryTreatAnvilResult(view, inventory, player, resultItem, AnvilUseType.CUSTOM_CRAFT, cost) + val result = + DependencyManager.tryTreatAnvilResult(view, inventory, player, resultItem, AnvilUseType.CUSTOM_CRAFT, cost) return CustomCraftResult(result, cost, amount, recipe) } @@ -300,7 +301,8 @@ object AnvilMergeLogic { return UnitRepairResult.EMPTY } - val result = DependencyManager.tryTreatAnvilResult(view, inventory, player, resultItem, AnvilUseType.UNIT_REPAIR, cost) + val result = + DependencyManager.tryTreatAnvilResult(view, inventory, player, resultItem, AnvilUseType.UNIT_REPAIR, cost) return UnitRepairResult(result, cost, repairAmount) } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt index 0da6b30..cb24eaa 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt @@ -14,6 +14,7 @@ import org.bukkit.inventory.AnvilInventory import org.bukkit.inventory.Inventory import org.bukkit.inventory.InventoryView import org.bukkit.inventory.ItemStack +import xyz.alexcrea.cuanvil.anvil.AnvilCost import xyz.alexcrea.cuanvil.anvil.AnvilUseType import xyz.alexcrea.cuanvil.api.event.listener.CAClickResultBypassEvent import xyz.alexcrea.cuanvil.api.event.listener.CAEarlyPreAnvilBypassEvent @@ -33,7 +34,6 @@ import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.componentLore import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener.Companion.ANVIL_OUTPUT_SLOT import xyz.alexcrea.cuanvil.util.MetricsUtil.trackError -import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil import java.util.logging.Level object DependencyManager { @@ -240,7 +240,7 @@ object DependencyManager { player: HumanEntity, result: ItemStack, useType: AnvilUseType, - cost: AnvilXpUtil.AnvilCost + cost: AnvilCost ): ItemStack? { val treatEvent = CATreatAnvilResult2Event(view, inventory, useType, result, cost) try { diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/DisenchantmentDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/DisenchantmentDependency.kt index 32ca99d..690b384 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/DisenchantmentDependency.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/DisenchantmentDependency.kt @@ -14,10 +14,10 @@ import org.bukkit.event.inventory.InventoryClickEvent import org.bukkit.event.inventory.PrepareAnvilEvent import org.bukkit.inventory.AnvilInventory import org.bukkit.inventory.ItemStack +import xyz.alexcrea.cuanvil.anvil.AnvilCost import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener import xyz.alexcrea.cuanvil.util.MetricsUtil.trackError import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil -import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil.AnvilCost import java.util.logging.Level import kotlin.reflect.KClass diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/HavenBagsDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/HavenBagsDependency.kt index 62d9e4e..6f30497 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/HavenBagsDependency.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/HavenBagsDependency.kt @@ -9,9 +9,9 @@ import org.bukkit.plugin.RegisteredListener import valorless.havenbags.HavenBags import valorless.havenbags.features.BagSkin import valorless.havenbags.features.BagUpgrade +import xyz.alexcrea.cuanvil.anvil.AnvilCost import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil -import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil.AnvilCost class HavenBagsDependency { diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt index 775a685..feee833 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt @@ -15,6 +15,7 @@ import org.bukkit.inventory.AnvilInventory import org.bukkit.inventory.InventoryView import org.bukkit.inventory.ItemStack import org.bukkit.inventory.meta.BookMeta +import xyz.alexcrea.cuanvil.anvil.AnvilCost import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.AnvilResult import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.CustomCraftResult @@ -30,10 +31,8 @@ import xyz.alexcrea.cuanvil.util.CustomRecipeUtil import xyz.alexcrea.cuanvil.util.MiniMessageUtil import xyz.alexcrea.cuanvil.util.anvil.AnvilLoreEditUtil import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil -import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil.AnvilCost import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil import xyz.alexcrea.cuanvil.util.config.LoreEditType -import java.math.BigDecimal import java.util.* import java.util.concurrent.atomic.AtomicReference import kotlin.math.min @@ -483,7 +482,6 @@ class AnvilResultListener : Listener { val paperMeta = rightItem.itemMeta ?: return - val paperCopy: ItemStack? if (LoreEditType.APPEND_PAPER.doConsume) { paperCopy = null diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt index b2f6785..0217983 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt @@ -15,6 +15,7 @@ import org.bukkit.inventory.InventoryView import org.bukkit.inventory.ItemStack import org.bukkit.inventory.meta.EnchantmentStorageMeta import org.bukkit.inventory.meta.ItemMeta +import xyz.alexcrea.cuanvil.anvil.AnvilCost import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.AnvilResult import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.doMerge import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.doRenaming @@ -24,7 +25,6 @@ import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.testUnitRepair import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.util.MaterialUtil.isAir import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil -import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil.AnvilCost import xyz.alexcrea.cuanvil.util.dialog.AnvilRenameDialogUtil /** diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilLoreEditUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilLoreEditUtil.kt index e9bb633..a021b46 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilLoreEditUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilLoreEditUtil.kt @@ -5,12 +5,12 @@ import org.bukkit.entity.HumanEntity import org.bukkit.inventory.ItemStack import org.bukkit.inventory.meta.BookMeta import org.bukkit.permissions.Permissible +import xyz.alexcrea.cuanvil.anvil.AnvilCost import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.LoreEditResult import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.componentLore import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.setComponentLore import xyz.alexcrea.cuanvil.util.MiniMessageUtil -import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil.AnvilCost import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil import xyz.alexcrea.cuanvil.util.config.LoreEditType import java.util.* @@ -44,8 +44,10 @@ object AnvilLoreEditUtil { val page = book.pages[0] val lines = ArrayList(page.split("\n")) val outLines = ArrayList(lines.size) - val colorCost = colorLines(player, LoreEditType.APPEND_BOOK, - lines, outLines) + val colorCost = colorLines( + player, LoreEditType.APPEND_BOOK, + lines, outLines + ) lore.addAll(outLines) @@ -81,7 +83,7 @@ object AnvilLoreEditUtil { // Handle xp cost.lore = uncolorCost - cost.lore+= currentLore.size * LoreEditType.REMOVE_BOOK.perLineCost + cost.lore += currentLore.size * LoreEditType.REMOVE_BOOK.perLineCost baseEditLoreXpCost(cost, first, result, LoreEditType.REMOVE_BOOK) return result @@ -120,7 +122,7 @@ object AnvilLoreEditUtil { fun tryLoreEditByBook(player: HumanEntity, first: ItemStack, second: ItemStack): LoreEditResult { val isAppend = bookLoreEditIsAppend(first, second) ?: return LoreEditResult.EMPTY - val type = if(isAppend) LoreEditType.APPEND_BOOK else LoreEditType.REMOVE_BOOK + val type = if (isAppend) LoreEditType.APPEND_BOOK else LoreEditType.REMOVE_BOOK val meta = second.itemMeta as BookMeta val cost = AnvilCost() @@ -166,9 +168,11 @@ object AnvilLoreEditUtil { // A bit overdone to color 1 line but hey val outList = ArrayList(1) - val colorCost = colorLines(player, LoreEditType.APPEND_PAPER, + val colorCost = colorLines( + player, LoreEditType.APPEND_PAPER, Collections.singletonList(second.itemMeta!!.displayName), - outList) + outList + ) val line = outList[0] if (appendEnd) @@ -232,7 +236,7 @@ object AnvilLoreEditUtil { second: ItemStack ): LoreEditResult { val isAppend = paperLoreEditIsAppend(first, second) ?: return LoreEditResult.EMPTY - val type = if(isAppend) LoreEditType.APPEND_BOOK else LoreEditType.REMOVE_BOOK + val type = if (isAppend) LoreEditType.APPEND_BOOK else LoreEditType.REMOVE_BOOK val cost = AnvilCost() val item = if (isAppend) @@ -248,18 +252,20 @@ object AnvilLoreEditUtil { result: ItemStack, editType: LoreEditType ) { - cost.lore+= editType.fixedCost + cost.lore += editType.fixedCost cost.workPenalty = AnvilXpUtil.calculatePenalty(first, null, result, editType.useType) } fun colorPermission(player: Permissible, editType: LoreEditType): AnvilColorUtil.ColorPermissions { - return AnvilColorUtil.calculatePermissions(player, + return AnvilColorUtil.calculatePermissions( + player, false, editType.allowColorCode, editType.allowHexColor, editType.allowMinimessage, - AnvilColorUtil.ColorUseType.LORE_EDIT) + AnvilColorUtil.ColorUseType.LORE_EDIT + ) } private fun colorLine(line: String, permission: AnvilColorUtil.ColorPermissions): Component? { @@ -269,8 +275,10 @@ object AnvilLoreEditUtil { ) } - private fun colorLines(player: Permissible, editType: LoreEditType, - lines: List, outLines: MutableList): Int { + private fun colorLines( + player: Permissible, editType: LoreEditType, + lines: List, outLines: MutableList + ): Int { val permission = colorPermission(player, editType) val colorCost = editType.useColorCost @@ -297,7 +305,7 @@ object AnvilLoreEditUtil { // Now handle color of each lines var hasUndidColor = false for ((index, line) in lines.withIndex()) { - if(line == null){ + if (line == null) { lines[index] = null continue } @@ -312,7 +320,7 @@ object AnvilLoreEditUtil { hasUndidColor = true result = clearedLine } else { - result = MiniMessageUtil.plain_text_mm.serialize(line) + result = MiniMessageUtil.plain_text_mm.serialize(line) } lines[index] = MiniMessageUtil.plain_text_mm.deserialize(result) @@ -341,7 +349,7 @@ object AnvilLoreEditUtil { var hasUndidColor = false val result: String - if(clearedLine != null){ + if (clearedLine != null) { hasUndidColor = true result = clearedLine } else { 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 8c39a0a..f6ee12f 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilXpUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilXpUtil.kt @@ -14,6 +14,7 @@ import org.bukkit.inventory.InventoryView import org.bukkit.inventory.ItemStack import org.bukkit.inventory.meta.Repairable import org.bukkit.persistence.PersistentDataType +import xyz.alexcrea.cuanvil.anvil.AnvilCost import xyz.alexcrea.cuanvil.anvil.AnvilUseType import xyz.alexcrea.cuanvil.config.ConfigHolder import xyz.alexcrea.cuanvil.dependency.DependencyManager @@ -21,65 +22,12 @@ import xyz.alexcrea.cuanvil.dependency.economy.EconomyManager import xyz.alexcrea.cuanvil.group.ConflictType import xyz.alexcrea.cuanvil.util.AnvilTitleUtil import xyz.alexcrea.cuanvil.util.dialog.AnvilRenameDialogUtil -import java.math.BigDecimal import kotlin.math.min -import io.delilaheve.util.ConfigOptions.getMonetaryMultiplier as moneyMultiplier object AnvilXpUtil { const val EXCLUSIVE_PENALTY_PREFIX = "repair_cost" - open class AnvilCost { - private val isAlone: Boolean - var valid = true // Get set as invalid if cost can be satisfied - var isMonetary = false - - var generic = 0 - var enchantment = 0 - var repair = 0 - var rename = 0 - var lore = 0 - var illegalPenalty = 0 - var workPenalty = 0 - var recipe = 0 - - constructor(generic: Int) { - this.generic = generic - isAlone = true - } - - constructor() { - isAlone = false - } - - fun asXpCost(): Int { - return generic + enchantment + repair + rename + lore + illegalPenalty + workPenalty + recipe - } - - open fun asMonetaryCost(): BigDecimal { - // multiply by per use type multipliers - return BigDecimal(generic) - .add(BigDecimal(enchantment).multiply(moneyMultiplier("enchantment"))) - .add(BigDecimal(repair).multiply(moneyMultiplier("repair"))) - .add(BigDecimal(rename).multiply(moneyMultiplier("rename"))) - .add(BigDecimal(lore).multiply(moneyMultiplier("lore_edit"))) - .add(BigDecimal(enchantment).multiply(moneyMultiplier("enchantment"))) - .add(BigDecimal(illegalPenalty).multiply(moneyMultiplier("work_penalty"))) - .add(BigDecimal(workPenalty).multiply(moneyMultiplier("work_penalty"))) - .add(BigDecimal(recipe).multiply(moneyMultiplier("recipe"))) - .multiply(moneyMultiplier("global")) - } - } - - class CustomCraftCost(val rawCost: Int): AnvilCost() { - - override fun asMonetaryCost(): BigDecimal { - return BigDecimal(rawCost) - .multiply(moneyMultiplier("global")) - } - - } - /** * Display the required cost (either as xp or as ) */ From 2f9d25bfe9950526fcade3883392192cdbf928a4 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Wed, 10 Jun 2026 15:18:31 +0200 Subject: [PATCH 182/207] fix fake prepare anvil on modern versions --- .../cuanvil/util/ModernPrepareAnvilCreator.kt | 12 ++++++++++++ .../plugins/ExcellentEnchantsDependency.kt | 12 ++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 nms/v1_21R1/src/main/kotlin/xyz/alexcrea/cuanvil/util/ModernPrepareAnvilCreator.kt diff --git a/nms/v1_21R1/src/main/kotlin/xyz/alexcrea/cuanvil/util/ModernPrepareAnvilCreator.kt b/nms/v1_21R1/src/main/kotlin/xyz/alexcrea/cuanvil/util/ModernPrepareAnvilCreator.kt new file mode 100644 index 0000000..0c51e0a --- /dev/null +++ b/nms/v1_21R1/src/main/kotlin/xyz/alexcrea/cuanvil/util/ModernPrepareAnvilCreator.kt @@ -0,0 +1,12 @@ +package xyz.alexcrea.cuanvil.util + +import org.bukkit.event.inventory.PrepareAnvilEvent +import org.bukkit.inventory.InventoryView +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.view.AnvilView + +object ModernPrepareAnvilCreator { + fun createPrepareAnvil(view: InventoryView, item: ItemStack?): PrepareAnvilEvent { + return PrepareAnvilEvent(view as AnvilView, item) + } +} diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt index 39bfad7..c2d3019 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt @@ -13,6 +13,7 @@ import xyz.alexcrea.cuanvil.enchant.wrapped.CAEEPreV5Enchantment import xyz.alexcrea.cuanvil.enchant.wrapped.CAEEV5Enchantment import xyz.alexcrea.cuanvil.enchant.wrapped.CAEEV5_4Enchantment import xyz.alexcrea.cuanvil.enchant.wrapped.CALegacyEEEnchantment +import xyz.alexcrea.cuanvil.util.ModernPrepareAnvilCreator import java.lang.reflect.Method import su.nightexpress.excellentenchants.api.EnchantRegistry as V5EnchantRegistry import su.nightexpress.excellentenchants.enchantment.impl.universal.CurseOfFragilityEnchant as LegacyCurseOfFragilityEnchant @@ -219,13 +220,16 @@ class ExcellentEnchantsDependency { } fun treatAnvilResult(event: CATreatAnvilResult2Event) { - val result = event.result - if (result == null) return + val result = event.result ?: return val first: ItemStack = treatInput(event.leftItem) val second: ItemStack = treatInput(event.rightItem) - - val fakeEvent = PrepareAnvilEvent(event.view, result) + val fakeEvent: PrepareAnvilEvent = try { + //TODO remove this on legacy removal + PrepareAnvilEvent(event.view, result) + } catch (_: NoSuchMethodError) { + ModernPrepareAnvilCreator.createPrepareAnvil(event.view, result) + } handleCombineMethod.invoke(this.usedAnvilListener, fakeEvent, first, second, result) event.result = fakeEvent.result From d91576b0deb927c5c7b123f2d7f50a3c69a0d24e Mon Sep 17 00:00:00 2001 From: alexcrea Date: Wed, 10 Jun 2026 15:30:25 +0200 Subject: [PATCH 183/207] update faststat [skip ci] --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 71945d5..bf81368 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -55,7 +55,7 @@ dependencies { compileOnly("org.spigotmc:spigot-api:1.18-R0.1-SNAPSHOT") // fast stats - implementation("dev.faststats.metrics:bukkit:0.24.0") + implementation("dev.faststats.metrics:bukkit:0.25.1") // minimessage implementation("net.kyori:adventure-text-minimessage:4.25.0") From d82bd9b22c24861dbf78e1dc093ca61840483b47 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Wed, 10 Jun 2026 17:07:52 +0200 Subject: [PATCH 184/207] do not packet manager on test --- .../cuanvil/dependency/DependencyManager.kt | 8 +++----- .../dependency/gui/GenericExternGuiTester.kt | 8 ++++++-- .../cuanvil/dependency/gui/GuiTesterSelector.kt | 15 --------------- .../dependency/packet/PacketManagerSelector.kt | 4 ++++ 4 files changed, 13 insertions(+), 22 deletions(-) delete mode 100644 src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt index cb24eaa..b730a0e 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt @@ -23,7 +23,6 @@ import xyz.alexcrea.cuanvil.api.event.listener.CATreatAnvilResult2Event import xyz.alexcrea.cuanvil.config.ConfigHolder import xyz.alexcrea.cuanvil.dependency.datapack.DataPackDependency import xyz.alexcrea.cuanvil.dependency.gui.GenericExternGuiTester -import xyz.alexcrea.cuanvil.dependency.gui.GuiTesterSelector import xyz.alexcrea.cuanvil.dependency.packet.PacketManager import xyz.alexcrea.cuanvil.dependency.packet.PacketManagerSelector import xyz.alexcrea.cuanvil.dependency.plugins.* @@ -40,7 +39,7 @@ object DependencyManager { lateinit var scheduler: TaskScheduler lateinit var packetManager: PacketManager - var externGuiTester: GenericExternGuiTester? = null + var externGuiTester: GenericExternGuiTester = GenericExternGuiTester() var enchantmentSquaredCompatibility: EnchantmentSquaredDependency? = null var ecoEnchantCompatibility: EcoEnchantDependency? = null @@ -68,7 +67,6 @@ object DependencyManager { // Packet Manager val forceProtocolib = ConfigHolder.DEFAULT_CONFIG.config.getBoolean("force_protocolib", false) packetManager = PacketManagerSelector.selectPacketManager(forceProtocolib) - externGuiTester = GuiTesterSelector.selectGuiTester // Enchantment Squared dependency if (pluginManager.isPluginEnabled("EnchantsSquared")) { @@ -192,7 +190,7 @@ object DependencyManager { var bypass = bypassEvent.isCancelled // Test if the inventory is a gui(version specific) - if (!bypass && (externGuiTester?.testIfGui(event.view) == true)) bypass = true + if (!bypass && externGuiTester.testIfGui(event.view)) bypass = true // Test if in an ax player warp rating gui if (!bypass && (axPlayerWarpsCompatibility?.testIfGui(player) == true)) bypass = true @@ -289,7 +287,7 @@ object DependencyManager { } // Test if the inventory is a gui(version specific) - if (!bypass && (externGuiTester?.testIfGui(event.view) == true)) bypass = true + if (!bypass && externGuiTester.testIfGui(event.view)) bypass = true // Test if in an ax player warp rating gui if (!bypass && (axPlayerWarpsCompatibility?.testIfGui(event.view.player) == true)) bypass = true diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GenericExternGuiTester.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GenericExternGuiTester.kt index 0e430ef..4046f4a 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GenericExternGuiTester.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GenericExternGuiTester.kt @@ -36,6 +36,11 @@ class GenericExternGuiTester { getHandleMethod = clazz.getMethod(HANDLE_METHOD_NAME) } + fun isInTest(): Boolean { + if(!testExist) testClassExist() + return inTesting + } + fun testClassExist() { testExist = true @@ -61,8 +66,7 @@ class GenericExternGuiTester { // Try if were in another plugin anvil inventory fun testIfGui(inventory: InventoryView): Boolean { // In case we are in a test environment - if(!testExist) testClassExist() - if(inTesting) return false + if(isInTest()) return false val clazz = getContainerClass(inventory) ?: return false diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt deleted file mode 100644 index e445d8d..0000000 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt +++ /dev/null @@ -1,15 +0,0 @@ -package xyz.alexcrea.cuanvil.dependency.gui - -import xyz.alexcrea.cuanvil.update.UpdateUtils - -object GuiTesterSelector { - - val selectGuiTester: GenericExternGuiTester? - get() { - val versionParts = UpdateUtils.currentMinecraftVersionArray() - if (versionParts[0] != 1) return null - - return GenericExternGuiTester() - } - -} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt index 4c3d680..6c18a75 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt @@ -1,6 +1,7 @@ package xyz.alexcrea.cuanvil.dependency.packet import org.bukkit.Bukkit +import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.dependency.MinecraftVersionUtil import xyz.alexcrea.cuanvil.dependency.packet.versions.* import xyz.alexcrea.cuanvil.update.UpdateUtils @@ -11,6 +12,9 @@ object PacketManagerSelector { fun selectPacketManager(forceProtocolib: Boolean): PacketManager { // Try to find version + if(DependencyManager.externGuiTester.isInTest()) + return NoPacketManager() + return if (forceProtocolib) protocolibIfPresent else { From b0f32fdba231b03610745da9c3375b43f176f05a Mon Sep 17 00:00:00 2001 From: alexcrea Date: Wed, 10 Jun 2026 17:18:43 +0200 Subject: [PATCH 185/207] do not load metrics in test --- .../kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt | 3 +++ src/test/java/io/delilaheve/CustomAnvilTests.java | 12 ++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt index 81ac870..1763db5 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt @@ -7,6 +7,7 @@ import io.delilaheve.CustomAnvil import io.delilaheve.util.ConfigOptions import xyz.alexcrea.cuanvil.command.DiagnosticExecutor import xyz.alexcrea.cuanvil.config.ConfigHolder +import xyz.alexcrea.cuanvil.dependency.DependencyManager object MetricsUtil { @@ -17,6 +18,8 @@ object MetricsUtil { private var FAST_STATS_METRICS: BukkitContext? = null fun loadMetrics(plugin: CustomAnvil) { + if(DependencyManager.externGuiTester.isInTest()) return + val config = ConfigHolder.DEFAULT_CONFIG.config val metricString = config.getString(ConfigOptions.METRIC_TYPE, MetricType.AUTO.value)!! val metricType = MetricType.from(metricString) diff --git a/src/test/java/io/delilaheve/CustomAnvilTests.java b/src/test/java/io/delilaheve/CustomAnvilTests.java index 45ecb7c..12e1ec1 100644 --- a/src/test/java/io/delilaheve/CustomAnvilTests.java +++ b/src/test/java/io/delilaheve/CustomAnvilTests.java @@ -8,11 +8,15 @@ public class CustomAnvilTests extends DefaultCustomAnvilTest { @Test public void simpleInitTest() { - Assertions.assertNotNull(server); - Assertions.assertNotNull(plugin); + try { + Assertions.assertNotNull(server); + Assertions.assertNotNull(plugin); - // Test shutdown - plugin.onDisable(); + // Test shutdown + plugin.onDisable(); + } catch (Exception e) { + Assertions.fail(e); + } } } From 12ec4e1f54822f254e7770a1df91ffca4a6a202f Mon Sep 17 00:00:00 2001 From: alexcrea Date: Sat, 13 Jun 2026 15:14:33 +0200 Subject: [PATCH 186/207] update faststats fix a potential plugin issue with disabling faststat in a certain way and fix potential submission on first run --- build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index fedeb77..6d2047e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,7 +22,7 @@ plugins { } group = "xyz.alexcrea" -version = "1.17.0" +version = "1.17.1" val isDevBuild = System.getenv("SMALL_COMMIT_HASH") != null val isPreRelease = System.getenv("IS_GITHUB_PRERELEASE") == "true" @@ -58,7 +58,7 @@ dependencies { compileOnly("org.spigotmc:spigot-api:1.18-R0.1-SNAPSHOT") // fast stats - implementation("dev.faststats.metrics:bukkit:0.25.1") + implementation("dev.faststats.metrics:bukkit:0.26.1") // minimessage implementation("net.kyori:adventure-text-minimessage:4.25.0") From 357832268632dde8284f54108d0059fde2881c97 Mon Sep 17 00:00:00 2001 From: alexcrea <42614139+alexcrea@users.noreply.github.com> Date: Sat, 13 Jun 2026 15:22:39 +0200 Subject: [PATCH 187/207] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 22d9da1..dab8ab6 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Custom Anvil **Custom Anvil** is a plugin that allows server administrators to customize every aspect of the anvil's mechanics. -It is expected to work on 1.18 to 1.21.7 minecraft servers running spigot or paper. +It is expected to work on 1.18 to 26.1.2 minecraft servers running spigot or paper. (the plugin support of 1.16.5 to 1.17.1 is experimental and may encounter issues) ### Download Locations: From 593527241a82edbb39f1c78bdcaf7b1333c103d0 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 16 Jun 2026 02:57:41 +0200 Subject: [PATCH 188/207] fix bugged unit repair & version bump --- build.gradle.kts | 2 +- .../kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 6d2047e..7b449fc 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,7 +22,7 @@ plugins { } group = "xyz.alexcrea" -version = "1.17.1" +version = "1.17.2" val isDevBuild = System.getenv("SMALL_COMMIT_HASH") != null val isPreRelease = System.getenv("IS_GITHUB_PRERELEASE") == "true" diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt index feee833..c5de6a8 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt @@ -121,7 +121,7 @@ class AnvilResultListener : Listener { view, inventory, player, leftItem, rightItem ) - if (unitRepairResult.isEmpty()) { + if (!unitRepairResult.isEmpty()) { onUnitRepairExtract( rightItem, event, player, inventory, unitRepairResult From 151666fd21a59fb3d0c8b87e201a14f05bcb9e29 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 16 Jun 2026 03:09:41 +0200 Subject: [PATCH 189/207] better excellent enchant fake event [skip ci] --- .../cuanvil/util/ModernPrepareAnvilCreator.kt | 12 ------------ .../plugins/ExcellentEnchantsDependency.kt | 12 +++++------- 2 files changed, 5 insertions(+), 19 deletions(-) delete mode 100644 nms/v1_21R1/src/main/kotlin/xyz/alexcrea/cuanvil/util/ModernPrepareAnvilCreator.kt diff --git a/nms/v1_21R1/src/main/kotlin/xyz/alexcrea/cuanvil/util/ModernPrepareAnvilCreator.kt b/nms/v1_21R1/src/main/kotlin/xyz/alexcrea/cuanvil/util/ModernPrepareAnvilCreator.kt deleted file mode 100644 index 0c51e0a..0000000 --- a/nms/v1_21R1/src/main/kotlin/xyz/alexcrea/cuanvil/util/ModernPrepareAnvilCreator.kt +++ /dev/null @@ -1,12 +0,0 @@ -package xyz.alexcrea.cuanvil.util - -import org.bukkit.event.inventory.PrepareAnvilEvent -import org.bukkit.inventory.InventoryView -import org.bukkit.inventory.ItemStack -import org.bukkit.inventory.view.AnvilView - -object ModernPrepareAnvilCreator { - fun createPrepareAnvil(view: InventoryView, item: ItemStack?): PrepareAnvilEvent { - return PrepareAnvilEvent(view as AnvilView, item) - } -} diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt index c2d3019..816a4df 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt @@ -13,7 +13,7 @@ import xyz.alexcrea.cuanvil.enchant.wrapped.CAEEPreV5Enchantment import xyz.alexcrea.cuanvil.enchant.wrapped.CAEEV5Enchantment import xyz.alexcrea.cuanvil.enchant.wrapped.CAEEV5_4Enchantment import xyz.alexcrea.cuanvil.enchant.wrapped.CALegacyEEEnchantment -import xyz.alexcrea.cuanvil.util.ModernPrepareAnvilCreator +import java.lang.reflect.Constructor import java.lang.reflect.Method import su.nightexpress.excellentenchants.api.EnchantRegistry as V5EnchantRegistry import su.nightexpress.excellentenchants.enchantment.impl.universal.CurseOfFragilityEnchant as LegacyCurseOfFragilityEnchant @@ -118,6 +118,8 @@ class ExcellentEnchantsDependency { private lateinit var handleRechargeMethod: Method private lateinit var handleCombineMethod: Method + private val prepareAnvilConstructor = PrepareAnvilEvent::class.java.constructors.first() as Constructor + fun redirectListeners() { val toUnregister = ArrayList() // get required PrepareAnvilEvent listener @@ -224,12 +226,8 @@ class ExcellentEnchantsDependency { val first: ItemStack = treatInput(event.leftItem) val second: ItemStack = treatInput(event.rightItem) - val fakeEvent: PrepareAnvilEvent = try { - //TODO remove this on legacy removal - PrepareAnvilEvent(event.view, result) - } catch (_: NoSuchMethodError) { - ModernPrepareAnvilCreator.createPrepareAnvil(event.view, result) - } + val fakeEvent = prepareAnvilConstructor.newInstance(event.view, result) + handleCombineMethod.invoke(this.usedAnvilListener, fakeEvent, first, second, result) event.result = fakeEvent.result From 29e08fe29bb81173916935bdc4987c6a23e2aa8b Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 16 Jun 2026 03:17:53 +0200 Subject: [PATCH 190/207] fix double space issue [ci skip] --- .github/workflows/gradle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 8c4a93c..66c1ad1 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -157,7 +157,7 @@ jobs: webhook-url: ${{ secrets.RELEASE_WEBHOOK_URL }} content: | ${{ github.event.release.prerelease == false && '<@&1338546156325568642>' || '<@&1352296092989001768>' }} - # New ${{ github.event.release.prerelease && 'beta' || '' }} version of custom anvil ! <:CustomAnvil:1262550667986342001>([Modrinth](https://modrinth.com/plugin/customanvil), [Hangar](https://hangar.papermc.io/alexcrea/CustomAnvil) and [GitHub](${{ github.event.release.html_url }}) links) + # New ${{ github.event.release.prerelease && 'beta ' || '' }}version of custom anvil ! <:CustomAnvil:1262550667986342001>([Modrinth](https://modrinth.com/plugin/customanvil), [Hangar](https://hangar.papermc.io/alexcrea/CustomAnvil) and [GitHub](${{ github.event.release.html_url }}) links) -# note: automated release. spigot is not uploaded yet. ${{ github.event.release.body }} From fc33b6fbd5c0386d88fb2a600064205d4eaf9008 Mon Sep 17 00:00:00 2001 From: alexcrea <42614139+alexcrea@users.noreply.github.com> Date: Tue, 16 Jun 2026 03:27:10 +0200 Subject: [PATCH 191/207] Update README.md --- README.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index dab8ab6..5e70842 100644 --- a/README.md +++ b/README.md @@ -63,13 +63,10 @@ See the [Compatibility list](https://github.com/alexcrea/CustomAnvil/blob/v1.x.x One of the configurations allow displaying price about 40 and removing Too Expensive. \ By how the minecraft client work: price above 40 can only be displayed green, even if the player does not own enough experience level. -Minecraft version 1.18 to latest marked as supported do not need any ProtocoLib dependency. \ +spigot version 1.18 to 1.21.11 do not need any ProtocoLib dependency. (26.1.0 or above requires it) \ Any recent paper version also are supported for this feature. -But you should wait for update for new version containing new enchantable item or new enchantments. -Other version need ProtocoLib enabled on your server for this feature. \ -You can also wait for an update of the plugin to support a newer version. - -Please note that 1.16.5 to 1.17.1 are not officially supported. Run at your own risk. +But you should wait for update for new version containing new enchantable item or new enchantments if any of this got added. +Else it is, likely, fine to use the current version you are ussing on a new paper version ### For custom enchantment plugin developers For information about the API, please refer to [the Wiki](https://github.com/alexcrea/CustomAnvil/wiki) \ From c064e4b1e1f5e628f6e903966410be38f3f3371c Mon Sep 17 00:00:00 2001 From: alexcrea <42614139+alexcrea@users.noreply.github.com> Date: Tue, 16 Jun 2026 03:32:46 +0200 Subject: [PATCH 192/207] forgot \ [skip ci] --- defaultconfigs/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/defaultconfigs/README.md b/defaultconfigs/README.md index a413b66..9e85e52 100644 --- a/defaultconfigs/README.md +++ b/defaultconfigs/README.md @@ -1,5 +1,5 @@ ### Default Plugin's Configurations From 1.18 to 1.20.6 use [1.18 configurations](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.18) \ -From 1.21 to 1.21.8 use [1.21 configurations](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21) -From 1.21.9 to 1.21.10 use [1.21.9 configurations](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21.9) -From 1.21.11 use [1.21.11 configurations](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21.11) \ No newline at end of file +From 1.21 to 1.21.8 use [1.21 configurations](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21) \ +From 1.21.9 to 1.21.10 use [1.21.9 configurations](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21.9) \ +From 1.21.11 use [1.21.11 configurations](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21.11) \ From b92b762551ed3e15266b0fb0b1a3ad44cbc6c36a Mon Sep 17 00:00:00 2001 From: alexcrea <42614139+alexcrea@users.noreply.github.com> Date: Tue, 16 Jun 2026 03:33:03 +0200 Subject: [PATCH 193/207] overdid \ [skip ci] --- defaultconfigs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/defaultconfigs/README.md b/defaultconfigs/README.md index 9e85e52..daa2088 100644 --- a/defaultconfigs/README.md +++ b/defaultconfigs/README.md @@ -2,4 +2,4 @@ From 1.18 to 1.20.6 use [1.18 configurations](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.18) \ From 1.21 to 1.21.8 use [1.21 configurations](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21) \ From 1.21.9 to 1.21.10 use [1.21.9 configurations](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21.9) \ -From 1.21.11 use [1.21.11 configurations](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21.11) \ +From 1.21.11 use [1.21.11 configurations](https://github.com/alexcrea/CustomAnvil/tree/master/defaultconfigs/1.21.11) From 96754fd2606407bc06ff2710434fdd27b007b4bb Mon Sep 17 00:00:00 2001 From: alexcrea <42614139+alexcrea@users.noreply.github.com> Date: Tue, 16 Jun 2026 03:48:47 +0200 Subject: [PATCH 194/207] better readme [skip ci] --- README.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5e70842..3c597b8 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ # Custom Anvil **Custom Anvil** is a plugin that allows server administrators to customize every aspect of the anvil's mechanics. -It is expected to work on 1.18 to 26.1.2 minecraft servers running spigot or paper. -(the plugin support of 1.16.5 to 1.17.1 is experimental and may encounter issues) ### Download Locations: @@ -16,7 +14,7 @@ the plugin can be downloaded on - Vanilla like default configuration. - Custom enchantment level limit. - Custom anvil recipes. -- Custom enchant restrictions (allow unsafe enchantment only for a group of item or create new restriction). +- Custom enchant restrictions (allows unsafe enchantment only for a group of item or create new restriction). - Custom items of unit repairs (repair damaged with unit of "material", for example the repair of diamond sword by diamonds). - Custom XP cost for every aspect of the anvil. - Permissions to bypass level limit or enchantment restriction. @@ -25,8 +23,12 @@ the plugin can be downloaded on - Gui to configure the plugin in game. - Support use of color code, hexadecimal color and minimessage for color/decoration - (Experimental) Folia support (gui do not work) +- (Experimental) Dialog rename (allows longer rename) +- (Experimental) Anvil with monetary cost (using vault) (require dialog rename) +And others ! --- ### Permissions: +Note that for most of them you also need to enable feature and in most case enable use of permission for the specfic feature (indicated with `(toggleable)`) ```yml # Generic and bypass permissions ca.affected: Player with this permission will be affected by the plugin @@ -44,17 +46,22 @@ ca.config.edit: Allow administrator to edit the plugin's config in game # ----------------------------------------------------------------------------- # Permissions related to use of color and minimessage ca.color.code: Allow player to use color code on rename if enabled (toggleable) +ca.color.code.[thecode] (for example ca.color.code.a): Allows usage of only certain color code (toggleable) ca.color.hex: Allow player to use hexadecimal color on rename if enabled (toggleable) -ca.rename.minimessage: Allow player to use minimessage formating on rename if enabled (toggleable) (only legacy compatible at the time) +ca.rename.minimessage: Allow player to use minimessage formating on rename if enabled (toggleable) # Permissions related to edition of the lore ca.lore_edit.book: Allow player to edit lore via book and quil if enabled (toggleable) ca.lore_edit.paper: Allow player to edit lore via paper if enabled (toggleable) + +# Others +ca.rename.dialog: Allow player to use the rename dialog (toggleable) ``` ### Commands -run `customanvil help` to get information about available commands (need permissions to use them) +run `/customanvil help` to get information about available commands \ +this only show subcommands you have permission for ### Supported Plugins See the [Compatibility list](https://github.com/alexcrea/CustomAnvil/blob/v1.x.x/COMPATIBILITY.md) @@ -97,3 +104,6 @@ Credits and thanks can be seen [here](https://github.com/alexcrea/CustomAnvil/bl ### Known issue: Most unknown registered enchantments (by unsupported custom enchantment plugin & datapacks) will not have restriction by default. Planned but no eta. + +### Do you need help with the plugin, or have any issue or suggestion? +You can ask on the discussion page, create a [GitHub issue](https://github.com/alexcrea/CustomAnvil/issues) or join my [discord](https://discord.gg/KHUNsUfRYJ)​ From 98d359f59fe0085f5a72b2fbabfe8c438c6cc96a Mon Sep 17 00:00:00 2001 From: alexcrea <42614139+alexcrea@users.noreply.github.com> Date: Tue, 16 Jun 2026 03:49:16 +0200 Subject: [PATCH 195/207] markdown issue [skip ci] --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3c597b8..afe6017 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,8 @@ the plugin can be downloaded on - (Experimental) Folia support (gui do not work) - (Experimental) Dialog rename (allows longer rename) - (Experimental) Anvil with monetary cost (using vault) (require dialog rename) -And others ! + +And more ! --- ### Permissions: Note that for most of them you also need to enable feature and in most case enable use of permission for the specfic feature (indicated with `(toggleable)`) From 4b5133c8720ed4ea344f75714aadc3301da6ab9b Mon Sep 17 00:00:00 2001 From: alexcrea <42614139+alexcrea@users.noreply.github.com> Date: Tue, 16 Jun 2026 03:49:32 +0200 Subject: [PATCH 196/207] =?UTF-8?q?markdown=20issue=C2=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index afe6017..9d9e678 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ the plugin can be downloaded on - (Experimental) Anvil with monetary cost (using vault) (require dialog rename) And more ! + --- ### Permissions: Note that for most of them you also need to enable feature and in most case enable use of permission for the specfic feature (indicated with `(toggleable)`) From cff94a2c5a6eb23ceef009fb1fb88fdc2416d0d1 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Tue, 16 Jun 2026 04:04:49 +0200 Subject: [PATCH 197/207] better thanks and put names in compatibility note [ci skip] --- COMPATIBILITY.md | 28 ++++++++++++++-------------- CREDITS.md | 21 ++++++++------------- 2 files changed, 22 insertions(+), 27 deletions(-) diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index 66ac561..916a089 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -6,54 +6,54 @@ This is cannot be fixed on geyser or my side. Here is various plugins that had issues with CustomAnvil where efforts was made for compatibility and should be working right: -some of them are cool I recommend checking them out ! +some if not all of them are cool ! I recommend checking them out ! ## Supported By CustomAnvil These plugins have compatibility handled by custom anvil. seek help on custom anvil and do not bother these developers #### Enchantment Plugins -- [ExcellentEnchants](https://www.spigotmc.org/resources/excellentenchants-%E2%AD%90-75-vanilla-like-enchantments.61693/): +- [ExcellentEnchants](https://www.spigotmc.org/resources/excellentenchants-%E2%AD%90-75-vanilla-like-enchantments.61693/) by NightExpress: Use ExcellentEnchants item type -- [EcoEnchant](https://www.spigotmc.org/resources/ecoenchants-%E2%AD%95-250-enchantments-%E2%9C%85-create-custom-enchants-%E2%9C%A8-essentials-cmi-support.79573/): +- [EcoEnchant](https://www.spigotmc.org/resources/ecoenchants-%E2%AD%95-250-enchantments-%E2%9C%85-create-custom-enchants-%E2%9C%A8-essentials-cmi-support.79573/) by Auxilor: Need to use /anvilconfigreload or a server restart to add newly added enchantment. Use EcoEnchant restriction system but new restriction can be added in custom anvil -- [Enchantment²](https://www.spigotmc.org/resources/enchants-squared-the-enchantsplus-rewrite-custom-enchantments-that-act-like-vanilla-ones.86747/): +- [Enchantment²](https://www.spigotmc.org/resources/enchants-squared-the-enchantsplus-rewrite-custom-enchantments-that-act-like-vanilla-ones.86747/) by Athlaeos: Support by Custom Anvil but still experimental. Automatic configuration. Plugin is not actively developed anymore -- [SuperEnchants](https://modrinth.com/plugin/superenchants) +- [SuperEnchants](https://modrinth.com/plugin/superenchants) by Aznos: Use SuperEnchant restrictions system but new restriction can be added in custom anvil #### Custom Items Plugins Custom Items support is considered unstable. If you find issue please report it ! -- [EcoItem](https://www.spigotmc.org/resources/30-sale%E2%8F%B3-ecoitems-%E2%AD%95-create-custom-items-%E2%9C%85-weapons-armors-tools-charms-%E2%9C%A8-item-levels-rarities.94601/) +- [EcoItem](https://www.spigotmc.org/resources/30-sale%E2%8F%B3-ecoitems-%E2%AD%95-create-custom-items-%E2%9C%85-weapons-armors-tools-charms-%E2%9C%A8-item-levels-rarities.94601/) by Exanthiax: May have some issue. but should partially work I hope -- [ItemAdder](https://www.spigotmc.org/resources/%E2%9C%A8itemsadder%E2%AD%90emotes-mobs-items-armors-hud-gui-emojis-blocks-wings-hats-liquids.73355/) +- [ItemAdder](https://www.spigotmc.org/resources/%E2%9C%A8itemsadder%E2%AD%90emotes-mobs-items-armors-hud-gui-emojis-blocks-wings-hats-liquids.73355/) by LoneDev: Need to fix unit item not working completly correctly as in can't have twice same base item #### Anvil Mechanics Plugins -- [Disenchantment](https://www.spigotmc.org/resources/disenchantment-1-21-1-1-20-6-new-book-splitting-mechanics.110741/) +- [Disenchantment](https://www.spigotmc.org/resources/disenchantment-1-21-1-1-20-6-new-book-splitting-mechanics.110741/) by H7KZ Partially use Custom Anvil maximum XP settings (>= 6.1.5) -- [HavenBags](https://www.spigotmc.org/resources/havenbags-shulker-like-player-bound-bags-1-17-1-21-4.110420/) +- [HavenBags](https://www.spigotmc.org/resources/havenbags-shulker-like-player-bound-bags-1-17-1-21-4.110420/) by hyperdefined For bag upgrade and skin via anvil. (version >= 1.31.0) -- [AxPlayerWarp](https://modrinth.com/project/QDJHDKvi) +- [AxPlayerWarp](https://modrinth.com/project/QDJHDKvi) by ArtillexStudios For its anvil inventory usage -- [ToolsStats](https://modrinth.com/project/oBZj9E15) +- [ToolsStats](https://modrinth.com/project/oBZj9E15) by Valorless For token application using anvil ### Known Partially Incompatible -- [UberEnchant](https://modrinth.com/plugin/uberenchant) +- [UberEnchant](https://modrinth.com/plugin/uberenchant) by coltonj96 Anvil handling as they are doing something similar to CustomAnvil. -It is by no mean there faults and I recomend checking them out +It is by no mean there faults and I recommend checking them out especially if custom anvil do not work for your use case ! -- [AdvencedEnchantments](https://ae.advancedplugins.net/) +- [AdvencedEnchantments](https://ae.advancedplugins.net/) by Advanced Plugins Paid plugin I do not own as I did not get commissioned for support. may be able to use api but cannot test on my side diff --git a/CREDITS.md b/CREDITS.md index 528ad15..56409f5 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -5,7 +5,7 @@ Thanks for all the contributors of bukkit, spigot, the paper team and the advent Thanks JetBrain for making IntelliJ ### Dependencies -Here dependencies are used by custom anvil +These dependencies (or a modified version of) are used by custom anvil - [IF](https://github.com/stefvanschie/IF) an inventory framework by stefvanschie - [Mockbukkit](https://github.com/MockBukkit/MockBukkit) for unit testing - [CentralPortalPlus](https://github.com/lalakii/central-portal-plus) by lalakii @@ -17,23 +17,18 @@ Here dependencies are used by custom anvil - [ModrinthUpdateChecker](https://github.com/Clickism/ModrinthUpdateChecker) by Clickism and thanks to the modrinth team ### Compatibility -Here is to credits all the author of plugins -It partially repeat the the [Compatibility list](https://github.com/alexcrea/CustomAnvil/blob/v1.x.x/COMPATIBILITY.md) -- Big Thanks for H7KZ for [Disenchantment](https://github.com/H7KZ/Disenchantment) -- [Enchantment²](https://www.spigotmc.org/resources/enchants-squared-the-enchantsplus-rewrite-custom-enchantments-that-act-like-vanilla-ones.86747/) by Athlaeos -- [EcoEnchant](https://www.spigotmc.org/resources/ecoenchants-%E2%AD%95-250-enchantments-%E2%9C%85-create-custom-enchants-%E2%9C%A8-essentials-cmi-support.79573/) by Auxilor -- [ExcellentEnchants](https://www.spigotmc.org/resources/excellentenchants-%E2%AD%90-75-vanilla-like-enchantments.61693/) by NightExpress -- [HavenBags](https://www.spigotmc.org/resources/havenbags-shulker-like-player-bound-bags-1-17-1-21-4.110420/) by hyperdefined -- [AxPlayerWarp](https://modrinth.com/project/QDJHDKvi) by ArtillexStudios -- [ToolsStats](https://modrinth.com/project/oBZj9E15) by Valorless +Thanks to all the cool creator making the minecraft plugin's ecosystem works ! \ +See [Compatibility list](https://github.com/alexcrea/CustomAnvil/blob/v1.x.x/COMPATIBILITY.md) for details + +but especially, Big Thanks for H7KZ maker of [Disenchantment](https://github.com/H7KZ/Disenchantment) ### Special Thanks -Thanks for Microsoft leading me into using a better operating system \ Thanks for all the users trying my plugin for these niche use cases -and for reporting issues and giving ideas ! +, reporting issues and giving ideas ! Thanks coltonj96 for [UberEnchant](https://modrinth.com/plugin/uberenchant). we may be incompatible with the anvil, but I do think it is a good alternative ! \ -I wish one day to work on cross compatibiltiy +I wish one day to work on cross compatibiltiy \ +* If custom anvil do not work well for you or your use case give it a try ! * From f82ccfa07e34ff5e2dde0256ef9c9013625e3c12 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Wed, 17 Jun 2026 02:14:09 +0200 Subject: [PATCH 198/207] make Version work with experimental 26.1 paper build --- src/main/java/xyz/alexcrea/cuanvil/update/Version.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/Version.java b/src/main/java/xyz/alexcrea/cuanvil/update/Version.java index 15476f5..a49fbdd 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/update/Version.java +++ b/src/main/java/xyz/alexcrea/cuanvil/update/Version.java @@ -21,7 +21,11 @@ public record Version(int major, int minor, int patch) { int[] versionParts = new int[]{0, 0, 0}; for (int i = 0; i < Math.min(3, partialVersion.length); i++) { - versionParts[i] = Integer.parseInt(partialVersion[i]); + try { + versionParts[i] = Integer.parseInt(partialVersion[i]); + } catch (NumberFormatException e) { + break; + } } return new Version(versionParts[0], versionParts[1], versionParts[2]); } From 9d616d2fd0bb4980d14f1fb85f26df207a7301dc Mon Sep 17 00:00:00 2001 From: alexcrea Date: Wed, 17 Jun 2026 02:14:53 +0200 Subject: [PATCH 199/207] remove use of legacy currentMinecraftVersionArray --- .../gui/config/global/BasicConfigGui.java | 12 +++--------- .../alexcrea/cuanvil/update/UpdateUtils.java | 16 ---------------- .../dependency/MinecraftVersionUtil.kt | 19 +++++++++---------- .../packet/PacketManagerSelector.kt | 6 +++--- 4 files changed, 15 insertions(+), 38 deletions(-) diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/BasicConfigGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/BasicConfigGui.java index 97f8e54..51936c7 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/BasicConfigGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/BasicConfigGui.java @@ -283,15 +283,9 @@ public class BasicConfigGui extends ChestGui implements ValueUpdatableGui { lore.add("§7If the player do not have the required xp level, the action will not be completable."); if(!this.packetManager.getCanSetInstantBuild()){ - if(MinecraftVersionUtil.INSTANCE.isTooNewForSpigot()){ - lore.add(""); - lore.add("§4/!\\§cCaution§4/!\\ §cYou need ProtocoLib installed and working or a paper server."); - lore.add("§cCurrently ProtocoLib is not detected."); - } else { - lore.add(""); - lore.add("§4/!\\§cCaution§4/!\\ §cYou need ProtocoLib installed and working or a newer version of this plugin for this to work."); - lore.add("§cCurrently ProtocoLib is not detected."); - } + lore.add(""); + lore.add("§4/!\\§cCaution§4/!\\ §cYou need ProtocoLib installed and working or a paper server."); + lore.add("§cCurrently ProtocoLib is not detected."); } String[] loreAsArray = new String[lore.size()]; diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/UpdateUtils.java b/src/main/java/xyz/alexcrea/cuanvil/update/UpdateUtils.java index c13515f..2907fef 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/update/UpdateUtils.java +++ b/src/main/java/xyz/alexcrea/cuanvil/update/UpdateUtils.java @@ -15,22 +15,6 @@ public class UpdateUtils { return Version.fromString(versionString); } - @Deprecated - public static int[] currentMinecraftVersionArray() { - String versionString = Bukkit.getServer().getBukkitVersion().split("-")[0]; - return UpdateUtils.readVersionFromString(versionString); - } - - public static int[] readVersionFromString(String versionString) { - String[] partialVersion = versionString.split("\\."); - int[] versionParts = new int[]{0, 0, 0}; - - for (int i = 0; i < Math.min(3, partialVersion.length); i++) { - versionParts[i] = Integer.parseInt(partialVersion[i]); - } - return versionParts; - } - public static void addToStringList(FileConfiguration config, String path, String... toAdd) { List groups = new ArrayList<>(config.getStringList(path)); groups.addAll(Arrays.asList(toAdd)); diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/MinecraftVersionUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/MinecraftVersionUtil.kt index d981f81..cbd2209 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/MinecraftVersionUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/MinecraftVersionUtil.kt @@ -6,29 +6,29 @@ object MinecraftVersionUtil { val craftbukkitVersion: String? get() { - val versionParts = UpdateUtils.currentMinecraftVersionArray() - if (versionParts[0] != 1) return null + val version = UpdateUtils.currentMinecraftVersion() + if (version.major != 1) return null - return when (versionParts[1]) { - 17 -> when (versionParts[2]) { + return when (version.minor) { + 17 -> when (version.patch) { 0, 1 -> "1_17R1" else -> null } - 18 -> when (versionParts[2]) { + 18 -> when (version.patch) { 0, 1 -> "1_18R1" 2 -> "1_18R2" else -> null } - 19 -> when (versionParts[2]) { + 19 -> when (version.patch) { 0, 1, 2 -> "1_19R1" 3 -> "1_19R2" 4 -> "1_19R3" else -> null } - 20 -> when (versionParts[2]) { + 20 -> when (version.patch) { 0, 1 -> "1_20R1" 2 -> "1_20R2" 3, 4 -> "1_20R3" @@ -36,7 +36,7 @@ object MinecraftVersionUtil { else -> null } - 21 -> when (versionParts[2]) { + 21 -> when (version.patch) { 0, 1 -> "1_21R1" 2, 3 -> "1_21R2" 4 -> "1_21R3" @@ -52,8 +52,7 @@ object MinecraftVersionUtil { } val isTooNewForSpigot: Boolean get() { - val versionParts = UpdateUtils.currentMinecraftVersionArray() - return versionParts[0] != 1 + return UpdateUtils.currentMinecraftVersion().major != 1 } } \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt index 6c18a75..d74ef08 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt @@ -38,8 +38,8 @@ object PacketManagerSelector { // Reobfuscated packet manager for spigot or paper as it remap private val reobfPacketManager: PacketManagerBase? get() { - val versionParts = UpdateUtils.currentMinecraftVersionArray() - if (versionParts[0] != 1) return null + val versionParts = UpdateUtils.currentMinecraftVersion() + if (versionParts.major != 1) return null try { val clazz = Class.forName("xyz.alexcrea.cuanvil.dependency.packet.versions." + @@ -47,7 +47,7 @@ object PacketManagerSelector { val manager = clazz.getConstructor().newInstance() return manager as PacketManagerBase - } catch (e: ClassNotFoundException) { + } catch (_: ClassNotFoundException) { return null } } From bc9cbe0b4459b1c00ccf44b2dde570d98f3b92ec Mon Sep 17 00:00:00 2001 From: alexcrea Date: Fri, 19 Jun 2026 20:29:10 +0200 Subject: [PATCH 200/207] fix and simplify xp handling - Fix xp limit not being respected - Fix player xp not being check causing error --- .../xyz/alexcrea/cuanvil/anvil/AnvilCost.kt | 17 +++++++++ .../cuanvil/listener/AnvilResultListener.kt | 12 ++++-- .../cuanvil/util/anvil/AnvilXpUtil.kt | 38 +++++++------------ 3 files changed, 40 insertions(+), 27 deletions(-) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilCost.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilCost.kt index f8ff89c..710687c 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilCost.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilCost.kt @@ -1,6 +1,8 @@ package xyz.alexcrea.cuanvil.anvil +import io.delilaheve.util.ConfigOptions import java.math.BigDecimal +import kotlin.math.min import io.delilaheve.util.ConfigOptions.getMonetaryMultiplier as moneyMultiplier open class AnvilCost { @@ -30,6 +32,21 @@ open class AnvilCost { return generic + enchantment + repair + rename + lore + illegalPenalty + workPenalty + recipe } + fun filteredXpCost(ignoreRules: Boolean = false): Int { + val original = asXpCost() + + // Test repair cost limit + return if ( + !ignoreRules && + !ConfigOptions.doRemoveCostLimit && + ConfigOptions.doCapCost + ) { + min(original, ConfigOptions.maxAnvilCost) + } else { + original + } + } + open fun asMonetaryCost(): BigDecimal { // multiply by per use type multipliers return BigDecimal(generic) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt index c5de6a8..e393dbd 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt @@ -270,13 +270,19 @@ class AnvilResultListener : Listener { } - private fun tryRemoveCost(player: Player, cost: AnvilCost): Boolean { + private fun tryRemoveCost(player: Player, result: AnvilResult): Boolean { if (player.gameMode == GameMode.CREATIVE) return true + + val cost = result.cost if (cost.isMonetary) { val result = EconomyManager.economy!!.remove(player, cost.asMonetaryCost()) if (!result) return false } else { - player.level -= cost.asXpCost() + val xpCost = cost.filteredXpCost() + if (xpCost > AnvilXpUtil.maximumXpCost(result.ignoreXpRules)) return false + if (player.level < xpCost) return false + + player.level -= xpCost } return true @@ -308,7 +314,7 @@ class AnvilResultListener : Listener { // If not creative middle click... if (event.click != ClickType.MIDDLE) { - if (!tryRemoveCost(player, cost)) return false + if (!tryRemoveCost(player, result)) return false // We remove what should be removed if (leftItem != null) leftItem.amount -= leftRemoveCount 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 f6ee12f..4846f31 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilXpUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/anvil/AnvilXpUtil.kt @@ -22,7 +22,6 @@ import xyz.alexcrea.cuanvil.dependency.economy.EconomyManager import xyz.alexcrea.cuanvil.group.ConflictType import xyz.alexcrea.cuanvil.util.AnvilTitleUtil import xyz.alexcrea.cuanvil.util.dialog.AnvilRenameDialogUtil -import kotlin.math.min object AnvilXpUtil { @@ -42,7 +41,15 @@ object AnvilXpUtil { cost.isMonetary = true setAnvilPrice(inventory, view, player, cost) } else - setAnvilInvXp(inventory, view, player, cost.asXpCost(), ignoreRules) + setAnvilInvXp(inventory, view, player, cost.filteredXpCost(ignoreRules), ignoreRules) + } + + fun maximumXpCost(ignoreRules: Boolean = false): Int { + return if (ConfigOptions.doRemoveCostLimit || ignoreRules) { + Int.MAX_VALUE + } else { + ConfigOptions.maxAnvilCost + 1 + } } /** @@ -55,28 +62,11 @@ object AnvilXpUtil { anvilCost: Int, ignoreRules: Boolean = false ) { - - // Test repair cost limit - val finalAnvilCost = if ( - !ignoreRules && - !ConfigOptions.doRemoveCostLimit && - ConfigOptions.doCapCost - ) { - min(anvilCost, ConfigOptions.maxAnvilCost) - } else { - anvilCost - } - - val maximumRepairCost = - if (ConfigOptions.doRemoveCostLimit || ignoreRules) { - Int.MAX_VALUE - } else { - ConfigOptions.maxAnvilCost + 1 - } + val maximumRepairCost = maximumXpCost(ignoreRules) // Try first just in case another plugin, or the test need this inventory.maximumRepairCost = maximumRepairCost - inventory.repairCost = finalAnvilCost + inventory.repairCost = anvilCost // TODO for 2.x.x use anvil view & set directly there /* Because Minecraft likes to have the final say in the repair cost displayed @@ -87,15 +77,15 @@ object AnvilXpUtil { ) { // retry after a tick inventory.maximumRepairCost = maximumRepairCost - inventory.repairCost = finalAnvilCost + inventory.repairCost = anvilCost // TODO for 2.x.x use anvil view & set directly there if (player !is Player) return@scheduleOnEntity if (player.gameMode != GameMode.CREATIVE) { val bypassToExpensive = (ConfigOptions.doReplaceTooExpensive) && - (finalAnvilCost >= 40) && - finalAnvilCost < inventory.maximumRepairCost + (anvilCost >= 40) && + anvilCost < inventory.maximumRepairCost DependencyManager.packetManager.setInstantBuild(player, bypassToExpensive) } From 950bad2168c67d8bdd6dd60360e14161c9879662 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Fri, 19 Jun 2026 20:31:31 +0200 Subject: [PATCH 201/207] versions bump --- build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 7b449fc..bb5a97f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,7 +22,7 @@ plugins { } group = "xyz.alexcrea" -version = "1.17.2" +version = "1.17.3" val isDevBuild = System.getenv("SMALL_COMMIT_HASH") != null val isPreRelease = System.getenv("IS_GITHUB_PRERELEASE") == "true" @@ -58,7 +58,7 @@ dependencies { compileOnly("org.spigotmc:spigot-api:1.18-R0.1-SNAPSHOT") // fast stats - implementation("dev.faststats.metrics:bukkit:0.26.1") + implementation("dev.faststats.metrics:bukkit:0.27.0") // minimessage implementation("net.kyori:adventure-text-minimessage:4.25.0") From 106cd53b02fcbb3d32331471ce77d471588d7861 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Sat, 20 Jun 2026 03:27:58 +0200 Subject: [PATCH 202/207] mark 26.2.x as supported on modrinth & hangar [ci skip] --- .github/workflows/gradle.yml | 2 +- gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 66c1ad1..a50cacd 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -26,7 +26,7 @@ jobs: contents: write env: - MODRINTH_VERSIONS: '["1.18.x", "1.19.x", "1.20.x", "1.21.x", "26.1.x"]' + MODRINTH_VERSIONS: '["1.18.x", "1.19.x", "1.20.x", "1.21.x", "26.1.x", "26.2.x"]' MODRINTH_PLATFORMS: '["spigot", "paper", "purpur", "folia"]' steps: diff --git a/gradle.properties b/gradle.properties index 19e22ef..4397d2e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,5 +9,5 @@ kotlin.daemon.jvmargs=-Xmx8G subprojects.reobfnms=v1_17R1,v1_18R1,v1_18R2,v1_19R1,v1_19R2,v1_19R3,v1_20R1,v1_20R2,v1_20R3,v1_20R4,v1_21R1,v1_21R2,v1_21R3,v1_21R4,v1_21R5,v1_21R6,v1_21R7 # list of version for hangar release -paperVersion=1.18-26.1.2 +paperVersion=1.18-26.2 From 178b372255d510e63f97958ba9d9a9171941832c Mon Sep 17 00:00:00 2001 From: alexcrea Date: Sat, 20 Jun 2026 14:55:31 +0200 Subject: [PATCH 203/207] paper mns use getter/setter no property access [ci skip] --- build.gradle.kts | 2 +- .../cuanvil/dependency/packet/versions/PaperPacketManager.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index bb5a97f..8fde3e6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,7 +22,7 @@ plugins { } group = "xyz.alexcrea" -version = "1.17.3" +version = "1.17.4" val isDevBuild = System.getenv("SMALL_COMMIT_HASH") != null val isPreRelease = System.getenv("IS_GITHUB_PRERELEASE") == "true" diff --git a/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/PaperPacketManager.kt b/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/PaperPacketManager.kt index 7e248e1..ee0c101 100644 --- a/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/PaperPacketManager.kt +++ b/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/versions/PaperPacketManager.kt @@ -24,8 +24,8 @@ class PaperPacketManager : PacketManagerBase(), PacketManager { sendedAbilities.mayfly = playerAbilities.mayfly sendedAbilities.instabuild = instantBuild sendedAbilities.mayBuild = playerAbilities.mayBuild - sendedAbilities.flyingSpeed = playerAbilities.flyingSpeed - sendedAbilities.walkingSpeed = playerAbilities.walkingSpeed + sendedAbilities.setFlyingSpeed(playerAbilities.getFlyingSpeed()) + sendedAbilities.setWalkingSpeed(playerAbilities.getWalkingSpeed()) } val packet = ClientboundPlayerAbilitiesPacket(sendedAbilities) nmsPlayer.connection.send(packet) From 95d3cf3228f1344fb75e8170c705337c7140c541 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Sat, 20 Jun 2026 23:43:01 +0200 Subject: [PATCH 204/207] update nightexpress --- impl/ExcellentEnchant5_4/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/impl/ExcellentEnchant5_4/build.gradle.kts b/impl/ExcellentEnchant5_4/build.gradle.kts index 87694be..f8c103d 100644 --- a/impl/ExcellentEnchant5_4/build.gradle.kts +++ b/impl/ExcellentEnchant5_4/build.gradle.kts @@ -13,5 +13,5 @@ repositories { dependencies { // Excellent Enchant compileOnly("su.nightexpress.excellentenchants:Core:5.4.1") - compileOnly("su.nightexpress.nightcore:main:2.14.1") + compileOnly("su.nightexpress.nightcore:main:2.16.2") } \ No newline at end of file From 37e8ca7da9b0af7546736f147e7ba19bbe0cbe37 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Sat, 20 Jun 2026 23:49:03 +0200 Subject: [PATCH 205/207] update excellentenchants --- impl/ExcellentEnchant5_4/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/impl/ExcellentEnchant5_4/build.gradle.kts b/impl/ExcellentEnchant5_4/build.gradle.kts index f8c103d..9fe598b 100644 --- a/impl/ExcellentEnchant5_4/build.gradle.kts +++ b/impl/ExcellentEnchant5_4/build.gradle.kts @@ -12,6 +12,6 @@ repositories { dependencies { // Excellent Enchant - compileOnly("su.nightexpress.excellentenchants:Core:5.4.1") + compileOnly("su.nightexpress.excellentenchants:Core:5.4.3") compileOnly("su.nightexpress.nightcore:main:2.16.2") } \ No newline at end of file From 7f7f049b7bf74b20228ea21261a587b049858958 Mon Sep 17 00:00:00 2001 From: alexcrea <42614139+alexcrea@users.noreply.github.com> Date: Mon, 22 Jun 2026 01:01:45 +0200 Subject: [PATCH 206/207] Update COMPATIBILITY.md --- COMPATIBILITY.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index 916a089..9d37521 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -13,7 +13,8 @@ These plugins have compatibility handled by custom anvil. seek help on custom an #### Enchantment Plugins - [ExcellentEnchants](https://www.spigotmc.org/resources/excellentenchants-%E2%AD%90-75-vanilla-like-enchantments.61693/) by NightExpress: - Use ExcellentEnchants item type + Use ExcellentEnchants item type \ + Also use ExcellentEnchant max enchant limit - [EcoEnchant](https://www.spigotmc.org/resources/ecoenchants-%E2%AD%95-250-enchantments-%E2%9C%85-create-custom-enchants-%E2%9C%A8-essentials-cmi-support.79573/) by Auxilor: Need to use /anvilconfigreload or a server restart to add newly added enchantment. From eb2e7b3abb3627cbc16dd83ba414c7e4909b6155 Mon Sep 17 00:00:00 2001 From: alexcrea Date: Mon, 22 Jun 2026 03:11:46 +0200 Subject: [PATCH 207/207] allow enchanted book for super enchant --- build.gradle.kts | 2 +- .../cuanvil/enchant/wrapped/CASuperEnchantEnchantment.java | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 8fde3e6..a5dc7c3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,7 +22,7 @@ plugins { } group = "xyz.alexcrea" -version = "1.17.4" +version = "1.17.5" val isDevBuild = System.getenv("SMALL_COMMIT_HASH") != null val isPreRelease = System.getenv("IS_GITHUB_PRERELEASE") == "true" 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 f5943be..6039dc8 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CASuperEnchantEnchantment.java +++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CASuperEnchantEnchantment.java @@ -5,6 +5,7 @@ import com.maddoxh.superEnchants.enchants.EnchantManager; import com.maddoxh.superEnchants.items.EnchantApplicator; import com.maddoxh.superEnchants.items.EnchantReader; import com.maddoxh.superEnchants.util.ConflictChecker; +import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; @@ -68,6 +69,8 @@ public class CASuperEnchantEnchantment extends CAEnchantmentBase implements Addi @Override public boolean isItemConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType, @NotNull ItemStack item) { + if(Material.ENCHANTED_BOOK.equals(item.getType())) return false; + return !enchant.canApplyTo(item.getType()); } }