====== 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.NewInputContent**((Player sends a message to chat. returns: * **eventobjid** (player who sent the message) * **content** (what was sent in the message) )) 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 (( if condition then -- do something end )) where we check the length of content using ''#'' ((''#'' 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 )) and we get the first character using the string:sub (( 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" )) 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: {{:developer_center:developer_editor:script:mwillustrationcommand.png?400| prefix (/), name(command), args(arg_1, ..., arg_n)}} for splitting a string, we use **Game:splitStr(text, mark)** ((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" )), 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 for((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.)) 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 ((args[#args + 1])) to nametouid(contentparts[i]) ((the element at the index i)), 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 unpack((Lua’s ''unpack'' function takes a table and returns all of its elements as separate values)) 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)((This function changes the in-game time. we use it this way: local targetTime = 14 World:setHours(targetTime) -- sets the time to 14:00 )). 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. (( targetplayer = affectedplayer or triggerplayer )) After we store the coordinates in a table. (( coordinates = {x,y,z} )). 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)(( for _,v in (coordinates) do if IsInvalidNum(v) then return 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 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.// ((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) )) - **Logical parameters** //Commands should accept the necessary parameter, and have defaults for optional ones to improve usability.//(( Examples: (player ID, item ID, or coordinates) )) - **Input validation** //User input is validated, so no bugs or unexpected outcomes happen// ((E.g checking if a coordinate is invalid in the tp command)) - **User feedback** //Provide feedback, so the users knows if the action was succesful, or not.// ((E.g sending a system message to the user, telling him that the command worked.)) - **Simplicity**: //Commands should be simple, and focus on a single task, making them easy to maintain and expand if needed.// ((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)) - **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// ((This could be following a similar pattern order, for example. ''/command target params...'', ''/command params... target'', etc )) ==== 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)