Skip to content

Commit 95c769b

Browse files
committed
fix: unwrap native type in custom argument type
1 parent 1b9df99 commit 95c769b

8 files changed

Lines changed: 155 additions & 25 deletions

File tree

‎api/common/src/main/kotlin/su/plo/slib/api/command/brigadier/CustomArgumentType.kt‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package su.plo.slib.api.command.brigadier
22

33
import com.mojang.brigadier.arguments.ArgumentType
4+
import com.mojang.brigadier.context.CommandContext
5+
import com.mojang.brigadier.suggestion.Suggestions
6+
import com.mojang.brigadier.suggestion.SuggestionsBuilder
47
import org.jetbrains.annotations.ApiStatus
8+
import java.util.concurrent.CompletableFuture
59

610
/**
711
* An argument type that wraps a native argument type.
@@ -24,11 +28,22 @@ interface CustomArgumentType<PARSED, NATIVE> : ArgumentType<PARSED> {
2428
/**
2529
* Whether native suggestions should be used.
2630
*
27-
* Set to `false` is you want to implement custom [listSuggestions].
31+
* Set to `false` if you want to implement custom [listSuggestions].
2832
*/
2933
fun useNativeSuggestions(): Boolean =
3034
true
3135

36+
/**
37+
* Suggestions of the [nativeType].
38+
*
39+
* Override to implement custom suggestions, along with [useNativeSuggestions] set to `false`.
40+
*/
41+
override fun <S> listSuggestions(
42+
context: CommandContext<S>,
43+
builder: SuggestionsBuilder,
44+
): CompletableFuture<Suggestions> =
45+
nativeType.listSuggestions(context, builder)
46+
3247
/**
3348
* This is controlled client-side and can't be changed server-side.
3449
*/

‎common-server/src/testFixtures/kotlin/su/plo/slib/server/TestServerCommands.kt‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import su.plo.slib.api.command.brigadier.McTextMessage
99
import su.plo.slib.api.event.command.McBrigadierCommandsRegisterEvent
1010
import su.plo.slib.api.logging.McLoggerFactory
1111
import su.plo.slib.api.server.command.brigadier.McArgumentTypes
12+
import su.plo.slib.server.command.NestedGameProfilesArgumentType
13+
import su.plo.slib.server.command.NestedGameProfilesTarget
1214
import su.plo.slib.server.command.TranslatedArgumentType
1315
import su.plo.slib.server.command.UuidArgumentType
1416
import java.util.concurrent.atomic.AtomicBoolean
@@ -102,6 +104,22 @@ fun registerCommands() {
102104
}
103105
)
104106

