Table of Contents

How to: Create Basic Commands with Scripts

In this article, you will learn how to create your own commands. Commands may seem insignificant but they can greatly improving your, and your player's experience, for players, they provide quick access to game features, be it changing settings, spawning items, or teleporting to locations. And for developers they are especially useful when it comes to debugging, whether you need to test a new feature, change an in-game variable, or switch to another game state.

In this article, it will be covered: how to create three essential commands using the game scripts. the ones that we'll be making are

But when you complete this tutorial, you will know how to create your own custom commands to make your game better.

Making the system

Setup

We create the following variables, which we'll need after.

  1. commandprefix (The prefix that a message should have in order to be considered a command)
  2. commandlist (A list where we will store all our commands)

For this tutorial, we will also use two functions in our commands. One that converts a player name to uid, and the other that checks if a number is invalid (Infinity, NaN, or nil).

nametouid:

function nametouid(name) 
    local _,_, playerlist = World:getAllPlayers(-1)
 
    for _,v in ipairs(playerlist) do 
        local _, curplayernick = Player:getNickname(v)
        if name == curplayernick then 
            return v -- If there is a player with that name, then returns his uid
        end
    end
 
    return nil
end

IsInvalidNum:

function IsInvalidNum(num) 
 
    if num == num and type(num) == "number" then 
 
        if num == math.huge or num == 1/0 then --Num is inf
            return true
        end
 
        return false
 
    else --Num is nan, invalid or nil
        return true
    end
 
end

Step 1 (Adding the listener)

First, we must add the listener function to the Player.NewInputContent1) event, and create a function that takes its args, our code should look like this:

local commandprefix = "/"
 
--[[
Utility functions placeholder 
]]--
 
local commandlist = {}
 
function PlayerSendMsg(e) 
 
    local playerid = e.eventobjid
    local content = e.content
 
end
 
ScriptSupportEvent:registerEvent([=[Player.NewInputContent]=], PlayerSendMsg)

Step 2: CHecking if the message is valid

After that, we check if the message is valid, and if it's a command. a valid message should have more than 1 character, and a command should start with our prefix, which in our case it will be /. If the message fits the criteria mentioned before, we will search it. if it doesn't, we won't. we do this with an if statement 2) where we check the length of content using # 3) and we get the first character using the string:sub 4) function, after doing that, we take everything after the prefix so we have the command directly without it, so it's easier to use after.

our code should look like this:

local commandprefix = "/"
 
--[[
Utility functions placeholder 
]]--
 
local commandlist = {}
 
function PlayerSendMsg(e) 
 
    local playerid = e.eventobjid -- The player who triggered the event (sent the message)
    local content = e.content -- The content that the player sent
 
    if #content > 1 and content:sub(1, 1) == commandprefix then 
        content = content:sub(2, #content)
    end
 
end
 
ScriptSupportEvent:registerEvent([=[Player.NewInputContent]=], PlayerSendMsg)
 

Step 3: Splitting the command and arguments

We now know when a message is a command, or isn't a command. we now need to search for the command. But for that, we need to split the message in its parts, so it's easier to work with it. A command should have this structure:

 prefix (/), name(command), args(arg_1, ..., arg_n)

for splitting a string, we use Game:splitStr(text, mark) 5), after we split our string, it will return a table where the first element will be the name of the command, and the remaining elements will be the arguments. we copy the remaining elements using a for6) loop.

