From caa03ab590e96a08c6ab447ae7b0d9c0f0b59a36 Mon Sep 17 00:00:00 2001 From: andyksaw Date: Thu, 5 Dec 2024 20:23:06 +0900 Subject: [PATCH] Add enum arg type --- .../paper/core/extensions/StringExtensions.kt | 1 + .../brigadier/arguments/EnumArgument.kt | 44 +++++++++++++++++++ .../brigadier/arguments/OnOffArgument.kt | 3 +- .../extensions/CommandContextExtensions.kt | 12 ++++- .../support/component/ComponentExtensions.kt | 21 +++++++++ .../kotlin/Enum.kt} | 2 +- .../commands/builds/BuildEditCommand.kt | 39 +++++++++++++--- .../builds/commands/builds/BuildSetCommand.kt | 29 +++--------- .../builds/data/EditableBuildField.kt | 7 +++ .../builds/repositories/BuildRepository.kt | 17 +++---- 10 files changed, 131 insertions(+), 44 deletions(-) create mode 100644 pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/core/support/brigadier/arguments/EnumArgument.kt create mode 100644 pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/core/support/component/ComponentExtensions.kt rename pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/core/{extensions/EnumExtensions.kt => support/kotlin/Enum.kt} (77%) create mode 100644 pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/features/builds/data/EditableBuildField.kt diff --git a/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/core/extensions/StringExtensions.kt b/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/core/extensions/StringExtensions.kt index 53c40f3d..10b411b2 100644 --- a/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/core/extensions/StringExtensions.kt +++ b/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/core/extensions/StringExtensions.kt @@ -6,6 +6,7 @@ import java.util.regex.Pattern * Converts a non-dash formatted UUID string into a * dash-formatted UUID string */ +// TODO: make this an extension of something more specific fun String.toDashFormattedUUID(): String { val pattern = Pattern.compile("(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})") return pattern.matcher(this).replaceAll("$1-$2-$3-$4-$5") diff --git a/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/core/support/brigadier/arguments/EnumArgument.kt b/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/core/support/brigadier/arguments/EnumArgument.kt new file mode 100644 index 00000000..de80b076 --- /dev/null +++ b/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/core/support/brigadier/arguments/EnumArgument.kt @@ -0,0 +1,44 @@ +package com.projectcitybuild.pcbridge.paper.core.support.brigadier.arguments + +import com.mojang.brigadier.arguments.ArgumentType +import com.mojang.brigadier.arguments.StringArgumentType +import com.mojang.brigadier.context.CommandContext +import com.mojang.brigadier.exceptions.CommandSyntaxException +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType +import com.mojang.brigadier.suggestion.Suggestions +import com.mojang.brigadier.suggestion.SuggestionsBuilder +import io.papermc.paper.command.brigadier.MessageComponentSerializer +import io.papermc.paper.command.brigadier.argument.CustomArgumentType +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.NamedTextColor +import java.util.concurrent.CompletableFuture + +class EnumArgument>( + private val enumClass: Class, +) : CustomArgumentType.Converted { + override fun convert(nativeType: String): T { + return try { + enumClass.enumConstants?.firstOrNull { it.name.equals(nativeType, ignoreCase = true) } + ?: throw IllegalArgumentException("Invalid value: $nativeType") + } catch (e: IllegalArgumentException) { + val message = MessageComponentSerializer.message() + .serialize(Component.text("Invalid value: $nativeType", NamedTextColor.RED)) + + throw CommandSyntaxException(SimpleCommandExceptionType(message), message) + } + } + + override fun getNativeType(): ArgumentType { + return StringArgumentType.word() + } + + override fun listSuggestions( + context: CommandContext, + builder: SuggestionsBuilder, + ): CompletableFuture { + enumClass.enumConstants?.forEach { value -> + builder.suggest(value.name) + } + return builder.buildFuture() + } +} \ No newline at end of file diff --git a/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/core/support/brigadier/arguments/OnOffArgument.kt b/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/core/support/brigadier/arguments/OnOffArgument.kt index f2471121..6e7b7dfb 100644 --- a/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/core/support/brigadier/arguments/OnOffArgument.kt +++ b/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/core/support/brigadier/arguments/OnOffArgument.kt @@ -13,7 +13,6 @@ import net.kyori.adventure.text.Component import net.kyori.adventure.text.format.NamedTextColor import java.util.concurrent.CompletableFuture -@Suppress("UnstableApiUsage") class OnOffArgument: CustomArgumentType.Converted { override fun getNativeType(): ArgumentType = StringArgumentType.word() @@ -33,7 +32,7 @@ class OnOffArgument: CustomArgumentType.Converted { override fun listSuggestions( context: CommandContext, - builder: SuggestionsBuilder + builder: SuggestionsBuilder, ): CompletableFuture = builder .apply { suggest("on") diff --git a/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/core/support/brigadier/extensions/CommandContextExtensions.kt b/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/core/support/brigadier/extensions/CommandContextExtensions.kt index 27c5500d..24b93285 100644 --- a/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/core/support/brigadier/extensions/CommandContextExtensions.kt +++ b/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/core/support/brigadier/extensions/CommandContextExtensions.kt @@ -2,7 +2,17 @@ package com.projectcitybuild.pcbridge.paper.core.support.brigadier.extensions import com.mojang.brigadier.context.CommandContext -fun CommandContext.getOptionalArgument(name: String, clazz: Class): V? { +fun CommandContext.getArgument( + name: String, + clazz: Class, +): T { + return getArgument(name, clazz) +} + +fun CommandContext.getOptionalArgument( + name: String, + clazz: Class, +): T? { return try { getArgument(name, clazz) } catch (e: IllegalStateException) { diff --git a/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/core/support/component/ComponentExtensions.kt b/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/core/support/component/ComponentExtensions.kt new file mode 100644 index 00000000..e040893b --- /dev/null +++ b/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/core/support/component/ComponentExtensions.kt @@ -0,0 +1,21 @@ +package com.projectcitybuild.pcbridge.paper.core.support.component + +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.TextComponent + +/** + * Joins a collection of components together with the given [separator] + * inserted in between each collection element + */ +fun List.join(separator: Component): TextComponent.Builder { + val component = Component.text() + withIndex().forEach { entry -> + component.append(entry.value) + + val isLast = entry.index == size - 1 + if (!isLast) { + component.append(separator) + } + } + return component +} \ No newline at end of file diff --git a/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/core/extensions/EnumExtensions.kt b/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/core/support/kotlin/Enum.kt similarity index 77% rename from pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/core/extensions/EnumExtensions.kt rename to pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/core/support/kotlin/Enum.kt index 89319cd1..80ee3a37 100644 --- a/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/core/extensions/EnumExtensions.kt +++ b/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/core/support/kotlin/Enum.kt @@ -1,4 +1,4 @@ -package com.projectcitybuild.pcbridge.paper.core.extensions +package com.projectcitybuild.pcbridge.paper.core.support.kotlin import java.lang.Enum.valueOf diff --git a/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/features/builds/commands/builds/BuildEditCommand.kt b/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/features/builds/commands/builds/BuildEditCommand.kt index 97e8989e..5e0c5c52 100644 --- a/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/features/builds/commands/builds/BuildEditCommand.kt +++ b/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/features/builds/commands/builds/BuildEditCommand.kt @@ -11,9 +11,15 @@ import com.projectcitybuild.pcbridge.paper.core.support.brigadier.extensions.exe import com.projectcitybuild.pcbridge.paper.core.support.brigadier.extensions.requiresPermission import com.projectcitybuild.pcbridge.paper.core.support.brigadier.extensions.suggestsSuspending import com.projectcitybuild.pcbridge.paper.core.support.brigadier.traceSuspending +import com.projectcitybuild.pcbridge.paper.core.support.component.join +import com.projectcitybuild.pcbridge.paper.features.builds.data.EditableBuildField import io.papermc.paper.command.brigadier.CommandSourceStack import io.papermc.paper.command.brigadier.Commands -import net.kyori.adventure.text.minimessage.MiniMessage +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.event.ClickEvent +import net.kyori.adventure.text.event.HoverEvent +import net.kyori.adventure.text.format.NamedTextColor +import net.kyori.adventure.text.format.TextDecoration import org.bukkit.entity.Player import org.bukkit.plugin.Plugin @@ -51,13 +57,34 @@ class BuildEditCommand( val build = buildRepository.get(name) checkNotNull(build) { "Build not found" } - val actions = listOf("name", "description", "lore") - val actionsRow = actions.joinToString(separator = " ") { field -> - val command = "/builds set ${build.id} $field " - "[${field.uppercase()}]" + val actions = EditableBuildField.entries + val actionComponents = actions.map { field -> + val existing = when (field) { + EditableBuildField.NAME -> build.name + EditableBuildField.DESCRIPTION -> build.description + EditableBuildField.LORE -> build.lore + } + val command = "/builds set ${build.id} ${field.name} ${existing.orEmpty()}" + val hoverText = "/builds set ${build.id} $field" + + Component.text() + .append(Component.text("[")) + .append( + Component.text(field.name, NamedTextColor.WHITE) + .decorate(TextDecoration.UNDERLINED) + .clickEvent(ClickEvent.suggestCommand(command)) + .hoverEvent(HoverEvent.showText(Component.text(hoverText))) + ) + .append(Component.text("]")) } + val actionComponent = actionComponents.join( + separator = Component.space() + ) + context.source.sender.sendMessage( - MiniMessage.miniMessage().deserialize("Click a field to edit:$actionsRow") + Component.text("Click a field to edit:", NamedTextColor.GRAY) + .appendNewline() + .append(actionComponent) ) } } \ No newline at end of file diff --git a/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/features/builds/commands/builds/BuildSetCommand.kt b/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/features/builds/commands/builds/BuildSetCommand.kt index c785d547..3f53f6f9 100644 --- a/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/features/builds/commands/builds/BuildSetCommand.kt +++ b/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/features/builds/commands/builds/BuildSetCommand.kt @@ -8,11 +8,12 @@ import com.mojang.brigadier.tree.LiteralCommandNode import com.projectcitybuild.pcbridge.paper.PermissionNode import com.projectcitybuild.pcbridge.paper.features.builds.repositories.BuildRepository import com.projectcitybuild.pcbridge.paper.core.support.brigadier.BrigadierCommand +import com.projectcitybuild.pcbridge.paper.core.support.brigadier.arguments.EnumArgument import com.projectcitybuild.pcbridge.paper.core.support.brigadier.extensions.executesSuspending -import com.projectcitybuild.pcbridge.paper.core.support.brigadier.extensions.getOptionalArgument import com.projectcitybuild.pcbridge.paper.core.support.brigadier.extensions.requiresPermission -import com.projectcitybuild.pcbridge.paper.core.support.brigadier.extensions.suggestsSuspending +import com.projectcitybuild.pcbridge.paper.core.support.brigadier.extensions.getOptionalArgument import com.projectcitybuild.pcbridge.paper.core.support.brigadier.traceSuspending +import com.projectcitybuild.pcbridge.paper.features.builds.data.EditableBuildField import io.papermc.paper.command.brigadier.CommandSourceStack import io.papermc.paper.command.brigadier.Commands import net.kyori.adventure.text.minimessage.MiniMessage @@ -29,17 +30,9 @@ class BuildSetCommand( .then( Commands.argument("id", IntegerArgumentType.integer()) .then( - Commands.argument("field", StringArgumentType.word()) - .suggests { _, suggestions -> - // TODO: clean this up - suggestions.suggest("description") - suggestions.suggest("name") - suggestions.suggest("lore") - suggestions.buildFuture() - } + Commands.argument("field", EnumArgument(EditableBuildField::class.java)) .then( Commands.argument("value", StringArgumentType.greedyString()) - .suggestsSuspending(plugin, ::suggestDescription) .executesSuspending(plugin, ::execute) ) .executesSuspending(plugin, ::execute) @@ -48,28 +41,18 @@ class BuildSetCommand( .build() } - private suspend fun suggestDescription( - context: CommandContext, - suggestions: SuggestionsBuilder, - ) { - val id = suggestions.remaining - - // TODO - } - private suspend fun execute(context: CommandContext) = context.traceSuspending { - val field = context.getArgument("field", String::class.java) + val field = context.getArgument("field", EditableBuildField::class.java) val id = context.getArgument("id", Int::class.java) val value = context.getOptionalArgument("value", String::class.java) ?: "" val player = context.source.executor as? Player checkNotNull(player) { "Only a player can use this command" } - val editableField = BuildRepository.EditableField.valueOf(field.uppercase()) buildRepository.set( id = id, player = player, - field = editableField, + field = field, value = value, ) diff --git a/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/features/builds/data/EditableBuildField.kt b/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/features/builds/data/EditableBuildField.kt new file mode 100644 index 00000000..83980983 --- /dev/null +++ b/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/features/builds/data/EditableBuildField.kt @@ -0,0 +1,7 @@ +package com.projectcitybuild.pcbridge.paper.features.builds.data + +enum class EditableBuildField { + NAME, + DESCRIPTION, + LORE, +} \ No newline at end of file diff --git a/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/features/builds/repositories/BuildRepository.kt b/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/features/builds/repositories/BuildRepository.kt index 13535bba..7b480b53 100644 --- a/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/features/builds/repositories/BuildRepository.kt +++ b/pcbridge-paper/src/main/kotlin/com/projectcitybuild/pcbridge/paper/features/builds/repositories/BuildRepository.kt @@ -4,18 +4,13 @@ import com.projectcitybuild.pcbridge.http.models.pcb.Build import com.projectcitybuild.pcbridge.http.models.pcb.PaginatedResponse import com.projectcitybuild.pcbridge.http.services.pcb.BuildHttpService import com.projectcitybuild.pcbridge.paper.core.support.kotlin.Trie +import com.projectcitybuild.pcbridge.paper.features.builds.data.EditableBuildField import org.bukkit.Location import org.bukkit.entity.Player class BuildRepository( private val buildHttpService: BuildHttpService, ) { - enum class EditableField { - NAME, - DESCRIPTION, - LORE, - } - class IdMap( initial: Map, ) { @@ -169,17 +164,17 @@ class BuildRepository( suspend fun set( id: Int, player: Player, - field: EditableField, + field: EditableBuildField, value: String, ): Build { val build = buildHttpService.set( id = id, playerUUID = player.uniqueId, - name = if (field == EditableField.NAME) value else null, - description = if (field == EditableField.DESCRIPTION) value else null, - lore = if (field == EditableField.LORE) value else null, + name = if (field == EditableBuildField.NAME) value else null, + description = if (field == EditableBuildField.DESCRIPTION) value else null, + lore = if (field == EditableBuildField.LORE) value else null, ) - if (field == EditableField.NAME) { + if (field == EditableBuildField.NAME) { cache?.update(id, newName = build.name) } return build