Cmdr: A fully extensible and type safe command console for Roblox Developers

I would suggest changing your type to this:

local Players = game:GetService("Players")

if not game:IsLoaded() then
	game.Loaded:Wait()
end

local treasures = Players.LocalPlayer:WaitForChild("Treasures")

return function(registry)
	local Util = registry.Cmdr.Util
	
	local treasureNames = Util.GetNames(treasures:GetChildren())
	table.sort(treasureNames) -- Guarantees the same order in the list 
	
	registry:RegisterType("treasure",
		Util.MakeEnumType(
			"TreasureName",
			treasureNames
		)
	)
end
1 Like

Hello!!

I’m currently stuck on something with Cmdr. I’m trying to make a command that utilizes multiple custom types. However I want one of the arguments to pick a specific type depending on it’s previous argument.


Here’s a picture to show what I mean.
Any help is super appreciated!!
Thanks in advance!

We call this dynamic arguments. I’m not sure if this is covered in the current documentation site (which is quite out of date), but it is mentioned on the Beta documentation site: https://eryn.io/Cmdr/beta/docs/commands#dynamic-arguments-and-inline-types

1 Like

Thanks a bunch, yeah I’ve noticed that the newer site has a lot of missing things like Dynamic Arguments. This was exactly what I needed!

Now that the new Roblox ban API has come out, who agrees with me that there should be a ban command, unban command, and checkban command by default?

3 Likes

There’s two things that I would like for my project, are these able to be done on cmdr or is there an alternative with these that I could use?

  1. Is there a way to have optional arguments? like you could set it to this or that, or just leave it blank to do both.
  2. Is there a way to have a system where if you press enter and you don’t have a valid command typed in, instead of executing it will autocomplete the top result?
Autocomplete Example

Autocomplete
If I were to press enter here, it would put the whole “clear” command into the box
(NOT EXECUTE IT)

Yes.

Just use tab for autocompletion, but I am sure it might be possible to tweak it to use the enter key.

Sorry i must have missed that part about using tab for autocomplete :sweat_smile:

Nice! How can I validate the user is in one of the permitted groups?

I didn’t see any replies here mentioning support for Player’s DisplayNames, so I thought I’d include my remake of the Player type. The AutoComplete now includes DisplayNames in the format “DisplayName @Username”.

New Player type module
local Util = require(script.Parent.Parent.Shared.Util)
local Players = game:GetService("Players")

local function GetDisplayNamePair(list)
	local names = {}
	for _,player in list do
		table.insert(names, `{player.DisplayName} @{player.Name}`)
	end
	return names
end

local playerType = {
	Transform = function (text)
		local findPlayer = Util.MakeFuzzyFinder(Players:GetPlayers())
		local result = findPlayer(text)
		
		if not result or #result == 0 then
			local list = GetDisplayNamePair(Players:GetPlayers())
			findPlayer = Util.MakeFuzzyFinder(list)
			
			local fResult = findPlayer(text)
			result = {}
			
			for _,found in fResult do
				table.insert(result, Players:FindFirstChild(found:split("@")[2]))
			end
		end
		
		return result
	end;

	Validate = function (players)
		return #players > 0, "No player with that name could be found."
	end;

	Autocomplete = function (players)
		return GetDisplayNamePair(players)
	end;

	Parse = function (players)
		return players[1]
	end;

	Default = function(player)
		return player.Name
	end;

	ArgumentOperatorAliases = {
		me = ".";
		all = "*";
		others = "**";
		random = "?";
	};
}

return function (cmdr)
	cmdr:RegisterType("player", playerType)
	cmdr:RegisterType("players", Util.MakeListableType(playerType, {
		Prefixes = "% teamPlayers";
	}))
end

This is especially useful if your game displays Players with only their DisplayName, and you want to avoid the extra effort of opening the Roblox menu to check who’s who.

I’m having an issue with creating types. I’ve tried everything.

Each time Autocomplete (except for the initial function) is called, this error is called.

 00:41:36.568  ReplicatedStorage.CmdrClient.CmdrInterface.AutoComplete:122: attempt to perform arithmetic (sub) on nil and number  -  Client - AutoComplete:122
  00:41:36.568  Stack Begin  -  Studio
  00:41:36.568  Script 'ReplicatedStorage.CmdrClient.CmdrInterface.AutoComplete', Line 122 - function Show  -  Studio - AutoComplete:122
  00:41:36.568  Script 'ReplicatedStorage.CmdrClient.CmdrInterface', Line 84 - function OnTextChanged  -  Studio - CmdrInterface:84
  00:41:36.568  Script 'ReplicatedStorage.CmdrClient.CmdrInterface.Window', Line 328  -  Studio - Window:328
  00:41:36.568  Stack End  -  Studio

Here is the type code:


ANOMALYTYPAGE = {
	[1] = "ItsPlasmaRBLX2",
	[2] = "Packages",
	[3] = "Spigot",
	[4] = "Brick"
}

local Util = require(script.Parent.Parent.Shared.Util)
local anomalyTypageFinder = Util.MakeFuzzyFinder(ANOMALYTYPAGE)

local storedKeyType = {
	Validate = function(anomalyName : string)
		if typeof(anomalyName) == "string" then
			return ANOMALYTYPAGE[anomalyName] ~= nil, "No anomaly with that name could be found."
		end;
		return false, "Anomaly name must be a string"
	end;

	Transform = function(text)
		return anomalyTypageFinder(text)
	end;

	Autocomplete = function(anomalyName)
		return Util.GetNames(ANOMALYTYPAGE)
	end;

	Parse = function(anomalyName : string)
		return anomalyName
	end;
}

