- eventobjid (player who sent the message)
- content (what was sent in the message)
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
- timeset (to change the ingame time)
- give (gives an item to a player)
- tp (teleport a player to specific x, y, z coordinates)
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.
- commandprefix (The prefix that a message should have in order to be considered a command)
- 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:
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:
- Clear and Descriptive Function Names a command should have a clear name that shows what it does. 14)
- Logical parameters Commands should accept the necessary parameter, and have defaults for optional ones to improve usability.15)
- Input validation User input is validated, so no bugs or unexpected outcomes happen 16)
- User feedback Provide feedback, so the users knows if the action was succesful, or not. 17)
- Simplicity: Commands should be simple, and focus on a single task, making them easy to maintain and expand if needed. 18)
- 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)
if condition then -- do something end
#
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
string:sub(startpos, endpos)Example:
local text = "Hello, Lua!" local subText = text:sub(1, 5) -- Extracts "Hello" print(subText) -- shows "Hello"
local text = "Hello world" local mark = " " local result = Game:splitStr(text, mark) -- {"Hello", "world"} print(result[1], result[2]) -- it will show "Hello world"
for i = 1, 5 do print(i) endwhere in each iteration it will print the value of i.
unpack
function takes a table and returns all of its elements as separate valueslocal targetTime = 14 World:setHours(targetTime) -- sets the time to 14:00
targetplayer = affectedplayer or triggerplayer
coordinates = {x,y,z}
for _,v in (coordinates) do if IsInvalidNum(v) then return end
/command target params…
, /command params… target
, etc