Skip to content

2.1. Command DSL

dyamo edited this page Oct 16, 2022 · 2 revisions

In this section you will learn how to make Brigadier commands using LirandAPI. You can see all the code here.

Command structure

The way commands are built: you create a root node that defines command name, then you add child notes to define command structure.

plugin.command("mycommand") { // root node
	
}

Node structure

You can add execution logic to the node. Execution builder functions pass BrigadierCommandContext to their lambda parameters when calling. This allows you to get some information about command execution such as execution source and passed arguments (getArgument).

plugin.command("mycommand") {
    executesPlayer { // this: BrigadierCommandContext<Player>
        source.sendMessage("Executed by player")
    }
    executesConsole { // this: BrigadierCommandContext<ConsoleCommandSender>
        source.sendMessage("Executed by console")
    }
    executes { // this: BrigadierCommandContext<CommandSender>
        source.sendMessage("Executed by player or console")
    }
}

Context has very useful extension function called fail. It allows you to break command execution and send some message to user.

plugin.command("mycommand") {
    executes {
        if (server.mainWorld.isClearWeather()) {
            fail("I don't wanna work at rain!")
        }
    }
}

You can also set some requirements for running the node:

plugin.command("mycommand") { 
    requiresPermissions("myplugin.perm1", "myplugin.perm2")
    requires { sender -> sender is Player && sender.name.startsWith("a") }
    
    executesPlayer {
        source.sendMessage("Executed only if requirements are met")
    }
}

Nodes hierarchy

In the cases when you need to construct more complex command you can create node hierarchy. Each node can have children that define next parameters in the command. You can add child node by calling relevant node builder inside the parent node builder.

Each previous parameter must be successfully parsed to let current parameter's logic work.

There are 2 types of parameters: literals and arguments.

Literals

Literals are parameters of the command that require provided literal string at the relevant node position to be successfully parsed. Node can have multiple child literal nodes.

plugin.command("time") { 
    literal("set") {
        // ...
    }
    literal("add") {
        // ...
    }
    literal("query") {
        alias("get") // time query... and time get... are equivalent
        // ...
    }
}

In this example there are 3 parallel literals, only one of which may be parsed.

Arguments

Arguments are parameters of the command that require string that meets requirements of the provided ArgumentType's parser and is at the relevant node position. Node can have only one child argument node.

We will talk about argument types a little later, but for now you need to know that they provide you a parser of the string parameter and suggestions for their input. The type is necessary for argument declaration.

plugin.command("time") { 
    literal("set") {
        argument("time", IntType(min = 0)) { timeArgument -> // ArgumentDefinition<Int>
            executes {
                server.mainWorld.setFullTime(timeArgument.get()) // Int
            }
        }
    }
}

Argument node builder provides you ArgumentDefinition that has generic type of the parcelable parameter. It lets you get already parsed argument value inside and only inside the BrigadierCommandContext.

So working with arguments is completely type-safe.

Optional arguments

You can omit some parameters of the command if their nodes are part of optional argument chain. Chains in Lirand API define some kind of relationship between nodes. Optional argument chain gives you ability to create omitable prefixed argument nodes with the same execution logic.

plugin.command("fill") {
    argument("block", MaterialType) { blockArgument -> // ArgumentDefinition<Material>
        executesPlayer {
            fill(blockArgument.get()) // Material
        }
        
        optionalArguments("modifier") {
            val facingArgument = option("facing" to EnumType.build<BlockFace>()) // OptionalArgumentDefinition<BlockFace>
            val randomArgument = option("random" to DoubleType(0, 100)) // OptionalArgumentDefinition<Double>
            
            executesPlayer {
                fill(blockArgument.get(), facingArgument.get(), randomArgument.get()) // Material BlockFace? Double?
            }
        }
    }
}

Each option provides you OptionalArgumentDefinition that has generic type of the parcelable parameter. It lets you get already parsed argument value if it's defined or null if it's not.

Note that optional argument chain can not have child nodes.

Clone this wiki locally