diff --git a/build.gradle.kts b/build.gradle.kts index 1ff46b1..49b00d9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,7 +22,7 @@ plugins { } group = "xyz.alexcrea" -version = "1.16.3" +version = "1.17.0" val isDevBuild = System.getenv("SMALL_COMMIT_HASH") != null val isPreRelease = System.getenv("IS_GITHUB_PRERELEASE") == "true" 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 new file mode 100644 index 0000000..a50af23 --- /dev/null +++ b/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dialog/AnvilRenameDialog.kt @@ -0,0 +1,14 @@ +package xyz.alexcrea.cuanvil.dialog + +import org.bukkit.entity.HumanEntity +import org.bukkit.event.inventory.PrepareAnvilEvent + +interface AnvilRenameDialog { + + fun canSendDialog(): Boolean + + fun tryShowDialog(player: HumanEntity, event: PrepareAnvilEvent) + + fun closeInventory(player: HumanEntity) + +} \ No newline at end of file diff --git a/nms/nms-paper/build.gradle.kts b/nms/nms-paper/build.gradle.kts index ea23f5c..d0e12a7 100644 --- a/nms/nms-paper/build.gradle.kts +++ b/nms/nms-paper/build.gradle.kts @@ -11,7 +11,7 @@ dependencies { implementation(project(":nms:nms-common")) // Used for nms - paperweight.paperDevBundle("1.20.6-R0.1-SNAPSHOT") + paperweight.paperDevBundle("1.21.7-R0.1-SNAPSHOT") } repositories { 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 new file mode 100644 index 0000000..00491e0 --- /dev/null +++ b/nms/nms-paper/src/main/kotlin/xyz/alexcrea/cuanvil/dialog/AnvilRenameDialogImpl.kt @@ -0,0 +1,181 @@ +package xyz.alexcrea.cuanvil.dialog + +import io.papermc.paper.dialog.Dialog +import io.papermc.paper.registry.data.dialog.ActionButton +import io.papermc.paper.registry.data.dialog.DialogBase +import io.papermc.paper.registry.data.dialog.action.DialogAction +import io.papermc.paper.registry.data.dialog.body.DialogBody +import io.papermc.paper.registry.data.dialog.input.DialogInput +import io.papermc.paper.registry.data.dialog.type.DialogType +import io.papermc.paper.threadedregions.scheduler.ScheduledTask +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.event.ClickCallback +import net.kyori.adventure.text.format.TextColor +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer +import net.minecraft.world.inventory.AnvilMenu +import org.bukkit.craftbukkit.event.CraftEventFactory +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.ItemStack +import org.bukkit.plugin.Plugin +import java.util.UUID +import java.util.function.BiFunction +import java.util.function.Consumer +import java.util.function.Supplier +import kotlin.collections.set + +@Suppress("UnstableApiUsage") +class AnvilRenameDialogImpl( + val fromFormated: BiFunction, + val maxLength: Supplier, + val plugin: Plugin, +): AnvilRenameDialog { + + companion object { + private const val RENAME_TEXT_KEY = "rename" + + private const val MAX_WIDTH = 512 + + private val PLAIN_TEXT_SERIALIZER = PlainTextComponentSerializer.plainText() + + // 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_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 lastLeftItem = HashMap() + private val runTaskMap = HashMap() + + private val containerField = CraftInventoryView::class.java.getDeclaredField("container") + + init { + containerField.setAccessible(true) + } + + override fun canSendDialog(): Boolean { + return true + } + + fun makeDialog(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) + .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(), + ), + ) + 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(), + )) + } + } + + 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) + } + + private fun setName(player: HumanEntity, view: CraftAnvilView, name: String?) { + val menu = (containerField.get(view) as AnvilMenu) + menu.itemName = name + + if(name == null) + lastNames.remove(player.uniqueId) + else + lastNames[player.uniqueId] = name + CraftEventFactory.callPrepareResultEvent(menu, 2); + } + + private fun nameFromItem(player: HumanEntity, item: ItemStack?): String? { + return if(item?.hasItemMeta() != true || !item.itemMeta.hasCustomName()) + PLAIN_TEXT_SERIALIZER.serializeOrNull(item?.effectiveName()) + else fromFormated.apply(player, item.effectiveName()) + } + + private fun tryShowDialogScheduled(player: HumanEntity, event: PrepareAnvilEvent) { + val view = event.view + 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) + lastLeftItem.remove(player.uniqueId) + else lastLeftItem[player.uniqueId] = leftItemStr + + setName(player, view, nameFromItem(player, leftItem)) + return + } + + if(lastName == renameText) + return + + if(renameText?.isBlank() == true) { + setName(player, view, lastNames[player.uniqueId]) + return + } + + val dialog = makeDialog(lastName) + { result -> setResult(player, view, result) } + player.showDialog(dialog) + } + + // We need to wait for a short time as changing item will change the name BEFORE the item change + // no guaranty both of them came in the same tick too so let's wait 2 tick.... + override fun tryShowDialog(player: HumanEntity, event: PrepareAnvilEvent) { + runTaskMap.remove(player.uniqueId)?.cancel() + + val task = player.scheduler.runDelayed( + plugin, + { _ -> + run { tryShowDialogScheduled(player, event) } + }, + {}, + 2 + ) + if(task == null) return + + runTaskMap[player.uniqueId] = task + } + + override fun closeInventory(player: HumanEntity) { + lastNames.remove(player.uniqueId) + lastLeftItem.remove(player.uniqueId) + runTaskMap.remove(player.uniqueId)?.cancel() + } + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilCloseListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilCloseListener.kt index 60a0339..d707b56 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilCloseListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilCloseListener.kt @@ -7,6 +7,7 @@ import org.bukkit.event.Listener import org.bukkit.event.inventory.InventoryCloseEvent import org.bukkit.inventory.AnvilInventory import xyz.alexcrea.cuanvil.dependency.packet.PacketManager +import xyz.alexcrea.cuanvil.util.dialog.AnvilRenameDialogUtil class AnvilCloseListener(private val packetManager: PacketManager) : Listener { @@ -18,6 +19,7 @@ class AnvilCloseListener(private val packetManager: PacketManager) : Listener { packetManager.setInstantBuild(player, false) } + AnvilRenameDialogUtil.anvilRenameDialog.closeInventory(player) } } \ 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 46b70fa..b7bedb3 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt @@ -18,6 +18,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 @@ -26,6 +27,7 @@ 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 xyz.alexcrea.cuanvil.util.dialog.AnvilRenameDialogUtil import java.util.concurrent.atomic.AtomicInteger /** @@ -41,6 +43,8 @@ class PrepareAnvilListener : Listener { const val ANVIL_OUTPUT_SLOT = 2 var IS_EMPTY_TEST = false + + const val RENAME_DIALOG_PERMISSION = "ca.rename.dialog" } /** @@ -80,6 +84,8 @@ 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 @@ -117,6 +123,16 @@ class PrepareAnvilListener : Listener { } + private fun tryRenameDialog( + player: HumanEntity, + event: PrepareAnvilEvent + ) { + if(!ConfigOptions.doRenameDialog || !AnvilRenameDialogUtil.anvilRenameDialog.canSendDialog()) return + if(ConfigOptions.doRenameDialogUsePermission && !player.hasPermission(RENAME_DIALOG_PERMISSION)) return + + AnvilRenameDialogUtil.anvilRenameDialog.tryShowDialog(player, event) + } + private fun isImmutable(item: ItemStack?): Boolean { if (item.isAir) return false @@ -208,11 +224,8 @@ class PrepareAnvilListener : Listener { var useColor = false if (ConfigOptions.renameColorPossible && renameText != null) { val component = AnvilColorUtil.handleColor( - renameText, player, - ConfigOptions.permissionNeededForColor, - ConfigOptions.allowColorCode, ConfigOptions.allowHexadecimalColor, ConfigOptions.allowMinimessage, - AnvilColorUtil.ColorUseType.RENAME - ) + renameText, + AnvilColorUtil.renamePermission(player)) if (component != null) { renameText = MiniMessageUtil.legacy_mm.serialize(component) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilColorUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilColorUtil.kt index 25c4268..0492d32 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilColorUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilColorUtil.kt @@ -60,25 +60,11 @@ object AnvilColorUtil { return ColorPermissions(canUseColorCode, canUseHexColor, canUseMinimessage, player) } - /** - * 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) + fun renamePermission(player: Permissible): ColorPermissions { + return calculatePermissions(player, + ConfigOptions.permissionNeededForColor, + ConfigOptions.allowColorCode, ConfigOptions.allowHexadecimalColor, ConfigOptions.allowMinimessage, + ColorUseType.RENAME) } /** diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/dialog/AnvilRenameDialogUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/dialog/AnvilRenameDialogUtil.kt new file mode 100644 index 0000000..c72ddf3 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/dialog/AnvilRenameDialogUtil.kt @@ -0,0 +1,51 @@ +package xyz.alexcrea.cuanvil.util.dialog + +import io.delilaheve.CustomAnvil +import io.delilaheve.util.ConfigOptions +import net.kyori.adventure.text.Component +import org.bukkit.entity.HumanEntity +import org.bukkit.event.inventory.PrepareAnvilEvent +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 + +object AnvilRenameDialogUtil { + + val anvilRenameDialog: AnvilRenameDialog; + + init { + val version = UpdateUtils.currentMinecraftVersion() + anvilRenameDialog = (if(!PlatformUtil.isPaper || + (version.major <= 1 && version.minor <= 21 && version.patch <= 6)) { + NoImplAnvilRenameDialog() + } else { + AnvilRenameDialogImpl({ player, component -> AnvilColorUtil.revertColorSmallest( + component, AnvilColorUtil.renamePermission(player) + ) }, + { ConfigOptions.renameDialogMaxSize }, + CustomAnvil.instance, + ) + }) + } + + class NoImplAnvilRenameDialog: AnvilRenameDialog { + + override fun canSendDialog(): Boolean { + return false + } + + override fun tryShowDialog( + player: HumanEntity, + event: PrepareAnvilEvent + ) { + + } + + override fun closeInventory(player: HumanEntity) { + + } + + } +} \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index d896fe6..f59dc46 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -102,7 +102,9 @@ permission_needed_for_color: true use_of_color_cost: 0 # Dialogue rename menu make use of dialog menu to allow bigger rename -# You can change the maximum size +# You can also change the maximum size up to 2147483647 +# +# This feature only work on paper 1.21.7 or later # # At the moment only english is available for this menu... sorry ! #