We do this starting it in the index 2, and in the length of the contentparts, where we set the last element in the args table 7) to nametouid(contentparts[i]) 8), so we automatically convert any name to uid. or if that's not possible, then we use only contentparts[i], that snippet should look like this:

 for i = 2, #contentparts do
            args[#args + 1] = nametouid(contentparts[i]) or contentparts[i]
        end

and our code should look like this:

local commandprefix = "/"
 
function nametouid(name) 
    local _,_, playerlist = World:getAllPlayers(-1)
 
    for _,v in ipairs(playerlist) do 
        local _, curplayernick = Player:getNickname(v)
        if name == curplayernick then 
            return v -- If there is a player with that name, then returns his uid
        end
    end
 
    return nil
end
 
--[[ IsInvalidNum function placeholder]]--
 
local commandlist = {}
 
function PlayerSendMsg(e) 
 
    local playerid = e.eventobjid -- The player who triggered the event (sent the message)
    local content = e.content -- The content that the player sent
 
    if #content > 1 and content:sub(1, 1) == commandprefix then -- if content's length is more than 1 and it has the prefix then
        content = content:sub(2, #content) -- takes everything after the prefix
 
    	local _, contentparts = Game:splitStr(content, " ")
	local command = commandlist[contentparts[1]]
 
        local args = {}
 
        for i = 2, #contentparts do
            args[#args + 1] = nametouid(contentparts[i]) or contentparts[i]
        end
 
    end
end
 
ScriptSupportEvent:registerEvent([=[Player.NewInputContent]=], PlayerSendMsg)

Step 4: Executing the function

Once we have identified the command and gathered the arguments, we need to execute the corresponding function from our command list. To achieve this, we call the function stored in the command variable, passing the playerid and any additional arguments.

If there are any arguments provided, we unpack9) them using unpack(args). Otherwise, we simply pass the playerid, this is if we want to log all the commands and who executed them, but this is optional.

our code should look like this:

local commandprefix = "/"
 
function nametouid(name) 
    local _,_, playerlist = World:getAllPlayers(-1)
 
    for _,v in ipairs(playerlist) do 
        local _, curplayernick = Player:getNickname(v)
        if name == curplayernick then 
            return v -- If there is a player with that name, then returns his uid
        end
    end
 
    return nil
end
 
--[[ IsInvalidNum function placeholder]]--
 
local commandlist = {}
 
function PlayerSendMsg(e) 
 
    local playerid = e.eventobjid -- The player who triggered the event (sent the message)
    local content = e.content -- The content that the player sent
 
    if #content > 1 and content:sub(1, 1) == commandprefix then -- if content's length is more than 1 and it has the prefix then
        content = content:sub(2, #content) -- takes everything after the prefix
 
    	local _, contentparts = Game:splitStr(content, " ") -- splits the string
	local command = commandlist[contentparts[1]] -- takes the command name
 
        local args = {} -- stores the args
 
        for i = 2, #contentparts do
            args[#args + 1] = nametouid(contentparts[i]) or contentparts[i] -- copies all the args and transforms them into uid if they are a player's name
        end
 
    	if args and #args >= 1 then
 
            command(playerid, unpack(args))
        else
 
            command(playerid)
        end
    end
end
 
ScriptSupportEvent:registerEvent([=[Player.NewInputContent]=], PlayerSendMsg)

Now. we are halfway there. Our command system works. but we have no commands, so in the next section, we will explain how to make them.

Making the commands

Now that we have the structure in place to detect and execute commands, let's create the actual command functions. These functions will define what each command does, and they need to be added to the commandlist table so they can be triggered when a player uses the corresponding command.

For this tutorial, we will create three commands: timeset, give, and tp.

Timeset

This command is pretty simple. We just give a time, and it will change the current in-game time. For this, we use World:setHours(targetTime)10). We also send a system message so the player knows that the command was triggered.

function timecommand(playerid, targetTime)
    Chat:sendSystemMsg("Setting the time to " .. tostring(targetTime), playerid)
    World:setHours(targetTime)
end

Give command

This command will give an item to a player. for this, we will use Backpack:addItem(targetplayer, itemid, quantity), and for this, we will use.

 targetplayer = affectedplayer or triggerplayer 

so that if no player is provided, it will give it to you. we also do

 quantity = quantity or 1 

for the very same reason. So that if we aren't given a quantity, we just give one item. Also the Chat:sendSystemMsg(msg, player) so the player knows that items were given to him.

function givecommand(triggerplayer, itemid, quantity, affectedplayer) 
    local targetplayer = affectedplayer or triggerplayer  -- Defaults to the triggering player if no target is specified
    quantity = quantity or 1  -- Defaults to 1 item if no quantity is specified
 
    Chat:sendSystemMsg("Added item(s) to your backpack!", targetplayer)  -- Sends a message to the target player
    Backpack:addItem(targetplayer, itemid, quantity)  -- Adds the item(s) to the target player's backpack
end

Tp command

This command will teleport a player to some x,y,z coordinates. We do this using Actor:setPosition(targetplayer, x, y, z). we first do the same as in the command before this one. 11) After we store the coordinates in a table. 12). We then do a for-each loop in the table that we created where we put the values through IsInvalidNum(), and if the value is invalid. we return (cancel the execution)13).

function IsInvalidNum(num) 
 
    if num == num and type(num) == "number" then 
 
        if num == math.huge or num == 1/0 then --Num is inf
            return true
        end
 
        return false
 
    else --Num is nan, invalid or nil
        return true
    end
 
end
 
function tpcommand(triggerplayer, x, y, z, affectedplayer)
    local coordinates = {x,y,z}
    local targetplayer = affectedplayer or triggerplayer
 
    for _,v in (coordinates) do if IsInvalidNum(v) then return end
    Chat:sendSystemMsg("Teleported to: " .. x .. "," .. y .. "," .. z, targetplayer)
    Actor:setPosition(targetplayer, x, y, z)
 
end
 

Final thoughts and code

In this article, we have provided you with the base to create a simple, yet effective command system. The structure showed here allows you to easily manage basic commands like setting time, giving items, and teleporting players. While we only showed a few examples, everything here is designed to be scalable, allowing you to expand it by adding new commands and functionalities as needed.

By following this guide, you not only learn to implement common commands, but you also see how to create your own commands that fit your specific requirements.

What makes a command good

When designing commands for your game, it's important to prioritize certain elements, such as simplicity, clarity, and functionality. because, well designed commands can greatly enhance user experience by offering flexibility and control while minimizing confusion or errors. Below are some key aspects that make a command effective:

  1. Clear and Descriptive Function Names a command should have a clear name that shows what it does. 14)
  2. Logical parameters Commands should accept the necessary parameter, and have defaults for optional ones to improve usability.15)
  3. Input validation User input is validated, so no bugs or unexpected outcomes happen 16)
  4. User feedback Provide feedback, so the users knows if the action was succesful, or not. 17)
  5. Simplicity: Commands should be simple, and focus on a single task, making them easy to maintain and expand if needed. 18)
  6. Consistency: Keeping a consistent structure, be it parameter order or feedback style, helps users learn the system faster and improves usability, both for players and devs 19)

