Loop and table with hook.Add issue.

Hey guys so I am attempting to take a table containing functions for chat commands and am using a loop to iterate through the table and individually add them to the hook “PlayerSay.” When doing this the first chat command of the table is read and works properly, however, every other chat command does not work. I used hook.GetTable() and it appears that all the functions are indeed being added to the hook “PlayerSay.” How can I make it to where all the chat commands work rather than just the first one while retaining the method of adding the elements of the table individually rather than all at once.

Code for table:



CommandList[1] = function( ply, text, bteam ) 
	local textLine = string.Explode( " ", string.lower( text ) )
        print( "Test1" ) --does print to console when typing /say
	if( textLine[1] == "/say" ) then
		local returnText = table.concat( string.Explode( " ", text ), " ", 2, #textLine )
		return returnText
	end	
end

CommandList[2] = function( ply, text, bteam ) 
	local textLine = string.Explode( " ", string.lower( text ) )
        print( "Test2" ) --does not print to console when typing /yell
	if( textLine[1] == "/yell" ) then
		local returnText = table.concat( string.Explode( " ", text ), " ", 2, #textLine )
		return returnText
	end	
end


Code for loop:



for k, func in pairs( CommandList ) do
	hook.Add( "PlayerSay", "PlayerSay" .. k, func)
end


Could it be that some other addon or gamemode also has “/yell” command? Try switching the command to something else.

I’m running things from base gamemode without other addons (other than custom models.) The string I check for isn’t the issue - I’m guessing it has something to do with how hooks work but I’m just not sure at this point.

You shouldn’t really be making a hook for every command – try to make a command wrapper that uses one hook.

Regardless, as far as I can tell everything in your code should work. I don’t see any reason why it wouldn’t. When you say anything in the chat, even just “hi”, you should see both Test1 and Test2 printed to the console. Yet you say you don’t.

The only reason I can think of is that either the for loop in your last snippet isn’t running, or it’s running before the Test2 (/yell) function is added to the table. Which raises the question: how come you put it in a separate snippet? Is it in a different file?

[editline]1st September 2017[/editline]

Oh, I missed this part:

I officially have no idea. It all looks fine.

[editline]1st September 2017[/editline]

You didn’t leave anything out, did you? I’m assuming that, like in the snippet, the hooks don’t return anything unless they match (with /say or /yell).

Another PlayerSay hook could be returning causing yours not to run.

and meanwhile everyone keeps recreating the same solutions with string.Explode()[1] or string.find+string.sub when all you need is a single string.match…
[lua]
local cmds = {}

hook.Add(“PlayerSay”, “ChatCommands”, function(ply, text, isteam)
local cmd, args = text:match("^/(%S+)%s*(.-)%s*$")
local fn = cmds[cmd]
if fn then
local ret = fn(ply, cmd, args)
return isstring(ret) and ret or false
end
end)

function cmds.kill(ply, cmd, text) ply:Kill() end
function cmds.yell(ply, cmd, text) return "[ANGRY] " … text end
[/lua]

That works if you want the args to be a single string, but in my opinion, ideally, you would want args to be a table. Also, you have to account for people putting multiple spaces between args.

That’s pretty cool, but I gotta admit, I have no idea what that pattern does. It’s far less readable than the (admittedly shitty but undeniably functional) string.Explode method. Now, if you were to explain the pattern with a huge comment block… :wink:

[lua]
hook.Add(“PlayerSay”, “ChatCommands”, function(ply, raw, isteam)
local cmd, text = raw:match("^/(%S+)%s*(.-)%s*$")
local fn = cmds[cmd]
if fn then
local args = {}
for word in text:gmatch("%S+") do
args[#args + 1] = word
end

    local ret = fn(ply, cmd, text, args)
    return isstring(ret) and ret or false
end

end)

function cmds.insult(ply, cmd, text, args)
return args[1] … " is a butthead"
end
[/lua]
Bonus round: argument parsing!
[lua]
function string.Tokenize(str)
local res, word = {}, {}
local quot, esc
for ch in str:gmatch(".") do
– in quoted segment?
if quot then
if esc or (ch ~= ‘"’ and ch ~= “\”) then
word[#word + 1] = ch
esc = false
elseif ch == “\” then
esc = true
else
– end of quoted segment
– don’t check if #word>0. it was quoted on purpose.
res[#res + 1], word = table.concat(word), {}
quot = false
end
elseif ch == ‘"’ then
– beginning of quoted segment
if #word > 0 then
res[#res + 1], word = table.concat(word), {}
end
quot = true
elseif ch == " " then
– break between arguments
if #word > 0 then
res[#res + 1], word = table.concat(word), {}
end
else
word[#word + 1] = ch
end
end
if #word > 0 then
res[#res + 1] = table.concat(word)
end
return res
end

hook.Add(“PlayerSay”, “ChatCommands”, function(ply, raw, isteam)
local cmd, text = raw:match("^/(%S+)%s*(.-)%s*$")
local fn = cmds[cmd]
if fn then
local args = string.Tokenize(args)
local ret = fn(ply, cmd, text, args)
return isstring(ret) and ret or false
end
end)
[/lua]

[editline]31st August 2017[/editline]

[lua]
– ^: start of string
– /: match a single ‘/’
– (%S+): match 1 or more non-spaces, as many as possible
– %s*: match 0 or more spaces, as many as possible
– (.-): match 0 or more of any character, as few as possible
– %s*: match 0 or more spaces, as many as possible
: end of string local cmd, args = text:match("^/(%S+)%s*(.-)%s*")
[/lua]
The reason you use “(.-)” to match the arguments, instead of “(.)", is because “(.-)” wants to match as few as possible, so while the “.” allows it to match any character (including whitespace), it’ll happily give up any whitespace to the the "%s” on either side – so you don’t have to use str:Trim() on the resulting match.

You also use “(.-)” instead of “(.+)” because “+” wants 1 or more characters, and then you can’t match if there’s no argument.

About to go see a movie, sorry – will give you a proper reply later.

^ at the beginning of a string = pattern starts at the beginning of the string, which skips it from trying to search every character
/ = command prefix
( = start of sub-pattern
%S = shortcut for [^%s], which matches any non-whitespace characters. This matches the actual command

  • = 1 or more repetitions – must match at least one character. This also disallows “/ test” from working
    ) = end of sub-pattern
    %s = separates the string command prefix from the arguments
  • = 0 or more repetitions. This allows “/test” and “/test foo” to work
    (.-) = matches all characters afterward
    %s*$ = cuts off trailing spaces at the end of the string

[editline]31st August 2017[/editline]

Ninja’d :stuck_out_tongue:

I forgot to explain %S+ lol, thanks for catching that

Very nice, Luni. That was a good explanation. Anyway taking the advice of everyone I’ve come up with a solution. Thank you for the help.