• Loop and table with hook.Add issue.
    12 replies, posted
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: [code] 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] Code for loop: [code] for k, func in pairs( CommandList ) do hook.Add( "PlayerSay", "PlayerSay" .. k, func) end [/code]
Could it be that some other addon or gamemode also has "/yell" command? Try switching the command to something else.
[QUOTE=edgarasf123;52634377]Could it be that some other addon or gamemode also has "/yell" command? Try switching the command to something else.[/QUOTE] 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 [i]anything[/i] 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:[QUOTE=Holywiremod;52634212]I used hook.GetTable() and it appears that all the functions are indeed being added to the hook "PlayerSay."[/QUOTE] 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.
[QUOTE=code_gs;52634803]You shouldn't really be making a hook for every command -- try to make a command wrapper that uses one hook.[/QUOTE] 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.
[QUOTE=Luni;52635376]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][/QUOTE] 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.... ;)
[QUOTE=code_gs;52635396]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.[/QUOTE] [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] [QUOTE=NeatNit;52635402]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.... ;)[/QUOTE] [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 [b]as few as possible[/b], 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 :p
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.
Sorry, you need to Log In to post a reply to this thread.