Skip to content

Commit

Permalink
Write command documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
senseiwells committed Nov 13, 2024
1 parent 344afeb commit d314d52
Show file tree
Hide file tree
Showing 4 changed files with 257 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -1,17 +1,35 @@
package net.casual.arcade.commands.type

import com.mojang.brigadier.StringReader
import com.mojang.brigadier.arguments.ArgumentType
import com.mojang.brigadier.arguments.StringArgumentType.StringType.QUOTABLE_PHRASE
import com.mojang.brigadier.context.CommandContext
import com.mojang.brigadier.suggestion.SuggestionProvider
import com.mojang.brigadier.suggestion.Suggestions
import com.mojang.brigadier.suggestion.SuggestionsBuilder
import net.casual.arcade.commands.mixins.ArgumentTypeInfosAccessor
import net.minecraft.commands.SharedSuggestionProvider
import net.minecraft.commands.synchronization.SuggestionProviders
import java.util.concurrent.CompletableFuture

public abstract class CustomArgumentType<T>: ArgumentType<T> {
init {
CLASS_MAP.computeIfAbsent(this::class.java) { this.getArgumentInfo() }
}

abstract override fun parse(reader: StringReader): T

override fun <S> parse(reader: StringReader, source: S): T {
return super.parse(reader, source)
}

override fun <S> listSuggestions(
context: CommandContext<S>,
builder: SuggestionsBuilder
): CompletableFuture<Suggestions> {
return super.listSuggestions(context, builder)
}

public fun getSuggestionProvider(): SuggestionProvider<SharedSuggestionProvider>? {
return SuggestionProviders.ASK_SERVER
}
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ afterEvaluate {
updateDocumentedDependencies("./README.md", false)
}

val testmod by sourceSets.creating {
val testmod: SourceSet by sourceSets.creating {
compileClasspath += sourceSets.main.get().compileClasspath
compileClasspath += sourceSets.main.get().output
runtimeClasspath += sourceSets.main.get().runtimeClasspath
Expand Down
192 changes: 192 additions & 0 deletions docs/arcade-commands/usage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
# Usage

> Return to [table of contents](getting-started.md)
## Creating Commands

The `CommandUtils` and `CommandTree` objects provide utilities for creating
much more readable command trees in kotlin.

We start by calling `CommandTree.buildLiteral` or `CommandTree.createLiteral`
which return a `LiteralArgumentBuilder` or `LiteralCommandNode` respectively.
This method takes a lambda of `LiteralArgumentBuilder<S>.() -> Unit` which
allows us to put everything related to this tree inside the lambda.

```kotlin
fun createExampleCommand(): LiteralArgumentBuilder<CommandSourceStack> {
return CommandTree.buildLiteral("example") {

}
}
```

We can add suggestions, requirements, literal subcommands and add arguments:
```kotlin
fun createExampleCommand(): LiteralArgumentBuilder<CommandSourceStack> {
return CommandTree.buildLiteral("example") {
requiresPermission(2)
argument("argument", StringArgumentType.string()) {
requires { it.level.dimension() == Level.OVERWORLD }
executes { /* Command logic */ }
}

literal("subcommand") {
argument("subcommand-argument", StringArgumentType.string()) {
suggests { _, b -> SharedSuggestionProvider.suggest(listOf("a", "b", "c"), b) }
executes { /* Command logic */ }
}
}
literal("other-subcommand") {
// ...
}
}
}
```
Other than the structuring of the command tree, everything else is the same as
brigadier.

## Custom Argument Types

Implementing custom argument types yourself is a pain as they usually have
to be synchronized with the client. Arcade works around this with lots of
trickery and allows you to easily implement your own argument types as
well as implementing some basic ones.

By default, Arcade implements `EnumArgument`, `MappedArgument`,
`TimeArgument`, and `TimeZoneArgument`.

`EnumArgument`s can be created with an Enum `Class` and it will
allow you to use the enum instances as arguments. Similarly,
`MappedArgument` lets you pass in a `Map<String, ?>` to specify the valid
options for your argument:

```kotlin
enum class MyEnum {
Foo, Bar
}

fun createExampleCommand(): LiteralArgumentBuilder<CommandSourceStack> {
return CommandTree.buildLiteral("example") {
/* /example Foo, or /example Bar */
argument("enum", EnumArgument.enumeration<MyEnum>()) {
executes {
val myEnum = EnumArgument.getEnumeration<MyEnum>(it, "enum")
Command.SINGLE_SUCCESS
}
}

/* /example one, or /example two */
val options = mapOf("one" to 1, "two" to 2)
argument("mapped", MappedArgument.mapped(options)) {
executes {
val option = MappedArgument.getMapped<Int>(it, "mapped")
Command.SINGLE_SUCCESS
}
}
}
}
```

The other two built-in argument types are self-explanatory.

### Implementing Your Own Argument Type

To implement your own `ArgumentType` you must extend the `CustomArgumentType<T>`
class. The `T` type is the type of your argument type, what you will be parsing into.

```kotlin
class ExampleArgumentType: CustomArgumentType<String>() {
override fun parse(reader: StringReader): String {
TODO("Not yet implemented")
}

override fun <S> listSuggestions(
context: CommandContext<S>,
builder: SuggestionsBuilder
): CompletableFuture<Suggestions> {
return super.listSuggestions(context, builder)
}

override fun getArgumentInfo(): CustomArgumentTypeInfo<*> {
return super.getArgumentInfo()
}
}
```

Here you can implement your `parse` method as you would if you were implementing
a vanilla argument type, and you can also provide suggestions.

By default, all `CustomArgumentTypes` are just disguising themselves as
`StringArgumentType.string()` - a quotable phrase. However, you can change
which argument type your custom argument type is shadowing:

```kotlin
class ExampleArgumentType: CustomArgumentType<String>() {
// ...

override fun getArgumentInfo(): CustomArgumentTypeInfo<*> {
// We're now pretending to be a ResourceLocationArgument
return CustomArgumentTypeInfo.of(ResourceLocationArgument::class.java)
}
}
```

For more complex argument types (that require arguments), such as `StringArgumentType`
you will need to implement `CustomArgumentTypeInfo` yourself.

## Hidden Commands

Hidden commands are an invaluable tool, especially in combination with
`Component`s as they allow you to register temporary commands with any
callback for a player that runs that 'command'.

You can register a hidden command using the `HiddenCommandManager`:
```kotlin
fun registerMyHiddenCommand() {
val command: String = HiddenCommandManager.register(
timeout = 10.Seconds,
command = { context ->
println("${context.player.scoreboardName} ran my hidden command!")
}
)
}
```
This returns a string containing the command which can be run, the
format of which is undefined.

This is most useful as previously mentioned with `Components` as we can
make functions that get run whenever a player clicks on a chat message.
We do this by using the `function` extension function on `MutableComponent`s.

```kotlin
fun sendMyHiddenCommand(player: ServerPlayer) {
player.sendSystemMessage(
Component.literal("[CLICK HERE]").function {
println("Player ${it.player.scoreboardName} clicked the message!")
}
)
}
```

We can also instead use `singleUseFunction` to denote that the function may
only be called once:
```kotlin
fun sendMyHiddenCommand(player: ServerPlayer) {
player.sendSystemMessage(
Component.literal("[CLICK HERE]").singleUseFunction {
println("Player ${it.player.scoreboardName} clicked the message!")
}
)
}
```
Once the player has clicked the message, it will no longer function on later clicks.
If we send the same component to multiple players, it will still limit it to
the first player who clicked the message.

> [!IMPORTANT]
> Even if you send a component with a function to only a specific player, it is
> theoretically possible that another player may run the command given they know
> what it is.
>
> Although unlikely, if your application requires only the player that was sent
> the message to be able to click it, you ***should*** add a check.
46 changes: 46 additions & 0 deletions src/testmod/kotlin/net/casual/arcade/ExampleMinigame.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
package net.casual.arcade

import com.mojang.brigadier.Command
import com.mojang.brigadier.StringReader
import com.mojang.brigadier.arguments.ArgumentType
import com.mojang.brigadier.arguments.StringArgumentType
import com.mojang.brigadier.builder.LiteralArgumentBuilder
import com.mojang.brigadier.context.CommandContext
import com.mojang.brigadier.suggestion.Suggestions
import com.mojang.brigadier.suggestion.SuggestionsBuilder
import com.mojang.serialization.MapCodec
import eu.pb4.sgui.api.gui.GuiInterface
import net.casual.arcade.commands.*
import net.casual.arcade.commands.arguments.EnumArgument
import net.casual.arcade.commands.arguments.MappedArgument
import net.casual.arcade.commands.hidden.HiddenCommand
import net.casual.arcade.commands.hidden.HiddenCommandManager
import net.casual.arcade.commands.type.CustomArgumentType
import net.casual.arcade.commands.type.CustomArgumentTypeInfo
import net.casual.arcade.dimensions.level.builder.CustomLevelBuilder
import net.casual.arcade.dimensions.level.vanilla.VanillaDimension
import net.casual.arcade.dimensions.utils.addCustomLevel
Expand All @@ -28,14 +43,19 @@ import net.casual.arcade.utils.teleportTo
import net.casual.arcade.visuals.countdown.TitledCountdown
import net.casual.arcade.visuals.screen.SelectionGuiBuilder
import net.fabricmc.api.ModInitializer
import net.minecraft.commands.CommandSourceStack
import net.minecraft.commands.Commands
import net.minecraft.commands.SharedSuggestionProvider
import net.minecraft.core.Registry
import net.minecraft.data.worldgen.DimensionTypes
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.item.Items
import net.minecraft.world.level.Level
import java.util.*
import java.util.concurrent.CompletableFuture

enum class ExamplePhases(
override val id: String
Expand Down Expand Up @@ -148,4 +168,30 @@ object ExampleMinigameMod: ModInitializer {
ExampleMinigameFactory.codec()
)
}
}


class ExampleArgumentType: CustomArgumentType<String>() {
override fun parse(reader: StringReader): String {
TODO("Not yet implemented")
}

override fun <S> listSuggestions(
context: CommandContext<S>,
builder: SuggestionsBuilder
): CompletableFuture<Suggestions> {
return super.listSuggestions(context, builder)
}

override fun getArgumentInfo(): CustomArgumentTypeInfo<*> {
return super.getArgumentInfo()
}
}

fun sendMyHiddenCommand(player: ServerPlayer) {
player.sendSystemMessage(
Component.literal("[CLICK HERE]").singleUseFunction {
println("Player ${it.player.scoreboardName} clicked the message!")
}
)
}

0 comments on commit d314d52

Please sign in to comment.