Skip to content
Jesse McKee edited this page May 12, 2024 · 5 revisions

Scripts have a way to write commands easily

import me.zodd.*
import net.kyori.adventure.text.Component
import org.spongepowered.api.command.parameter.CommonParameters

ScriptCommandManager {
    val playerParam = CommonParameters.PLAYER
    val reasonParam = CommonParameters.MESSAGE

    //This method creates a command named "kick" -- /kick
    command("kick") {
        //Permissions are optional, but notably the name of the command is provided in context -- $it
        permission = "plugin.command.$it" //plugin.command.kick
        //Aliases for the command, run it with /boot or /zap instead
        aliases += listOf("boot", "zap")
        
        //Parameters are added with the `+` sign.
        +playerParam
        +reasonParam
        //Commands will run the contents of this block during execution.
        executes {
            val reason = Component.text(requireOne(reasonParam))
            requireOne(playerParam).kick(reason)
            //Commands require a CommandResult
            //success() or error(Component)
            success()
        }
    }

    //You can write multiple commands
    command("...") {
        executes {
            success()
        }
    }
}

The command DSL is more or less a wrapper around Sponge's command builder.

There are some significant changes that make creating commands much simpler!

First, there are two ways to write commands.

If you only need to make a single command, you can create it this way.

ScriptCommandManager.command("simple") {}

For multiple commands and better control over scope you can use a closure.

ScriptCommandManager {
    command("dynamic") {}
    command("command2") {}
}

Now in order to create a full command, you'll need an executes block.

ScriptCommandManager {
    command("foo") {
        /*
        This is the simplest command possible, but it doesn't do anything
        other than prevent an error message.
         */
        executes {
            /*
            * Commands are required to return a CommandResult
            * the api provides two convenience methods
            * success() and error(Component)
             */
            success()
        }
    }
}

Your first real command

In this section we will go over how commands are constructed in the DSL syntax. It's important to note that much of this api wraps around Sponge's CommandBuilder. This brings us the major advantage of having command completion support for your commands!

Now because we wrap Sponge's command API, we do also copy a lot of the methods, but they are constructed in a more script-like syntax. With a notable exception being subcommands.

Our first command will have no additional args. So this example will not explain how to add them! Keeping in mind the above example was the bare minimum for a valid command, so everything else seen can be considered optional.

ScriptCommandManager {
    command("bar") {
        //We can add as many aliases as we like
        aliases += "bbar"
        aliases += "cbar"
        //You can even use a list if you provide many
        aliases += listOf("dbar", "ebar", "fbar")

        //Registering a permission is simple
        //Command closures also provide the command name to context : $it = "bar"
        permission = "plugin.command.$it"

        //For advanced requirements you may consider using an executionRequirement
        //Predicate<CommandCause>
        executionRequirement = Predicate { true }

        //Provide a description!
        description = "This command has an example for how it works"

        executes {
            //Anything wanted to be run during the command happens here!
            //You have full access to CommandContext from SpongeAPI
            sendMessage(Identity.nil(), Component.text("Bar has been executed"))
            success()
        }
    }
}

Command arguments

Alright we've covered most of what makes up a command. Let's get into the good stuff now!

Since we know how commands work this guide will omit the command manager from here on out! Additionally, I will be using a method #sendMessageToCause that I will provide at the end of this guide. It's just a wrapper to handle strings and send a message.

Sponge's list of Common Parameters can be found here

This command will have the syntax /razz <true/false>

command("razz") {
    //Commands also need to take argument sometimes!
    //We can do that by using a UnaryPlus "+"
    //This command will take a boolean
    +CommonParameters.BOOLEAN
    executes {
        //Like regular sponge commands, you need to handle parameters
        val param = requireOne(CommonParameters.BOOLEAN)
        sendMessageToCause("Razz command selected : $param")
        success()
    }
}

Custom Parameters and Flags

It's also important to be able to add your own parameters of course! There's a few ways to add your own parameters and even flags!

SpongeAPI Parameter Reference

command("rizz") {
    //Simple param -- The string is the key
    val customParam = "secret" withType Parameter.string()
    //Simple flag -- This will build a flag with `-f`
    val customFlag = "f" buildFlag "plugin.flag.force"

        //Here we use SpongeAPI directly to build a parameter
        val optionalParam = Parameter.world().key("world").optional().build()
        val anotherFlag = ("s" asFlag "plugin.flag.s").aliases("sour", "crank").build()
        executes {
            val param = one(optionalParam)
            if (!param.isPresent) {
                sendMessageToCause("No world!")
            } else {
                sendMessageToCause("World! ${param.get().key().formatted()}")
            }
            if (hasFlag(customFlag) && hasFlag(anotherFlag)) {
                sendMessageToCause("Double flag mode!")
            } else {
                sendMessageToCause("No flags!")
            }
            success()
        }

    //Remember to use the "+"!
    +customParam
    +customFlag
    executes {
        val param = requireOne(customParam)
        if (hasFlag(customFlag)) {
            sendMessageToCause("You've forced our hand! -- Now you'll get it: ", param)
        } else {
            Logger.info("$param wasn't sent!")
            sendMessageToCause("We weren't forced into this, nothing to report!")
        }
        success()
    }
}

Subcommands

Finally, what would a command be without subcommands?

Now despite what the guide suggests previously the executes block actually isn't necessary! You may, however find that you want to handle your command errors instead!

Otherwise, you'll receive errors such as Unknown or incomplete command, see below for error at position ... if the command is run.

command("baz") {
    //Commands may have subcommands too!
    subcommand("buzz") {
        //So can sub-commands
        subcommand("braz") {
            //sub-sub-sub-commands!?
            subcommand("broz") {
                executes {
                    sendMessageToCause("Listen we could do this all day...")
                    success()
                }
            }
            executes {
                sendMessageToCause("Sub-sub-commands!")
                success()
            }
        }
        //Commented out here because it's not strictly necessary!
        /*executes {
            sendMessageToCause("Buzz subcommand")
            success()
        }*/
    }
    executes {
        sendMessageToCause("Baz Command")
        success()
    }
}

Message sender method used in examples

This isn't necessary for your scripts. This is provided only to explain examples above.

Additional examples, as well as a script with most of the examples above can be found here

fun CommandContext.sendMessageToCause(vararg msg: String) {
    val firstToken = msg.first()
    val remainingTokens = msg.filterNot { it.contentEquals(firstToken) }
    var finalMessage: TextComponent = Component.text(firstToken)
    remainingTokens.forEach { finalMessage = finalMessage.append(Component.text(it)) }
    cause().sendMessage(Identity.nil(), finalMessage)
}