return function (cmdr)
	cmdr:RegisterType("anomaly", storedKeyType)
	cmdr:RegisterType("anomalies", Util.MakeListableType(storedKeyType))
end


Side note, even if the Autocomplete function is called it will throw an error, regardless of what is in the function. This means if the function is blank, it will still throw an error.

Apparently in the AutoComplete master function the variable start, stop returns nil for my type.

My work around was adding a continue condition. Surprisingly the type’s autocomplete works.

anyone got any idea why im having a issue? hwenever i utilize cmdr, the script says "CMDR disabled for security, BEforeRun not setup or something?

That’s because for safety, you can’t run any commands until you setup a BeforeRun hook. Read the documentation to learn more on how to set it up. The link will take you to the beta version of the Cmdr docs. For clarification, a BeforeRun hook is what you will use for command permissions. It will check if the player attempting to execute the command is allowed to but you can define custom behavior/logic because you’re going to be the one writing it.

It is setup… It says it is not however.

Then you probably didn’t do it correctly. The error clearly says you didn’t set up the BeforeRun hook. How did you set it up? Can you provide the code you used?

is it possible to make the CMDR usable only to specific people?

I’m having a very weird issue with Cmdr. I am making a viewstats command so that my Moderators can view other players stats by typing their username with the auto-complete menu if they’re in the server or use the @ prefix to type their full username if they’re not in the server. However, the issue is that when Cmdr gets to a point where it needs to return something inside the command, it just doesn’t. And that happens for every command for some really weird reason. After using commands a bunch more times, the very delayed command responses I should’ve gotten ages ago finally appear.

I tried using print statements and they got where they needed to. Once it gets to a return statement, it just self destructs.

Note: The game uses ProfileService and the game is called RoKarate. You can search it using the search bar and it’ll be the first game. I have confirmed that every variable gives what it’s supposed to such as the DataManager existing and the DataManager giving the current profile if they are in the server and all that.

Here is the following code I used:

return {
	Name = "viewstats",
	Aliases = {"stats"},
	Description = "View a players stats.",
	Group = "Moderator",
	Args = {
		{
			Type = "player @ string",
			Name = "player @ username",
			Description = "The full username of the player."
		}
	}
}
local Players = game:GetService("Players")
local ServerStorage = game:GetService("ServerStorage")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local DataManager = require(ServerStorage.DataManager) -- This does exist
local Format = require(ReplicatedStorage.Modules.FormatNumber.Simple) -- This does exist

local function toDHMS(seconds)
	local days = math.floor(seconds / 86400)
	local hours = math.floor((seconds % 86400) / 3600)
	local minutes = math.floor((seconds % 3600) / 60)
	local seconds = math.floor(seconds % 60)
	return ("%i:%02i:%02i:%02i"):format(days,hours,minutes,seconds)
end

return function(context, player)
	if typeof(player) == "Instance" then
		local profile = DataManager:GetProfile(player) -- This does work
		
		context:Reply(player.Name.."'s stats:")
		context:Reply("---------------------------------------------")
		context:Reply("Belt: "..profile.Data.Belt)
		context:Reply("Strength: "..Format.Format(profile.Data.Strength))
		context:Reply("Health: "..Format.Format(profile.Data.Health))
		context:Reply("MaxStamina: "..Format.Format(profile.Data.MaxStamina))
		context:Reply("Wins: "..Format.Format(profile.Data.Wins))
		context:Reply("Kills: "..Format.Format(profile.Data.TotalKills))
		context:Reply("Robux Donated: "..Format.Format(profile.Data.RobuxDonated))
		context:Reply("Playtime: "..toDHMS(profile.Data.Playtime))
		context:Reply("---------------------------------------------")

		return "Successfully retrieved the players stats."
	elseif typeof(player) == "string" then
		local gotUserId, userId = pcall(function()
			return Players:GetUserIdFromNameAsync(player)
		end)

		if not gotUserId then
			return `Failed to get UserId associated with {player}.\nError: {userId}`
		end

		local profileStore = DataManager:GetProfileStore()
		local profile = profileStore:ViewProfileAsync("Player_"..userId)

		if profile == nil then
			return player.." does not have any data!"
		end

		context:Reply(player.."'s stats:")
		context:Reply("---------------------------------------------")
		context:Reply("Belt: "..profile.Data.Belt)
		context:Reply("Strength: "..Format.Format(profile.Data.Strength))
		context:Reply("Health: "..Format.Format(profile.Data.Health))
		context:Reply("MaxStamina: "..Format.Format(profile.Data.MaxStamina))
		context:Reply("Wins: "..Format.Format(profile.Data.Wins))
		context:Reply("Kills: "..Format.Format(profile.Data.TotalKills))
		context:Reply("Robux Donated: "..Format.Format(profile.Data.RobuxDonated))
		context:Reply("Playtime: "..toDHMS(profile.Data.Playtime))
		context:Reply("---------------------------------------------")
		
		return "Successfully retrieved the players stats."
	end
end
1 Like

I’m having an issue where the mobile keyboard constantly flickers on/off. Looks like it changes every frame. This starts happening when you tap the command panel.

All I’m doing is ‘:Toggle()’ once to open the gui.

Does someone know how to fix this?

this is incredible and i love it
however, i would like a feature where only a few people (like moderators, admins) can toggle the UI
cause right now any player can just press a given key to toggle the UI which is eh