107+
registry.register(
108+
literalCommand("brigadier-nested-custom-type") {
109+
val target by argument("target", NestedGameProfilesArgumentType())
110+
111+
executes {
112+
val message =
113+
when (target) {
114+
is NestedGameProfilesTarget.Everyone -> "everyone"
115+
is NestedGameProfilesTarget.Selector -> "native selector"
116+
}
117+
118+
source.source.sendMessage("Nested custom type: $message")
119+
}
120+
}
121+
)
122+
105123
registry.register(
106124
literalCommand("brigadier-position-selector") {
107125
val position by argument("position", McArgumentTypes.position())
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package su.plo.slib.server.command
2+
3+
import com.mojang.brigadier.StringReader
4+
import com.mojang.brigadier.arguments.ArgumentType
5+
import com.mojang.brigadier.context.CommandContext
6+
import com.mojang.brigadier.suggestion.Suggestions
7+
import com.mojang.brigadier.suggestion.SuggestionsBuilder
8+
import su.plo.slib.api.command.brigadier.CustomArgumentType
9+
import su.plo.slib.api.server.command.brigadier.McArgumentTypes
10+
import su.plo.slib.api.server.command.brigadier.McGameProfilesArgumentResolver
11+
import java.util.concurrent.CompletableFuture
12+
13+
private const val EVERYONE = "everyone"
14+
15+
sealed interface NestedGameProfilesTarget {
16+
data object Everyone : NestedGameProfilesTarget
17+
18+
data class Selector(val resolver: McGameProfilesArgumentResolver) : NestedGameProfilesTarget
19+
}
20+
21+
class NestedGameProfilesArgumentType :
22+
CustomArgumentType<NestedGameProfilesTarget, McGameProfilesArgumentResolver> {
23+
override val nativeType: ArgumentType<McGameProfilesArgumentResolver> =
24+
McArgumentTypes.gameProfiles()
25+
26+
override fun useNativeSuggestions(): Boolean = false
27+
28+
override fun parse(reader: StringReader): NestedGameProfilesTarget {
29+
val start = reader.cursor
30+
31+
if (reader.readUnquotedString() == EVERYONE) {
32+
return NestedGameProfilesTarget.Everyone
33+
}
34+
35+
reader.cursor = start
36+
37+
return NestedGameProfilesTarget.Selector(nativeType.parse(reader))
38+
}
39+
40+
override fun <S> listSuggestions(
41+
context: CommandContext<S>,
42+
builder: SuggestionsBuilder,
43+
): CompletableFuture<Suggestions> {
44+
if (!EVERYONE.startsWith(builder.remaining, ignoreCase = true)) {
45+
return super.listSuggestions(context, builder)
46+
}
47+
48+
builder.suggest(EVERYONE)
49+
50+
return builder.buildFuture()
51+
}
52+
}

‎common/src/main/kotlin/su/plo/slib/command/brigadier/CustomArgumentCommandNode.kt‎

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,19 @@ package su.plo.slib.command.brigadier
33
import com.mojang.brigadier.Command
44
import com.mojang.brigadier.RedirectModifier
55
import com.mojang.brigadier.StringReader
6+
import com.mojang.brigadier.arguments.ArgumentType
67
import com.mojang.brigadier.builder.RequiredArgumentBuilder
7-
import com.mojang.brigadier.context.CommandContext
88
import com.mojang.brigadier.context.CommandContextBuilder
99
import com.mojang.brigadier.context.ParsedArgument
1010
import com.mojang.brigadier.exceptions.CommandSyntaxException
1111
import com.mojang.brigadier.suggestion.SuggestionProvider
12-
import com.mojang.brigadier.suggestion.Suggestions
13-
import com.mojang.brigadier.suggestion.SuggestionsBuilder
1412
import com.mojang.brigadier.tree.ArgumentCommandNode
1513
import com.mojang.brigadier.tree.CommandNode
1614
import su.plo.slib.api.command.brigadier.CustomArgumentType
1715
import su.plo.slib.api.command.brigadier.McBrigadierSource
18-
import java.util.concurrent.CompletableFuture
1916
import java.util.function.Predicate
2017

18+
@Suppress("UNCHECKED_CAST")
2119
class CustomArgumentCommandNode<S, PARSED, NATIVE>(
2220
name: String,
2321
val customArgumentType: CustomArgumentType<PARSED, NATIVE>,
@@ -30,7 +28,7 @@ class CustomArgumentCommandNode<S, PARSED, NATIVE>(
3028
customSuggestions: SuggestionProvider<S>?,
3129
) : ArgumentCommandNode<S, NATIVE>(
3230
name,
33-
customArgumentType.nativeType,
31+
customArgumentType.unwrapNativeType() as ArgumentType<NATIVE>,
3432
command,
3533
requirement,
3634
redirect,
@@ -73,13 +71,7 @@ fun <S, PARSED, NATIVE> RequiredArgumentBuilder<S, PARSED>.buildCustom(
7371
isFork,
7472
suggestionsProvider ?:
7573
if (!type.useNativeSuggestions()) {
76-
object : SuggestionProvider<S> {
77-
override fun getSuggestions(
78-
context: CommandContext<S?>,
79-
builder: SuggestionsBuilder,
80-
): CompletableFuture<Suggestions> =
81-
type.listSuggestions(context, builder)
82-
}
74+
SuggestionProvider<S> { context, builder -> type.listSuggestions(context, builder) }
8375
} else {
8476
null
8577
},
@@ -89,3 +81,6 @@ fun <S, PARSED, NATIVE> RequiredArgumentBuilder<S, PARSED>.buildCustom(
8981

9082
return result
9183
}
84+
85+
tailrec fun ArgumentType<*>.unwrapNativeType(): ArgumentType<*> =
86+
if (this is CustomArgumentType<*, *>) nativeType.unwrapNativeType() else this

‎minestom/src/main/kotlin/su/plo/slib/minestom/command/MinestomCommandManager.kt‎

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import su.plo.slib.command.brigadier.proxied
4545
import su.plo.slib.command.brigadier.toMcTextComponent
4646
import su.plo.slib.minestom.command.brigadier.MinestomArgumentType
4747
import su.plo.slib.minestom.command.brigadier.MinestomBrigadierSource
48+
import su.plo.slib.minestom.command.brigadier.withParsingSender
4849

4950
class MinestomCommandManager(
5051
private val minecraftServer: McServerLib
@@ -163,18 +164,27 @@ class MinestomCommandManager(
163164
private fun <T> ArgumentCommandNode<McBrigadierSource, T>.toMinestom(): Pair<Argument<T>, CommandExecutor?> {
164165
val argumentType = type
165166
val executor = command?.toMinestom()
167+
val customNode = this as? CustomArgumentCommandNode<*, *, *>
166168

167-
if (argumentType is MinestomArgumentType<T>) {
169+
if (customNode == null && argumentType is MinestomArgumentType<T>) {
168170
return argumentType.argumentBuilder.invoke(name) to executor
169171
}
170172

171-
val minestomArgument = object : Argument<T>(name) {
173+
val nativeArgument = (argumentType as? MinestomArgumentType<*>)?.argumentBuilder?.invoke(name)
174+
175+
val minestomArgument = object : Argument<T>(
176+
name,
177+
nativeArgument?.allowSpace() ?: false,
178+
nativeArgument?.useRemaining() ?: false,
179+
) {
172180
@Suppress("UNCHECKED_CAST")
173181
override fun parse(sender: CommandSender, input: String): T {
174182
pendingParseError.remove()
175183
return try {
176-
if (this@toMinestom is CustomArgumentCommandNode<*, *, *>) {
177-
(customArgumentType.parse(StringReader(input)) as T)
184+
if (customNode != null) {
185+
withParsingSender(sender) {
186+
customNode.customArgumentType.parse(StringReader(input)) as T
187+
}
178188
} else {
179189
argumentType.parse(StringReader(input))
180190
}
@@ -185,12 +195,13 @@ class MinestomCommandManager(
185195
}
186196

187197
override fun parser(): ArgumentParserType =
188-
argumentType.toMinestomParserType()
198+
nativeArgument?.parser() ?: argumentType.toMinestomParserType()
189199

190200
override fun nodeProperties(): ByteArray? {
191-
val effectiveType = (argumentType as? CustomArgumentType<*, *>)?.nativeType ?: argumentType
192-
if (effectiveType is StringArgumentType) {
193-
return NetworkBuffer.makeArray(NetworkBuffer.VAR_INT, effectiveType.type.ordinal)
201+
nativeArgument?.let { return it.nodeProperties() }
202+
203+
if (argumentType is StringArgumentType) {
204+
return NetworkBuffer.makeArray(NetworkBuffer.VAR_INT, argumentType.type.ordinal)
194205
}
195206

196207
return super.nodeProperties()
Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,49 @@
11
package su.plo.slib.minestom.command.brigadier
22

3+
import com.mojang.brigadier.LiteralMessage
34
import com.mojang.brigadier.StringReader
45
import com.mojang.brigadier.arguments.ArgumentType
6+
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType
7+
import net.minestom.server.command.CommandSender
58
import net.minestom.server.command.builder.arguments.Argument
9+
import net.minestom.server.command.builder.exception.ArgumentSyntaxException
10+
11+
private val parsingSender = ThreadLocal<CommandSender>()
12+
13+
internal fun <T> withParsingSender(sender: CommandSender, block: () -> T): T {
14+
val previous = parsingSender.get()
15+
parsingSender.set(sender)
16+
17+
try {
18+
return block()
19+
} finally {
20+
if (previous == null) parsingSender.remove() else parsingSender.set(previous)
21+
}
22+
}
623

724
data class MinestomArgumentType<S>(
825
val argumentBuilder: (String) -> Argument<S>,
926
) : ArgumentType<S> {
27+
private val parsingArgument by lazy { argumentBuilder("slib_parsing") }
28+
1029
override fun parse(reader: StringReader): S {
11-
throw UnsupportedOperationException()
30+
val sender = parsingSender.get()
31+
?: throw UnsupportedOperationException(
32+
"${this::class.java.name} can only be parsed while a command is being parsed"
33+
)
34+
35+
val input = reader.remaining
36+
37+
val parsed =
38+
try {
39+
parsingArgument.parse(sender, input)
40+
} catch (e: ArgumentSyntaxException) {
41+
throw SimpleCommandExceptionType(LiteralMessage(e.message))
42+
.createWithContext(reader)
43+
}
44+
45+
reader.cursor = reader.totalLength
46+
47+
return parsed
1248
}
1349
}

‎paper/src/main/kotlin/su/plo/slib/paper/command/brigadier/PaperBrigadierCommands.kt‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import su.plo.slib.command.brigadier.collectBrigadierCommands
2020
import su.plo.slib.command.brigadier.copyFor
2121
import su.plo.slib.command.brigadier.localizedFor
2222
import su.plo.slib.command.brigadier.proxied
23+
import su.plo.slib.command.brigadier.unwrapNativeType
2324
import su.plo.slib.paper.PaperServerLib
2425
import su.plo.slib.paper.command.PaperUnboundCommandSource
2526
import java.lang.ref.WeakReference
@@ -70,9 +71,6 @@ internal fun ArgumentType<*>.toPaperArgumentType(): ArgumentType<*>? =
7071
if (this is CustomArgumentType<*, *>) PaperArgumentTypeAdapter(this as CustomArgumentType<Any, Any>)
7172
else null
7273

73-
private tailrec fun ArgumentType<*>.unwrapNativeType(): ArgumentType<*> =
74-
if (this is CustomArgumentType<*, *>) nativeType.unwrapNativeType() else this
75-
7674
internal class PaperArgumentTypeAdapter<PARSED : Any, NATIVE : Any>(
7775
private val delegate: CustomArgumentType<PARSED, NATIVE>,
7876
) : PaperCustomArgumentType<PARSED, NATIVE> {

‎scripts/smoke-test.sh‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ case "$ENV_TYPE" in
3232
"Command 'brigadier-position-selector' registered"
3333
"Command 'brigadier-game-profiles-selector' registered"
3434
"Command 'brigadier-custom-type' registered"
35+
"Command 'brigadier-nested-custom-type' registered"
3536
"Command 'brigadier-server-translation' registered"
3637
"Command 'brigadier-multi-arg' registered"
3738
"Message from main thread"
@@ -43,6 +44,8 @@ case "$ENV_TYPE" in
4344
)
4445
COMMAND_INPUTS=(
4546
"brigadier-custom-type invalid-uuid"
47+
"brigadier-nested-custom-type everyone"
48+
"brigadier-nested-custom-type @a"
4649
"brigadier-server-translation bad"
4750
"brigadier-server-translation ok"
4851
"brigadier-entity-selector entities @e"
@@ -54,6 +57,8 @@ case "$ENV_TYPE" in
5457
)
5558
COMMAND_OUTPUT_PATTERNS=(
5659
"Invalid UUID"
60+
"Nested custom type: everyone"
61+
"Nested custom type: native selector"
5762
"slib argument error: bad"
5863
"slib execute error: ok"
5964
"Found entities:"

0 commit comments

Comments
 (0)