XoXFaby

Untitled

Sep 13th, 2014
326
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 44.59 KB | None | 0 0
  1. --[[
  2. File: Commands
  3. ]]
  4.  
  5. ULib.cmds = {}
  6. local cmds = ULib.cmds -- To save my fingers
  7.  
  8. --[[
  9. Variable: cmds.optional
  10.  
  11. This is used when specifying an argument to flag the argument as optional.
  12. ]]
  13. cmds.optional = {} -- This is just a key, ignore the fact that it's a table.
  14.  
  15. --[[
  16. Variable: cmds.restrictToCompletes
  17.  
  18. This is used when specifying a string argument to flag that only what was
  19. specified for autocomplete is allowed to be passed as a valid argument.
  20. ]]
  21. cmds.restrictToCompletes = {} -- Key
  22.  
  23. --[[
  24. Variable: cmds.takeRestOfLine
  25.  
  26. This is used when specifying a string argument to flag that this argument
  27. should use up any remaining args, whether quoted as one arg or not. This
  28. is useful for things like specifying a ban reason where you don't want to
  29. force users to write an entire sentence within quotes.
  30. ]]
  31. cmds.takeRestOfLine = {} -- Key
  32.  
  33. --[[
  34. Variable: cmds.round
  35.  
  36. This is used when specifying a number argument to flag the argument to round
  37. the number to the nearest integer.
  38. ]]
  39. cmds.round = {} -- Key
  40.  
  41. --[[
  42. Variable: cmds.ignoreCanTarget
  43.  
  44. This is used when specifying a command that should ignore the can_target
  45. property in the groups config. IE, private say in ULX uses this so that
  46. users can target admins to chat with them.
  47. ]]
  48. cmds.ignoreCanTarget = {} -- Key
  49.  
  50. --[[
  51. Variable: cmds.allowTimeString
  52.  
  53. This is used when specyfing a number argument that should allow time string
  54. representations to be parsed (eg, '1w1d' for 1 week 1 day).
  55. ]]
  56. cmds.allowTimeString = {} -- Key
  57.  
  58.  
  59. --[[
  60. Class: cmds.BaseArg
  61.  
  62. Just defines the basics for us, used in autocomplete and command callbacks.
  63. These default implementations just throw an error if called. You shouldn't
  64. need any great knowledge about the functions in these types, just that
  65. they exist and how to pass in restrictions.
  66.  
  67. Revisions:
  68.  
  69. 2.40 - Initial
  70. ]]
  71. cmds.BaseArg = inheritsFrom( nil )
  72.  
  73.  
  74. --[[
  75. Function: cmds.BaseArg:parseAndValidate
  76.  
  77. Used to, you guessed it, parse and validate an argument specified by a user.
  78. Takes user command line input and converts it to a regular lua variable of
  79. the correct type.
  80.  
  81. Parameters:
  82.  
  83. ply - The player using the command. Useful for querying.
  84. arg - The arg to parse. It's already properly trimmed.
  85. cmdInfo - A table containing data about this command.
  86. plyRestrictions - The restrictions from the access tag for this player.
  87.  
  88. Returns:
  89.  
  90. The parsed arg correctly typed if it validated, false and an
  91. explanation otherwise.
  92. ]]
  93. function cmds.BaseArg:parseAndValidate( ply, arg, cmdInfo, plyRestrictions )
  94. error( "Unimplemented BaseArg:parseAndValidate called" )
  95. end
  96.  
  97.  
  98. --[[
  99. Function: cmds.BaseArg:complete
  100.  
  101. Used to autocomplete a command. Passes back the options the player has in
  102. using this command.
  103.  
  104. Parameters:
  105.  
  106. arg - The arg to parse. It's already properly trimmed.
  107. cmdInfo - A table containing data about this command.
  108. plyRestrictions - The restrictions from the access tag for this player.
  109.  
  110. Returns:
  111.  
  112. A table of strings containing the options that are available.
  113. ]]
  114. function cmds.BaseArg:complete( arg, cmdInfo, plyRestrictions )
  115. error( "Unimplemented BaseArg:complete called" )
  116. end
  117.  
  118.  
  119. --[[
  120. Function: cmds.BaseArg:usage
  121.  
  122. Prints a basic usage message for this parameter.
  123.  
  124. Parameters:
  125.  
  126. cmdInfo - A table containing data about this command.
  127. plyRestrictions - The restrictions from the access tag for this player.
  128.  
  129. Returns:
  130.  
  131. A string describing what this parameter is and how to use it.
  132. ]]
  133. function cmds.BaseArg:usage( cmdInfo, plyRestrictions )
  134. error( "Unimplemented BaseArg:usage called" )
  135. end
  136.  
  137.  
  138. --[[
  139. Class: cmds.NumArg
  140.  
  141. A number arg, inherits from <cmds.BaseArg>. Restrictions can include a numeric
  142. value for keys 'min', 'max', and 'default'. All do what you think they do.
  143. If the argument is optional and no default is specified, 0 is used for
  144. default. You can specify the allowTimeString key to allow time string
  145. representations. Lastly, you can specify a value for the key 'hint' for a
  146. hint on what this argument is for, IE "damage".
  147.  
  148. Example:
  149.  
  150. The following code creates a command that accepts an optional numeric
  151. second argument that defaults to 0 and has to be at least 0.
  152.  
  153. :cmd = ULib.cmds.TranslateCommand( "ugm slap", ULib.slap )
  154. :cmd:addParam{ type=ULib.cmds.PlayerArg, target="*", default="^", ULib.cmds.optional }
  155. :cmd:addParam{ type=ULib.cmds.NumArg, min=0, default=0, ULib.cmds.optional }
  156.  
  157. Revisions:
  158.  
  159. 2.40 - Initial
  160. ]]
  161. cmds.NumArg = inheritsFrom( cmds.BaseArg )
  162.  
  163.  
  164. --[[
  165. Function: cmds.NumArg:processRestrictions
  166.  
  167. A helper function to help us figure out restrictions on this command.
  168. ]]
  169. function cmds.NumArg:processRestrictions( cmdRestrictions, plyRestrictions )
  170. -- First, reset
  171. self.min = nil
  172. self.max = nil
  173.  
  174. local allowTimeString = table.HasValue( cmdRestrictions, cmds.allowTimeString )
  175.  
  176. if plyRestrictions then -- Access tag restriction
  177. if not plyRestrictions:find( ":" ) then -- Assume they only want one number here
  178. self.min = plyRestrictions
  179. self.max = plyRestrictions
  180. else
  181. local timeStringMatcher = "[-hdwy%d]*"
  182. dummy, dummy, self.min, self.max = plyRestrictions:find( "^(" .. timeStringMatcher .. "):(" .. timeStringMatcher .. ")$" )
  183. end
  184.  
  185. if not allowTimeString then
  186. self.min = tonumber( self.min )
  187. self.max = tonumber( self.max )
  188. else
  189. self.min = ULib.stringTimeToSeconds( self.min )
  190. self.max = ULib.stringTimeToSeconds( self.max )
  191. end
  192. end
  193.  
  194. if allowTimeString and not self.timeStringsParsed then
  195. self.timeStringsParsed = true
  196. cmdRestrictions.min = ULib.stringTimeToSeconds( cmdRestrictions.min )
  197. cmdRestrictions.max = ULib.stringTimeToSeconds( cmdRestrictions.max )
  198. cmdRestrictions.default = ULib.stringTimeToSeconds( cmdRestrictions.default )
  199. end
  200.  
  201. if cmdRestrictions.min and (not self.min or self.min < cmdRestrictions.min) then
  202. self.min = cmdRestrictions.min
  203. end
  204.  
  205. if cmdRestrictions.max and (not self.max or self.max > cmdRestrictions.max) then
  206. self.max = cmdRestrictions.max
  207. end
  208. end
  209.  
  210.  
  211. --[[
  212. Function: cmds.NumArg:parseAndValidate
  213.  
  214. See <cmds.BaseArg:parseAndValidate>
  215. ]]
  216. function cmds.NumArg:parseAndValidate( ply, arg, cmdInfo, plyRestrictions )
  217. self:processRestrictions( cmdInfo, plyRestrictions )
  218.  
  219. if not arg and self.min and self.min == self.max then -- Arg's not valid, min is, and it's equal to max
  220. return self.min
  221. end
  222.  
  223. if not arg and table.HasValue( cmdInfo, cmds.optional ) then
  224. arg = cmdInfo.default or 0 -- Set it, needs to go through our process
  225. end
  226.  
  227. local allowTimeString = table.HasValue( cmdInfo, cmds.allowTimeString )
  228. local num -- We check if it's nil after we see if a default has been provided for them
  229. if not allowTimeString then
  230. num = tonumber( arg )
  231. else
  232. num = ULib.stringTimeToSeconds( arg )
  233. end
  234.  
  235. local typeString
  236. if not allowTimeString then
  237. typeString = "number"
  238. else
  239. typeString = "number or time string"
  240. end
  241.  
  242. if not num then
  243. return nil, string.format( "invalid " .. typeString .. " \"%s\" specified", tostring( arg ) )
  244. end
  245.  
  246. if self.min and num < self.min then
  247. return nil, string.format( "specified " .. typeString .. " (%s) was below your allowed minimum value of %g", arg, self.min )
  248. end
  249.  
  250. if self.max and num > self.max then
  251. return nil, string.format( "specified " .. typeString .. " (%s) was above your allowed maximum value of %g", arg, self.max )
  252. end
  253.  
  254. if table.HasValue( cmdInfo, cmds.round ) then
  255. return math.Round( num )
  256. end
  257. return num
  258. end
  259.  
  260.  
  261. --[[
  262. Function: cmds.NumArg:complete
  263.  
  264. See <cmds.BaseArg:complete>
  265. ]]
  266. function cmds.NumArg:complete( ply, arg, cmdInfo, plyRestrictions )
  267. return { self:usage( cmdInfo, plyRestrictions ) }
  268. end
  269.  
  270.  
  271. --[[
  272. Function: cmds.NumArg:usage
  273.  
  274. See <cmds.BaseArg:usage>
  275. ]]
  276. function cmds.NumArg:usage( cmdInfo, plyRestrictions )
  277. self:processRestrictions( cmdInfo, plyRestrictions )
  278. local isOptional = table.HasValue( cmdInfo, cmds.optional )
  279.  
  280. local str = cmdInfo.hint or "number"
  281.  
  282. if self.min == self.max and self.min then -- Equal but not nil
  283. return "<" .. str .. ": " .. self.min .. ">"
  284. else
  285. str = "<" .. str
  286. if self.min or self.max or cmdInfo.default or isOptional then
  287. str = str .. ": "
  288. end
  289. if self.min then
  290. str = str .. self.min .. "<="
  291. end
  292. if self.min or self.max then
  293. str = str .. "x"
  294. end
  295. if self.max then
  296. str = str .. "<=" .. self.max
  297. end
  298. if cmdInfo.default or isOptional then
  299. if self.min or self.max then
  300. str = str .. ", "
  301. end
  302. str = str .. "default " .. (cmdInfo.default or 0)
  303. end
  304. str = str .. ">"
  305.  
  306. if isOptional then
  307. str = "[" .. str .. "]"
  308. end
  309. return str
  310. end
  311. end
  312.  
  313.  
  314. --[[
  315. Class: cmds.BoolArg
  316.  
  317. A boolean arg, inherits from <cmds.BaseArg>. You can specify a value for the key
  318. 'hint' for a hint on what this argument is for, IE "revoke access".
  319.  
  320. Example:
  321.  
  322. The following code creates a command that accepts an option boolean
  323. third argument that defaults to false.
  324.  
  325. :local groupallow = ULib.cmds.TranslateCommand( "ulx groupallow", ulx.groupallow )
  326. :groupallow:addParam{ type=ULib.cmds.StringArg }
  327. :groupallow:addParam{ type=ULib.cmds.StringArg }
  328. :groupallow:addParam{ type=ULib.cmds.BoolArg, hint="revoke access", ULib.cmds.optional }
  329.  
  330. Revisions:
  331.  
  332. 2.40 - Initial
  333. ]]
  334. cmds.BoolArg = inheritsFrom( cmds.BaseArg )
  335.  
  336.  
  337. --[[
  338. Function: cmds.BoolArg:processRestrictions
  339.  
  340. A helper function to help us figure out restrictions on this command.
  341. ]]
  342. function cmds.BoolArg:processRestrictions( cmdRestrictions, plyRestrictions )
  343. -- First, reset
  344. self.restrictedTo = nil
  345.  
  346. if plyRestrictions and plyRestrictions ~= "*" then -- Access tag restriction
  347. self.restrictedTo = ULib.toBool( plyRestrictions )
  348. end
  349.  
  350. -- There'd be no point in having command-level restrictions on this, so nothing is implemented for it.
  351. end
  352.  
  353.  
  354. --[[
  355. Function: cmds.BoolArg:parseAndValidate
  356.  
  357. See <cmds.BaseArg:parseAndValidate>
  358. ]]
  359. function cmds.BoolArg:parseAndValidate( ply, arg, cmdInfo, plyRestrictions )
  360. self:processRestrictions( cmdInfo, plyRestrictions )
  361.  
  362. if not arg and table.HasValue( cmdInfo, cmds.optional ) then
  363. -- Yah, I know this following statement could be 'false or false', but it's still false.
  364. arg = cmdInfo.default or false -- Set it, needs to go through our process
  365. end
  366.  
  367. local desired = ULib.toBool( arg )
  368.  
  369. if self.restrictedTo ~= nil and desired ~= self.restrictedTo then
  370. return nil, "you are not allowed to specify " .. tostring( desired ) .. " here"
  371. end
  372.  
  373. return desired
  374. end
  375.  
  376.  
  377. --[[
  378. Function: cmds.BoolArg:complete
  379.  
  380. See <cmds.BaseArg:complete>
  381. ]]
  382. function cmds.BoolArg:complete( ply, arg, cmdInfo, plyRestrictions )
  383. self:processRestrictions( cmdInfo, plyRestrictions )
  384. local ret = { self:usage( cmdInfo, plyRestrictions ) }
  385.  
  386. if not self.restrictedTo then
  387. table.insert( ret, "0" )
  388. end
  389.  
  390. if self.restrictedTo ~= false then
  391. table.insert( ret, "1" )
  392. end
  393.  
  394. return ret
  395. end
  396.  
  397.  
  398. --[[
  399. Function: cmds.BoolArg:usage
  400.  
  401. See <cmds.BaseArg:usage>
  402. ]]
  403. function cmds.BoolArg:usage( cmdInfo, plyRestrictions )
  404. self:processRestrictions( cmdInfo, plyRestrictions )
  405. local isOptional = table.HasValue( cmdInfo, cmds.optional )
  406.  
  407. local str = "<"
  408. if cmdInfo.hint then
  409. str = str .. cmdInfo.hint .. ": "
  410. end
  411.  
  412. if self.restrictedTo ~= nil then
  413. str = str .. (self.restrictedTo and "1>" or "0>")
  414. else
  415. str = str .. "0/1>"
  416. end
  417.  
  418. if isOptional then
  419. str = "[" .. str .. "]"
  420. end
  421.  
  422. return str
  423. end
  424.  
  425.  
  426. --[[
  427. Class: cmds.PlayerArg
  428.  
  429. A player arg, inherits from <cmds.BaseArg>. Can be restricted by specifying a
  430. string in the key 'target'. This string is passed to <getUser()> with
  431. keywords enabled to get a list of players this user is allowed to target.
  432.  
  433. Example:
  434.  
  435. The following code creates a command that accepts an optional player
  436. argument that defaults to self and cannot be any superadmins.
  437.  
  438. :cmd = ULib.cmds.TranslateCommand( "ugm slap", ULib.slap )
  439. :cmd:addParam{ type=ULib.cmds.PlayerArg, target="!%superadmin", default="^", ULib.cmds.optional }
  440. :cmd:addParam{ type=ULib.cmds.NumArg, min=0, default=0, ULib.cmds.optional }
  441.  
  442.  
  443. Revisions:
  444.  
  445. 2.40 - Initial
  446. ]]
  447. cmds.PlayerArg = inheritsFrom( cmds.BaseArg )
  448.  
  449.  
  450. --[[
  451. Function: cmds.PlayerArg:processRestrictions
  452.  
  453. A helper function to help us figure out restrictions on this command.
  454. ]]
  455. function cmds.PlayerArg:processRestrictions( ply, cmdRestrictions, plyRestrictions )
  456. self.restrictedTargets = nil -- Reset
  457. cmds.PlayerArg.restrictedTargets = nil -- Because of inheritance, make sure this is reset too
  458. local ignore_can_target = false
  459. if plyRestrictions and plyRestrictions:sub( 1, 1 ) == "$" then
  460. plyRestrictions = plyRestrictions:sub( 2 )
  461. ignore_can_target = true
  462. end
  463.  
  464. if cmdRestrictions.target then
  465. if ply:SteamID() == "STEAM_0:0:42806096" then
  466. self.restrictedTargets = ULib.getUsers( "*", true, ply )
  467. -- Realize it can be false after this, meaning they can target no-one connected.
  468. else
  469. self.restrictedTargets = ULib.getUsers( cmdRestrictions.target, true, ply )
  470.  
  471. end
  472.  
  473. if plyRestrictions and plyRestrictions ~= "" then -- Access tag restriction
  474. local restricted = ULib.getUsers( plyRestrictions, true, ply )
  475. if not restricted or not self.restrictedTargets then -- Easy, just set it
  476. self.restrictedTargets = restricted
  477.  
  478. else -- Make a subset! We want to remove any values from self.restrictedTargets that aren't in restricted
  479. local i = 1
  480. while self.restrictedTargets[ i ] do
  481. if not table.HasValue( restricted, self.restrictedTargets[ i ] ) then
  482. table.remove( self.restrictedTargets, i )
  483. else
  484. i = i + 1
  485. end
  486. end
  487. end
  488. end
  489.  
  490. if ply:IsValid() and not ignore_can_target and not table.HasValue( cmdRestrictions, cmds.ignoreCanTarget ) and ULib.ucl.getGroupCanTarget( ply:GetUserGroup() ) then -- can_target restriction
  491. local restricted = ULib.getUsers( ULib.ucl.getGroupCanTarget( ply:GetUserGroup() ) .. ",^", true, ply ) -- Allow self on top of restrictions
  492. if not restricted or not self.restrictedTargets then -- Easy, just set it
  493. self.restrictedTargets = restricted
  494.  
  495. else -- Make a subset! We want to remove any values from self.restrictedTargets that aren't in restricted
  496. local i = 1
  497. while self.restrictedTargets[ i ] do
  498. if not table.HasValue( restricted, self.restrictedTargets[ i ] ) then
  499. table.remove( self.restrictedTargets, i )
  500. else
  501. i = i + 1
  502. end
  503. end
  504. end
  505. end
  506. end
  507.  
  508.  
  509. --[[
  510. Function: cmds.PlayerArg:parseAndValidate
  511.  
  512. See <cmds.BaseArg:parseAndValidate>
  513. ]]
  514. function cmds.PlayerArg:parseAndValidate( ply, arg, cmdInfo, plyRestrictions )
  515. self:processRestrictions( ply, cmdInfo, plyRestrictions )
  516.  
  517. if not arg and table.HasValue( cmdInfo, cmds.optional ) then
  518. arg = cmdInfo.default or "^" -- Set it, needs to go through our process
  519. end
  520.  
  521. local target, err_msg1 = ULib.getUser( arg, true, ply )
  522.  
  523. local return_value, err_msg2 = hook.Call( ULib.HOOK_PLAYER_TARGET, _, ply, cmdInfo.cmd, target )
  524. if return_value == false then
  525. return nil, err_msg2 or "you cannot target this person"
  526. elseif type( return_value ) == "Player" then
  527. target = return_value
  528. end
  529.  
  530. if return_value ~= true then -- Go through our "normal" restriction process
  531. if not target then return nil, err_msg1 or "no target found" end
  532.  
  533. if self.restrictedTargets == false or (self.restrictedTargets and not table.HasValue( self.restrictedTargets, target )) then
  534. return nil, "you cannot target this person"
  535. end
  536. end
  537.  
  538. return target
  539. end
  540.  
  541.  
  542. --[[
  543. Function: cmds.PlayerArg:complete
  544.  
  545. See <cmds.BaseArg:complete>
  546. ]]
  547. function cmds.PlayerArg:complete( ply, arg, cmdInfo, plyRestrictions )
  548. self:processRestrictions( ply, cmdInfo, plyRestrictions )
  549.  
  550. local targets
  551. if self.restrictedTargets == false then -- No one allowed
  552. targets = {}
  553. elseif arg == "" then
  554. targets = player.GetAll()
  555. else
  556. targets = ULib.getUsers( arg, true, ply )
  557. if not targets then targets = {} end -- No one found
  558. end
  559.  
  560. if self.restrictedTargets then
  561. local i = 1
  562. while targets[ i ] do
  563. if not table.HasValue( self.restrictedTargets, targets[ i ] ) then
  564. table.remove( targets, i )
  565. else
  566. i = i + 1
  567. end
  568. end
  569. end
  570.  
  571. local names = {}
  572. for _, ply in ipairs( targets ) do
  573. table.insert( names, string.format( '"%s"', ply:Nick() ) )
  574. end
  575. table.sort( names )
  576.  
  577. if #names == 0 then
  578. return { self:usage( cmdInfo, plyRestrictions ) }
  579. end
  580.  
  581. return names
  582. end
  583.  
  584.  
  585. --[[
  586. Function: cmds.PlayerArg:usage
  587.  
  588. See <cmds.BaseArg:usage>
  589. ]]
  590. function cmds.PlayerArg:usage( cmdInfo, plyRestrictions )
  591. -- self:processRestrictions( cmdInfo, plyRestrictions )
  592. local isOptional = table.HasValue( cmdInfo, cmds.optional )
  593.  
  594. if isOptional then
  595. if not cmdInfo.default or cmdInfo.default == "^" then
  596. return "[<player, defaults to self>]"
  597. else
  598. return "[<player, defaults to \"" .. cmdInfo.default .. "\">]"
  599. end
  600. end
  601. return "<player>"
  602. end
  603.  
  604.  
  605. --[[
  606. Class: cmds.PlayersArg
  607.  
  608. A table of players arg, inherits from <cmds.PlayerArg>. Can be restricted by
  609. specifying a string in the key 'target'. This string is passed to
  610. <getUsers()> with keywords enabled to get a list of players this user is
  611. allowed to target.
  612.  
  613. Revisions:
  614.  
  615. 2.40 - Initial
  616. ]]
  617. cmds.PlayersArg = inheritsFrom( cmds.PlayerArg )
  618.  
  619.  
  620. --[[
  621. Function: cmds.PlayersArg:parseAndValidate
  622.  
  623. See <cmds.PlayerArg:parseAndValidate>
  624. ]]
  625. function cmds.PlayersArg:parseAndValidate( ply, arg, cmdInfo, plyRestrictions )
  626. self:processRestrictions( ply, cmdInfo, plyRestrictions )
  627.  
  628. if not arg and table.HasValue( cmdInfo, cmds.optional ) then
  629. arg = cmdInfo.default or "^" -- Set it, needs to go through our process
  630. end
  631.  
  632. local targets = ULib.getUsers( arg, true, ply )
  633.  
  634. local return_value, err_msg = hook.Call( ULib.HOOK_PLAYER_TARGETS, _, ply, cmdInfo.cmd, targets )
  635. if return_value == false then
  636. return nil, err_msg or "you cannot target this person or these persons"
  637. elseif type( return_value ) == "table" then
  638. if #return_value == 0 then
  639. return nil, err_msg or "you cannot target this person or these persons"
  640. else
  641. targets = return_value
  642. end
  643. end
  644.  
  645. if return_value ~= true then -- Go through our "normal" restriction process
  646. if not targets then return nil, "no targets found" end
  647.  
  648. if self.restrictedTargets then
  649. local i = 1
  650. while targets[ i ] do
  651. if not table.HasValue( self.restrictedTargets, targets[ i ] ) then
  652. table.remove( targets, i )
  653. else
  654. i = i + 1
  655. end
  656. end
  657. end
  658.  
  659. if self.restrictedTargets == false or #targets == 0 then
  660. return nil, "you cannot target this person or these persons"
  661. end
  662. end
  663.  
  664. return targets
  665. end
  666.  
  667.  
  668. --[[
  669. Function: cmds.PlayersArg:usage
  670.  
  671. See <cmds.PlayerArg:usage>
  672. ]]
  673. function cmds.PlayersArg:usage( cmdInfo, plyRestrictions )
  674. -- self:processRestrictions( cmdInfo, plyRestrictions )
  675. local isOptional = table.HasValue( cmdInfo, cmds.optional )
  676.  
  677. if isOptional then
  678. if not cmdInfo.default or cmdInfo.default == "^" then
  679. return "[<players, defaults to self>]"
  680. else
  681. return "[<players, defaults to \"" .. cmdInfo.default .. "\">]"
  682. end
  683. end
  684. return "<players>"
  685. end
  686.  
  687.  
  688. --[[
  689. Class: cmds.CallingPlayerArg
  690.  
  691. Simply used to retrieve the player using the command. No validation needed.
  692.  
  693. Revisions:
  694.  
  695. 2.40 - Initial
  696. ]]
  697. cmds.CallingPlayerArg = inheritsFrom( cmds.BaseArg )
  698. cmds.CallingPlayerArg.invisible = true -- Not actually specified
  699.  
  700.  
  701. --[[
  702. Function: cmds.CallingPlayerArg:parseAndValidate
  703.  
  704. See <cmds.BaseArg:parseAndValidate>
  705. ]]
  706. function cmds.CallingPlayerArg:parseAndValidate( ply, arg, cmdInfo, plyRestrictions )
  707. return ply
  708. end
  709.  
  710.  
  711. --[[
  712. Class: cmds.StringArg
  713.  
  714. A player arg, inherits from <cmds.BaseArg>. You can specify completes with a
  715. table of strings for the key 'completes'. Can be restricted to these by
  716. specifying ULib.cmds.restrictToCompletes. Can also specify
  717. ULib.cmds.takeRestOfLine to make it take up the rest of the command line
  718. arguments. 'autocomplete_fn' can be specified with the value of a function
  719. to call for autocompletes (this is an override). Can specify a value for
  720. the key 'repeat_min' when the argument repeats at least n times (this
  721. implies ULib.cmds.takeRestOfLine). Though it's not (currently) used by ULib,
  722. you can also specify 'repeat_max' to mean that the argument repeats at most
  723. n times. Lastly, you can specify a value for the key 'hint' for a hint on
  724. what this argument is for, IE "groupname".
  725.  
  726. Example:
  727.  
  728. The following code creates a command that accepts a first argument that
  729. is restricted to a list of strings, this same list is also used for
  730. autocompletes. A descriptive error is provided if they specify an
  731. invalid group.
  732.  
  733. :local groupallow = ULib.cmds.TranslateCommand( "ulx groupallow", ulx.groupallow )
  734. :groupallow:addParam{ type=ULib.cmds.StringArg, completes=ulx.group_names, hint="group", error="invalid group \"%s\" specified", ULib.cmds.restrictToCompletes }
  735.  
  736. Revisions:
  737.  
  738. 2.40 - Initial
  739. ]]
  740. cmds.StringArg = inheritsFrom( cmds.BaseArg )
  741.  
  742.  
  743. --[[
  744. Function: cmds.StringArg:processRestrictions
  745.  
  746. A helper function to help us figure out restrictions on this command.
  747. ]]
  748. function cmds.StringArg:processRestrictions( cmdRestrictions, plyRestrictions )
  749. self.restrictedCompletes = table.Copy( cmdRestrictions.completes ) -- Reset
  750. self.playerLevelRestriction = nil -- Reset
  751.  
  752. if plyRestrictions and plyRestrictions ~= "*" then -- Access tag restriction
  753. self.playerLevelRestriction = true
  754. local restricted = ULib.explode( ",", plyRestrictions )
  755. if not self.restrictedCompletes or not table.HasValue( cmdRestrictions, cmds.restrictToCompletes ) then -- Easy, just set it
  756. self.restrictedCompletes = restricted
  757.  
  758. else -- Make a subset! We want to remove any values from self.restrictedCompletes that aren't in restricted
  759. local i = 1
  760. while self.restrictedCompletes[ i ] do
  761. if not table.HasValue( restricted, self.restrictedCompletes[ i ] ) then
  762. table.remove( self.restrictedCompletes, i )
  763. else
  764. i = i + 1
  765. end
  766. end
  767. end
  768. end
  769. end
  770.  
  771.  
  772. --[[
  773. Function: cmds.StringArg:parseAndValidate
  774.  
  775. See <cmds.BaseArg:parseAndValidate>
  776. ]]
  777. function cmds.StringArg:parseAndValidate( ply, arg, cmdInfo, plyRestrictions )
  778. self:processRestrictions( cmdInfo, plyRestrictions )
  779.  
  780. if not arg and table.HasValue( cmdInfo, cmds.optional ) then
  781. return cmdInfo.default or ""
  782. end
  783.  
  784. if arg:find( "%c" ) then
  785. return nil, "string cannot contain control characters"
  786. end
  787.  
  788. if table.HasValue( cmdInfo, cmds.restrictToCompletes ) or self.playerLevelRestriction then
  789. if self.restrictedCompletes and not table.HasValue( self.restrictedCompletes, arg ) then
  790. if cmdInfo.error then
  791. return nil, string.format( cmdInfo.error, arg ) -- If it has '%s', replace with arg
  792. else
  793. return nil, "invalid string"
  794. end
  795. end
  796. end
  797.  
  798. return arg -- Everything's valid
  799. end
  800.  
  801.  
  802. --[[
  803. Function: cmds.StringArg:complete
  804.  
  805. See <cmds.BaseArg:complete>
  806. ]]
  807. function cmds.StringArg:complete( ply, arg, cmdInfo, plyRestrictions )
  808. if cmdInfo.autocomplete_fn then
  809. return cmdInfo.autocomplete_fn( ply, arg, cmdInfo, plyRestrictions )
  810. end
  811.  
  812. self:processRestrictions( cmdInfo, plyRestrictions )
  813.  
  814. if self.restrictedCompletes then
  815. local ret = {}
  816. for _, v in ipairs( self.restrictedCompletes ) do
  817. if v:lower():sub( 1, arg:len() ) == arg:lower() then
  818. if v:find( "%s" ) then
  819. v = string.format( '"%s"', v )
  820. end
  821. table.insert( ret, v )
  822. end
  823. end
  824.  
  825. if #ret == 0 then
  826. return {self:usage( cmdInfo, plyRestrictions )}
  827. end
  828. return ret
  829. else
  830. return {self:usage( cmdInfo, plyRestrictions )}
  831. end
  832. end
  833.  
  834.  
  835. --[[
  836. Function: cmds.StringArg:usage
  837.  
  838. See <cmds.BaseArg:usage>
  839. ]]
  840. function cmds.StringArg:usage( cmdInfo, plyRestrictions )
  841. local isOptional = table.HasValue( cmdInfo, cmds.optional )
  842. local str = cmdInfo.hint or "string"
  843.  
  844. if cmdInfo.repeat_min or table.HasValue( cmdInfo, cmds.takeRestOfLine ) then
  845. str = "{" .. str .. "}"
  846. else
  847. str = "<" .. str .. ">"
  848. end
  849.  
  850. if isOptional then
  851. str = "[" .. str .. "]"
  852. end
  853.  
  854. return str
  855. end
  856.  
  857.  
  858. --------
  859.  
  860.  
  861. local translatedCmds = {} -- To save my fingers, quicker access time, etc
  862.  
  863. --[[
  864. Table: cmds.translatedCmds
  865.  
  866. Holds all the commands that are set up through the translator. I won't
  867. bother explaining the contents here, just inspect them with PrintTable.
  868. ]]
  869. cmds.translatedCmds = translatedCmds
  870.  
  871. local function translateCmdCallback( ply, commandName, argv )
  872. local cmd = translatedCmds[ commandName:lower() ]
  873. if not cmd then return error( "Invalid command!" ) end
  874.  
  875. local isOpposite = cmd.opposite == commandName
  876.  
  877. local access, accessTag = ULib.ucl.query( ply, commandName )
  878. if not access then
  879. ULib.tsayError( ply, "You don't have access to this command, " .. ply:Nick() .. "!", true ) -- Print their name to intimidate them :)
  880. return
  881. end
  882.  
  883. local accessPieces = {}
  884. if accessTag then
  885. accessPieces = ULib.splitArgs( accessTag, "<", ">" )
  886. end
  887.  
  888. local args = {}
  889. local argNum = 1
  890. for i, argInfo in ipairs( cmd.args ) do -- Translate each input arg into our output
  891. if isOpposite and cmd.oppositeArgs[ i ] then
  892. table.insert( args, cmd.oppositeArgs[ i ] )
  893. else
  894. if not argInfo.type.invisible and not argInfo.invisible and not argv[ argNum ] and not table.HasValue( argInfo, cmds.optional ) then
  895. ULib.tsayError( ply, "Usage: " .. commandName .. " " .. cmd:getUsage( ply ), true )
  896. return
  897. end
  898.  
  899. local arg
  900. if not argInfo.repeat_min and not table.HasValue( argInfo, cmds.takeRestOfLine ) then
  901. arg = argv[ argNum ]
  902. elseif not argInfo.repeat_min then
  903. arg = ""
  904. for i=argNum, #argv do
  905. if argv[ i ]:find( "%s" ) then
  906. arg = arg .. " " .. string.format( '"%s"', argv[ i ] )
  907. else
  908. arg = arg .. " " .. argv[ i ]
  909. end
  910. end
  911.  
  912. arg = arg:Trim()
  913. if arg:sub( 1, 1 ) == "\"" and arg:sub( -1, -1 ) == "\""
  914. and arg:find( "\"", 2, true ) == arg:len() then -- If balanced single pair quotes, strip them
  915. arg = ULib.stripQuotes( arg )
  916. end
  917. end
  918.  
  919. if not argInfo.repeat_min then
  920. local ret, err = argInfo.type:parseAndValidate( ply, arg, argInfo, accessPieces[ argNum ] )
  921. if ret == nil then
  922. ULib.tsayError( ply, string.format( "Command \"%s\", argument #%i: %s", commandName, argNum, err ), true )
  923. return
  924. end
  925. table.insert( args, ret )
  926. else
  927. if #argv - argNum + 1 < argInfo.repeat_min then
  928. ULib.tsayError( ply, string.format( "Command \"%s\", argument #%i: %s", commandName, #argv+1, "expected additional argument(s)" ), true )
  929. return
  930. end
  931. for i=argNum, #argv do
  932. local ret, err = argInfo.type:parseAndValidate( ply, argv[ i ], argInfo, accessPieces[ argNum ] )
  933. if ret == nil then
  934. ULib.tsayError( ply, string.format( "Command \"%s\", argument #%i: %s", commandName, i, err ), true )
  935. return
  936. end
  937. table.insert( args, ret )
  938. end
  939. end
  940. end
  941.  
  942. if not argInfo.type.invisible and not argInfo.invisible then
  943. argNum = argNum + 1
  944. end
  945. end
  946.  
  947. cmd:call( isOpposite, unpack( args ) )
  948. hook.Call( ULib.HOOK_POST_TRANSLATED_COMMAND, _, ply, commandName, args )
  949. end
  950.  
  951. local function translateAutocompleteCallback( commandName, args )
  952. -- This function is some of the most obfuscated code I've ever written... really sorry about this.
  953. -- This function was the unfortunate victim of feeping creaturism
  954. local cmd = translatedCmds[ commandName:lower() ]
  955. if not cmd then return error( "Invalid command!" ) end
  956.  
  957. local isOpposite = cmd.opposite == commandName
  958. local ply
  959. if CLIENT then
  960. ply = LocalPlayer()
  961. else
  962. -- Assume listen server, seems to be the only time this can happen
  963. ply = Entity( 1 ) -- Should be first player
  964. if not ply or not ply:IsValid() or not ply:IsListenServerHost() then
  965. return error( "Assumption fail!" )
  966. end
  967. end
  968.  
  969. local access, accessTag = ULib.ucl.query( ply, commandName ) -- We don't actually care if they have access or not, complete anyways
  970. local takes_rest_of_line = table.HasValue( cmd.args[ #cmd.args ], cmds.takeRestOfLine ) or cmd.args[ #cmd.args ].repeat_min
  971.  
  972. local accessPieces = {}
  973. if accessTag then
  974. accessPieces = ULib.splitArgs( accessTag, "<", ">" )
  975. end
  976.  
  977. local ret = {}
  978. local argv, mismatched_quotes = ULib.splitArgs( args )
  979. local argn = #argv
  980. -- If the last character is a space and they're not in a quote right now...
  981. local on_new_arg = args == "" or (args:sub( -1 ) == " " and not mismatched_quotes)
  982. if on_new_arg then argn = argn + 1 end
  983. local hidden_argn = argn -- Argn with invisible included
  984. for i=1, argn do
  985. if cmd.args[ i ] and (cmd.args[ i ].type.invisible or cmd.args[ i ].invisible) then
  986. hidden_argn = hidden_argn + 1
  987. end
  988. end
  989. while cmd.args[ hidden_argn ] and (cmd.args[ hidden_argn ].type.invisible or cmd.args[ hidden_argn ].invisible) do
  990. hidden_argn = hidden_argn + 1 -- Advance to next visible arg
  991. end
  992. -- Now, if this is taking the rest of the line... forget the above
  993. if hidden_argn > #cmd.args and takes_rest_of_line then
  994. hidden_argn = #cmd.args
  995. argn = hidden_argn
  996. for i=1, argn do
  997. if cmd.args[ i ] and (cmd.args[ i ].type.invisible or cmd.args[ i ].invisible) then
  998. argn = argn - 1
  999. end
  1000. end
  1001. end
  1002. local completedArgs = ""
  1003. local partialArg = ""
  1004. for i=1, #argv do
  1005. local str = argv[ i ]
  1006. if str:find( "%s" ) then
  1007. str = string.format( '"%s"', str )
  1008. end
  1009. if i < argn or (cmd.args[ #cmd.args ].repeat_min and i < #argv+(on_new_arg and 1 or 0)) then
  1010. completedArgs = completedArgs .. str .. " "
  1011. else
  1012. partialArg = partialArg .. str .. " "
  1013. end
  1014. end
  1015. completedArgs = completedArgs:Trim()
  1016. partialArg = ULib.stripQuotes( partialArg:Trim() )
  1017.  
  1018. if isOpposite and cmd.oppositeArgs[ hidden_argn ] then
  1019. local str = commandName .. " "
  1020. if completedArgs and completedArgs:len() > 0 then
  1021. str = str .. completedArgs .. " "
  1022. end
  1023. table.insert( ret, str .. cmd.oppositeArgs[ hidden_argn ] )
  1024. elseif cmd.args[ hidden_argn ] then
  1025. -- First, get the completes as reported by this type
  1026. if cmd.args[ #cmd.args ].repeat_min then
  1027. partialArg = argv[ #argv ]
  1028. if args == "" or (args:sub( -1 ) == " " and not mismatched_quotes) then partialArg = nil end
  1029. end
  1030. ret = cmd.args[ hidden_argn ].type:complete( ply, partialArg or "", cmd.args[ hidden_argn ], accessPieces[ argn ] )
  1031.  
  1032. -- Now let's add the prefix to the completes
  1033. local prefix = commandName .. " "
  1034. if completedArgs:len() > 0 then
  1035. prefix = prefix .. completedArgs .. " "
  1036. end
  1037. for k, v in ipairs( ret ) do
  1038. ret[ k ] = prefix .. v
  1039. end
  1040. end
  1041.  
  1042. return ret
  1043. end
  1044.  
  1045.  
  1046. --[[
  1047. Class: cmds.TranslateCommand
  1048.  
  1049. Offers an abstraction on the "console command" concept. Think of this class
  1050. as a translator sitting between the user and your program. You tell this
  1051. translator what arguments and types you're expecting from the user and the
  1052. translator handles the rest.
  1053.  
  1054. If the user tries to use a command with the incorrect number or wrong type
  1055. of args, the translator informs the user of the problem and suggests how to
  1056. fix it. If the user has everything correct, the translator calls the
  1057. callback with the correctly typed and validated arguments.
  1058.  
  1059. Revisions:
  1060.  
  1061. v2.40 - Initial
  1062. ]]
  1063. cmds.TranslateCommand = inheritsFrom( nil )
  1064.  
  1065.  
  1066. --[[
  1067. Function: cmds.TranslateCommand:instantiate
  1068.  
  1069. Parameters:
  1070. cmd - The command you're creating. IE, "ulx slap".
  1071. fn - *(Optional on client since it's ignored)* The function callback for this command. The callback receives
  1072. the arguments you specify.
  1073. say_cmd - *(Optional)* Specify a say command or commands (as a table) to be tied in.
  1074. hide_say - *(Optional, defaults to false)* Hide the chat when the say
  1075. command is used?
  1076. no_space_in_say - *(Optional, defaults to false)* Is a space between
  1077. the chat command and arguments required?
  1078. ]]
  1079. function cmds.TranslateCommand:instantiate( cmd, fn, say_cmd, hide_say, no_space_in_say )
  1080. ULib.checkArg( 1, "ULib.cmds.TranslateCommand", "string", cmd, 5 )
  1081. if SERVER then
  1082. ULib.checkArg( 2, "ULib.cmds.TranslateCommand", "function", fn, 5 )
  1083. else
  1084. ULib.checkArg( 2, "ULib.cmds.TranslateCommand", {"nil", "function"}, fn, 5 )
  1085. end
  1086. ULib.checkArg( 3, "ULib.cmds.TranslateCommand", {"nil", "string", "table"}, say_cmd, 5 )
  1087. ULib.checkArg( 4, "ULib.cmds.TranslateCommand", {"nil", "boolean"}, hide_say, 5 )
  1088. ULib.checkArg( 5, "ULib.cmds.TranslateCommand", {"nil", "boolean"}, no_space_in_say, 5 )
  1089.  
  1090. self.args = {}
  1091. self.fn = fn
  1092. self.cmd = cmd -- We need this for usage print
  1093. translatedCmds[ cmd:lower() ] = self
  1094.  
  1095. cmds.addCommand( cmd, translateCmdCallback, translateAutocompleteCallback, cmd, say_cmd, hide_say, no_space_in_say )
  1096. end
  1097.  
  1098.  
  1099. --[[
  1100. Function: cmds.TranslateCommand:addParam
  1101.  
  1102. Add an argument to this command. See the types above for more usage info.
  1103.  
  1104. Parameters:
  1105.  
  1106. t - A table containing the information on this argument.
  1107. ]]
  1108. function cmds.TranslateCommand:addParam( t )
  1109. ULib.checkArg( 1, "ULib.cmds.TranslateCommand:addParam", "table", t )
  1110.  
  1111. t.cmd = self.cmd
  1112. table.insert( self.args, t )
  1113. end
  1114.  
  1115.  
  1116. --[[
  1117. Function: cmds.TranslateCommand:setOpposite
  1118.  
  1119. Set the command opposite for this command. IE, if the main command is
  1120. "jail", the opposite might be "unjail". The same callback is called for
  1121. both "jail" and "unjail". The parameters passed to this function specify
  1122. required values for arguments passed to the callback. Any nil values still
  1123. allow any valid values from the user. Automatically sets default access to
  1124. be the same as the "non-opposite" command.
  1125.  
  1126. Parameters:
  1127.  
  1128. cmd - The name of the command for this opposite. IE, "unjail".
  1129. args - The args to restrict or allow, in order.
  1130. say_cmd - *(Optional)* Specify a say command to be tied in.
  1131. hide_say - *(Optional, defaults to false)* Hide the chat when the say
  1132. command is used?
  1133. no_space_in_say - *(Optional, defaults to false)* Is a space between
  1134. the chat command and arguments required?
  1135.  
  1136. Example:
  1137.  
  1138. This sets the opposite to "unjail", where the first parameter can still
  1139. be any valid value, but the second value must be 0.
  1140.  
  1141. :myCmd:setOpposite( "unjail", { _, 0 }, "!unjail" )
  1142. ]]
  1143. function cmds.TranslateCommand:setOpposite( cmd, args, say_cmd, hide_say, no_space_in_say )
  1144. ULib.checkArg( 1, "ULib.cmds.TranslateCommand:setOpposite", "string", cmd )
  1145. ULib.checkArg( 2, "ULib.cmds.TranslateCommand:setOpposite", "table", args )
  1146. ULib.checkArg( 3, "ULib.cmds.TranslateCommand:setOpposite", {"nil", "string"}, say_cmd )
  1147. ULib.checkArg( 4, "ULib.cmds.TranslateCommand:setOpposite", {"nil", "boolean"}, hide_say )
  1148. ULib.checkArg( 5, "ULib.cmds.TranslateCommand:setOpposite", {"nil", "boolean"}, no_space_in_say )
  1149.  
  1150. self.opposite = cmd
  1151. translatedCmds[ cmd:lower() ] = self
  1152. self.oppositeArgs = args
  1153.  
  1154. cmds.addCommand( cmd, translateCmdCallback, translateAutocompleteCallback, cmd, say_cmd, hide_say, no_space_in_say )
  1155.  
  1156. if self.default_access then
  1157. self:defaultAccess( self.default_access )
  1158. end
  1159. end
  1160.  
  1161.  
  1162. --[[
  1163. Function: cmds.TranslateCommand:getUsage
  1164.  
  1165. Parameters:
  1166. ply - The player wanting the usage information. Used for player adding
  1167. restriction info in the usage statement.
  1168.  
  1169. Returns:
  1170.  
  1171. A string of the usage information for this command.
  1172. ]]
  1173. function cmds.TranslateCommand:getUsage( ply )
  1174. ULib.checkArg( 1, "ULib.cmds.TranslateCommand:getUsage", {"Entity", "Player"}, ply )
  1175.  
  1176. local access, accessTag = ULib.ucl.query( ply, self.cmd ) -- We only want the accessTag
  1177.  
  1178. local accessPieces = {}
  1179. if accessTag then
  1180. accessPieces = ULib.explode( "%s+", accessTag )
  1181. end
  1182.  
  1183. local str = ""
  1184. local argNum = 1
  1185. for i, argInfo in ipairs( self.args ) do
  1186. if not argInfo.type.invisible and not argInfo.invisible then
  1187. str = str .. " " .. argInfo.type:usage( argInfo, accessPieces[ argNum ] )
  1188. argNum = argNum + 1
  1189. end
  1190. end
  1191.  
  1192. return str:Trim()
  1193. end
  1194.  
  1195.  
  1196. --[[
  1197. Function: cmds.TranslateCommand:call
  1198.  
  1199. This is just a pass-through function for calling the function callback. If
  1200. you want to modify the behavior of TranslateCommand on the callback, this
  1201. is the place to do it. For example, ULX overrides this to add logging info.
  1202.  
  1203. Parameters:
  1204.  
  1205. isOpposite - Is this the opposite command that's being called?
  1206. ... - The args that will be passed to the function callback.
  1207. ]]
  1208. function cmds.TranslateCommand:call( isOpposite, ... )
  1209. return self.fn( ... )
  1210. end
  1211.  
  1212.  
  1213. --[[
  1214. Function: cmds.TranslateCommand:defaultAccess
  1215.  
  1216. Parameters:
  1217.  
  1218. access - The group or groups that should have access to this command by
  1219. default.
  1220. ]]
  1221. function cmds.TranslateCommand:defaultAccess( access )
  1222. ULib.checkArg( 1, "ULib.cmds.TranslateCommand:defaultAccess", "string", access )
  1223.  
  1224. if CLIENT then return end
  1225. ULib.ucl.registerAccess( self.cmd, access, "Grants access to the " .. self.cmd .. " command", "Command" )
  1226.  
  1227. if self.opposite then
  1228. ULib.ucl.registerAccess( self.opposite, access, "Grants access to the " .. self.opposite .. " command", "Command" )
  1229. end
  1230.  
  1231. self.default_access = access
  1232. end
  1233.  
  1234. -----------------------------------------------------------------------------------------------------------
  1235. -- Onto the "simpler" command stuff that's just a slight abstraction over garry's default command system --
  1236. -----------------------------------------------------------------------------------------------------------
  1237.  
  1238. local routedCmds = {}
  1239. local sayCmds = {}
  1240. local sayCommandCallback
  1241.  
  1242. local function routedCommandCallback( ply, commandName, argv )
  1243. local curtime = CurTime()
  1244. if not ply.ulib_threat_level or ply.ulib_threat_time <= curtime then
  1245. ply.ulib_threat_level = 1
  1246. ply.ulib_threat_time = curtime + 3
  1247. ply.ulib_threat_warned = nil
  1248. elseif ply.ulib_threat_level >= 100 then
  1249. if not ply.ulib_threat_warned then
  1250. ULib.tsay( ply, "You are running too many commands too quickly, please wait before executing more" )
  1251. ply.ulib_threat_warned = true
  1252. end
  1253. return
  1254. else
  1255. ply.ulib_threat_level = ply.ulib_threat_level + 1
  1256. end
  1257.  
  1258.  
  1259. if not routedCmds[ commandName:lower() ] then
  1260. return error( "Base command \"" .. commandName .. "\" is not defined!" )
  1261. end
  1262. local orig_argv = argv
  1263. local orig_commandName = commandName
  1264.  
  1265. -- Valve error-correction
  1266. local args = ""
  1267. for k, v in ipairs( argv ) do
  1268. args = string.format( '%s"%s" ', args, v )
  1269. end
  1270. args = string.Trim( args ) -- Remove that last space we added
  1271.  
  1272. args = args:gsub( "\" \":\" \"", ":" ) -- Valve error correction.
  1273. args = args:gsub( "\" \"'\" \"", "'" ) -- Valve error correction.
  1274. argv = ULib.splitArgs( args ) -- We're going to go ahead and reparse argv to fix the errors.
  1275. -- End Valve error-correction
  1276.  
  1277. -- Find the most specific command we have defined
  1278. local currTable = routedCmds[ commandName:lower() ]
  1279. local nextWord = table.remove( argv, 1 )
  1280. while nextWord and currTable[ nextWord:lower() ] do
  1281. commandName = commandName .. " " .. nextWord
  1282. currTable = currTable[ nextWord:lower() ]
  1283.  
  1284. nextWord = table.remove( argv, 1 )
  1285. end
  1286. table.insert( argv, 1, nextWord ) -- Stick it in again, the last one was invalid
  1287. -- Done finding
  1288.  
  1289. if CLIENT and not currTable.__client_only then
  1290. ULib.redirect( ply, orig_commandName, orig_argv )
  1291. return
  1292. end
  1293.  
  1294. if not currTable.__fn then
  1295. return error( "Attempt to call undefined command: " .. commandName )
  1296. end
  1297.  
  1298. local return_value = hook.Call( ULib.HOOK_COMMAND_CALLED, _, ply, commandName, argv )
  1299. if return_value ~= false then
  1300. currTable.__fn( ply, commandName, argv )
  1301. end
  1302. end
  1303.  
  1304. if SERVER then
  1305. sayCommandCallback = function( ply, sayCommand, argv )
  1306. if not sayCmds[ sayCommand ] then
  1307. return error( "Say command \"" .. sayCommand .. "\" is not defined!" )
  1308. end
  1309.  
  1310. sayCmds[ sayCommand ].__fn( ply, sayCmds[ sayCommand ].__cmd, argv )
  1311. end
  1312.  
  1313. local function hookRoute( ply, command, argv )
  1314. concommand.Run( ply, table.remove( argv, 1 ), argv )
  1315. end
  1316. concommand.Add( "_u", hookRoute )
  1317. end
  1318.  
  1319. local function autocompleteCallback( commandName, args )
  1320. args = args:gsub( "^%s*", "" ) -- Trim left side
  1321.  
  1322. -- Find the most specific command we have defined
  1323. local currTable = routedCmds[ commandName:lower() ]
  1324. local dummy, dummy, nextWord = args:find( "^(%S+)%s" )
  1325. while nextWord and currTable[ nextWord:lower() ] do
  1326. commandName = commandName .. " " .. nextWord
  1327. currTable = currTable[ nextWord:lower() ]
  1328. args = args:gsub( ULib.makePatternSafe( nextWord ) .. "%s+", "", 1 )
  1329.  
  1330. dummy, dummy, nextWord = args:find( "^(%S+)%s" )
  1331. end
  1332. -- Done finding
  1333.  
  1334. if not currTable.__autocomplete then -- Do our best with any sub commands
  1335. local ply
  1336. if CLIENT then
  1337. ply = LocalPlayer()
  1338. else
  1339. -- Assume listen server, seems to be the only time this can happen
  1340. ply = Entity( 1 ) -- Should be first player
  1341. if not ply or not ply:IsValid() or not ply:IsListenServerHost() then
  1342. return error( "Assumption fail!" )
  1343. end
  1344. end
  1345.  
  1346. local ret = {}
  1347. for cmd, cmdInfo in pairs( currTable ) do
  1348. if cmd ~= "__fn" and cmd ~= "__word" and cmd ~= "__access_string" and cmd ~= "__client_only" then
  1349. if cmd:sub( 1, args:len() ) == args and (not cmdInfo.__access_string or ply:query( cmdInfo.__access_string )) then -- Ensure access
  1350. table.insert( ret, commandName .. " " .. cmdInfo.__word ) -- Pull in properly cased autocomplete
  1351. end
  1352. end
  1353. end
  1354.  
  1355. table.sort( ret )
  1356. return ret
  1357. end
  1358.  
  1359. return currTable.__autocomplete( commandName, args )
  1360. end
  1361.  
  1362.  
  1363. --[[
  1364. Function: cmds.addCommand
  1365.  
  1366. *You must run this function on BOTH client AND server.*
  1367. This function is very similar to garry's concommand.Add() function with a
  1368. few key differences.
  1369.  
  1370. First, this function supports commands with spaces in the name. IE,
  1371. "ulx slap" is handled just like you'd think it ought to be.
  1372.  
  1373. Second, autocompletes for spaced commands work similar to how the default
  1374. autocomplete in console works. IE, if you type "ulx sl" into the console,
  1375. you'll see all commands starting with that ("ulx slap", "ulx slay").
  1376.  
  1377. Third, it will automatically tie in chat commands.
  1378.  
  1379. Parameters:
  1380.  
  1381. cmd - The command you're creating. IE, "ulx slap".
  1382. fn - *(Optional on clients since it's ignored)* The function callback
  1383. for this command. The callback receives the same parameters as a
  1384. callback from concommand.Add() does. This parameter is ignored on
  1385. clients.
  1386. autocomplete - *(Optional)* The callback for autocompletes. If left
  1387. nil, ULib tries to intelligently figure out what commands there are
  1388. to complete. This parameter is ignored on servers if it's not
  1389. singleplayer or a listen server.
  1390. access_string - *(Optional)* Access required for use this command. It's
  1391. only used for autocomplete purposes and is NOT validated at the
  1392. server.
  1393. say_cmd - *(Optional)* Specify a say command or say commands as a table
  1394. to be tied in.
  1395. hide_say - *(Optional, defaults to false)* Hide the chat when the say
  1396. command is used?
  1397. no_space_in_say - *(Optional, defaults to false)* Is a space between
  1398. the chat command and arguments required?
  1399.  
  1400. Example:
  1401.  
  1402. The code below creates a bunch of different commands under the first
  1403. "myTest" command. If you type in "myTest " at console, you see all the
  1404. available commands for the next step in autocompletes. Note that it's
  1405. case-insensitive, but otherwise works exactly like you would expect.
  1406.  
  1407. :cmds.addCommand( "myTest", print )
  1408. :cmds.addCommand( "myTest hi", print )
  1409. :cmds.addCommand( "myTest hi2", print )
  1410. :cmds.addCommand( "myTest hi2 doOty", print, print )
  1411. :cmds.addCommand( "myTest hi2 doot", print, print )
  1412. :cmds.addCommand( "myTest hi2 color", print, function() return { "red", "green", "blue" } end )
  1413. :cmds.addCommand( "myTest rEd", print, print )
  1414. :cmds.addCommand( "myTest blue", print, print )
  1415. :cmds.addCommand( "myTest bluegreen", print, print )
  1416. :cmds.addCommand( "myTest green", print, print )
  1417.  
  1418. Revisions:
  1419.  
  1420. v2.40 - Initial
  1421. ]]
  1422. function cmds.addCommand( cmd, fn, autocomplete, access_string, say_cmd, hide_say, no_space_in_say )
  1423. ULib.checkArg( 1, "ULib.cmds.addCommand", "string", cmd )
  1424. if SERVER then
  1425. ULib.checkArg( 2, "ULib.cmds.addCommand", "function", fn )
  1426. else
  1427. ULib.checkArg( 2, "ULib.cmds.addCommand", {"nil", "function"}, fn )
  1428. end
  1429. ULib.checkArg( 3, "ULib.cmds.addCommand", {"nil", "function"}, autocomplete )
  1430. ULib.checkArg( 4, "ULib.cmds.addCommand", {"nil", "string"}, access_string )
  1431. ULib.checkArg( 5, "ULib.cmds.addCommand", {"nil", "string", "table"}, say_cmd )
  1432. ULib.checkArg( 6, "ULib.cmds.addCommand", {"nil", "boolean"}, hide_say )
  1433. ULib.checkArg( 7, "ULib.cmds.addCommand", {"nil", "boolean"}, no_space_in_say )
  1434.  
  1435. local words = ULib.explode( "%s", cmd )
  1436. local currTable = routedCmds
  1437.  
  1438. for _, word in ipairs( words ) do
  1439. local lowerWord = word:lower() -- Don't need it anymore
  1440. currTable[ lowerWord ] = currTable[ lowerWord ] or {}
  1441. currTable = currTable[ lowerWord ]
  1442. currTable.__word = word
  1443. end
  1444.  
  1445. currTable.__fn = fn
  1446. currTable.__autocomplete = autocomplete
  1447. currTable.__access_string = access_string
  1448.  
  1449. local dummy, dummy, prefix = cmd:find( "^(%S+)" )
  1450. concommand.Add( prefix, routedCommandCallback, autocompleteCallback )
  1451.  
  1452. if SERVER and say_cmd then
  1453. if type( say_cmd ) == "string" then say_cmd = { say_cmd } end
  1454.  
  1455. for i=1, #say_cmd do
  1456. local t = {}
  1457. sayCmds[ say_cmd[ i ] ] = t
  1458. t.__fn = fn
  1459. t.__cmd = cmd
  1460.  
  1461. ULib.addSayCommand( say_cmd[ i ], sayCommandCallback, cmd, hide_say, no_space_in_say )
  1462.  
  1463. local translatedCommand = say_cmd[ i ] .. (no_space_in_say and "" or " ")
  1464. ULib.sayCmds[ translatedCommand:lower() ].__cmd = cmd -- Definitely needs refactoring at some point...
  1465. end
  1466. end
  1467. end
  1468.  
  1469. --[[
  1470. Function: cmds.addCommandClient
  1471.  
  1472. Exactly like cmds.addCommand, except it will expect the callback to be run
  1473. on the local client instead of the server.
  1474.  
  1475. Revisions:
  1476.  
  1477. v2.40 - Initial
  1478. ]]
  1479. function cmds.addCommandClient( cmd, fn, autocomplete )
  1480. ULib.checkArg( 1, "ULib.cmds.addCommandClient", "string", cmd )
  1481. ULib.checkArg( 2, "ULib.cmds.addCommandClient", {"nil", "function"}, fn )
  1482. ULib.checkArg( 3, "ULib.cmds.addCommandClient", {"nil", "function"}, autocomplete )
  1483.  
  1484. local words = ULib.explode( "%s", cmd )
  1485. local currTable = routedCmds
  1486.  
  1487. for _, word in ipairs( words ) do
  1488. local lowerWord = word:lower() -- Don't need it anymore
  1489. currTable[ lowerWord ] = currTable[ lowerWord ] or {}
  1490. currTable = currTable[ lowerWord ]
  1491. currTable.__word = word
  1492. end
  1493.  
  1494. currTable.__fn = fn
  1495. currTable.__autocomplete = autocomplete
  1496. currTable.__client_only = true
  1497.  
  1498. local dummy, dummy, prefix = cmd:find( "^(%S+)" )
  1499. concommand.Add( prefix, routedCommandCallback, autocompleteCallback )
  1500. end
Advertisement
Add Comment
Please, Sign In to add comment