Here’s some helpful resources you might find handy to have to have-on-hand before reading:
- Information Thread: https://devforum.roblox.com/t/HD/216819
- MainModule: https://www.roblox.com/library/3239236979/HD
- Loader: HD Admin - Roblox
- API: https://github.com/1ForeverHD/HDAdmin#api
Command creation has to be on of my favourite aspects when it comes to developing as a whole. Commands are enjoyable to make, relatively quick to add, and only require minimal understanding of HD Admin to implement! This page will guide you through the command creation process and hopefully give you a better insight into the HD Admin system.
Command Template
Inside the Commands module, you’ll find row after row of tables with properties such as ‘Name’, ‘Aliases’, ‘Args’, etc. These are command segments and are adaptations of the command template:
-----------------------------------
{
Name = "";
Aliases = {};
Prefixes = {settings.Prefix};
Rank = 1;
RankLock = false;
Loopable = false;
Tags = {};
Description = "";
Contributors = {};
--
Args = {};
Function = function(speaker, args)
end;
UnFunction = function(speaker, args)
end;
--
};
-----------------------------------
Here’s an overview of each property:
-
Name - the name used to activate a command. This is the core name of a command which appears directly under the Commands page. Use camelCase when defining a name and its aliases.
-
Aliases - alternative or shortened names of the core name. For example, the command ‘speed’ has aliases ‘walkspeed’ and ‘ws’. Format:
{"alias1", "alias2", "etc"}
-
Prefixes - a list of acceptable prefixes for the command. These are the characters positioned before a command’s name to activate it. For example, ;jump forever. The first prefix in the list is the one displayed before the command on the commands page. The two core prefixes are:
- settings.Prefix - the game’s default prefix or player’s custom prefix
- settings.UniversalPrefix - the globally used prefix (!) for HD Admin
-
Rank - the default minimum rank required to activate the command. Ranks range from 0-5 (NonAdmin - Owner). Ranks can be changed by the game-owner under ‘Modify Commands’ in Settings. You can determine a commands rank using this rough guideline:
- 0 (NonAdmin) - screen-bound actions which only appear for the client (e.g. ;cmds)
- 1 (VIP) - non-obtrusive commands, generally fun or aesthetical
- 2 (Mod) - more powerful commands that enhance the users in-game ability
- 3 (Admin) - abusive commands that affect the whole server
- 4 (HeadAdmin) - commands that affect the whole game or have a permanent effect
-
RankLock - when set to true, players can only use the command on others with a lower rank. (e.g. an Admin cannot ban the Owner as the ‘Ban’ command has RankLock set to true).
-
Loopable - Can the command be looped by placing ‘loop’ before the command name? For example, ;loopkill foreverhd. All loop commands have a forced cooldown of 0.1 seconds and will pause until a player has re-spawned if their health is equal to 0.
-
Tags - These are key-words used to describe the command. For example (‘fun’, ‘abusive’, rank’, etc). Tags will become useful in a future update where developers can select and disable groups of commands based on their tags.
-
Description - A clear, concise but relatively short explanation of the command.
-
Contributors - The names of people who worked on the command. In this case, you! Contributors appear within a commands profile and on their credits page.
Additional properties…
-
RequiresRig - Does the command require a certain RigType? If so, set it to
Enum.HumanoidRigType[rigName]
. For example, the ‘size’ command is limited to R15. -
Teleport - This is for when we need the players selected in the player argument as one whole group instead of individuals in an iteration.
Now for the fun bit…
-
Args - These are the arguments passed in the ‘args’ table parameter of ‘Function’. Arguments can range from a Player instance, to a Color3 value, to numbers.
Here are a few examples:- number/integer/studs/speed - returns a number value, default 0
- text/string/reason/question - returns a string which has been filtered through FilterStringForBroadcast(), default " "
-
material - returns a Material value, default
Enum.Material.Plastic
You can find a full list under
MainModule > Server > Modules > Parser > Arguments
. All arguments are automatically converted to lowercase. The first argument isargs[1]
, the secondargs[2]
, thirdargs[3]
, etc. -
Function - this is where the magic happens. Function is as the name implies, a function which will be executed after a players message is successfully parsed. It has three arguments,
(speaker, args, self)
:-
speaker - this is the Player instance of the person who originally executed the command.
-
args - these correspond to the arguments defined earlier and said by the speaker. For example, if
Args = {"Player", "Color"}
and the speaker said;paint forever red
thenargs[1]
would equalgame.Players.ForeverHD
andargs[2]
equalColor3.fromRGB(255,0,0)
. -
self - this is the command table itself. e.g. (self.Name, self.Aliases, self.Prefixes, etc).
-
-
UnFunction - same as Function but activated when ‘un’ is placed before the command name (e.g. ;unfire forever)
Important note: For commands that can be intensive in large quantities (such as ‘poop’), it’s strongly recommended you add a cooldown at the end of the command to prevent it being used on the same player for that specified period of time. For example, ‘poop’ uses a wait(12) at the end of its function.
Background / Framework
This is not essential for creating commands, but is useful to know to give you a better understanding of the system as a whole.
HD Admin uses a modular framework. It can be split into four core components:
- Loader
- MainModule
- MainFramework
- Every other module
How do these all connect? In simple:
- Loader requires the MainModule
- MainModule requires the MainFramework and updates it with settings from the Loader
- MainModule initialises MainFramework, this involves:
- Defining services and variables
- Adding all the client objects (GUIs, starter-scripts, modules, etc) to the appropriate locations
- Requiring all other modules
- Now the client initialises MainFramework. A similar process occurs where the services and variables are defined and modules setup
Key notes:
- To retrieve the framework in any other module, do:
local main = _G.HDAdminMain
- To require another module, do:
local module = main:GetModule(moduleName)
Core Functions
It’s vital you have a solid understanding of core functions to reduce data redundancy and improve your work flow when writing commands.
What are core functions? These are pre-defined functions of frequently repeated code designed to prevent you from writing out the same functions over and over again. There are three modules to host these core functions - Client, Shared and Server:
To access core functions, simply do main:GetModule(“cf”):FunctionName()
I’ve forcefully combined all core functions under the module name ‘cf’ as this is significantly easier to write than main:GetModule(location.."CoreFunctions")
, and means they can be referenced from both server and client under the same name.
(see Background/Framework for more information on referencing modules).
I would strongly recommend learning as many core functions as possible. Here’s a list of the most important functions you should definitely be aware of:
- :FindValue(table, value)
- :GetHRP(plr)
- :GetHumanoid(plr)
- :GetHead(plr)
- :GetNeck(plr)
- :GetRankName(rankIdToConvert)
- :GetRankId(rankNameToConvert)
- :GetUserId(userName)
- :GetName(userId)
- :TweenModel(model, CF, info)
- :AnchorModel(model, state)
- :CreateClone(character)
- :SetTransparency(model, value)
- :AnchorModel(model, state)
All of the above are from SharedCoreFunctions therefore you can use on both the client and server.
Setting up your command creation place
There are two methods to creating and testing commands:
- Using purely the Loader
- Using the the MainModule and an adapted loader
Using the Loader
This is by far the quickest, simplest and easiest way of making and testing commands. As long as you don’t need to modify specific parts of the system, I’d highly recommend using this method:
- Grab a copy of the Loader and place it in Workspace or ServerScriptService
- Under ‘CustomFeatures’, check for the Commands module, which should contain the ClientCommands module. If missing, make sure to use the most up-to-date version of the Loader.
- Publish the game and begin creating your very first command!
Using the MainModule
- Grab a copy of the MainModule and insert it into ServerScriptService of your development place
- Grab a copy of the Loader and place it in Workspace or ServerScriptService
- Modify the Loader module so it requires the newly inserted MainModule instead of 3239236979
- Replace the code from the Commands module (under Modules in the Server folder) with ‘Commands Module Code’ below.
- Publish the game.
Commands Module Code
-- << RETRIEVE FRAMEWORK >>
local main = _G.HDAdminMain
local settings = main.settings
-- << COMMANDS >>
local module = {
-----------------------------------
{
Name = "";
Aliases = {};
Prefixes = {settings.Prefix};
Rank = 1;
RankLock = false;
Loopable = false;
Tags = {};
Description = "";
Contributors = {};
--
Args = {};
Function = function(speaker, args)
end;
UnFunction = function(speaker, args)
end;
--
};
-----------------------------------
{
Name = "";
Aliases = {};
Prefixes = {settings.Prefix};
Rank = 1;
RankLock = false;
Loopable = false;
Tags = {};
Description = "";
Contributors = {};
--
Args = {};
ClientCommand = true;
--[[
FireAllClients = true;
BlockWhenPunished = true;
PreFunction = function(speaker, args)
end;
Function = function(speaker, args)
wait(10)
end;
--]]
--
};
-----------------------------------
};
return module
Networking and Client Commands
You’ll find a good majority of fun commands are better off handled on the client to…
- Create a smoother effect. For example, using TweenService to move an object on the server may look rigid, as apposed to doing the same on the client.
- Minimise the strain on the server.
- Eliminate the risk of data leaks (server-side). Lets say your command generates 20 parts. If an error occurred, these parts may not be cleaned up. By generating these parts on the client, the bugged parts only occur for that one person, and only remain for the duration of the client.
"So how do I go about creating commands on the client?!" HD Admin has been designed so you don’t have to handle any networking whatsoever. Simply setup the command on the server as normal, add an additional ClientCommand = true;
property, then write the remainder of the code within the ‘Client Commands’ module which can be found in both the Loader and MainModule.
Key notes:
Here’s some new server command properties…
-
ClientCommand = true; - Informs the Parser to execute code within the ClientCommands module of the corresponding command name.
-
FireAllClients = true; - Displays the client effect to everyone in the server, instead of the specified player. For example, for ‘blur’ we only want the command to occur for the targeted plr, whereas for ‘poop’ we want everyone to see the effect occurring for the targeted plr.
-
FireSpeaker = true; - Forces the remote to fire the event to the speaker instead of the plr if a Player argument is specified. For example, for the ;view command, we want the client code to execute for the Speaker, instead of the plr.
-
ReplicationEffect = true; - Allows the client to fire information to other clients. Within a ClientCommand, use
main:GetModule("cf"):ReplicateEffect(self.Name, {...})
to send information, and the ReplicationEffect parameter to display the effect for other clients:
ReplicationEffect = function(speaker, args, self)
--Effect here
end
-
ReplicateEffect() has a limit of 2 calls per second. For commands which require finer intervals, consider storing information in a table, then firing it off every second. Example: for a piano command, you could store the keys pressed and time between each press, then fire this information off to the other clients every second. Within ReplicationEffect (for other clients), you would then ‘play’ the sounds in the specified order with the noted time delays.
-
BlockWhenPunished = true; - Prevent the command running if the plr’s characters parent is nil. Use this for client commands which change the parent of a player’s character. In doing so, it prevents conflicts if the player’s character was to be punished/unpunished during the command effect.
-
PreFunction - If you wanted to do something before the remote event is fired off, use this. The current order of execution is
PreFunction > Client Command > Function
.
Here’s a video demonstration of how to go about putting together a client command:
Events
Events are a collection of commonly used actions stored in one central location for ease of use and organisation. You can find the module under MainModule > Client > SharedModules > Events
. The template is very similar to that of a client command:
----------------------------------------------------------------------
["EventName"] = function(bindable, parent, ...)
end;
----------------------------------------------------------------------
Where…
-
Bindable is the BindableEvent object associated with the action,
-
Parent is the object you want the BindableEvent to be parented to. For example, if parented to a player’s Humanoid, then the event will be destroyed when the player respawns.
-
… are any additional arguments you want to pass through.
Example
Lets say you want to toggle an effect when a player ‘double jumps’. In the events module, write the function to activate this effect…
["DoubleJumped"] = function(bindable, parent, ...)
local humanoid = parent
local jumps = 0
local jumpDe = true
humanoid:GetPropertyChangedSignal("Jump"):Connect(function()
if jumpDe then
jumpDe = false
jumps = jumps + 1
if jumps == 4 then
bindable:Fire()
end
wait()
jumpDe = true
wait(0.2)
jumps = jumps - 1
end
end)
end;
In a separate module/script/localscript do…
local main = _G.HDAdminMain or require(game:GetService("ReplicatedStorage"):WaitForChild("HDAdminSetup")):GetMain()
local eventsModule = main:GetModule("Events")
local bindable = eventsModule:New("DoubleJumped", humanoid)
bindable.Event:Connect(function()
-- Toggle effect
end)
Creating Clones
For commands where the player is being affected (moved, killed, etc), consider creating a clone of them. This means the player can ‘return’ to their original state at the end of the effect, instead of being altered, respawned, damaged, etc.
To do this…
- Make sure BlockWhenPunished within the command segment is set to true:
BlockWhenPunished = true;
- Set the parent to nil of the target player’s character:
plr.Character.Parent = nil
- Create and retrieve the clone:
local clone, tracks = main:GetModule(“cf”):CreateClone(plr.Character)
- If the player is equal to the target player, set their cameraSubject to the clone’s humanoid
if plr == main.player then main:GetModule(“cf”):ChangeCameraSubject(clone.Humanoid) end
- Do your magic
- Destroy the clone and set the player’s cameraSubject back to their humanoid
clone:Destroy()
local humanoid = main:GetModule(“cf”):GetHumanoid(plr)
if plr == main.player and humanoid then main:GetModule(“cf”):ChangeCameraSubject(humanoid) end
For a good example, check out the client code of the ‘icecream’ command.
Morphs
When morphing players, always use the MorphHandler module:
main:GetModule(“MorphHandler”):Morph(player, morph)
Place your morph in the ‘Morphs’ folder under Server for it to be registered when using the ‘Morphs’ argument. This also means you can easily access the morph by doing:
local morph = main.server.Morphs[“morphName”]
Testing
When testing a command, ask yourself…
- Does it work for both R6 and R15?
- Is it compatible on all devices?
- If you die or delete body parts from your character, does the command produce an error?
- Does the command appear correctly for all players? Use ‘Start’ test instead of ‘Play Solo’, with at least 2 clients, for this.
- Does your command break when used on multiple players simultaneously?
Publishing a Pack
For more info on the pack process, roles and payment, check out:
If you’re interested in becoming an Intern and being paid for your commands, visit:
Summary / Key Notes
-
Set the rank of a command appropriately. The goal is to make as many fun commands as possible VIP (1), but if the command can affect other players or the server, consider bumping it up a rank. Read ‘Rank’ under the properties overview for more info.
-
Learn and use Shared Core Functions. This reduces data redundancy and speeds up the command creation process.
-
Make fun commands on the client. For commands where you’re creating/moving objects, consider running the code on the client. This prevents the risk of server-data-leaks and creates a smoother effect.
-
Create clones of a player to fake effects/death. Instead of performing the client command on the player, create a clone of them, hide the original player, and use this instead.
-
Use the MorphHandler module to morph players into different characters. Simply do:
main:GetModule("MorphHandler"):Morph(player, morphInstance)
. Place your morph in the ‘Morphs’ folder under Server for it to be registered when using the ‘Morphs’ argument. -
Avoid writing code outside the scope of the command. This makes your command messy, harder to test and ultimately more unlikely of being approved.
-
Parent objects to
main.workspaceFolder
if you want them to be destroyed after using the ;clear command. For example, the ;clone command parents each clone to workspaceFolder on creation:clone.Parent = main.workspaceFolder
-
Copyright. This is fairly self explanatory. Only use assets that are free, made by you, or HD Admin has permission to use. We have a Gold License with Zapsplat if you ever need sound effects.
-
Test your command fully Your command might work in Play Solo, but what about in-game with other players? A command littered with bugs reflects negatively on you and increases the time spent for people verifying your pack.
To do:
- Add parser explanation, commands are executed asynchronously, etc
- Add description of how parser/controllers are loaded