Using the DSL

Defining a simple message command

As a first example and to take a first look at the Kotlin DSL syntax, we will first create a simple command to send messages to a player.

Example - Sending a message to a player using the Kotlin DSL

We want to create a command that lets us send a message to a player. To do this, we want to register a command with the following syntax:

/sendmessageto <player> <msg>

We can then use the following command registration:

commandTree("sendmessageto") {
    playerArgument("player") { // Defines a new PlayerArgument("player")
        greedyStringArgument("msg") { // Defines a new GreedyStringArgument("msg")
            anyExecutor { _, args -> // Command can be executed by anyone and anything (such as entities, the console, etc.)
                val player: Player = args["player"] as Player
                val message: String = args["msg"] as String
                player.sendMessage(message)
            }
        }
    }
}
commandAPICommand("sendmessageto") {
    playerArgument("player") // Defines a new PlayerArgument("player")
    greedyStringArgument("msg") // Defines a new GreedyStringArgument("msg")
    anyExecutor { _, args -> // Command can be executed by anyone and anything (such as entities, the console, etc.)
        val player: Player = args["player"] as Player
        val message: String = args["msg"] as String
        player.sendMessage(message)
    }
}

Here you can see some interesting things:

  • You do not need to call the .register() method when using the DSL
  • You do not need to initialise any arguments

Executors

The Kotlin DSL also provides executors to execute your command. You've seen the anyExecutor in the example above.

To find out, which DSL executor corresponds to "normal" executors, you can refer to the table below:

DSL Executor"normal" Executor
anyExecutor()executes()
playerExecutor()executesPlayer()
entityExecutor()executesEntity()
consoleExecutor()executesConsole()
commandBlockExecutor()executesCommandBlock()
proxyExecutor()executesProxy()
nativeExecutor()executesNative()

Arguments

The DSL implements almost every argument with a method. You've seen the playerArgument() and the greedyStringArgument() method in the example at the top of this page.

The way arguments are implemented is pretty straight forward: It's basically the argument class' name, but as a method. So if you wanted to use a ItemStackArgument in your command, you would use the itemStackArgument() method of the DSL.

One thing to note is that the DSL also features every existing constructor. This means if you want to use an IntegerArgument with a minimum of 0 and a maximum of 10, you normally would implement it like this:

new IntegerArgument("integer", 0, 10)

However, when using this DSL it is implemented like this:

integerArgument("integer", 0, 10)

Developer's Note:

There are a few arguments not having a method which directly corresponds to their respective argument.

These arguments most likely use a builder pattern and because of that require further implementation by the user.

To use these arguments, the DSL also provides the argument() method which takes in any argument as a parameter.


Editing arguments

When using the DSL, you might want to modify the behaviour of certain arguments by adding requirements or suggestions to them.

To give you a general idea how you could accomplish that, the sendMessageTo command is adding a broadcast option which should only be executed by server operators.

commandTree("sendMessageTo") {
    playerArgument("player") {
        greedyStringArgument("msg") {
            playerExecutor { _, args ->
                val player: Player = args["player"] as Player
                val message: String = args["msg"] as String
                player.sendMessage(message)
            }
        }
    }
    literalArgument("broadcast") {
        withRequirement { sender: CommandSender -> sender.isOp } // Applies the requirement to the broadcast literal argument
        /* add more methods here that modify argument behaviour */
        greedyStringArgument("msg") {
            playerExecutor { _, args ->
                val message: String = args["msg"] as String
                Bukkit.broadcastMessage(message)
            }
        }
    }
}
commandAPICommand("sendMessageTo") {
    playerArgument("player")
    greedyStringArgument("msg")
    playerExecutor { _, args ->
        val player: Player = args["player"] as Player
        val message: String = args["msg"] as String
        player.sendMessage(message)
    }
}

commandAPICommand("sendMessageTo") {
    literalArgument("broadcast") {
        withRequirement { sender: CommandSender -> sender.isOp } // Applies the requirement to the broadcast literal argument
        /* add more methods here that modify argument behaviour */
    }
    greedyStringArgument("msg")
    playerExecutor { _, args ->
        val message: String = args["msg"] as String
        Bukkit.broadcastMessage(message)
    }
}

Notice how you can just add the requirement in a CommandTree by adding it to the argument block where you also define the next arguments and the executor.

However, when modifying the behaviour of an argument in a CommandAPICommand you have to add an extra block where you can implement the additional behaviour.

Adding requirements to commands

Expanding on the previous example where we added a requirement to a single argument, we now also want to add a requirement to a whole command.

This works similar to how argument behaviour is modified in a CommandTree:

commandTree("commandRequirement") {
    withRequirement { sender: CommandSender -> sender.isOp}
    playerExecutor { player, _ ->
        player.sendMessage("This command can only be executed by players who are server operators.")
    }
}
commandAPICommand("commandRequirement") {
    withRequirement { sender: CommandSender -> sender.isOp}
    playerExecutor { player, _ ->
        player.sendMessage("This command can only be executed by players who are server operators.")
    }
}

More examples

Now, a few more examples are shown to demonstrate the use of this DSL a little more:

Example - Implementing optional arguments with the Kotlin DSL

We want to create a /give command with the following syntax:

/optionalArgument give <item>
/optionalArgument give <item> <amount>

To declare an argument as optional you need to set the optional value to true:

commandTree("optionalArgument") {
    literalArgument("give") {
        itemStackArgument("item") {
            integerArgument("amount", optional = true) {
                playerExecutor { player, args ->
                    // This command will let you execute:
                    // "/optionalArgument give minecraft:stick"
                    // "/optionalArgument give minecraft:stick 5"
                    val itemStack: ItemStack = args["item"] as ItemStack
                    val amount: Int = args.getOptional("amount").orElse(1) as Int
                    itemStack.amount = amount
                    player.inventory.addItem(itemStack)
                }
            }
        }
    }
}
commandAPICommand("optionalArgument") {
    literalArgument("give")
    itemStackArgument("item")
    integerArgument("amount", optional = true) // This sets the argument as optional
    playerExecutor { player, args ->
        // This command will let you execute:
        // "/optionalArgument give minecraft:stick"
        // "/optionalArgument give minecraft:stick 5"
        val itemStack: ItemStack = args["item"] as ItemStack
        val amount: Int = args.getOptional("amount").orElse(1) as Int
        itemStack.amount = amount
        player.inventory.addItem(itemStack)
    }
}

Example - Replacing suggestions using the Kotlin DSL

We want to create a command with the following syntax to demonstrate replacing suggestions using the Kotlin DSL:

/replaceSuggestions <strings>

Replacing suggestions works similar to how you would add a requirement to an argument as shown in Editing arguments.

You just have to use the replaceSuggestions method this time:

commandTree("replaceSuggestions") {
    stringArgument("strings") {
        replaceSuggestions(ArgumentSuggestions.strings("one", "two", "three")) // Replaces the suggestions for the "strings" StringArgument
        playerExecutor { player, args ->
            player.sendMessage("You chose option ${args["strings"] as String}!")
        }
    }
}
commandAPICommand("replaceSuggestions") {
    stringArgument("strings") {
        replaceSuggestions(ArgumentSuggestions.strings("one", "two", "three")) // Replaces the suggestions for the "strings" StringArgument
    }
    playerExecutor { player, args ->
        player.sendMessage("You chose option ${args["strings"] as String}!")
    }
}