Full code

local commandprefix = "/"
 
function nametouid(name) 
    local _,_, playerlist = World:getAllPlayers(-1)
 
    for _,v in ipairs(playerlist) do 
        local _, curplayernick = Player:getNickname(v)
        if name == curplayernick then 
            return v -- If there is a player with that name, then returns his uid
        end
    end
 
    return nil
end
 
function IsInvalidNum(num) 
 
    if num == num and type(num) == "number" then 
 
        if num == math.huge or num == 1/0 then --Num is inf
            return true
        end
 
        return false
 
    else --Num is nan, invalid or nil
        return true
    end
 
end
 
function timecommand(playerid, targetTime)
    Chat:sendSystemMsg("Setting the time to " .. tostring(targetTime), playerid)
    World:setHours(targetTime)
end
 
function givecommand(triggerplayer, itemid, quantity, affectedplayer) 
    local targetplayer = affectedplayer or triggerplayer
    quantity = quantity or 1
 
    Chat:sendSystemMsg("Added item(s) to your backpack!", targetplayer)
    Backpack:addItem(targetplayer, itemid, quantity)
end
 
function tpcommand(triggerplayer, x, y, z, affectedplayer)
    local coordinates = {x,y,z}
    local targetplayer = affectedplayer or triggerplayer
 
    for _,v in (coordinates) do if IsInvalidNum(v) then return end
    Chat:sendSystemMsg("Teleported to: " .. x .. "," .. y .. "," .. z, targetplayer)
    Actor:setPosition(targetplayer, x, y, z)
 
end
 
local commandlist = {timeset = timecommand, give = givecommand, tp = tpcommand}
 
function PlayerSendMsg(e) 
 
    local playerid = e.eventobjid -- The player who triggered the event (sent the message)
    local content = e.content -- The content that the player sent
 
    if #content > 1 and content:sub(1, 1) == commandprefix then 
        content = content:sub(2, #content)
 
        local _, contentparts = Game:splitStr(content, " ")
        local command = commandlist[contentparts[1]]
        local args = {}
 
        for i = 2, #contentparts do
            args[#args + 1] = nametouid(contentparts[i]) or contentparts[i]
        end
 
        if command then
            if args and #args >= 1 then
                Chat:sendSystemMsg("Running with args", playerid)
                command(playerid, unpack(args))
            else
                Chat:sendSystemMsg("Running without args", playerid)
                command(playerid)
            end
        end
 
    end
end
 
ScriptSupportEvent:registerEvent([=[Player.NewInputContent]=], PlayerSendMsg)
1)
Player sends a message to chat. returns:
  • eventobjid (player who sent the message)
  • content (what was sent in the message)
2)
if condition then 
	-- do something 
end
3)
# shows the length of a table, or string:
-- Length of a string
local text = "Hello, Lua!"
local length = #text  -- #text gives 11
 
-- Length of a table
local numbers = {1, 2, 3, 4, 5}
local count = #numbers  -- #numbers gives 5
4)
String:sub (substring) Extracts a substring from the string, starting at a specified position and ending at another position. it's used this way:
 string:sub(startpos, endpos)
Example:
local text = "Hello, Lua!"
local subText = text:sub(1, 5)  -- Extracts "Hello"
print(subText) -- shows "Hello"
5)
Game:splitStr Splits a text into a table of its components based on a specified delimiter (mark), and returns a table example:
local text = "Hello world"
local mark = " "
 
local result = Game:splitStr(text, mark)  -- {"Hello", "world"}
print(result[1], result[2])  -- it will show "Hello world"
6)
for i = 1, x, step do … end. is a loop that runs from i = 1 to i = x, executing the code inside for each value of i. example:
for i = 1, 5 do
    print(i)
end
where in each iteration it will print the value of i.
7)
args[#args + 1]
8)
the element at the index i
9)
Lua’s unpack function takes a table and returns all of its elements as separate values
10)
This function changes the in-game time. we use it this way:
local targetTime = 14
World:setHours(targetTime) -- sets the time to 14:00
11)
 targetplayer = affectedplayer or triggerplayer 
12)
 coordinates = {x,y,z} 
13)
for _,v in (coordinates) do 
  if IsInvalidNum(v) then 
    return 
  end 
 
14)
The name should be descriptive enough to represent the action being performed, making it easy to identify the purpose of the command (e.g., tp, give)
15)
Examples: (player ID, item ID, or coordinates)
16)
E.g checking if a coordinate is invalid in the tp command
17)
E.g sending a system message to the user, telling him that the command worked.
18)
Having many commands for different tasks in most cases is easier to handle for both, the users and developers than having just one command that does everything
19)
This could be following a similar pattern order, for example. /command target params…, /command params… target, etc