Making Your Own Command¶
Making your own commands requires you to modify the engine's source code. And since I'm kind enough to open source it, you can download the source code from the GitHub repo, clone it, and sync it to Roblox Studio using Rojo.
Asymptote Engine's command parser is based on Mojang's Brigadier command parser, which they used in Minecraft Java.
Command Tree¶
Every part of a command is represented as a node, and the "tree" is formed by how these nodes are linked together.
Components¶
The main node types of every tree are: * Root Node. The starting point of any command, it does not contain logic but serves as the parent for all registered commands.
-
Literal Nodes. These are fixed strings of a command, such as
killandteleport. They are used for command names and sub-commands. All commands must start with a literal node for the command's name. -
Argument Nodes. This is where the users can input data, such as booleans
trueandfalse, numbers, strings, entity selectors, and many more to turn texts to data during parsing.
Take the /kill command for example:
dispatcher:register(
CommandHelper.literal("kill")
:executes(function(c)
return KillCommand.kill(c, {c:getSource():getPlayerOrThrow()})
end)
:andThen(
CommandHelper.argument("victims", EntityArgument.entities())
:executes(function(c)
local targets = EntityArgument.getEntities(c, "victims")
return KillCommand.kill(c, targets)
end)
)
)
And here's a representation of that tree:
This allows for deep nesting commands, sub-commands, and arguments.
Programming Your First Command¶
All server-side logic for commands are located in src/server/commands. While the command parser itself is located in src/shared/commands.
To create your first command, let's make a simple /greet command. First make a new file, something like GreetCommand.lua, and place somewhere you won't lose it. Inside the file,
--!strict
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerScriptService = game:GetService("ServerScriptService")
local CommandHelper = require(ServerScriptService.server.commands.registry.CommandHelper)
local CommandSourceStack = require(ServerScriptService.server.commands.source.CommandSourceStack)
local CommandDispatcher = require(ReplicatedStorage.shared.commands.CommandDispatcher)
local EntityArgument = require(ReplicatedStorage.shared.commands.arguments.asymptote.EntityArgument)
local CommandContext = require(ReplicatedStorage.shared.commands.context.CommandContext)
local MutableTextComponent = require(ReplicatedStorage.shared.network.chat.MutableTextComponent)
local NamedTextColors = require(ReplicatedStorage.shared.network.chat.NamedTextColors)
local TextStyle = require(ReplicatedStorage.shared.network.chat.TextStyle)
local GreetCommand = {}
function GreetCommand.register(dispatcher: CommandDispatcher.CommandDispatcher<CommandSourceStack.CommandSourceStack>): ()
dispatcher:register(
CommandHelper.literal("greet")
-- Path 1: /greet
:executes(function(c)
local sender = c:getSource():getPlayerOrThrow()
return GreetCommand.greet(c, {sender})
end)
-- Path 2: /greet <target>
:andThen(
CommandHelper.argument("target", EntityArgument.entities())
:executes(function(c)
local targets = EntityArgument.getEntities(c, "target")
return GreetCommand.greet(c, targets)
end)
)
)
end
function GreetCommand.greet(c: CommandContext.CommandContext<CommandSourceStack.CommandSourceStack>, targets: {Player}): number
local sender = c:getSource():getPlayerOrThrow()
local senderName = sender and sender.DisplayName or "System"
for _, player in targets do
local message = MutableTextComponent.literal("Hello ")
:appendComponent(
MutableTextComponent.literal(player.DisplayName)
:withStyle(TextStyle.empty():withColor(NamedTextColors.YELLOW):withBold(true))
)
:appendString(", ")
:appendComponent(
MutableTextComponent.literal(senderName)
:withStyle(TextStyle.empty():withColor(NamedTextColors.DARK_AQUA):withItalic(true))
)
:appendString(" says hi!")
c:getSource():sendSuccess(message)
end
return #targets
end
return GreetCommand