Scripting HelpersARCHIVE
A little piece of Roblox history. Preserved for everyone, one question at a time.Read about the archive →
✓ ANSWER ACCEPTED

string.sub() and findfirstchild not working?

I'm trying to make a sort of admin command type script, where you can type :moderator Player and it will grant that player moderator permissions, but when I try to index the player through game.Players:FindFirstChild(Player), it returns saying that the Player is not a valid member of game.Players.

if string.sub(chatInput.Text,1,string.len(":moderator")) == ":moderator" then
	local playerToAdd = string.sub(chatInput.Text,string.len(":moderator")+1)
	if game.Players:FindFirstChild(playerToAdd) then
		_M.addModerator(player.UserId,game.Players:FindFirstChild(playerToAdd).UserId)
		_M.getModeratorsInGame()
	else
		addMessage(player,(playerToAdd.." is not a valid member of players!"))
		print'could not find player!'
	end
end

So say if I input the text, :moderator Player1 (Player1 IS a valid member, it's me..) it will respond saying "Player1 is not a valid member of players!"

For the full script

local ContextActionService = game:GetService("ContextActionService")
local _M = require(game.ReplicatedStorage.MainModule) 

local MAX_MESSAGES = 10
local MESSAGE_HEIGHT = 25
 
local player = game.Players.LocalPlayer
local messages = {}
local chatMessageEvent = game.ReplicatedStorage.ChatMessage
 
local chatScreen = script.Parent
local chatFrame = chatScreen:WaitForChild("ChatFrame")
local chatInput = chatFrame:WaitForChild("ChatInput")
local messageFrame = chatFrame:WaitForChild("MessageFrame")

local messageTemplate = messageFrame.Message:Clone()
messageFrame.Message:Destroy()
 
local function findFirstChild(instance, name)
    for _, child in pairs(instance:GetChildren()) do
        if string.lower(child.Name) == string.lower(name) then
            return child
        end
    end
end

local function addMessage(sender, message)
	if #messages >= MAX_MESSAGES then
		table.remove(messages, #messages):Destroy()
	end
 	for i = 1, #messages do
		local y = (MAX_MESSAGES - i - 1) * MESSAGE_HEIGHT
		messages[i].Position = UDim2.new(0, 0, 0, y)
	end	
	local newMessage = messageTemplate:Clone()
	newMessage.NameLabel.Text = "[".._M.PlayerType(game.Players.LocalPlayer.UserId).."] "..sender.Name .. ": "
	newMessage.Content.Text = message
	newMessage.Parent = messageFrame
	newMessage.Position = UDim2.new(0, 0, 0, (MAX_MESSAGES - 1) * MESSAGE_HEIGHT)
	newMessage.NameLabel.TextColor3 = _M.chatColors(_M.PlayerType(game.Players.LocalPlayer.UserId))
	newMessage.Content.TextColor3 = Color3.new(1,1,1)
	table.insert(messages, 1, newMessage)
end
 
local function onFocusLost(enterPressed, inputObject)
	if enterPressed then
		if string.sub(chatInput.Text,1,string.len(":moderator")) == ":moderator" then
			local playerToAdd = string.sub(chatInput.Text,string.len(":moderator")+1)
			if game.Players:FindFirstChild(playerToAdd) then
				_M.addModerator(player.UserId,game.Players:FindFirstChild(playerToAdd).UserId)
				_M.getModeratorsInGame()
			else
				addMessage(player,(playerToAdd.." is not a valid member of players!"))
				print'could not find player!'
			end
		else
			local filtered = game:GetService("Chat"):FilterStringForBroadcast(chatInput.Text,game.Players.LocalPlayer)
			addMessage(player, filtered)			
			chatMessageEvent:FireServer(filtered)
			chatInput.Text = "Click here or press '/' to type!"
		end
	end
end
 
local function onSlashPressed(actionName, inputState, inputObject)
	if inputState == Enum.UserInputState.End then
		chatInput:CaptureFocus()
	end
end
 
game.StarterGui:SetCoreGuiEnabled(Enum.CoreGuiType.Chat, false)
 
chatMessageEvent.OnClientEvent:connect(addMessage)
chatInput.FocusLost:connect(onFocusLost)
ContextActionService:BindAction("Chatting", onSlashPressed, false, Enum.KeyCode.Slash)

^ Taken from the tutorial, but I have changed several elements to my needs using a module script, which personally has a lot of the dataStore services and keys for them, sooo.... ^

1 answer

✓ Accepted answer

As a style suggestion: use msg:sub(...) instead of string.sub(msg, ...) because it is much shorter. Similarly for :len(), but really you should use the # operator instead of :len().

The problem is simple; you could debug it by printing the following:

print("/" .. playerToAdd .. "/")

If you input :moderator Player, you will get the following as output:

/ Player1/

Notice that there is a an empty space between the / and Player1?

There isn't a player in the game called " Player1", there is one in the game called "Player1". You have an extra space in front.


There are several paths forward.

  1. Consider this correct, and it's the user using it wrong. You have to say moderator:Player1 with no space.

  2. Modify the pattern to search for to be "moderator: " instead of "moderator:" (with a space at the end), which would make using a singe space mandatory

  3. Actually allow any number of space characters after the :

I think (3) is the most ergonomic and reasonable option.

To accomplish it, first I want to refactor the code.

(TODO)

local moderator = chatInput.Text:match("^moderator:%s*(.+)")
if moderator then
	local player = game.Players:FindFirstChild(moderator)

This discussion is preserved as it was. New answers, comments, and votes are closed.